Контекст: сопоставление

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 с уровнем уверенности и объяснением.
MatchSignalVOОдин из сигналов: identity / embedding / classification_tag / packaging_fingerprint / multi_supplier_consensus / manual.
OrphanItemEOffer без identity_profile, ожидает резолюцию.
OrphanCategoryVO enumhot (24ч SLA) / warm (7 дней) / cold (30 дней).
RematchRequestVOКоманда «пересчитать matching по этим offers».
MatchExplanationVOСтруктурированный разбор решения для аудита и UX.
MatchConfidenceVO enumexact / 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)PolicyMatchDecisionMatchDecided
ПрименитьВторойСигнал (ApplySecondSignal)MatcherMatchDecisionSecondSignalFound, возможно MatchUpgraded
ПрименитьРешениеМодерации (ApplyModerationDecision)Moderation BC consumerMatchDecisionMatchUpgraded / MatchDecided + DecisionApplied ack
ОткрытьOrphan (OpenOrphan)PolicyOrphanItemOrphanItemAssigned
РезолвитьOrphan (ResolveOrphan)Moderation BC consumerOrphanItemOrphanResolved + DecisionApplied ack
ЗапроситьПовторныйМатчинг (RequestRematch)PolicyRematchRequested

Политики

ТриггерРеакция
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) от ModerationApplyModerationDecision → emit MatchUpgraded / OrphanResolved + ack DecisionApplied{case_id}
IdentityProfileChanged (Catalog) + добавлен critical attrRequestRematch для всех canonical этого профиля
ManufacturerAliasAdded (Catalog)RequestRematch для offers, где manufacturer_parsed соответствует alias
Daily scheduledRequestRematch для 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.

Инварианты

  1. На один SupplierOffer — ровно одно текущее MatchDecision (история сохраняется через event sourcing).
  2. match_confidence = unmatchedcanonical_product_ref is null.
  3. probable может попасть в автоматическую смету только при наличии хотя бы одного второго сигнала (см. MatchSignal).
  4. weak — никогда не попадает в автомат, всегда moderation_queue.
  5. SLA orphan resolution: hot ≤ 24ч, warm ≤ 7 дней, cold ≤ 30 дней. Превышение → алерт в #tracium-ai-ops (поскольку резолюция — задача AI-агента в Moderation BC).
  6. 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

Подписанные интеграционные события

ИсточникСобытиеРеакция
OffersSupplierOfferSeenAttemptMatch
OffersOfferCharacteristicsUpdatedAttemptMatch (single rematch)
CatalogIdentityProfileChangedRequestRematch массовый
CatalogManufacturerAliasAddedRequestRematch точечный
EnrichmentCharacteristicEnrichedByAIВозможный second signal для probable
ModerationProposedAction(MatchUpgrade) для case.kind=match_reviewApplyModerationDecisionMatchUpgraded + ack
ModerationProposedAction(ResolveOrphan) для case.kind=orphan_resolutionApplyModerationDecisionOrphanResolved + ack

Связи в context map

BCПаттернНазначение
OffersACL (upstream)Превращает грязные supplier-канвы в чистые decisions
CatalogPL (downstream → Catalog)Публикует MatchDecided → Catalog решает создавать canonical
EnrichmentCustomer/SupplierИспользует embeddings от Enrichment как сигнал
Supplier NetworkSK (TrustLevel влияет на conflict resolution)
ModerationCustomer/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

Связанные файлы