File size: 4,195 Bytes
207961e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Known-good GEE snippets when the AI emits invalid index method names.
"""
import re
from typing import Dict, Optional, Tuple

# Common typos / hallucinated ee.Image methods → normalizedDifference bands
_METHOD_FIXES: Tuple[Tuple[re.Pattern, str], ...] = (
    # AI often invents this when user types "NBI" (built-up) — not NBR burn
    (re.compile(r"\.normalizedBurnIndex\s*\(\s*\)", re.I), ".normalizedDifference(['B11', 'B8'])"),
    (re.compile(r"\.normalizedBuiltUpIndex\s*\(\s*\)", re.I), ".normalizedDifference(['B11', 'B8'])"),
    (re.compile(r"\.normalizedDifferenceBuiltUpIndex\s*\(\s*\)", re.I), ".normalizedDifference(['B11', 'B8'])"),
    (re.compile(r"\.normalizedBuiltUp\s*\(\s*\)", re.I), ".normalizedDifference(['B11', 'B8'])"),
    (re.compile(r"\.nbi\s*\(\s*\)", re.I), ".normalizedDifference(['B11', 'B8'])"),
    (re.compile(r"\.ndbi\s*\(\s*\)", re.I), ".normalizedDifference(['B11', 'B8'])"),
)

_REGIONS: Tuple[Tuple[str, str], ...] = (
    ("andhra pradesh", "Andhra Pradesh"),
    ("tamil nadu", "Tamil Nadu"),
    ("uttar pradesh", "Uttar Pradesh"),
    ("madhya pradesh", "Madhya Pradesh"),
    ("west bengal", "West Bengal"),
    ("karnataka", "Karnataka"),
    ("maharashtra", "Maharashtra"),
    ("kerala", "Kerala"),
    ("goa", "Goa"),
    ("india", "India"),
    ("rajasthan", "Rajasthan"),
    ("gujarat", "Gujarat"),
    ("punjab", "Punjab"),
    ("bihar", "Bihar"),
    ("assam", "Assam"),
)


def sanitize_ai_gee_code(code: str) -> str:
    """Rewrite hallucinated index helpers to valid normalizedDifference calls."""
    sanitized = code
    for pattern, replacement in _METHOD_FIXES:
        sanitized = pattern.sub(replacement, sanitized)
    return sanitized


def _detect_region(message: str) -> str:
    msg = message.lower()
    for needle, label in _REGIONS:
        if needle in msg:
            return label
    return "India"


def _region_fc_snippet(region_name: str) -> str:
    if region_name == "India":
        return (
            'ee.FeatureCollection("FAO/GAUL/2015/level0")'
            '.filter(ee.Filter.eq("ADM0_NAME", "India"))'
        )
    return (
        'ee.FeatureCollection("FAO/GAUL/2015/level1")'
        f'.filter(ee.Filter.eq("ADM1_NAME", "{region_name}"))'
    )


def build_chat_gee_fallback(user_message: str) -> Optional[Tuple[str, Dict]]:
    """
    Return (gee_code, vis_params) for common chat requests when AI code fails.
    """
    msg = user_message.lower()
    region = _detect_region(user_message)
    region_fc = _region_fc_snippet(region)
    s2 = (
        "ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')"
        ".filterBounds(region)"
        ".filterDate('2024-03-01','2024-04-01')"
        ".filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))"
        ".mean()"
    )

    # NBI / NDBI — built-up index (user often types "NBI" without the D)
    if re.search(r"\b(ndbi|nbi|built[\s-]?up)\b", msg) and not re.search(
        r"\b(nbr|burn|wildfire)\b", msg
    ):
        code = f"""region = {region_fc}
image = {s2}.normalizedDifference(['B11','B8']).clipToCollection(region)"""
        vis = {"min": -0.2, "max": 0.6, "palette": ["#0000ff", "#ffff00", "#ff0000"]}
        return code, vis

    if re.search(r"\bndvi\b", msg):
        code = f"""region = {region_fc}
image = {s2}.normalizedDifference(['B8','B4']).clipToCollection(region)"""
        vis = {"min": -0.1, "max": 0.9, "palette": ["#d73027", "#fee08b", "#1a9850"]}
        return code, vis

    if re.search(r"\bndwi\b", msg):
        code = f"""region = {region_fc}
image = {s2}.normalizedDifference(['B3','B8']).clipToCollection(region)"""
        vis = {"min": -0.3, "max": 0.5, "palette": ["#ffffcc", "#a1dab4", "#41b6c4", "#2c7fb8", "#253494"]}
        return code, vis

    if re.search(r"\bsentinel[\s-]?1\b|\bsar\b", msg):
        code = f"""region = {region_fc}
image = ee.ImageCollection('COPERNICUS/S1_GRD').filterBounds(region).filter(ee.Filter.eq('instrumentMode', 'IW')).filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV')).filterDate('2024-03-01','2024-04-01').select('VV').mean().clipToCollection(region)"""
        vis = {"min": -25, "max": 0}
        return code, vis

    return None