Регион точки резолвится по настоящей границе, а не по прямоугольнику (#3052) #3522

Merged
lekss361 merged 1 commit from feat/3052-region-polygons into main 2026-09-15 06:44:32 +00:00

1 commit

Author SHA1 Message Date
bot-backend
30832c27ed fix(tradein): region_for_point резолвит по настоящему полигону, не по bbox (#3052)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / browser-tests (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 6m3s
Прямоугольники bbox_region Москвы (77) и области (50) пересекались:
bbox_region(50) геометрически содержит bbox_region(77) как прямоугольник, и
region_for_point отдавал точку первому по площади bbox совпадению без учёта
того, что реальные админ-границы не пересекаются. Из-за этого Химки, Реутов,
Котельники, Люберцы резолвились в Москву — 8 647 московских лотов уходили в
отказ по покрытию, а 11 172 областные сделки ложно попадали в московское
ядро, хотя коридор ДКП фильтрует строго по коду региона.

Границы — полигоны OSM/Nominatim (region_boundaries/boundaries.geojson.json,
ODbL, _source/_license сохранены в файле по требованию лицензии), парсятся
один раз на импорте модуля. Point-in-polygon — ray casting (PNPOLY) с even-odd
правилом по плоскому списку колец: один и тот же код корректно обрабатывает
и дыру (Москва вырезана из полигона области) и мультиполигон (10 несвязных
частей Москвы — Зеленоград, анклавы). Без shapely — его нет в зависимостях
tradein backend, тянуть ради одной функции незачем.

bbox_region остаётся дешёвым предфильтром перед полигоном (его читают
geocoder.py и msk_raw_import.py), просто больше не финальный ответ: порядок
_POINT_LOOKUP_ORDER (компактный bbox раньше объёмного) теперь экономит
polygon-проверки на частом случае Москва/область, а не определяет корректность.

Тест на Химки/Балашиху (test_3051_region_registry_moscow_oblast.py) раньше
фиксировал это как "известное ограничение" bbox-резолва — обновлён под
исправленное поведение (code 50, не 77). Новый test_3052_region_polygon_
boundary.py — приёмка по девяти контрольным точкам issue #3052 + инвариант
"полигон вложен в свой bbox_region" (bbox не должен молча отсекать то, что
полигон бы принял).

Исключение (50, 77) в test_regions_do_not_overlap НЕ снято: тест проверяет
raw-прямоугольники (is_within_bbox/bbox_region), а не итоговый резолв точки —
bbox_region(77) геометрически остаётся подмножеством bbox_region(50) как
прямоугольник независимо от полигонов, это не тот инвариант, который фикс
меняет. Честная непересекаемость теперь проверена на уровне результата
region_for_point в test_3052_region_polygon_boundary.py.

Замер: ~25 мкс на вызов region_for_point (240k вызовов, точки по всем трём
регионам вперемешку) — в пределах бюджета.
2026-09-13 15:45:13 +03:00