gendesign/backend/app/services/site_finder/ird_overlay_lookup.py
bot-backend 8df0f157a7
All checks were successful
CI / changes (push) Successful in 9s
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Successful in 5m52s
CI / backend-tests (push) Successful in 6m13s
feat(sf): parcel_ird_overlaps read-side для analyze (#1067 D9a)
ИРД-overlay lookup: ST_Intersects участка по ird_overlays (м.132) → ограничения
(ЗОУИТ/ПЗЗ-границы/ОКН/ООПТ) с reg_numb_border + zone_index/type (когда резолвлено).
Standalone-модуль (нулевой collision с parcels.py). Graceful degrade если таблица
ещё не задеплоена. Комплементарно quarter_dump_lookup (per-quarter blob).

Refs #1067 (D9a). Next D9b: flag-gated wiring в analyze-surface.
2026-06-06 21:52:54 +03:00

82 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""ИРД-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.
_IRD_OVERLAP_SQL = text(
"""
SELECT layer_kind, reg_numb_border, category, subcategory,
zone_index, zone_type_name
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}], 'ird_by_kind': {layer_kind: count}}.
Пусто при отсутствии участка/таблицы/пересечений (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"],
}
)
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"]