feat: add governed hydrology and historical imagery
This commit is contained in:
@@ -86,7 +86,13 @@ class FakeImageResponse:
|
||||
return self.content[:limit]
|
||||
|
||||
|
||||
def _selection_payload(*, side_m: float = 512.0, force_refresh: bool = True, area_id=None) -> OrthophotoAcquireRequest:
|
||||
def _selection_payload(
|
||||
*,
|
||||
side_m: float = 512.0,
|
||||
force_refresh: bool = True,
|
||||
area_id=None,
|
||||
product_key: str = "most_recent",
|
||||
) -> OrthophotoAcquireRequest:
|
||||
west, south = 199_000.0, 210_000.0
|
||||
transformer = Transformer.from_crs("EPSG:31370", "EPSG:4326", always_xy=True)
|
||||
min_lon, min_lat = transformer.transform(west, south)
|
||||
@@ -100,6 +106,7 @@ def _selection_payload(*, side_m: float = 512.0, force_refresh: bool = True, are
|
||||
"crs": "EPSG:4326",
|
||||
},
|
||||
area_id=area_id,
|
||||
product_key=product_key,
|
||||
force_refresh=force_refresh,
|
||||
)
|
||||
|
||||
@@ -136,6 +143,25 @@ def test_orthophoto_request_is_bounded_and_uses_official_wms_contract() -> None:
|
||||
assert len(prepared["request_hash"]) == 64
|
||||
|
||||
|
||||
def test_orthophoto_product_registry_exposes_only_governed_official_layers() -> None:
|
||||
settings = Settings(_env_file=None)
|
||||
products = OrthophotoAcquisitionService.list_products(settings)
|
||||
keys = [item["key"] for item in products]
|
||||
|
||||
assert keys[0] == "most_recent"
|
||||
assert {"2025", "2012", "2008_2011", "2000_2003", "1979_1990", "1971"}.issubset(keys)
|
||||
assert next(item for item in products if item["key"] == "most_recent")["supports_detection"] is True
|
||||
assert all(item["supports_detection"] is False for item in products if item["key"] != "most_recent")
|
||||
|
||||
prepared = OrthophotoAcquisitionService._prepared_request(_selection_payload(product_key="1971"), settings)
|
||||
assert prepared["params"]["LAYERS"] == "OKZPAN71VL"
|
||||
assert prepared["wms_url"] == "https://geo.api.vlaanderen.be/OKZ/wms"
|
||||
|
||||
with pytest.raises(AppError) as exc_info:
|
||||
OrthophotoAcquisitionService._prepared_request(_selection_payload(product_key="arbitrary-layer"), settings)
|
||||
assert exc_info.value.code == "ORTHOPHOTO_PRODUCT_NOT_SUPPORTED"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_m", "expected_code"),
|
||||
[(64.0, "ORTHOPHOTO_SELECTION_TOO_SMALL"), (1_200.0, "ORTHOPHOTO_SELECTION_TOO_LARGE")],
|
||||
@@ -240,7 +266,7 @@ def test_orthophoto_acquisition_reuses_fresh_exact_request_without_provider_call
|
||||
cached = Dataset(
|
||||
id=uuid4(),
|
||||
project_id=project_id,
|
||||
name=f"orthofoto_selectie_{prepared['request_hash'][:12]}.tif",
|
||||
name=f"orthofoto_most_recent_{prepared['request_hash'][:12]}.tif",
|
||||
dataset_type="raster",
|
||||
source="Digitaal Vlaanderen",
|
||||
source_name="digitaal_vlaanderen_orthophoto",
|
||||
@@ -277,6 +303,54 @@ def test_orthophoto_provider_rejects_non_image_response() -> None:
|
||||
assert exc_info.value.code == "ORTHOPHOTO_PROVIDER_INVALID_RESPONSE"
|
||||
|
||||
|
||||
def test_historical_orthophoto_persists_temporal_product_provenance(tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
payload = _selection_payload(product_key="2020")
|
||||
db = FakeSession({(Project, project_id): Project(id=project_id, name="Mol")})
|
||||
settings = Settings(_env_file=None, storage_root=str(tmp_path), orthophoto_resolution_m=1.0)
|
||||
prepared = OrthophotoAcquisitionService._prepared_request(payload, settings)
|
||||
response = FakeImageResponse(_source_tiff(prepared["width"], prepared["height"]))
|
||||
|
||||
result = OrthophotoAcquisitionService.acquire(
|
||||
db,
|
||||
project_id,
|
||||
payload,
|
||||
settings=settings,
|
||||
opener=lambda *_args, **_kwargs: response,
|
||||
)
|
||||
|
||||
dataset = next(row for row in db.added if isinstance(row, Dataset))
|
||||
assert result["product_key"] == "2020"
|
||||
assert result["supports_detection"] is False
|
||||
assert dataset.observed_at.year == 2020
|
||||
assert dataset.temporal_granularity == "year"
|
||||
assert dataset.source_metadata["layer"] == "OMWRGB20VL"
|
||||
assert dataset.source_metadata["product_key"] == "2020"
|
||||
assert dataset.provenance_metadata["spatial_hash"] == prepared["spatial_hash"]
|
||||
|
||||
|
||||
def test_persisted_orthophoto_renders_browser_png(tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
path = tmp_path / "ortho.tif"
|
||||
path.write_bytes(_source_tiff(32, 24))
|
||||
dataset = Dataset(
|
||||
id=dataset_id,
|
||||
project_id=project_id,
|
||||
name="ortho.tif",
|
||||
dataset_type="raster",
|
||||
source="Digitaal Vlaanderen",
|
||||
source_name="digitaal_vlaanderen_orthophoto",
|
||||
status="ready",
|
||||
storage_path=str(path),
|
||||
)
|
||||
db = FakeSession({(Dataset, dataset_id): dataset})
|
||||
|
||||
png = OrthophotoAcquisitionService.render_png(db, project_id, dataset_id)
|
||||
|
||||
assert png.startswith(b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
|
||||
def test_orthophoto_endpoint_returns_canonical_job_envelope(monkeypatch) -> None:
|
||||
project_id = uuid4()
|
||||
output_dataset_id = uuid4()
|
||||
@@ -307,6 +381,22 @@ def test_orthophoto_endpoint_returns_canonical_job_envelope(monkeypatch) -> None
|
||||
assert any(isinstance(row, Job) for row in db.added)
|
||||
|
||||
|
||||
def test_orthophoto_product_endpoint_returns_canonical_envelope() -> None:
|
||||
project_id = uuid4()
|
||||
db = FakeSession({(Project, project_id): Project(id=project_id, name="Mol")})
|
||||
app.dependency_overrides[get_db] = lambda: db
|
||||
try:
|
||||
response = TestClient(app).get(f"/api/v1/projects/{project_id}/datasets/orthophoto/products")
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert set(body) == {"data"}
|
||||
assert body["data"]["total"] == len(body["data"]["items"])
|
||||
assert body["data"]["items"][0]["key"] == "most_recent"
|
||||
|
||||
|
||||
def test_frontend_connects_map_selection_to_existing_detection_and_qa_flows() -> None:
|
||||
app_source = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||
hook_source = (ROOT / "frontend" / "src" / "hooks" / "useMapOrthophotoAnalysis.ts").read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user