File size: 8,069 Bytes
5194558
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
"""Gradio dashboard assembly."""

from __future__ import annotations

import gradio as gr
import pandas as pd
import plotly.graph_objects as go

from weatherpred.agent import make_gemini_brief, run_weather_agent
from weatherpred.modeling import build_weather_bundle
from weatherpred.zerogpu import spaces

def make_line_plot(
    comparison: pd.DataFrame,
    title: str,
    yaxis_title: str,
    series: list[tuple[str, str, str]],
) -> go.Figure:
    fig = go.Figure()
    for column, name, color in series:
        fig.add_trace(
            go.Scatter(
                x=comparison["date"],
                y=comparison[column],
                mode="lines+markers",
                name=name,
                line={"color": color, "width": 3},
            )
        )
    fig.update_layout(
        title=title,
        xaxis_title="Date",
        yaxis_title=yaxis_title,
        hovermode="x unified",
        template="plotly_white",
        legend={"orientation": "h", "y": -0.25},
        margin={"l": 40, "r": 20, "t": 60, "b": 70},
    )
    return fig


def make_temperature_plot(comparison: pd.DataFrame) -> go.Figure:
    return make_line_plot(
        comparison,
        "Five-Day Temperature Forecast",
        "Temperature (C)",
        [
            ("api_high_c", "Open-Meteo high", "#0f8b8d"),
            ("model_high_c", "ONNX model high", "#e05d3d"),
            ("api_low_c", "Open-Meteo low", "#74b6b7"),
            ("model_low_c", "ONNX model low", "#f0a08c"),
        ],
    )


def make_rain_plot(comparison: pd.DataFrame) -> go.Figure:
    return make_line_plot(
        comparison,
        "Five-Day Rain Forecast",
        "Rain (mm)",
        [
            ("api_rain_mm", "Open-Meteo rain", "#0f8b8d"),
            ("model_rain_mm", "Model rain proxy", "#e05d3d"),
        ],
    )


def make_wind_plot(comparison: pd.DataFrame) -> go.Figure:
    return make_line_plot(
        comparison,
        "Five-Day Wind Forecast",
        "Wind (km/h)",
        [
            ("api_wind_kmh", "Open-Meteo wind", "#0f8b8d"),
            ("model_wind_kmh", "Historical model wind", "#e05d3d"),
        ],
    )


@spaces.GPU(duration=10)
def zero_gpu_probe() -> str:
    return "ZeroGPU compatibility probe is available."


def make_metrics(summary: dict) -> str:
    return (
        f"Historical rows: {summary['history_rows']:,}\n"
        f"History window: {summary['history_start']} to {summary['history_end']}\n"
        f"Average model confidence proxy: {summary['avg_model_confidence']:.0%}\n"
        f"Open-Meteo max high: {summary['max_api_high_c']:.1f}C\n"
        f"ONNX model max high: {summary['max_model_high_c']:.1f}C"
    )


def render_all_charts(comparison: pd.DataFrame) -> tuple:
    return (
        gr.update(value=make_temperature_plot(comparison), visible=True),
        gr.update(value=make_rain_plot(comparison), visible=True),
        gr.update(value=make_wind_plot(comparison), visible=True),
    )


def refresh_dashboard(force_refresh_history: bool) -> tuple:
    bundle = build_weather_bundle(force_refresh_history=force_refresh_history)
    comparison = bundle.comparison.copy()
    summary = bundle.summary
    brief = make_gemini_brief(comparison, summary)
    return (
        comparison,
        "all",
        make_temperature_plot(comparison),
        make_rain_plot(comparison),
        make_wind_plot(comparison),
        make_metrics(summary),
        brief,
        [{"role": "assistant", "content": "Dashboard refreshed. Showing temperature, rain, and wind forecasts."}],
    )


