ADR-0028: Federated identity taxonomies (ETIM как один из многих)
Status: accepted Date: 2026-04-18 Deciders: команда проекта
Контекст
При анализе поставщиков обнаружено: часть из них (Systeme Electric, DKC, КЭАЗ через отдельный каталог) отдаёт характеристики и классификацию в стандарте ETIM (International Electrotechnical Model) — отраслевой стандарт для электротехники.
Но:
- ETIM покрывает не всё. В машиностроении его нет; используются другие стандарты:
eCl@ss,UNSPSC,GS1 GTIN,OKPD2(РФ),ISO 13584 PLIB, internal industry-specific. - Часть поставщиков даёт ETIM параллельно со своими внутренними кодами; часть даёт только внутренние коды (ETM, smart-shop.pro).
- Производитель одновременно может иметь
MPN(manufacturer part number),GTIN/EAN(barcode),ETIM feature set,OKPD2 code— все они указывают на один и тот же товар.
Задача: принять ETIM как полезный sign, но не завязываться на него архитектурно. Tracium должен уметь сопоставлять товары через любую из поддерживаемых taxonomies, и добавлять новые (eCl@ss, UNSPSC) без break-change.
Решение
Federated taxonomy layer — Tracium поддерживает произвольный набор identity standards, каждый из которых — опциональный сигнал для matching + источник характеристик. ETIM — первый поддерживаемый, но не привилегированный.
1. IdentityStandard aggregate (Catalog BC, seed registry)
IdentityStandard
├── id
├── code: string // "etim", "eclass", "gs1_gtin", "unspsc", "okpd2", "mpn", ...
├── version: string? // "v7" для ETIM, "10.0.1" для eCl@ss, null для версионно-нейтральных (MPN/GTIN)
├── domain: string // "electrical" | "mechanical" | "pharma" | "universal" | "custom"
├── kind: classification | characteristic_schema | identifier | hybrid
├── description
├── official_source_url?
├── status: active | deprecated | experimental
└── lifecycle metadata
Event-sourced. Расширение — через admin UI (не ADR; низкий риск, семантика проверяется per-mapping).
Стартовый seed:
| code | version | domain | kind |
|---|---|---|---|
etim | v7 | electrical | hybrid |
etim | v6 | electrical | hybrid (для legacy поставщиков) |
gs1_gtin | — | universal | identifier |
mpn | — | universal | identifier |
okpd2 | 2014 | universal (RU) | classification |
Остальные (eclass, unspsc, iso13584) добавляются по мере появления поставщика, использующего их.
2. TaxonomyCharacteristicMapping aggregate
TaxonomyCharacteristicMapping
├── id
├── standard_ref: IdentityStandard
├── external_feature_code // EF001234 для ETIM; AD-VEV для eCl@ss; etc.
├── tracium_characteristic_key
├── unit_override?
├── value_mapping_ref? // для enum-значений (EV-codes у ETIM)
├── source, confidence
└── lifecycle: active | deprecated | superseded_by(code)
Уникальность: (standard_ref, external_feature_code). Event-sourced.
3. TaxonomyClassMapping aggregate
TaxonomyClassMapping
├── id
├── standard_ref
├── external_class_code // EC001234 ETIM; 27-01-01-01 eCl@ss
├── tracium_tags: [ClassificationTag] // multi-tag: одна external class → N наших tags
├── parent_external_class_code? // сохраняем hierarchy у standard'а
├── source, confidence
4. Пересечение стандартов
Один и тот же товар может быть описан несколькими стандартами одновременно:
- ETIM feature
EF001234→ Tracium keynominal_current_A. - eCl@ss feature
AD-VEV-00123→ Tracium keynominal_current_A.
Два TaxonomyCharacteristicMapping указывают на один и тот же tracium_characteristic_key. При матчинге это нормально: оба сигнала независимо подтверждают, что товар имеет nominal_current_A=100.
5. CanonicalProduct.external_identities[]
CanonicalProduct хранит набор external identities, которые о нём известны:
CanonicalProduct {
...
external_identities: [
{ standard_ref: etim_v7, external_code: "EC001234" }, // ETIM class
{ standard_ref: mpn, external_code: "NXM-125S" }, // Manufacturer Part Number
{ standard_ref: gs1_gtin, external_code: "4601234567890" }, // barcode
{ standard_ref: okpd2, external_code: "27.33.13.120" },
]
}
Это не identity_signature (критические характеристики), а дополнительные identity markers. Используются Matching как сигналы high-confidence.
6. SupplierCharacteristicMapping — расширение target
SupplierCharacteristicMapping {
supplier_ref
supplier_char_code
target: CharacteristicMappingTarget
}
CharacteristicMappingTarget = Direct | ViaTaxonomy
Direct {
tracium_characteristic_key
unit_override?
}
ViaTaxonomy {
standard_ref
external_feature_code
// реальный tracium_characteristic_key резолвится через TaxonomyCharacteristicMapping
}
Lookup при парсе supplier payload:
1. supplier_char_code → SupplierCharacteristicMapping → target.
2a. Direct target: использовать tracium_characteristic_key.
2b. ViaTaxonomy target: lookup TaxonomyCharacteristicMapping[standard, external_feature_code] → key.
3. Ни одного mapping нет → Moderation proposal.
Поставщики, отдающие native ETIM коды (Systeme, DKC), создают 1 SupplierCharacteristicMapping с ViaTaxonomy{standard=etim_v7} для всех своих кодов — one-time setup. Supplier без общей taxonomy (ETM) использует Direct для каждого ConfigCharCode.
7. Matching через multiple identities
Matching обновляется: при попытке связать SupplierOffer с CanonicalProduct, кроме сравнения critical characteristics, проверяет overlap external_identities:
- Если supplier payload несёт ETIM class
EC001234, а canonical имеетexternal_identities: [..., {etim_v7, EC001234}, ...]— это сильный сигнал дляmatch_confidence=strongдаже при частичном совпадении прочих атрибутов. - Если barcode совпадает (GS1 GTIN) — практически гарантия
exact. - MPN match + manufacturer match →
strong(для некоторых товаров —exact).
MatchSignal в ADR-0029 event storming (существующий) расширяется: identity signal теперь может содержать {kind: "gs1_gtin" | "etim_class" | "mpn" | ...}.
8. Capabilities
Capabilities.Taxonomies {
SupportedStandards []TaxonomyStandardRef // какие стандарты connector возвращает в payload
NativeCoding bool // true если payload содержит external_code напрямую (Systeme/DKC ETIM)
MixedWithInternal bool // true если connector отдаёт и свои, и стандартные коды
ExportsDictionary bool // есть ли endpoint для выгрузки справочника (DKC /etim/*)
}
9. Сосуществование с SupplierClassificationMapping / SupplierManufacturerMapping
Те же aggregates расширяются по аналогии:
SupplierClassificationMapping.target = Direct | ViaTaxonomy(standard, external_class_code)→TaxonomyClassMapping.SupplierManufacturerMappingостаётся direct-only (производители не имеют глобальной taxonomy).
10. ETIM как первый конкретный standard
ETIM используется первым, потому что 3 из 5 поставщиков его отдают. Но:
- В config connector’а —
taxonomy_standards: [etim_v7], неetim: enabled(общий формат). TaxonomyCharacteristicMappingseed идёт из официального ETIM dump (публично доступен).- Когда появится machinery поставщик (eCl@ss), seed из eCl@ss dump, без правок модели.
Последствия
Плюсы
- Гибкость: новая отрасль / стандарт — новый
IdentityStandard+ таблица mapping’ов. Без изменения модели. - Cross-supplier consistency: два поставщика, отдающие ETIM
EF001234, мапятся в один Tracium key — one-time work. - Matching boost: overlap external_identities — сильные сигналы для match_confidence.
- Identity, а не только classification: barcode (GS1), MPN, OKPD2, промышленные stadnarts — все унифицировано.
- Не лок-ин на ETIM: если в машиностроении ETIM нет — работа без него.
Минусы
- Промежуточный слой — ещё один lookup:
supplier → taxonomy → traciumvssupplier → tracium. Performance impact незначителен (in-memory maps). - Matrix размеров: N supplier × M standards × K Tracium keys → потенциально большой справочник. Mitigation: lazy population, один mapping — один source of truth.
- Ответственность за качество taxonomy mappings — catalog ops. Это новая операционная работа.
Нейтральные последствия
- Поставщики без taxonomy support (ETM, smart-shop.pro):
SupplierCharacteristicMapping.target=Direct— поведение как раньше. CanonicalProduct.external_identities[]— опциональное поле; у legacy canonicals пустое.
Рассмотренные альтернативы
A. Принять ETIM как центральный, мапить всё через него
Было в ранней редакции ADR-0028. Отклонено: ETIM — отраслевой (электротехника), в машиностроении / фарме / food — другие стандарты. Принятие ETIM core ломает ADR-0016 extensibility принцип.
B. Оставить только SupplierCharacteristicMapping direct, без taxonomy layer
Работает, но дублирует mapping effort. Два ETIM-поставщика → 2× ту же работу.
C. External IDs как free-form map без schema
canonical.external_ids = {"etim": "EC01", "mpn": "X"} — удобно, но теряется semantic (нет таблицы characteristic-level, нет validation, нет units, нет version-aware dispatch).
Миграция
- Ввести
IdentityStandard,TaxonomyCharacteristicMapping,TaxonomyClassMappingaggregates. - Seed ETIM v7 из официального dump.
- Seed
mpn,gs1_gtin,okpd2(identifier kind; no feature map, только external_code вCanonicalProduct.external_identities[]). - Existing
SupplierCharacteristicMapping{target=Direct}остаются как есть. - Для Systeme / DKC — при подключении connector’а сразу создаём mappings с
ViaTaxonomy{etim_v7}. - Matching обновляется для использования
external_identities[]overlap как сигнала — separate PR, после заполнения registries.
Implementation status updates
- 2026-04-26 (P4c-1).
canonical_products.classification_tags TEXT[]column added (migration0030).SupplierIndexerreads the column end-to-end. Writers (AI classifier and per-supplier mapping) deferred — seedocs/superpowers/specs/2026-04-26-p4c-1-classification-tags-design.mdand the deferred-work plansdocs/plans/2026-04-26-14-21-future-ai-normalization-tags.md,docs/plans/2026-04-26-14-28-future-sticky-override-mechanism.md.
Ссылки
- ADR-0016 — extensible kind dictionaries + supplier mappings (таргет расширен до
ViaTaxonomy). - ADR-0024 — supplier connector contract (Capabilities.Taxonomies).
- ADR-0029 — revision-based incremental sync (taxonomy dictionaries тоже sync через cursors).
../../10-business/contexts/catalog.md—IdentityStandard,TaxonomyCharacteristicMapping,TaxonomyClassMapping,CanonicalProduct.external_identities.- ETIM International — https://www.etim-international.com.
- GS1 — https://www.gs1.org.
- eCl@ss — https://www.eclass.eu.
- OKPD2 — https://classifikators.ru/okpd (справочник РФ).