feat(msk-import): ДомКлик как четвёртый источник переливки сырья в listings
All checks were successful
CI / changes (pull_request) Successful in 11s
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 / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 7m21s
All checks were successful
CI / changes (pull_request) Successful in 11s
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 / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 7m21s
Сбор ДомКлика по Москве пошёл (PR #3496), но переливать его в listings было нечем: в SOURCE_VIEWS стояли только Циан, Авито и Яндекс. Вью msk_raw.domclick_latest в проде уже существует, не хватало двух записей в словарях и распознавания города. ## Город читается первым компонентом адреса У Циана города в адресе нет вовсе и Москву выдаёт префикс округа; у Яндекса регион стоит вторым компонентом после страны. У ДомКлика адрес начинается прямо с города: «Москва, улица Нижние Мнёвники, 16 к2». Замер по 5 024 карточкам первого прохода: первый компонент имеет ровно одно значение — «Москва», областных нет ни одной. Это ожидаемо, сырьё собрано запросом с GUID-ом Москвы и отфильтровано по bbox ещё на стороне сборщика. Поэтому фильтр здесь — вторая линия, а не единственная. Сменится GUID в сборщике или приедет в ту же таблицу второй город — импорт не потащит его в Москву молча, а отложит в skipped_not_moscow. Новая Москва регион не ломает: «Москва, пос. Птичное, …», «Москва, x. Ильичевка, …» — посёлок стоит ВТОРЫМ компонентом, первым всегда город, что и соответствует коду 77. Сравнение идёт по компоненту целиком, а не по префиксу строки: иначе «Московская область» и «Московский» прошли бы как Москва. Оба случая закреплены тестом. ## Тесты Распознавание Москвы, включая две формы Новой Москвы; отбраковка области, Екатеринбурга, похожего на префикс «Московский», пустой строки и None; проводка источника в оба словаря. 45 passed, ruff чистый. Claude-Session: https://claude.ai/code/session_01NQb6WeJtagZwZnUsSjDizs
This commit is contained in:
parent
628f59dcc0
commit
f6938452d0
2 changed files with 53 additions and 0 deletions
|
|
@ -77,10 +77,21 @@ source_id) считает сам кит (`ScrapedLot.compute_dedup_hash`), це
|
||||||
значения, «Москва» 10 610 и «Московская область» 10 783. Координаты у Яндекса
|
значения, «Москва» 10 610 и «Московская область» 10 783. Координаты у Яндекса
|
||||||
заполнены у 100% карточек, поэтому ни геокод, ни `geocode_missing` ему не нужны.
|
заполнены у 100% карточек, поэтому ни геокод, ни `geocode_missing` ему не нужны.
|
||||||
|
|
||||||
|
Отбор Москвы (source=domclick) тоже стоит ноль вызовов, но по другой причине:
|
||||||
|
сырьё собрано запросом с GUID-ом Москвы и отфильтровано по bbox ещё на стороне
|
||||||
|
сборщика, поэтому областных карточек в нём нет по построению. Регион читается
|
||||||
|
ПЕРВЫМ компонентом адреса («Москва, улица Нижние Мнёвники, 16 к2»), и фильтр
|
||||||
|
оставлен второй линией — на случай, если в ту же таблицу когда-нибудь приедет
|
||||||
|
второй город. Замер по 5 024 карточкам первого прохода: единственное значение
|
||||||
|
первого компонента — «Москва». Новая Москва приходит как «Москва, пос. Птичное,
|
||||||
|
…», то есть посёлок стоит вторым компонентом и регион 77 не ломает. Координаты
|
||||||
|
заполнены у 100% карточек.
|
||||||
|
|
||||||
Запуск:
|
Запуск:
|
||||||
python -m app.tasks.msk_raw_import --dry-run
|
python -m app.tasks.msk_raw_import --dry-run
|
||||||
python -m app.tasks.msk_raw_import --limit 500
|
python -m app.tasks.msk_raw_import --limit 500
|
||||||
python -m app.tasks.msk_raw_import --source yandex
|
python -m app.tasks.msk_raw_import --source yandex
|
||||||
|
python -m app.tasks.msk_raw_import --source domclick
|
||||||
python -m app.tasks.msk_raw_import --source avito --geocode --geocode-limit 9000
|
python -m app.tasks.msk_raw_import --source avito --geocode --geocode-limit 9000
|
||||||
python -m app.tasks.msk_raw_import --source avito --allow-unfiltered # аварийный
|
python -m app.tasks.msk_raw_import --source avito --allow-unfiltered # аварийный
|
||||||
"""
|
"""
|
||||||
|
|
@ -160,6 +171,7 @@ SOURCE_VIEWS = {
|
||||||
"cian": "msk_raw.cian_latest",
|
"cian": "msk_raw.cian_latest",
|
||||||
"avito": "msk_raw.avito_latest",
|
"avito": "msk_raw.avito_latest",
|
||||||
"yandex": "msk_raw.yandex_latest",
|
"yandex": "msk_raw.yandex_latest",
|
||||||
|
"domclick": "msk_raw.domclick_latest",
|
||||||
}
|
}
|
||||||
|
|
||||||
_PAGE_SQL = """
|
_PAGE_SQL = """
|
||||||
|
|
@ -485,11 +497,30 @@ def is_moscow_yandex_address(address: str | None) -> bool:
|
||||||
return len(parts) > 1 and parts[1] == "Москва"
|
return len(parts) > 1 and parts[1] == "Москва"
|
||||||
|
|
||||||
|
|
||||||
|
def is_moscow_domclick_address(address: str | None) -> bool:
|
||||||
|
"""У ДомКлика регион — ПЕРВЫЙ компонент адреса: «Москва, улица …».
|
||||||
|
|
||||||
|
Источник собран запросом с GUID-ом Москвы в параметре address и дополнительно
|
||||||
|
отфильтрован по bbox на стороне сборщика, так что областных карточек в сырье
|
||||||
|
и не должно быть. Фильтр здесь — вторая линия: сменится GUID в сборщике или
|
||||||
|
появится второй город в той же таблице — импорт не потащит его в Москву
|
||||||
|
молча. Замер по 5 024 карточкам первого прохода: первый компонент имеет ровно
|
||||||
|
одно значение, «Москва», областных нет ни одной.
|
||||||
|
|
||||||
|
Новая Москва отдельным значением НЕ приходит: «Москва, x. Ильичевка, …»,
|
||||||
|
«Москва, пос. Птичное, …» — посёлок стоит ВТОРЫМ компонентом, первый всегда
|
||||||
|
город, что совпадает с кодом региона 77.
|
||||||
|
"""
|
||||||
|
parts = [part.strip() for part in (address or "").split(",")]
|
||||||
|
return bool(parts) and parts[0] == "Москва"
|
||||||
|
|
||||||
|
|
||||||
# Источники, у которых город виден в самой карточке. Ключ отсутствует —
|
# Источники, у которых город виден в самой карточке. Ключ отсутствует —
|
||||||
# источник про город молчит, и без пред-геокода писать его нельзя (avito).
|
# источник про город молчит, и без пред-геокода писать его нельзя (avito).
|
||||||
CITY_FILTERS = {
|
CITY_FILTERS = {
|
||||||
"cian": is_moscow_address,
|
"cian": is_moscow_address,
|
||||||
"yandex": is_moscow_yandex_address,
|
"yandex": is_moscow_yandex_address,
|
||||||
|
"domclick": is_moscow_domclick_address,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -461,3 +461,25 @@ def test_geocode_is_refused_for_yandex(captured: list[dict[str, Any]]) -> None:
|
||||||
mod.import_msk_raw(
|
mod.import_msk_raw(
|
||||||
_FakeSession([{"id": 1, "payload": _yandex()}]), source="yandex", geocode=True
|
_FakeSession([{"id": 1, "payload": _yandex()}]), source="yandex", geocode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_domclick_moscow_is_recognised_without_geocode() -> None:
|
||||||
|
"""Регион ДомКлика — первый компонент адреса; Новая Москва тоже Москва."""
|
||||||
|
assert mod.is_moscow_domclick_address("Москва, улица Нижние Мнёвники, 16 к2")
|
||||||
|
assert mod.is_moscow_domclick_address("Москва, пос. Птичное, Лесная улица, 82")
|
||||||
|
assert mod.is_moscow_domclick_address("Москва, x. Ильичевка, хутор Ильичёвка, 1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_domclick_non_moscow_is_rejected() -> None:
|
||||||
|
"""Вторая линия обороны: GUID в сборщике сменится — импорт не смолчит."""
|
||||||
|
assert not mod.is_moscow_domclick_address("Московская область, Химки, 9 Мая, 21к3")
|
||||||
|
assert not mod.is_moscow_domclick_address("Екатеринбург, улица Ясная, 2")
|
||||||
|
# «Москва» должна быть отдельным компонентом, а не префиксом другого слова.
|
||||||
|
assert not mod.is_moscow_domclick_address("Московский, 1-я улица, 5")
|
||||||
|
assert not mod.is_moscow_domclick_address("")
|
||||||
|
assert not mod.is_moscow_domclick_address(None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_domclick_view_is_wired_into_source_views() -> None:
|
||||||
|
assert mod.SOURCE_VIEWS["domclick"] == "msk_raw.domclick_latest"
|
||||||
|
assert mod.CITY_FILTERS["domclick"] is mod.is_moscow_domclick_address
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue