This commit is contained in:
parent
a66a554462
commit
7fe39f2e68
8 changed files with 80 additions and 22 deletions
|
|
@ -1379,12 +1379,8 @@ def export_parcel_forecast(
|
||||||
if format == "docx":
|
if format == "docx":
|
||||||
return Response(
|
return Response(
|
||||||
content=render_report_docx(run.result),
|
content=render_report_docx(run.result),
|
||||||
media_type=(
|
media_type=("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
headers={"Content-Disposition": f'attachment; filename="{base_name}.docx"'},
|
||||||
),
|
|
||||||
headers={
|
|
||||||
"Content-Disposition": f'attachment; filename="{base_name}.docx"'
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# pptx — байты (PowerPoint OOXML), отдельной веткой (зеркало docx early-return),
|
# pptx — байты (PowerPoint OOXML), отдельной веткой (зеркало docx early-return),
|
||||||
|
|
@ -1395,9 +1391,7 @@ def export_parcel_forecast(
|
||||||
media_type=(
|
media_type=(
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||||
),
|
),
|
||||||
headers={
|
headers={"Content-Disposition": f'attachment; filename="{base_name}.pptx"'},
|
||||||
"Content-Disposition": f'attachment; filename="{base_name}.pptx"'
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if format == "json":
|
if format == "json":
|
||||||
|
|
@ -3003,7 +2997,11 @@ def analyze_parcel(
|
||||||
db, geom_wkt, centroid_lon, centroid_lat
|
db, geom_wkt, centroid_lon, centroid_lat
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("ird block failed for cad=%s — analyze response unaffected", cad_num)
|
logger.warning(
|
||||||
|
"ird block failed for cad=%s — analyze response unaffected",
|
||||||
|
cad_num,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
return result_payload
|
return result_payload
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,11 +135,11 @@ class Settings(BaseSettings):
|
||||||
openrouteservice_api_key: str = ""
|
openrouteservice_api_key: str = ""
|
||||||
|
|
||||||
# ИРД-слой в analyze (#1067 D9b «GG-форсайт»): поле `ird` в ответе analyze —
|
# ИРД-слой в analyze (#1067 D9b «GG-форсайт»): поле `ird` в ответе analyze —
|
||||||
# parcel_ird_overlaps (м.132, incl opportunity) + функц.зона/КРТ (геопортал WFS) +
|
# parcel_ird_overlaps (м.132, incl opportunity) + КРТ (геопортал WFS) +
|
||||||
# ПЗЗ-регламент зоны (C8b). Default OFF: источники 2-3 — live-зависимость от внешнего
|
# ПЗЗ-регламент зоны (C8b). Включён 2026-06-07 после B6-harvest + прогрева
|
||||||
# ЕКБ-геопортала (reverse-eng, без SLA) в hot-пути analyze. Включать после прогона
|
# zone_regulation_cache (#1102). Override через env ENABLE_IRD_ANALYZE=false остаётся
|
||||||
# harvest на проде (B6) и заполнения zone_regulation_cache.
|
# возможен. functional_zone выключен — слой пуст в геопортале ЕКБ (0 фич, #1058).
|
||||||
enable_ird_analyze: bool = False
|
enable_ird_analyze: bool = True
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ from app.services.site_finder.zone_regulation import get_or_fetch_zone_regulatio
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Короткий timeout для geoportal-вызовов в hot-пути analyze (default клиента — 20s, слишком долго).
|
# Короткий timeout для geoportal-вызовов в hot-пути analyze (default клиента — 20s, слишком долго).
|
||||||
_GEOPORTAL_TIMEOUT_S = 8
|
_GEOPORTAL_TIMEOUT_S = 4
|
||||||
|
|
||||||
|
|
||||||
def _functional_zone_at(
|
def _functional_zone_at(
|
||||||
|
|
@ -105,10 +105,14 @@ def build_ird_analyze_block(
|
||||||
"krt": [],
|
"krt": [],
|
||||||
"zone_regulation": None,
|
"zone_regulation": None,
|
||||||
}
|
}
|
||||||
|
# guard активен только в unit-тестах; в prod centroid_lon/lat всегда float
|
||||||
|
# (EKB-fallback в parcels.py).
|
||||||
# Geoportal-источники — только при валидном centroid (network, опц.).
|
# Geoportal-источники — только при валидном centroid (network, опц.).
|
||||||
if lon is not None and lat is not None:
|
if lon is not None and lat is not None:
|
||||||
client = client or EKBGeoportalClient(timeout=_GEOPORTAL_TIMEOUT_S)
|
client = client or EKBGeoportalClient(timeout=_GEOPORTAL_TIMEOUT_S)
|
||||||
block["functional_zone"] = _functional_zone_at(client, lon, lat)
|
# Слой генплан-функц.зон пуст в геопортале ЕКБ (0 фич, 2026-06); источник = ФГИС ТП (#1058).
|
||||||
|
# Не дёргаем geoportal впустую — вернём _functional_zone_at когда появится ingest.
|
||||||
|
block["functional_zone"] = None
|
||||||
block["krt"] = _krt_at(client, lon, lat)
|
block["krt"] = _krt_at(client, lon, lat)
|
||||||
block["zone_regulation"] = _zone_regulation_at(db, client, lon, lat)
|
block["zone_regulation"] = _zone_regulation_at(db, client, lon, lat)
|
||||||
return block
|
return block
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,19 @@ _PATCHES = [
|
||||||
),
|
),
|
||||||
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
||||||
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
||||||
|
# enable_ird_analyze=True by default — мокаем чтобы не делать сетевые вызовы в тестах
|
||||||
|
patch(
|
||||||
|
"app.api.v1.parcels.build_ird_analyze_block",
|
||||||
|
return_value={
|
||||||
|
"ird_overlaps": [],
|
||||||
|
"ird_by_kind": {},
|
||||||
|
"opportunity_overlaps": [],
|
||||||
|
"planning_projects": [],
|
||||||
|
"functional_zone": None,
|
||||||
|
"krt": [],
|
||||||
|
"zone_regulation": None,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -270,9 +283,9 @@ def test_inline_weights_rejects_nan() -> None:
|
||||||
content=raw_body,
|
content=raw_body,
|
||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 422, (
|
assert (
|
||||||
f"Ожидали 422 для NaN-weight, получили {resp.status_code}: {resp.text}"
|
resp.status_code == 422
|
||||||
)
|
), f"Ожидали 422 для NaN-weight, получили {resp.status_code}: {resp.text}"
|
||||||
finally:
|
finally:
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
_stop_patches()
|
_stop_patches()
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,19 @@ _PATCHES = [
|
||||||
),
|
),
|
||||||
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
||||||
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
||||||
|
# enable_ird_analyze=True by default — мокаем чтобы не делать сетевые вызовы в тестах
|
||||||
|
patch(
|
||||||
|
"app.api.v1.parcels.build_ird_analyze_block",
|
||||||
|
return_value={
|
||||||
|
"ird_overlaps": [],
|
||||||
|
"ird_by_kind": {},
|
||||||
|
"opportunity_overlaps": [],
|
||||||
|
"planning_projects": [],
|
||||||
|
"functional_zone": None,
|
||||||
|
"krt": [],
|
||||||
|
"zone_regulation": None,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,19 @@ _PATCHES = [
|
||||||
),
|
),
|
||||||
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
||||||
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
||||||
|
# enable_ird_analyze=True by default — мокаем чтобы не делать сетевые вызовы в тестах
|
||||||
|
patch(
|
||||||
|
"app.api.v1.parcels.build_ird_analyze_block",
|
||||||
|
return_value={
|
||||||
|
"ird_overlaps": [],
|
||||||
|
"ird_by_kind": {},
|
||||||
|
"opportunity_overlaps": [],
|
||||||
|
"planning_projects": [],
|
||||||
|
"functional_zone": None,
|
||||||
|
"krt": [],
|
||||||
|
"zone_regulation": None,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,19 @@ _PATCHES = [
|
||||||
),
|
),
|
||||||
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
patch("app.api.v1.parcels.compute_velocity", return_value=None),
|
||||||
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
patch("app.api.v1.parcels.compute_gate_verdict", return_value={"verdict": "unknown"}),
|
||||||
|
# enable_ird_analyze=True by default — мокаем чтобы не делать сетевые вызовы в тестах
|
||||||
|
patch(
|
||||||
|
"app.api.v1.parcels.build_ird_analyze_block",
|
||||||
|
return_value={
|
||||||
|
"ird_overlaps": [],
|
||||||
|
"ird_by_kind": {},
|
||||||
|
"opportunity_overlaps": [],
|
||||||
|
"planning_projects": [],
|
||||||
|
"functional_zone": None,
|
||||||
|
"krt": [],
|
||||||
|
"zone_regulation": None,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,9 @@ def _patch_overlay(monkeypatch: Any, overlay: dict[str, Any]) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_full_block_assembly(monkeypatch: Any) -> None:
|
def test_full_block_assembly(monkeypatch: Any) -> None:
|
||||||
"""Все источники → overlaps + split opportunity + функц.зона + КРТ + регламент."""
|
"""Все источники → overlaps + split opportunity + КРТ + регламент.
|
||||||
|
functional_zone всегда None: слой пуст в геопортале ЕКБ (0 фич, 2026-06) — WFS не дёргается.
|
||||||
|
"""
|
||||||
_patch_overlay(monkeypatch, _OVERLAY)
|
_patch_overlay(monkeypatch, _OVERLAY)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
ird_analyze,
|
ird_analyze,
|
||||||
|
|
@ -67,7 +69,9 @@ def test_full_block_assembly(monkeypatch: Any) -> None:
|
||||||
"opportunity_auction",
|
"opportunity_auction",
|
||||||
"opportunity_krt",
|
"opportunity_krt",
|
||||||
]
|
]
|
||||||
assert block["functional_zone"] == {"name": "Ж-зона"}
|
# functional_zone WFS выключен (слой пуст в геопортале ЕКБ, #1058) — всегда None
|
||||||
|
assert block["functional_zone"] is None
|
||||||
|
assert "functional_zone" not in [layer for layer, _, _ in client.calls]
|
||||||
assert block["krt"] == [{"num_oks_krt": "КРТ-7"}]
|
assert block["krt"] == [{"num_oks_krt": "КРТ-7"}]
|
||||||
assert block["zone_regulation"] == {"zone_index": "Ц-1"}
|
assert block["zone_regulation"] == {"zone_index": "Ц-1"}
|
||||||
assert block["planning_projects"] == _PLANNING
|
assert block["planning_projects"] == _PLANNING
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue