| from __future__ import annotations |
|
|
| from copy import deepcopy |
| from typing import Any |
|
|
| from .evidence import make_evidence |
| from .models import EvidenceItem |
|
|
|
|
| CHORWAD_SAMPLE_LAT = 21.00248 |
| CHORWAD_SAMPLE_LON = 70.24537 |
|
|
|
|
| def is_sample_site( |
| project_name: str, |
| site_name: str, |
| boundary_source: str, |
| anchor_lat: float | None = None, |
| anchor_lon: float | None = None, |
| ) -> bool: |
| text = " ".join([project_name or "", site_name or "", boundary_source or ""]).lower() |
| return ("chorwad" in text and "sample" in text) or _near_chorwad_sample(anchor_lat, anchor_lon) |
|
|
|
|
| def apply_chorwad_sample_fallbacks( |
| *, |
| project_name: str, |
| site_name: str, |
| boundary_source: str, |
| site_identity: dict[str, Any] | None, |
| climate: dict[str, Any], |
| osm_context: dict[str, Any], |
| topography: dict[str, Any], |
| soil: dict[str, Any], |
| anchor_lat: float | None = None, |
| anchor_lon: float | None = None, |
| ) -> tuple[dict[str, Any] | None, dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], list[EvidenceItem], list[str]]: |
| if not is_sample_site(project_name, site_name, boundary_source, anchor_lat, anchor_lon): |
| return site_identity, climate, osm_context, topography, soil, [], [] |
|
|
| evidence: list[EvidenceItem] = [] |
| warnings: list[str] = [] |
| used_layers: list[str] = [] |
| if _missing_identity(site_identity): |
| site_identity = _sample_identity() |
| used_layers.append("site identity") |
| if _missing_climate(climate): |
| climate = _sample_climate() |
| used_layers.append("climate") |
| if not (osm_context.get("counts") or osm_context.get("features")): |
| osm_context = _sample_osm_context() |
| used_layers.append("context") |
| if not topography: |
| topography = _sample_topography() |
| used_layers.append("topography") |
| if not soil: |
| soil = _sample_soil() |
| used_layers.append("soil") |
| if not used_layers: |
| return site_identity, climate, osm_context, topography, soil, [], [] |
|
|
| warnings.append( |
| "Chorwad sample fallback data was used for " |
| + ", ".join(used_layers) |
| + " because one or more live public-data calls were unavailable. Use this only for demo/testing, not final project evidence." |
| ) |
| evidence.append( |
| make_evidence( |
| category="Demo fallback", |
| finding="Bundled Chorwad sample fallback data was used to keep the judge demo complete when live APIs are unavailable.", |
| source_name="Bundled sample fallback", |
| source_url="", |
| source_type="demo fixture", |
| resolution_or_scope=", ".join(used_layers), |
| confidence="low", |
| limitation="This fallback is not live public data and must not be used as final site evidence.", |
| design_implication="Use the demo to understand workflow and output structure; rerun with live data or verified uploads for actual work.", |
| verification_needed="Replace with live API results, CAD/KML/GeoJSON, site photos, and site visit observations.", |
| output_label="site_visit_required", |
| ) |
| ) |
| return site_identity, climate, osm_context, topography, soil, evidence, warnings |
|
|
|
|
| def _near_chorwad_sample(anchor_lat: float | None, anchor_lon: float | None) -> bool: |
| if anchor_lat is None or anchor_lon is None: |
| return False |
| return abs(anchor_lat - CHORWAD_SAMPLE_LAT) <= 0.01 and abs(anchor_lon - CHORWAD_SAMPLE_LON) <= 0.01 |
|
|
|
|
| def _missing_climate(climate: dict[str, Any]) -> bool: |
| for key in ("forecast", "recent_historical", "climate_normal"): |
| value = climate.get(key) |
| if isinstance(value, dict) and value: |
| return False |
| return True |
|
|
|
|
| def _missing_identity(site_identity: dict[str, Any] | None) -> bool: |
| if not site_identity: |
| return True |
| return not any( |
| site_identity.get(key) |
| for key in ("display_name", "city", "town", "village", "district", "state", "country") |
| ) |
|
|
|
|
| def _sample_identity() -> dict[str, Any]: |
| return { |
| "display_name": "Malia Taluka, Junagadh, Gujarat, India", |
| "district": "Junagadh", |
| "state": "Gujarat", |
| "country": "India", |
| "postcode": "362250", |
| } |
|
|
|
|
| def _sample_climate() -> dict[str, Any]: |
| months = [ |
| (1, 21.8, 0.3), |
| (2, 23.4, 0.0), |
| (3, 25.9, 0.0), |
| (4, 27.8, 1.9), |
| (5, 29.3, 11.5), |
| (6, 29.1, 187.4), |
| (7, 27.5, 380.2), |
| (8, 26.8, 224.4), |
| (9, 26.9, 193.7), |
| (10, 27.4, 65.9), |
| (11, 25.7, 5.3), |
| (12, 23.2, 4.8), |
| ] |
| month_rows = [ |
| {"month": month, "temperature_c": temp, "precipitation_mm": rain} |
| for month, temp, rain in months |
| ] |
| total_rain = round(sum(row["precipitation_mm"] for row in month_rows), 1) |
| return { |
| "forecast": { |
| "current_temperature_c": 31.5, |
| "current_humidity_pct": 68, |
| "current_wind_speed_kmh": 23.1, |
| "current_wind_direction_deg": 235, |
| }, |
| "recent_historical": { |
| "period": "cached Chorwad sample derived from a previous successful public-data run", |
| "months": deepcopy(month_rows), |
| "total_precipitation_mm": total_rain, |
| }, |
| "climate_normal": { |
| "period": "cached 10-year style Chorwad sample for demo fallback only", |
| "months": month_rows, |
| "total_precipitation_mm": total_rain, |
| }, |
| } |
|
|
|
|
| def _sample_osm_context() -> dict[str, Any]: |
| return { |
| "counts": {"water": 3, "roads/access": 1, "landuse:industrial": 1, "buildings": 2}, |
| "radius_m": 500, |
| "features": [ |
| { |
| "type": "way", |
| "tags": {"highway": "residential", "name": "sample access road"}, |
| "geometry": [ |
| {"lat": 21.00170, "lon": 70.24480}, |
| {"lat": 21.00250, "lon": 70.24515}, |
| {"lat": 21.00320, "lon": 70.24555}, |
| ], |
| }, |
| { |
| "type": "way", |
| "tags": {"natural": "water"}, |
| "geometry": [ |
| {"lat": 21.00160, "lon": 70.24365}, |
| {"lat": 21.00335, "lon": 70.24365}, |
| {"lat": 21.00335, "lon": 70.24430}, |
| {"lat": 21.00160, "lon": 70.24430}, |
| {"lat": 21.00160, "lon": 70.24365}, |
| ], |
| }, |
| { |
| "type": "way", |
| "tags": {"landuse": "industrial"}, |
| "geometry": [ |
| {"lat": 21.00295, "lon": 70.24615}, |
| {"lat": 21.00345, "lon": 70.24615}, |
| {"lat": 21.00345, "lon": 70.24670}, |
| {"lat": 21.00295, "lon": 70.24670}, |
| {"lat": 21.00295, "lon": 70.24615}, |
| ], |
| }, |
| { |
| "type": "way", |
| "tags": {"building": "yes"}, |
| "geometry": [ |
| {"lat": 21.00205, "lon": 70.24615}, |
| {"lat": 21.00225, "lon": 70.24615}, |
| {"lat": 21.00225, "lon": 70.24635}, |
| {"lat": 21.00205, "lon": 70.24635}, |
| {"lat": 21.00205, "lon": 70.24615}, |
| ], |
| }, |
| ], |
| } |
|
|
|
|
| def _sample_topography() -> dict[str, Any]: |
| return { |
| "mean_elevation_m": 5.1, |
| "relief_m": 2.0, |
| "approx_slope_pct": 0.98, |
| "interpretation": "Cached sample suggests low relief; verify contours, drainage, and waterlogging on site.", |
| } |
|
|
|
|
| def _sample_soil() -> dict[str, Any]: |
| return { |
| "texture_signal": "mixed or uncertain topsoil signal", |
| "clay_pct": None, |
| "sand_pct": None, |
| "silt_pct": None, |
| "ph_h2o": None, |
| "design_implication": "Use only as a prompt to request local soil/geotechnical verification.", |
| } |
|
|