Spaces:
Sleeping
Sleeping
Commit Β·
50e15a2
1
Parent(s): 4692887
Show current values and 24h delta below each forecast metric
Browse files
app.py
CHANGED
|
@@ -6,12 +6,14 @@ Usage:
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import logging
|
|
|
|
| 9 |
from datetime import timedelta
|
| 10 |
|
| 11 |
import gradio as gr
|
| 12 |
|
| 13 |
from hrrr_fetch import fetch_hrrr_input
|
| 14 |
from model_utils import run_forecast, load_model, AVAILABLE_MODELS
|
|
|
|
| 15 |
from visualization import (
|
| 16 |
get_static_maps,
|
| 17 |
plot_temperature,
|
|
@@ -185,6 +187,21 @@ button.primary {
|
|
| 185 |
padding: 10px 28px !important;
|
| 186 |
}
|
| 187 |
button.primary:hover { background: #0A74E0 !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
"""
|
| 189 |
|
| 190 |
# ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -199,6 +216,41 @@ def _resolve_model(display: str) -> str:
|
|
| 199 |
return model_keys[model_choices.index(display)]
|
| 200 |
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
def _hero_placeholder() -> str:
|
| 203 |
return (
|
| 204 |
'<div class="hero-card">'
|
|
@@ -208,7 +260,30 @@ def _hero_placeholder() -> str:
|
|
| 208 |
)
|
| 209 |
|
| 210 |
|
| 211 |
-
def _hero_html(r: dict, cycle_str: str, forecast_str: str, model_label: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
return (
|
| 213 |
'<div class="hero-card">'
|
| 214 |
# temperature + status
|
|
@@ -217,18 +292,24 @@ def _hero_html(r: dict, cycle_str: str, forecast_str: str, model_label: str) ->
|
|
| 217 |
f'<span class="hero-temp-unit">Β°C</span></div>'
|
| 218 |
f'<div class="hero-status">{r["rain_status"]}</div>'
|
| 219 |
"</div>"
|
|
|
|
| 220 |
# metric tiles
|
| 221 |
'<div class="hero-metrics">'
|
| 222 |
f'<div class="metric-tile"><div class="metric-value">{r["temperature_f"]:.0f}Β°F</div>'
|
| 223 |
-
'<div class="metric-label">Temperature</div>
|
|
|
|
| 224 |
f'<div class="metric-tile"><div class="metric-value">{r["humidity_pct"]:.0f}%</div>'
|
| 225 |
-
'<div class="metric-label">Humidity</div>
|
|
|
|
| 226 |
f'<div class="metric-tile"><div class="metric-value">{r["wind_speed_ms"]:.1f}</div>'
|
| 227 |
-
f'<div class="metric-label">Wind m/s {r["wind_dir_str"]}</div>
|
|
|
|
| 228 |
f'<div class="metric-tile"><div class="metric-value">{r["gust_ms"]:.1f}</div>'
|
| 229 |
-
'<div class="metric-label">Gust m/s</div>
|
|
|
|
| 230 |
f'<div class="metric-tile"><div class="metric-value">{r["precipitation_mm"]:.2f}</div>'
|
| 231 |
-
'<div class="metric-label">Precip mm</div>
|
|
|
|
| 232 |
"</div>"
|
| 233 |
# meta line
|
| 234 |
'<div class="hero-meta">'
|
|
@@ -267,8 +348,9 @@ def do_forecast(model_display: str, progress=gr.Progress()):
|
|
| 267 |
except Exception as e:
|
| 268 |
raise gr.Error(f"Inference failed: {e}")
|
| 269 |
|
|
|
|
| 270 |
model_label = model_display.split("(")[0].strip()
|
| 271 |
-
hero = _hero_html(r, cycle_str, forecast_str, model_label)
|
| 272 |
temp_fig = plot_temperature(input_array, r, cycle_str, forecast_str)
|
| 273 |
precip_fig = plot_precipitation(input_array, r, cycle_str, forecast_str)
|
| 274 |
wind_fig = plot_wind_speed(input_array, r, cycle_str, forecast_str)
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import logging
|
| 9 |
+
import math
|
| 10 |
from datetime import timedelta
|
| 11 |
|
| 12 |
import gradio as gr
|
| 13 |
|
| 14 |
from hrrr_fetch import fetch_hrrr_input
|
| 15 |
from model_utils import run_forecast, load_model, AVAILABLE_MODELS
|
| 16 |
+
from var_mapping import JUMBO_ROW, JUMBO_COL
|
| 17 |
from visualization import (
|
| 18 |
get_static_maps,
|
| 19 |
plot_temperature,
|
|
|
|
| 187 |
padding: 10px 28px !important;
|
| 188 |
}
|
| 189 |
button.primary:hover { background: #0A74E0 !important; }
|
| 190 |
+
|
| 191 |
+
/* ββ Current / delta annotations ββ */
|
| 192 |
+
.metric-current {
|
| 193 |
+
font-size: 11px; font-weight: 500;
|
| 194 |
+
color: var(--muted);
|
| 195 |
+
margin-top: 6px;
|
| 196 |
+
line-height: 1.4;
|
| 197 |
+
}
|
| 198 |
+
.delta-up { color: #FF3B30; font-weight: 600; }
|
| 199 |
+
.delta-down { color: #0A84FF; font-weight: 600; }
|
| 200 |
+
.delta-neutral { color: #86868B; font-weight: 600; }
|
| 201 |
+
.hero-current-note {
|
| 202 |
+
font-size: 13px; color: var(--muted);
|
| 203 |
+
margin-top: 2px; margin-bottom: 0;
|
| 204 |
+
}
|
| 205 |
"""
|
| 206 |
|
| 207 |
# ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 216 |
return model_keys[model_choices.index(display)]
|
| 217 |
|
| 218 |
|
| 219 |
+
def _extract_current(input_array) -> dict:
|
| 220 |
+
"""Extract current observed values at Jumbo from the input array."""
|
| 221 |
+
tmp_k = float(input_array[JUMBO_ROW, JUMBO_COL, 0])
|
| 222 |
+
rh = float(input_array[JUMBO_ROW, JUMBO_COL, 1])
|
| 223 |
+
u = float(input_array[JUMBO_ROW, JUMBO_COL, 2])
|
| 224 |
+
v = float(input_array[JUMBO_ROW, JUMBO_COL, 3])
|
| 225 |
+
gust = float(input_array[JUMBO_ROW, JUMBO_COL, 4])
|
| 226 |
+
apcp = float(input_array[JUMBO_ROW, JUMBO_COL, 6])
|
| 227 |
+
tmp_c = tmp_k - 273.15
|
| 228 |
+
return {
|
| 229 |
+
"temperature_c": tmp_c,
|
| 230 |
+
"temperature_f": tmp_c * 9 / 5 + 32,
|
| 231 |
+
"humidity_pct": max(0.0, min(100.0, rh)),
|
| 232 |
+
"wind_speed_ms": math.sqrt(u**2 + v**2),
|
| 233 |
+
"gust_ms": max(gust, 0.0),
|
| 234 |
+
"precipitation_mm": max(apcp, 0.0),
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _delta_html(now_val: float, fcst_val: float, fmt: str = ".1f", unit: str = "") -> str:
|
| 239 |
+
"""Render 'Now X Β· +/-delta' with colored arrow."""
|
| 240 |
+
diff = fcst_val - now_val
|
| 241 |
+
if abs(diff) < 0.05:
|
| 242 |
+
arrow, cls = "", "delta-neutral"
|
| 243 |
+
elif diff > 0:
|
| 244 |
+
arrow, cls = " β", "delta-up"
|
| 245 |
+
else:
|
| 246 |
+
arrow, cls = " β", "delta-down"
|
| 247 |
+
sign = "+" if diff >= 0 else ""
|
| 248 |
+
return (
|
| 249 |
+
f'Now {now_val:{fmt}}{unit} Β· '
|
| 250 |
+
f'<span class="{cls}">{sign}{diff:{fmt}}{unit}{arrow}</span>'
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
def _hero_placeholder() -> str:
|
| 255 |
return (
|
| 256 |
'<div class="hero-card">'
|
|
|
|
| 260 |
)
|
| 261 |
|
| 262 |
|
| 263 |
+
def _hero_html(r: dict, cur: dict, cycle_str: str, forecast_str: str, model_label: str) -> str:
|
| 264 |
+
# delta strings for each metric
|
| 265 |
+
d_temp = _delta_html(cur["temperature_f"], r["temperature_f"], ".0f", "Β°F")
|
| 266 |
+
d_hum = _delta_html(cur["humidity_pct"], r["humidity_pct"], ".0f", "%")
|
| 267 |
+
d_wind = _delta_html(cur["wind_speed_ms"], r["wind_speed_ms"], ".1f", "")
|
| 268 |
+
d_gust = _delta_html(cur["gust_ms"], r["gust_ms"], ".1f", "")
|
| 269 |
+
d_prec = _delta_html(cur["precipitation_mm"], r["precipitation_mm"], ".2f", "")
|
| 270 |
+
|
| 271 |
+
# main temperature delta
|
| 272 |
+
temp_diff = r["temperature_c"] - cur["temperature_c"]
|
| 273 |
+
sign = "+" if temp_diff >= 0 else ""
|
| 274 |
+
if abs(temp_diff) < 0.05:
|
| 275 |
+
tcls = "delta-neutral"
|
| 276 |
+
elif temp_diff > 0:
|
| 277 |
+
tcls = "delta-up"
|
| 278 |
+
else:
|
| 279 |
+
tcls = "delta-down"
|
| 280 |
+
temp_note = (
|
| 281 |
+
f'<p class="hero-current-note">'
|
| 282 |
+
f'Now {cur["temperature_c"]:.1f}Β°C Β· '
|
| 283 |
+
f'<span class="{tcls}">{sign}{temp_diff:.1f}Β°C in 24h</span>'
|
| 284 |
+
f'</p>'
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
return (
|
| 288 |
'<div class="hero-card">'
|
| 289 |
# temperature + status
|
|
|
|
| 292 |
f'<span class="hero-temp-unit">Β°C</span></div>'
|
| 293 |
f'<div class="hero-status">{r["rain_status"]}</div>'
|
| 294 |
"</div>"
|
| 295 |
+
+ temp_note +
|
| 296 |
# metric tiles
|
| 297 |
'<div class="hero-metrics">'
|
| 298 |
f'<div class="metric-tile"><div class="metric-value">{r["temperature_f"]:.0f}Β°F</div>'
|
| 299 |
+
f'<div class="metric-label">Temperature</div>'
|
| 300 |
+
f'<div class="metric-current">{d_temp}</div></div>'
|
| 301 |
f'<div class="metric-tile"><div class="metric-value">{r["humidity_pct"]:.0f}%</div>'
|
| 302 |
+
f'<div class="metric-label">Humidity</div>'
|
| 303 |
+
f'<div class="metric-current">{d_hum}</div></div>'
|
| 304 |
f'<div class="metric-tile"><div class="metric-value">{r["wind_speed_ms"]:.1f}</div>'
|
| 305 |
+
f'<div class="metric-label">Wind m/s {r["wind_dir_str"]}</div>'
|
| 306 |
+
f'<div class="metric-current">{d_wind}</div></div>'
|
| 307 |
f'<div class="metric-tile"><div class="metric-value">{r["gust_ms"]:.1f}</div>'
|
| 308 |
+
f'<div class="metric-label">Gust m/s</div>'
|
| 309 |
+
f'<div class="metric-current">{d_gust}</div></div>'
|
| 310 |
f'<div class="metric-tile"><div class="metric-value">{r["precipitation_mm"]:.2f}</div>'
|
| 311 |
+
f'<div class="metric-label">Precip mm</div>'
|
| 312 |
+
f'<div class="metric-current">{d_prec}</div></div>'
|
| 313 |
"</div>"
|
| 314 |
# meta line
|
| 315 |
'<div class="hero-meta">'
|
|
|
|
| 348 |
except Exception as e:
|
| 349 |
raise gr.Error(f"Inference failed: {e}")
|
| 350 |
|
| 351 |
+
cur = _extract_current(input_array)
|
| 352 |
model_label = model_display.split("(")[0].strip()
|
| 353 |
+
hero = _hero_html(r, cur, cycle_str, forecast_str, model_label)
|
| 354 |
temp_fig = plot_temperature(input_array, r, cycle_str, forecast_str)
|
| 355 |
precip_fig = plot_precipitation(input_array, r, cycle_str, forecast_str)
|
| 356 |
wind_fig = plot_wind_speed(input_array, r, cycle_str, forecast_str)
|