Контекст: сопоставление
NOTE
Статус: Target design. Документ описывает целевую доменную модель. Соответствующий код реализован частично (см.
backend/internal/core/) или пока не начат. Правила маркировки — в50-processes/documentation-standard.md.
Назначение
Anti-Corruption Layer: переводит грязную, противоречивую модель SupplierOffer в чистое решение «к какому CanonicalProduct это относится» с уровнем уверенности. Защищает Catalog от прямого знания о форматах поставщиков.
Главный смысл
Решение матчинга — не атрибут offer’а и не атрибут canonical’а. Это самостоятельный, объяснимый, версионируемый артефакт
MatchDecision. Сегодняшнийprobableможет стать завтрашнимexact— поэтому матчинг идемпотентен, но не стабилен по времени.
Агрегаты / сущности / value objects
| Имя | Тип | Назначение |
|---|---|---|
MatchDecision | 🟨 Aggregate | Решение SupplierOffer ↔ CanonicalProduct с уровнем уверенности и объяснением. |
MatchSignal | VO | Один из сигналов: identity / embedding / classification_tag / packaging_fingerprint / multi_supplier_consensus / manual. |
OrphanItem | E | Offer без identity_profile, ожидает резолюцию. |
OrphanCategory | VO enum | hot (24ч SLA) / warm (7 дней) / cold (30 дней). |
RematchRequest | VO | Команда «пересчитать matching по этим offers». |
MatchExplanation | VO | Структурированный разбор решения для аудита и UX. |
MatchConfidence | VO enum | exact / strong / probable / weak / unmatched. |
Доменные события
| Событие | Причина |
|---|---|
СовпадениеЗапрошено (MatchAttemptRequested) | Policy: новый offer / изменения характеристик |
СовпадениеРешено (MatchDecided) | Matcher вынес решение |
ВторойСигналНайден (SecondSignalFound) | Для probable: embedding / consensus / packaging / tag / manual |
СовпадениеУлучшено (MatchUpgraded) | probable → strong, strong → exact |
СовпадениеПонижено (MatchDowngraded) | Редко: при противоречии новых данных |
OrphanСтатусПрисвоен (OrphanItemAssigned) | Offer не подпал ни под один Identity Profile |
OrphanРезолюцияВыполнена (OrphanResolved) | Принят к canonical / профилю / помечен unclassifiable |
ОчередьМодерацииПополнена (ModerationItemAdded) | probable/weak без второго сигнала или конфликт |
ПовторныйМатчингЗапущен (RematchRequested) | Изменён Identity Profile / новые алиасы / новый кандидат |
RematchВолнаЗавершена (RematchBatchCompleted) | Окончание массового rematch |
Команды
| Команда | Актор | Целевой агрегат | Результат |
|---|---|---|---|
ЗапроситьСовпадение (AttemptMatch) | Policy | MatchDecision | MatchDecided |
ПрименитьВторойСигнал (ApplySecondSignal) | Matcher | MatchDecision | SecondSignalFound, возможно MatchUpgraded |
ПрименитьРешениеМодерации (ApplyModerationDecision) | Moderation BC consumer | MatchDecision | MatchUpgraded / MatchDecided + DecisionApplied ack |
ОткрытьOrphan (OpenOrphan) | Policy | OrphanItem | OrphanItemAssigned |
РезолвитьOrphan (ResolveOrphan) | Moderation BC consumer | OrphanItem | OrphanResolved + DecisionApplied ack |
ЗапроситьПовторныйМатчинг (RequestRematch) | Policy | — | RematchRequested |
Политики
| Триггер | Реакция |
|---|---|
SupplierOfferSeen (Offers) | → AttemptMatch |
OfferCharacteristicsUpdated (Offers) | → AttemptMatch (rematch single) |
MatchDecided=unmatched + есть identity_signature | → команда СоздатьКаноническийТовар (Catalog) |
MatchDecided=probable + ни одного второго сигнала | → publish ModerationItemAdded(match_review) → AI-агент в Moderation BC |
MatchDecided=weak | → publish ModerationItemAdded(match_review) всегда |
MatchDecided=unmatched + identity_profile отсутствует | → OpenOrphan → publish OrphanItemAssigned → AI-агент orphan_resolution |
ProposedAction(MatchUpgrade|ResolveOrphan) от Moderation | → ApplyModerationDecision → emit MatchUpgraded / OrphanResolved + ack DecisionApplied{case_id} |
IdentityProfileChanged (Catalog) + добавлен critical attr | → RequestRematch для всех canonical этого профиля |
ManufacturerAliasAdded (Catalog) | → RequestRematch для offers, где manufacturer_parsed соответствует alias |
| Daily scheduled | → RequestRematch для probable matches без upgrade за N дней |
Read-модели
- 🟩
match_decisions_by_offer(PG) — текущее решение по каждому offer. - 🟩
orphan_pool(PG + ES) — для дашбордов и наблюдения за входной очередью AI-агента. - 🟩
match_explainability_log(CH) — для аудита и обучения.
Сама очередь модерации (moderation_queue) — read-model Moderation BC, не Matching. Matching только публикует ModerationItemAdded / OrphanItemAssigned через PL.
Инварианты
- На один
SupplierOffer— ровно одно текущееMatchDecision(история сохраняется через event sourcing). match_confidence = unmatched⇔canonical_product_refis null.probableможет попасть в автоматическую смету только при наличии хотя бы одного второго сигнала (см.MatchSignal).weak— никогда не попадает в автомат, всегдаmoderation_queue.- SLA orphan resolution:
hot ≤ 24ч,warm ≤ 7 дней,cold ≤ 30 дней. Превышение → алерт в#tracium-ai-ops(поскольку резолюция — задача AI-агента в Moderation BC). - Matching не имеет ручного UI / оператора: все ручные действия делегированы Moderation BC (где AI-агент по умолчанию автоматизирует, человек — только escalation).
Интеграционные события (публикуем)
Топик: matching.events.v1. Partition key: supplier_offer_id.
| Имя | Когда |
|---|---|
MatchDecided | Каждое решение (новое, upgrade, downgrade) |
MatchUpgraded | Изменение confidence вверх (после second signal или решения Moderation) |
OrphanItemAssigned | Новый orphan — вход для Moderation orphan_resolution агента |
OrphanResolved | Резолюция orphan (после ApplyModerationDecision) |
ModerationItemAdded | Кейс match_review для Moderation BC |
RematchBatchCompleted | Большой rematch, для метрик |
DecisionApplied{case_id} | Ack для Moderation BC после применения ProposedAction |
Подписанные интеграционные события
| Источник | Событие | Реакция |
|---|---|---|
| Offers | SupplierOfferSeen | AttemptMatch |
| Offers | OfferCharacteristicsUpdated | AttemptMatch (single rematch) |
| Catalog | IdentityProfileChanged | RequestRematch массовый |
| Catalog | ManufacturerAliasAdded | RequestRematch точечный |
| Enrichment | CharacteristicEnrichedByAI | Возможный second signal для probable |
| Moderation | ProposedAction(MatchUpgrade) для case.kind=match_review | ApplyModerationDecision → MatchUpgraded + ack |
| Moderation | ProposedAction(ResolveOrphan) для case.kind=orphan_resolution | ApplyModerationDecision → OrphanResolved + ack |
Связи в context map
| BC | Паттерн | Назначение |
|---|---|---|
| Offers | ACL (upstream) | Превращает грязные supplier-канвы в чистые decisions |
| Catalog | PL (downstream → Catalog) | Публикует MatchDecided → Catalog решает создавать canonical |
| Enrichment | Customer/Supplier | Использует embeddings от Enrichment как сигнал |
| Supplier Network | SK (TrustLevel влияет на conflict resolution) | — |
| Moderation | Customer/Supplier (Matching → Moderation, обратно через PL ProposedAction) | Все ручные решения делегированы AI-агентам Moderation BC |
Мини event storming
flowchart LR subgraph OFF["Offers"] E1["🟧 SupplierOfferSeen"] end subgraph M["Matching (ACL)"] P1["🟪 Policy: new offer"] CMD1["🟦 AttemptMatch"] MD["🟨 MatchDecision"] E2["🟧 MatchDecided"] SUB1["🟪 Policy: probable<br/>+ second signal?"] CMD2["🟦 publish ModerationItemAdded"] OI["🟨 OrphanItem"] SUB2["🟪 Policy: unmatched<br/>+ no profile"] CMD3["🟦 OpenOrphan"] APPLY["🟦 ApplyModerationDecision"] E3["🟧 MatchUpgraded / OrphanResolved"] end subgraph MOD["Moderation (AI-агент)"] AGENT["⚙️ AgentRunner<br/>(match_review / orphan_resolution)"] PA["🟧 ProposedAction"] end subgraph C["Catalog"] SUBC["🟪 Policy: unmatched<br/>+ identity_signature"] CMDC["🟦 СоздатьКаноническийТовар"] end subgraph EN["Enrichment"] EN1["🟧 CharacteristicEnrichedByAI"] end E1 --> P1 --> CMD1 --> MD --> E2 E2 --> SUB1 SUB1 -->|нет| CMD2 E2 --> SUB2 -->|нет profile| CMD3 --> OI CMD2 -.PL.-> AGENT OI -.PL.-> AGENT AGENT --> PA -.PL.-> APPLY --> E3 E2 -.PL.-> SUBC --> CMDC EN1 -.signal.-> CMD1
Связанные файлы
- Deep-dive:
../product-identity.md(Match Confidence, Second Signal, Orphan SLA). - Сценарий:
../scenarios/matching-flow.md,../scenarios/ai-moderation-flow.md. offers.md,catalog.md,enrichment.md,moderation.md.