fix(tradein/estimator): полный сьют зелёный после дедуп-ключа (#2265)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 12s
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 2m20s

This commit is contained in:
bot-backend 2026-07-03 10:48:15 +03:00
parent 9cdc7cef21
commit 6f81ee73d7

View file

@ -4586,8 +4586,10 @@ _DEDUP_PRICE_BUCKET_RUB = 100_000
# Типы улиц (для отсечения от названия). District-маркеры (мкр/м-н/квартал/
# район) СЮДА НЕ входят — иначе «мкр. ЖБИ, улица Сыромолотова» матчилось бы на
# «мкр ЖБИ» вместо реальной улицы; они убираются как шум в _ADDR_NOISE_WORDS.
_STREET_TYPE_WORDS = (
# «мкр ЖБИ» вместо реальной улицы; они убираются как шум в _DEDUP_ADDR_NOISE_WORDS.
# NB: имена с префиксом _DEDUP_ — чтобы НЕ конфликтовать с модульным
# _STREET_NAME_RE (extract_street_name, другой парсер выше).
_DEDUP_STREET_TYPE_WORDS = (
"улица",
"ул",
"переулок",
@ -4613,30 +4615,32 @@ _STREET_TYPE_WORDS = (
"линия",
)
# Длинные типы раньше коротких, чтобы «улица» матчилось раньше «ул».
_STREET_TYPE_ALT = "|".join(re.escape(w) for w in sorted(_STREET_TYPE_WORDS, key=len, reverse=True))
_DEDUP_STREET_TYPE_ALT = "|".join(
re.escape(w) for w in sorted(_DEDUP_STREET_TYPE_WORDS, key=len, reverse=True)
)
# «<тип>[.] <название>»: название = опциональная ведущая цифро-группа + 1-3
# буквенных слова, до запятой/цифры. Ведущая цифра нужна для нумерованных улиц
# («8 Марта», «40 лет Октября», «22 Партсъезда»); она разрешена ТОЛЬКО перед
# буквенным словом (?:\d+\s+)?[а-яё]…, поэтому НОМЕР ДОМА (число ПОСЛЕ названия,
# не сопровождаемое буквой) в токен не попадает: «Белинского 86» → «белинского».
_STREET_NAME_RE = re.compile(
rf"(?:^|[,\s])(?:{_STREET_TYPE_ALT})\.?\s+"
_DEDUP_STREET_NAME_RE = re.compile(
rf"(?:^|[,\s])(?:{_DEDUP_STREET_TYPE_ALT})\.?\s+"
r"((?:\d+\s+)?[а-яё][а-яё\-]+(?:\s+[а-яё][а-яё\-]+){0,2})",
flags=re.IGNORECASE,
)
# Номер дома = первое число ПОСЛЕ названия улицы (опц. «д.»/«дом»), с литерой/
# корпусом («46», «8а», «12/3»). Ищется в остатке строки от конца street-матча —
# ведущая цифра нумерованной улицы («8 Марта») сюда не попадает.
_HOUSE_NO_RE = re.compile(
_DEDUP_HOUSE_NO_RE = re.compile(
r"\b(?:д(?:ом)?\.?\s*)?(\d+(?:[а-яё]|/\d+)?)\b",
flags=re.IGNORECASE,
)
# Слова-маркеры дома, которые могли попасть в захват названия (напр. «ленина д 5»).
_HOUSE_MARKER_WORDS = frozenset(
_DEDUP_HOUSE_MARKER_WORDS = frozenset(
{"дом", "д", "корпус", "корп", "к", "строение", "стр", "литера", "лит", "владение", "вл"}
)
# Шум-токены (город/страна/район/микрорайон) на случай, если попали в название.
_ADDR_NOISE_WORDS = frozenset(
_DEDUP_ADDR_NOISE_WORDS = frozenset(
{
"россия",
"рф",
@ -4666,17 +4670,17 @@ def _parse_street_house(addr: str | None) -> tuple[str, str]:
if not addr:
return "", ""
s = addr.strip().lower()
m = _STREET_NAME_RE.search(s)
m = _DEDUP_STREET_NAME_RE.search(s)
if not m:
return "", ""
words = [
w
for w in m.group(1).split()
if w and w not in _HOUSE_MARKER_WORDS and w not in _ADDR_NOISE_WORDS
if w and w not in _DEDUP_HOUSE_MARKER_WORDS and w not in _DEDUP_ADDR_NOISE_WORDS
]
street = " ".join(words).strip()
house = ""
hm = _HOUSE_NO_RE.search(s, m.end())
hm = _DEDUP_HOUSE_NO_RE.search(s, m.end())
if hm:
house = hm.group(1)
return street, house