gendesign/backend/app/services/site_finder/ird_overlay_lookup.py
lekss361 9b9c6e85d3
Some checks failed
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 1m26s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 3m29s
Deploy / deploy (push) Has been cancelled
feat(sf): parcel_ird_overlaps read-side для analyze (#1092)
ird_overlay_lookup.py — read-side ИРД-overlay lookup: ST_Intersects участка (WKT 4326) против ird_overlays (м.132) → ограничения + reg_numb_border + zone_index/type + группировка by_kind. Standalone (parcels.py не тронут), GIST idx_ird_overlays_geom, graceful-degrade при отсутствии таблицы. Wiring в analyze деферится в D9b flag-gated. 4 теста.

Refs #1067.
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-06 19:01:06 +00: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"]