diff --git a/backend/tests/test_sprint205_agricultural_parcel_history.py b/backend/tests/test_sprint205_agricultural_parcel_history.py index ae172f0b..3c2343ea 100644 --- a/backend/tests/test_sprint205_agricultural_parcel_history.py +++ b/backend/tests/test_sprint205_agricultural_parcel_history.py @@ -276,6 +276,7 @@ def test_operator_uses_canonical_upload_and_is_packaged_for_runtime() -> None: assert "Landbouwgebruikspercelen" in source_catalog assert "agriculture: 'Landbouwgebruikspercelen'" in dataset_display assert "agentschap_landbouw_zeevisserij_agricultural_parcels: 'Agentschap Landbouw en Zeevisserij'" in dataset_display + assert "const source = first ? getDatasetDisplayName(first) : 'Tijdreeks'" in map_workspace def test_upload_contract_is_annual_definitive_and_scope_specific(tmp_path: Path) -> None: diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 510d8865..fa4c7080 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -8557,3 +8557,59 @@ Known limitations: Next: - Implement P2 annual agricultural-use parcels, beginning with an edition and code-list comparability audit before any import or trend is exposed. + +## Sprint 205 - Governed annual agricultural-use parcels (2026-07-15) + +Implemented: +- Added an explicit ALZ operator for the definitive 2008-2025 annual + agricultural-use parcel GeoPackages. The operator uses a fixed official + allowlist, streamed archive limits, ZIP path validation and native + EPSG:31370 schema checks before exact regional clipping. +- Retained each official archive, annual crop-code list and checksummed + manifest. Derived GeoJSON enters the application only through the canonical + Dataset upload service and ordinary Dataset/vector_feature persistence. +- Added server-owned exact PostGIS metrics for total declared-use hectares, + official main-crop groups and feature count. Comma-separated official group + labels found during the live source audit remain compatible with the first + persisted import. +- Added the Landbouw map theme, readable source labels, the 18-edition source + inventory and historical comparison UI. Parcel-level object lineage remains + disabled because declarations and boundaries are not stable identities. + +Validation evidence: +- Full local readiness passed backend compilation, 647 backend tests, one + Alembic head, frontend TypeScript typecheck/build and shell syntax gates. +- Tower deployment passed container health, PostGIS 3.6 migration/schema + smoke, frontend proxy and icon verification. +- All 18 definitive editions were imported into one governed regional series + for the exact 28-municipality Kempen transport-region Area. Database audit + found 18 ready Datasets, 18 DatasetVersions, matching feature/source-id + counts, EPSG:4326 throughout and zero invalid geometries. +- The 2025 source contained 594,730 Flanders features. Exact regional clipping + retained 54,071 features, including 411 clipped at the boundary, measuring + 60,087.0667 ha. The 2008 edition retained 43,343 regional features and + measured 63,272.1152 ha. +- Live Mol analysis measured 3,223.4983 ha declared use across 2,374 features + for 2025, including 934.2348 ha grassland, 874.7350 ha maize and 329.1544 ha + potatoes. All configured crop/use groups returned explicit hectare values. +- The exact Mol 2008-2025 comparison returned all 18 observations: 3,343.2441 + ha to 3,223.4983 ha, a change of -119.7458 ha (-3.5817%). The UI states that + declarations are not cadastral ownership and individual objects cannot be + followed reliably between years. +- Browser validation confirmed the current-state metrics, complete historical + chart, 18-edition inventory and readable authority labels through the live + LAN runtime. + +Known limitations: +- The current campaign remains provisional and is deliberately excluded until + a definitive annual edition is published. +- Declared agricultural use includes non-crop elements such as water, hedges, + buildings and infrastructure; it is not ownership, cadastral area or a + direct measure of agricultural production. +- Cross-year comparison is valid for governed category and area totals only, + not for parcel identity, causal attribution or forecasts. + +Next: +- Implement P3 Buildings and Addresses Register through a governed snapshot + operator, preserving register lifecycle semantics separately from GRB + footprint geometry and excluding personal data. diff --git a/docs/TODO.md b/docs/TODO.md index af621b61..4372dda6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -25,7 +25,7 @@ - [x] Integrate governed Waterinfo/VMM annual station series without presenting point measurements as area-wide water volume. - [x] Add bounded historical orthophoto acquisition for official 1971-2025 products with a map overlay and no current-GRB QA on old imagery. - [x] Add BWK/Natura 2000 through an explicit provider/operator contract. -- [ ] Add annual agricultural-use parcels through an explicit provider/operator contract. +- [x] Add annual agricultural-use parcels through an explicit provider/operator contract. - [ ] Extend the official 1778/1873/1969 historical buildings, water and roads series from Mol to the approved regional scope with partitioned source audits. - [x] Connect a drawn rectangle to bounded official orthophoto acquisition, local configured-YOLO detection and persisted GRB QA. @@ -45,11 +45,11 @@ pass live Mol validation before regional expansion. ### P2 - Annual agricultural-use parcels -- [ ] Inventory official annual editions and document schema/code-list changes before selecting a comparable year range. -- [ ] Retain annual source files and crop code lists; normalize only fields whose meaning is stable across editions. -- [ ] Persist separate annual Datasets with hectares by crop/use class and an explicit unstable-parcel-identity limitation. -- [ ] Compare area/category totals over time; do not claim parcel lineage where identifiers or boundaries changed. -- [ ] Validate Mol first, then partition all 28 Kempen municipalities with completeness/checksum manifests. +- [x] Inventory official annual editions and document schema/code-list changes before selecting a comparable year range. +- [x] Retain annual source files and crop code lists; normalize only fields whose meaning is stable across editions. +- [x] Persist separate annual Datasets with hectares by crop/use class and an explicit unstable-parcel-identity limitation. +- [x] Compare area/category totals over time; do not claim parcel lineage where identifiers or boundaries changed. +- [x] Validate Mol first, then partition all 28 Kempen municipalities with completeness/checksum manifests. ### P3 - Buildings and Addresses Register diff --git a/frontend/src/components/datasets/SourceCatalogPanel.tsx b/frontend/src/components/datasets/SourceCatalogPanel.tsx index 9b57cb7e..d106a7fe 100644 --- a/frontend/src/components/datasets/SourceCatalogPanel.tsx +++ b/frontend/src/components/datasets/SourceCatalogPanel.tsx @@ -201,7 +201,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
Landbouwgebruikspercelen {agricultureDatasets.length} definitieve jaargangen{agricultureYears.length ? ` · ${Math.min(...agricultureYears)}-${Math.max(...agricultureYears)}` : ''} -

