mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 10:51:13 +00:00
[AZ-226] Add generated tile staging
Keep generated tiles auditable and untrusted onboard while preserving covariance, quality, and sidecar metadata for post-flight sync. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
from .interfaces import LocalTileManager, TileManager
|
||||
from .types import (
|
||||
CacheValidationReport,
|
||||
GeneratedTileCandidate,
|
||||
GeneratedTileSidecar,
|
||||
GeneratedTileSyncPackage,
|
||||
TileGenerationRequest,
|
||||
TileManifestEntry,
|
||||
TileMetadataLookup,
|
||||
TileValidationDecision,
|
||||
@@ -11,8 +15,12 @@ from .types import (
|
||||
|
||||
__all__ = [
|
||||
"CacheValidationReport",
|
||||
"GeneratedTileCandidate",
|
||||
"GeneratedTileSidecar",
|
||||
"GeneratedTileSyncPackage",
|
||||
"LocalTileManager",
|
||||
"TileManager",
|
||||
"TileGenerationRequest",
|
||||
"TileManifestEntry",
|
||||
"TileMetadataLookup",
|
||||
"TileValidationDecision",
|
||||
|
||||
@@ -8,7 +8,11 @@ from shared.errors import ErrorEnvelope
|
||||
|
||||
from .types import (
|
||||
CacheValidationReport,
|
||||
GeneratedTileCandidate,
|
||||
GeneratedTileSidecar,
|
||||
GeneratedTileSyncPackage,
|
||||
TileManifestEntry,
|
||||
TileGenerationRequest,
|
||||
TileMetadataLookup,
|
||||
TileValidationDecision,
|
||||
freshness_status,
|
||||
@@ -40,6 +44,7 @@ class LocalTileManager:
|
||||
self._trusted_by_tile_id: dict[str, CacheTileRecord] = {}
|
||||
self._descriptor_by_tile_id: dict[str, str] = {}
|
||||
self._tile_id_by_chunk_id: dict[str, str] = {}
|
||||
self._generated_candidates: list[GeneratedTileCandidate] = []
|
||||
|
||||
def validate_cache(self, entries: list[TileManifestEntry]) -> CacheValidationReport:
|
||||
if not self._postgis_available:
|
||||
@@ -102,6 +107,54 @@ class LocalTileManager:
|
||||
descriptor_ref=self._descriptor_by_tile_id[tile_id],
|
||||
)
|
||||
|
||||
def orthorectify_frame(self, request: TileGenerationRequest) -> GeneratedTileCandidate:
|
||||
if not request.frame_usable:
|
||||
return GeneratedTileCandidate(accepted=False, rejection_reason="frame_not_usable")
|
||||
if request.parent_covariance_m > 5.0:
|
||||
return GeneratedTileCandidate(accepted=False, rejection_reason="covariance_too_high")
|
||||
if request.quality_score < 0.25:
|
||||
return GeneratedTileCandidate(accepted=False, rejection_reason="quality_too_low")
|
||||
|
||||
trust_level = "generated" if request.parent_covariance_m <= 3.0 else "candidate"
|
||||
tile_id = f"generated-{request.mission_id}-{request.frame_id}"
|
||||
candidate = GeneratedTileCandidate(
|
||||
accepted=True,
|
||||
tile_id=tile_id,
|
||||
cog_ref=f"generated/{request.mission_id}/{tile_id}.cog.tif",
|
||||
sidecar=GeneratedTileSidecar(
|
||||
tile_id=tile_id,
|
||||
parent_frame_id=request.frame_id,
|
||||
parent_covariance_m=request.parent_covariance_m,
|
||||
quality_score=request.quality_score,
|
||||
trust_level=trust_level,
|
||||
provenance=request.source_provenance,
|
||||
),
|
||||
)
|
||||
self._generated_candidates.append(candidate)
|
||||
return candidate
|
||||
|
||||
def package_sync(self, mission_id: str) -> GeneratedTileSyncPackage:
|
||||
sidecars = tuple(
|
||||
candidate.sidecar
|
||||
for candidate in self._generated_candidates
|
||||
if candidate.sidecar is not None
|
||||
)
|
||||
manifest_delta = tuple(
|
||||
{
|
||||
"tile_id": sidecar.tile_id,
|
||||
"trust_level": sidecar.trust_level,
|
||||
"parent_covariance_m": sidecar.parent_covariance_m,
|
||||
"provenance": sidecar.provenance,
|
||||
}
|
||||
for sidecar in sidecars
|
||||
)
|
||||
return GeneratedTileSyncPackage(
|
||||
package_ref=f"generated/{mission_id}/sync-package.json",
|
||||
mission_id=mission_id,
|
||||
manifest_delta=manifest_delta,
|
||||
sidecars=sidecars,
|
||||
)
|
||||
|
||||
def _validate_entry(self, entry: TileManifestEntry) -> TileValidationDecision:
|
||||
if entry.signature_hash not in self._trusted_signature_hashes:
|
||||
return TileValidationDecision(
|
||||
|
||||
@@ -53,6 +53,42 @@ class TileMetadataLookup(TileManagerModel):
|
||||
error: ErrorEnvelope | None = None
|
||||
|
||||
|
||||
class TileGenerationRequest(TileManagerModel):
|
||||
mission_id: str = Field(min_length=1)
|
||||
frame_id: str = Field(min_length=1)
|
||||
image_ref: str = Field(min_length=1)
|
||||
timestamp_ns: int = Field(ge=0)
|
||||
parent_covariance_m: float = Field(ge=0.0)
|
||||
frame_usable: bool
|
||||
quality_score: float = Field(ge=0.0, le=1.0)
|
||||
footprint: dict[str, float]
|
||||
source_provenance: str = Field(min_length=1)
|
||||
|
||||
|
||||
class GeneratedTileSidecar(TileManagerModel):
|
||||
tile_id: str = Field(min_length=1)
|
||||
parent_frame_id: str = Field(min_length=1)
|
||||
parent_covariance_m: float = Field(ge=0.0)
|
||||
quality_score: float = Field(ge=0.0, le=1.0)
|
||||
trust_level: Literal["generated", "candidate"]
|
||||
provenance: str = Field(min_length=1)
|
||||
|
||||
|
||||
class GeneratedTileCandidate(TileManagerModel):
|
||||
accepted: bool
|
||||
tile_id: str | None = None
|
||||
cog_ref: str | None = None
|
||||
sidecar: GeneratedTileSidecar | None = None
|
||||
rejection_reason: str | None = None
|
||||
|
||||
|
||||
class GeneratedTileSyncPackage(TileManagerModel):
|
||||
package_ref: str = Field(min_length=1)
|
||||
mission_id: str = Field(min_length=1)
|
||||
manifest_delta: tuple[dict[str, object], ...]
|
||||
sidecars: tuple[GeneratedTileSidecar, ...]
|
||||
|
||||
|
||||
def freshness_status(expires_at: datetime, now: datetime) -> Literal["fresh", "stale"]:
|
||||
normalized_expiry = expires_at
|
||||
if normalized_expiry.tzinfo is None:
|
||||
|
||||
Reference in New Issue
Block a user