feat(provenance): govern source snapshots and data inputs

This commit is contained in:
Jens
2026-08-01 23:46:17 +02:00
parent cebeb5f3b4
commit 5b3c17b494
96 changed files with 20156 additions and 351 deletions
@@ -1,8 +1,9 @@
from __future__ import annotations
import json
from datetime import UTC, datetime
from hashlib import sha256
from pathlib import Path
from types import SimpleNamespace
from uuid import uuid4
import pytest
@@ -10,12 +11,13 @@ from fastapi.testclient import TestClient
from pydantic import ValidationError
from app.main import app
from app.models import Dataset, Export
from app.models import Dataset, Export, SourceRegistry, SourceSnapshot
from app.schemas.export import ExportCreateResponse, MapResultExportRequest
from app.schemas.project import ProjectRead
from app.services.export_service import ExportService
from app.services.project_service import ProjectService
from app.services.storage_service import StorageService
from app.services.source_registry_service import SourceRegistryService
from app.services.temporal_analysis_service import TemporalAnalysisService
from app.services.thematic_raster_analysis_service import ThematicRasterAnalysisService
@@ -48,6 +50,71 @@ def bbox_payload() -> dict:
}
def governed_dataset(
*,
project_id,
dataset_id,
name: str,
dataset_type: str,
source_key: str,
dataset_role: str = "source",
) -> Dataset:
"""Build an in-memory stand-in for a passed governed dataset.
Map-result export is an operational consumption boundary. These tests
must therefore model the same source registry/snapshot, checksum and
passed-contract evidence supplied by a real adapter rather than relying
on an old transient Dataset fixture.
"""
source = SourceRegistry(
id=uuid4(),
**SourceRegistryService.definition_for(source_key).as_model_values(),
)
checksum = sha256(f"{dataset_id}:{source_key}:{dataset_type}".encode("utf-8")).hexdigest()
snapshot = SourceSnapshot(
id=uuid4(),
source_registry_id=source.id,
snapshot_key=f"test:{source_key}:{checksum}",
checksum_sha256=checksum,
fetched_at=datetime.now(UTC),
crs="EPSG:31370",
units=source.default_units,
spatial_resolution_json={"x": 1.0, "y": 1.0, "unit": "m"},
temporal_coverage_json={"status": "test-fixture"},
geographic_coverage_json={"zone": "Flanders"},
observed_schema_json={"dataset_type": dataset_type},
freshness_status="current",
ingest_status="ingested",
known_limitations_json=["In-memory governed fixture used only by this export test."],
snapshot_metadata_json={"fixture_mode": True},
)
return Dataset(
id=dataset_id,
project_id=project_id,
name=name,
dataset_type=dataset_type,
source="governed test fixture",
dataset_role=dataset_role,
source_name=source.source_key,
source_registry_id=source.id,
source_snapshot_id=snapshot.id,
source_registry=source,
source_snapshot=snapshot,
data_contract_key=("geointel.raster.geotiff" if dataset_type == "raster" else "geointel.vector.geojson"),
data_contract_version="1.0.0",
validation_status="passed",
provenance_status="complete",
lineage_status="not_applicable",
quarantine_status="not_quarantined",
checksum_sha256=checksum,
metadata_json={"fixture_mode": True},
source_metadata={"fixture_mode": True, "source_registry_key": source.source_key},
provenance_metadata={"fixture_mode": True, "source_snapshot_id": str(snapshot.id)},
status="ready",
)
def test_map_result_export_request_requires_a_complete_target() -> None:
with pytest.raises(ValidationError):
MapResultExportRequest(project_id=uuid4(), mode="current", bbox=bbox_payload())
@@ -67,13 +134,12 @@ def test_current_vector_map_result_uses_authoritative_selection_export(monkeypat
project_id = uuid4()
dataset_id = uuid4()
area_id = uuid4()
dataset = Dataset(
id=dataset_id,
dataset = governed_dataset(
project_id=project_id,
dataset_id=dataset_id,
name="buildings.geojson",
dataset_type="vector",
source="fixture",
status="ready",
source_key="grb",
)
db = FakeSession({(Dataset, dataset_id): dataset})
expected = ExportCreateResponse(
@@ -109,14 +175,12 @@ def test_current_vector_map_result_uses_authoritative_selection_export(monkeypat
def test_partitioned_vector_map_result_uses_governed_partition_export(monkeypatch) -> None:
project_id = uuid4()
dataset_id = uuid4()
dataset = Dataset(
id=dataset_id,
dataset = governed_dataset(
project_id=project_id,
dataset_id=dataset_id,
name="vha-municipality.geojson",
dataset_type="vector",
source="VHA",
source_name="vmm_vha_bathymetry_profiles",
status="ready",
source_key="vmm_vha_bathymetry_profiles",
)
db = FakeSession({(Dataset, dataset_id): dataset})
expected = ExportCreateResponse(
@@ -159,14 +223,12 @@ def test_partitioned_vector_map_result_uses_governed_partition_export(monkeypatc
def test_raster_map_result_is_recomputed_and_persisted(tmp_path, monkeypatch) -> None:
project_id = uuid4()
dataset_id = uuid4()
dataset = Dataset(
id=dataset_id,
dataset = governed_dataset(
project_id=project_id,
dataset_id=dataset_id,
name="space-occupation.tif",
dataset_type="raster",
source="official",
source_name="department_omgeving_thematic_raster",
status="ready",
source_key="department_omgeving_thematic_raster",
)
db = FakeSession({(Dataset, dataset_id): dataset})
export_path = tmp_path / "space-occupation-analysis.json"
@@ -209,7 +271,26 @@ def test_evolution_map_result_is_recomputed_and_persisted(tmp_path, monkeypatch)
project_id = uuid4()
earlier_id = uuid4()
later_id = uuid4()
db = FakeSession()
earlier_dataset = governed_dataset(
project_id=project_id,
dataset_id=earlier_id,
name="forest-earlier.geojson",
dataset_type="vector",
source_key="inbo_bwk_natura2000",
)
later_dataset = governed_dataset(
project_id=project_id,
dataset_id=later_id,
name="forest-later.geojson",
dataset_type="vector",
source_key="inbo_bwk_natura2000",
)
db = FakeSession(
{
(Dataset, earlier_id): earlier_dataset,
(Dataset, later_id): later_dataset,
}
)
export_path = tmp_path / "forest-evolution.json"
captured: dict = {}