File size: 8,173 Bytes
43ea1a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37b38a7
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
import requests
from datetime import datetime, timedelta
import re

OPEN_METEO_FORECAST = "https://api.open-meteo.com/v1/forecast"
OPEN_METEO_ARCHIVE = "https://archive-api.open-meteo.com/v1/archive"

# Default testing coordinates for Chennai
DEFAULT_LAT = 13.0827
DEFAULT_LON = 80.2707


def _ensure_datetime(d):
    """Ensure d is a full datetime object, not just a date."""
    if d is None:
        return None
    if isinstance(d, datetime):
        return d
    return datetime(d.year, d.month, d.day)


def _infer_past_date_from_query(query: str):
    current_year = datetime.now().year
    m = re.search(r'\b(19\d{2}|20\d{2})\b', query.lower())
    if m:
        year = int(m.group(1))
        if year < current_year:
            now = datetime.utcnow()
            try:
                return datetime(year, now.month, now.day)
            except ValueError:
                return datetime(year, now.month, 28)
    return None


def _extract_all_past_years(query: str):
    current_year = datetime.now().year
    now = datetime.now()
    q = query.lower()
    seen = set()
    years_to_process = []

    range_matches = re.finditer(r'\b(19\d{2}|20\d{2})\s*(?:to|-|and)\s*(19\d{2}|20\d{2})\b', q)
    for m in range_matches:
        start_y = int(m.group(1))
        end_y = int(m.group(2))
        if start_y > end_y:
            start_y, end_y = end_y, start_y
        for y in range(start_y, end_y + 1):
            if y < current_year and y not in seen:
                seen.add(y)
                years_to_process.append(y)

    single_matches = re.finditer(r'\b(19\d{2}|20\d{2}|2[0-5])\b', q)
    for m in single_matches:
        val = m.group(1)
        y = 2000 + int(val) if len(val) == 2 else int(val)
        if y < current_year and y not in seen:
            seen.add(y)
            years_to_process.append(y)

    years_to_process = sorted(years_to_process, reverse=True)[:6]
    years_to_process.sort()

    past_dates = []
    for year in years_to_process:
        try:
            past_dates.append(datetime(year, now.month, now.day))
        except ValueError:
            past_dates.append(datetime(year, now.month, 28))
    return past_dates


def execute_plan(plan):
    """

    Executes the deterministic plan generated by the Planner.

    Routes to the correct external APIs or internal ML modules.

    """
    intent = plan.get("intent", "weather")
    all_intents = plan.get("all_intents", [intent])
    target_date = _ensure_datetime(plan.get("date"))
    ctx = plan.get("context", {})
    query = plan.get("query", "")

    # Fallback: infer past date from query year
    if target_date is None and intent in ["weather_history", "weather"]:
        inferred = _infer_past_date_from_query(query)
        if inferred:
            target_date = inferred
            intent = "weather_history"
            plan["intent"] = intent

    execution_result = {
        "weather": None,
        "forecast": None,
        "historical_weather": None,
        "historical_comparison": None,
        "cyclone": None,
        "earthquake": None,
        "tsunami": None,
        "models": None
    }

    try:
        from weather_service import (get_cyclones, get_earthquakes, get_tsunamis,
                                     get_weather, get_forecast, fetch_historical_weather)

        now = datetime.utcnow().date()

        # ── DISASTER ROUTE ────────────────────────────────────────────────────
        # Full report: always fetch weather + forecast + cyclones + earthquakes
        if "disaster" in all_intents:
            execution_result["weather"]    = get_weather()
            execution_result["forecast"]   = get_forecast()
            execution_result["cyclone"]    = get_cyclones()
            execution_result["earthquake"] = get_earthquakes()
            execution_result["tsunami"]    = get_tsunamis()
            # Don't return early β€” other intents below may add more data

        # ── COMPARISON ROUTE ──────────────────────────────────────────────────
        if intent == "weather_comparison" or ctx.get("wants_comparison"):
            execution_result["weather"] = get_weather()
            past_dates = _extract_all_past_years(query)

            if not past_dates and target_date:
                target_dt_only = target_date.date() if isinstance(target_date, datetime) else target_date
                if target_dt_only < now:
                    past_dates = [target_date]

            if past_dates:
                comparison_results = []
                for past_dt in past_dates:
                    past_date_only = past_dt.date()
                    archive_limit = datetime.utcnow().date() - timedelta(days=5)
                    if past_date_only <= archive_limit:
                        hist = fetch_historical_weather(past_dt, days_range=1)
                        if hist and "error" not in hist:
                            hist["queried_year"] = past_dt.year
                            hist["queried_date"] = past_dt.strftime("%Y-%m-%d")
                            comparison_results.append(hist)

                if comparison_results:
                    execution_result["historical_comparison"] = comparison_results
                    execution_result["historical_weather"] = comparison_results[0]

            execution_result["forecast"] = get_forecast()

        # ── STANDARD WEATHER / HISTORY / PREDICTION ROUTE ────────────────────
        elif intent in ["weather_history", "weather", "prediction"]:
            if target_date:
                target_date_only = target_date.date() if isinstance(target_date, datetime) else target_date
                if target_date_only < now:
                    execution_result["historical_weather"] = fetch_historical_weather(target_date, days_range=1)
                elif target_date_only > now and (target_date_only - now).days <= 7:
                    execution_result["forecast"] = get_forecast()

            target_date_only = target_date.date() if target_date and isinstance(target_date, datetime) else target_date
            if not target_date or target_date_only == now:
                execution_result["weather"] = get_weather()

            if not target_date and intent in ["weather", "prediction"]:
                execution_result["forecast"] = get_forecast()

        # ── CYCLONE ROUTE ─────────────────────────────────────────────────────
        if "cyclone" in all_intents and execution_result["cyclone"] is None:
            cy_name = ctx.get("cyclone_name")
            cy_year = ctx.get("year")
            c_data = get_cyclones(name=cy_name, year=cy_year)
            if ctx.get("wants_recent") and not cy_name:
                cyc_list = sorted(c_data.get("cyclones", []), key=lambda c: c["year"], reverse=True)[:3]
                c_data["cyclones"] = cyc_list
            execution_result["cyclone"] = c_data

        # ── EARTHQUAKE ROUTE ──────────────────────────────────────────────────
        if "earthquake" in all_intents and execution_result["earthquake"] is None:
            execution_result["earthquake"] = get_earthquakes()

        # ── TSUNAMI ROUTE ─────────────────────────────────────────────────────
        if "tsunami" in all_intents and execution_result["tsunami"] is None:
            execution_result["tsunami"] = get_tsunamis()

    except ImportError as e:
        print(f"Executor Import Error: {e}")

    return execution_result