gendesign/backend/tests/services/test_utility_characteristics.py
Light1YT 5428c73db5
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 1m12s
CI / openapi-codegen-check (pull_request) Successful in 2m7s
CI / backend-tests (pull_request) Successful in 14m35s
feat(site-finder): §3 «Сети и точки подключения» — попапы показывают характеристики (#2111)
Audit #2111 (HIGH): попапы карт §3 показывали данные, которые есть в источнике,
но недоливались до фронта. Три слоя:

- Utility OSM: гуманизируем voltage/operator/diameter/material/цепи из tags JSONB
  в одну RU-строку UtilityInfrastructureFeature.characteristics
  («110 кВ/10 кВ · МРСК Урала», «⌀ 500 мм · steel»). diameter берёт первый
  числовой run (не склеивает мульти-размеры «500x300»).
- Точки подключения (НСПД 36328): EngineeringStructure.purpose (назначение/класс,
  дедуп против type) + рендер «Назначение» и «№ ЕГРН» (cad_num уже был в схеме).
- ЗОУИТ: бэкенд уже отдавал reg_numb_border + coverage_pct — фронт рендерит
  «Рег. №» и «Перекрытие участка N%» (0..1 → %, typeof-guard сохраняет 0%).

Аддитивные optional-поля схемы (str|None=None) — без breaking changes.
api-types.ts перегенерён офлайн из app.openapi(). Юнит-тесты: _utility_characteristics
(formatter edge-cases) + purpose dedup-vs-type.
2026-06-30 17:59:59 +05:00

137 lines
5.4 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.

"""Тесты для _utility_characteristics (#2111 §3 «Сети и точки подключения»).
Покрывает гуманизацию OSM tags инженерных сетей в одну RU-строку для попапов:
напряжение (multi/кВ/В), оператор, диаметр трубопровода, материал, цепи, фильтр
junk-значений. Хелпер module-private — импортируем напрямую.
"""
from __future__ import annotations
from app.services.site_finder.utility_infrastructure_loader import (
_format_diameter,
_format_voltage,
_utility_characteristics,
)
# ── _format_voltage ───────────────────────────────────────────────────────────
def test_format_voltage_kv_single() -> None:
assert _format_voltage("110000") == "110 кВ"
def test_format_voltage_volts_below_1000() -> None:
assert _format_voltage("400") == "400 В"
def test_format_voltage_multi_mixed() -> None:
# «110000;10000» → оба ≥1000 → кВ, join через «/».
assert _format_voltage("110000;10000") == "110 кВ/10 кВ"
def test_format_voltage_multi_kv_and_volts() -> None:
assert _format_voltage("10000;400") == "10 кВ/400 В"
def test_format_voltage_fractional_kv() -> None:
# 400 В → «400 В»; 6600 → «6.6 кВ» (без хвостового нуля).
assert _format_voltage("6600") == "6.6 кВ"
def test_format_voltage_skips_non_numeric() -> None:
assert _format_voltage("110000;junk;10000") == "110 кВ/10 кВ"
def test_format_voltage_all_junk_returns_none() -> None:
assert _format_voltage("abc;;def") is None
# ── _format_diameter ──────────────────────────────────────────────────────────
def test_format_diameter_plain_digits() -> None:
assert _format_diameter("500") == "⌀ 500 мм"
def test_format_diameter_strips_mm_suffix() -> None:
assert _format_diameter("500mm") == "⌀ 500 мм"
assert _format_diameter("300 мм") == "⌀ 300 мм"
def test_format_diameter_no_digits_returns_none() -> None:
assert _format_diameter("large") is None
def test_format_diameter_multidim_takes_first_run() -> None:
# «500x300» (прямоугольный короб) → берём первый числовой run, не склеиваем.
assert _format_diameter("500x300") == "⌀ 500 мм"
assert _format_diameter("DN500") == "⌀ 500 мм"
# ── _utility_characteristics ──────────────────────────────────────────────────
def test_characteristics_power_voltage_and_operator() -> None:
out = _utility_characteristics("power", {"voltage": "110000;10000", "operator": "МРСК Урала"})
assert out == "110 кВ/10 кВ · МРСК Урала"
def test_characteristics_pipeline_diameter_and_material() -> None:
out = _utility_characteristics("gas", {"diameter": "500mm", "material": "steel"})
assert out == "⌀ 500 мм · steel"
def test_characteristics_single_voltage_volts() -> None:
assert _utility_characteristics("power", {"voltage": "400"}) == "400 В"
def test_characteristics_circuits_when_numeric() -> None:
out = _utility_characteristics("power", {"voltage": "10000", "circuits": "2"})
assert out == "10 кВ · 2 цеп."
def test_characteristics_cables_fallback_for_circuits() -> None:
out = _utility_characteristics("power", {"cables": "3"})
assert out == "3 цеп."
def test_characteristics_non_numeric_circuits_skipped() -> None:
# circuits=«single» не число → пропускаем; остаётся только напряжение.
assert _utility_characteristics("power", {"voltage": "400", "circuits": "single"}) == "400 В"
def test_characteristics_junk_voltage_filtered() -> None:
# voltage=yes (junk) → пропуск; ничего больше → None.
assert _utility_characteristics("power", {"voltage": "yes"}) is None
def test_characteristics_filters_empty_and_no() -> None:
assert _utility_characteristics("water", {"operator": " ", "material": "no"}) is None
def test_characteristics_none_tags_returns_none() -> None:
assert _utility_characteristics("power", None) is None
def test_characteristics_empty_dict_returns_none() -> None:
assert _utility_characteristics("power", {}) is None
def test_characteristics_not_a_dict_returns_none() -> None:
# Защитный guard: tags из БД мог прийти не dict (broken JSONB) → None, не падаем.
assert _utility_characteristics("power", "not-a-dict") is None # type: ignore[arg-type]
def test_characteristics_full_order() -> None:
# Порядок частей: напряжение · оператор · диаметр · материал · цепи.
out = _utility_characteristics(
"power",
{
"voltage": "10000",
"operator": "Облкоммунэнерго",
"diameter": "200",
"material": "медь",
"circuits": "2",
},
)
assert out == "10 кВ · Облкоммунэнерго · ⌀ 200 мм · медь · 2 цеп."