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] = {