Оценка: дедуп склеивает улицы вида «40-летия Комсомола», пороги Tier H — в настройки для бэктеста #3552
2 changed files with 78 additions and 4 deletions
|
|
@ -7330,15 +7330,17 @@ _DEDUP_STREET_TYPE_ALT = "|".join(
|
||||||
# «<тип>[.] <название>»: название = опциональная ведущая цифро-группа + 1-3
|
# «<тип>[.] <название>»: название = опциональная ведущая цифро-группа + 1-3
|
||||||
# буквенных слова, до запятой/цифры. Ведущая цифра нужна для нумерованных улиц
|
# буквенных слова, до запятой/цифры. Ведущая цифра нужна для нумерованных улиц
|
||||||
# («8 Марта», «40 лет Октября», «22 Партсъезда»; #2291 класс 4 — и через дефис:
|
# («8 Марта», «40 лет Октября», «22 Партсъезда»; #2291 класс 4 — и через дефис:
|
||||||
# «40-летия Комсомола», «22-го Партсъезда»); она разрешена ТОЛЬКО перед
|
# «40-летия Комсомола», «22-го Партсъезда»; однобуквенный порядковый «2-я
|
||||||
# буквенным словом (?:\d+(?:\s+|-))?[а-яё]…, поэтому НОМЕР ДОМА (число ПОСЛЕ названия,
|
# Новосибирская», «3-й Конный», в т.ч. перед нумерованным именем «2-й переулок
|
||||||
|
# 1 Мая»); она разрешена ТОЛЬКО перед буквенным словом
|
||||||
|
# (?:\d+-[а-яё]\s+)?(?:\d+(?:\s+|-))?[а-яё]…, поэтому НОМЕР ДОМА (число ПОСЛЕ названия,
|
||||||
# не сопровождаемое буквой) в токен не попадает: «Белинского 86» → «белинского».
|
# не сопровождаемое буквой) в токен не попадает: «Белинского 86» → «белинского».
|
||||||
# #2269 (б): тип-суффикс может быть приклеен запятой/точкой без пробела
|
# #2269 (б): тип-суффикс может быть приклеен запятой/точкой без пробела
|
||||||
# («ул.,6к1»): после `\.?` разрешаем ЛИБО пробел (обычный формат), ЛИБО запятую
|
# («ул.,6к1»): после `\.?` разрешаем ЛИБО пробел (обычный формат), ЛИБО запятую
|
||||||
# (→ следом номер дома). Ветка «тип ПЕРЕД именем» — только для формата с пробелом.
|
# (→ следом номер дома). Ветка «тип ПЕРЕД именем» — только для формата с пробелом.
|
||||||
_DEDUP_STREET_NAME_RE = re.compile(
|
_DEDUP_STREET_NAME_RE = re.compile(
|
||||||
rf"(?:^|[,\s])(?:{_DEDUP_STREET_TYPE_ALT})\.?\s+"
|
rf"(?:^|[,\s])(?:{_DEDUP_STREET_TYPE_ALT})\.?\s+"
|
||||||
r"((?:\d+(?:\s+|-))?[а-яё][а-яё\-]+(?:\s+[а-яё][а-яё\-]+){0,2})",
|
r"((?:\d+-[а-яё]\s+)?(?:\d+(?:\s+|-))?[а-яё][а-яё\-]+(?:\s+[а-яё][а-яё\-]+){0,2})",
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
# #2269 (а): «<название> <тип>» — тип-слово ПОСЛЕ имени (формат domklik:
|
# #2269 (а): «<название> <тип>» — тип-слово ПОСЛЕ имени (формат domklik:
|
||||||
|
|
@ -7350,10 +7352,19 @@ _DEDUP_STREET_NAME_RE = re.compile(
|
||||||
# Рассветная». Тип НЕ входит в захват (group 1) — токен байт-в-байт совпадает.
|
# Рассветная». Тип НЕ входит в захват (group 1) — токен байт-в-байт совпадает.
|
||||||
_DEDUP_STREET_NAME_SUFFIX_RE = re.compile(
|
_DEDUP_STREET_NAME_SUFFIX_RE = re.compile(
|
||||||
r"(?:^|[,\s])"
|
r"(?:^|[,\s])"
|
||||||
r"((?:\d+(?:\s+|-))?[а-яё][а-яё\-]+(?:\s+[а-яё][а-яё\-]+){0,2})\s+"
|
r"((?:\d+-[а-яё]\s+)?(?:\d+(?:\s+|-))?[а-яё][а-яё\-]+(?:\s+[а-яё][а-яё\-]+){0,2})\s+"
|
||||||
rf"(?:{_DEDUP_STREET_TYPE_ALT})(?=\.|,|\s|$)",
|
rf"(?:{_DEDUP_STREET_TYPE_ALT})(?=\.|,|\s|$)",
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
# #2291 класс 4: третий формат порядковой улицы — номер ПЕРЕД типом («8-я улица
|
||||||
|
# Текстильщиков», «2-я ул. Синичкина», «12-й пр. Марьиной Рощи»). Переставляем в
|
||||||
|
# «улица 8-я текстильщиков», чтобы он дал тот же токен, что «улица 8-я Текстильщиков»
|
||||||
|
# и «8-я Текстильщиков улица». Без перестановки ветка 1 взяла бы «текстильщиков»
|
||||||
|
# без номера, а номер нельзя терять: «9-я Парковая, 5» и «12-я Парковая, 5» — разные дома.
|
||||||
|
_DEDUP_ORDINAL_BEFORE_TYPE_RE = re.compile(
|
||||||
|
rf"(^|[,\s])(\d+-[а-яё]{{1,2}})\s+((?:{_DEDUP_STREET_TYPE_ALT})\.?)\s+",
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
)
|
||||||
# #2269 (в): fallback без уличного типа — «микрорайон/мкр/мкр-н/м-н <имя>» или
|
# #2269 (в): fallback без уличного типа — «микрорайон/мкр/мкр-н/м-н <имя>» или
|
||||||
# «<имя> микрорайон». Имя = 1-3 буквенных слова. Токен получает префикс «mkr »,
|
# «<имя> микрорайон». Имя = 1-3 буквенных слова. Токен получает префикс «mkr »,
|
||||||
# чтобы НЕ пересекаться с уличными токенами (микрорайон ≠ одноимённая улица).
|
# чтобы НЕ пересекаться с уличными токенами (микрорайон ≠ одноимённая улица).
|
||||||
|
|
@ -7539,6 +7550,7 @@ def _parse_street_house(addr: str | None) -> tuple[str, str]:
|
||||||
# #2291 (класс 2): «ё» → «е» — «Шевелёва»/«Шевелева» и «Миномётчиков»/
|
# #2291 (класс 2): «ё» → «е» — «Шевелёва»/«Шевелева» и «Миномётчиков»/
|
||||||
# «Минометчиков» должны схлопываться в один канонический токен.
|
# «Минометчиков» должны схлопываться в один канонический токен.
|
||||||
s = s.replace("ё", "е")
|
s = s.replace("ё", "е")
|
||||||
|
s = _DEDUP_ORDINAL_BEFORE_TYPE_RE.sub(r"\1\3 \2 ", s)
|
||||||
# Ветка 1 (существующая, #2265): тип ПЕРЕД именем («улица Рассветная»).
|
# Ветка 1 (существующая, #2265): тип ПЕРЕД именем («улица Рассветная»).
|
||||||
# Пробуется ПЕРВОЙ — новые ветки только если она дала пусто. Логика не тронута.
|
# Пробуется ПЕРВОЙ — новые ветки только если она дала пусто. Логика не тронута.
|
||||||
m = _DEDUP_STREET_NAME_RE.search(s)
|
m = _DEDUP_STREET_NAME_RE.search(s)
|
||||||
|
|
|
||||||
|
|
@ -1040,3 +1040,65 @@ def test_dedup_hyphen_numbered_street_different_houses_stay_distinct() -> None:
|
||||||
for h in ("8А", "35")
|
for h in ("8А", "35")
|
||||||
]
|
]
|
||||||
assert len(estimator._dedup_cross_source(lots)) == 2
|
assert len(estimator._dedup_cross_source(lots)) == 2
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# #2291 класс 4 — однобуквенные порядковые («2-я Новосибирская», «3-й Конный»)
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("addr", "expected"),
|
||||||
|
[
|
||||||
|
# Три формата одной улицы (живые адреса с прода, tradein-postgres, 17.09):
|
||||||
|
# cian «тип номер имя» раньше давал ('', ''), avito/yandex «номер тип имя» и
|
||||||
|
# domklik «номер имя тип» теряли номер ('синичкина').
|
||||||
|
("2-я ул. Синичкина,22", ("2-я синичкина", "22")), # avito
|
||||||
|
("Москва, 2-я Синичкина улица, 22", ("2-я синичкина", "22")), # domklik
|
||||||
|
("Россия, Москва, 2-я улица Синичкина, 22", ("2-я синичкина", "22")), # yandex
|
||||||
|
("улица 2-я Новосибирская, 5", ("2-я новосибирская", "5")),
|
||||||
|
("пер. 3-й Конный, 7", ("3-й конный", "7")),
|
||||||
|
("ул. 1-й Пятилетки, 3", ("1-й пятилетки", "3")),
|
||||||
|
("12-й пр. Марьиной Рощи,8с2", ("12-й марьиной рощи", "8")),
|
||||||
|
# Раньше мкр-ветка: ('mkr перловка', '1') — номер улицы вместо дома.
|
||||||
|
("мкр. Перловка, улица 1-я Крестьянская, 33к2", ("1-я крестьянская", "33/2")),
|
||||||
|
# Порядковый перед нумерованным именем: без него было бы ('', '').
|
||||||
|
("2-й пер. 1 Мая,13", ("2-й 1 мая", "13")),
|
||||||
|
(
|
||||||
|
"Россия, Московская область, Павловский Посад, 2-й переулок 1 Мая, 11",
|
||||||
|
("2-й 1 мая", "11"),
|
||||||
|
),
|
||||||
|
# Регрессии: номер дома с дефисом остаётся номером дома.
|
||||||
|
("ул. Ленина, 12-а", ("ленина", "12")),
|
||||||
|
("ул. Щорса, 7-б", ("щорса", "7")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_parse_street_house_single_letter_ordinal(addr: str, expected: tuple[str, str]) -> None:
|
||||||
|
assert estimator._parse_street_house(addr) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_dedup_collapses_single_letter_ordinal_three_formats() -> None:
|
||||||
|
addrs = [
|
||||||
|
("avito", "2-я ул. Синичкина,22"),
|
||||||
|
("domklik", "Москва, 2-я Синичкина улица, 22"),
|
||||||
|
("yandex", "Россия, Москва, 2-я улица Синичкина, 22"),
|
||||||
|
("cian", "ЮВАО, р-н Лефортово, улица 2-я Синичкина, 22"),
|
||||||
|
]
|
||||||
|
lots = [
|
||||||
|
_lot(source=s, address=a, area=44.0, price=9_900_000, floor=3, cad=None) for s, a in addrs
|
||||||
|
]
|
||||||
|
assert len(estimator._dedup_cross_source(lots)) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_dedup_different_ordinal_streets_stay_distinct() -> None:
|
||||||
|
# Раньше domklik терял номер: «9-я Парковая» и «12-я Парковая» обе давали
|
||||||
|
# ('парковая', '5'), и квартиры с одинаковыми этажом/площадью/ценой в двух
|
||||||
|
# разных домах Измайлова сливались в одну.
|
||||||
|
lots = [
|
||||||
|
_lot(source=s, address=a, area=44.0, price=9_900_000, floor=3, cad=None)
|
||||||
|
for s, a in (
|
||||||
|
("domklik", "Москва, 9-я Парковая улица, 5"),
|
||||||
|
("domklik", "Москва, 12-я Парковая улица, 5"),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
assert len(estimator._dedup_cross_source(lots)) == 2
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue