Add governed VMM flood hazard scenarios
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-15 22:14:12 +02:00
parent 6238b252a9
commit 501f824257
38 changed files with 2059 additions and 18 deletions
+66 -4
View File
@@ -21,6 +21,9 @@ from app.schemas.assistant import (
AssistantStatus,
AssistantTemporalSeries,
)
from app.schemas.flood_hazard import FloodHazardSelectionRequest
from app.services.flood_hazard_acquisition_service import FloodHazardAcquisitionService
from app.services.flood_hazard_analysis_service import FloodHazardAnalysisService
from app.services.vector_feature_service import VectorFeatureService
@@ -252,16 +255,22 @@ class GeoAssistantService:
db.query(Dataset)
.filter(Dataset.project_id == project_id)
.filter(Dataset.status == "ready")
.filter(Dataset.dataset_type.in_(["vector", "geojson"]))
.all()
)
vector_datasets = [dataset for dataset in datasets if dataset.dataset_type in {"vector", "geojson"}]
flood_hazard_datasets = [
dataset
for dataset in datasets
if dataset.dataset_type == "raster" and dataset.source_name == FloodHazardAcquisitionService.PROVIDER
and (area is None or dataset.area_id is None or dataset.area_id == area.id)
]
warnings: list[str] = []
context_metrics: list[AssistantContextMetric] = []
source_dataset_ids: list[UUID] = []
current_context: list[dict[str, Any]] = []
if bbox is not None:
for dataset in self._current_datasets(datasets):
for dataset in self._current_datasets(vector_datasets):
kwargs: dict[str, Any] = {"dataset": dataset, "bbox": bbox}
if area is not None:
kwargs["selection_geometry"] = area.geometry
@@ -314,10 +323,60 @@ class GeoAssistantService:
}
)
for dataset in sorted(
flood_hazard_datasets,
key=lambda item: str((item.source_metadata or {}).get("product_key") or item.name),
):
try:
result = FloodHazardAnalysisService.analyze(
db,
project_id,
dataset.id,
FloodHazardSelectionRequest(bbox=bbox, area_id=area.id if area is not None else None),
settings=self.settings,
)
except AppError as exc:
warnings.append(f"{dataset.name}: {exc.message}")
continue
metadata = dataset.source_metadata if isinstance(dataset.source_metadata, dict) else {}
scenario_label = str(metadata.get("product_display_name") or result["product_key"])
serialized_metrics: list[dict[str, Any]] = []
for metric in result["summary"]["metrics"]:
item = AssistantContextMetric(
theme="flood_hazard",
label=f"{metric['metric_label']} - {scenario_label}",
value=float(metric["metric_value"]),
unit=str(metric["metric_unit"]),
source=FloodHazardAcquisitionService.ATTRIBUTION,
dataset_id=dataset.id,
is_estimate=False,
)
context_metrics.append(item)
serialized_metrics.append(item.model_dump(mode="json"))
serialized_metrics[-1]["measurement_quality"] = "exacte_berekening_binnen_gemodelleerd_scenario"
source_dataset_ids.append(dataset.id)
current_context.append(
{
"dataset_name": dataset.name,
"dataset_id": str(dataset.id),
"theme": "flood_hazard",
"source": FloodHazardAcquisitionService.ATTRIBUTION,
"scenario": {
"label": scenario_label,
"mechanism": result["mechanism"],
"climate_context": result["climate_context"],
"probability_class": result["probability_class"],
"return_period_years": result["return_period_years"],
},
"metrics": serialized_metrics,
"warning": result["limitation_message"],
}
)
temporal_series: list[AssistantTemporalSeries] = []
temporal_context: list[dict[str, Any]] = []
include_history = self.history_requested(payload.question)
for key, observations in self._series(datasets):
for key, observations in self._series(vector_datasets):
first = observations[0]
last = observations[-1]
source_metadata = last.source_metadata if isinstance(last.source_metadata, dict) else {}
@@ -364,7 +423,9 @@ class GeoAssistantService:
"available_temporal_series": temporal_context,
"rules": {
"water_volume_available": False,
"water_volume_reason": "Geen gebiedsdekkende waterdiepte of bathymetrie gekoppeld.",
"water_volume_reason": "Geen bathymetrie gekoppeld voor de permanente inhoud van waterlichamen.",
"flood_hazard_scenarios_available": bool(flood_hazard_datasets),
"flood_depth_area_integral_is_concurrent_volume": False,
"object_counts_are_supporting_metrics": True,
"causal_explanations_available": False,
"forecast_available": False,
@@ -402,6 +463,7 @@ class GeoAssistantService:
"Neem waarden en jaren letterlijk over en bereken zelf geen gemiddelde, tempo, oorzaak of afgeleide trend. "
"Gebruik platte tekst met korte alinea's en opsommingen, zonder Markdown-symbolen. "
"Bereken of suggereer nooit watervolume zonder gekoppelde diepte of bathymetrie. "
"Noem de VMM-diepte-oppervlakte-integraal nooit een werkelijk, permanent of gelijktijdig watervolume. "
"Als de gevraagde informatie niet in de context staat, zeg precies welke bron of meting ontbreekt. "
"CONTEXT_JSON:\n" + json.dumps(context, ensure_ascii=False, separators=(",", ":"))
)