mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 16:31:15 +00:00
[AZ-488] UAV tile batch upload + 5-rule quality gate
Replaces the 501 stub at POST /api/satellite/upload with a multipart
batch endpoint that ingests UAV-captured tiles, runs each item through
a 5-rule quality gate, and persists accepted tiles via the AZ-484
multi-source storage path with source='uav'.
Quality gate (in fixed order, first failure wins): JPEG format
(content-type + magic), size band 5 KiB-5 MiB, exact 256x256
dimensions, captured-at age (no future >30 s skew, no older than
7 days), luminance variance on 32x32 downsample. Closed reject-reason
enumeration in v1.0.0 contract.
Authorization: custom PermissionsRequirement / PermissionsAuthorization
Handler that reads the JWT `permissions` claim (tolerates both
repeated-string and JSON-array shapes). Endpoint protected by
RequiresGpsPermission policy; 401 without token, 403 without GPS perm.
Persistence: file-first to ./tiles/uav/{z}/{x}/{y}.jpg, then
ITileRepository.InsertAsync UPSERT (per-source UPSERT contract from
AZ-484). Per-item failures reported in response without aborting the
batch. Kestrel MaxRequestBodySize and FormOptions limits set to
MaxBatchSize x MaxBytes (default 100 x 5 MiB = 500 MiB).
New frozen contract: _docs/02_document/contracts/api/uav-tile-upload.md
v1.0.0. PT-08 NFR added to performance-tests.md as Deferred (harness
work tracked in PT-07 leftover, per AZ-488 § Risk 4).
Tests: 11 quality-gate unit tests, 5 handler unit tests, 3 file-path
unit tests, 12 permission-handler unit tests, 7 integration tests
(AC-1..AC-6, AC-8). All 253 unit tests + smoke integration suite
green.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,15 +2,15 @@
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Acquires satellite imagery tiles from Google Maps, stores them on disk, and persists metadata to the database. Handles session tokens, concurrent downloads, retry logic, and tile deduplication.
|
||||
**Purpose**: Acquires satellite imagery tiles from Google Maps, stores them on disk, and persists metadata to the database. Handles session tokens, concurrent downloads, retry logic, and tile deduplication. Since AZ-488 it also hosts the UAV upload pipeline: the `UavTileQualityGate` 5-rule validator and the `UavTileUploadHandler` that persists `source='uav'` rows via `ITileRepository.InsertAsync`.
|
||||
|
||||
**Architectural Pattern**: Service + Gateway (wraps external API with retry/throttling)
|
||||
**Architectural Pattern**: Service + Gateway (wraps external API with retry/throttling) + per-source quality gate (UAV upload path)
|
||||
|
||||
**csproj**: `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` (split out of the monolithic `SatelliteProvider.Services` project in epic AZ-309)
|
||||
|
||||
**Upstream dependencies**: Common (DTOs, Enums — `TileSource` + `TileSourceConverter` since AZ-484, GeoUtils, configs, RateLimitException), DataAccess (TileEntity, ITileRepository)
|
||||
**Upstream dependencies**: Common (DTOs, Enums — `TileSource` + `TileSourceConverter` since AZ-484, plus `UavTileMetadata` / `UavTileBatchUploadResponse` / `UavTileRejectReasons` since AZ-488; GeoUtils; configs `MapConfig`, `StorageConfig`, `ProcessingConfig`, `UavQualityConfig`; RateLimitException), DataAccess (TileEntity, ITileRepository), SixLabors.ImageSharp 3.1.11 (UAV decode + variance check).
|
||||
|
||||
**Downstream consumers**: RegionProcessing and WebApi — both via `ITileService` from Common (no compile-time `ProjectReference` from any consumer to this project's concrete types).
|
||||
**Downstream consumers**: RegionProcessing and WebApi — both via `ITileService` from Common; WebApi also resolves `IUavTileQualityGate` and `IUavTileUploadHandler` from this component (DI only — no compile-time `ProjectReference` from any consumer to this project's concrete types).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
@@ -29,6 +29,20 @@
|
||||
| `GetOrDownloadTileAsync` (AZ-310) | z, x, y, CancellationToken | `TileBytes` | Yes | propagated from downloader |
|
||||
| `DownloadAndStoreSingleTileAsync` (AZ-311) | lat, lon, zoom, CancellationToken | `TileMetadata` | Yes | propagated from downloader |
|
||||
|
||||
### Service: UavTileQualityGate (implements IUavTileQualityGate, AZ-488)
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `Validate` | imageBytes, contentType, `UavTileMetadata` | `UavTileQualityResult` (accept + reason code) | No | none (decode exceptions caught and translated to `INVALID_FORMAT`) |
|
||||
|
||||
Rules run in fixed order (Format → Size band → Dimensions → Captured-at age → Blank/uniform); first failure short-circuits. Thresholds come from `UavQualityConfig`. Time comes from injected `TimeProvider` (defaults to `TimeProvider.System`) for deterministic tests.
|
||||
|
||||
### Service: UavTileUploadHandler (implements IUavTileUploadHandler, AZ-488)
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `HandleAsync` | `metadataJson`, `IReadOnlyList<UavUploadFile>`, CancellationToken | `UavTileUploadHandlerResult` (envelope error OR per-item response) | Yes | propagated `IOException`/`UnauthorizedAccessException` per item, translated to per-item `STORAGE_FAILURE` |
|
||||
|
||||
Per-item flow: parse metadata JSON → reject envelope (mismatch, oversize, malformed JSON) OR run each item through `IUavTileQualityGate` → for accepted items, write JPEG to `{StorageConfig.TilesDirectory}/uav/{zoom}/{x}/{y}.jpg` then call `ITileRepository.InsertAsync` (per-source UPSERT) with `source='uav'` and the request-supplied `capturedAt`. File-before-row ordering keeps an orphan file (rather than a row pointing at nothing) when persistence fails.
|
||||
|
||||
## 4. Data Access Patterns
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
Reference in New Issue
Block a user