From 50fdb843e404b8033cfe386f44d7a8a0be948b9e Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 7 Jun 2026 20:50:27 +0300 Subject: [PATCH] =?UTF-8?q?feat(sf):=20=D0=B2=D1=80=D0=B5=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0=20pat=5Fsubzones=20=D0=B2=20analyze=20=D0=98=D0=A0=D0=94?= =?UTF-8?q?-=D0=B1=D0=BB=D0=BE=D0=BA=20(#1151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/services/site_finder/ird_analyze.py | 7 +++ backend/tests/services/test_ird_analyze.py | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/backend/app/services/site_finder/ird_analyze.py b/backend/app/services/site_finder/ird_analyze.py index ada78730..2b04ff08 100644 --- a/backend/app/services/site_finder/ird_analyze.py +++ b/backend/app/services/site_finder/ird_analyze.py @@ -36,6 +36,7 @@ from app.services.site_finder.granddoc_lookup import parcel_granddoc from app.services.site_finder.ird_overlay_lookup import parcel_ird_overlaps from app.services.site_finder.krt_lookup import parcel_krt_requisites from app.services.site_finder.okn_lookup import parcel_okn_objects +from app.services.site_finder.pat_lookup import parcel_pat_subzones from app.services.site_finder.planning_lookup import parcel_planning_overlaps from app.services.site_finder.reservation_lookup import parcel_reservations from app.services.site_finder.zone_regulation import get_or_fetch_zone_regulation @@ -134,6 +135,8 @@ def build_ird_analyze_block( ``zone_regulation`` (dict|None), ``okn_objects`` (list, ОКН в радиусе 100 м от участка из okn_objects, #1066; [{source_id, category, distance_m}], пусто при отсутствии объектов/таблицы). + ``pat_subzones`` (list, приаэродромные подзоны ПАТ из pat_subzones, #1151; + [{subzone_no, name, restriction, aerodrome}], пусто при отсутствии пересечений/таблицы). ``granddoc`` — разработанная граддокументация (ППТ/ПМТ) на участок: статус + реквизиты ПАГЕ (#1061); [{project_type, doc_status_name, is_active, paga_number, paga_date, subject, doc_full_name}]. Правообладатель/срок по КРТ-территориям отдаётся отдельно @@ -156,6 +159,10 @@ def build_ird_analyze_block( # ОКН-объекты (#1066): ST_DWithin к okn_objects (428 строк, GIST), дёшево, graceful. # Идёт рядом с reservations — DB-источники основного потока, без сетевых зависимостей. "okn_objects": parcel_okn_objects(db, geom_wkt), + # ПАТ-подзоны (#1151/#1065): приаэродромные ограничения подзон 1-6 из pat_subzones. + # DB ST_Intersects (GIST), дёшево, graceful. Рядом с okn_objects — DB-источник основного + # потока. + "pat_subzones": parcel_pat_subzones(db, geom_wkt), # КРТ-реквизиты (DB, дёшево, graceful): planning_projects ⋈ ekb_krt_sites (#1060). # Идёт рядом с planning_projects — оба DB-источника, не в geoportal-конкурентной части. "krt_requisites": parcel_krt_requisites(db, geom_wkt), diff --git a/backend/tests/services/test_ird_analyze.py b/backend/tests/services/test_ird_analyze.py index 54ed2180..fbbcf0af 100644 --- a/backend/tests/services/test_ird_analyze.py +++ b/backend/tests/services/test_ird_analyze.py @@ -73,6 +73,8 @@ def _patch_overlay(monkeypatch: Any, overlay: dict[str, Any]) -> None: monkeypatch.setattr(ird_analyze, "parcel_okn_objects", lambda db, wkt: []) # По умолчанию: пустой список — granddoc=[] (graceful, см. #1061). monkeypatch.setattr(ird_analyze, "parcel_granddoc", lambda db, wkt: []) + # По умолчанию: пустой список — pat_subzones=[] (graceful, таблица может не быть задеплоена). + monkeypatch.setattr(ird_analyze, "parcel_pat_subzones", lambda db, wkt: []) def test_full_block_assembly(monkeypatch: Any) -> None: @@ -459,3 +461,47 @@ def test_granddoc_empty_when_no_overlap(monkeypatch: Any) -> None: assert "granddoc" in block assert block["granddoc"] == [] + + +# --------------------------------------------------------------------------- +# ПАТ-подзоны (#1151, #1065) +# --------------------------------------------------------------------------- + +_PAT_ROWS: list[dict[str, Any]] = [ + { + "subzone_no": 3, + "name": "Третья подзона", + "restriction": "Высота зданий не более 50 м", + "aerodrome": "Екатеринбург (Кольцово)", + } +] + + +def test_pat_subzones_wired_to_block(monkeypatch: Any) -> None: + """parcel_pat_subzones возвращает подзону → block['pat_subzones'] содержит её (#1151).""" + _patch_overlay(monkeypatch, _OVERLAY) + monkeypatch.setattr(ird_analyze, "parcel_pat_subzones", lambda db, wkt: list(_PAT_ROWS)) + monkeypatch.setattr(ird_analyze, "get_or_fetch_zone_regulation", lambda *a, **kw: None) + monkeypatch.setattr(ird_analyze, "_krt_at", lambda db, lon, lat: []) + client = _FakeClient(fz=[]) + + block = ird_analyze.build_ird_analyze_block(object(), "POINT(60 56)", None, None, client=client) + + assert "pat_subzones" in block + assert len(block["pat_subzones"]) == 1 + assert block["pat_subzones"][0]["subzone_no"] == 3 + assert block["pat_subzones"][0]["aerodrome"] == "Екатеринбург (Кольцово)" + + +def test_pat_subzones_empty_when_no_overlap(monkeypatch: Any) -> None: + """parcel_pat_subzones пуст (нет пересечений / таблица не задеплоена) → pat_subzones == [].""" + _patch_overlay(monkeypatch, _OVERLAY) + monkeypatch.setattr(ird_analyze, "parcel_okn_objects", lambda db, wkt: []) + monkeypatch.setattr(ird_analyze, "get_or_fetch_zone_regulation", lambda *a, **kw: None) + monkeypatch.setattr(ird_analyze, "_krt_at", lambda db, lon, lat: []) + client = _FakeClient(fz=[]) + + block = ird_analyze.build_ird_analyze_block(object(), "POINT(60 56)", 60.6, 56.8, client=client) + + assert "pat_subzones" in block + assert block["pat_subzones"] == []