gendesign/backend/tests/test_network_obremenenie.py
Light1YT bc1da8eac9
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m52s
CI / backend-tests (pull_request) Successful in 11m34s
fix(gate): area-gate ЗОУИТ utility-easement blocker (sub-17) — снять over-block
ZOUIT_OVERLAP_SUB17 (единственный subcategory-blocker = охранная зона
ЛЭП/газа/трубопровода) был ХАРД-блокером на ЛЮБОЕ пересечение → блокировал
434/574 (75.6%) участков как «нельзя строить МКД». Прод-замер: реальное
покрытие участка этими зонами median=6%, p90=47%, только 1/17 >60% — тонкие
инж-коридоры. По РФ охранная зона ограничивает застройку ВНУТРИ полосы, а не
весь участок (МКД сажается на незанятой части). Хард-блок на 6%-покрытии неверен
и (после фикса резолва зон #1891) прятал financial_estimate у ~90% участков.

Фикс: area-gate.
- quarter_dump_lookup._get_zouit_overlaps + _get_cad_zouit_overlaps: добавлен
  coverage_pct (доля площади участка под зоной, в geography м², NULLIF-guard).
- gate_verdict.compute_gate_verdict: sub-17 (и cad network/blocker) АГРЕГИРУЮТСЯ
  (sum-capped, консервативно к блоку) → блокер ТОЛЬКО если покрытие >
  settings.gate_zouit_engineering_blocker_min_coverage (0.6, env-tunable), иначе
  WARNING ZOUIT_ENGINEERING_PARTIAL с % покрытия. Больше нет 4× дубль-блокеров.
  СЗЗ/OKN/прочие ЗОУИТ — без изменений (warnings). main_vri/резидентность — без
  изменений.
- coverage_pct отсутствует (legacy/synthetic) → трактуем как 0 → warning
  (документировано: дамп всегда даёт coverage; ложный хард-блок дороже).

Тесты: +помесь gate/aggregation/coverage (131 combined passed); правка
_make_zouit_row (7-я колонка coverage_pct) в tests/test_quarter_dump_lookup.py.
Полный бэкенд локально: 2 failed → fixed → перепроверка. ruff чисто. Схема не
менялась (gate_verdict/overlaps — dict[str,Any], api-types regen не нужен).

Refs #1881
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:45:59 +05:00

146 lines
6 KiB
Python
Raw Permalink 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.

"""Tests for network-obremenenie классификатор (#1070) — pure function, no DB.
type_zone-строки взяты из реальных distinct-значений cad_zouit (PROD, 2026-06-14).
"""
from app.services.site_finder.gate_verdict import compute_gate_verdict
from app.services.site_finder.network_obremenenie import (
classify_network_zone,
is_network_obremenenie,
network_kind_label,
)
# ── classify_network_zone: реальные type_zone из cad_zouit ────────────────────
def test_thermal_zone():
assert classify_network_zone("Охранная зона тепловых сетей") == "thermal"
def test_electricity_zone():
tz = (
"Охранная зона объектов электроэнергетики (объектов электросетевого хозяйства "
"и объектов по производству электрической энергии)"
)
assert classify_network_zone(tz) == "electricity"
def test_pipeline_zone_gas_first():
# «трубопроводов (газопроводов, нефтепроводов …)» — газ ловится раньше pipeline.
tz = (
"Охранная зона трубопроводов (газопроводов, нефтепроводов "
"и нефтепродуктопроводов, аммиакопроводов)"
)
assert classify_network_zone(tz) == "gas"
def test_communication_zone():
assert classify_network_zone("Охранная зона линий и сооружений связи") == "communication"
def test_general_engineering_zone():
assert classify_network_zone("Охранная зона инженерных коммуникаций") == "general"
# ── негативные: НЕ сетевые ЗОУИТ ──────────────────────────────────────────────
def test_servitude_not_network():
assert classify_network_zone("Зона публичного сервитута") is None
def test_szz_not_network():
assert classify_network_zone("Санитарно-защитная зона") is None
def test_natural_zouit_not_network():
assert classify_network_zone("Зоны с особыми условиями использования территории") is None
def test_none_and_empty():
assert classify_network_zone(None) is None
assert classify_network_zone("") is None
def test_general_keyword_without_protection_marker_is_skipped():
# «инженерные коммуникации» без «охранная зона» → не ловим (избегаем ложных).
assert classify_network_zone("Размещение инженерных коммуникаций") is None
# ── helpers ───────────────────────────────────────────────────────────────────
def test_is_network_obremenenie():
assert is_network_obremenenie("Охранная зона тепловых сетей") is True
assert is_network_obremenenie("Зона публичного сервитута") is False
def test_network_kind_label():
assert network_kind_label("thermal") == "теплоснабжение"
assert network_kind_label("general") == "инженерные коммуникации"
assert network_kind_label(None) is None
# ── gate_verdict: сетевое обременение → выделенный blocker ─────────────────────
def _dump_available() -> dict:
return {"available": True, "stale": False}
def test_gate_verdict_thermal_network_blocker():
"""Теплосеть (#1070 ООО Брик) покрывающая участок → ZOUIT_NETWORK_OBREMENENIE blocker.
Area-gated: при полном покрытии (coverage > порога) сетевое обременение блокирует.
"""
overlaps = [
{
"source": "cad_zouit",
"type_zone": "Охранная зона тепловых сетей",
"name": "ТС-123",
"is_network_zone": True,
"network_kind": "thermal",
"network_kind_label": "теплоснабжение",
"coverage_pct": 0.8,
}
]
verdict = compute_gate_verdict(None, overlaps, None, _dump_available())
codes = [b["code"] for b in verdict["blockers"]]
assert "ZOUIT_NETWORK_OBREMENENIE" in codes
assert verdict["can_build_mkd"] is False
detail = next(
b["detail"] for b in verdict["blockers"] if b["code"] == "ZOUIT_NETWORK_OBREMENENIE"
)
assert "теплоснабжение" in detail
def test_gate_verdict_servitude_not_network_blocker():
"""Сервитут (не сеть) → НЕ ZOUIT_NETWORK_OBREMENENIE."""
overlaps = [
{
"source": "cad_zouit",
"type_zone": "Зона публичного сервитута",
"name": "",
"is_network_zone": False,
"network_kind": None,
"network_kind_label": None,
}
]
verdict = compute_gate_verdict(None, overlaps, None, _dump_available())
codes = [b["code"] for b in verdict["blockers"]]
assert "ZOUIT_NETWORK_OBREMENENIE" not in codes
def test_gate_verdict_network_classified_when_flag_absent():
"""Back-compat: overlap без флага → gate_verdict классифицирует сам (area-gated blocker)."""
overlaps = [
{
"source": "cad_zouit",
"type_zone": "Охранная зона объектов электроэнергетики",
"name": "ВЛ-10кВ",
"coverage_pct": 0.75,
}
]
verdict = compute_gate_verdict(None, overlaps, None, _dump_available())
codes = [b["code"] for b in verdict["blockers"]]
assert "ZOUIT_NETWORK_OBREMENENIE" in codes