253 lines
13 KiB
Python
253 lines
13 KiB
Python
"""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([]) == []
|