Контекст: каталог
NOTE
Статус: Target design. Документ описывает целевую доменную модель. Соответствующий код реализован частично (см.
backend/internal/core/) или пока не начат. Правила маркировки — в50-processes/documentation-standard.md.
Назначение
Хранит единую модель товара — CanonicalProduct — независимую от того, как товар представлен у конкретного поставщика. Catalog — источник истины «что считается одним и тем же товаром» и «какие у этого товара характеристики».
Главный смысл
identity_signatureуникален: два разных canonical product не могут иметь одинаковую сигнатуру; одинаковые offers разных поставщиков сходятся в один canonical.
Агрегаты / сущности / value objects
| Имя | Тип | Назначение |
|---|---|---|
CanonicalProduct | 🟨 Aggregate | Единая единица товара. Корень: identity_signature. Содержит media: [MediaAsset], certificates: [MediaAsset], videos: [MediaAsset], external_identities: [ExternalIdentity] (набор ID из federated taxonomies: ETIM class, GTIN, MPN, OKPD2, eCl@ss — ADR-0028). |
IdentityProfile | E | Шаблон критических характеристик. Deep-dive: ../product-identity.md. |
Manufacturer | E | Производитель + aliases[]. |
Characteristic | E | Атрибут: key, value_type, default_unit. Deep-dive: ../characteristics.md. |
Unit | E | Единица измерения с to_canonical. |
EquivalenceClass | E | Группа взаимозаменяемых canonical products. |
IdentitySignature | VO | stable_hash(manufacturer, mpn?, profile, critical_attrs). |
LifecycleStatus | VO enum | new / active / deprecated / discontinued. |
ClassificationTag | VO | Свободный тег (НЕ иерархия). |
MediaAsset | VO | (kind, url_small?, url_large?, mime, format?, watermarked, rights_source_credential_ref?). Kind: image / video / certificate / datasheet. |
SupplierCharacteristicMapping | 🟨 Aggregate | ACL маппинг: (supplier_ref, supplier_char_code) → MappingTarget. MappingTarget = Direct(tracium_key) | ViaTaxonomy(standard_ref, external_code). Event-sourced. ADR-0028. |
SupplierClassificationMapping | 🟨 Aggregate | ACL маппинг: (supplier_ref, supplier_class_code) → MappingTarget (Direct [tags] или ViaTaxonomy). Event-sourced. |
SupplierManufacturerMapping | 🟨 Aggregate | ACL маппинг: (supplier_ref, supplier_manufacturer_code) → Manufacturer.id. Event-sourced. |
IdentityStandard | 🟨 Aggregate | Справочник federated taxonomies: (code, version?, domain, kind, status). Стартовый seed: etim v6/v7, gs1_gtin, mpn, okpd2. Расширяемый (eCl@ss, UNSPSC, ISO 13584) через admin UI. ADR-0028. |
TaxonomyCharacteristicMapping | 🟨 Aggregate | (standard_ref, external_feature_code) → Characteristic.key + unit_override?. Один маппинг переиспользуется N поставщиками. |
TaxonomyClassMapping | 🟨 Aggregate | (standard_ref, external_class_code) → [ClassificationTag] (multi-tag). |
TaxonomyValueMapping | VO | Для enum-значений: (standard_ref, external_value_code) → tracium_value. |
ExternalIdentity | VO | Marker на CanonicalProduct: (standard_ref, external_code). Несколько одновременно. |
MappingTarget | VO | Discriminated union: Direct / ViaTaxonomy. |
MappingSource | VO enum | declared / imported / inferred / operator / moderation_accepted. |
MappingConfidence | VO | (score: float, method: string) — для inferred / imported записей. |
Полный реестр — ../data-model.md.
Доменные события
| Событие (рус → en) | Причина | Источник |
|---|---|---|
КаноническийТоварСоздан (CanonicalCreated) | Появился новый identity_signature | Policy Matching: MatchDecided=unmatched |
ХарактеристикаДобавлена (CharacteristicAdded) | Добавлен normalized атрибут | Команда ОбновитьХарактеристикиТовара |
ХарактеристикаИзменена (CharacteristicChanged) | Изменено значение / unit | То же |
ХарактеристикаУдалена (CharacteristicRemoved) | Удалён атрибут | Manual / Matching rebuild |
МедиаДобавлены (MediaAssetAdded) | Добавлено image / video / certificate / datasheet | Enrichment / manual / ProposedAction |
МедиаУдалены (MediaAssetRemoved) | Удалены устаревшие assets | Operator |
КонтентОбновлён (ContentUpdated) | Описание / SEO / агрегированные media изменены | Enrichment / manual |
СтатусЖизненногоЦиклаИзменён (LifecycleStatusChanged) | new → active → deprecated → discontinued | Policy / manual |
ПреемникУстановлен (ReplacedBySet) | discontinued получил преемника | Manual / supplier feed |
ПрофильИдентичностиСоздан (IdentityProfileCreated) | Новый профиль | Manual / ML proposal |
КритическийАтрибутДобавлен (CriticalAttributeAdded) | Profile.critical_attribute_keys расширен | ADR-required |
КлассЭквивалентностиСформирован (EquivalenceClassFormed) | Группа аналогов создана | Manual / rule-driven |
МаппингХарактеристикиДобавлен (SupplierCharacteristicMappingAdded) | Новая запись в SupplierCharacteristicMapping | Dictionary import / operator / moderation |
МаппингКлассификацииДобавлен (SupplierClassificationMappingAdded) | Новая запись в SupplierClassificationMapping | Import / operator / moderation |
МаппингПроизводителяДобавлен (SupplierManufacturerMappingAdded) | Новая запись в SupplierManufacturerMapping | Import / operator / moderation |
МаппингПодтверждён (MappingConfirmedByModeration) | AI-агент или операторвинд подтвердил inferred-маппинг | Moderation BC |
СтандартИдентичностиЗарегистрирован (IdentityStandardRegistered) | Новый federated standard (ETIM/eCl@ss/GS1/…) | Admin UI |
МаппингТаксономииХарактеристикДобавлен (TaxonomyCharacteristicMappingAdded) | Import / operator / moderation | Canonical Catalog |
МаппингТаксономииКлассаДобавлен (TaxonomyClassMappingAdded) | То же | |
ВнешнийИдентификаторПрисвоен (ExternalIdentityAssigned) | CanonicalProduct получил marker (ETIM class / GTIN / MPN) | Matching / operator |
ВнешнийИдентификаторУдалён (ExternalIdentityRevoked) | Маркер оказался ошибочным | Moderation |
Команды
| Команда | Актор | Целевой агрегат | Результат |
|---|---|---|---|
СоздатьКаноническийТовар (CreateCanonical) | Matching | CanonicalProduct | КаноническийТоварСоздан |
ОбновитьХарактеристикиТовара (UpdateCharacteristics) | Enrichment / Matching | CanonicalProduct | Характеристика* |
ИзменитьСтатусТовара (ChangeLifecycleStatus) | Policy | CanonicalProduct | СтатусЖизненногоЦиклаИзменён |
УстановитьПреемника (SetReplacedBy) | Moderation BC consumer (через ProposedAction) | CanonicalProduct | ПреемникУстановлен |
ПрименитьРешениеМодерации (ApplyModerationDecision) | Moderation BC consumer | CanonicalProduct / Manufacturer | соответствующее domain event + ack |
СоздатьIdentityProfile (CreateIdentityProfile) | Operator (RFC) — структурное решение | IdentityProfile | ПрофильИдентичностиСоздан |
ДобавитьКритическийАтрибут (AddCriticalAttribute) | Operator (ADR) — структурное breaking | IdentityProfile | КритическийАтрибутДобавлен (breaking) |
СоздатьКлассЭквивалентности (FormEquivalenceClass) | Policy / rule / Moderation suggest | EquivalenceClass | КлассЭквивалентностиСформирован |
ДобавитьAliasПроизводителя (AddManufacturerAlias) | Moderation BC consumer (через ProposedAction) | Manufacturer | ManufacturerAliasAdded |
Политики
| Триггер | Реакция |
|---|---|
MatchDecided=unmatched (Matching) + есть identity_signature | → команда СоздатьКаноническийТовар |
OfferCharacteristicsUpdated (Offers) + match=exact/strong | → команда ОбновитьХарактеристикиТовара. Lookup цепочка: SupplierCharacteristicMapping(supplier_ref, char_code) → MappingTarget. Если Direct — использовать tracium_key напрямую. Если ViaTaxonomy — второй lookup TaxonomyCharacteristicMapping(standard_ref, external_code) → tracium_key. Если ни один mapping не найден → CharacteristicMappingProposalOpened → Moderation. |
OfferObservationRecorded + connector Capabilities.Taxonomies.NativeCoding=true + offer несёт external codes (ETIM class, GTIN, MPN) | → команда AssignExternalIdentity для связанного Canonical (если matched) → ExternalIdentityAssigned. Этот marker дальше используется Matching как high-confidence сигнал. |
OfferMediaUpdated (Offers) + match=exact/strong | → команда ОбновитьМедиаТовара (merge с учётом watermark priority: watermarked=false перетирает watermarked=true для того же logical_id) |
CharacteristicEnrichedByAI (Enrichment, non-critical + confidence ≥ threshold) | → команда ОбновитьХарактеристикиТовара напрямую |
ProposedAction(AcceptEnrichment) от Moderation (critical attr) | → команда ОбновитьХарактеристикиТовара + ack |
ProposedAction(SetReplacedBy|AddManufacturerAlias|AcceptMapping) от Moderation | → соответствующая команда + ack |
Все active offers стали discontinued (Offers) | → команда ИзменитьСтатусТовара(deprecated) |
CriticalAttributeAdded (Catalog) | → команда RematchAll для затронутых canonicals |
| Кластер orphans с похожими characteristics обнаружен (Matching) | → publish IdentityProfileProposalCreated → AI-агент identity_profile_proposal (всегда defer на operator RFC) |
EnrichmentJobCompleted{kind=characteristic_dictionary_refresh} (Ingestion) | → merge новых mappings в SupplierCharacteristicMapping; неизвестные коды → Moderation |
EnrichmentJobCompleted{kind=manufacturer_dictionary_refresh} | → merge в SupplierManufacturerMapping; для новых Manufacturer’ов — predict alias через ML, fallback на Moderation |
EnrichmentJobCompleted{kind=classification_dictionary_refresh} | → merge в SupplierClassificationMapping; multi-tag preserved (1 supplier_class_code → N ClassificationTag) |
Read-модели
- 🟩
canonical_index(Elasticsearch) — для Search. - 🟩
canonical_with_active_offers(PG materialized) — для админки и Pricing pre-fetch. - 🟩
equivalence_graph— для подбора аналогов.
Инварианты
identity_signatureуникален в системе.manufacturer_refвсегда канонизирован (не алиас).lifecycle_status = discontinued⇒replaced_byопционально, но если задан — тоже canonical, не offer.- Все
characteristics[].unitсоответствуютvalue_typeсвоейCharacteristic. - Critical characteristics не могут быть
nullуCanonicalProduct. Если все источники молчат — canonical не создаётся, offer становится orphan. IdentityProfile.critical_attribute_keys— break-change при изменении: только через ADR.EquivalenceClass.members[]— только canonical products одногоIdentityProfile(или явно многопрофильный flag).MediaAsset.watermarked = falseна canonical допустим только если есть хотя бы один offer-источник с credential, имеющейmedia_unwatermarkedfeature (ADR-0024).SupplierCharacteristicMappingуникален по(supplier_ref, supplier_char_code).MappingTarget— ровно один: либоDirect, либоViaTaxonomy. Не оба одновременно.SupplierClassificationMappingдопускает multi-tag: 1 supplier-код → N ClassificationTag. Но Tracium ClassificationTag остаётся плоским (ADR), иерархия поставщика распаковывается в tag-set.- Любое изменение mapping публикует интеграционное событие — downstream (Matching, Enrichment) должны инвалидировать кеши по
supplier_ref. TaxonomyCharacteristicMappingуникален по(standard_ref, external_feature_code). Один external_code → одинtracium_key. Разные external_code разных стандартов могут указывать на один и тот жеtracium_key(это ожидаемо: ETIM EF001234 и eCl@ss AD-VEV-00123 оба =nominal_current_A).CanonicalProduct.external_identities[]— set по(standard_ref, external_code). Канонический не может иметь два разных GTIN (если такое замечено — Moderation review).IdentityStandard.domain— справочный; НЕ блокирует использование (электрический товар может иметь и ETIM, и GS1).
Интеграционные события (публикуем)
Топик: catalog.events.v1. Partition key: aggregate_id (canonical_id).
| Имя | Когда |
|---|---|
CanonicalCreated | Создание canonical |
CanonicalCharacteristicsChanged | Любые изменения characteristics (для Search reindex, Enrichment) |
CanonicalMediaChanged | Media update (для Search / UI) |
CanonicalLifecycleStatusChanged | Для Pricing / Search фильтров |
IdentityProfileChanged | Для Matching invalidation |
EquivalenceClassChanged | Для Search analog index |
Топик: catalog.mappings.v1. Partition key: supplier_ref.
| Имя | Когда |
|---|---|
SupplierCharacteristicMappingAdded/Changed/Removed | Изменения ACL справочника характеристик |
SupplierClassificationMappingAdded/Changed/Removed | Изменения ACL справочника классификации |
SupplierManufacturerMappingAdded/Changed/Removed | Изменения ACL справочника производителей |
Топик: catalog.taxonomies.v1. Partition key: standard_ref.
| Имя | Когда |
|---|---|
IdentityStandardRegistered/Changed/Deprecated | Изменения в справочнике federated standards |
TaxonomyCharacteristicMappingAdded/Changed/Removed | Изменения ETIM/eCl@ss/… характеристик маппинга |
TaxonomyClassMappingAdded/Changed/Removed | То же для классификации |
TaxonomyValueMappingAdded/Changed/Removed | Для enum-значений |
Топик: catalog.external_identities.v1. Partition key: canonical_id.
| Имя | Когда |
|---|---|
ExternalIdentityAssigned | Canonical получил marker (ETIM class, GTIN, MPN, …) |
ExternalIdentityRevoked | Marker оказался ошибочным |
Подписанные интеграционные события
| Источник | Событие | Зачем |
|---|---|---|
| Matching | MatchDecided | Триггер СоздатьКаноническийТовар или линковка |
| Enrichment | CharacteristicEnrichedByAI (non-critical) | Применение к canonical |
| Moderation | ProposedAction(AcceptEnrichment|SetReplacedBy|AddManufacturerAlias) | ApplyModerationDecision |
| Offers | OfferLifecycleStatusChanged | Возможный downgrade canonical |
Связи в context map
| BC | Паттерн | Назначение |
|---|---|---|
| Matching | ACL (upstream от Catalog) | Защищает canonical от шумных supplier-данных. Catalog не работает с raw supplier payload. |
| Offers | Customer/Supplier (Catalog — supplier) | Catalog не зависит от Offers; Offers — downstream consumer canonical_id. |
| Enrichment | Published Language | Enrichment слушает CanonicalCharacteristicsChanged, публикует CharacteristicEnrichedByAI. |
| Search | Open Host Service | Catalog публикует событиями; Search строит read-проекцию. |
| Moderation | Customer/Supplier | Не-структурные ручные действия (replaced_by, alias, accept critical enrichment) делегированы AI-агентам. Только IdentityProfile / CriticalAttribute остаются за человеком (RFC/ADR). |
Мини event storming
flowchart LR subgraph M["Matching (ACL)"] MD["🟧 MatchDecided<br/>=unmatched"] end subgraph C["Catalog"] P1["🟪 Policy: новый identity_signature"] CMD1["🟦 СоздатьКаноническийТовар"] CP["🟨 CanonicalProduct"] E1["🟧 КаноническийТоварСоздан"] E2["🟧 ХарактеристикаДобавлена"] end subgraph E["Enrichment"] EE["🟧 CharacteristicEnrichedByAI"] end subgraph S["Search"] SR["🟩 canonical_index"] end MD --> P1 --> CMD1 --> CP --> E1 EE -.subscribed.-> CP CP --> E2 E1 --> SR E2 --> SR
Связанные файлы
- Deep-dive:
../product-identity.md,../characteristics.md. - Сценарий:
../scenarios/matching-flow.md,../scenarios/analog-search.md,../scenarios/supplier-dictionary-sync.md. - Архитектура:
../../20-architecture/event-sourcing.md,../../20-architecture/schemas/events/catalog.events.v1.json(TBD). - ADR-0016 — расширяемые kind-словари + supplier-to-tracium mapping aggregates.
- ADR-0024 — supplier connector contract (media rights, identity pack, Taxonomies capabilities).
- ADR-0025 — price & stock observation extensions (WarehouseKind косвенно через offers).
- ADR-0028 — federated identity taxonomies (ETIM/eCl@ss/GS1/MPN/OKPD2; TaxonomyCharacteristicMapping, TaxonomyClassMapping, ExternalIdentity).