Add governed buildings register snapshot
This commit is contained in:
@@ -7,6 +7,23 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Sprint 206 Governed Buildings and Addresses Register snapshot (2026-07-15)
|
||||||
|
|
||||||
|
- Added an explicit operator for the current official Digitaal Vlaanderen
|
||||||
|
building, building-unit and address OGC collections with complete pagination,
|
||||||
|
safety limits, retries and retained raw SHA256 evidence.
|
||||||
|
- Added exact EPSG:31370 Area clipping, EPSG:4326 building persistence and
|
||||||
|
classified reconciliation against checksummed persisted GRB partitions.
|
||||||
|
- Persisted only building lifecycle data and aggregate unit/address counts;
|
||||||
|
full addresses, street names and house/box numbers remain outside queryable
|
||||||
|
output, and ambiguous address/GRB relations are never forced.
|
||||||
|
- Added exact footprint, lifecycle, unit, address-status and confirmed-GRB
|
||||||
|
selection metrics, including correct filtered `feature_count` execution.
|
||||||
|
- Made the Map workspace prefer the richer Mol register snapshot only for the
|
||||||
|
matching Mol Area and retain regional GRB coverage everywhere else.
|
||||||
|
- Added source-inventory presentation, runtime packaging and focused operator,
|
||||||
|
privacy, reconciliation, metric and UI tests.
|
||||||
|
|
||||||
## Sprint 205 Governed agricultural-use parcel history (2026-07-15)
|
## Sprint 205 Governed agricultural-use parcel history (2026-07-15)
|
||||||
|
|
||||||
- Added an explicit ALZ operator for the definitive 2008-2025 annual
|
- Added an explicit ALZ operator for the definitive 2008-2025 annual
|
||||||
|
|||||||
@@ -1132,6 +1132,28 @@ transport region. Every annual source ZIP and crop code list remains under the
|
|||||||
storage volume. PostGIS computes exact hectares for drawn rectangles and
|
storage volume. PostGIS computes exact hectares for drawn rectangles and
|
||||||
persisted Areas; parcel identities are deliberately unavailable for lineage.
|
persisted Areas; parcel identities are deliberately unavailable for lineage.
|
||||||
|
|
||||||
|
## Buildings and Addresses Register snapshot
|
||||||
|
|
||||||
|
After the Mol Area and regional GRB buildings have been provisioned, prepare
|
||||||
|
the official register evidence with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec geointel python /app/scripts/provision_buildings_addresses_register.py --fetch-only
|
||||||
|
```
|
||||||
|
|
||||||
|
Review the generated manifest and then persist through DatasetService:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec geointel python /app/scripts/provision_buildings_addresses_register.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting `building_registry` Dataset uses ordinary EPSG:4326
|
||||||
|
`vector_features`; no register-specific table or direct operator database write
|
||||||
|
exists. Exact PostGIS selection exposes footprint hectares, lifecycle counts,
|
||||||
|
aggregate unit/address counts and GRB reconciliation counts. Raw address pages
|
||||||
|
are checksummed storage evidence only. Address labels and house/box numbers are
|
||||||
|
not copied into queryable properties.
|
||||||
|
|
||||||
## Helpful repository scripts
|
## Helpful repository scripts
|
||||||
|
|
||||||
- `bash scripts/backend_install.sh`
|
- `bash scripts/backend_install.sh`
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ FULL_AREA_CLIPPED_OPERATOR_TOOLS = {
|
|||||||
"provision_waterinfo_station_history.py",
|
"provision_waterinfo_station_history.py",
|
||||||
"provision_mol_bwk_natura2000.py",
|
"provision_mol_bwk_natura2000.py",
|
||||||
"provision_agricultural_parcel_history.py",
|
"provision_agricultural_parcel_history.py",
|
||||||
|
"provision_buildings_addresses_register.py",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -144,6 +145,7 @@ class VectorFeatureService:
|
|||||||
"agricultural": "agriculture",
|
"agricultural": "agriculture",
|
||||||
"landbouw": "agriculture",
|
"landbouw": "agriculture",
|
||||||
"landbouwgebruik": "agriculture",
|
"landbouwgebruik": "agriculture",
|
||||||
|
"building_registry": "buildings",
|
||||||
}
|
}
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
if not isinstance(candidate, str) or not candidate.strip():
|
if not isinstance(candidate, str) or not candidate.strip():
|
||||||
@@ -538,6 +540,10 @@ class VectorFeatureService:
|
|||||||
is_estimate = bool(config.get("is_estimate", False))
|
is_estimate = bool(config.get("is_estimate", False))
|
||||||
if not is_estimate and config.get("warning_only_when_estimate", True):
|
if not is_estimate and config.get("warning_only_when_estimate", True):
|
||||||
warning = None
|
warning = None
|
||||||
|
elif method == "feature_count" and (filter_property or dimension in {1, 2}):
|
||||||
|
metric_value = float(
|
||||||
|
db.query(func.count(VectorFeature.id)).filter(*metric_filter).scalar() or 0
|
||||||
|
)
|
||||||
elif method != "feature_count":
|
elif method != "feature_count":
|
||||||
raise AppError(
|
raise AppError(
|
||||||
code="INVALID_SELECTION_AGGREGATION",
|
code="INVALID_SELECTION_AGGREGATION",
|
||||||
|
|||||||
@@ -0,0 +1,356 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from shapely.geometry import Point, box, mapping
|
||||||
|
from shapely.ops import transform as transform_geometry
|
||||||
|
|
||||||
|
from app.models import Dataset
|
||||||
|
from app.schemas.operations import VectorSelectionSummary
|
||||||
|
from app.services.vector_feature_service import VectorFeatureService
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
BBOX = {"min_x": 5.0, "min_y": 51.1, "max_x": 5.3, "max_y": 51.4, "crs": "EPSG:4326"}
|
||||||
|
|
||||||
|
|
||||||
|
def load_operator():
|
||||||
|
script_path = ROOT / "scripts" / "provision_buildings_addresses_register.py"
|
||||||
|
spec = importlib.util.spec_from_file_location("buildings_addresses_register_operator", script_path)
|
||||||
|
assert spec is not None
|
||||||
|
assert spec.loader is not None
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules[spec.name] = module
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def building_feature(object_id: str, geometry, status: str = "Gerealiseerd") -> dict: # noqa: ANN001
|
||||||
|
return {
|
||||||
|
"type": "Feature",
|
||||||
|
"id": f"Gebouw.{object_id}",
|
||||||
|
"geometry": mapping(geometry),
|
||||||
|
"properties": {
|
||||||
|
"ObjectId": int(object_id),
|
||||||
|
"VersieId": "2026-07-15T08:00:00+02:00",
|
||||||
|
"GeometrieMethode": "IngemetenGRB",
|
||||||
|
"GebouwStatus": status,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def unit_feature(object_id: str, building_id: str, point: Point) -> dict:
|
||||||
|
return {
|
||||||
|
"type": "Feature",
|
||||||
|
"id": f"Gebouweenheid.{object_id}",
|
||||||
|
"geometry": mapping(point),
|
||||||
|
"properties": {
|
||||||
|
"ObjectId": int(object_id),
|
||||||
|
"GebouwObjectId": int(building_id),
|
||||||
|
"GebouweenheidStatus": "Gerealiseerd",
|
||||||
|
"Functie": "NietGekend",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def address_feature(object_id: str, point: Point) -> dict:
|
||||||
|
return {
|
||||||
|
"type": "Feature",
|
||||||
|
"id": f"Adres.{object_id}",
|
||||||
|
"geometry": mapping(point),
|
||||||
|
"properties": {
|
||||||
|
"ObjectId": int(object_id),
|
||||||
|
"AdresStatus": "InGebruik",
|
||||||
|
"PositieSpecificatie": "Gebouweenheid",
|
||||||
|
"VolledigAdres": "Teststraat 1 bus 2, 2400 Mol",
|
||||||
|
"Straatnaam": "Teststraat",
|
||||||
|
"Huisnummer": "1",
|
||||||
|
"Busnummer": "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ScalarQuery:
|
||||||
|
def __init__(self, value: float):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def filter(self, *args): # noqa: ANN002, ARG002
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scalar(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceScalarSession:
|
||||||
|
def __init__(self, values: list[float]):
|
||||||
|
self.values = iter(values)
|
||||||
|
|
||||||
|
def query(self, *args): # noqa: ANN002, ARG002
|
||||||
|
return ScalarQuery(next(self.values))
|
||||||
|
|
||||||
|
|
||||||
|
class OfficialResponse:
|
||||||
|
status_code = 200
|
||||||
|
|
||||||
|
def __init__(self, payload: dict, url: str):
|
||||||
|
self.payload = payload
|
||||||
|
self.url = url
|
||||||
|
self.content = json.dumps(payload).encode("utf-8")
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self.payload
|
||||||
|
|
||||||
|
def raise_for_status(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class TwoPageOfficialSession:
|
||||||
|
def __init__(self):
|
||||||
|
self.calls = 0
|
||||||
|
|
||||||
|
def get(self, url, *, params, timeout): # noqa: ANN001, ARG002
|
||||||
|
self.calls += 1
|
||||||
|
if self.calls == 1:
|
||||||
|
payload = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [building_feature("1", box(5.10, 51.20, 5.101, 51.201))],
|
||||||
|
"links": [{"rel": "next", "href": f"{url}?startIndex=1"}],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
payload = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [building_feature("2", box(5.102, 51.20, 5.103, 51.201))],
|
||||||
|
"links": [],
|
||||||
|
}
|
||||||
|
return OfficialResponse(payload, f"{url}?page={self.calls}")
|
||||||
|
|
||||||
|
|
||||||
|
def normalized_fixture(module): # noqa: ANN001
|
||||||
|
boundary_wgs84 = box(5.09, 51.19, 5.12, 51.22)
|
||||||
|
boundary_lambert72 = transform_geometry(module.TO_LAMBERT72.transform, boundary_wgs84)
|
||||||
|
polygon = box(5.10, 51.20, 5.105, 51.205)
|
||||||
|
buildings, summary = module.normalize_buildings(
|
||||||
|
[building_feature("100", polygon)],
|
||||||
|
boundary_lambert72,
|
||||||
|
)
|
||||||
|
return boundary_wgs84, boundary_lambert72, polygon, buildings, summary
|
||||||
|
|
||||||
|
|
||||||
|
def test_official_collection_pagination_retains_checksummed_pages(tmp_path: Path) -> None:
|
||||||
|
module = load_operator()
|
||||||
|
session = TwoPageOfficialSession()
|
||||||
|
raw_dir = tmp_path / "raw"
|
||||||
|
|
||||||
|
features, summary = module.fetch_collection(
|
||||||
|
session,
|
||||||
|
url=module.BUILDING_ITEMS_URL,
|
||||||
|
name="buildings",
|
||||||
|
bbox=(5.0, 51.0, 5.2, 51.2),
|
||||||
|
raw_dir=raw_dir,
|
||||||
|
page_limit=1,
|
||||||
|
max_features=10,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert [feature["properties"]["ObjectId"] for feature in features] == [1, 2]
|
||||||
|
assert summary["page_count"] == 2
|
||||||
|
assert all((tmp_path / page["path"]).is_file() for page in summary["pages"])
|
||||||
|
assert all(len(page["sha256"]) == 64 for page in summary["pages"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_buildings_are_clipped_in_lambert72_and_keep_lifecycle_status() -> None:
|
||||||
|
module = load_operator()
|
||||||
|
boundary = box(5.10, 51.20, 5.11, 51.21)
|
||||||
|
boundary_lambert72 = transform_geometry(module.TO_LAMBERT72.transform, boundary)
|
||||||
|
source = box(5.095, 51.195, 5.105, 51.205)
|
||||||
|
|
||||||
|
buildings, summary = module.normalize_buildings(
|
||||||
|
[building_feature("100", source, "InAanbouw")],
|
||||||
|
boundary_lambert72,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert summary == {"rejected_or_outside_count": 0, "clipped_count": 1}
|
||||||
|
record = buildings["100"]
|
||||||
|
assert record["status_key"] == "under_construction"
|
||||||
|
assert record["was_clipped"] is True
|
||||||
|
assert record["geometry_wgs84"].difference(boundary.buffer(1e-7)).area < 1e-12
|
||||||
|
assert record["area_ha"] > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_official_unit_relation_and_exact_address_position_are_aggregated_without_labels() -> None:
|
||||||
|
module = load_operator()
|
||||||
|
_, boundary_lambert72, polygon, buildings, _ = normalized_fixture(module)
|
||||||
|
point = polygon.centroid
|
||||||
|
units, unit_summary = module.normalize_units(
|
||||||
|
[unit_feature("200", "100", point)],
|
||||||
|
boundary_lambert72,
|
||||||
|
buildings,
|
||||||
|
)
|
||||||
|
address_counts, address_summary = module.link_addresses(
|
||||||
|
[address_feature("300", point)],
|
||||||
|
boundary_lambert72,
|
||||||
|
buildings,
|
||||||
|
units,
|
||||||
|
)
|
||||||
|
module.reconcile_with_grb(
|
||||||
|
buildings,
|
||||||
|
[{"source_feature_id": "GRB.1", "geometry_wgs84": polygon}],
|
||||||
|
)
|
||||||
|
output, totals = module.build_output_features(
|
||||||
|
buildings,
|
||||||
|
units,
|
||||||
|
address_counts,
|
||||||
|
observed_date=module.date(2026, 7, 15),
|
||||||
|
area_name="Gemeente Mol - officiële grens",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert unit_summary["orphan_building_count"] == 0
|
||||||
|
assert address_summary["match_method_counts"] == {"unit_position_exact": 1}
|
||||||
|
assert totals["linked_unit_count"] == 1
|
||||||
|
assert totals["linked_address_count"] == 1
|
||||||
|
properties = output[0]["properties"]
|
||||||
|
assert properties["unit_count"] == 1
|
||||||
|
assert properties["active_address_count"] == 1
|
||||||
|
assert properties["grb_match_status"] == "matched"
|
||||||
|
for prohibited in ("VolledigAdres", "Straatnaam", "Huisnummer", "Busnummer", "HuisnummerLabel"):
|
||||||
|
assert prohibited not in properties
|
||||||
|
|
||||||
|
|
||||||
|
def test_ambiguous_unit_position_is_reported_and_never_forced() -> None:
|
||||||
|
module = load_operator()
|
||||||
|
boundary = box(5.09, 51.19, 5.12, 51.22)
|
||||||
|
boundary_lambert72 = transform_geometry(module.TO_LAMBERT72.transform, boundary)
|
||||||
|
point = Point(5.105, 51.205)
|
||||||
|
buildings, _ = module.normalize_buildings(
|
||||||
|
[
|
||||||
|
building_feature("100", box(5.10, 51.20, 5.106, 51.21)),
|
||||||
|
building_feature("101", box(5.104, 51.20, 5.11, 51.21)),
|
||||||
|
],
|
||||||
|
boundary_lambert72,
|
||||||
|
)
|
||||||
|
units, _ = module.normalize_units(
|
||||||
|
[unit_feature("200", "100", point), unit_feature("201", "101", point)],
|
||||||
|
boundary_lambert72,
|
||||||
|
buildings,
|
||||||
|
)
|
||||||
|
|
||||||
|
counts, summary = module.link_addresses(
|
||||||
|
[address_feature("300", point)],
|
||||||
|
boundary_lambert72,
|
||||||
|
buildings,
|
||||||
|
units,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert summary["ambiguous_address_count"] == 1
|
||||||
|
assert summary["matched_address_count"] == 0
|
||||||
|
assert not counts
|
||||||
|
|
||||||
|
|
||||||
|
def test_grb_reconciliation_distinguishes_exact_and_unmatched_geometry() -> None:
|
||||||
|
module = load_operator()
|
||||||
|
_, _, polygon, buildings, _ = normalized_fixture(module)
|
||||||
|
buildings["101"] = {
|
||||||
|
**buildings["100"],
|
||||||
|
"object_id": "101",
|
||||||
|
"geometry_wgs84": box(5.11, 51.21, 5.115, 51.215),
|
||||||
|
"geometry_lambert72": transform_geometry(
|
||||||
|
module.TO_LAMBERT72.transform,
|
||||||
|
box(5.11, 51.21, 5.115, 51.215),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
summary = module.reconcile_with_grb(
|
||||||
|
buildings,
|
||||||
|
[{"source_feature_id": "GRB.1", "geometry_wgs84": polygon}],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert buildings["100"]["grb_match_method"] == "exact_geometry"
|
||||||
|
assert buildings["100"]["grb_match_confidence"] == 1.0
|
||||||
|
assert buildings["101"]["grb_match_status"] == "unmatched"
|
||||||
|
assert summary["match_status_counts"] == {"matched": 1, "unmatched": 1}
|
||||||
|
assert summary["match_rate"] == 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_and_relation_metrics_use_filtered_server_owned_aggregations() -> None:
|
||||||
|
module = load_operator()
|
||||||
|
metrics = module.selection_metrics()
|
||||||
|
assert {item["metric_key"] for item in metrics} >= {
|
||||||
|
"registered_building_count",
|
||||||
|
"realized_building_count",
|
||||||
|
"building_unit_count",
|
||||||
|
"linked_address_count",
|
||||||
|
"active_address_count",
|
||||||
|
"grb_matched_building_count",
|
||||||
|
}
|
||||||
|
status_metrics = [item for item in metrics if item["metric_key"].endswith("building_count")]
|
||||||
|
assert any(item.get("filter_property") == "building_status_key" for item in status_metrics)
|
||||||
|
assert "huishoudens" in next(item for item in metrics if item["metric_key"] == "linked_address_count")["warning"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filtered_feature_count_and_numeric_relations_validate_as_selection_summary() -> None:
|
||||||
|
module = load_operator()
|
||||||
|
dataset = Dataset(
|
||||||
|
id=uuid4(),
|
||||||
|
project_id=uuid4(),
|
||||||
|
name="buildings_addresses_register.geojson",
|
||||||
|
dataset_type="vector",
|
||||||
|
dataset_role="reference",
|
||||||
|
source_name=module.SOURCE_NAME,
|
||||||
|
reference_layer_name="building_registry",
|
||||||
|
source_metadata={
|
||||||
|
"theme": "buildings",
|
||||||
|
"semantic_metrics": False,
|
||||||
|
"selection_aggregation": {
|
||||||
|
"metric_key": "building_footprint_area",
|
||||||
|
"method": "intersection_area",
|
||||||
|
"label": "Gebouwgrondoppervlakte",
|
||||||
|
"unit": "ha",
|
||||||
|
"geometry_dimension": 2,
|
||||||
|
},
|
||||||
|
"selection_metrics": module.selection_metrics(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
session = SequenceScalarSession([100_000, 2, 0, 1, 0, 4, 3, 4, 3, 2])
|
||||||
|
|
||||||
|
result = VectorFeatureService.summarize_features_by_bbox(
|
||||||
|
session,
|
||||||
|
dataset=dataset,
|
||||||
|
bbox=BBOX,
|
||||||
|
total_feature_count=3,
|
||||||
|
full_dataset_area=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics = {item["metric_key"]: item for item in result["metrics"]}
|
||||||
|
assert result["metric_value"] == 10.0
|
||||||
|
assert metrics["registered_building_count"]["metric_value"] == 3
|
||||||
|
assert metrics["realized_building_count"]["metric_value"] == 2
|
||||||
|
assert metrics["building_unit_count"]["metric_value"] == 4
|
||||||
|
assert metrics["active_address_count"]["metric_value"] == 3
|
||||||
|
assert metrics["grb_matched_building_count"]["metric_value"] == 2
|
||||||
|
VectorSelectionSummary(**result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_operator_is_canonical_packaged_and_mol_scoped_in_explorer() -> None:
|
||||||
|
operator = (ROOT / "scripts/provision_buildings_addresses_register.py").read_text(encoding="utf-8")
|
||||||
|
service = (ROOT / "backend/app/services/vector_feature_service.py").read_text(encoding="utf-8")
|
||||||
|
dockerfile = (ROOT / "deploy/unraid/Dockerfile.all-in-one").read_text(encoding="utf-8")
|
||||||
|
readiness = (ROOT / "scripts/run_readiness_check.sh").read_text(encoding="utf-8")
|
||||||
|
workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
|
||||||
|
catalog = (ROOT / "frontend/src/components/datasets/SourceCatalogPanel.tsx").read_text(encoding="utf-8")
|
||||||
|
display = (ROOT / "frontend/src/lib/datasetDisplay.ts").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "/datasets/upload" in operator
|
||||||
|
assert "VectorFeature" not in operator
|
||||||
|
assert "INSERT INTO vector_features" not in operator
|
||||||
|
assert "VolledigAdres" in operator and '"VolledigAdres", "Straatnaam"' in operator
|
||||||
|
assert '"provision_buildings_addresses_register.py"' in service
|
||||||
|
assert "COPY scripts/provision_buildings_addresses_register.py" in dockerfile
|
||||||
|
assert "py_compile scripts/provision_buildings_addresses_register.py" in readiness
|
||||||
|
assert "datasetCoversSelectedArea" in workspace
|
||||||
|
assert "Gebouwen- en Adressenregister" in catalog
|
||||||
|
assert "building_registry: 'Gebouwenregister'" in display
|
||||||
|
assert "digitaal_vlaanderen_buildings_addresses_register: 'Digitaal Vlaanderen'" in display
|
||||||
@@ -80,6 +80,7 @@ COPY scripts/provision_official_landuse_timeseries.py /app/scripts/provision_off
|
|||||||
COPY scripts/provision_waterinfo_station_history.py /app/scripts/provision_waterinfo_station_history.py
|
COPY scripts/provision_waterinfo_station_history.py /app/scripts/provision_waterinfo_station_history.py
|
||||||
COPY scripts/provision_mol_bwk_natura2000.py /app/scripts/provision_mol_bwk_natura2000.py
|
COPY scripts/provision_mol_bwk_natura2000.py /app/scripts/provision_mol_bwk_natura2000.py
|
||||||
COPY scripts/provision_agricultural_parcel_history.py /app/scripts/provision_agricultural_parcel_history.py
|
COPY scripts/provision_agricultural_parcel_history.py /app/scripts/provision_agricultural_parcel_history.py
|
||||||
|
COPY scripts/provision_buildings_addresses_register.py /app/scripts/provision_buildings_addresses_register.py
|
||||||
COPY scripts/provision_regional_timeseries.py /app/scripts/provision_regional_timeseries.py
|
COPY scripts/provision_regional_timeseries.py /app/scripts/provision_regional_timeseries.py
|
||||||
COPY scripts/geographic_scopes.py /app/scripts/geographic_scopes.py
|
COPY scripts/geographic_scopes.py /app/scripts/geographic_scopes.py
|
||||||
COPY scripts/provision_geographic_scope.py /app/scripts/provision_geographic_scope.py
|
COPY scripts/provision_geographic_scope.py /app/scripts/provision_geographic_scope.py
|
||||||
|
|||||||
@@ -1639,6 +1639,21 @@ Datasets share one scope-specific temporal series, but declare
|
|||||||
`identity_stable=false`; their temporal response compares area totals and
|
`identity_stable=false`; their temporal response compares area totals and
|
||||||
returns no parcel-level added/removed/modified claims.
|
returns no parcel-level added/removed/modified claims.
|
||||||
|
|
||||||
|
For `reference_layer_name=building_registry`, the primary metric is exact
|
||||||
|
intersected building-footprint area in hectares. Supplemental server-owned
|
||||||
|
metrics expose register building/status counts, aggregate building-unit and
|
||||||
|
address-status counts, and confirmed GRB matches. Filtered `feature_count`
|
||||||
|
metrics apply their configured property filter in PostGIS just like filtered
|
||||||
|
area/sum metrics. Address labels and house/box numbers are never part of the
|
||||||
|
queryable Feature properties or response contract.
|
||||||
|
|
||||||
|
The governed snapshot is scoped to its persisted `area_id`. The frontend may
|
||||||
|
prefer it over the regional GRB building layer only when that exact Area is
|
||||||
|
active; another municipality or the full region must continue to use the
|
||||||
|
regional GRB Dataset. No new register-specific API endpoint exists: upload,
|
||||||
|
GeoJSON, exact selection and temporal provenance use the existing Dataset
|
||||||
|
contracts.
|
||||||
|
|
||||||
## Local GeoIntel assistant
|
## Local GeoIntel assistant
|
||||||
|
|
||||||
The assistant is an optional read-only language interface over persisted
|
The assistant is an optional read-only language interface over persisted
|
||||||
|
|||||||
+38
-6
@@ -329,12 +329,44 @@ quality metrics.
|
|||||||
|
|
||||||
## Gebouwenregister
|
## Gebouwenregister
|
||||||
|
|
||||||
- Naam: Gebouwenregister Vlaanderen
|
The governed operator `scripts/provision_buildings_addresses_register.py`
|
||||||
- Type: vector/API/metadata
|
reads the official Digitaal Vlaanderen OGC API Features collections
|
||||||
- Gebruik: gebouwmetadata en statusinformatie
|
`Gebouw`, `Gebouweenheid` and `Adres`. The source is continuously updated;
|
||||||
- Toegang: nader te bepalen
|
GeoIntel therefore creates a dated snapshot rather than claiming an annual
|
||||||
- Cache: PostGIS/metadata tabellen
|
historical series. Raw response pages, request URLs and SHA256 checksums are
|
||||||
- Prioriteit: V2
|
retained as operator evidence.
|
||||||
|
|
||||||
|
Building polygons are clipped against the exact persisted Area in
|
||||||
|
EPSG:31370, transformed to EPSG:4326 and persisted through the normal Dataset
|
||||||
|
upload route with `source_name=digitaal_vlaanderen_buildings_addresses_register`
|
||||||
|
and `reference_layer_name=building_registry`. Register lifecycle state remains
|
||||||
|
separate from GRB geometry. Each register building receives an explicit
|
||||||
|
`matched`, `review`, `ambiguous` or `unmatched` GRB reconciliation result; a
|
||||||
|
low-confidence or duplicate match is never silently promoted.
|
||||||
|
|
||||||
|
Building units use the official `GebouwObjectId` relation. The public address
|
||||||
|
collection does not expose that relation directly, so addresses are linked
|
||||||
|
only through an exact, unambiguous unit position or unambiguous polygon
|
||||||
|
containment. Ambiguous and unmatched rows are counted in the manifest and are
|
||||||
|
never forced to the nearest building.
|
||||||
|
|
||||||
|
The queryable layer contains building polygons, lifecycle status and aggregate
|
||||||
|
unit/address counts only. Street names, full addresses, house numbers and box
|
||||||
|
numbers are excluded. Address counts are not households, dwellings, residents
|
||||||
|
or population. Raw source pages remain restricted operator evidence and are
|
||||||
|
not exposed by the API or map.
|
||||||
|
|
||||||
|
Official endpoints and catalogues:
|
||||||
|
|
||||||
|
- https://geo.api.vlaanderen.be/Gebouwenregister/ogc/features/v1/collections/Gebouw/items
|
||||||
|
- https://geo.api.vlaanderen.be/Gebouwenregister/ogc/features/v1/collections/Gebouweenheid/items
|
||||||
|
- https://geo.api.vlaanderen.be/Adressenregister/ogc/features/v1/collections/Adres/items
|
||||||
|
- https://www.vlaanderen.be/datavindplaats/catalogus/gebouwen-en-adressenregister
|
||||||
|
- https://www.vlaanderen.be/datavindplaats/catalogus/gebouwenregister
|
||||||
|
|
||||||
|
Digitaal Vlaanderen is transitioning the download products in summer 2026.
|
||||||
|
This operator uses the current production OGC API and does not depend on the
|
||||||
|
retiring `/v2/extract` path.
|
||||||
|
|
||||||
## Lokale demo datasets
|
## Lokale demo datasets
|
||||||
|
|
||||||
|
|||||||
@@ -61,22 +61,43 @@ GRB-gebouwpolygonen worden gebruikt als ground-truth proxy. Niet absoluut perfec
|
|||||||
|
|
||||||
### Rol
|
### Rol
|
||||||
|
|
||||||
Aanvullende gebouwinformatie bij geometrieën.
|
Authoritative register lifecycle and relation metadata attached to a dated
|
||||||
|
building snapshot. It complements GRB; it does not replace the separate GRB
|
||||||
|
footprint evidence or its QA role.
|
||||||
|
|
||||||
### Gebruik
|
### Gebruik
|
||||||
|
|
||||||
- gebouwmetadata
|
- stable building object and version identity
|
||||||
- identificatie
|
- official lifecycle status and geometry method
|
||||||
- status
|
- aggregate registered building-unit counts by lifecycle status
|
||||||
- koppeling met GRB-gebouwpolygonen indien mogelijk
|
- aggregate address counts by register status
|
||||||
|
- classified reconciliation against persisted GRB geometry
|
||||||
|
|
||||||
### Type
|
### Type
|
||||||
|
|
||||||
Vector/API/metadata.
|
Official OGC API Features input, normalized polygon GeoJSON upload and
|
||||||
|
PostGIS `vector_features` output.
|
||||||
|
|
||||||
### Prioriteit
|
### Persisted contract
|
||||||
|
|
||||||
V2 of V1.5.
|
- `dataset_role=reference`
|
||||||
|
- `source_name=digitaal_vlaanderen_buildings_addresses_register`
|
||||||
|
- `reference_layer_name=building_registry`
|
||||||
|
- `temporal_granularity=snapshot`
|
||||||
|
- stable building identity within the register, but no fabricated historical
|
||||||
|
observation between snapshots
|
||||||
|
- polygon geometry clipped in EPSG:31370 and persisted as EPSG:4326
|
||||||
|
- selection metrics: exact footprint hectares, building lifecycle counts,
|
||||||
|
unit counts, address-status counts and confirmed GRB match counts
|
||||||
|
|
||||||
|
### Privacy and semantics
|
||||||
|
|
||||||
|
Queryable properties may contain register object ids, lifecycle status,
|
||||||
|
geometry method, aggregate unit/address counts and GRB reconciliation evidence.
|
||||||
|
They must not contain `VolledigAdres`, `Straatnaam`, `Huisnummer`,
|
||||||
|
`HuisnummerLabel` or `Busnummer`. Address and unit counts may not be labelled
|
||||||
|
as population, residents, households or dwellings. Building footprint is
|
||||||
|
ground area, not floor area, height or volume.
|
||||||
|
|
||||||
## OpenStreetMap
|
## OpenStreetMap
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,27 @@ artifact; Dataset and vector_feature rows remain the queryable PostGIS state.
|
|||||||
The manifest binds source, crop-code list and upload artifact checksums. A
|
The manifest binds source, crop-code list and upload artifact checksums. A
|
||||||
checksum conflict with an existing annual Dataset fails closed.
|
checksum conflict with an existing annual Dataset fails closed.
|
||||||
|
|
||||||
|
Buildings and Addresses Register snapshot evidence lives under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
storage/operator-evidence/buildings-addresses-register/mol/{observed-date}/
|
||||||
|
raw/
|
||||||
|
buildings_page_*.json
|
||||||
|
building_units_page_*.json
|
||||||
|
addresses_page_*.json
|
||||||
|
buildings_addresses_register.geojson
|
||||||
|
buildings_addresses_register.manifest.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Raw pages contain the unmodified official response and are retained only as
|
||||||
|
checksummed operator evidence. They can contain address labels and must never
|
||||||
|
be served as a map/API artifact. The normalized GeoJSON deliberately contains
|
||||||
|
one polygon per register building with lifecycle state, aggregate relation
|
||||||
|
counts and classified GRB reconciliation only. It enters PostGIS exclusively
|
||||||
|
through DatasetService and ordinary `vector_features`; the operator never
|
||||||
|
writes database rows directly. The manifest binds all raw pages, the normalized
|
||||||
|
artifact, exact Area boundary and every GRB partition used for reconciliation.
|
||||||
|
|
||||||
Offline demo export artifacts can be inspected and cleaned with:
|
Offline demo export artifacts can be inspected and cleaned with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -484,6 +484,14 @@ Object additions/removals stay hidden because annual parcel identity is not
|
|||||||
stable. The Sources inventory only labels the series available after real
|
stable. The Sources inventory only labels the series available after real
|
||||||
Datasets exist.
|
Datasets exist.
|
||||||
|
|
||||||
|
When the governed Buildings and Addresses Register snapshot is loaded for Mol,
|
||||||
|
the `Bebouwing` theme automatically prefers that richer Dataset only while the
|
||||||
|
exact Mol Area is active. It shows register lifecycle, unit/address aggregate
|
||||||
|
metrics and GRB reconciliation without exposing address labels. Selecting
|
||||||
|
another municipality or the complete Kempen scope falls back to the complete
|
||||||
|
regional GRB building layer. This avoids presenting a Mol-only snapshot as
|
||||||
|
regional coverage.
|
||||||
|
|
||||||
## Useful repository scripts
|
## Useful repository scripts
|
||||||
|
|
||||||
- `bash scripts/frontend_install.sh`
|
- `bash scripts/frontend_install.sh`
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
|||||||
)
|
)
|
||||||
const bwkDatasets = ready.filter((dataset) => dataset.source_name === 'inbo_bwk_natura2000')
|
const bwkDatasets = ready.filter((dataset) => dataset.source_name === 'inbo_bwk_natura2000')
|
||||||
const agricultureDatasets = ready.filter((dataset) => dataset.source_name === 'agentschap_landbouw_zeevisserij_agricultural_parcels')
|
const agricultureDatasets = ready.filter((dataset) => dataset.source_name === 'agentschap_landbouw_zeevisserij_agricultural_parcels')
|
||||||
|
const buildingsRegisterDatasets = ready.filter(
|
||||||
|
(dataset) => dataset.source_name === 'digitaal_vlaanderen_buildings_addresses_register',
|
||||||
|
)
|
||||||
|
const latestBuildingsRegister = [...buildingsRegisterDatasets].sort(
|
||||||
|
(left, right) => new Date(right.observed_at ?? 0).getTime() - new Date(left.observed_at ?? 0).getTime(),
|
||||||
|
)[0]
|
||||||
const agricultureYears = agricultureDatasets
|
const agricultureYears = agricultureDatasets
|
||||||
.flatMap((dataset) => dataset.observed_at ? [new Date(dataset.observed_at).getUTCFullYear()] : [])
|
.flatMap((dataset) => dataset.observed_at ? [new Date(dataset.observed_at).getUTCFullYear()] : [])
|
||||||
const pendingSources = AVAILABLE_SOURCES.filter((source) => {
|
const pendingSources = AVAILABLE_SOURCES.filter((source) => {
|
||||||
@@ -115,6 +121,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
|||||||
if (source.key === 'historical_orthophoto') return historicalOrthophotos.length === 0
|
if (source.key === 'historical_orthophoto') return historicalOrthophotos.length === 0
|
||||||
if (source.key === 'bwk') return bwkDatasets.length === 0
|
if (source.key === 'bwk') return bwkDatasets.length === 0
|
||||||
if (source.key === 'agriculture') return agricultureDatasets.length === 0
|
if (source.key === 'agriculture') return agricultureDatasets.length === 0
|
||||||
|
if (source.key === 'buildings_register') return buildingsRegisterDatasets.length === 0
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
const themes = Object.keys(THEME_LABELS).map((theme) => {
|
const themes = Object.keys(THEME_LABELS).map((theme) => {
|
||||||
@@ -174,7 +181,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 ? (
|
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 || buildingsRegisterDatasets.length > 0 ? (
|
||||||
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
|
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
|
||||||
{waterinfoDatasets.length > 0 ? (
|
{waterinfoDatasets.length > 0 ? (
|
||||||
<article>
|
<article>
|
||||||
@@ -204,6 +211,17 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
|||||||
<p>Oppervlakte en officiële hoofdteeltgroepen zijn historisch vergelijkbaar; perceelidentiteiten blijven bewust niet gekoppeld tussen jaren.</p>
|
<p>Oppervlakte en officiële hoofdteeltgroepen zijn historisch vergelijkbaar; perceelidentiteiten blijven bewust niet gekoppeld tussen jaren.</p>
|
||||||
</article>
|
</article>
|
||||||
) : null}
|
) : null}
|
||||||
|
{latestBuildingsRegister ? (
|
||||||
|
<article>
|
||||||
|
<strong>Gebouwen- en Adressenregister</strong>
|
||||||
|
<span>
|
||||||
|
{(latestBuildingsRegister.feature_count ?? 0).toLocaleString('nl-BE')} gebouwen · {' '}
|
||||||
|
{Number(latestBuildingsRegister.source_metadata?.['building_unit_count'] ?? 0).toLocaleString('nl-BE')} eenheden · {' '}
|
||||||
|
{Number(latestBuildingsRegister.source_metadata?.['linked_address_count'] ?? 0).toLocaleString('nl-BE')} gekoppelde adressen
|
||||||
|
</span>
|
||||||
|
<p>Registerstatus en geaggregeerde koppelingen voor Mol; adreslabels en persoonsgegevens worden niet in de kaartlaag getoond.</p>
|
||||||
|
</article>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -120,8 +120,22 @@ function datasetMatchesTheme(dataset: DatasetCreateResponse, theme: DataTheme):
|
|||||||
return theme.tokens.some((token) => searchText.includes(token))
|
return theme.tokens.some((token) => searchText.includes(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickThemeDataset(datasets: DatasetCreateResponse[], theme: DataTheme): DatasetCreateResponse | null {
|
function datasetCoversSelectedArea(dataset: DatasetCreateResponse, selectedAreaId: string | null): boolean {
|
||||||
const candidates = datasets.filter((dataset) => datasetMatchesTheme(dataset, theme))
|
const coverageScope = String(dataset.source_metadata?.['coverage_scope'] ?? '')
|
||||||
|
if (coverageScope !== 'municipality' || !dataset.area_id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return Boolean(selectedAreaId) && dataset.area_id === selectedAreaId
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickThemeDataset(
|
||||||
|
datasets: DatasetCreateResponse[],
|
||||||
|
theme: DataTheme,
|
||||||
|
selectedAreaId: string | null,
|
||||||
|
): DatasetCreateResponse | null {
|
||||||
|
const candidates = datasets.filter(
|
||||||
|
(dataset) => datasetMatchesTheme(dataset, theme) && datasetCoversSelectedArea(dataset, selectedAreaId),
|
||||||
|
)
|
||||||
candidates.sort((left, right) => {
|
candidates.sort((left, right) => {
|
||||||
const score = (dataset: DatasetCreateResponse) =>
|
const score = (dataset: DatasetCreateResponse) =>
|
||||||
(dataset.reference_layer_name && theme.tokens.includes(dataset.reference_layer_name.toLowerCase()) ? 1_000_000 : 0) +
|
(dataset.reference_layer_name && theme.tokens.includes(dataset.reference_layer_name.toLowerCase()) ? 1_000_000 : 0) +
|
||||||
@@ -129,6 +143,7 @@ function pickThemeDataset(datasets: DatasetCreateResponse[], theme: DataTheme):
|
|||||||
(dataset.source_name === 'department_omgeving_land_use' ? 90_000 : 0) +
|
(dataset.source_name === 'department_omgeving_land_use' ? 90_000 : 0) +
|
||||||
(dataset.source_name === 'inbo_bwk_natura2000' ? 95_000 : 0) +
|
(dataset.source_name === 'inbo_bwk_natura2000' ? 95_000 : 0) +
|
||||||
(dataset.source_name === 'agentschap_landbouw_zeevisserij_agricultural_parcels' ? 98_000 : 0) +
|
(dataset.source_name === 'agentschap_landbouw_zeevisserij_agricultural_parcels' ? 98_000 : 0) +
|
||||||
|
(dataset.source_name === 'digitaal_vlaanderen_buildings_addresses_register' ? 120_000 : 0) +
|
||||||
(dataset.dataset_role === 'reference' ? 10_000 : 0) +
|
(dataset.dataset_role === 'reference' ? 10_000 : 0) +
|
||||||
(dataset.observed_at ? new Date(dataset.observed_at).getTime() / 100_000_000 : 0) +
|
(dataset.observed_at ? new Date(dataset.observed_at).getTime() / 100_000_000 : 0) +
|
||||||
(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0)
|
(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0)
|
||||||
@@ -636,9 +651,9 @@ export function MapWorkspace({
|
|||||||
const themeDatasetMap = useMemo(
|
const themeDatasetMap = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
DATA_THEMES.map((theme) => [theme.id, pickThemeDataset(availableMapDatasets, theme)]),
|
DATA_THEMES.map((theme) => [theme.id, pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId)]),
|
||||||
) as Record<DataThemeId, DatasetCreateResponse | null>,
|
) as Record<DataThemeId, DatasetCreateResponse | null>,
|
||||||
[availableMapDatasets],
|
[availableMapDatasets, selectedMapAreaId],
|
||||||
)
|
)
|
||||||
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
||||||
const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
|
const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
|
||||||
@@ -740,7 +755,7 @@ export function MapWorkspace({
|
|||||||
}, [activeTemporalSeries])
|
}, [activeTemporalSeries])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (advancedMode || !activeThemeDataset || (selectedMapDataset && datasetMatchesTheme(selectedMapDataset, activeTheme))) {
|
if (advancedMode || !activeThemeDataset || selectedMapDataset?.id === activeThemeDataset.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onOpenDatasetInMap(activeThemeDataset)
|
onOpenDatasetInMap(activeThemeDataset)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const DATASET_LABEL_BY_LAYER: Record<string, string> = {
|
|||||||
forest: 'Bos en groen',
|
forest: 'Bos en groen',
|
||||||
nature_value: 'Natuurwaarde',
|
nature_value: 'Natuurwaarde',
|
||||||
agriculture: 'Landbouwgebruikspercelen',
|
agriculture: 'Landbouwgebruikspercelen',
|
||||||
|
building_registry: 'Gebouwenregister',
|
||||||
regional_boundary: 'Grens vervoerregio Kempen',
|
regional_boundary: 'Grens vervoerregio Kempen',
|
||||||
municipality_boundaries: 'Gemeentegrenzen Kempen',
|
municipality_boundaries: 'Gemeentegrenzen Kempen',
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,7 @@ const DATASET_SOURCE_LABELS: Record<string, string> = {
|
|||||||
department_omgeving_land_use: 'Departement Omgeving',
|
department_omgeving_land_use: 'Departement Omgeving',
|
||||||
agentschap_landbouw_zeevisserij_agricultural_parcels: 'Agentschap Landbouw en Zeevisserij',
|
agentschap_landbouw_zeevisserij_agricultural_parcels: 'Agentschap Landbouw en Zeevisserij',
|
||||||
digitaal_vlaanderen_orthophoto: 'Digitaal Vlaanderen',
|
digitaal_vlaanderen_orthophoto: 'Digitaal Vlaanderen',
|
||||||
|
digitaal_vlaanderen_buildings_addresses_register: 'Digitaal Vlaanderen',
|
||||||
grb: 'GRB',
|
grb: 'GRB',
|
||||||
historical_landuse: 'Digitaal Vlaanderen',
|
historical_landuse: 'Digitaal Vlaanderen',
|
||||||
inbo_bwk_natura2000: 'INBO',
|
inbo_bwk_natura2000: 'INBO',
|
||||||
|
|||||||
@@ -1481,6 +1481,33 @@ Datasets. `--force` refreshes retained evidence but cannot silently replace a
|
|||||||
conflicting persisted annual checksum. Use `--scope mol` for an independent
|
conflicting persisted annual checksum. Use `--scope mol` for an independent
|
||||||
municipal series.
|
municipal series.
|
||||||
|
|
||||||
|
## Buildings and Addresses Register snapshot
|
||||||
|
|
||||||
|
Prepare and audit the current official Mol snapshot without persistence:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec geointel python /app/scripts/provision_buildings_addresses_register.py --fetch-only
|
||||||
|
```
|
||||||
|
|
||||||
|
Import the audited artifact through the canonical Dataset upload route:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec geointel python /app/scripts/provision_buildings_addresses_register.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The operator requires a persisted Mol Area and the complete regional GRB
|
||||||
|
buildings Dataset with valid manifest/partition checksums. It reads only the
|
||||||
|
official `Gebouw`, `Gebouweenheid` and `Adres` OGC collections, clips in
|
||||||
|
EPSG:31370 and retains every raw response page under the storage volume.
|
||||||
|
`--force` refetches evidence; it cannot overwrite a conflicting snapshot for
|
||||||
|
the same Area/date. Safety limits are configurable with `--page-limit`,
|
||||||
|
`--max-buildings`, `--max-units` and `--max-addresses`.
|
||||||
|
|
||||||
|
Only aggregate unit/address counts enter the queryable building layer. Review
|
||||||
|
`address_relations`, `grb_reconciliation`, checksums and limitations in the
|
||||||
|
manifest before accepting a broader import. Raw address response pages are
|
||||||
|
operator evidence and must not be published.
|
||||||
|
|
||||||
## Tower deployment
|
## Tower deployment
|
||||||
|
|
||||||
Push the local branch to Gitea, then rebuild the Unraid/Tower Docker runtime:
|
Push the local branch to Gitea, then rebuild the Unraid/Tower Docker runtime:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,7 @@ ${PYTHON_BIN} -m py_compile scripts/provision_official_landuse_timeseries.py
|
|||||||
${PYTHON_BIN} -m py_compile scripts/provision_waterinfo_station_history.py
|
${PYTHON_BIN} -m py_compile scripts/provision_waterinfo_station_history.py
|
||||||
${PYTHON_BIN} -m py_compile scripts/provision_mol_bwk_natura2000.py
|
${PYTHON_BIN} -m py_compile scripts/provision_mol_bwk_natura2000.py
|
||||||
${PYTHON_BIN} -m py_compile scripts/provision_agricultural_parcel_history.py
|
${PYTHON_BIN} -m py_compile scripts/provision_agricultural_parcel_history.py
|
||||||
|
${PYTHON_BIN} -m py_compile scripts/provision_buildings_addresses_register.py
|
||||||
${PYTHON_BIN} -m py_compile scripts/provision_regional_timeseries.py
|
${PYTHON_BIN} -m py_compile scripts/provision_regional_timeseries.py
|
||||||
${PYTHON_BIN} -m py_compile scripts/geographic_scopes.py
|
${PYTHON_BIN} -m py_compile scripts/geographic_scopes.py
|
||||||
${PYTHON_BIN} -m py_compile scripts/provision_geographic_scope.py
|
${PYTHON_BIN} -m py_compile scripts/provision_geographic_scope.py
|
||||||
|
|||||||
Reference in New Issue
Block a user