def parse_chart_selection(message: str) -> tuple[str, str]:
    text = message.lower()
    wants_temperature = any(word in text for word in ["temperature", "temp", "hot", "cold", "high", "low"])
    wants_rain = any(word in text for word in ["rain", "precipitation", "precip", "shower"])
    wants_wind = "wind" in text
    wants_all = any(word in text for word in ["all", "everything", "reset", "both"])

    if wants_all or sum([wants_temperature, wants_rain, wants_wind]) > 1:
        return "all", "Showing all three charts in the fixed 2x2 layout."
    if wants_temperature:
        return "temperature", "Temperature is shown in the top-left chart."
    if wants_rain:
        return "rain", "Rain is shown in the top-right chart."
    if wants_wind:
        return "wind", "Wind is shown in the bottom-left chart, including the historical model wind prediction."
    return "all", "I can point you to temperature, rain, wind, or all charts in the 2x2 dashboard."


def chat_with_dashboard(message: str, history: list, comparison: pd.DataFrame) -> tuple:
    if comparison is None or getattr(comparison, "empty", True):
        comparison = build_weather_bundle(force_refresh_history=False).comparison.copy()

    if not message.strip():
        return (history or [], "all", comparison, *render_all_charts(comparison), "")

    agent_run = run_weather_agent(message)
    next_comparison = agent_run.comparison if agent_run.comparison is not None else comparison
    charts = render_all_charts(next_comparison)
    history = history or []
    history.append({"role": "user", "content": message})
    history.append({"role": "assistant", "content": agent_run.response})
    selection = agent_run.action.get("variable", "all")
    return (history, selection, next_comparison, *charts, agent_run.response)


with gr.Blocks(title="Tokyo Weather Forecast Lab") as demo:
    gr.Markdown(
        """
        # Tokyo Weather Forecast Lab

        Compare Open-Meteo's five-day forecast with a local ONNX model using
        cached 30-year Tokyo weather history. Use the chat panel to show
        temperature, rain, wind, or all forecast charts in the fixed 2x2 layout.
        """
    )
    forecast_state = gr.State(pd.DataFrame())
    chart_selection = gr.State("all")

    with gr.Row():
        refresh = gr.Button("Refresh forecast", variant="primary")
        force_history = gr.Checkbox(
            label="Refresh 30-year historical cache",
            value=False,
        )

    with gr.Row(visible=False):
        gpu_probe_button = gr.Button("ZeroGPU probe")
        gpu_probe_output = gr.Textbox(label="ZeroGPU probe")

    with gr.Row():
        with gr.Column(scale=1):
            temperature_plot = gr.Plot(label="Temperature")
        with gr.Column(scale=1):
            rain_plot = gr.Plot(label="Rain")

    with gr.Row():
        with gr.Column(scale=1):
            wind_plot = gr.Plot(label="Wind")
        with gr.Column(scale=1):
            gr.Markdown("## Forecast Chat")
            chatbot = gr.Chatbot(label="Dashboard assistant", height=360)
            chat_input = gr.Textbox(
                label="Ask the dashboard",
                placeholder="Try: show rain, show wind, show temperature, show all charts",
            )

    with gr.Row():
        metrics_box = gr.Textbox(label="Data and model metrics", lines=6)
        brief_box = gr.Textbox(label="AI analyst brief", lines=6)

    gpu_probe_button.click(
        zero_gpu_probe,
        inputs=None,
        outputs=gpu_probe_output,
    )

    refresh.click(
        refresh_dashboard,
        inputs=[force_history],
        outputs=[
            forecast_state,
            chart_selection,
            temperature_plot,
            rain_plot,
            wind_plot,
            metrics_box,
            brief_box,
            chatbot,
        ],
    )
    demo.load(
        refresh_dashboard,
        inputs=[force_history],
        outputs=[
            forecast_state,
            chart_selection,
            temperature_plot,
            rain_plot,
            wind_plot,
            metrics_box,
            brief_box,
            chatbot,
        ],
    )
    chat_input.submit(
        chat_with_dashboard,
        inputs=[chat_input, chatbot, forecast_state],
        outputs=[
            chatbot,
            chart_selection,
            forecast_state,
            temperature_plot,
            rain_plot,
            wind_plot,
            brief_box,
        ],
    ).then(lambda: "", outputs=chat_input)