feat(scope): make Belgium and North Sea operational default
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-22 02:11:48 +02:00
parent 46884cbbc9
commit 0aff8e3b8c
61 changed files with 2499 additions and 194 deletions
@@ -277,6 +277,48 @@ def test_regional_official_vector_sources_are_configurable_in_every_runtime() ->
assert f'Target="{key}"' in template
def test_segmentation_and_mdk_acquisition_are_configurable_in_every_runtime() -> None:
compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8")
unraid_compose = (ROOT / "docker-compose.unraid.yml").read_text(encoding="utf-8")
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
env_example = (ROOT / ".env.example").read_text(encoding="utf-8")
unraid_env = (ROOT / "deploy" / "unraid" / "geointel.env.example").read_text(encoding="utf-8")
template = (ROOT / "deploy" / "unraid" / "geointel-unraid-template.xml").read_text(encoding="utf-8")
for key in (
"YOLO_SEG_ENABLED",
"YOLO_SEG_MODEL_PATH",
"SAM_ENABLED",
"SAM_MODEL_PATH",
"SEGMENTATION_MAX_MASKS_PER_TILE",
"SEGMENTATION_DUPLICATE_IOU_THRESHOLD",
"MDK_BATHYMETRY_ACQUISITION_ENABLED",
"MDK_BATHYMETRY_COVERAGE_ID",
"MDK_BATHYMETRY_MAX_BBOX_DEG2",
):
assert key in compose, key
assert key in unraid_compose, key
assert f'{key}="${{{key}:-' in run_script, key
assert f'-e {key}="${key}"' in run_script, key
assert f"{key}=" in env_example, key
assert f"{key}=" in unraid_env, key
assert f'Target="{key}"' in template, key
def test_compose_reconciles_interrupted_runs_after_restart_like_unraid_runtime() -> None:
compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8")
start_script = (ROOT / "deploy" / "unraid" / "all-in-one-start.sh").read_text(encoding="utf-8")
assert (
"GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP: "
"${GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP:-true}"
) in compose
assert (
'GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP='
'"${GEOINTEL_RECONCILE_INTERRUPTED_RUNS_ON_STARTUP:-true}"'
) in start_script
def test_docker_build_contexts_exclude_vendor_build_and_cache_outputs() -> None:
required_patterns = {
"node_modules",
@@ -0,0 +1,153 @@
from __future__ import annotations
import io
from pathlib import Path
from uuid import uuid4
import pytest
from app.core.config import Settings
from app.schemas.bathymetry import MdkBathymetryAcquireRequest
from app.schemas.operations import VectorSelectionBBox
from app.services.mdk_bathymetry_acquisition_service import MdkBathymetryAcquisitionService
CAPABILITIES_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
<WCS_Capabilities version="1.0.0" xmlns="http://www.opengis.net/wcs">
<ContentMetadata>
<CoverageOfferingBrief>
<name>depth_model_20m_lat</name>
<label>Belgian Continental Shelf depth model</label>
</CoverageOfferingBrief>
</ContentMetadata>
</WCS_Capabilities>
"""
class FakeResponse:
def __init__(self, content: bytes, content_type: str = "application/xml") -> None:
self._stream = io.BytesIO(content)
self.headers = {"Content-Type": content_type}
def read(self, limit: int = -1) -> bytes:
return self._stream.read(limit)
def __enter__(self):
return self
def __exit__(self, *args):
return False
def _payload(**overrides) -> MdkBathymetryAcquireRequest:
values = {
"bbox": VectorSelectionBBox(min_x=2.5, min_y=51.3, max_x=2.6, max_y=51.4),
"force_refresh": True,
}
values.update(overrides)
return MdkBathymetryAcquireRequest(**values)
def _settings(**overrides) -> Settings:
values = {
"mdk_bathymetry_acquisition_enabled": True,
"mdk_bathymetry_coverage_id": "depth_model_20m_lat",
}
values.update(overrides)
return Settings(**values)
def test_acquisition_fails_closed_when_disabled() -> None:
settings = _settings(mdk_bathymetry_acquisition_enabled=False)
with pytest.raises(Exception) as exc_info:
MdkBathymetryAcquisitionService.acquire(None, uuid4(), _payload(), settings=settings)
assert getattr(exc_info.value, "code", None) == "MDK_BATHYMETRY_ACQUISITION_DISABLED"
def test_acquisition_fails_closed_without_coverage_id() -> None:
settings = _settings(mdk_bathymetry_coverage_id=None)
with pytest.raises(Exception) as exc_info:
MdkBathymetryAcquisitionService.acquire(None, uuid4(), _payload(), settings=settings)
assert getattr(exc_info.value, "code", None) == "MDK_BATHYMETRY_COVERAGE_NOT_CONFIGURED"
def test_acquisition_rejects_oversized_bbox() -> None:
settings = _settings(mdk_bathymetry_max_bbox_deg2=0.001)
with pytest.raises(Exception) as exc_info:
MdkBathymetryAcquisitionService.acquire(None, uuid4(), _payload(), settings=settings)
assert getattr(exc_info.value, "code", None) == "MDK_BATHYMETRY_BBOX_TOO_LARGE"
def test_acquisition_requires_reachable_probe() -> None:
settings = _settings()
def failing_opener(request, timeout=None):
raise OSError("connection refused")
with pytest.raises(Exception) as exc_info:
MdkBathymetryAcquisitionService.acquire(None, uuid4(), _payload(), settings=settings, opener=failing_opener)
assert getattr(exc_info.value, "code", None) == "MDK_BATHYMETRY_ENDPOINT_NOT_READY"
def test_acquisition_requires_advertised_coverage_id() -> None:
settings = _settings(mdk_bathymetry_coverage_id="not_advertised_coverage")
def opener(request, timeout=None):
return FakeResponse(CAPABILITIES_XML)
with pytest.raises(Exception) as exc_info:
MdkBathymetryAcquisitionService.acquire(None, uuid4(), _payload(), settings=settings, opener=opener)
assert getattr(exc_info.value, "code", None) == "MDK_BATHYMETRY_COVERAGE_NOT_ADVERTISED"
def test_acquisition_rejects_non_geotiff_coverage_response() -> None:
settings = _settings()
responses = []
def opener(request, timeout=None):
url = request.full_url if hasattr(request, "full_url") else str(request)
responses.append(url)
if "GetCapabilities" in url:
return FakeResponse(CAPABILITIES_XML)
return FakeResponse(b"<ServiceExceptionReport>boom</ServiceExceptionReport>", "application/xml")
with pytest.raises(Exception) as exc_info:
MdkBathymetryAcquisitionService.acquire(None, uuid4(), _payload(), settings=settings, opener=opener)
assert getattr(exc_info.value, "code", None) == "MDK_BATHYMETRY_INVALID_RESPONSE"
assert any("GetCoverage" in url for url in responses)
coverage_urls = [url for url in responses if "GetCoverage" in url]
assert "coverage=depth_model_20m_lat" in coverage_urls[0]
assert "format=GeoTIFF" in coverage_urls[0]
def test_get_coverage_url_is_bounded_and_pinned() -> None:
settings = _settings()
bbox = [2.5, 51.3, 2.6, 51.4]
url = MdkBathymetryAcquisitionService._get_coverage_url(settings, "depth_model_20m_lat", bbox)
assert url.startswith("https://")
assert "request=GetCoverage" in url
assert "version=1.0.0" in url
assert "crs=EPSG%3A4326" in url or "crs=EPSG:4326" in url
width, height = MdkBathymetryAcquisitionService._pixel_dimensions(bbox)
assert 1 <= width <= MdkBathymetryAcquisitionService.MAX_PIXELS_PER_SIDE
assert 1 <= height <= MdkBathymetryAcquisitionService.MAX_PIXELS_PER_SIDE
def test_source_module_never_disables_tls_verification() -> None:
source = (
Path(__file__).resolve().parents[1] / "app" / "services" / "mdk_bathymetry_acquisition_service.py"
).read_text(encoding="utf-8")
assert "_create_unverified_context" not in source
assert "CERT_NONE" not in source
assert "check_hostname = False" not in source
+20 -1
View File
@@ -119,7 +119,7 @@ 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 == "available"
assert asset.status == "approved"
assert asset.will_download_models is False
@@ -134,6 +134,25 @@ def test_model_asset_catalog_resolves_known_asset(tmp_path: Path) -> None:
assert asset.model_path == str(model_file)
def test_model_asset_catalog_only_exposes_explicit_active_asset_in_runtime(tmp_path: Path) -> None:
active_file = tmp_path / "approved-building-detector.pt"
active_file.write_bytes(b"approved")
(tmp_path / "training-smoke.pt").write_bytes(b"experiment")
(tmp_path / "partial-checkpoint.pt").write_bytes(b"partial")
settings = Settings(
yolo_models_dir=str(tmp_path),
yolo_model_path=str(active_file),
yolo_enabled=True,
)
response = ModelAssetCatalogService.list_assets(settings=settings)
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"
def test_model_asset_catalog_rejects_unknown_asset(tmp_path: Path) -> None:
settings = Settings(yolo_models_dir=str(tmp_path), yolo_enabled=True)
@@ -119,6 +119,72 @@ def test_regional_product_registry_is_explicit_and_source_specific() -> None:
assert products["urbis_buildings"]["coverage_zones"] == ["brussels"]
assert products["urbis_buildings"]["license_note"] == "Buildings are published under CC0."
assert "FPS Finance" in products["urbis_cadastral_parcels"]["license_note"]
# urbis_street_axes is live-validated against the UrbIS WFS capabilities:
# urbisvector:StreetAxes exposes INSPIRE_ID and LineString geometry. The
# same capabilities document advertises no hydrography feature type, so
# Brussels surface water intentionally stays not_configured.
assert products["urbis_street_axes"]["coverage_zones"] == ["brussels"]
assert products["urbis_street_axes"]["collection"] == "urbisvector:StreetAxes"
assert products["urbis_street_axes"]["geometry_types"] == [
"LineString",
"MultiLineString",
]
assert products["urbis_street_axes"]["theme"] == "roads"
assert products["urbis_land_cover_blocks"]["collection"] == "urbisvector:Blocks"
assert products["urbis_land_cover_blocks"]["theme"] == "space_occupation"
assert products["urbis_forest_parks"]["theme"] == "forest"
assert products["urbis_water_surfaces"]["theme"] == "water"
def test_urbis_land_cover_products_filter_only_documented_block_classes() -> None:
scope_wgs84 = Polygon(
[(4.35, 50.84), (4.36, 50.84), (4.36, 50.85), (4.35, 50.85), (4.35, 50.84)]
)
scope_metric = Polygon([_TO_LAMBERT72.transform(x, y) for x, y in scope_wgs84.exterior.coords])
min_x, min_y, max_x, max_y = scope_metric.bounds
def block(block_type: str):
return {
"type": "Feature",
"id": f"Blocks.{block_type}",
"geometry": {
"type": "Polygon",
"coordinates": [[
[min_x + 10, min_y + 10],
[min_x + 100, min_y + 10],
[min_x + 100, min_y + 100],
[min_x + 10, min_y + 100],
[min_x + 10, min_y + 10],
]],
},
"properties": {
"INSPIRE_ID": f"https://databrussels.be/id/block/{block_type}",
"TYPE": block_type,
},
}
forest_product = OfficialVectorAcquisitionService._product("urbis_forest_parks")
water_product = OfficialVectorAcquisitionService._product("urbis_water_surfaces")
land_cover_product = OfficialVectorAcquisitionService._product("urbis_land_cover_blocks")
assert OfficialVectorAcquisitionService._normalize_regional_feature(
forest_product, block("FO"), scope_metric, "brussels"
) is not None
assert OfficialVectorAcquisitionService._normalize_regional_feature(
forest_product, block("CB"), scope_metric, "brussels"
) is None
assert OfficialVectorAcquisitionService._normalize_regional_feature(
water_product, block("WB"), scope_metric, "brussels"
) is not None
assert OfficialVectorAcquisitionService._normalize_regional_feature(
water_product, block("GB"), scope_metric, "brussels"
) is None
normalized = OfficialVectorAcquisitionService._normalize_regional_feature(
land_cover_product, block("CB"), scope_metric, "brussels"
)
assert normalized is not None
assert normalized["properties"]["TYPE"] == "CB"
assert normalized["properties"]["clipped_area_ha"] > 0
def test_spw_arcgis_paging_is_bounded_stable_and_clipped() -> None:
+2 -2
View File
@@ -404,8 +404,8 @@ def test_frontend_prefers_materialized_national_workspace_and_resolves_drawn_bbo
assert "Belgium and North Sea Workbench" in focus
assert "nationalProject" in workspace_hook
assert "data.areas.length > 0" in workspace_hook
assert "dataset.status === 'ready'" in workspace_hook
assert "return nationalProject.id" in workspace_hook
assert "NATIONAL_WORKSPACE_REGION" in workspace_hook
assert "externalApi.resolveCoverage" in coverage_hook
assert "coverage.outside_supported_scope" in map_workspace
assert "coverageStatusLabel" in map_workspace
+142
View File
@@ -0,0 +1,142 @@
from __future__ import annotations
from uuid import uuid4
import pytest
from app.core.config import Settings
from app.core.errors import AppError
from app.models import AnalysisRun, Dataset, Job, Project
from app.services.detection_service import DetectionService
from app.services.job_service import JobService
from app.services.segmentation_service import SegmentationService
class FakeSession:
"""Minimal session double without rollback support, mirroring existing test doubles."""
def __init__(self, objects=None) -> None:
self.objects = objects or {}
self.added = []
self.commits = 0
def get(self, model, item_id):
return self.objects.get((model, item_id))
def add(self, item) -> None:
self.added.append(item)
if getattr(item, "id", None) is not None:
self.objects[(item.__class__, item.id)] = item
def commit(self) -> None:
self.commits += 1
def refresh(self, item) -> None:
pass
def _project_and_dataset():
project_id = uuid4()
dataset_id = uuid4()
project = Project(id=project_id, name="Mol")
dataset = Dataset(
id=dataset_id,
project_id=project_id,
name="ortho.tif",
dataset_type="raster",
source="user_upload",
storage_path="storage/uploads/ortho.tif",
)
db = FakeSession(objects={(Project, project_id): project, (Dataset, dataset_id): dataset})
return db, project_id, dataset_id
def _statuses(db: FakeSession) -> tuple[list[str], list[str]]:
runs = [item.status for item in db.added if isinstance(item, AnalysisRun)]
jobs = [item.status for item in db.added if isinstance(item, Job)]
return runs, jobs
def test_invalid_fixture_detections_mark_run_and_job_failed() -> None:
db, project_id, dataset_id = _project_and_dataset()
with pytest.raises(AppError) as exc_info:
DetectionService.run_detection(
db=db,
project_id=project_id,
dataset_id=dataset_id,
model_id="manual-fixture-detector",
confidence_threshold=0.5,
parameters_json={"fixture_mode": True, "fixture_detections": "not-a-list"},
settings=Settings(_env_file=None),
)
assert exc_info.value.code == "INVALID_FIXTURE_DETECTIONS"
run_statuses, job_statuses = _statuses(db)
assert run_statuses and all(status == "failed" for status in run_statuses)
assert job_statuses and all(status == "failed" for status in job_statuses)
def test_invalid_fixture_segmentations_mark_run_and_job_failed() -> None:
db, project_id, dataset_id = _project_and_dataset()
with pytest.raises(AppError) as exc_info:
SegmentationService.run_segmentation(
db=db,
project_id=project_id,
dataset_id=dataset_id,
model_id="fixture-segmenter",
confidence_threshold=0.5,
parameters_json={"fixture_mode": True, "fixture_segmentations": "not-a-list"},
settings=Settings(_env_file=None),
)
assert exc_info.value.code == "INVALID_FIXTURE_SEGMENTATIONS"
run_statuses, job_statuses = _statuses(db)
assert run_statuses and all(status == "failed" for status in run_statuses)
assert job_statuses and all(status == "failed" for status in job_statuses)
def test_unexpected_error_in_sync_job_marks_job_failed() -> None:
project_id = uuid4()
db = FakeSession(objects={(Project, project_id): Project(id=project_id, name="Mol")})
def exploding_operation():
raise RuntimeError("unexpected internal failure")
with pytest.raises(RuntimeError):
JobService.run_sync_job(
db=db,
project_id=project_id,
job_type="test.unexpected",
parameters={},
operation=exploding_operation,
)
jobs = [item for item in db.added if isinstance(item, Job)]
assert jobs
final_job = jobs[-1]
assert final_job.status == "failed"
assert "Unexpected internal error" in (final_job.error_message or "")
def test_app_error_in_sync_job_still_marks_job_failed() -> None:
project_id = uuid4()
db = FakeSession(objects={(Project, project_id): Project(id=project_id, name="Mol")})
def failing_operation():
raise AppError(code="SOME_DOMAIN_ERROR", message="Bounded failure", status_code=422)
with pytest.raises(AppError):
JobService.run_sync_job(
db=db,
project_id=project_id,
job_type="test.bounded",
parameters={},
operation=failing_operation,
)
jobs = [item for item in db.added if isinstance(item, Job)]
assert jobs
assert jobs[-1].status == "failed"
assert jobs[-1].error_message == "Bounded failure"
@@ -0,0 +1,333 @@
from __future__ import annotations
import json
from pathlib import Path
from uuid import uuid4
import pytest
from geoalchemy2.shape import to_shape
from app.core.config import Settings
from app.models import Dataset, Project, Segmentation
from app.services.detection_georeferencing import pixel_points_to_epsg4326_polygon
from app.services.model_registry_service import ModelRegistryService
from app.services.segmentation_service import SegmentationService
ROOT = Path(__file__).resolve().parents[2]
class FakeSession:
def __init__(self, objects=None) -> None:
self.objects = objects or {}
self.added = []
self.commits = 0
self.refreshes = []
def get(self, model, item_id):
return self.objects.get((model, item_id))
def add(self, item) -> None:
self.added.append(item)
if getattr(item, "id", None) is not None:
self.objects[(item.__class__, item.id)] = item
def commit(self) -> None:
self.commits += 1
def refresh(self, item) -> None:
self.refreshes.append(item)
class AvailableSegAdapter:
def __init__(self, settings: Settings) -> None:
self.settings = settings
@staticmethod
def dependencies_available() -> bool:
return True
def load_model(self, model_path: Path):
return object()
def predict_tile(self, model, tile_path: Path, confidence_threshold: float) -> list[dict]:
return [
{
"class_name": "building",
"confidence": 0.91,
"points": [[10.0, 20.0], [30.0, 20.0], [30.0, 40.0], [10.0, 40.0]],
"bbox": [10.0, 20.0, 30.0, 40.0],
"properties": {"class_id": 0},
}
]
class ClassAgnosticSamAdapter(AvailableSegAdapter):
def predict_tile(self, model, tile_path: Path, confidence_threshold: float) -> list[dict]:
return [
{
"class_name": "segment",
"confidence": None,
"points": [[5.0, 5.0], [25.0, 5.0], [25.0, 25.0], [5.0, 25.0]],
"bbox": [5.0, 5.0, 25.0, 25.0],
"properties": {"class_id": -1},
}
]
class MissingDependencySegAdapter(AvailableSegAdapter):
@staticmethod
def dependencies_available() -> bool:
return False
def _project_and_dataset(dataset_type: str = "raster"):
project_id = uuid4()
dataset_id = uuid4()
project = Project(id=project_id, name="Mol")
dataset = Dataset(
id=dataset_id,
project_id=project_id,
name="ortho.tif",
dataset_type=dataset_type,
source="user_upload",
storage_path="storage/uploads/ortho.tif",
)
db = FakeSession(objects={(Project, project_id): project, (Dataset, dataset_id): dataset})
return db, project_id, dataset_id
def _settings(tmp_path: Path, **overrides) -> Settings:
values = {
"yolo_seg_enabled": True,
"yolo_seg_model_path": str(tmp_path / "seg.pt"),
"sam_enabled": True,
"sam_model_path": str(tmp_path / "sam.pt"),
"yolo_max_tiles": 4,
}
values.update(overrides)
return Settings(**values)
def _manifest(tmp_path: Path, tile_count: int = 1) -> 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(
{
"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],
"index": index,
}
)
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(
json.dumps(
{
"tile_set_id": "tiles-fixture",
"source_dataset_id": str(uuid4()),
"source_raster_id": str(uuid4()),
"tile_size": 100,
"overlap": 0,
"count": tile_count,
"tiles": tiles,
}
),
encoding="utf-8",
)
return manifest_path
def test_segmentation_models_report_not_configured_when_disabled(tmp_path: Path) -> None:
settings = _settings(tmp_path, yolo_seg_enabled=False, sam_enabled=False)
models = {
model.model_id: model
for model in ModelRegistryService.list_segmentation_model_capabilities(settings=settings)
}
assert models["yolo-seg-configured"].configured is False
assert models["yolo-seg-configured"].status == "not_configured"
assert models["sam-configured"].configured is False
assert models["sam-configured"].status == "not_configured"
def test_segmentation_models_report_dependency_unavailable(tmp_path: Path) -> None:
(tmp_path / "seg.pt").write_bytes(b"weights")
(tmp_path / "sam.pt").write_bytes(b"weights")
settings = _settings(tmp_path)
models = {
model.model_id: model
for model in ModelRegistryService.list_segmentation_model_capabilities(
settings=settings,
yolo_seg_adapter_class=MissingDependencySegAdapter,
sam_adapter_class=MissingDependencySegAdapter,
)
}
assert models["yolo-seg-configured"].status == "dependency_unavailable"
assert models["sam-configured"].status == "dependency_unavailable"
def test_segmentation_models_report_configured_with_local_weights(tmp_path: Path) -> None:
(tmp_path / "seg.pt").write_bytes(b"weights")
(tmp_path / "sam.pt").write_bytes(b"weights")
settings = _settings(tmp_path)
models = {
model.model_id: model
for model in ModelRegistryService.list_segmentation_model_capabilities(
settings=settings,
yolo_seg_adapter_class=AvailableSegAdapter,
sam_adapter_class=ClassAgnosticSamAdapter,
)
}
assert models["yolo-seg-configured"].configured is True
assert models["yolo-seg-configured"].status == "configured"
assert models["sam-configured"].configured is True
assert models["sam-configured"].status == "configured"
def test_segmentation_dependency_check_uses_real_imports_not_find_spec() -> None:
source = (ROOT / "backend" / "app" / "services" / "segmentation_adapter.py").read_text(encoding="utf-8")
assert 'find_spec("ultralytics")' not in source
assert "import ultralytics" in source
assert "import torch" in source
def test_configured_segmentation_requires_tile_manifest(tmp_path: Path) -> None:
(tmp_path / "seg.pt").write_bytes(b"weights")
db, project_id, dataset_id = _project_and_dataset()
settings = _settings(tmp_path)
with pytest.raises(Exception) as exc_info:
SegmentationService.run_segmentation(
db=db,
project_id=project_id,
dataset_id=dataset_id,
model_id="yolo-seg-configured",
confidence_threshold=0.5,
settings=settings,
yolo_seg_adapter_class=AvailableSegAdapter,
sam_adapter_class=ClassAgnosticSamAdapter,
)
assert getattr(exc_info.value, "code", None) == "SEGMENTATION_TILE_MANIFEST_REQUIRED"
def test_configured_yolo_seg_run_persists_georeferenced_masks(tmp_path: Path) -> None:
(tmp_path / "seg.pt").write_bytes(b"weights")
db, project_id, dataset_id = _project_and_dataset()
settings = _settings(tmp_path)
manifest_path = _manifest(tmp_path)
response = SegmentationService.run_segmentation(
db=db,
project_id=project_id,
dataset_id=dataset_id,
model_id="yolo-seg-configured",
confidence_threshold=0.5,
tile_manifest_path=str(manifest_path),
settings=settings,
yolo_seg_adapter_class=AvailableSegAdapter,
sam_adapter_class=ClassAgnosticSamAdapter,
)
assert response.status == "success"
assert response.segmentation_count == 1
persisted = [item for item in db.added if isinstance(item, Segmentation)]
assert len(persisted) == 1
segmentation = persisted[0]
assert segmentation.class_name == "building"
assert segmentation.confidence == pytest.approx(0.91)
geometry = to_shape(segmentation.geometry)
assert geometry.geom_type == "MultiPolygon"
min_x, min_y, max_x, max_y = geometry.bounds
assert 4.0 <= min_x <= 5.0
assert 51.0 <= min_y <= 52.0
assert max_x <= 5.0
assert max_y <= 52.0
assert segmentation.area_m2 is not None and segmentation.area_m2 > 0
assert segmentation.provenance_json["inference"] == "local"
assert segmentation.provenance_json["model_id"] == "yolo-seg-configured"
def test_configured_sam_run_is_class_agnostic(tmp_path: Path) -> None:
(tmp_path / "sam.pt").write_bytes(b"weights")
db, project_id, dataset_id = _project_and_dataset()
settings = _settings(tmp_path)
manifest_path = _manifest(tmp_path)
response = SegmentationService.run_segmentation(
db=db,
project_id=project_id,
dataset_id=dataset_id,
model_id="sam-configured",
confidence_threshold=0.5,
tile_manifest_path=str(manifest_path),
settings=settings,
yolo_seg_adapter_class=AvailableSegAdapter,
sam_adapter_class=ClassAgnosticSamAdapter,
)
assert response.status == "success"
assert response.segmentation_count == 1
persisted = [item for item in db.added if isinstance(item, Segmentation)]
assert persisted[0].class_name == "segment"
assert persisted[0].confidence is None
def test_unconfigured_segmentation_run_fails_closed(tmp_path: Path) -> None:
db, project_id, dataset_id = _project_and_dataset()
settings = _settings(tmp_path, yolo_seg_enabled=False)
manifest_path = _manifest(tmp_path)
response = SegmentationService.run_segmentation(
db=db,
project_id=project_id,
dataset_id=dataset_id,
model_id="yolo-seg-configured",
confidence_threshold=0.5,
tile_manifest_path=str(manifest_path),
settings=settings,
yolo_seg_adapter_class=AvailableSegAdapter,
sam_adapter_class=ClassAgnosticSamAdapter,
)
assert response.status == "failed"
assert response.error_code == "SEGMENTATION_MODEL_UNAVAILABLE"
assert not [item for item in db.added if isinstance(item, Segmentation)]
def test_pixel_points_to_epsg4326_polygon_uses_tile_transform() -> None:
tile = {
"transform": [4.0, 0.01, 0.0, 52.0, 0.0, -0.01],
"bounds": [4.0, 51.0, 5.0, 52.0],
"pixel_window": [0, 0, 100, 100],
}
polygon = pixel_points_to_epsg4326_polygon(
points=[[0.0, 0.0], [100.0, 0.0], [100.0, 100.0], [0.0, 100.0]],
tile=tile,
crs="EPSG:4326",
)
min_x, min_y, max_x, max_y = polygon.bounds
assert min_x == pytest.approx(4.0)
assert max_x == pytest.approx(5.0)
assert min_y == pytest.approx(51.0)
assert max_y == pytest.approx(52.0)
def test_pixel_points_to_epsg4326_polygon_rejects_degenerate_input() -> None:
tile = {"transform": [4.0, 0.01, 0.0, 52.0, 0.0, -0.01]}
with pytest.raises(Exception) as exc_info:
pixel_points_to_epsg4326_polygon(points=[[0.0, 0.0], [1.0, 1.0]], tile=tile)
assert getattr(exc_info.value, "code", None) == "SEGMENTATION_INVALID_MASK"
@@ -4,7 +4,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def test_frontend_declares_mol_as_primary_operating_focus() -> None:
def test_frontend_declares_national_scope_as_primary_operating_focus() -> None:
focus = (ROOT / "frontend" / "src" / "config" / "primaryFocus.ts").read_text(
encoding="utf-8"
)
@@ -23,19 +23,17 @@ def test_frontend_declares_mol_as_primary_operating_focus() -> None:
/ "WorkbenchNavigation.tsx"
).read_text(encoding="utf-8")
assert "PRIMARY_FOCUS_LABEL = 'Mol'" in focus
assert "PRIMARY_FOCUS_REGION = 'Mol, Kempen'" in focus
assert "[5.1167, 51.1919]" in focus
assert "isPrimaryFocusProjectData" in focus
assert "isPrimaryFocusProjectData(project, data.datasets)" in project_hook
assert "NATIONAL_WORKSPACE_PROJECT_NAME = 'Belgium and North Sea Workbench'" in focus
assert "NATIONAL_WORKSPACE_REGION = 'Belgie en Belgische Noordzee'" in focus
assert "NATIONAL_MAP_CENTER" in focus
assert "return nationalProject.id" in project_hook
assert "hasMappedAnalysisContext(data)" in project_hook
assert "dataset.dataset_type === 'raster'" in project_hook
assert "dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson'" in project_hook
assert "const primaryContext = inspectedCandidates.find" in project_hook
assert "PRIMARY_FOCUS_AREA_NAME" in project_hook
assert "PRIMARY_FOCUS_AREA_GEOJSON" in project_hook
assert "4.35,51.28" not in project_hook
assert "center: PRIMARY_FOCUS_CENTER" in map_source
assert "PRIMARY_FOCUS_AREA_NAME" not in project_hook
assert "PRIMARY_FOCUS_AREA_GEOJSON" not in project_hook
assert "center: NATIONAL_MAP_CENTER" in map_source
assert "zoom: NATIONAL_MAP_ZOOM" in map_source
assert "GeoIntel" in navigation
assert "Atlas Workbench" in navigation
@@ -142,7 +142,7 @@ def test_large_vector_persistence_flushes_once_without_per_feature_refresh() ->
assert db.refreshes == 0
def test_municipality_workspace_is_wired_into_runtime_and_frontend_priority() -> None:
def test_municipality_workspace_remains_a_regression_fixture_without_frontend_priority() -> None:
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8")
focus = (ROOT / "frontend" / "src" / "config" / "primaryFocus.ts").read_text(encoding="utf-8")
@@ -154,7 +154,8 @@ def test_municipality_workspace_is_wired_into_runtime_and_frontend_priority() ->
assert "py_compile scripts/provision_mol_municipality_workspace.py" in readiness
assert "COPY scripts/provision_mol_municipality_workspace.py" in dockerfile
assert "PRIMARY_FOCUS_MUNICIPALITY_PROJECT_NAME = 'Mol Municipality Workbench'" in focus
assert "items.find(isPrimaryFocusMunicipalityProject)" in project_hook
assert "items.find(isPrimaryFocusMunicipalityProject)" not in project_hook
assert "return nationalProject.id" in project_hook
assert "datasets.find(isPrimaryFocusMunicipalityBoundaryDataset)" in dataset_hook
assert "featureCollectionBounds(featureCollection)" in map_source
assert "useMemo(() => getFeatureCollectionBBox(mapFeatureCollection)" in map_workspace
@@ -8,13 +8,15 @@ def read(path: str) -> str:
return (ROOT / path).read_text(encoding="utf-8")
def test_regional_workspace_is_automatic_and_map_has_one_scope_selector() -> None:
def test_national_workspace_is_automatic_and_map_has_one_scope_selector() -> None:
project_hook = read("frontend/src/hooks/useProjectWorkspace.ts")
map_workspace = read("frontend/src/components/map/MapWorkspace.tsx")
national_check = project_hook.index("const nationalProject")
regional_check = project_hook.index("const regionalProject")
municipality_check = project_hook.index("const municipalityProject")
assert regional_check < municipality_check
assert national_check < regional_check
assert "return nationalProject.id" in project_hook
assert "const municipalityProject" not in project_hook
assert 'aria-label="Regio"' not in map_workspace
assert 'aria-label="Ingeladen regiobereik"' in map_workspace
assert "Snel naar een gemeente (optioneel)" in map_workspace
@@ -56,7 +58,8 @@ def test_configured_yolo_and_active_asset_are_selected_without_hiding_limitation
assert "asset.active" in hook
assert "getYoloPreflight" in hook
assert 'aria-label="Status gebouwdetectie"' in lab
assert "resultaten blijven controleplichtig" in lab
assert "Nog niet nationaal gevalideerd" in lab
assert "vereisen lokale referentiedata en QA" in lab
assert "Modelkalibratie voor beheerders" in lab
@@ -249,7 +249,7 @@ def test_end_user_dataset_sources_are_human_readable() -> None:
assert "department_omgeving_land_use: 'Departement Omgeving'" in display
assert "statbel: 'Statbel'" in display
assert "getDatasetSourceDisplayName(activeThemeDataset)" in workspace
assert "resultDataset ? getDatasetSourceDisplayName(resultDataset)" in workspace
assert "getDatasetSourceDisplayName(resultDataset)" in workspace
assert "Snel naar een gemeente (optioneel)" in workspace
assert "latestDatasetBySeries" in catalog
assert "Historische meetmomenten" in catalog
@@ -174,7 +174,10 @@ def test_bathymetry_source_registry_is_honest_and_nationally_extensible() -> Non
assert by_key["vha_inland_profiles"]["integration_status"] == "operational"
assert by_key["vha_inland_profiles"]["acquisition_supported"] is True
assert by_key["mdk_bcp_bathymetry"]["vertical_reference"] == "LAT"
assert by_key["mdk_bcp_bathymetry"]["acquisition_supported"] is False
# Bounded MDK acquisition now exists but stays fail-closed until the
# operator enables it explicitly with a live-validated coverage id.
assert by_key["mdk_bcp_bathymetry"]["acquisition_supported"] is True
assert by_key["mdk_bcp_bathymetry"]["configured"] is False
assert by_key["spw_walloon_waterway_bathymetry"]["vertical_reference"] == "mDNG"
assert by_key["spw_walloon_waterway_bathymetry"]["license_note"].startswith("CC BY 4.0")
@@ -411,7 +411,9 @@ def test_expansion_scripts_are_packaged_and_readiness_checked() -> None:
for item in BathymetryProfileAcquisitionService.list_sources()
}
assert sources["mdk_bcp_bathymetry"]["integration_status"] == "probe_only"
assert sources["mdk_bcp_bathymetry"]["acquisition_supported"] is False
# Bounded acquisition is implemented but remains disabled by default.
assert sources["mdk_bcp_bathymetry"]["acquisition_supported"] is True
assert sources["mdk_bcp_bathymetry"]["configured"] is False
assert "EL_wcs" in sources["mdk_bcp_bathymetry"]["service_url"]
@@ -134,6 +134,10 @@ def test_product_registries_expose_honest_forest_agriculture_nature_and_soil() -
"spw_picc_water_surfaces",
"urbis_buildings",
"urbis_cadastral_parcels",
"urbis_street_axes",
"urbis_land_cover_blocks",
"urbis_forest_parks",
"urbis_water_surfaces",
} == set(vector)
assert vector["bwk_natura2000_2025"]["authority_level"] == "authoritative"
assert vector["dov_soil_types"]["authority_level"] == "authoritative_historical_baseline"
@@ -401,7 +405,7 @@ def test_official_vector_routes_and_frontend_use_canonical_backend_path(monkeypa
assert products_response.status_code == 200
assert set(products_response.json()) == {"data"}
assert products_response.json()["data"]["total"] == 8
assert products_response.json()["data"]["total"] == 12
assert acquire_response.status_code == 200
assert set(acquire_response.json()) == {"data"}
assert acquire_response.json()["data"]["job_type"] == "vector.official.acquire"