bitsofchris Claude Opus 4.7 (1M context) commited on
Commit
26b0dfb
·
1 Parent(s): 24b9903

Drop hero gauge plot; hero table shows 1 decimal for forecasts

Browse files

- Remove hero_gauges and its wiring. Gauge visual didn't earn its
vertical space at any breakpoint vs the existing 3-row hero table.
- hero_markdown forecast rows now show one decimal place (e.g. 62.3°F)
instead of rounding to integer, to match the current-temperature row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +2 -28
  2. src/weather_ui.py +1 -79
app.py CHANGED
@@ -22,7 +22,6 @@ from src.forecast import forecast_series
22
  from src.weather_ui import (
23
  aligned_comparison_markdown,
24
  combined_figure,
25
- hero_gauges,
26
  hero_markdown,
27
  residual_figure,
28
  )
@@ -275,30 +274,6 @@ def refresh():
275
  nws_temp=week["nws_aligned"].get("temp_f"),
276
  horizon_hours=1,
277
  )
278
-
279
- # Gauge trio above the hero table: current Ecowitt + each model's
280
- # next-round-hour prediction.
281
- cur_temp_for_gauge = (
282
- float(realtime["temp_f"]) if realtime and realtime.get("temp_f") is not None
283
- else float("nan")
284
- )
285
- next_hour_utc = pd.Timestamp.now(tz="UTC").ceil("h")
286
-
287
- def _nearest_value(series, target):
288
- if series is None or series.empty:
289
- return None
290
- idx = series.index.get_indexer([target], method="nearest")[0]
291
- if idx < 0 or idx >= len(series):
292
- return None
293
- return float(series.iloc[idx])
294
-
295
- toto_temp_fcst = week["totos"].get("temp_f")
296
- toto_next = _nearest_value(
297
- toto_temp_fcst.median if toto_temp_fcst is not None else None,
298
- next_hour_utc,
299
- )
300
- nws_next = _nearest_value(week["nws_aligned"].get("temp_f"), next_hour_utc)
301
- gauge_fig = hero_gauges(cur_temp_for_gauge, toto_next, nws_next)
302
  if "temp_f" in week["totos"]:
303
  comparison_md = (
304
  "### 🆚 Toto vs NWS — same hour, side-by-side\n\n"
@@ -318,7 +293,7 @@ def refresh():
318
  resid_fig = residual_figure(resid_df) if not resid_df.empty else None
319
 
320
  persist.push_db_async()
321
- return hero, gauge_fig, comparison_md, week["fig"], scoreboard, resid_fig
322
 
323
 
324
  # --- scoreboard ----------------------------------------------------------
@@ -477,7 +452,6 @@ with gr.Blocks(title="Toto Weather Forecast", theme=gr.themes.Soft()) as demo:
477
  gr.Markdown(SUBTITLE)
478
 
479
  hero_md = gr.Markdown()
480
- gauge_plot = gr.Plot(label="Now vs next-hour forecasts", show_label=False)
481
  gr.Markdown(f"### 📅 {VIEW_WEEK['label']}")
482
  week_plot = gr.Plot(label="Weekly")
483
  comparison_md = gr.Markdown()
@@ -547,7 +521,7 @@ with gr.Blocks(title="Toto Weather Forecast", theme=gr.themes.Soft()) as demo:
547
  "Full spec: [`docs/toto-inference.md`](https://huggingface.co/spaces/bitsofchris/time-series-ai-weather-forecast/blob/main/docs/toto-inference.md)."
548
  )
549
 
550
- outputs = [hero_md, gauge_plot, comparison_md, week_plot, scoreboard_md, residual_plot]
551
  demo.load(refresh, outputs=outputs)
552
 
553
 
 
22
  from src.weather_ui import (
23
  aligned_comparison_markdown,
24
  combined_figure,
 
25
  hero_markdown,
26
  residual_figure,
27
  )
 
274
  nws_temp=week["nws_aligned"].get("temp_f"),
275
  horizon_hours=1,
276
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  if "temp_f" in week["totos"]:
278
  comparison_md = (
279
  "### 🆚 Toto vs NWS — same hour, side-by-side\n\n"
 
293
  resid_fig = residual_figure(resid_df) if not resid_df.empty else None
294
 
295
  persist.push_db_async()
296
+ return hero, comparison_md, week["fig"], scoreboard, resid_fig
297
 
298
 
299
  # --- scoreboard ----------------------------------------------------------
 
452
  gr.Markdown(SUBTITLE)
453
 
454
  hero_md = gr.Markdown()
 
455
  gr.Markdown(f"### 📅 {VIEW_WEEK['label']}")
456
  week_plot = gr.Plot(label="Weekly")
457
  comparison_md = gr.Markdown()
 
521
  "Full spec: [`docs/toto-inference.md`](https://huggingface.co/spaces/bitsofchris/time-series-ai-weather-forecast/blob/main/docs/toto-inference.md)."
522
  )
523
 
524
+ outputs = [hero_md, comparison_md, week_plot, scoreboard_md, residual_plot]
525
  demo.load(refresh, outputs=outputs)
526
 
527
 
