premises_lookup: KNN ближайшего здания ранжирует в ГРАДУСАХ (LIMIT 1) — на широте ЕКБ выбирает не ближайшее по метрам, parking_ratio чужого дома / потеря вали… #1616

Closed
opened 2026-06-16 17:20:43 +00:00 by bot-backend · 0 comments
Collaborator

Severity: medium · Категория: geo/SRID correctness · Файл: backend/app/services/site_finder/premises_lookup.py:84-108, 100-107, 150-159

Файл: backend/app/services/site_finder/premises_lookup.py:100-107 (_RESOLVE_CAD_SQL), фильтрация по метрам — стр. 88-97 и 150-159; вводящий в заблуждение комментарий — стр. 78-82.

Суть: ORDER BY b.geom <-> ST_SetSRID(ST_MakePoint(:lon,:lat),4326) LIMIT 1 — KNN-оператор <-> на geometry SRID 4326 считает ПЛАНАРНОЕ расстояние в ГРАДУСАХ. На широте ЕКБ (~56.8°N) 1° широты ≈ 111320м, 1° долготы ≈ 60955м (анизотропия ×1.83). Поэтому сортировка по градусам не эквивалентна сортировке по метрам, когда кандидаты лежат в разных направлениях (восток-запад vs север-юг). С LIMIT 1 рассматривается только ОДИН ближайший-по-градусам кандидат, и лишь после берётся его фактическая geography-дистанция и сравнивается с max_dist_m=50м.

Импакт: для конкурента-ЖК (competitors.py:802 resolve_cad_for_domrf → get_building_premises_for_match → parking_ratio) может быть (а) пропущено реально-ближайшее здание в радиусе 50м (ложное «нет данных»), либо (б) приписан parking_ratio/помещения соседнего здания (искажённый сигнал). Это ровно та ошибка, которой код пытался избежать («parking_ratio чужого дома хуже отсутствия»). Комментарий стр. 78-82 неверно утверждает монотонность <-> метрам для малых расстояний.

Фикс: ранжировать в метрах через geography — ORDER BY b.geom::geography <-> ST_SetSRID(ST_MakePoint(:lon,:lat),4326)::geography LIMIT 1 (KNN на geography считает сферическую дистанцию), либо взять top-K (напр. LIMIT 5) и переранжировать по ST_Distance(::geography) в Python/SQL, затем применить max_dist_m. Поправить вводящий в заблуждение комментарий.

Почему баг: Используется в competitors.py:802 для каждого конкурента-ЖК: resolve_cad_for_domrf(lat,lon) → get_building_premises_for_match → parking_ratio. Из-за градусного KNN+LIMIT 1 конкуренту может быть приписан parking_ratio/помещения СОСЕДНЕГО здания (искажённый сигнал в отчёте), либо реальное здание в радиусе 50 м не находится вовсе (ложное «нет данных»). Это именно то, чего код пытался избежать («precision важнее recall — parking_ratio чужого дома хуже отсутствия»), но градусный KNN ломает гарантию ближайшего. Фикс: ранжировать по geography (ORDER BY b.geom::geography <-> point::geography) или взять top-K кандидатов и переранжировать по ST_Distance(::geography) в метрах.

Как чинить: Заменить ORDER BY b.geom <-> point на geography-KNN ORDER BY b.geom::geography <-> ST_SetSRID(ST_MakePoint(lon,lat),4326)::geography (PostGIS поддерживает KNN на geography с GiST), ИЛИ выбрать LIMIT 5-10 кандидатов в подзапросе и в обёртке отсортировать/отфильтровать по metric ST_Distance(::geography). Поправить вводящий в заблуждение комментарий про «монотонность».


deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.78 · unit B39

**Severity:** medium · **Категория:** geo/SRID correctness · **Файл:** `backend/app/services/site_finder/premises_lookup.py:84-108, 100-107, 150-159` Файл: backend/app/services/site_finder/premises_lookup.py:100-107 (_RESOLVE_CAD_SQL), фильтрация по метрам — стр. 88-97 и 150-159; вводящий в заблуждение комментарий — стр. 78-82. Суть: `ORDER BY b.geom <-> ST_SetSRID(ST_MakePoint(:lon,:lat),4326) LIMIT 1` — KNN-оператор `<->` на geometry SRID 4326 считает ПЛАНАРНОЕ расстояние в ГРАДУСАХ. На широте ЕКБ (~56.8°N) 1° широты ≈ 111320м, 1° долготы ≈ 60955м (анизотропия ×1.83). Поэтому сортировка по градусам не эквивалентна сортировке по метрам, когда кандидаты лежат в разных направлениях (восток-запад vs север-юг). С LIMIT 1 рассматривается только ОДИН ближайший-по-градусам кандидат, и лишь после берётся его фактическая geography-дистанция и сравнивается с max_dist_m=50м. Импакт: для конкурента-ЖК (competitors.py:802 resolve_cad_for_domrf → get_building_premises_for_match → parking_ratio) может быть (а) пропущено реально-ближайшее здание в радиусе 50м (ложное «нет данных»), либо (б) приписан parking_ratio/помещения соседнего здания (искажённый сигнал). Это ровно та ошибка, которой код пытался избежать («parking_ratio чужого дома хуже отсутствия»). Комментарий стр. 78-82 неверно утверждает монотонность <-> метрам для малых расстояний. Фикс: ранжировать в метрах через geography — `ORDER BY b.geom::geography <-> ST_SetSRID(ST_MakePoint(:lon,:lat),4326)::geography LIMIT 1` (KNN на geography считает сферическую дистанцию), либо взять top-K (напр. LIMIT 5) и переранжировать по ST_Distance(::geography) в Python/SQL, затем применить max_dist_m. Поправить вводящий в заблуждение комментарий. **Почему баг:** Используется в competitors.py:802 для каждого конкурента-ЖК: resolve_cad_for_domrf(lat,lon) → get_building_premises_for_match → parking_ratio. Из-за градусного KNN+LIMIT 1 конкуренту может быть приписан parking_ratio/помещения СОСЕДНЕГО здания (искажённый сигнал в отчёте), либо реальное здание в радиусе 50 м не находится вовсе (ложное «нет данных»). Это именно то, чего код пытался избежать («precision важнее recall — parking_ratio чужого дома хуже отсутствия»), но градусный KNN ломает гарантию ближайшего. Фикс: ранжировать по geography (`ORDER BY b.geom::geography <-> point::geography`) или взять top-K кандидатов и переранжировать по ST_Distance(::geography) в метрах. **Как чинить:** Заменить `ORDER BY b.geom <-> point` на geography-KNN `ORDER BY b.geom::geography <-> ST_SetSRID(ST_MakePoint(lon,lat),4326)::geography` (PostGIS поддерживает KNN на geography с GiST), ИЛИ выбрать LIMIT 5-10 кандидатов в подзапросе и в обёртке отсортировать/отфильтровать по metric ST_Distance(::geography). Поправить вводящий в заблуждение комментарий про «монотонность». --- <sub>deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.78 · unit B39</sub>
bot-backend added the
week ревью 1
label 2026-06-16 17:20:43 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1616
No description provided.