From 7072d7803e4e32ddb2222ce0706955a48ec4bf2a Mon Sep 17 00:00:00 2001 From: Light1YT Date: Wed, 17 Jun 2026 18:44:29 +0500 Subject: [PATCH] fix(site-finder): compute nearest_top3 metro from osm_poi_ekb (#1667) Replace bare metro_block placeholder ({"nearest_top3": None}, blocked on a never-merged 22h metro scraper) with a direct KNN query against osm_poi_ekb category='metro_stop' (loaded by poi_loader). Returns 3 nearest stations with name + rounded distance_m; empty list when none nearby (vs None on error), SAVEPOINT + try/except like adjacent SF-B5 blocks. No frontend consumer yet. Closes #1667 --- backend/app/api/v1/parcels.py | 40 +++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/backend/app/api/v1/parcels.py b/backend/app/api/v1/parcels.py index a12e1946..5c1bc138 100644 --- a/backend/app/api/v1/parcels.py +++ b/backend/app/api/v1/parcels.py @@ -2127,8 +2127,44 @@ def analyze_parcel( except Exception as e: logger.warning("red_lines_block query failed for %s: %s", cad_num, e) - # B5-4) Metro placeholder — заполнится после merge 22h metro scraper - metro_block: dict[str, Any] = {"nearest_top3": None} + # B5-4) Metro — ближайшие 3 станции метро к участку. + # Данные уже в osm_poi_ekb (category='metro_stop', грузятся poi_loader.py:44), + # скрапер не нужен — прямой KNN-запрос по geom <-> centroid участка. + # nearest_top3=[] (НЕ None) когда метро не найдено: отличаем "посчитано, рядом + # нет" от "не реализовано". None только при ошибке запроса. + metro_block: dict[str, Any] = {"nearest_top3": []} + try: + with db.begin_nested(): + metro_rows = ( + db.execute( + text(""" + SELECT name, + ST_Distance( + m.geom::geography, + ST_Centroid(ST_GeomFromText(:wkt, 4326))::geography + ) AS dist_m + FROM osm_poi_ekb m + WHERE m.category = 'metro_stop' + ORDER BY m.geom <-> ST_Centroid(ST_GeomFromText(:wkt, 4326)) + LIMIT 3 + """), + {"wkt": geom_wkt}, + ) + .mappings() + .all() + ) + metro_block = { + "nearest_top3": [ + { + "name": mr["name"], + "distance_m": round(float(mr["dist_m"])) if mr["dist_m"] is not None else None, + } + for mr in metro_rows + ] + } + except Exception as e: + logger.warning("metro_block query failed for %s: %s", cad_num, e) + metro_block = {"nearest_top3": None} # B5-5) District price ranges из objective_lots (SF-B5) district_price_block: dict[str, Any] = {