From cb5425ad1ad55199e15574d42b44f6e0fdb44b40 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 16 Jul 2026 18:47:15 +0200 Subject: [PATCH] Fix latest snapshot map ranking --- CHANGELOG.md | 2 ++ .../test_sprint223_governed_grb_refresh.py | 10 ++++++++++ docs/CODEX_EXECUTION_LOG.md | 13 ++++++++++++- frontend/src/components/map/MapWorkspace.tsx | 18 +++++++++++++----- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 863ab60b..7799d5c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ path, and retained every older snapshot. - Added compact Status UI, deterministic guardrail tests and readiness syntax coverage without changing migrations or enabling automatic refresh. +- Fixed map theme ranking so a newer official observation always wins over an + older snapshot with a marginally larger feature count. ## Sprint 222 Official source edition probes (2026-07-16) diff --git a/backend/tests/test_sprint223_governed_grb_refresh.py b/backend/tests/test_sprint223_governed_grb_refresh.py index 65cf022a..a0947bc5 100644 --- a/backend/tests/test_sprint223_governed_grb_refresh.py +++ b/backend/tests/test_sprint223_governed_grb_refresh.py @@ -248,3 +248,13 @@ def test_refresh_api_and_frontend_remain_explicit_only() -> None: assert "void probeCatalogs(" not in hook assert 'choices=("plan", "stage", "apply")' in operator assert "--confirm-plan-sha256" in operator + + +def test_map_theme_ranking_prefers_newer_observation_over_feature_count() -> None: + root = Path(__file__).resolve().parents[2] + workspace = (root / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text(encoding="utf-8") + observed_sort = workspace.index("const observedAtDifference") + feature_tiebreaker = workspace.index("right.feature_count", observed_sort) + assert observed_sort < feature_tiebreaker + assert "new Date(right.observed_at ?? 0).getTime()" in workspace + assert "if (observedAtDifference !== 0) return observedAtDifference" in workspace diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index b57c4423..a313e313 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -9365,9 +9365,20 @@ Boundaries: Validation so far: - Backend compile and frontend typecheck passed. - Source-catalog plus governed-refresh focused suite passed: 20 tests. -- Full `scripts/run_readiness_check.sh` passed: 779 backend tests, 110 +- Full `scripts/run_readiness_check.sh` passed: 780 backend tests, 110 documented route contracts, one Alembic head, frontend typecheck/build and all packaged smoke checks. +- Live Tower staging completed all 112 municipality/theme partitions for + official edition `2026-07-15`: 1,054,223 features, 1,250,873,313 artifact + bytes and staged plan SHA-256 + `7a772b70771c223b14d16c097c917fb160883349df61e24a2730e8c9591d56fd`. +- Checksum-confirmed apply created four new Datasets with exact PostGIS counts: + 466,092 buildings, 84,513 roads, 88,330 water features and 415,288 parcels. + The four `2026-07-14` snapshots remain present; total project datasets moved + from 632 to 636 and source integrity remains zero-error. +- Live browser validation exposed and fixed additive theme ranking where two + extra old water features could outweigh a newer observation date. Ranking is + now priority, observation date, import date, then feature-count tie-breaker. Next: - Run the full release gate, deploy to Tower, execute a live read-only plan and diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index b0d1028e..51cabe5a 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -279,7 +279,7 @@ function pickThemeDataset( && datasetCoversSelectedArea(dataset, selectedAreaId, regionalScope), ) candidates.sort((left, right) => { - const score = (dataset: DatasetCreateResponse) => + const priorityScore = (dataset: DatasetCreateResponse) => (dataset.area_id && dataset.area_id === selectedAreaId ? 10_000_000 : 0) + (dataset.reference_layer_name && theme.tokens.includes(dataset.reference_layer_name.toLowerCase()) ? 1_000_000 : 0) + (dataset.source_name === 'grb' ? 100_000 : 0) + @@ -292,10 +292,18 @@ function pickThemeDataset( (dataset.source_name === 'vmm_flood_hazard' ? 5_000_000 : 0) + (dataset.source_metadata?.['product_key'] === 'dtm_1m' ? 1_000_000 : 0) + (dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100' ? 1_000_000 : 0) + - (dataset.dataset_role === 'reference' ? 10_000 : 0) + - (dataset.observed_at ? new Date(dataset.observed_at).getTime() / 100_000_000 : 0) + - (dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0) - return score(right) - score(left) + (dataset.dataset_role === 'reference' ? 10_000 : 0) + const priorityDifference = priorityScore(right) - priorityScore(left) + if (priorityDifference !== 0) return priorityDifference + + const observedAtDifference = new Date(right.observed_at ?? 0).getTime() - new Date(left.observed_at ?? 0).getTime() + if (observedAtDifference !== 0) return observedAtDifference + + const importedAtDifference = new Date(right.imported_at ?? 0).getTime() - new Date(left.imported_at ?? 0).getTime() + if (importedAtDifference !== 0) return importedAtDifference + + return (right.feature_count ?? right.vector_summary?.feature_count ?? 0) + - (left.feature_count ?? left.vector_summary?.feature_count ?? 0) }) return candidates[0] ?? null }