Контекст: поиск
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
| Имя | Тип | Назначение |
|---|---|---|
SearchQuery | VO | Запрос: текст / артикул / фасеты / «не хуже чем». |
SearchResult | 🟩 Read model | Ранжированный список кандидатов. |
SearchHit | VO | Один результат + score + explain. |
BetterDirection | VO enum | higher / lower / neutral (для «не хуже чем»). |
EmbeddingVector | VO | Dense vector. |
RankingProfile | E | Веса ранжирования (хранятся в ../../20-architecture/schemas/elasticsearch/). |
AnalogQuery | VO | Запрос аналогов: identity_profile + допуски. |
Режимы поиска
- Свободный текст — fuzzy по всем полям canonical product и offers.
- По артикулу — exact / substring по
supplier_sku,mpn,manufacturer_sku_aliases. - Фасетная фильтрация —
voltage = 220V AND ip_rating = IP67. - Подбор аналогов — для известного canonical: same Identity Profile + match critical attrs.
- «Не хуже чем» — для каждого числового параметра: направление + порог.
- Семантический — embeddings (hybrid: BM25 + cosine).
Доменные события
| Событие | Причина |
|---|---|
ПоисковыйЗапросВыполнен (SearchExecuted) | Любой запрос пользователя |
АналогиЗапрошены (AnalogsRequested) | Запрос аналогов для canonical |
ИндексОбновлён (SearchIndexUpdated) | Из событий Catalog / Offers / Enrichment |
РанжированиеИзменено (RankingProfileChanged) | Operator |
ОбъяснениеЗапрошено (ExplanationRequested) | _explain для админ UI |
Команды
| Команда | Актор | Целевой агрегат | Результат |
|---|---|---|---|
Найти (Search) | Customer / Estimate | — | SearchResult + SearchExecuted |
НайтиАналоги (FindAnalogs) | Customer / Estimate | — | SearchResult + AnalogsRequested |
ПереиндексироватьCanonical (ReindexCanonical) | Consumer | — | SearchIndexUpdated |
ИзменитьВеса (UpdateRankingProfile) | Operator (PR) | RankingProfile | RankingProfileChanged |
Политики
| Триггер | Реакция |
|---|---|
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) — для подбора аналогов.
Инварианты
- Search никогда не источник истины — только read-проекция. Восстанавливается replay’ем событий Catalog / Offers / Enrichment.
- Discontinued canonical показывается с явной меткой и ссылкой на
replaced_by. match_confidence = weakпоказывается с меткой «не проверено».- Pricing_mode = on_request фильтруется на уровне API с явной опцией «включая on_request».
- Visibility-фильтр применяется ДО ранжирования (исключает запрещённые документы из выборки).
- Ranking weights — версионируются, изменения только через PR.
Ранжирование
Порядок результатов в свободном поиске (порядок убывания вклада):
- Точное совпадение по MPN или supplier_sku (boost).
- BM25 по имени и описанию.
- Близость по характеристикам (если парсер извлёк числа из запроса).
- Наличие у поставщиков (есть остаток / нет).
- Свежесть данных (
last_seen_at).
Веса в ../../20-architecture/schemas/elasticsearch/.
Интеграционные события (публикуем)
Топик: search.events.v1. Partition key: query_hash.
| Имя | Когда |
|---|---|
SearchExecuted | Аналитика популярных запросов |
AnalogsRequested | Для feedback loop матчинга |
RankingProfileChanged | Изменения весов |
Подписанные интеграционные события
| Источник | Событие | Реакция |
|---|---|---|
| Catalog | CanonicalCreated, CanonicalCharacteristicsChanged, CanonicalLifecycleStatusChanged | Reindex |
| Offers | OfferObservationRecorded | Update boost-полей |
| Enrichment | EmbeddingComputed | Update vector |
| Visibility | VisibilityPolicyChanged | Invalidate per-subject filter compilation |
Связи в context map
| BC | Паттерн | Назначение |
|---|---|---|
| Catalog | Conformist | Принимает модель Catalog как есть |
| Offers | Conformist | — |
| Enrichment | Conformist | Embeddings |
| Visibility | SK | Фильтр на запросе |
| Estimate | OHS (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
Связанные файлы
catalog.md,offers.md,enrichment.md,visibility.md,estimate.md.- Сценарий:
../scenarios/analog-search.md. - Архитектура:
../../20-architecture/schemas/elasticsearch/(TBD).