Контекст: поиск

NOTE

Статус: Target design. Документ описывает целевую доменную модель. Соответствующий код реализован частично (см. backend/internal/core/) или пока не начат. Правила маркировки — в 50-processes/documentation-standard.md.

Назначение

Поиск товаров по артикулу, описанию, характеристикам, аналогам. Использует read-проекции от Catalog, Offers, Enrichment. Применяет фильтры Visibility. Возвращает ранжированный список с объяснением.

Главный смысл

Search не владеет канонической истиной — это read-only контекст с проекциями. Поэтому Search всегда eventually consistent и не блокирует Catalog/Offers.

Разграничение с proposal pipeline

Каталожный поиск (этот документ) и proposal pipeline (search-proposal.md) — два отдельных use-case внутри search BC. Каталог отвечает на “какие canonical продукты подходят”. Proposal pipeline отвечает на “сколько стоит купить X у поставщиков клиента”. События идут в РАЗНЫЕ топики: search.events.v1 (каталог) и search.proposal.events.v1 (proposal pipeline). См. ADR-0035.

Агрегаты / сущности / value objects

ИмяТипНазначение
SearchQueryVOЗапрос: текст / артикул / фасеты / «не хуже чем».
SearchResult🟩 Read modelРанжированный список кандидатов.
SearchHitVOОдин результат + score + explain.
BetterDirectionVO enumhigher / lower / neutral (для «не хуже чем»).
EmbeddingVectorVODense vector.
RankingProfileEВеса ранжирования (хранятся в ../../20-architecture/schemas/elasticsearch/).
AnalogQueryVOЗапрос аналогов: identity_profile + допуски.

Режимы поиска

  1. Свободный текст — fuzzy по всем полям canonical product и offers.
  2. По артикулу — exact / substring по supplier_sku, mpn, manufacturer_sku_aliases.
  3. Фасетная фильтрацияvoltage = 220V AND ip_rating = IP67.
  4. Подбор аналогов — для известного canonical: same Identity Profile + match critical attrs.
  5. «Не хуже чем» — для каждого числового параметра: направление + порог.
  6. Семантический — embeddings (hybrid: BM25 + cosine).

Доменные события

СобытиеПричина
ПоисковыйЗапросВыполнен (SearchExecuted)Любой запрос пользователя
АналогиЗапрошены (AnalogsRequested)Запрос аналогов для canonical
ИндексОбновлён (SearchIndexUpdated)Из событий Catalog / Offers / Enrichment
РанжированиеИзменено (RankingProfileChanged)Operator
ОбъяснениеЗапрошено (ExplanationRequested)_explain для админ UI

Команды

КомандаАкторЦелевой агрегатРезультат
Найти (Search)Customer / EstimateSearchResult + SearchExecuted
НайтиАналоги (FindAnalogs)Customer / EstimateSearchResult + AnalogsRequested
ПереиндексироватьCanonical (ReindexCanonical)ConsumerSearchIndexUpdated
ИзменитьВеса (UpdateRankingProfile)Operator (PR)RankingProfileRankingProfileChanged

Политики

ТриггерРеакция
CanonicalCreated (Catalog)ReindexCanonical
CanonicalCharacteristicsChanged (Catalog)ReindexCanonical (partial update)
CanonicalLifecycleStatusChanged=discontinued→ mark в индексе (показывать с пометкой)
EmbeddingComputed (Enrichment)→ update vector в индексе
OfferObservationRecorded (Offers) — наличие/цена изменились→ update boost-полей в индексе
EquivalenceClassChanged→ update analog-graph index

Read-модели

  • 🟩 canonical_index (Elasticsearch) — основной индекс с BM25 + dense_vector.
  • 🟩 offer_search_index (ES) — supplier_sku / characteristics.
  • 🟩 analog_graph (PG/ES) — для подбора аналогов.

Инварианты

  1. Search никогда не источник истины — только read-проекция. Восстанавливается replay’ем событий Catalog / Offers / Enrichment.
  2. Discontinued canonical показывается с явной меткой и ссылкой на replaced_by.
  3. match_confidence = weak показывается с меткой «не проверено».
  4. Pricing_mode = on_request фильтруется на уровне API с явной опцией «включая on_request».
  5. Visibility-фильтр применяется ДО ранжирования (исключает запрещённые документы из выборки).
  6. Ranking weights — версионируются, изменения только через PR.

Ранжирование

Порядок результатов в свободном поиске (порядок убывания вклада):

  1. Точное совпадение по MPN или supplier_sku (boost).
  2. BM25 по имени и описанию.
  3. Близость по характеристикам (если парсер извлёк числа из запроса).
  4. Наличие у поставщиков (есть остаток / нет).
  5. Свежесть данных (last_seen_at).

Веса в ../../20-architecture/schemas/elasticsearch/.

Интеграционные события (публикуем)

Топик: search.events.v1. Partition key: query_hash.

ИмяКогда
SearchExecutedАналитика популярных запросов
AnalogsRequestedДля feedback loop матчинга
RankingProfileChangedИзменения весов

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

ИсточникСобытиеРеакция
CatalogCanonicalCreated, CanonicalCharacteristicsChanged, CanonicalLifecycleStatusChangedReindex
OffersOfferObservationRecordedUpdate boost-полей
EnrichmentEmbeddingComputedUpdate vector
VisibilityVisibilityPolicyChangedInvalidate per-subject filter compilation

Связи в context map

BCПаттернНазначение
CatalogConformistПринимает модель Catalog как есть
OffersConformist
EnrichmentConformistEmbeddings
VisibilitySKФильтр на запросе
EstimateOHS (Search → Estimate)Estimate орк-strator вызывает Search

Мини event storming

flowchart LR
    subgraph C["Catalog"]
        CC["🟧 CanonicalCreated"]
        CCH["🟧 CanonicalCharacteristicsChanged"]
    end
    subgraph EN["Enrichment"]
        EM["🟧 EmbeddingComputed"]
    end
    subgraph OFF["Offers"]
        OOB["🟧 OfferObservationRecorded"]
    end
    subgraph S["Search"]
        IDX["🟩 canonical_index"]
        CMD["🟦 Search"]
        RES["🟩 SearchResult"]
        E1["🟧 SearchExecuted"]
    end
    subgraph V["Visibility"]
        VP["🟩 CompiledPolicy"]
    end
    subgraph U["Customer / Estimate"]
        US["🟫 Actor"]
    end

    CC -.PL.-> IDX
    CCH -.PL.-> IDX
    EM -.PL.-> IDX
    OOB -.PL.-> IDX
    US --> CMD --> RES --> E1
    IDX -. read .-> CMD
    VP -. filter .-> CMD

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