Prefer governed orthophoto editions in source status
This commit is contained in:
@@ -28,6 +28,9 @@
|
|||||||
- Made staged, reviewed and applied release evidence write-once. An idempotent
|
- Made staged, reviewed and applied release evidence write-once. An idempotent
|
||||||
apply validates and reuses the existing evidence instead of changing its
|
apply validates and reuses the existing evidence instead of changing its
|
||||||
timestamp or authorization history.
|
timestamp or authorization history.
|
||||||
|
- Made the read-only source-freshness audit prefer the governed official
|
||||||
|
`YYYY.NN` orthophoto edition over retained legacy `most_recent_at_*` labels,
|
||||||
|
while preserving the existing rolling-source review interval.
|
||||||
|
|
||||||
## Sprint 230 Governed orthophoto release preflight (2026-07-17)
|
## Sprint 230 Governed orthophoto release preflight (2026-07-17)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from collections import defaultdict
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import re
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ SOURCE_POLICIES: dict[str, SourcePolicy] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_POLICY = SourcePolicy("Niet-geclassificeerde bron", "edition")
|
DEFAULT_POLICY = SourcePolicy("Niet-geclassificeerde bron", "edition")
|
||||||
|
_ORTHOPHOTO_EDITION = re.compile(r"^20\d{2}\.\d{2}$")
|
||||||
|
|
||||||
|
|
||||||
def _as_utc(value: datetime | None) -> datetime | None:
|
def _as_utc(value: datetime | None) -> datetime | None:
|
||||||
@@ -81,10 +83,16 @@ def _latest_dataset(datasets: list[Dataset]) -> Dataset:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _latest_source_version(policy: SourcePolicy, datasets: list[Dataset]) -> str | None:
|
def _latest_source_version(source_name: str, policy: SourcePolicy, datasets: list[Dataset]) -> str | None:
|
||||||
versioned = [item for item in datasets if item.source_version]
|
versioned = [item for item in datasets if item.source_version]
|
||||||
if not versioned:
|
if not versioned:
|
||||||
return None
|
return None
|
||||||
|
if source_name == "digitaal_vlaanderen_orthophoto":
|
||||||
|
official_editions = [
|
||||||
|
item for item in versioned if _ORTHOPHOTO_EDITION.fullmatch((item.source_version or "").strip())
|
||||||
|
]
|
||||||
|
if official_editions:
|
||||||
|
return _latest_dataset(official_editions).source_version
|
||||||
if policy.refresh_policy == "rolling_snapshot":
|
if policy.refresh_policy == "rolling_snapshot":
|
||||||
named_current = [
|
named_current = [
|
||||||
item
|
item
|
||||||
@@ -258,7 +266,7 @@ class SourceFreshnessService:
|
|||||||
version_count=sum(len(versions_by_dataset.get(item.id, [])) for item in source_datasets),
|
version_count=sum(len(versions_by_dataset.get(item.id, [])) for item in source_datasets),
|
||||||
latest_imported_at=_latest_datetime(item.imported_at for item in source_datasets),
|
latest_imported_at=_latest_datetime(item.imported_at for item in source_datasets),
|
||||||
latest_observed_at=_latest_datetime(item.observed_at for item in source_datasets),
|
latest_observed_at=_latest_datetime(item.observed_at for item in source_datasets),
|
||||||
latest_source_version=_latest_source_version(policy, source_datasets),
|
latest_source_version=_latest_source_version(source_name, policy, source_datasets),
|
||||||
refresh_policy=policy.refresh_policy,
|
refresh_policy=policy.refresh_policy,
|
||||||
review_interval_days=policy.review_interval_days,
|
review_interval_days=policy.review_interval_days,
|
||||||
next_review_at=next_review_at,
|
next_review_at=next_review_at,
|
||||||
|
|||||||
@@ -157,6 +157,33 @@ def test_rolling_orthophoto_prefers_explicit_current_snapshot_over_historical_ob
|
|||||||
assert report.items[0].latest_source_version == "most_recent_at_2026-07-14"
|
assert report.items[0].latest_source_version == "most_recent_at_2026-07-14"
|
||||||
|
|
||||||
|
|
||||||
|
def test_orthophoto_freshness_prefers_governed_official_edition_over_rolling_marker() -> None:
|
||||||
|
legacy = _dataset(
|
||||||
|
"digitaal_vlaanderen_orthophoto",
|
||||||
|
imported_at=NOW - timedelta(days=2),
|
||||||
|
observed_at=datetime(2026, 7, 15, tzinfo=timezone.utc),
|
||||||
|
source_version="most_recent_at_2026-07-15",
|
||||||
|
)
|
||||||
|
official = _dataset(
|
||||||
|
"digitaal_vlaanderen_orthophoto",
|
||||||
|
imported_at=NOW - timedelta(days=1),
|
||||||
|
observed_at=datetime(2025, 4, 5, tzinfo=timezone.utc),
|
||||||
|
source_version="2025.04",
|
||||||
|
)
|
||||||
|
|
||||||
|
report = SourceFreshnessService.build_report(
|
||||||
|
legacy.project_id,
|
||||||
|
[legacy, official],
|
||||||
|
[_version(legacy), _version(official)],
|
||||||
|
now=NOW,
|
||||||
|
)
|
||||||
|
|
||||||
|
item = report.items[0]
|
||||||
|
assert item.latest_source_version == "2025.04"
|
||||||
|
assert item.refresh_policy == "rolling_snapshot"
|
||||||
|
assert item.review_interval_days == 180
|
||||||
|
|
||||||
|
|
||||||
def test_spatial_partitions_do_not_become_a_false_historical_series() -> None:
|
def test_spatial_partitions_do_not_become_a_false_historical_series() -> None:
|
||||||
first = _dataset(
|
first = _dataset(
|
||||||
"dov_soil_map",
|
"dov_soil_map",
|
||||||
|
|||||||
@@ -9791,13 +9791,49 @@ Implemented:
|
|||||||
the original checksum-bound evidence and never rewrite approval history.
|
the original checksum-bound evidence and never rewrite approval history.
|
||||||
|
|
||||||
Validation so far:
|
Validation so far:
|
||||||
- 38 focused Sprint 222/230/231 tests pass with deprecations treated as errors.
|
- 48 focused Sprint 221/222/230/231 tests pass. The release-management suite
|
||||||
Coverage includes official-edition ordering, first-baseline authorization,
|
contains 11 direct plan/stage/review/apply tests. Coverage includes
|
||||||
GetMap allowlisting/limits, RGB/CRS normalization, preview generation,
|
official-edition ordering in both catalog and source-freshness views,
|
||||||
tampering, preflight drift, named review, loopback-only apply and complete
|
first-baseline authorization, GetMap allowlisting/limits, RGB/CRS
|
||||||
upload provenance.
|
normalization, preview generation, tampering, preflight drift, named review,
|
||||||
|
loopback-only apply and complete upload provenance.
|
||||||
|
- Complete local readiness passed with 878 backend tests, 110 documented
|
||||||
|
routes, one Alembic head `202607160001`, frontend typecheck and production
|
||||||
|
build. Target compile and Ruff checks also passed.
|
||||||
|
|
||||||
|
Live validation:
|
||||||
|
- Commits `06d05f0` and `0c2ebc1` were pushed, deployed through the repository
|
||||||
|
to Tower and validated against PostGIS 3.6. The all-in-one container became
|
||||||
|
healthy on port 1202 and passed migrations, schema/collation, API proxy and
|
||||||
|
browser shell checks.
|
||||||
|
- The packaged operator staged official edition `2025.04` for bbox
|
||||||
|
`5.110,51.180,5.117,51.185`: one 921,848-byte official WMS response became a
|
||||||
|
496x562, three-band EPSG:31370 GeoTIFF at an approximately one-metre grid.
|
||||||
|
All 20 deterministic flight-day samples reported `2025-04-05`.
|
||||||
|
- The generated preview was visually inspected as a complete RGB orthophoto
|
||||||
|
with expected Mol roads, buildings and sports context. Named review bound
|
||||||
|
plan SHA-256 `c6cefd19dc60d7469b4885d9d1f5eff4e52a88808bbe3ac21879c5650e558662`
|
||||||
|
and review SHA-256
|
||||||
|
`9f0a3a65eb184fea7f91daa5018c0980ed03b792bd465a98c65f2add2413d90b`.
|
||||||
|
- Checksum-confirmed apply created exactly one immutable raster Dataset
|
||||||
|
`3cf14603-03da-42f5-b152-4bbecad7d006`; project total moved from 636 to 637.
|
||||||
|
Dataset, version and staged GeoTIFF share SHA-256
|
||||||
|
`f87701de6680f8ad25407996ddfd08056bf0533c05572ae489e9d478a72e7e46`.
|
||||||
|
The canonical API returns the complete request, catalog, flight-date,
|
||||||
|
reviewer and artifact provenance.
|
||||||
|
- An idempotent retry retained 637 Datasets and the same Dataset ID. A live
|
||||||
|
audit exposed that the applied-evidence timestamp could still be rewritten;
|
||||||
|
write-once validation was added, tested, redeployed and the evidence file
|
||||||
|
remained byte-identical on the next retry at SHA-256
|
||||||
|
`f4ae7b2315aed9370c8ca04dcb57eabf0c0e49c40cf63a862bb4e69bacd4b16d`.
|
||||||
|
- Browser validation at 1920x911 showed a correctly rendered MapLibre canvas,
|
||||||
|
coherent regional layer workflow, no horizontal overflow and no console
|
||||||
|
warnings or errors. A final live API audit found the generic source status
|
||||||
|
still selected a retained rolling label; source-freshness now prefers the
|
||||||
|
official edition without changing the 180-day review policy.
|
||||||
|
|
||||||
Next:
|
Next:
|
||||||
- Run complete readiness, deploy the packaged operator to Tower, inspect the
|
- Surface the existing release evidence read-only in the operator Status
|
||||||
live bounded Mol review preview and only then apply the first official
|
workspace so official/local edition, review identity, Dataset ID and hash
|
||||||
`2025.04` immutable baseline with explicit review evidence.
|
integrity can be inspected without shell access. Keep all refresh actions
|
||||||
|
operator-only and outside the browser.
|
||||||
|
|||||||
Reference in New Issue
Block a user