ADR-0070: Идентичность матчинга = (manufacturer_id, артикул); матч по одному артикулу не является точным
Status: accepted Date: 2026-07-08 Deciders: Maxim Belkanov
Поправка (2026-07-09): первопричина коллизии «10127» — НЕ матчер, а провижинер каноника на ингесте:
canonicalIdentityKeyстроит"mpn|"+article(производитель игнорируется), иEnsureForOfferкладёт офферы в детерминированныйcanonical_id = uuid5(ns, ключ). Доказано:uuid5(ns, "mpn|10127")= прод-каноник18f21aa4.exact_mpn-решения матчера — no-op поверх уже склеенного. Масштаб: ~98k слитых каноников / 220k офферов. Этот ADR (matcher identity) — part 1 (необходим, недостаточен). Первопричина и full re-key — ADR-0071 + спека2026-07-09-canonical-identity-manufacturer-scoped-design.md.
Контекст
Матчер склеивал в один канонический товар офферы разных товаров разных
производителей, совпавшие лишь по номеру артикула. Живой прод-кейс: артикул
«10127» — не мусор, а настоящий, но неуникальный между производителями номер
(Конкорд-кабель, Тахион-прожектор, Maytoni-лента, ПТК-защёлка, Helukabel-кабель
независимо используют его). Пять из шести офферов получили статус exact_mpn
(confidence exact, LLM не вызывался), хотя это физически разные товары.
Три сцепленных дефекта в BC матчинга:
- Ключ бренда — внутренний код, а не бренд. Матчер брал производителя из
manufacturer_code(пуст у russvet, «10127»-мусор у etm), а не изmanufacturer_name/manufacturer_id. - ASCII-нормализация обнуляет кириллицу.
NormalizeForMatchи SQLregexp_replace(..., '[^a-z0-9]', ...)режут кириллические бренды (Конкорд, Тахион) в пустую строку с обеих сторон. Строить идентичность на нормализации имени нельзя. - Пустой бренд короткозамыкал в матч по одному артикулу, и статус
exact/strongтерминален (не переигрывается), поэтому ошибка не исправлялась даже после дозаполнения бренда.
manufacturer_id — резолвенный FK из manufacturers (auto_mfr по lower(name),
миграция 0178), script-agnostic и alias-способный, — доступен матчеру и является
корректным ключом идентичности бренда.
Решение
- Ключ точной идентичности — пара (
manufacturer_id, нормализованный артикул). Артикул производителя уникален только внутри производителя. Матч по артикулу без непустого совпадающегоmanufacturer_idне может бытьexact. Идентичность бренда строится наmanufacturer_id(равенство UUID), не на нормализации строки имени. - Пустой бренд → не
exact, неstrong. Tier1 exact не выполняется при пустомmanufacturer_id; fingerprint даётstrongтолько при согласованном непустом бренде (ManufacturerAgreed), иначе (совпал артикул/категория, но не бренд) остаётсяprobable→ Tier3 LLM. Оффер без бренда остаётсяunmatched(переигрываемо), а не терминально ошибочным. - Гард по категории. Даже при совпадении артикула кандидат отклоняется, если у обеих сторон известна категория и она различается.
manufacturer_idвходит вevidence_hash. Дозаполнение бренда (charnorm/ ETIM резолвитmanufacturer_idпозже первичного матча) меняет hash → hash-гейт переигрывает решение → корректный матч после обогащения.
Последствия
- Точность матчинга выше: неуникальный короткий артикул больше не склеивает разные
бренды. Полнота авто-матча временно ниже (пустобрендовые офферы уходят в Tier3/
unmatchedвместо терминальногоexact), но recheck доматчивает их после дозаполнения бренда; часть нагрузки смещается на Tier3 LLM. - Изменения замкнуты в BC матчинга (impact LOW):
OfferCandidate.ManufacturerID,FingerprintCandidate.ManufacturerAgreed,EvidenceSignals.ManufacturerID, портFindCanonicalsByNormalizedMPN(mpn, manufacturerID, categorySlug, …). Схема БД без миграций —manufacturer_idуже существует. - Ремедиация уже склеенных каноников — отдельный runbook: недостаточно удалить
терминальные решения (оффер сохранит
canonical_id, аcurrentCanonicalDecisionпереподтвердит его). Нужен split/detach: обнулитьsupplier_offers.canonical_id, удалить решения, откатитьcanonical_aliases/status='replaced'от прежних слияний, затем ре-матч + пометка каноников dirty (пере-проекция характеристик).
Известное ограничение
Категорийный гард опирается на CategorySlug. Идентификаторы ОКПД2 / ТН ВЭД
(предложенные как дополнительный негативный сигнал) сейчас не проецируются в
факты: identity-коды выбрасываются до offer_characteristic_facts
(bulk_snapshot_pipeline.go, charvalue_role.go). Усиление гарда этими кодами —
отдельный follow-up, требующий их проекции.
Связано
- ADR-0045 (Tier 3 LLM matcher) — LLM теперь реально арбитрирует неоднозначные
article-only совпадения (раньше
exactего пропускал). - ADR-0044 (charnorm) — источник дозаполнения бренда (
manufacturer_id) после первичного матча.