Контекст: сеть поставщиков
NOTE
Статус: Target design. Документ описывает целевую доменную модель. Соответствующий код реализован частично (см.
backend/internal/core/) или пока не начат. Правила маркировки — в50-processes/documentation-standard.md.
Назначение
Моделирует поставщиков как граф ролей и связей. Производитель → дистрибьютор → агрегатор → агент → склад. Вычисляет SupplyChainTrace для каждого offer (snapshot цепочки). Используется Pricing, Visibility, Offers, Matching для understanding origin данных.
Главный смысл
Поставщик в реальности — не атомарная единица, а узел в графе. Один и тот же бизнес-субъект может быть
manufacturer,distributor,api_providerиwarehouse_operatorодновременно. Граф изменяется отдельным flow, trace для offers пересчитывается event-driven.
Агрегаты / сущности / value objects
| Имя | Тип | Назначение |
|---|---|---|
Supplier | 🟨 Aggregate | Узел графа. Корень: supplier_id. |
SupplierKind | VO enum | manufacturer / distributor / aggregator / marketplace / marketplace_seller / agent / warehouse_operator / logistics / mixed. |
SupplierRole | E | Одна из ролей supplier’а. Расширяемый словарь. |
RoleKindCode | VO | api_provider / warehouse_operator / sales_agent / manufacturer / distributor / aggregator / logistics. |
RoleScope | VO | full / classification_tag:{tag} / manufacturer_group:{id} / warehouse:{code}. |
SupplierRelationship | E | Направленное ребро. |
RelationshipKindCode | VO | aggregates / resells / is_agent_of / uses_api_of / uses_warehouse_of / subsidiary_of / exclusive_for / listed_on / GENERIC. |
TrustLevel | VO enum | origin / tier_1 / tier_2 / unverified. |
SupplyChainTrace | VO | Shared Kernel. Snapshot цепочки для offer/observation. Содержит nodes[] + warehouse_chain[]. |
WarehouseChainLink | VO | Элемент warehouse_chain: (warehouse_ref, warehouse_kind, lead_time_days?). |
Warehouse | E | Склад, привязан к warehouse_operator. Обязательное поле kind: WarehouseKind. |
WarehouseKind | VO enum | regional_center / logistics_center / pickup_office / manufacturer_warehouse / third_party / transit. Фиксированный enum (как TrustLevel) — расширяется только через ADR. См. ADR-0025. |
Seller | E | Lightweight узел внутри агрегатора (SupplierKind=aggregator|marketplace). Корень: (supplier_ref, external_seller_id). Не имеет своей credential/session. ADR-0026. |
SellerStatus | VO enum | active / inactive / banned. |
SellerRating | VO | (score 0..5, sample_size?, observed_at, source). Snapshot на Seller + history в CH. |
RelationshipSource | VO enum | declared / observed / inferred. |
Confidence | VO | Для inferred отношений. |
Доменные события
| Событие | Причина |
|---|---|
ПоставщикДобавлен (SupplierCreated) | Operator |
РольПоставщикаДобавлена (SupplierRoleAdded) | Operator / observation |
РольПоставщикаУдалена (SupplierRoleRemoved) | Operator |
СвязьУстановлена (SupplierRelationshipEstablished) | Operator / observation / inferred |
СвязьИзменена (SupplierRelationshipUpdated) | Operator |
СвязьУдалена (SupplierRelationshipRemoved) | Operator |
СловарьРолейРасширен (RoleKindDictionaryExtended) | Operator (admin UI) |
СловарьСвязейРасширен (RelationshipKindDictionaryExtended) | Operator |
ЦепочкаПоставокПересчитана (SupplyChainTraceRecomputed) | Policy: при изменении графа / создании offer |
ЦикловОбнаружено (SupplierGraphCycleDetected) | Validator (alert, не block) |
ВыводСвязиЗапрошен (InferredRelationshipProposed) | ML/heuristics → moderation |
ПродавецЗарегистрирован (SellerRegistered) | Marketplace connector первый раз увидел seller’а |
РейтингПродавцаОбновлён (SellerRatingUpdated) | Новый snapshot rating из fetch’а |
СтатусПродавцаИзменён (SellerStatusChanged) | active ↔ inactive ↔ banned (observed исчезновение seller’а N раз подряд → inactive) |
ПродавецПовышенДоПоставщика (SellerPromotedToSupplier) | Operator решил оформить seller’а как полноценный Supplier aggregate |
Команды
| Команда | Актор | Целевой агрегат | Результат |
|---|---|---|---|
СоздатьПоставщика (CreateSupplier) | Operator | Supplier | SupplierCreated |
ДобавитьРоль (AddRole) | Operator | SupplierRole | SupplierRoleAdded |
УстановитьСвязь (EstablishRelationship) | Operator | SupplierRelationship | SupplierRelationshipEstablished |
ПересчитатьTrace (RecomputeTrace) | Engine | — (per offer/observation) | SupplyChainTraceRecomputed |
ПодтвердитьInferredСвязь (ConfirmInferredRelationship) | Operator | SupplierRelationship | upgrade source declared |
Политики
| Триггер | Реакция |
|---|---|
SupplierOfferSeen (Offers) | → RecomputeTrace (initial) |
SupplierRelationshipEstablished/Updated/Removed | → RecomputeTrace для всех offers с participating suppliers |
SupplierRoleAdded/Removed | → RecomputeTrace затронутых offers |
| Schedule (daily) | → RecomputeTrace всех offers (защита от пропущенных событий) |
| Inferred relationship с confidence ≥ threshold | → publish InferredRelationshipProposed → AI-агент inferred_relationship_review в Moderation BC |
ProposedAction(ConfirmInferredRelationship) от Moderation | → upgrade source declared + ack DecisionApplied |
| Cycle detected | → alert, не block |
Marketplace payload: новый external_seller_id | → SellerRegistered + SupplierRelationshipEstablished{kind=listed_on, source=observed} |
| Marketplace payload: обновлённый rating | → SellerRatingUpdated (только если diff ≥ min_delta, избегаем шума) |
| Seller отсутствует в fetch N раз подряд (default 5) | → SellerStatusChanged{status=inactive}. Через M дней inactive → banned без operator-override → не используется в Pricing. |
| Operator promotes seller | → SellerPromotedToSupplier + миграция observations на новый supplier_ref + закрытие listed_on ребра |
Read-модели
- 🟩
supplier_graph_view(PG materialized + ES) — для визуализации в админке. - 🟩
current_supply_chain_traces(PG materialized) —(offer_id) → trace. - 🟩
relationships_pending_review(PG) — inferred → moderation.
Инварианты
kindотражает основную роль;roles[]— полный набор.trust_levelвлияет на conflict resolution и автоматический матчинг. 2a. Seller lightweight invariant (ADR-0026):Sellerentity существует только в контекстеlisted_onсвязи с однимSupplier{kind=aggregator|marketplace}. Seller без aggregator’а — invalid state. 2b. Seller default trust_level =unverified; upgrade возможен только черезSellerPromotedToSupplier(становится полнымSupplier) или через moderation decision. 2c.SellerRating.scoreв диапазоне[0, 5];score=nullдопустимо (marketplace не отдал). При отсутствии rating Pricing tie-break использует толькоtrust_level + price + freshness.SupplyChainTrace— value object, не aggregate; пересчитывается отдельным consumer’ом.- Цикл в графе детектируется и помечается алертом, но не запрещён (могут быть валидные cross-роли).
inferredотношения не попадают напрямую в trace — только после подтверждения модератором.Manufacturer(Catalog) иSupplierсroles=[manufacturer]— связаны черезmanufacturer.supplier_ref(оптionally).Warehouse.kindобязательно и присваивается при создании. Изменениеkind— через отдельный commandReclassifyWarehouse+ audit.kind = manufacturer_warehouseавтоматически присваивается приobserved_in_payloadсtrust_level=origin, если payload пришёл напрямую из API производителя или изInfoSuppStores-подобного поля поставщика-дистрибьютора. ЛинкWarehouse.supplier_refв этом случае указывает на Supplier с рольюmanufacturer.SupplyChainTrace.warehouse_chainпересчитывается синхронно сSupplyChainTraceRecomputed; отражает путь товара:manufacturer_warehouse → logistics_center → pickup_office(в типичном для ЭТМ случае).
Источники отношений
declared— заведено вручную (договоры, бизнес-знание).observed— извлечено из payload (e.g., ETM вmnf_codeуказывает manufacturer → implicitresells).inferred— выведено ML (overlap прайсов → вероятноaggregates).
inferred → AI-агент Moderation BC (inferred_relationship_review) перед попаданием в граф; человек только при escalation.
Использование SupplyChainTrace
| Где | Как |
|---|---|
| Pricing | Discovery альтернативных источников (тот же canonical через разные chains, разная цена) |
| Pricing | Breakdown с структурой маржи (origin / dist / aggregator / our markup) |
| Matching | Conflict resolution через trust_level (origin > tier_1 > tier_2) |
| Visibility | Predicate real_manufacturer_in, chain_includes_supplier, chain_depth_gt, warehouse_kind_includes |
| Estimate | min_lead_time optimization использует warehouse_chain + StockForecast для эффективного lead time |
| Audit / compliance | Происхождение для маркируемой / медицинской продукции |
| Phase 6+ — execution routing | Маршрутизация заказа по цепочке |
Интеграционные события (публикуем)
Топик: supplier.graph.events.v1 (см. ADR-0017). Partition key: supplier_id для графовых; offer_id для trace.
| Имя | Когда |
|---|---|
SupplierCreated, SupplierRoleAdded, SupplierRoleRemoved | Изменения узлов |
SupplierRelationshipEstablished/Updated/Removed | Изменения рёбер |
RoleKindDictionaryExtended, RelationshipKindDictionaryExtended | Изменения словарей (ADR-0016) |
SupplyChainTraceRecomputed | Per offer — для Offers / Pricing / Visibility |
SupplierGraphCycleDetected | Для алерта |
Топик: supplier.sellers.v1. Partition key: aggregator_supplier_id.
| Имя | Когда |
|---|---|
SellerRegistered, SellerStatusChanged, SellerRatingUpdated, SellerPromotedToSupplier | ADR-0026. Consumers: Pricing (invalidate cache), admin UI, CH sink. |
Подписанные интеграционные события
| Источник | Событие | Реакция |
|---|---|---|
| Offers | SupplierOfferSeen | Initial RecomputeTrace |
| Catalog | ManufacturerCreated | Возможный auto-link к Supplier с manufacturer role |
Связи в context map
| BC | Паттерн | Назначение |
|---|---|---|
| Offers | SK (SupplyChainTrace VO) | Snapshot trace вкладывается в SupplierOffer |
| Pricing | SK (SupplyChainTrace, TrustLevel) | Conflict resolution, breakdown |
| Visibility | SK | Chain-предикаты |
| Matching | SK (TrustLevel) | Влияет на confidence priority |
| Catalog | Customer/Supplier (Catalog ↔ Supplier Network через Manufacturer.supplier_ref) | — |
| Moderation | Customer/Supplier | inferred отношения проверяет AI-агент, не человек |
Мини event storming
flowchart LR OP["🟫 Operator"] subgraph SN["Supplier Network"] ADD["🟦 EstablishRelationship"] SR["🟨 SupplierRelationship"] E1["🟧 SupplierRelationshipEstablished"] P1["🟪 Policy: пересчитать traces"] REC["🟦 RecomputeTrace"] E2["🟧 SupplyChainTraceRecomputed"] end subgraph OFF["Offers"] OS["🟧 SupplierOfferSeen"] TR["🟦 SupplyChainTrace VO"] end subgraph PR["Pricing"] SUBP["🟪 Subscribe SupplyChainTraceRecomputed"] end subgraph V["Visibility"] SUBV["🟪 chain-predicates"] end OP --> ADD --> SR --> E1 --> P1 --> REC --> E2 OS -.PL.-> REC E2 -.PL.-> TR E2 -.SK.-> SUBP E2 -.SK.-> SUBV
Связанные файлы
offers.md,pricing.md,visibility.md,matching.md,catalog.md,moderation.md.- ADR-0016 — расширяемые kind-словари + supplier mappings +
listed_on. - ADR-0017 — event-driven supply chain recalc.
- ADR-0025 — WarehouseKind enum + warehouse_chain в SupplyChainTrace.
- ADR-0026 — Seller entity, SellerRating, marketplace observations.
- Сценарий:
../scenarios/ingestion-flow.md,../scenarios/ai-moderation-flow.md,../scenarios/supplier-dictionary-sync.md.