(high) normalize.py/normalize_address: асимметричное раскрытие ' д '->' дом ' даёт разные ключи для одного дома #1534

Closed
opened 2026-06-15 05:59:25 +00:00 by bot-backend · 1 comment
Collaborator

Severity: high · Категория: logic · Файл: tradein-mvp/backend/app/services/matching/normalize.py:36-38

normalize.py:37 (правило (' д ', ' дом '), аналогично ' к '->' корпус ' на :38). Маркер 'д'/'к' раскрывается в 'дом'/'корпус' только если источник записал номер через отдельный токен. SERP (Avito/Yandex serp) пишут 'Ленина 5' (голое число), а detail/БТИ — 'д. 5' -> токен 'д'. Проверено: normalize_address('ул. Ленина, д. 5')='улица ленина дом 5', normalize_address('улица Ленина 5')='улица ленина 5'. Импакт: разные normalized_address -> разные fingerprint -> промах Tier 2a (fingerprint) и Tier 2b (normalized_address) в houses.py:118-139; при дрожании координат >30 м промахивается и Tier 3 -> INSERT дубля дома (houses.py:197). Подрывает дедупликацию между источниками — ровно то, для чего нужен 3-tier. Как чинить: нормализовать к ЕДИНОЙ канонической форме — либо выкидывать токены 'дом'/'корпус'/'д'/'к' целиком, оставляя только номер, либо наоборот вставлять 'дом' перед первым числом-номером независимо от наличия маркера. Покрыть тестами симметрии serp-vs-detail форм.

Почему баг: normalize_address — источник ключа для fingerprint (address_fingerprint) и для Tier 2b (normalized_address). Цель функции по docstring — 'stable, source-independent keys'. Асимметричная вставка 'дом'/'корпус' зависит от того, как конкретный скрапер записал номер, поэтому ключ НЕ source-independent: два описания одного физического дома дают два разных канонических ключа. Это рассинхрон дедупликации между Avito/Cian/Yandex и приводит к дубликатам в houses (ровно та проблема, которую вся 3-tier логика призвана устранить).

Как чинить: Не вставлять слова 'дом'/'корпус' при раскрытии; вместо этого ВЫРЕЗАТЬ токены-маркеры 'д'/'к'/'корп'/'стр' (привести всё к чистому '<улица> <число> <число>' без слов-маркеров), либо нормализовать обе формы к одной (всегда вставлять 'дом' перед числом-номером). Ключ должен быть инвариантен к наличию/отсутствию слова 'дом'.


deep-audit «армия ботов 2» · verify+harden, confidence 0.9 · unit U130

**Severity:** high · **Категория:** logic · **Файл:** `tradein-mvp/backend/app/services/matching/normalize.py:36-38` normalize.py:37 (правило (' д ', ' дом '), аналогично ' к '->' корпус ' на :38). Маркер 'д'/'к' раскрывается в 'дом'/'корпус' только если источник записал номер через отдельный токен. SERP (Avito/Yandex serp) пишут 'Ленина 5' (голое число), а detail/БТИ — 'д. 5' -> токен 'д'. Проверено: normalize_address('ул. Ленина, д. 5')='улица ленина дом 5', normalize_address('улица Ленина 5')='улица ленина 5'. Импакт: разные normalized_address -> разные fingerprint -> промах Tier 2a (fingerprint) и Tier 2b (normalized_address) в houses.py:118-139; при дрожании координат >30 м промахивается и Tier 3 -> INSERT дубля дома (houses.py:197). Подрывает дедупликацию между источниками — ровно то, для чего нужен 3-tier. Как чинить: нормализовать к ЕДИНОЙ канонической форме — либо выкидывать токены 'дом'/'корпус'/'д'/'к' целиком, оставляя только номер, либо наоборот вставлять 'дом' перед первым числом-номером независимо от наличия маркера. Покрыть тестами симметрии serp-vs-detail форм. **Почему баг:** normalize_address — источник ключа для fingerprint (address_fingerprint) и для Tier 2b (normalized_address). Цель функции по docstring — 'stable, source-independent keys'. Асимметричная вставка 'дом'/'корпус' зависит от того, как конкретный скрапер записал номер, поэтому ключ НЕ source-independent: два описания одного физического дома дают два разных канонических ключа. Это рассинхрон дедупликации между Avito/Cian/Yandex и приводит к дубликатам в houses (ровно та проблема, которую вся 3-tier логика призвана устранить). **Как чинить:** Не вставлять слова 'дом'/'корпус' при раскрытии; вместо этого ВЫРЕЗАТЬ токены-маркеры 'д'/'к'/'корп'/'стр' (привести всё к чистому '<улица> <число> <число>' без слов-маркеров), либо нормализовать обе формы к одной (всегда вставлять 'дом' перед числом-номером). Ключ должен быть инвариантен к наличию/отсутствию слова 'дом'. --- <sub>deep-audit «армия ботов 2» · verify+harden, confidence 0.9 · unit U130</sub>
bot-backend added the
week ревью 1
label 2026-06-15 05:59:25 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:02:19 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода: tradein-mvp/backend/app/services/matching/normalize.py:37 ((' д ', ' дом ')) и :38 ((' к ', ' корпус ')), применяются безусловным s.replace(short, full) в цикле :60-61.

