fix(tradein/estimator): сузить bare-street ветку до 3-сегментного district-chain паттерна (#2291)
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 1m0s

Deep-review нашёл over-merge риск: bare-street ветка фабриковала уличный токен на голых
2-сегментных адресах без mkr/район-контекста ("ВИЗ, 24" и т.п.) — теперь требует явный
mkr-маркер в более раннем сегменте, ограничивая ветку заявленным 3-сегментным паттерном.
This commit is contained in:
bot-backend 2026-07-04 15:45:42 +03:00
parent 859d4fa317
commit 0de77690e9
2 changed files with 42 additions and 2 deletions

View file

@ -4987,7 +4987,7 @@ _DEDUP_STREET_TYPE_WORDS_SET = frozenset(_DEDUP_STREET_TYPE_WORDS)
def _bare_street_before_house_segment(s: str) -> tuple[str, str] | None:
"""Bare-street ветка (#2291, класс 1): «<имя>, <номер>» без типа/мкр-слова.
"""Bare-street ветка (#2291, класс 1): «<мкр-сегмент>, <имя>, <номер>».
Root cause: `_DEDUP_MKR_SUFFIX_RE` матчит «<имя> м-н/мкр» ГДЕ УГОДНО в
строке. Для DomClick-формата «Заречный м-н, Готвальда, 24» голая улица
@ -4999,6 +4999,17 @@ def _bare_street_before_house_segment(s: str) -> tuple[str, str] | None:
Разбивает адрес по запятым; ищет ПЕРВЫЙ сегмент, ЦЕЛИКОМ совпадающий с
`_DEDUP_HOUSE_NO_RE` (номер дома). Сегмент НЕПОСРЕДСТВЕННО перед ним 1-3
слова, без мкр-слова/уличного типа/шум-токена кандидат в bare-улицу.
#2291 over-merge guard (deep-review, blocking): ветка активируется ТОЛЬКО
для 3+ сегментного district-chain паттерна обязателен ЕЩЁ более ранний
сегмент (до кандидата-улицы), который сам содержит мкр/район-слово
(`_DEDUP_MKR_WORDS`). Без этого условия голые 2-сегментные адреса без
какого-либо mkr-контекста («ВИЗ, 24», «Академический, 24», «ЖК Некий,
24» частый неформальный ЕКБ-шорткат в объявлениях) фабриковали бы
уличный токен из названия района/ЖК и рисковали ложным cross-merge двух
РАЗНЫХ физлотов. Такие адреса должны вернуть None (решают ветки 1/2/3;
итог безопасный `("", "")`, как и до всей этой ветки).
Не найдено/не подходит None (решают ветки 1/2/3, в частности mkr-fallback
для «мкр Пионерский, 5», где сегмент перед номером содержит мкр-слово).
"""
@ -5010,7 +5021,16 @@ def _bare_street_before_house_segment(s: str) -> tuple[str, str] | None:
hm = _DEDUP_HOUSE_NO_RE.fullmatch(house_seg)
if hm is None:
continue
raw_words = segments[idx - 1].strip().split()
street_idx = idx - 1
earlier_segments = segments[:street_idx]
if not earlier_segments or not any(
any(w in _DEDUP_MKR_WORDS for w in seg.strip().split()) for seg in earlier_segments
):
# Нет более раннего сегмента с mkr/район-словом → это не заявленный
# 3-сегментный district-chain, а голый 2-сегментный адрес — не
# фабрикуем уличный токен, отдаём другим веткам (итог — "", "").
continue
raw_words = segments[street_idx].strip().split()
if not raw_words or len(raw_words) > 3:
continue
if any(

View file

@ -569,6 +569,26 @@ def test_parse_street_house_bare_street_regression_guards() -> None:
assert estimator._parse_street_house("мкр. ЖБИ, улица Сыромолотова") == ("сыромолотова", "")
@pytest.mark.parametrize(
"addr",
[
# Deep-review over-merge risk (#2291): голый 2-сегментный адрес БЕЗ
# какого-либо mkr/район-контекста — частый неформальный ЕКБ-шорткат в
# объявлениях (район/ЖК + номер дома, без слова «улица»/«мкр»/«м-н»).
# Bare-street ветка НЕ должна фабриковать уличный токен из района/ЖК —
# нет более раннего сегмента с mkr-словом → должно остаться ("", "").
"ВИЗ, 24",
"Академический, 24",
"ЖК Некий, 24",
],
)
def test_parse_street_house_bare_street_requires_earlier_mkr_segment(addr: str) -> None:
# Тот же bare-2-сегментный паттерн, что у целевого кейса («<имя>, <номер>»),
# но БЕЗ более раннего mkr-сегмента — ветка должна самоограничиться и
# вернуть безопасный ("", ""), а не сфабрикованный уличный токен.
assert estimator._parse_street_house(addr) == ("", "")
def test_dedup_collapses_domclick_bare_street_mkr_misfire(monkeypatch: pytest.MonkeyPatch) -> None:
# Класс 1 (#2291) e2e: DomClick «Заречный м-н, Готвальда, 24» раньше
# ключевался по району и никогда не сливался с avito/cian, где «готвальда»