fix(platform): govern geospatial analysis and raster handoffs
This commit is contained in:
@@ -13,12 +13,23 @@ from shapely.geometry import box, mapping
|
||||
|
||||
from app.core.config import Settings
|
||||
from app.core.errors import AppError
|
||||
from app.models import AnalysisRun, Area, Dataset, Detection, Job, Project, SourceRegistry, SourceSnapshot
|
||||
from app.models import (
|
||||
AnalysisRun,
|
||||
Area,
|
||||
Dataset,
|
||||
DatasetVersion,
|
||||
Detection,
|
||||
Job,
|
||||
Project,
|
||||
SourceRegistry,
|
||||
SourceSnapshot,
|
||||
)
|
||||
from app.services.detection_georeferencing import pixel_bbox_to_epsg4326_polygon
|
||||
from app.services.detection_service import DetectionService
|
||||
from app.services.model_registry_service import ModelRegistryService
|
||||
from app.services.model_validation_scope_service import ModelValidationScopeService
|
||||
from app.services.runtime_model_provenance_service import RuntimeModelProvenanceService
|
||||
from app.services.tile_manifest_service import TileManifestService
|
||||
from app.services.yolo_adapter import YoloDetectionAdapter
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -189,6 +200,8 @@ def _project_and_dataset(dataset_type: str = "raster"):
|
||||
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",
|
||||
@@ -201,6 +214,9 @@ def _project_and_dataset(dataset_type: str = "raster"):
|
||||
)
|
||||
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
|
||||
|
||||
@@ -309,28 +325,34 @@ def _write_model_sidecar(
|
||||
)
|
||||
|
||||
|
||||
def _manifest(tmp_path: Path, tile_count: int = 1) -> Path:
|
||||
def _manifest(tmp_path: Path, tile_count: int = 1, dataset: Dataset | None = None) -> Path:
|
||||
tiles = []
|
||||
for index in range(tile_count):
|
||||
tile_path = tmp_path / f"tile_{index:04d}.tif"
|
||||
tile_path.write_bytes(b"fixture")
|
||||
tiles.append(
|
||||
{
|
||||
tile = {
|
||||
"path": str(tile_path),
|
||||
"pixel_window": [0, 0, 100, 100],
|
||||
"bounds": [4.0, 51.0, 5.0, 52.0],
|
||||
"transform": [4.0, 0.01, 0.0, 52.0, 0.0, -0.01],
|
||||
"crs": "EPSG:4326",
|
||||
"index": index,
|
||||
**TileManifestService.tile_integrity(tile_path),
|
||||
}
|
||||
)
|
||||
tiles.append(tile)
|
||||
binding = (
|
||||
TileManifestService.dataset_binding(SimpleNamespace(get=lambda *_args: None), dataset)
|
||||
if dataset is not None
|
||||
else {}
|
||||
)
|
||||
manifest_path = tmp_path / "manifest.json"
|
||||
manifest_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
**binding,
|
||||
"tile_set_id": "tiles-fixture",
|
||||
"source_dataset_id": str(uuid4()),
|
||||
"source_raster_id": str(uuid4()),
|
||||
"source_dataset_id": binding.get("source_dataset_id", str(uuid4())),
|
||||
"source_raster_id": binding.get("source_raster_id", str(uuid4())),
|
||||
"crs": "EPSG:4326",
|
||||
"bounds": [4.0, 51.0, 5.0, 52.0],
|
||||
"tile_size": 100,
|
||||
@@ -514,7 +536,7 @@ def test_yolo_run_fails_closed_before_adapter_load_without_sidecar(tmp_path: Pat
|
||||
dataset_id=dataset_id,
|
||||
model_id="yolo-configured",
|
||||
confidence_threshold=0.5,
|
||||
tile_manifest_path=str(_manifest(tmp_path)),
|
||||
tile_manifest_path=str(_manifest(tmp_path, dataset=db.get(Dataset, dataset_id))),
|
||||
settings=settings,
|
||||
yolo_adapter_class=AvailableAdapter,
|
||||
)
|
||||
@@ -530,7 +552,7 @@ def test_yolo_run_rejects_manifest_over_tile_limit(tmp_path: Path) -> None:
|
||||
model_path.write_bytes(b"local weights")
|
||||
settings = _settings(tmp_path, yolo_model_path=str(model_path), yolo_max_tiles=1)
|
||||
_write_model_sidecar(model_path, settings, db=db)
|
||||
manifest_path = _manifest(tmp_path, tile_count=2)
|
||||
manifest_path = _manifest(tmp_path, tile_count=2, dataset=db.get(Dataset, dataset_id))
|
||||
|
||||
result = DetectionService.run_detection(
|
||||
db=db,
|
||||
@@ -609,7 +631,7 @@ def test_yolo_run_rejects_unbound_model_snapshot_before_adapter_load(tmp_path: P
|
||||
dataset_id=dataset_id,
|
||||
model_id="yolo-configured",
|
||||
confidence_threshold=0.5,
|
||||
tile_manifest_path=str(_manifest(tmp_path)),
|
||||
tile_manifest_path=str(_manifest(tmp_path, dataset=db.get(Dataset, dataset_id))),
|
||||
settings=settings,
|
||||
yolo_adapter_class=NeverLoadUnboundModelAdapter,
|
||||
)
|
||||
@@ -638,7 +660,7 @@ def test_yolo_run_persists_mocked_georeferenced_detections(tmp_path: Path) -> No
|
||||
model_path.write_bytes(b"local weights")
|
||||
settings = _settings(tmp_path, yolo_model_path=str(model_path), yolo_model_version="local-test")
|
||||
_write_model_sidecar(model_path, settings, db=db)
|
||||
manifest_path = _manifest(tmp_path, tile_count=1)
|
||||
manifest_path = _manifest(tmp_path, tile_count=1, dataset=db.get(Dataset, dataset_id))
|
||||
|
||||
result = DetectionService.run_detection(
|
||||
db=db,
|
||||
@@ -678,7 +700,7 @@ def test_yolo_class_filter_is_case_insensitive_and_persists_canonical_class(tmp_
|
||||
model_path.write_bytes(b"local weights")
|
||||
settings = _settings(tmp_path, yolo_model_path=str(model_path))
|
||||
_write_model_sidecar(model_path, settings, db=db)
|
||||
manifest_path = _manifest(tmp_path, tile_count=1)
|
||||
manifest_path = _manifest(tmp_path, tile_count=1, dataset=db.get(Dataset, dataset_id))
|
||||
|
||||
result = DetectionService.run_detection(
|
||||
db=db,
|
||||
@@ -706,7 +728,7 @@ def test_yolo_run_suppresses_cross_tile_duplicate_detections(tmp_path: Path) ->
|
||||
model_path.write_bytes(b"local weights")
|
||||
settings = _settings(tmp_path, yolo_model_path=str(model_path), yolo_duplicate_iou_threshold=0.5)
|
||||
_write_model_sidecar(model_path, settings, db=db)
|
||||
manifest_path = _manifest(tmp_path, tile_count=2)
|
||||
manifest_path = _manifest(tmp_path, tile_count=2, dataset=db.get(Dataset, dataset_id))
|
||||
|
||||
result = DetectionService.run_detection(
|
||||
db=db,
|
||||
|
||||
Reference in New Issue
Block a user