Fix latest snapshot map ranking
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-16 18:47:15 +02:00
parent 18bef44d0e
commit cb5425ad1a
4 changed files with 37 additions and 6 deletions
+2
View File
@@ -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)
@@ -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
+12 -1
View File
@@ -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
+13 -5
View File
@@ -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
}