diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe7703b..17e51a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added an explicit regional GRB building operator that fetches the approved Kempen scope in 28 resumable municipality partitions and follows every OGC API pagination link. - Assigned boundary-crossing GRB features to exactly one partition using maximum municipality intersection, with deterministic NIS-code tie breaking and one retained source identity. +- Added a covered-by-member fast path so ordinary interior buildings avoid the regional boundary-owner scan while true border cases retain the exact overlap rule. - Added streaming artifact copy and batch-wise partition indexing through `DatasetService` and `VectorFeatureService`, avoiding one giant multipart parse while preserving one normal regional dataset for existing map and PostGIS selection flows. - Added truncation guards, source/checksum manifests, immutable observation dates, duplicate-source rejection and focused service/operator tests. - Kept the provider endpoint contract unchanged and retained explicit operator-only fetching; no source request runs during application startup or interactive map use. diff --git a/backend/tests/test_sprint190_regional_grb_buildings.py b/backend/tests/test_sprint190_regional_grb_buildings.py index a281699e..2b5ed95d 100644 --- a/backend/tests/test_sprint190_regional_grb_buildings.py +++ b/backend/tests/test_sprint190_regional_grb_buildings.py @@ -84,6 +84,41 @@ def test_partition_assignment_is_deterministic_and_has_no_cross_member_duplicate assert alpha_features[1]["properties"]["partition_assignment"] == "maximum_boundary_intersection" +def test_interior_buildings_skip_regional_owner_scan(monkeypatch) -> None: + operator = load_operator() + scopes = importlib.import_module("geographic_scopes") + member = scopes.ScopeMember("Alpha", "10001") + boundary = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + scope = scopes.GeographicScope( + key="single", + display_name="Single", + project_name="Single", + project_region="Single", + area_name="Single boundary", + authority_name="Test", + authority_url="https://example.test", + scope_type="municipality", + limitation_message="Test.", + members=(member,), + ) + monkeypatch.setattr( + operator, + "assign_owner_nis", + lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("interior feature used slow owner scan")), + ) + + features, _ = operator.build_partition_features( + [({"type": "FeatureCollection", "features": [feature("GBG.inside", Polygon([(0.1, 0.1), (0.2, 0.1), (0.2, 0.2), (0.1, 0.2)]))]}, "https://example.test/grb")], + member=member, + members=[(member, boundary)], + regional_boundary=boundary, + scope=scope, + max_features=10, + ) + + assert [item["id"] for item in features] == ["GBG.inside"] + + def test_combined_artifact_streams_partitions_and_rejects_duplicate_source_ids(tmp_path: Path) -> None: operator = load_operator() scope = importlib.import_module("geographic_scopes").KEMPEN_TRANSPORT_REGION_SCOPE diff --git a/scripts/provision_regional_grb_buildings.py b/scripts/provision_regional_grb_buildings.py index 813345fa..19240f57 100644 --- a/scripts/provision_regional_grb_buildings.py +++ b/scripts/provision_regional_grb_buildings.py @@ -230,6 +230,7 @@ def build_partition_features( assigned_elsewhere_count = 0 outside_scope_count = 0 clipped_to_scope_count = 0 + member_boundary = next(boundary for candidate, boundary in members if candidate.nis_code == member.nis_code) for payload, source_url in pages: source_urls.append(source_url) @@ -247,7 +248,11 @@ def build_partition_features( if source_geometry is None or not source_geometry.intersects(regional_boundary): outside_scope_count += 1 continue - owner_nis = assign_owner_nis(source_geometry, members) + owner_nis = ( + member.nis_code + if member_boundary.covers(source_geometry) + else assign_owner_nis(source_geometry, members) + ) if owner_nis != member.nis_code: assigned_elsewhere_count += 1 continue