3 changed files with 55 additions and 1 deletions
|
|
@ -2994,7 +2994,7 @@ def analyze_parcel(
|
||||||
if settings.enable_ird_analyze:
|
if settings.enable_ird_analyze:
|
||||||
try:
|
try:
|
||||||
result_payload["ird"] = build_ird_analyze_block(
|
result_payload["ird"] = build_ird_analyze_block(
|
||||||
db, geom_wkt, centroid_lon, centroid_lat
|
db, geom_wkt, centroid_lon, centroid_lat, cad_num
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ from sqlalchemy.orm import Session
|
||||||
from app.services.scrapers.ekb_geoportal_client import EKBGeoportalClient
|
from app.services.scrapers.ekb_geoportal_client import EKBGeoportalClient
|
||||||
from app.services.site_finder.ird_overlay_lookup import parcel_ird_overlaps
|
from app.services.site_finder.ird_overlay_lookup import parcel_ird_overlaps
|
||||||
from app.services.site_finder.planning_lookup import parcel_planning_overlaps
|
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
|
from app.services.site_finder.zone_regulation import get_or_fetch_zone_regulation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -75,6 +76,7 @@ def build_ird_analyze_block(
|
||||||
geom_wkt: str | None,
|
geom_wkt: str | None,
|
||||||
lon: float | None,
|
lon: float | None,
|
||||||
lat: float | None,
|
lat: float | None,
|
||||||
|
cad_num: str | None = None,
|
||||||
*,
|
*,
|
||||||
client: EKBGeoportalClient | None = None,
|
client: EKBGeoportalClient | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
@ -84,6 +86,8 @@ def build_ird_analyze_block(
|
||||||
db: сессия.
|
db: сессия.
|
||||||
geom_wkt: геометрия участка WKT (EPSG:4326) для DB-overlay ``ird_overlays``.
|
geom_wkt: геометрия участка WKT (EPSG:4326) для DB-overlay ``ird_overlays``.
|
||||||
lon/lat: centroid участка (EPSG:4326) для geoportal-резолва функц.зоны/КРТ/регламента.
|
lon/lat: centroid участка (EPSG:4326) для geoportal-резолва функц.зоны/КРТ/регламента.
|
||||||
|
cad_num: кадастровый номер ЗУ для lookup изъятия/резервирования (м.136 land_reservation).
|
||||||
|
None → reservations=[].
|
||||||
client: переиспользуемый EKBGeoportalClient (для тестов/одного TLS-пула). По умолчанию
|
client: переиспользуемый EKBGeoportalClient (для тестов/одного TLS-пула). По умолчанию
|
||||||
создаётся с укороченным timeout.
|
создаётся с укороченным timeout.
|
||||||
|
|
||||||
|
|
@ -91,6 +95,7 @@ def build_ird_analyze_block(
|
||||||
dict: ``ird_overlaps``/``ird_by_kind`` (DB-overlay, incl opportunity_*),
|
dict: ``ird_overlaps``/``ird_by_kind`` (DB-overlay, incl opportunity_*),
|
||||||
``opportunity_overlaps`` (подмножество layer_kind='opportunity_*'),
|
``opportunity_overlaps`` (подмножество layer_kind='opportunity_*'),
|
||||||
``planning_projects`` (ППТ/ПМТ overlap, DB, м.134 #1085),
|
``planning_projects`` (ППТ/ПМТ overlap, DB, м.134 #1085),
|
||||||
|
``reservations`` (list, изъятие/резервирование из land_reservation по cad_num, #1091/#1062),
|
||||||
``functional_zone`` (генплан, dict|None), ``krt`` (list), ``zone_regulation`` (dict|None).
|
``functional_zone`` (генплан, dict|None), ``krt`` (list), ``zone_regulation`` (dict|None).
|
||||||
Любой источник деградирует независимо — блок всегда well-formed.
|
Любой источник деградирует независимо — блок всегда well-formed.
|
||||||
"""
|
"""
|
||||||
|
|
@ -102,6 +107,7 @@ def build_ird_analyze_block(
|
||||||
"ird_by_kind": overlay.get("ird_by_kind", {}),
|
"ird_by_kind": overlay.get("ird_by_kind", {}),
|
||||||
"opportunity_overlaps": opportunity,
|
"opportunity_overlaps": opportunity,
|
||||||
"planning_projects": parcel_planning_overlaps(db, geom_wkt),
|
"planning_projects": parcel_planning_overlaps(db, geom_wkt),
|
||||||
|
"reservations": parcel_reservations(db, cad_num),
|
||||||
"functional_zone": None,
|
"functional_zone": None,
|
||||||
"krt": [],
|
"krt": [],
|
||||||
"zone_regulation": None,
|
"zone_regulation": None,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ _PLANNING = [{"project_type": "ppt", "doc_status_name": "действующий"
|
||||||
def _patch_overlay(monkeypatch: Any, overlay: dict[str, Any]) -> None:
|
def _patch_overlay(monkeypatch: Any, overlay: dict[str, Any]) -> None:
|
||||||
monkeypatch.setattr(ird_analyze, "parcel_ird_overlaps", lambda db, wkt: overlay)
|
monkeypatch.setattr(ird_analyze, "parcel_ird_overlaps", lambda db, wkt: overlay)
|
||||||
monkeypatch.setattr(ird_analyze, "parcel_planning_overlaps", lambda db, wkt: list(_PLANNING))
|
monkeypatch.setattr(ird_analyze, "parcel_planning_overlaps", lambda db, wkt: list(_PLANNING))
|
||||||
|
monkeypatch.setattr(ird_analyze, "parcel_reservations", lambda db, cad_num: [])
|
||||||
|
|
||||||
|
|
||||||
def test_full_block_assembly(monkeypatch: Any) -> None:
|
def test_full_block_assembly(monkeypatch: Any) -> None:
|
||||||
|
|
@ -191,3 +192,50 @@ def test_session_not_shared_between_threads(monkeypatch: Any) -> None:
|
||||||
assert (
|
assert (
|
||||||
zone_reg_thread[0] is main_thread
|
zone_reg_thread[0] is main_thread
|
||||||
), "zone_regulation должен исполняться в основном потоке, а не в фоновом (Session safety)"
|
), "zone_regulation должен исполняться в основном потоке, а не в фоновом (Session safety)"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Reservation tests (#1091, #1062)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
_RESERVATION_ROW = {
|
||||||
|
"reservation_kind": "изъятие",
|
||||||
|
"basis_act": "Постановление Правительства",
|
||||||
|
"act_number": "12-ПП",
|
||||||
|
"act_date": "2024-03-01",
|
||||||
|
"purpose": "дорожное строительство",
|
||||||
|
"doc_url": "https://example.com/doc.pdf",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_reservations_wired_to_block(monkeypatch: Any) -> None:
|
||||||
|
"""cad_num передан → parcel_reservations возвращает запись → ключ reservations в block."""
|
||||||
|
_patch_overlay(monkeypatch, _OVERLAY)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
ird_analyze,
|
||||||
|
"parcel_reservations",
|
||||||
|
lambda db, cad_num: [_RESERVATION_ROW] if cad_num == "66:41:0101001:123" else [],
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(ird_analyze, "get_or_fetch_zone_regulation", lambda *a, **kw: None)
|
||||||
|
client = _FakeClient(fz=[], krt=[])
|
||||||
|
|
||||||
|
block = ird_analyze.build_ird_analyze_block(
|
||||||
|
object(), "POINT(60 56)", None, None, "66:41:0101001:123", client=client
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "reservations" in block
|
||||||
|
assert len(block["reservations"]) == 1
|
||||||
|
assert block["reservations"][0]["reservation_kind"] == "изъятие"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reservations_none_cad_num_returns_empty(monkeypatch: Any) -> None:
|
||||||
|
"""cad_num=None → reservations == [] (parcel_reservations graceful, ключ присутствует)."""
|
||||||
|
_patch_overlay(monkeypatch, {"ird_overlaps": [], "ird_by_kind": {}})
|
||||||
|
monkeypatch.setattr(ird_analyze, "get_or_fetch_zone_regulation", lambda *a, **kw: None)
|
||||||
|
client = _FakeClient(fz=[], krt=[])
|
||||||
|
|
||||||
|
block = ird_analyze.build_ird_analyze_block(object(), None, None, None, None, client=client)
|
||||||
|
|
||||||
|
assert "reservations" in block
|
||||||
|
assert block["reservations"] == []
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue