from __future__ import annotations from typing import Any from django.db.models import Avg, QuerySet from .models import AnalysisGridObservation, RemoteSensingRun, SoilLocation SATELLITE_METRIC_FIELDS = ( "ndvi", "ndwi", "lst_c", "soil_vv_db", "dem_m", "slope_deg", ) def build_location_satellite_snapshot( location: SoilLocation, *, block_code: str = "", ) -> dict[str, Any]: run = get_latest_completed_remote_sensing_run(location, block_code=block_code) if run is None: return { "status": "missing", "block_code": block_code, "run_id": None, "temporal_extent": None, "cell_count": 0, "resolved_metrics": {}, "metric_sources": {}, } observations = get_run_observations(run) summary = summarize_observations(observations) return { "status": "completed", "block_code": run.block_code, "run_id": run.id, "temporal_extent": { "start_date": run.temporal_start.isoformat() if run.temporal_start else None, "end_date": run.temporal_end.isoformat() if run.temporal_end else None, }, "cell_count": observations.count(), "resolved_metrics": summary, "metric_sources": { metric_name: "remote_sensing" for metric_name in summary }, } def build_location_block_satellite_snapshots(location: SoilLocation) -> list[dict[str, Any]]: block_layout = location.block_layout or {} blocks = block_layout.get("blocks") or [] if not blocks: return [build_location_satellite_snapshot(location)] snapshots = [] for block in blocks: snapshots.append( build_location_satellite_snapshot( location, block_code=str(block.get("block_code") or "").strip(), ) ) return snapshots def get_latest_completed_remote_sensing_run( location: SoilLocation, *, block_code: str = "", ) -> RemoteSensingRun | None: return ( RemoteSensingRun.objects.filter( soil_location=location, block_code=block_code or "", status=RemoteSensingRun.STATUS_SUCCESS, ) .order_by("-temporal_end", "-created_at", "-id") .first() ) def get_run_observations(run: RemoteSensingRun) -> QuerySet[AnalysisGridObservation]: return ( AnalysisGridObservation.objects.select_related("cell", "run") .filter( cell__soil_location=run.soil_location, cell__block_code=run.block_code or "", temporal_start=run.temporal_start, temporal_end=run.temporal_end, ) .order_by("cell__cell_code") ) def summarize_observations( observations: QuerySet[AnalysisGridObservation], ) -> dict[str, float]: aggregates = observations.aggregate( **{ f"{metric_name}_mean": Avg(metric_name) for metric_name in SATELLITE_METRIC_FIELDS } ) summary: dict[str, float] = {} for metric_name in SATELLITE_METRIC_FIELDS: value = aggregates.get(f"{metric_name}_mean") if value is None: continue summary[metric_name] = round(float(value), 6) return summary