Корень: Раскрытие маркера зависит от того, записал ли источник номер отдельным токеном д/к. SERP-форма без маркера остаётся «голым» числом → другой normalized_address → другой fingerprint → промах Tier 2a/2b в houses.py:119-139.

Эмпирически воспроизведено в этом прогоне (запуск normalize_address из app/services/matching/normalize.py):

  • normalize_address('ул. Ленина, д. 5')'улица ленина дом 5'
  • normalize_address('улица Ленина 5')'улица ленина 5'

Разные ключи для одного дома — функция перестаёт быть source-independent (нарушение docstring :3).

Что править:

  • Привести обе формы к одной канонической: либо ВЫРЕЗАТЬ токены-маркеры д/к/корп/стр/дом/корпус целиком (оставив <улица> <число> <число>), либо ВСЕГДА вставлять дом перед первым числом-номером независимо от наличия маркера. Ключ должен быть инвариантен к наличию слова «дом».
  • Координировать с фиксом #1535 (тот же _ABBREV блок :37-38) — желательно один PR на оба, иначе конфликт по файлу.

Что посмотреть:

  • Код: normalize.py:_ABBREV :20-39 и normalize_address :42-64; call-sites houses.py:118 (norm_addr), :58 (fp); тесты добавить в tests/test_matching.py (рядом с test_normalize_expands_ul_abbreviation:29) — кейсы симметрии serp-vs-detail.
  • Vault: релевантных заметок по этому симптому не найдено (есть только sber/forecasting rollback-заметки в inbox, не про normalize).
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** `tradein-mvp/backend/app/services/matching/normalize.py:37` (`(' д ', ' дом ')`) и `:38` (`(' к ', ' корпус ')`), применяются безусловным `s.replace(short, full)` в цикле `:60-61`. **Корень:** Раскрытие маркера зависит от того, записал ли источник номер отдельным токеном `д`/`к`. SERP-форма без маркера остаётся «голым» числом → другой `normalized_address` → другой `fingerprint` → промах Tier 2a/2b в `houses.py:119-139`. Эмпирически воспроизведено в этом прогоне (запуск `normalize_address` из `app/services/matching/normalize.py`): - `normalize_address('ул. Ленина, д. 5')` → `'улица ленина дом 5'` - `normalize_address('улица Ленина 5')` → `'улица ленина 5'` Разные ключи для одного дома — функция перестаёт быть source-independent (нарушение docstring `:3`). **Что править:** - Привести обе формы к одной канонической: либо ВЫРЕЗАТЬ токены-маркеры `д`/`к`/`корп`/`стр`/`дом`/`корпус` целиком (оставив `<улица> <число> <число>`), либо ВСЕГДА вставлять `дом` перед первым числом-номером независимо от наличия маркера. Ключ должен быть инвариантен к наличию слова «дом». - Координировать с фиксом #1535 (тот же `_ABBREV` блок `:37-38`) — желательно один PR на оба, иначе конфликт по файлу. **Что посмотреть:** - Код: `normalize.py:_ABBREV` `:20-39` и `normalize_address` `:42-64`; call-sites `houses.py:118` (`norm_addr`), `:58` (`fp`); тесты добавить в `tests/test_matching.py` (рядом с `test_normalize_expands_ul_abbreviation:29`) — кейсы симметрии serp-vs-detail. - Vault: релевантных заметок по этому симптому не найдено (есть только sber/forecasting rollback-заметки в inbox, не про normalize).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1534
No description provided.