Commit Β·
6fd1ae5
1
Parent(s): 8949df4
Move weekly forecast above the comparison table; timestamps into hero
Browse files- Lead with the forecast itself: hero β weekly chart β 24h same-hour
table β radar β scoreboard β accordions. The reader sees the live
forecast first, then the side-by-side detail.
- Hero table grows a 'When' column: each row carries its own timestamp
('2:02 PM EDT, Mon May 11' for Ecowitt, '2 PM EDT, Mon May 11' for
the NWS hour). Drops the standalone 'Last Ecowitt readingβ¦' caption
since the same info is now in the table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app.py +2 -3
- src/weather_ui.py +9 -15
app.py
CHANGED
|
@@ -429,6 +429,8 @@ with gr.Blocks(title="Toto Weather Forecast", theme=gr.themes.Soft()) as demo:
|
|
| 429 |
gr.Markdown(SUBTITLE)
|
| 430 |
|
| 431 |
hero_md = gr.Markdown()
|
|
|
|
|
|
|
| 432 |
comparison_md = gr.Markdown()
|
| 433 |
gr.HTML(
|
| 434 |
# KOKX is the NWS radar site at Upton, NY β covers Long Island incl.
|
|
@@ -450,9 +452,6 @@ with gr.Blocks(title="Toto Weather Forecast", theme=gr.themes.Soft()) as demo:
|
|
| 450 |
"<span style='opacity:0.55'>π Live data + forecast auto-refresh every 15 minutes.</span>"
|
| 451 |
)
|
| 452 |
|
| 453 |
-
gr.Markdown(f"### π
{VIEW_WEEK['label']}")
|
| 454 |
-
week_plot = gr.Plot(label="Weekly")
|
| 455 |
-
|
| 456 |
gr.Markdown("### π How has each model done so far?")
|
| 457 |
scoreboard_md = gr.Markdown()
|
| 458 |
residual_plot = gr.Plot(label="Forecast residual")
|
|
|
|
| 429 |
gr.Markdown(SUBTITLE)
|
| 430 |
|
| 431 |
hero_md = gr.Markdown()
|
| 432 |
+
gr.Markdown(f"### π
{VIEW_WEEK['label']}")
|
| 433 |
+
week_plot = gr.Plot(label="Weekly")
|
| 434 |
comparison_md = gr.Markdown()
|
| 435 |
gr.HTML(
|
| 436 |
# KOKX is the NWS radar site at Upton, NY β covers Long Island incl.
|
|
|
|
| 452 |
"<span style='opacity:0.55'>π Live data + forecast auto-refresh every 15 minutes.</span>"
|
| 453 |
)
|
| 454 |
|
|
|
|
|
|
|
|
|
|
| 455 |
gr.Markdown("### π How has each model done so far?")
|
| 456 |
scoreboard_md = gr.Markdown()
|
| 457 |
residual_plot = gr.Plot(label="Forecast residual")
|
src/weather_ui.py
CHANGED
|
@@ -85,17 +85,16 @@ def hero_markdown(
|
|
| 85 |
if cur_temp is None or when_ts is None:
|
| 86 |
return "_(no current readings yet)_"
|
| 87 |
|
| 88 |
-
|
| 89 |
|
| 90 |
nws_temp_str = "β"
|
| 91 |
-
|
| 92 |
glyph = "π‘"
|
| 93 |
gap_str = ""
|
| 94 |
-
nws_hour_label = "this hour"
|
| 95 |
if nws_first is not None and not nws_first.empty:
|
| 96 |
idx0 = nws_first.index[0] if isinstance(nws_first, pd.DataFrame) else nws_first.name
|
| 97 |
if isinstance(idx0, pd.Timestamp):
|
| 98 |
-
|
| 99 |
row = nws_first.iloc[0] if isinstance(nws_first, pd.DataFrame) else nws_first
|
| 100 |
if isinstance(row, pd.Series):
|
| 101 |
if "temp_f" in row and pd.notna(row["temp_f"]):
|
|
@@ -104,20 +103,15 @@ def hero_markdown(
|
|
| 104 |
sign = "+" if gap >= 0 else ""
|
| 105 |
gap_str = f" <span style='opacity:0.55'>(NWS off by {sign}{gap:.1f}Β°F)</span>"
|
| 106 |
if "short_forecast" in row:
|
| 107 |
-
|
| 108 |
-
glyph = emoji_for(nws_short)
|
| 109 |
|
| 110 |
table = (
|
| 111 |
-
"| Source | Temperature |\n"
|
| 112 |
-
"|---|---|\n"
|
| 113 |
-
f"| π‘ Ecowitt (measured) | **{cur_temp:.1f}Β°F** |\n"
|
| 114 |
-
f"| π NWS forecast
|
| 115 |
-
)
|
| 116 |
-
return (
|
| 117 |
-
f"### {glyph} {place}\n\n"
|
| 118 |
-
f"{table}\n\n"
|
| 119 |
-
f"<span style='opacity:0.55'>Last Ecowitt reading at {when}</span>"
|
| 120 |
)
|
|
|
|
| 121 |
|
| 122 |
|
| 123 |
def aligned_comparison_markdown(
|
|
|
|
| 85 |
if cur_temp is None or when_ts is None:
|
| 86 |
return "_(no current readings yet)_"
|
| 87 |
|
| 88 |
+
eco_when = when_ts.tz_convert(tz).strftime("%-I:%M %p %Z, %a %b %-d")
|
| 89 |
|
| 90 |
nws_temp_str = "β"
|
| 91 |
+
nws_when = "β"
|
| 92 |
glyph = "π‘"
|
| 93 |
gap_str = ""
|
|
|
|
| 94 |
if nws_first is not None and not nws_first.empty:
|
| 95 |
idx0 = nws_first.index[0] if isinstance(nws_first, pd.DataFrame) else nws_first.name
|
| 96 |
if isinstance(idx0, pd.Timestamp):
|
| 97 |
+
nws_when = idx0.tz_convert(tz).strftime("%-I %p %Z, %a %b %-d")
|
| 98 |
row = nws_first.iloc[0] if isinstance(nws_first, pd.DataFrame) else nws_first
|
| 99 |
if isinstance(row, pd.Series):
|
| 100 |
if "temp_f" in row and pd.notna(row["temp_f"]):
|
|
|
|
| 103 |
sign = "+" if gap >= 0 else ""
|
| 104 |
gap_str = f" <span style='opacity:0.55'>(NWS off by {sign}{gap:.1f}Β°F)</span>"
|
| 105 |
if "short_forecast" in row:
|
| 106 |
+
glyph = emoji_for(str(row["short_forecast"]))
|
|
|
|
| 107 |
|
| 108 |
table = (
|
| 109 |
+
"| Source | Temperature | When |\n"
|
| 110 |
+
"|---|---|---|\n"
|
| 111 |
+
f"| π‘ Ecowitt (measured) | **{cur_temp:.1f}Β°F** | {eco_when} |\n"
|
| 112 |
+
f"| π NWS forecast | {nws_temp_str}{gap_str} | {nws_when} |"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
)
|
| 114 |
+
return f"### {glyph} {place}\n\n{table}"
|
| 115 |
|
| 116 |
|
| 117 |
def aligned_comparison_markdown(
|