Commit Β·
616bb9e
1
Parent(s): d6d17d4
Reorder layout, residual to 1h, scoreboard temp+humidity only
Browse files- Drop pressure from the scoreboard table (NWS doesn't forecast pressure
so there's nothing to compare).
- Residual chart now pinned to 1 h-ahead so it matches the first row of
the table.
- Move the scoreboard + residual section below the weekly forecast
chart, under a new 'π How has each model done so far?' heading. The
page now opens with the forecast story (hero β comparison β radar β
72h chart) and only after that asks 'and how well does it actually
do?'. The explainer accordions stay at the bottom.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app.py +8 -7
- src/weather_ui.py +1 -1
app.py
CHANGED
|
@@ -281,9 +281,9 @@ def refresh():
|
|
| 281 |
comparison_md = ""
|
| 282 |
scoreboard = render_scoreboard(log_conn)
|
| 283 |
|
| 284 |
-
# Residual chart β
|
| 285 |
-
# table
|
| 286 |
-
resid_df = forecast_log.residuals(log_conn, metric="temp_f", window_hours=48, lag_hours=
|
| 287 |
resid_fig = residual_figure(resid_df) if not resid_df.empty else None
|
| 288 |
|
| 289 |
persist.push_db_async()
|
|
@@ -292,10 +292,10 @@ def refresh():
|
|
| 292 |
|
| 293 |
# --- scoreboard ----------------------------------------------------------
|
| 294 |
SCOREBOARD_HORIZONS_H = [1, 3, 12]
|
|
|
|
| 295 |
SCOREBOARD_METRICS = [
|
| 296 |
("temp_f", "Temperature", "Β°F"),
|
| 297 |
("humidity", "Humidity", "%"),
|
| 298 |
-
("pressure_inhg", "Pressure", "inHg"),
|
| 299 |
]
|
| 300 |
|
| 301 |
|
|
@@ -440,12 +440,13 @@ with gr.Blocks(title="Toto Weather Forecast", theme=gr.themes.Soft()) as demo:
|
|
| 440 |
"<span style='opacity:0.55'>π Live data + forecast auto-refresh every 15 minutes.</span>"
|
| 441 |
)
|
| 442 |
|
| 443 |
-
scoreboard_md = gr.Markdown()
|
| 444 |
-
residual_plot = gr.Plot(label="Forecast residual")
|
| 445 |
-
|
| 446 |
gr.Markdown(f"### π
{VIEW_WEEK['label']}")
|
| 447 |
week_plot = gr.Plot(label="Weekly")
|
| 448 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
with gr.Accordion("How the scoreboard is calculated", open=False):
|
| 450 |
gr.Markdown(
|
| 451 |
"We score each model on **how close its prediction was to the actual Ecowitt reading** "
|
|
|
|
| 281 |
comparison_md = ""
|
| 282 |
scoreboard = render_scoreboard(log_conn)
|
| 283 |
|
| 284 |
+
# Residual chart β 1 h-ahead, matching the first row of the scoreboard
|
| 285 |
+
# table so the picture corresponds to the easiest headline number.
|
| 286 |
+
resid_df = forecast_log.residuals(log_conn, metric="temp_f", window_hours=48, lag_hours=1.0)
|
| 287 |
resid_fig = residual_figure(resid_df) if not resid_df.empty else None
|
| 288 |
|
| 289 |
persist.push_db_async()
|
|
|
|
| 292 |
|
| 293 |
# --- scoreboard ----------------------------------------------------------
|
| 294 |
SCOREBOARD_HORIZONS_H = [1, 3, 12]
|
| 295 |
+
# Pressure has no NWS counterpart, so the head-to-head scoreboard skips it.
|
| 296 |
SCOREBOARD_METRICS = [
|
| 297 |
("temp_f", "Temperature", "Β°F"),
|
| 298 |
("humidity", "Humidity", "%"),
|
|
|
|
| 299 |
]
|
| 300 |
|
| 301 |
|
|
|
|
| 440 |
"<span style='opacity:0.55'>π Live data + forecast auto-refresh every 15 minutes.</span>"
|
| 441 |
)
|
| 442 |
|
|
|
|
|
|
|
|
|
|
| 443 |
gr.Markdown(f"### π
{VIEW_WEEK['label']}")
|
| 444 |
week_plot = gr.Plot(label="Weekly")
|
| 445 |
|
| 446 |
+
gr.Markdown("### π How has each model done so far?")
|
| 447 |
+
scoreboard_md = gr.Markdown()
|
| 448 |
+
residual_plot = gr.Plot(label="Forecast residual")
|
| 449 |
+
|
| 450 |
with gr.Accordion("How the scoreboard is calculated", open=False):
|
| 451 |
gr.Markdown(
|
| 452 |
"We score each model on **how close its prediction was to the actual Ecowitt reading** "
|
src/weather_ui.py
CHANGED
|
@@ -183,7 +183,7 @@ def emoji_strip_markdown(nws_df: pd.DataFrame, tz: str, n: int = 12) -> str:
|
|
| 183 |
|
| 184 |
def residual_figure(
|
| 185 |
df: pd.DataFrame,
|
| 186 |
-
title: str = "Forecast residual β
|
| 187 |
) -> go.Figure:
|
| 188 |
"""Plot signed residuals over time for Toto and NWS. Zero is perfect."""
|
| 189 |
fig = go.Figure()
|
|
|
|
| 183 |
|
| 184 |
def residual_figure(
|
| 185 |
df: pd.DataFrame,
|
| 186 |
+
title: str = "Forecast residual β 1 h-ahead prediction minus Ecowitt actual, last 48 h (Β°F)",
|
| 187 |
) -> go.Figure:
|
| 188 |
"""Plot signed residuals over time for Toto and NWS. Zero is perfect."""
|
| 189 |
fig = go.Figure()
|