gendesign/backend/tests/test_network_zone_humanize.py
bot-backend 7c12812f81
Some checks failed
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m49s
Deploy / build-worker (push) Successful in 4m31s
Deploy / build-frontend (push) Successful in 5m19s
Deploy / deploy (push) Has been cancelled
feat(site-finder): позитив-разрез сетевых ЗОУИТ «сеть рядом с участком» (Фаза C2/C3) (#2281)
2026-07-03 08:18:24 +00:00

253 lines
13 KiB
Python
Raw 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.

"""Unit-тесты гуманизации сетевых охранных зон §3 (#2119 C2/C3).
Pure / без БД. Покрывает:
- refine_net_kind: type_zone-база + дочистка general по name_by_doc (тепло/газ/электро).
- human_label: regex-кейсы (ВЛ 0,4 кВ, КЛ 10 кВ, теплосеть с кад.номером, газопровод,
нераспознанное), last-mile-подача ≤1 кВ, извлечение ТП.
- _shape_network_zones: сортировка по distance + лимит _NETWORK_ZONE_PER_KIND per-kind.
Строки name_by_doc — реальные образцы с прода (cad_zouit, 2026-07-03).
"""
from __future__ import annotations
from app.services.site_finder import connection_capacity_lookup as ccl
from app.services.site_finder.network_zone_humanize import human_label, refine_net_kind
# type_zone-константы (как в проде / SQL-белом списке).
_TZ_THERMAL = "Охранная зона тепловых сетей"
_TZ_GENERAL = "Охранная зона инженерных коммуникаций"
_TZ_ELECTRIC = (
"Охранная зона объектов электроэнергетики (объектов электросетевого хозяйства "
"и объектов по производству электрической энергии)"
)
_TZ_PIPELINE = (
"Охранная зона трубопроводов (газопроводов, нефтепроводов и "
"нефтепродуктопроводов, аммиакопроводов)"
)
_TZ_COMMS = "Охранная зона линий и сооружений связи"
# ── refine_net_kind: база по type_zone ────────────────────────────────────────
def test_refine_thermal_type_zone_stays_thermal() -> None:
"""Специфичный type_zone «тепловых сетей» → thermal независимо от name."""
assert refine_net_kind(_TZ_THERMAL, "Тепловая сеть от ЦТП Баумана") == "thermal"
def test_refine_electric_type_zone_stays_electricity() -> None:
assert refine_net_kind(_TZ_ELECTRIC, "Охранная зона ВЛ 0,4кВ ТП 3164") == "electricity"
def test_refine_pipeline_type_zone_stays_pipeline_or_gas() -> None:
"""type_zone «трубопроводов (газопроводов…)» c газом в имени → gas."""
assert refine_net_kind(_TZ_PIPELINE, "Охранная зона газопровода высокого давления") == "gas"
def test_refine_comms_type_zone_stays_communication() -> None:
assert refine_net_kind(_TZ_COMMS, "Охранная зона сооружения связи ООО «ВЕРТИКАЛЬ-УРАЛ»") == (
"communication"
)
def test_refine_non_network_returns_none() -> None:
assert refine_net_kind("Санитарно-защитная зона", "что угодно") is None
assert refine_net_kind(None, None) is None
# ── refine_net_kind: дочистка general по name_by_doc ───────────────────────────
def test_refine_general_thermal_by_name() -> None:
"""«Охранная зона инженерных коммуникаций» с теплосетью в name → thermal (прод-кейс)."""
name = (
"Охранная зона объекта теплоснабжения «Трубопровод (тепловые сети) "
"с кадастровым номером 66:41:0108079:76"
)
assert refine_net_kind(_TZ_GENERAL, name) == "thermal"
def test_refine_general_gas_by_name() -> None:
"""general с газопроводом в name → gas (прод-кейс догазификации)."""
name = (
"Охранная зона газораспределительной сети в отношении объекта: Газопровод низкого давления"
)
assert refine_net_kind(_TZ_GENERAL, name) == "gas"
def test_refine_general_electricity_by_vl_regex() -> None:
"""general с «ВЛ 0,4 кВ» в name → electricity (даже без слова электросеть)."""
assert refine_net_kind(_TZ_GENERAL, "Охранная зона ВЛ 0,4 кВ от ТП 71796") == "electricity"
def test_refine_general_electricity_by_transformer() -> None:
"""general «Трансформаторная подстанция» без «ВЛ N кВ» → electricity (прод-кейс)."""
name = (
'Охранная зона объекта "Трансформаторная подстанция", кадастровый номер 66:41:0404019:333'
)
assert refine_net_kind(_TZ_GENERAL, name) == "electricity"
def test_refine_general_gas_distribution_not_electricity() -> None:
"""«газораспределительн» → gas раньше, «распределительн»-электро не перехватывает."""
assert refine_net_kind(_TZ_GENERAL, "Охранная зона газораспределительной сети") == "gas"
def test_refine_general_unrecognized_stays_general() -> None:
"""general без распознанного вида → остаётся general («вид не уточнён»)."""
assert refine_net_kind(_TZ_GENERAL, "Охранная зона неизвестного объекта") == "general"
assert refine_net_kind(_TZ_GENERAL, None) == "general"
# ── human_label: thermal ──────────────────────────────────────────────────────
def test_label_thermal_with_cad_num() -> None:
"""thermal с кад.номером трубопровода в name → подпись включает кад.№."""
name = (
"Охранная зона объекта теплоснабжения с кадастровым номером "
"66:41:0106021:815 (Тепловая сеть)"
)
assert human_label("thermal", name) == (
"Тепловая сеть (по охранной зоне), кад. № 66:41:0106021:815"
)
def test_label_thermal_without_cad_num() -> None:
name = "Охранная зона объекта теплоснабжения «Тепловая сеть от ЦТП Баумана, 7а»"
assert human_label("thermal", name) == "Тепловая сеть (по охранной зоне)"
# ── human_label: electricity (ВЛ/КЛ N кВ + ТП) ─────────────────────────────────
def test_label_electricity_vl_04kv_last_mile() -> None:
"""ВЛ 0,4 кВ (≤1 кВ) — last-mile: напряжение как второстепенный факт, ТП — якорь."""
name = "Охранная зона ВЛ 0,4кВ ТП 3164 руб.№16 от оп. 3 до уч. 66:41:0106128:17"
assert human_label("electricity", name) == (
"Ввод от ТП 3164 (по охранной зоне, 0,4 кВ — last-mile)"
)
def test_label_electricity_kl_10kv_magistral() -> None:
"""КЛ 10 кВ (>1 кВ) — магистральный ввод: напряжение в основной подписи."""
name = "Охранная зона КЛ 10 кВ от ТП 5521 до РП 12"
assert human_label("electricity", name) == "Ввод 10 кВ от ТП 5521 (по охранной зоне)"
def test_label_electricity_vl_6kv_with_ktpn() -> None:
"""ВЛ 6 кВ + КТПН — берём первый якорный ТП/КТПН, напряжение магистральное."""
name = (
"Охранная зона Линейное ответвление от ВЛ 6 кВ ТП 3736-3747 "
"с отпайками, ТП 32505 (КТПН п/№ 589)"
)
assert human_label("electricity", name) == "Ввод 6 кВ от ТП 3736-3747 (по охранной зоне)"
def test_label_electricity_no_voltage_but_tp() -> None:
"""Нет напряжения, но есть ТП → «Электросеть от ТП M»."""
assert human_label("electricity", "Охранная зона объекта от ТП 999") == (
"Электросеть от ТП 999 (по охранной зоне)"
)
def test_label_electricity_bare() -> None:
"""Нет ни напряжения, ни ТП → generic электро-подпись."""
assert human_label("electricity", "Охранная зона объекта электросетевого хозяйства") == (
"Объект электросетевого хозяйства (по охранной зоне)"
)
# ── human_label: gas / pipeline / general / прочие ─────────────────────────────
def test_label_gas_with_cad_num() -> None:
name = "Охранная зона газопровода высокого давления, кадастровый номер 66:41:0110003:7983"
assert human_label("gas", name) == (
"Трубопровод (газо-/нефтепровод) по охранной зоне, кад. № 66:41:0110003:7983"
)
def test_label_pipeline_without_cad_num() -> None:
assert human_label("pipeline", "Охранная зона нефтепровода") == (
"Трубопровод (газо-/нефтепровод) по охранной зоне"
)
def test_label_general_unrecognized() -> None:
assert human_label("general", "Охранная зона инженерных коммуникаций") == (
"Инженерные коммуникации (вид не уточнён)"
)
def test_label_communication_and_water() -> None:
assert human_label("communication", "Охранная зона сооружения связи") == (
"Линия связи (по охранной зоне)"
)
assert human_label("water", "Охранная зона водопровода") == (
"Сеть водоснабжения/водоотведения (по охранной зоне)"
)
def test_label_none_fallback() -> None:
assert human_label(None, "что угодно") == "Охранная зона"
# ── _shape_network_zones: сортировка + лимит per-kind ──────────────────────────
def _row(type_zone: str, name: str, dist: int, reg: str = "reg") -> dict:
"""Имитация .mappings()-строки cad_zouit (dict доступ по ключам)."""
return {
"type_zone": type_zone,
"name_by_doc": name,
"reg_numb_border": reg,
"distance_m": dist,
}
def test_shape_limits_two_per_kind_sorted() -> None:
"""До 2 ближайших НА net_kind; в пределах вида — по distance ASC."""
rows = [
_row(_TZ_THERMAL, "Тепловая сеть A", 200, "r1"),
_row(_TZ_THERMAL, "Тепловая сеть B", 50, "r2"),
_row(_TZ_THERMAL, "Тепловая сеть C", 120, "r3"), # третья thermal — отбрасывается
_row(_TZ_ELECTRIC, "Охранная зона ВЛ 10 кВ ТП 5", 80, "r4"),
]
out = ccl._shape_network_zones(rows)
thermal = [z for z in out if z["net_kind"] == "thermal"]
electric = [z for z in out if z["net_kind"] == "electricity"]
assert len(thermal) == 2 # лимит 2 из 3
assert [z["distance_m"] for z in thermal] == [50, 120] # ближайшие две, отсортированы
assert len(electric) == 1
def test_shape_intersecting_zone_distance_zero() -> None:
"""distance_m=0 (зона пересекает участок) сохраняется и идёт первой."""
rows = [
_row(_TZ_THERMAL, "Тепловая сеть далеко", 250, "r1"),
_row(_TZ_GENERAL, "объект теплоснабжения кад. 66:41:0108079:76", 0, "r2"),
]
out = ccl._shape_network_zones(rows)
assert out[0]["distance_m"] == 0
# general с теплом в name дочистился до thermal.
assert out[0]["net_kind"] == "thermal"
def test_shape_outer_sort_by_distance_then_kind() -> None:
"""Внешний порядок: distance ASC, затем net_kind (стабильно)."""
rows = [
_row(_TZ_ELECTRIC, "ВЛ 10 кВ ТП 1", 100, "r1"),
_row(_TZ_THERMAL, "Тепловая сеть", 100, "r2"),
_row(_TZ_GENERAL, "Газопровод низкого давления", 30, "r3"),
]
out = ccl._shape_network_zones(rows)
assert out[0]["distance_m"] == 30 # gas ближе всех
assert out[0]["net_kind"] == "gas"
# при равном distance=100 — electricity раньше thermal (алфавит).
assert [z["net_kind"] for z in out[1:]] == ["electricity", "thermal"]
def test_shape_empty() -> None:
assert ccl._shape_network_zones([]) == []