feat(sf): вшить функц.зоны генплана (ekb_genplan_functional_zone) в analyze (#1058) #1140
2 changed files with 64 additions and 11 deletions
|
|
@ -31,6 +31,7 @@ from sqlalchemy import text
|
|||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.services.scrapers.ekb_geoportal_client import EKBGeoportalClient
|
||||
from app.services.site_finder.functional_zone_lookup import parcel_functional_zones
|
||||
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.planning_lookup import parcel_planning_overlaps
|
||||
|
|
@ -125,7 +126,8 @@ def build_ird_analyze_block(
|
|||
``planning_projects`` (ППТ/ПМТ overlap, DB, м.134 #1085),
|
||||
``reservations`` (list, изъятие/резервирование из land_reservation по cad_num, #1091/#1062),
|
||||
``krt_requisites`` (list, КРТ-реквизиты из ekb_krt_sites + planning_projects, #1060),
|
||||
``functional_zone`` (генплан, dict|None),
|
||||
``functional_zones`` (list, генплан-зоны из ekb_genplan_functional_zone, #1058),
|
||||
``functional_zone`` (первая зона|None, back-compat; dict|None),
|
||||
``krt`` (list, КРТ-геометрии из DB ekb_krt_geometry, #1130),
|
||||
``zone_regulation`` (dict|None).
|
||||
Любой источник деградирует независимо — блок всегда well-formed.
|
||||
|
|
@ -142,12 +144,20 @@ def build_ird_analyze_block(
|
|||
# КРТ-реквизиты (DB, дёшево, graceful): planning_projects ⋈ ekb_krt_sites (#1060).
|
||||
# Идёт рядом с planning_projects — оба DB-источника, не в geoportal-конкурентной части.
|
||||
"krt_requisites": parcel_krt_requisites(db, geom_wkt),
|
||||
# Функц.зоны генплана (#1058): DB-вызов ekb_genplan_functional_zone, дёшево, graceful.
|
||||
# functional_zones — полный список; functional_zone — первая зона|None (back-compat).
|
||||
"functional_zones": [],
|
||||
"functional_zone": None,
|
||||
# КРТ-геометрии (#1130): теперь из DB ekb_krt_geometry, не из живого WFS.
|
||||
# Заполняется ниже через _krt_at(db, lon, lat) — дёшевый DB-вызов.
|
||||
"krt": [],
|
||||
"zone_regulation": None,
|
||||
}
|
||||
# Функц.зоны генплана (#1058): DB ekb_genplan_functional_zone, дёшево, в основном потоке.
|
||||
fz = parcel_functional_zones(db, geom_wkt)
|
||||
block["functional_zones"] = fz
|
||||
block["functional_zone"] = fz[0] if fz else None
|
||||
|
||||
# guard активен только в unit-тестах; в prod centroid_lon/lat всегда float
|
||||
# (EKB-fallback в parcels.py).
|
||||
if lon is not None and lat is not None:
|
||||
|
|
@ -155,10 +165,6 @@ def build_ird_analyze_block(
|
|||
# Нет нужды в ThreadPool: krt теперь DB-вызов, не сеть.
|
||||
block["krt"] = _krt_at(db, lon, lat)
|
||||
|
||||
# Слой генплан-функц.зон пуст в геопортале ЕКБ (0 фич, 2026-06); источник = ФГИС ТП (#1058).
|
||||
# Не дёргаем geoportal впустую — вернём _functional_zone_at когда появится ingest.
|
||||
block["functional_zone"] = None
|
||||
|
||||
# zone_regulation остаётся единственным живым geoportal-вызовом в hot-пути.
|
||||
# Выполняем в основном потоке (Session не потокобезопасна).
|
||||
client = client or EKBGeoportalClient(timeout=_GEOPORTAL_TIMEOUT_S)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
"""Тесты build_ird_analyze_block (#1067 D9b analyze-wiring, #1130 krt-from-db).
|
||||
"""Тесты build_ird_analyze_block (#1067 D9b analyze-wiring, #1130 krt-from-db, #1058 fz-wiring).
|
||||
|
||||
DB/сеть не дёргаются: мокаем parcel_ird_overlaps + get_or_fetch_zone_regulation + _krt_at
|
||||
(module-level) и передаём fake EKBGeoportalClient. Проверяем сборку блока, split opportunity_*,
|
||||
graceful-деградацию каждого источника, skip geoportal при отсутствии centroid.
|
||||
+ parcel_functional_zones (module-level) и передаём fake EKBGeoportalClient.
|
||||
Проверяем сборку блока, split opportunity_*, graceful-деградацию каждого источника,
|
||||
skip geoportal при отсутствии centroid.
|
||||
|
||||
#1130: krt теперь из DB (ekb_krt_geometry) — клиент WFS больше НЕ вызывается для слоя «krt».
|
||||
_krt_at(db, lon, lat) мокается на уровне модуля.
|
||||
#1058: functional_zones из DB ekb_genplan_functional_zone; functional_zone — первая зона|None.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -66,11 +67,13 @@ def _patch_overlay(monkeypatch: Any, overlay: dict[str, Any]) -> None:
|
|||
monkeypatch.setattr(ird_analyze, "parcel_planning_overlaps", lambda db, wkt: list(_PLANNING))
|
||||
monkeypatch.setattr(ird_analyze, "parcel_reservations", lambda db, cad_num: [])
|
||||
monkeypatch.setattr(ird_analyze, "parcel_krt_requisites", lambda db, wkt: list(_KRT_REQUISITES))
|
||||
# По умолчанию: пустой список — functional_zone=None (back-compat).
|
||||
monkeypatch.setattr(ird_analyze, "parcel_functional_zones", lambda db, wkt: [])
|
||||
|
||||
|
||||
def test_full_block_assembly(monkeypatch: Any) -> None:
|
||||
"""Все источники → overlaps + split opportunity + КРТ (из DB) + регламент.
|
||||
functional_zone всегда None: слой пуст в геопортале ЕКБ (0 фич, 2026-06).
|
||||
functional_zones из DB (пустой мок → functional_zone=None).
|
||||
krt из DB (ekb_krt_geometry), WFS больше не вызывается для слоя «krt».
|
||||
"""
|
||||
_patch_overlay(monkeypatch, _OVERLAY)
|
||||
|
|
@ -91,7 +94,8 @@ def test_full_block_assembly(monkeypatch: Any) -> None:
|
|||
"opportunity_auction",
|
||||
"opportunity_krt",
|
||||
]
|
||||
# functional_zone WFS выключен (слой пуст в геопортале ЕКБ, #1058) — всегда None
|
||||
# parcel_functional_zones мокирован на [] → functional_zones=[], functional_zone=None
|
||||
assert block["functional_zones"] == []
|
||||
assert block["functional_zone"] is None
|
||||
# krt из DB, не из WFS — client.calls не содержит «krt»
|
||||
assert "krt" not in [layer for layer, _, _ in client.calls]
|
||||
|
|
@ -320,3 +324,46 @@ def test_krt_requisites_empty_when_no_overlap(monkeypatch: Any) -> None:
|
|||
|
||||
assert "krt_requisites" in block
|
||||
assert block["krt_requisites"] == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Функциональные зоны генплана (#1058)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_FZ_ROWS: list[dict[str, Any]] = [
|
||||
{"zone_type": "Ж", "status": "утверждён", "area_ha": 2.5, "zone_class_code": "2.1"},
|
||||
{"zone_type": "О", "status": "утверждён", "area_ha": 0.8, "zone_class_code": "3.0"},
|
||||
]
|
||||
|
||||
|
||||
def test_functional_zones_two_zones(monkeypatch: Any) -> None:
|
||||
"""parcel_functional_zones → 2 зоны: functional_zones == список, functional_zone == первая."""
|
||||
_patch_overlay(monkeypatch, _OVERLAY)
|
||||
monkeypatch.setattr(ird_analyze, "parcel_functional_zones", lambda db, wkt: list(_FZ_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)", 60.6, 56.8, client=client)
|
||||
|
||||
assert block["functional_zones"] == _FZ_ROWS
|
||||
assert len(block["functional_zones"]) == 2
|
||||
assert block["functional_zone"] == _FZ_ROWS[0]
|
||||
assert block["functional_zone"]["zone_type"] == "Ж"
|
||||
|
||||
|
||||
def test_functional_zones_empty(monkeypatch: Any) -> None:
|
||||
"""parcel_functional_zones пуст → functional_zones==[], functional_zone==None."""
|
||||
_patch_overlay(monkeypatch, _OVERLAY)
|
||||
# _patch_overlay уже мокает parcel_functional_zones → [], переопределяем явно для ясности
|
||||
monkeypatch.setattr(ird_analyze, "parcel_functional_zones", 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 block["functional_zones"] == []
|
||||
assert block["functional_zone"] is None
|
||||
assert "functional_zones" in block # ключ всегда присутствует
|
||||
assert "functional_zone" in block
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue