gendesign/backend/tests/workers/test_opportunity_harvest.py
lekss361 b1530467f7
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m28s
Deploy / build-worker (push) Successful in 2m35s
Deploy / deploy (push) Successful in 1m10s
feat(sf): opportunity-ЗУ harvest → ird_overlays (future-supply, #1100)
opportunity_harvest.py — grid-walk НСПД opportunity-слоёв (ЗУ аукцион/схема/свободные/межевание/ККР) по кварталам ЕКБ → ird_overlays с layer_kind='opportunity_*'. Переиспользует движок ird_harvest (#1090): _upsert_feature/_ekb_quarters. +krt_territories=37430 в LAYERS. Beat monthly (1-е 05:30). Self-contained, прод-прогон gated B6.

Closes #1086.
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-06 19:32:45 +00:00

118 lines
4.2 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.

"""Тесты harvest_opportunity_overlays (#1086) — НСПД opportunity-harvest → ird_overlays.
Сеть/БД не дёргаются: мокаем NSPDClient + SessionLocal, проверяем grid-walk по opportunity-слоям,
layer_kind='opportunity_*', skip пустого квартала, commit/close. Движок общий с ird_harvest.
"""
from __future__ import annotations
from contextlib import contextmanager
from typing import Any
from app.workers.tasks import opportunity_harvest
class _Feat:
def __init__(self, geometry: dict[str, Any] | None, properties: dict[str, Any]) -> None:
self.geometry = geometry
self.properties = properties
class _SearchRes:
def __init__(self, feat: _Feat | None) -> None:
self.first = feat
class _FakeClient:
"""NSPDClient-двойник: квартал → полигон (3857), grid-walk → 1 фича на слой."""
def __init__(self, *a: Any, **kw: Any) -> None:
self.grid_calls: list[int] = []
def search_by_cad(self, cad: str, thematic_id: int = 1) -> _SearchRes:
ring = [[0, 0], [100, 0], [100, 100], [0, 100], [0, 0]]
geom = {"type": "Polygon", "coordinates": [ring]}
return _SearchRes(_Feat(geom, {"cad": cad}))
def get_features_in_bbox_grid(self, layer_id: int, bbox: Any, **kw: Any) -> list[_Feat]:
self.grid_calls.append(layer_id)
return [
_Feat(
{"type": "Polygon", "coordinates": [[[1, 1], [2, 1], [2, 2], [1, 1]]]},
{
"geom_data_id": 2000 + layer_id,
"reg_numb_border": "66:41:0204016:10",
"category": 36940,
"subcategory": 5,
},
)
]
class _FakeDB:
"""Session-двойник: пишет execute-вызовы, begin_nested как no-op CM."""
def __init__(self) -> None:
self.executed: list[tuple[Any, dict[str, Any] | None]] = []
self.committed = False
self.closed = False
@contextmanager
def begin_nested(self) -> Any:
yield
def execute(self, sql: Any, params: dict[str, Any] | None = None) -> Any:
self.executed.append((sql, params))
return None
def commit(self) -> None:
self.committed = True
def close(self) -> None:
self.closed = True
def _patch(monkeypatch: Any, db: _FakeDB) -> _FakeClient:
client = _FakeClient()
monkeypatch.setattr(opportunity_harvest, "NSPDClient", lambda *a, **kw: client)
monkeypatch.setattr(opportunity_harvest, "SessionLocal", lambda: db)
return client
def test_harvest_explicit_quarters_all_opportunity_layers(monkeypatch: Any) -> None:
"""1 квартал × N opportunity-слоёв → upsert по фиче на слой; layer_kind='opportunity_*'."""
db = _FakeDB()
client = _patch(monkeypatch, db)
n_layers = len(opportunity_harvest.OPPORTUNITY_LAYER_KINDS)
result = opportunity_harvest.harvest_opportunity_overlays(quarters=["66:41:0204016"])
assert result == {"quarters": 1, "features": n_layers}
assert len(client.grid_calls) == n_layers
assert len(db.executed) == n_layers
assert db.committed is True
assert db.closed is True
kinds = {params["layer_kind"] for _, params in db.executed if params}
assert kinds == set(opportunity_harvest.OPPORTUNITY_LAYER_KINDS.values())
assert all(k.startswith("opportunity_") for k in kinds)
def test_harvest_skips_empty_quarter(monkeypatch: Any) -> None:
"""Квартал без геометрии в НСПД → 0 кварталов/фич, без падения."""
db = _FakeDB()
client = _patch(monkeypatch, db)
monkeypatch.setattr(client, "search_by_cad", lambda cad, thematic_id=1: _SearchRes(None))
result = opportunity_harvest.harvest_opportunity_overlays(quarters=["66:41:9999999"])
assert result == {"quarters": 0, "features": 0}
assert db.executed == []
assert db.committed is True
def test_krt_layer_registered() -> None:
"""ККР (37430) добавлен в LAYERS и в opportunity-набор (#1086)."""
from app.services.scrapers.nspd_client import LAYERS
assert LAYERS["krt_territories"] == 37430
assert "krt_territories" in opportunity_harvest.OPPORTUNITY_LAYER_KINDS