File size: 7,969 Bytes
c590d67
 
 
 
 
 
 
 
 
338ebb6
 
 
 
 
 
 
 
 
 
 
c590d67
338ebb6
c590d67
 
 
 
 
 
 
 
 
 
 
 
338ebb6
 
c590d67
338ebb6
c590d67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338ebb6
 
 
 
 
 
c590d67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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.",
    }