src/weather_ui.py CHANGED
@@ -110,7 +110,7 @@ def hero_markdown(
110
 
111
  def _row(label: str, val: float | None, ts):
112
  when = ts.tz_convert(tz).strftime("%-I %p %Z, %a %b %-d") if ts is not None else "—"
113
- cell = f"**{val:.0f}°F**" if val is not None else "—"
114
  return f"| {label} | {cell} | {when} |"
115
 
116
  table = (
@@ -180,84 +180,6 @@ def emoji_strip_markdown(nws_df: pd.DataFrame, tz: str, n: int = 12) -> str:
180
  return f"| {hours} |\n{sep}\n| {glyphs} |\n| {temps} |"
181
 
182
 
183
- def hero_gauges(
184
- cur_temp: float,
185
- toto_next: float | None,
186
- nws_next: float | None,
187
- temp_range: tuple[float, float] = (20.0, 100.0),
188
- ) -> go.Figure:
189
- """Three horizontal bar 'thermometers' stacked vertically — readable
190
- on mobile portrait without squishing. Cool/warm background bands
191
- give a sense of where on the temperature scale each value sits."""
192
- rows = [
193
- ("📡 Ecowitt (now)", cur_temp, "#222", None),
194
- ("🤖 Toto (next hr)", toto_next, "#1f77b4", cur_temp),
195
- ("🌎 NWS (next hr)", nws_next, "#d62728", cur_temp),
196
- ]
197
-
198
- fig = go.Figure()
199
- lo, hi = temp_range
200
- # Background shading: cooler on the left, warmer on the right.
201
- for x0, x1, color in [
202
- (lo, lo + (hi - lo) * 0.25, "rgba(31,119,180,0.18)"),
203
- (lo + (hi - lo) * 0.25, lo + (hi - lo) * 0.50, "rgba(31,119,180,0.08)"),
204
- (lo + (hi - lo) * 0.50, lo + (hi - lo) * 0.75, "rgba(214,39,40,0.08)"),
205
- (lo + (hi - lo) * 0.75, hi, "rgba(214,39,40,0.18)"),
206
- ]:
207
- fig.add_shape(
208
- type="rect",
209
- x0=x0, x1=x1, y0=-0.5, y1=len(rows) - 0.5,
210
- fillcolor=color, line=dict(width=0),
211
- layer="below",
212
- )
213
-
214
- y_labels = [r[0] for r in rows]
215
- for label, value, color, ref in rows:
216
- if value is None or (isinstance(value, float) and value != value):
217
- # No data — skip the bar but keep the row label by drawing 0-length.
218
- fig.add_trace(go.Bar(
219
- y=[label], x=[lo], orientation="h",
220
- marker_color="rgba(0,0,0,0)",
221
- text=["—"], textposition="outside",
222
- textfont=dict(size=14, color="#888"),
223
- showlegend=False, hoverinfo="skip",
224
- ))
225
- continue
226
- suffix = ""
227
- if ref is not None:
228
- d = value - ref
229
- sign = "+" if d >= 0 else ""
230
- suffix = f" <span style='color:#888'>({sign}{d:.1f})</span>"
231
- fig.add_trace(go.Bar(
232
- y=[label], x=[value - lo], base=[lo], orientation="h",
233
- marker=dict(color=color, line=dict(color=color, width=0)),
234
- text=[f"<b>{value:.1f}°F</b>{suffix}"],
235
- textposition="outside",
236
- textfont=dict(size=14),
237
- cliponaxis=False,
238
- showlegend=False,
239
- hovertemplate=f"{label}: %{{x:.1f}}°F<extra></extra>",
240
- ))
241
-
242
- fig.update_xaxes(
243
- range=[lo, hi], title_text="°F",
244
- showgrid=True, gridcolor="rgba(0,0,0,0.08)",
245
- zeroline=False,
246
- )
247
- fig.update_yaxes(
248
- categoryorder="array", categoryarray=list(reversed(y_labels)),
249
- showgrid=False,
250
- )
251
- fig.update_layout(
252
- height=240,
253
- margin=dict(l=130, r=90, t=20, b=40),
254
- bargap=0.35,
255
- plot_bgcolor="rgba(0,0,0,0)",
256
- paper_bgcolor="rgba(0,0,0,0)",
257
- )
258
- return fig
259
-
260
-
261
  def residual_figure(
262
  df: pd.DataFrame,
263
  title: str = "Forecast residual — 1 h-ahead prediction minus Ecowitt actual, last 48 h (°F)",
 
110
 
111
  def _row(label: str, val: float | None, ts):
112
  when = ts.tz_convert(tz).strftime("%-I %p %Z, %a %b %-d") if ts is not None else "—"
113
+ cell = f"**{val:.1f}°F**" if val is not None else "—"
114
  return f"| {label} | {cell} | {when} |"
115
 
116
  table = (
 
180
  return f"| {hours} |\n{sep}\n| {glyphs} |\n| {temps} |"
181
 
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  def residual_figure(
184
  df: pd.DataFrame,
185
  title: str = "Forecast residual — 1 h-ahead prediction minus Ecowitt actual, last 48 h (°F)",