This commit is contained in:
parent
95e97da713
commit
10b79da2e9
2 changed files with 159 additions and 2 deletions
|
|
@ -146,9 +146,14 @@ def get_quarter_dump_data(
|
||||||
max_age = timedelta(days=_DUMP_MAX_AGE_DAYS)
|
max_age = timedelta(days=_DUMP_MAX_AGE_DAYS)
|
||||||
|
|
||||||
if row is None:
|
if row is None:
|
||||||
# Дампа нет — ставим harvest в очередь
|
# Дампа нет — ставим harvest в очередь и делаем fallback на cad_zouit (#243).
|
||||||
harvest_triggered = _trigger_harvest(quarter)
|
harvest_triggered = _trigger_harvest(quarter)
|
||||||
return make_empty_result(harvest_triggered=harvest_triggered)
|
cad_zouit_overlaps: list[dict[str, Any]] = (
|
||||||
|
_get_cad_zouit_overlaps(db, parcel_wkt) if parcel_wkt is not None else []
|
||||||
|
)
|
||||||
|
result = make_empty_result(harvest_triggered=harvest_triggered)
|
||||||
|
result["nspd_zouit_overlaps"] = cad_zouit_overlaps
|
||||||
|
return result
|
||||||
|
|
||||||
fetched_at: datetime = row[1]
|
fetched_at: datetime = row[1]
|
||||||
# Убедимся что timezone-aware для корректного сравнения
|
# Убедимся что timezone-aware для корректного сравнения
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,12 @@ import pytest
|
||||||
from app.services.site_finder.quarter_dump_lookup import (
|
from app.services.site_finder.quarter_dump_lookup import (
|
||||||
EMPTY_DUMP_RESULT,
|
EMPTY_DUMP_RESULT,
|
||||||
_extract_features_by_layer,
|
_extract_features_by_layer,
|
||||||
|
_get_cad_zouit_overlaps,
|
||||||
_get_opportunity_parcels,
|
_get_opportunity_parcels,
|
||||||
_get_red_lines,
|
_get_red_lines,
|
||||||
_get_risk_zones,
|
_get_risk_zones,
|
||||||
derive_quarter_cad,
|
derive_quarter_cad,
|
||||||
|
get_quarter_dump_data,
|
||||||
make_empty_result,
|
make_empty_result,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -402,3 +404,153 @@ def test_make_empty_result_has_tier4_keys() -> None:
|
||||||
)
|
)
|
||||||
def test_derive_quarter_cad(cad: str, expected: str | None) -> None:
|
def test_derive_quarter_cad(cad: str, expected: str | None) -> None:
|
||||||
assert derive_quarter_cad(cad) == expected
|
assert derive_quarter_cad(cad) == expected
|
||||||
|
|
||||||
|
|
||||||
|
# ── #243: cad_zouit fallback when dump absent ─────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def _make_mock_db_no_dump_with_cad_zouit(
|
||||||
|
cad_zouit_rows: list[tuple[Any, ...]],
|
||||||
|
) -> MagicMock:
|
||||||
|
"""Мок Session: nspd_quarter_dumps.first() → None; cad_zouit.fetchall() → rows."""
|
||||||
|
db = MagicMock()
|
||||||
|
first_result = MagicMock()
|
||||||
|
first_result.first.return_value = None # нет дампа
|
||||||
|
|
||||||
|
fetchall_result = MagicMock()
|
||||||
|
fetchall_result.fetchall.return_value = cad_zouit_rows
|
||||||
|
|
||||||
|
# Первый вызов execute (SELECT nspd_quarter_dumps) → first()=None
|
||||||
|
# Второй вызов execute (SELECT cad_zouit) → fetchall()=rows
|
||||||
|
db.execute.side_effect = [first_result, fetchall_result]
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
def test_cad_zouit_fallback_fires_when_no_dump() -> None:
|
||||||
|
"""#243: cad_zouit fallback срабатывает когда nspd_quarter_dumps пуст глобально.
|
||||||
|
|
||||||
|
Ожидаем: nspd_zouit_overlaps непустой, source='cad_zouit'.
|
||||||
|
"""
|
||||||
|
cad_zouit_rows: list[tuple[Any, ...]] = [
|
||||||
|
# (type_zone, category_name, name_by_doc, reg_numb_border, id)
|
||||||
|
("Охранная зона трубопровода", "Трубопровод", "ГВС ул. Ленина", "66-66-00/123", 42),
|
||||||
|
]
|
||||||
|
db = _make_mock_db_no_dump_with_cad_zouit(cad_zouit_rows)
|
||||||
|
|
||||||
|
wkt = "POLYGON((60 56,61 56,61 57,60 57,60 56))"
|
||||||
|
result = get_quarter_dump_data(db, "66:41:0603016:194", wkt)
|
||||||
|
|
||||||
|
overlaps: list[dict[str, Any]] = result["nspd_zouit_overlaps"]
|
||||||
|
assert len(overlaps) == 1
|
||||||
|
assert overlaps[0]["source"] == "cad_zouit"
|
||||||
|
assert overlaps[0]["type_zone"] == "Охранная зона трубопровода"
|
||||||
|
assert overlaps[0]["name"] == "ГВС ул. Ленина"
|
||||||
|
# dump.available остаётся False (нет дампа)
|
||||||
|
assert result["nspd_dump"]["available"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_cad_zouit_fallback_no_dump_no_parcel_wkt() -> None:
|
||||||
|
"""#243: dump=None + parcel_wkt=None → overlaps пустой, не вызываем cad_zouit."""
|
||||||
|
db = MagicMock()
|
||||||
|
first_result = MagicMock()
|
||||||
|
first_result.first.return_value = None
|
||||||
|
db.execute.return_value = first_result
|
||||||
|
|
||||||
|
result = get_quarter_dump_data(db, "66:41:0603016:194", None)
|
||||||
|
|
||||||
|
assert result["nspd_zouit_overlaps"] == []
|
||||||
|
# cad_zouit SELECT не должен вызываться (только один execute для dump lookup)
|
||||||
|
assert db.execute.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_cad_zouit_fallback_empty_when_no_overlaps() -> None:
|
||||||
|
"""#243: dump=None + cad_zouit возвращает 0 строк → nspd_zouit_overlaps пуст."""
|
||||||
|
db = _make_mock_db_no_dump_with_cad_zouit([])
|
||||||
|
|
||||||
|
wkt = "POLYGON((60 56,61 56,61 57,60 57,60 56))"
|
||||||
|
result = get_quarter_dump_data(db, "66:41:0603016:194", wkt)
|
||||||
|
|
||||||
|
assert result["nspd_zouit_overlaps"] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_cad_zouit_overlaps_returns_correct_schema() -> None:
|
||||||
|
"""_get_cad_zouit_overlaps: поля group_key/layer/source/type_zone корректны."""
|
||||||
|
rows: list[tuple[Any, ...]] = [
|
||||||
|
# (type_zone, category_name, name_by_doc, reg_numb_border, id)
|
||||||
|
("Охранная зона ЛЭП 110кВ", "Электроснабжение", "ВЛ-110", "66-123", 7),
|
||||||
|
("СЗЗ промышленного объекта", "Санитарная", "Завод", "66-456", 8),
|
||||||
|
]
|
||||||
|
db = _make_mock_db_with_rows(rows)
|
||||||
|
|
||||||
|
result = _get_cad_zouit_overlaps(db, "POLYGON((0 0,1 0,1 1,0 0))")
|
||||||
|
|
||||||
|
assert len(result) == 2
|
||||||
|
lep = result[0]
|
||||||
|
assert lep["group_key"] == "cad_zouit"
|
||||||
|
assert lep["source"] == "cad_zouit"
|
||||||
|
assert lep["type_zone"] == "Охранная зона ЛЭП 110кВ"
|
||||||
|
assert lep["subcategory"] is None # всегда NULL в cad_zouit
|
||||||
|
assert lep["name"] == "ВЛ-110"
|
||||||
|
assert lep["raw_props"]["zouit_id"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_nspd_path_used_when_zouit_count_nonzero() -> None:
|
||||||
|
"""#243 backward compat: когда dump существует с zouit_count>0 — NSPD path, не cad_zouit.
|
||||||
|
|
||||||
|
Мок: row.zouit_count=3, fetchall возвращает NSPD features.
|
||||||
|
Проверяем source='nspd-quarter-dump' (не cad_zouit).
|
||||||
|
"""
|
||||||
|
# Строка дампа: все поля через MagicMock
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
|
dump_row = MagicMock()
|
||||||
|
dump_row.__getitem__ = lambda self, i: [
|
||||||
|
"66:41:0603016", # quarter_cad
|
||||||
|
datetime(2026, 1, 1, tzinfo=UTC), # fetched_at_utc (свежий)
|
||||||
|
100, # total_features
|
||||||
|
None, # harvest_error
|
||||||
|
1, # territorial_zones_count
|
||||||
|
3, # zouit_count > 0 → NSPD path
|
||||||
|
0, # engineering_count
|
||||||
|
0, # risks_count
|
||||||
|
0, # opportunity_count
|
||||||
|
0, # red_lines_count
|
||||||
|
][i]
|
||||||
|
|
||||||
|
nspd_zouit_rows: list[tuple[Any, ...]] = [
|
||||||
|
# (layer, props)
|
||||||
|
("zouit_engineering", {"name": "ЛЭП", "subcategory": 17}),
|
||||||
|
]
|
||||||
|
|
||||||
|
db = MagicMock()
|
||||||
|
first_result = MagicMock()
|
||||||
|
first_result.first.return_value = dump_row
|
||||||
|
|
||||||
|
# Последующие вызовы execute (spatial queries) — возвращаем пустые кроме zouit
|
||||||
|
zouit_result = MagicMock()
|
||||||
|
zouit_result.fetchall.return_value = nspd_zouit_rows
|
||||||
|
|
||||||
|
empty_result = MagicMock()
|
||||||
|
empty_result.fetchall.return_value = []
|
||||||
|
empty_result.first.return_value = None
|
||||||
|
|
||||||
|
# [0]=dumps lookup, [1]=zoning(first), [2]=zouit(fetchall), [3+]=others
|
||||||
|
db.execute.side_effect = [
|
||||||
|
first_result,
|
||||||
|
empty_result, # _get_zoning first()
|
||||||
|
zouit_result, # _get_zouit_overlaps fetchall()
|
||||||
|
empty_result, # _get_engineering_nearby fetchall()
|
||||||
|
empty_result, # _get_risk_zones fetchall()
|
||||||
|
empty_result, # _get_opportunity_parcels fetchall()
|
||||||
|
empty_result, # _get_red_lines fetchall()
|
||||||
|
]
|
||||||
|
|
||||||
|
result = get_quarter_dump_data(
|
||||||
|
db, "66:41:0603016:194", "POLYGON((60 56,61 56,61 57,60 57,60 56))"
|
||||||
|
)
|
||||||
|
|
||||||
|
overlaps: list[dict[str, Any]] = result["nspd_zouit_overlaps"]
|
||||||
|
assert len(overlaps) == 1
|
||||||
|
assert overlaps[0]["source"] == "nspd-quarter-dump"
|
||||||
|
# dump.available=True т.к. свежий дамп
|
||||||
|
assert result["nspd_dump"]["available"] is True
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue