"""ИРД-overlay lookup для analyze_parcel (#1067 D9). Читает ``ird_overlays`` (м.132, наполняется harvest-task #1067 B5) и отдаёт ИРД-ограничения, пересекающие участок: ЗОУИТ / ПЗЗ-границы / ОКН / ООПТ-леса-вода — с reg_numb_border и (когда резолвлено, #1067 C8/D9-content) читаемым типом/индексом зоны. Отличие от ``quarter_dump_lookup`` (nspd_quarter_dumps blob-кеш): тот отдаёт per-quarter дамп ЗОУИТ/zoning из NSPD QuarterDump; ``ird_overlays`` — выделенный per-feature ИРД-store с reg_numb_border + content-колонками (для геопортал-резолва ВРИ/типа). Комплементарно: канонический ИРД-слой «волшебной таблицы». Graceful: если таблица ``ird_overlays`` ещё не задеплоена (pre-migration) или БД-ошибка — возвращает пустой результат (analyze не падает). Wiring в analyze — flag-gated (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__) # Пустой результат — sentinel для analyze try/except (НЕ мутировать). IRD_EMPTY_RESULT: dict[str, Any] = {"ird_overlaps": [], "ird_by_kind": {}} # ИРД-overlay по участку: ST_Intersects через GIST idx_ird_overlays_geom. geom уже EPSG:4326 # (harvest стор ST_Transform 3857→4326). Участок передаётся как WKT в 4326. CAST psycopg v3. # content добавлен для геопортальных ЗОУИТ-слоёв (gknspecial_harvest #1063/#1064/#1065/#1066). _IRD_OVERLAP_SQL = text( """ SELECT layer_kind, reg_numb_border, category, subcategory, zone_index, zone_type_name, content FROM ird_overlays WHERE ST_Intersects(geom, ST_GeomFromText(CAST(:parcel_wkt AS text), 4326)) ORDER BY layer_kind, reg_numb_border """ ) def parcel_ird_overlaps(db: Session, parcel_wkt: str | None) -> dict[str, Any]: """ИРД-ограничения, пересекающие участок. Args: db: сессия. parcel_wkt: геометрия участка в WKT (EPSG:4326). None → пустой результат. Returns: {'ird_overlaps': [{layer_kind, reg_numb_border, category, subcategory, zone_index, zone_type_name, content}], 'ird_by_kind': {layer_kind: count}}. content — JSONB-dict с ограничениями/реквизитами зоны (геопортальные слои: gknspecial_harvest #1063/#1064/#1065/#1066); None для НСПД-слоёв до резолва D9. Пусто при отсутствии участка/таблицы/пересечений (analyze не падает). """ if not parcel_wkt: return {"ird_overlaps": [], "ird_by_kind": {}} try: rows = db.execute(_IRD_OVERLAP_SQL, {"parcel_wkt": parcel_wkt}).mappings().all() except (OperationalError, ProgrammingError) as exc: # Таблица ещё не задеплоена / БД-ошибка — graceful degrade. logger.warning("parcel_ird_overlaps: ird_overlays недоступна, skip: %s", exc) return {"ird_overlaps": [], "ird_by_kind": {}} overlaps: list[dict[str, Any]] = [] by_kind: dict[str, int] = {} for r in rows: kind = r["layer_kind"] overlaps.append( { "layer_kind": kind, "reg_numb_border": r["reg_numb_border"], "category": r["category"], "subcategory": r["subcategory"], "zone_index": r["zone_index"], "zone_type_name": r["zone_type_name"], "content": r["content"], } ) by_kind[kind] = by_kind.get(kind, 0) + 1 return {"ird_overlaps": overlaps, "ird_by_kind": by_kind} __all__ = ["IRD_EMPTY_RESULT", "parcel_ird_overlaps"]