Московская область в реестре регионов, обход — по специфичности вместо кода #3501

Merged
lekss361 merged 1 commit from feat/region-50-registry into main 2026-09-12 13:59:44 +00:00

1 commit

Author SHA1 Message Date
bot-backend
18add6d667 feat(regions): регион 50 в реестре + обход по специфичности вместо кода региона
All checks were successful
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 / changes (pull_request) Successful in 12s
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 5m36s
Московская область заводится в реестр покрытия. Это предусловие для импорта
411 056 сделок Росреестра, которые лежат в FDW нетронутыми: импорт валидирует
region_code по REGIONS, и без записи в реестре не стартует.

## Обход реестра больше не по коду региона

region_for_point шёл по sorted(REGIONS) — «первый по коду выигрывает».
Докстринг называл это страховкой «на случай, если регионы когда-нибудь
пересекутся». Случай наступил прямо здесь: прямоугольник области содержит
прямоугольник Москвы целиком, а 50 меньше 77. Наивное добавление отправило бы
ВСЕ московские точки в регион 50, включая центр — и это доехало бы до оценки,
estimator.py:4862 берёт отсюда скоуп коридора ДКП, а сделок по области ноль.

Ключ сортировки — площадь bbox_region по возрастанию: вложенный регион всегда
проверяется раньше объемлющего, и следующий новый регион встанет на место сам,
без правки этого места. Порядок стал (77, 50, 66).

region_by_city оставлен по коду осознанно: там точное сравнение строки, а не
вложенность прямоугольников, и тот же класс бага возможен только при дубле
имени города между регионами. Дублей нет, это закреплено тестом.

## Границы области — по данным, а не по карте

У области нет города-ядра, поэтому tight/wide/product_core здесь не «город плюс
отступ», а эмпирический пояс: разброс координат 45 294 подмосковных карточек
Циана. product_core взят по перцентилям 0.5..99.5, wide — полный наблюдённый
диапазон. Административный bbox стоит только в bbox_region, где он и нужен как
fallback-accept. Обещать охват продукта там, где нет ни одного объявления,
нельзя — поэтому product_code намеренно уже административной границы.

Тиров обогащения у области нет ни одного, frozenset() пуст намеренно.
canonical_city=None: источники по области несут настоящий город (Химки,
Балашиха), в отличие от Москвы, где Росреестр отдаёт округ или поселение.

## Известное ограничение, которое этот PR НЕ чинит

По координате Химки и Балашиха резолвятся в 77, а не в 50. Это свойство
прямоугольника, а не выбранного порядка: любой прямоугольник, содержащий и
Зеленоград, и центр Москвы, покоординатно содержит Химки. Замер по собранным
московским карточкам ДомКлика подтверждает — Москва реально дотягивается до
долготы 37.95 (восточнее центра Балашихи) и до широты 56.01.

Масштаб: из 24 784 подмосковных объявлений Циана 18 288 лежат внутри
московского прямоугольника. Хуже от этого PR не стало — до него они резолвились
туда же, просто без конкурента. Оставшиеся 6 496 раньше не резолвились никуда и
падали в дефолтный регион 66, то есть в Свердловскую область; теперь встают в 50.

Практическое следствие для следующего шага: импорт подмосковных объявлений
обязан брать регион из города, а не из координаты. По имени Химки, Балашиха и
Красногорск разрешаются верно.

Настоящее решение — граница полигоном вместо прямоугольника (#3052).

## Тесты

Регрессия на порядок: центр Москвы, Зеленоград и Новая Москва остаются в 77;
Серпухов, Дмитров, Электросталь встают в 50; Екатеринбург не тронут.
Известное ограничение закреплено отдельным тестом, а не замолчано.

Существующий test_regions_do_not_overlap получил исключение для пары (50,77) —
вложенность реальна. Исключение не голословно: перед ним стоит проверка, что
bbox_region(50) действительно содержит bbox_region(77) как множество. Выедет 77
за границы области — тест упадёт с понятной причиной, а не пропустит настоящее
пересечение через список исключений.

81 passed по регионным файлам, ruff чистый.

Claude-Session: https://claude.ai/code/session_01NQb6WeJtagZwZnUsSjDizs
2026-09-12 16:53:08 +03:00