fix(platform): govern geospatial analysis and raster handoffs

This commit is contained in:
Jens
2026-08-30 06:00:15 +02:00
parent 96f90373dc
commit 80a2d1654d
63 changed files with 2335 additions and 312 deletions
+33 -5
View File
@@ -11,10 +11,20 @@ from fastapi.testclient import TestClient
from app.core.config import Settings
from app.core.errors import AppError
from app.main import app
from app.models import AnalysisRun, Dataset, Detection, Job, Project, SourceRegistry, SourceSnapshot
from app.models import (
AnalysisRun,
Dataset,
DatasetVersion,
Detection,
Job,
Project,
SourceRegistry,
SourceSnapshot,
)
from app.services.detection_service import DetectionService
from app.services.model_asset_catalog_service import ModelAssetCatalogService
from app.services.runtime_model_provenance_service import RuntimeModelProvenanceService
from app.services.tile_manifest_service import TileManifestService
class FakeSession:
@@ -98,6 +108,8 @@ def _project_and_raster_dataset():
source_name="test-derived-raster",
storage_path="storage/uploads/source.tif",
checksum_sha256=checksum,
crs="EPSG:4326",
bounds_json={"min_x": 4.0, "min_y": 51.0, "max_x": 5.0, "max_y": 52.0},
source_registry_id=source_registry_id,
source_snapshot_id=source_snapshot_id,
data_contract_key="geointel.raster.geotiff",
@@ -110,17 +122,22 @@ def _project_and_raster_dataset():
)
dataset.source_registry = source_registry
dataset.source_snapshot = source_snapshot
dataset.versions.append(
DatasetVersion(id=uuid4(), dataset_id=dataset_id, version=1, checksum_sha256=checksum)
)
db = FakeSession(objects={(Project, project_id): project, (Dataset, dataset_id): dataset})
return db, project_id, dataset_id
def _manifest(tmp_path: Path) -> Path:
def _manifest(tmp_path: Path, db: FakeSession, dataset: Dataset) -> Path:
tile_path = tmp_path / "tile_0000.tif"
tile_path.write_bytes(b"tile")
binding = TileManifestService.dataset_binding(db, dataset)
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(
json.dumps(
{
**binding,
"tile_set_id": "tiles-fixture",
"count": 1,
"crs": "EPSG:4326",
@@ -133,6 +150,7 @@ def _manifest(tmp_path: Path) -> Path:
"transform": [4.0, 0.01, 0.0, 52.0, 0.0, -0.01],
"crs": "EPSG:4326",
"index": 0,
**TileManifestService.tile_integrity(tile_path),
}
],
}
@@ -226,7 +244,11 @@ def test_model_asset_catalog_lists_supported_local_model_files(tmp_path: Path) -
assert asset.size_bytes == len(b"local model")
assert len(asset.sha256) == 64
assert asset.active is True
assert asset.status == "approved"
assert asset.runtime_available is True
assert asset.runtime_status == "active"
assert asset.governed_validation_status == "not_verified_by_catalog"
assert asset.promotion_status == "not_verified_by_catalog"
assert asset.status == "runtime_active"
assert asset.will_download_models is False
@@ -257,7 +279,10 @@ def test_model_asset_catalog_only_exposes_explicit_active_asset_in_runtime(tmp_p
assert response.total == 1
assert response.items[0].filename == active_file.name
assert response.items[0].active is True
assert response.items[0].status == "approved"
assert response.items[0].runtime_status == "active"
assert response.items[0].governed_validation_status == "not_verified_by_catalog"
assert response.items[0].promotion_status == "not_verified_by_catalog"
assert response.items[0].status == "runtime_active"
def test_model_asset_catalog_rejects_unknown_asset(tmp_path: Path) -> None:
@@ -284,6 +309,9 @@ def test_model_assets_api_returns_canonical_envelope(monkeypatch, tmp_path: Path
assert payload["data"]["total"] == 1
assert payload["data"]["items"][0]["model_asset_id"] == "building-detector-pt"
assert payload["data"]["items"][0]["active"] is True
assert payload["data"]["items"][0]["runtime_status"] == "active"
assert payload["data"]["items"][0]["governed_validation_status"] == "not_verified_by_catalog"
assert payload["data"]["items"][0]["promotion_status"] == "not_verified_by_catalog"
assert payload["data"]["items"][0]["will_download_models"] is False
@@ -309,7 +337,7 @@ def test_detection_run_persists_selected_model_asset_parameters(tmp_path, monkey
model_id="yolo-configured",
model_asset_id="building-detector-pt",
confidence_threshold=0.5,
tile_manifest_path=str(_manifest(tmp_path)),
tile_manifest_path=str(_manifest(tmp_path, db, db.get(Dataset, dataset_id))),
settings=settings,
yolo_adapter_class=MockYoloAdapter,
)