"""Lookup функциональных зон генплана ЕКБ-2045 для analyze_parcel (#1058). Читает ``ekb_genplan_functional_zone`` (м.139) и отдаёт зоны, пересекающие участок. Graceful: таблица ещё не задеплоена / ошибка БД / пустой wkt → возвращает []. Analyze-врезка (вшивание в build_ird_analyze_block / analyze_parcel) — отдельный follow-up после merge #1131, чтобы не конфликтить с ird-analyze PR. Зеркалит ird_overlay_lookup.py (стиль, graceful handling, CAST psycopg v3). """ from __future__ import annotations import logging from typing import Any from sqlalchemy import text from sqlalchemy.exc import OperationalError, ProgrammingError from sqlalchemy.orm import Session logger = logging.getLogger(__name__) # Spatial overlap участка: ST_Intersects через GIST idx_ekb_genplan_fz_geom. # Участок передаётся как WKT в 4326. CAST psycopg v3 (НИКОГДА :x::type). _ZONE_OVERLAP_SQL = text( """ SELECT zone_type, status, area_ha, zone_class_code FROM ekb_genplan_functional_zone WHERE ST_Intersects(geom, ST_GeomFromText(CAST(:parcel_wkt AS text), 4326)) ORDER BY zone_type, status """ ) def parcel_functional_zones(db: Session, parcel_wkt: str | None) -> list[dict[str, Any]]: """Функциональные зоны генплана, пересекающие участок. Args: db: SQLAlchemy-сессия. parcel_wkt: геометрия участка в WKT (EPSG:4326). None → пустой результат. Returns: Список dict: [{zone_type, status, area_ha, zone_class_code}]. Пусто при: нет wkt / таблица не задеплоена / нет пересечений / ошибка БД. """ if not parcel_wkt: return [] try: rows = db.execute(_ZONE_OVERLAP_SQL, {"parcel_wkt": parcel_wkt}).mappings().all() except (OperationalError, ProgrammingError) as exc: # Таблица ещё не задеплоена или ошибка БД — graceful degrade (analyze не падает). logger.warning( "parcel_functional_zones: ekb_genplan_functional_zone недоступна, skip: %s", exc ) return [] return [ { "zone_type": r["zone_type"], "status": r["status"], "area_ha": float(r["area_ha"]) if r["area_ha"] is not None else None, "zone_class_code": r["zone_class_code"], } for r in rows ] __all__ = ["parcel_functional_zones"]