Add demo raster fixture workflow
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-23 00:12:20 +02:00
parent 4ef2d82cfa
commit ca730edc1c
13 changed files with 208 additions and 14 deletions
+1
View File
@@ -10,6 +10,7 @@ class DemoWorkflowResponse(BaseModel):
area_id: UUID
reference_dataset_id: UUID
candidate_dataset_id: UUID
raster_dataset_id: UUID | None = None
quality_check_id: UUID
metric_count: int
status: str
+104 -1
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import json
import os
import importlib
from datetime import datetime, timezone
from pathlib import Path
from uuid import UUID, uuid4
@@ -14,6 +15,7 @@ from app.schemas.demo import DemoWorkflowResponse
from app.services.geojson_service import parse_geojson_payload
from app.services.qa_service import QaService
from app.services.quality_service import QualityService
from app.services.raster_service import extract_raster_metadata
from app.services.storage_service import StorageService
from app.services.vector_feature_service import VectorFeatureService
from app.utils.geometry import area_m2, geometry_bbox_polygon, normalize_to_multipolygon
@@ -24,6 +26,7 @@ class DemoWorkflowService:
AREA_NAME = "Demo AOI - Geel buildings"
REFERENCE_FILENAME = "demo_reference_buildings.geojson"
CANDIDATE_FILENAME = "demo_predicted_buildings.geojson"
RASTER_FILENAME = "demo_context_raster.tif"
EXPECTED_METRICS_FILENAME = "expected_qa_metrics.json"
@staticmethod
@@ -101,15 +104,28 @@ class DemoWorkflowService:
.filter(Dataset.project_id == project_id)
.filter(Dataset.dataset_role == "source")
.filter(Dataset.source_name == "fixture")
.filter(Dataset.dataset_type == "vector")
.first()
)
raster = DemoWorkflowService._find_demo_raster_dataset(db, project_id)
quality_check = (
db.query(QualityCheck)
.filter(QualityCheck.project_id == project_id)
.filter(QualityCheck.check_type == "demo_candidate_vs_reference")
.first()
)
return bool(area and reference and candidate and quality_check)
return bool(area and reference and candidate and raster and quality_check)
@staticmethod
def _find_demo_raster_dataset(db: Session, project_id: UUID) -> Dataset | None:
return (
db.query(Dataset)
.filter(Dataset.project_id == project_id)
.filter(Dataset.dataset_type == "raster")
.filter(Dataset.source_name == "fixture")
.filter(Dataset.name == DemoWorkflowService.RASTER_FILENAME)
.first()
)
@staticmethod
def _create_area(db: Session, project_id: UUID) -> Area:
@@ -207,6 +223,84 @@ class DemoWorkflowService:
)
return dataset
@staticmethod
def _create_demo_raster_bytes() -> bytes:
numpy = importlib.import_module("numpy")
rasterio = importlib.import_module("rasterio")
rasterio_io = importlib.import_module("rasterio.io")
rasterio_transform = importlib.import_module("rasterio.transform")
width = 64
height = 48
data = numpy.linspace(20, 220, num=width * height, dtype=numpy.uint8).reshape((height, width))
transform = rasterio_transform.from_bounds(4.9895, 51.1595, 4.9930, 51.1615, width, height)
with rasterio_io.MemoryFile() as memfile:
with memfile.open(
driver="GTiff",
width=width,
height=height,
count=1,
dtype="uint8",
crs="EPSG:4326",
transform=transform,
nodata=0,
) as dataset:
dataset.write(data, 1)
return memfile.read()
@staticmethod
def _create_raster_dataset(db: Session, *, project_id: UUID, area_id: UUID) -> Dataset:
dataset_id = uuid4()
raw = DemoWorkflowService._create_demo_raster_bytes()
storage_info = StorageService.persist_dataset_file(
project_id=str(project_id),
dataset_id=str(dataset_id),
dataset_type="raster",
original_filename=DemoWorkflowService.RASTER_FILENAME,
content=raw,
content_type="image/tiff",
)
metadata = extract_raster_metadata(storage_info["storage_path"])
bounds = metadata.get("bounds")
bounds_json = None
if isinstance(bounds, list) and len(bounds) == 4:
bounds_json = {"minx": bounds[0], "miny": bounds[1], "maxx": bounds[2], "maxy": bounds[3]}
dataset = Dataset(
id=dataset_id,
project_id=project_id,
area_id=area_id,
name=DemoWorkflowService.RASTER_FILENAME,
dataset_type="raster",
source="fixture",
dataset_role="source",
source_name="fixture",
reference_layer_name=None,
source_metadata={
"fixture": True,
"fixture_name": DemoWorkflowService.RASTER_FILENAME,
"usage": "offline demo raster workflow only",
},
provenance_metadata={
"created_by": "demo_workflow",
"source_path": "generated:demo_context_raster",
},
imported_at=datetime.now(timezone.utc),
storage_path=storage_info["storage_path"],
original_filename=storage_info["original_filename"],
stored_filename=storage_info["stored_filename"],
content_type=storage_info["content_type"],
size_bytes=storage_info["size_bytes"],
checksum_sha256=storage_info["checksum_sha256"],
crs=metadata.get("crs"),
bounds_json=bounds_json,
metadata_json=metadata,
status="ready",
)
db.add(dataset)
db.commit()
db.refresh(dataset)
return dataset
@staticmethod
def _persist_qa(
db: Session,
@@ -306,8 +400,10 @@ class DemoWorkflowService:
.filter(Dataset.project_id == existing.id)
.filter(Dataset.dataset_role == "source")
.filter(Dataset.source_name == "fixture")
.filter(Dataset.dataset_type == "vector")
.first()
)
raster = DemoWorkflowService._find_demo_raster_dataset(db, existing.id)
quality_check = (
db.query(QualityCheck)
.filter(QualityCheck.project_id == existing.id)
@@ -316,6 +412,8 @@ class DemoWorkflowService:
.first()
)
if area and reference and candidate and quality_check:
if not raster:
raster = DemoWorkflowService._create_raster_dataset(db=db, project_id=existing.id, area_id=area.id)
if not DemoWorkflowService._quality_check_matches_expected(db, quality_check):
area = DemoWorkflowService._sync_demo_area(db, area)
quality_check = DemoWorkflowService._persist_qa(
@@ -330,6 +428,7 @@ class DemoWorkflowService:
area_id=area.id,
reference_dataset_id=reference.id,
candidate_dataset_id=candidate.id,
raster_dataset_id=raster.id,
quality_check_id=quality_check.id,
metric_count=db.query(Metric).filter(Metric.quality_check_id == quality_check.id).count(),
status="ready",
@@ -352,6 +451,7 @@ class DemoWorkflowService:
area = None
reference = None
candidate = None
raster = None
quality_check = None
created = True
@@ -381,6 +481,8 @@ class DemoWorkflowService:
source_name="fixture",
reference_layer_name=None,
)
if not raster:
raster = DemoWorkflowService._create_raster_dataset(db=db, project_id=project.id, area_id=area.id)
if not quality_check:
quality_check = DemoWorkflowService._persist_qa(
db=db,
@@ -395,6 +497,7 @@ class DemoWorkflowService:
area_id=area.id,
reference_dataset_id=reference.id,
candidate_dataset_id=candidate.id,
raster_dataset_id=raster.id,
quality_check_id=quality_check.id,
metric_count=6,
status="ready",