gendesign/backend/app/services/site_finder/planning_lookup.py
lekss361 53e76738b4
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-worker (push) Successful in 2m26s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m22s
Deploy / deploy (push) Successful in 1m14s
feat(sf): wire ППТ/ПМТ planning_projects в analyze ird-блок (#1105)
planning_lookup.py (parcel_planning_overlaps, ST_Intersects через GIST idx_planning_projects_geom) + ird_analyze.py ключ planning_projects (DB-only). ППТ/ПМТ overlap участка в analyze ird-блоке. За флагом enable_ird_analyze (OFF), parcels.py не тронут. Зеркалит ird_overlay_lookup. Зависит от #1104 (м.134). 7 тестов.

Refs #1085.
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-06 20:50:34 +00:00

56 lines
2.5 KiB
Python

"""ППТ/ПМТ planning-overlay lookup для analyze_parcel (#1085 analyze-wiring).
Читает ``planning_projects`` (м.134, наполняется planning_harvest #1085) и отдаёт проекты
планировки/межевания, пересекающие участок: что/где утверждено к застройке (future-supply).
Комплементарно ИРД-overlay (``ird_overlay_lookup``): тот — ограничения участка, этот — граддоки.
Graceful: если таблица ``planning_projects`` ещё не задеплоена (pre-migration) или БД-ошибка —
возвращает пустой список (analyze не падает). Wiring в analyze — за тем же флагом, что ИРД (D9b).
"""
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__)
# ППТ/ПМТ overlap по участку: ST_Intersects через GIST idx_planning_projects_geom. geom уже 4326
# (harvest стор WFS geoloc как 4326). Участок — WKT в 4326. CAST psycopg v3.
_PLANNING_OVERLAP_SQL = text(
"""
SELECT project_type, doc_status_name, full_name, project_name, dmd_actual_year
FROM planning_projects
WHERE ST_Intersects(geom, ST_GeomFromText(CAST(:parcel_wkt AS text), 4326))
ORDER BY project_type, dmd_actual_year DESC NULLS LAST
"""
)
def parcel_planning_overlaps(db: Session, parcel_wkt: str | None) -> list[dict[str, Any]]:
"""ППТ/ПМТ, пересекающие участок.
Args:
db: сессия.
parcel_wkt: геометрия участка в WKT (EPSG:4326). None → пустой список.
Returns:
list[{project_type, doc_status_name, full_name, project_name, dmd_actual_year}].
Пусто при отсутствии участка/таблицы/пересечений (analyze не падает).
"""
if not parcel_wkt:
return []
try:
rows = db.execute(_PLANNING_OVERLAP_SQL, {"parcel_wkt": parcel_wkt}).mappings().all()
except (OperationalError, ProgrammingError) as exc:
# Таблица ещё не задеплоена / БД-ошибка — graceful degrade.
logger.warning("parcel_planning_overlaps: planning_projects недоступна, skip: %s", exc)
return []
return [dict(r) for r in rows]
__all__ = ["parcel_planning_overlaps"]