gendesign/backend/tests/services/test_krt_lookup.py
lekss361 97731a2b09
Some checks are pending
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 9s
Deploy / build-backend (push) Successful in 1m29s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m23s
feat(sf): КРТ-реквизиты (ekb_krt_sites) в ИРД-блок analyze (#1131)
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-07 11:50:29 +00:00

176 lines
6.7 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.

"""Тесты parcel_krt_requisites (#1060) — КРТ-реквизиты из ekb_krt_sites + planning_projects.
DB/сеть не дёргаются: мокаем db.execute через FakeDB.
Проверяем:
- exact-матч → matched=True, confidence='exact', реквизиты заполнены;
- planning overlap без матча → matched=False, confidence='none', без застройщика;
- пустой WKT → [];
- OperationalError (таблица не задеплоена) → graceful [];
- дубликаты строк (один pp_id × krt_id) → дедублируются.
"""
from __future__ import annotations
from decimal import Decimal
from typing import Any
from sqlalchemy.exc import OperationalError
from app.services.site_finder.krt_lookup import parcel_krt_requisites
class _MappingRow(dict): # type: ignore[type-arg]
"""Имитирует sqlalchemy MappingResult row (dict-like)."""
class _Result:
def __init__(self, rows: list[dict[str, Any]]) -> None:
self._rows = [_MappingRow(r) for r in rows]
def mappings(self) -> _Result:
return self
def all(self) -> list[_MappingRow]:
return self._rows
class _FakeDB:
"""Мок SQLAlchemy Session: первый вызов execute → first_rows; второй → second_rows."""
def __init__(
self,
first_rows: list[dict[str, Any]] | None = None,
second_rows: list[dict[str, Any]] | None = None,
raise_first: Exception | None = None,
raise_second: Exception | None = None,
) -> None:
self._calls = 0
self._first = first_rows or []
self._second = second_rows or []
self._raise_first = raise_first
self._raise_second = raise_second
def execute(self, sql: Any, params: dict[str, Any] | None = None) -> _Result:
self._calls += 1
if self._calls == 1:
if self._raise_first:
raise self._raise_first
return _Result(self._first)
if self._raise_second:
raise self._raise_second
return _Result(self._second)
_WKT = "POLYGON((60.5 56.8, 60.6 56.8, 60.6 56.9, 60.5 56.9, 60.5 56.8))"
# Типичная строка exact-матча (planning_projects ⋈ ekb_krt_sites)
_EXACT_ROW = {
"pp_id": 1166,
"pp_full_name": (
" ПАГЕ № 1970 от 16.08.2024\nПроект межевания территории в районе улицы Летчиков"
),
"dmd_actual_year": 2024,
"doc_status_name": "Действующий",
"krt_id": 7,
"site_name": "2. КРТ жилой застройки в районе ул. Летчиков",
"developer_name": "ООО СЗ Летчиков",
"contract_number": "2-7/21.7-14",
"auction_final_price": Decimal("5000000"),
"gradpotential_sqm": Decimal("25000"),
"area_ha": Decimal("1.50"),
"krt_kind": "жилой застройки",
}
def test_exact_match_returns_requisites() -> None:
"""Exact-матч по ПАГЕ-номер+год → matched=True, реквизиты застройщика заполнены."""
db = _FakeDB(first_rows=[_EXACT_ROW])
result = parcel_krt_requisites(db, _WKT)
assert len(result) == 1
r = result[0]
assert r["matched"] is True
assert r["confidence"] == "exact"
assert r["krt_geom_ref"] == 1166
assert r["site_name"] == "2. КРТ жилой застройки в районе ул. Летчиков"
assert r["developer_name"] == "ООО СЗ Летчиков"
assert r["contract_number"] == "2-7/21.7-14"
assert r["auction_final_price"] == 5_000_000.0
assert r["gradpotential_sqm"] == 25_000.0
assert r["area_ha"] == 1.50
def test_exact_match_deduplicates_pp_krt_pair() -> None:
"""Дублирующиеся строки (pp_id × krt_id) дедублируются → один результат."""
db = _FakeDB(first_rows=[_EXACT_ROW, _EXACT_ROW])
result = parcel_krt_requisites(db, _WKT)
assert len(result) == 1
def test_no_match_returns_geom_only() -> None:
"""Первый запрос пуст (нет exact-матча) → второй запрос → matched=False без реквизитов."""
no_match_row = {
"pp_id": 999,
"pp_full_name": " ПАГЕ № 4033 от 27.12.2022\n"
"Проект планировки территории в районе улицы Краснофлотцев",
"dmd_actual_year": 2022,
"doc_status_name": "Действующий",
}
db = _FakeDB(first_rows=[], second_rows=[no_match_row])
result = parcel_krt_requisites(db, _WKT)
assert len(result) == 1
r = result[0]
assert r["matched"] is False
assert r["confidence"] == "none"
assert r["krt_geom_ref"] == 999
assert r["developer_name"] is None
assert r["auction_final_price"] is None
assert r["gradpotential_sqm"] is None
assert r["site_name"] is None
def test_empty_wkt_returns_empty_list() -> None:
"""parcel_wkt=None → пустой список без вызова DB."""
db = _FakeDB(first_rows=[_EXACT_ROW])
assert parcel_krt_requisites(db, None) == []
assert db._calls == 0 # DB не вызывалась
def test_empty_string_wkt_returns_empty_list() -> None:
"""parcel_wkt='' → пустой список."""
assert parcel_krt_requisites(_FakeDB(), "") == []
def test_operational_error_first_query_returns_empty() -> None:
"""OperationalError на первом запросе (таблица не задеплоена) → []."""
db = _FakeDB(raise_first=OperationalError("stmt", {}, Exception("relation does not exist")))
result = parcel_krt_requisites(db, _WKT)
assert result == []
def test_operational_error_second_query_returns_empty() -> None:
"""OperationalError на втором запросе (no-match fallback) → []."""
db = _FakeDB(
first_rows=[],
raise_second=OperationalError("stmt", {}, Exception("relation does not exist")),
)
result = parcel_krt_requisites(db, _WKT)
assert result == []
def test_none_decimal_fields_become_none() -> None:
"""NULL Decimal-поля → None в результате, не падают при float()."""
row = {**_EXACT_ROW, "auction_final_price": None, "gradpotential_sqm": None, "area_ha": None}
db = _FakeDB(first_rows=[row])
result = parcel_krt_requisites(db, _WKT)
assert result[0]["auction_final_price"] is None
assert result[0]["gradpotential_sqm"] is None
assert result[0]["area_ha"] is None
def test_no_overlap_at_all_returns_empty() -> None:
"""Оба запроса возвращают пустые списки → []."""
db = _FakeDB(first_rows=[], second_rows=[])
result = parcel_krt_requisites(db, _WKT)
assert result == []