Oppervlakte en officiele hoofdteeltgroepen zijn historisch vergelijkbaar; perceelidentiteiten blijven bewust niet gekoppeld tussen jaren.

+

Oppervlakte en officiële hoofdteeltgroepen zijn historisch vergelijkbaar; perceelidentiteiten blijven bewust niet gekoppeld tussen jaren.

) : null} diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index cad92b96..e000b2e8 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -62,7 +62,7 @@ const DATA_THEMES: DataTheme[] = [ id: 'agriculture', label: 'Landbouw', shortLabel: 'Landbouwgebruik', - description: 'Jaarlijkse officiele landbouwgebruikspercelen en hoofdteeltgroepen.', + description: 'Jaarlijkse officiële landbouwgebruikspercelen en hoofdteeltgroepen.', tokens: ['agriculture', 'agricultural', 'landbouw', 'landbouwgebruik', 'agpa'], }, { @@ -144,7 +144,7 @@ function temporalSeriesLabel(items: DatasetCreateResponse[]): string { return configuredLabel } const first = items[0] - const source = first?.source_name ?? first?.source ?? 'Tijdreeks' + const source = first ? getDatasetDisplayName(first) : 'Tijdreeks' const firstYear = first?.observed_at ? new Date(first.observed_at).getUTCFullYear() : null const last = items[items.length - 1] const lastYear = last?.observed_at ? new Date(last.observed_at).getUTCFullYear() : null