diff --git a/CHANGELOG.md b/CHANGELOG.md index ef366a49..e1c4e9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added an explicit operator provisioner for the official Digitaal Vlaanderen Mol municipality boundary (NIS `13025`) and the complete GRB GBG building population clipped to that boundary. - Added auditable source artifacts and a manifest with page count, checksums, exact WGS84 bounds, municipality area, feature totals and truncation state; incomplete pagination now fails closed. +- Declared EPSG:4326 explicitly in both official GeoJSON artifacts so downstream QA does not downgrade known OGC provenance to an inferred CRS. - Provisioning remains explicit and imports Project, Area and Dataset records through existing canonical API routes and DatasetService/VectorFeatureService persistence rather than writing directly to PostGIS. - Made the complete `Mol Municipality Workbench` the preferred fresh-session context and the official municipality boundary its lightweight default layer, ahead of historical Postel validation projects. - Replaced large coordinate arrays and spread-based bounds calculations with streaming, memoized GeoJSON bounds so municipality-scale vector layers remain safe in MapLibre. diff --git a/backend/tests/test_sprint181_mol_municipality_workspace.py b/backend/tests/test_sprint181_mol_municipality_workspace.py index 2ab55f37..82e3d01f 100644 --- a/backend/tests/test_sprint181_mol_municipality_workspace.py +++ b/backend/tests/test_sprint181_mol_municipality_workspace.py @@ -52,6 +52,7 @@ def test_mol_provisioner_uses_official_identity_and_exact_boundary_clipping() -> assert module.MUNICIPALITY_NIS_CODE == "13025" assert module.PROJECT_NAME == "Mol Municipality Workbench" + assert module.GEOJSON_CRS == {"type": "name", "properties": {"name": "EPSG:4326"}} assert len(buildings) == 2 assert summary["bbox_feature_count"] == 3 assert summary["outside_boundary_count"] == 1 @@ -147,4 +148,3 @@ def test_municipality_workspace_is_wired_into_runtime_and_frontend_priority() -> assert "featureCollectionBounds(featureCollection)" in map_source assert "useMemo(() => getFeatureCollectionBBox(mapFeatureCollection)" in map_workspace assert "Math.min(...xs)" not in map_source - diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index d0e138f1..5046b8ce 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -7406,6 +7406,8 @@ Open: - Added deterministic persistent source artefacts and a manifest containing source URLs, checksums, page/feature counts, boundary bounds and area, and an explicit truncation flag. Pagination and identity checks fail closed. +- Declared EPSG:4326 in both generated GeoJSON FeatureCollections so the + importer records `crs_assumed=false` for this known official OGC source. - Kept the provider boundary honest: provisioning is an operator action and imports through canonical Project, Area and Dataset HTTP routes. It does not enable the dormant live GRB provider or write directly to `vector_features`. @@ -7424,12 +7426,43 @@ Open: - Exact clipping, pagination/truncation, persistence-scaling and frontend wiring regression coverage passed locally. +- Full backend suite and readiness gate passed with `496` tests and the + `81`-route API contract audit. Frontend typecheck/build, Ruff, shell syntax, + single Alembic head `202606120900` and the complete offline migration chain + also passed. - No API route, ORM model, migration, QA metric, detection result or model configuration changed. +## Tower and live GIS evidence + +- Pushed and deployed commit `490325e`; live PostGIS `3.6`, required + schema/indexes, Alembic head, frontend proxy, API proxy and icon checks + passed. +- Provisioned project `143dee9a-b279-4984-9d30-1687fac3efcc` and official + area `3166ef08-3b40-410a-a6c0-0b854186e557` with bounds + `[5.035357, 51.1534809, 5.24218961, 51.32265943]` and projected area + `114.546677 km2`. +- The complete source bbox returned `89,153` unique GRB candidates over `90` + pages. Exact municipality clipping retained `36,941` buildings, rejected + `52,212` outside features and clipped `24` boundary-crossing features. The + manifest is complete and explicitly reports `reference_truncated=false`. +- PostGIS contains `36,941` vector feature rows and `36,941` distinct source + IDs, with zero invalid geometries and zero non-EPSG:4326 geometries. Strict + coverage reports 18 floating-point boundary artefacts whose combined + outside area is only `5.75e-9 m2`; no material building area falls outside + Mol. +- A fresh browser session selected the municipality project, official area + and one-feature boundary automatically instead of a historical Postel + validation context. Selecting the complete building layer rendered all + `36,941` features and a live AOI PostGIS query returned `250` persisted + features with dataset/export actions enabled. +- Browser verification at `1280x720` and `2560x1080` found one nonblank + MapLibre canvas, no console warnings/errors and no horizontal overflow. The + widescreen map canvas measured `1678x734` CSS pixels. + ## Next pass -- Deploy and execute the explicit provisioner on Tower, verify full PostGIS - feature counts and exact Mol bounds, then audit the complete municipality in - MapLibre before acquiring raster imagery for a deliberately selected Mol - analysis zone. +- Add viewport/zoom-aware vector delivery before expanding beyond Mol or + layering additional municipality-wide reference classes. Then acquire and + tile georeferenced imagery only for an explicitly selected Mol analysis zone + and run configured-YOLO plus QA/QC against the persisted GRB reference. diff --git a/scripts/provision_mol_municipality_workspace.py b/scripts/provision_mol_municipality_workspace.py index ff56b75e..bb2d0be9 100644 --- a/scripts/provision_mol_municipality_workspace.py +++ b/scripts/provision_mol_municipality_workspace.py @@ -39,6 +39,7 @@ DEFAULT_OUTPUT_DIR = Path("/app/storage/operator-data/mol-municipality") DEFAULT_API_URL = "http://127.0.0.1:8000" DEFAULT_PAGE_LIMIT = 1000 DEFAULT_MAX_FEATURES = 100000 +GEOJSON_CRS = {"type": "name", "properties": {"name": "EPSG:4326"}} def parse_args() -> argparse.Namespace: @@ -336,6 +337,7 @@ def prepare_artifacts(args: argparse.Namespace) -> tuple[Path, Path, dict[str, A boundary_payload = { "type": "FeatureCollection", "name": "Official municipality boundary - Mol", + "crs": GEOJSON_CRS, "features": [boundary_feature], "source": "Digitaal Vlaanderen VRBG OGC API Features collection Refgem", "source_url": boundary_source_url, @@ -348,6 +350,7 @@ def prepare_artifacts(args: argparse.Namespace) -> tuple[Path, Path, dict[str, A buildings_payload = { "type": "FeatureCollection", "name": "GRB buildings - complete municipality Mol", + "crs": GEOJSON_CRS, "features": buildings, "source": "Digitaal Vlaanderen GRB OGC API Features collection GBG", "source_url": building_summary["source_urls"][0], @@ -567,10 +570,14 @@ def provision_workspace( ) return { - "project": project, - "area": area, - "boundary_dataset": boundary_dataset, - "building_dataset": building_dataset, + "project_id": project_id, + "project_name": project.get("name"), + "area_id": area_id, + "area_name": area.get("name"), + "boundary_dataset_id": str(boundary_dataset["id"]), + "boundary_feature_count": boundary_dataset.get("feature_count"), + "building_dataset_id": str(building_dataset["id"]), + "building_feature_count": building_dataset.get("feature_count"), }