Commit ·
bb2faab
1
Parent(s): f0a2503
Add rainfall subplot and label NWS hero row with the actual hour
Browse files- src/ecowitt.py: HISTORY_FIELDS gains rain_in_hr (rainfall_piezo.rain_rate).
- app.py: include rainfall_piezo channel in the call_back; add 'Rainfall
rate' as a fourth metric (above humidity, between temperature and
humidity in the stacked plot). NWS doesn't expose rain rate so the
rainfall subplot shows Ecowitt past + Toto forecast only.
- src/weather_ui.py: hero second row now reads e.g. 'NWS forecast for
4 PM EDT' instead of 'this hour' so the comparison time is unambiguous.
- src/weather_ui.py: bump combined plot height 720 → 900 to fit 4 panels.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app.py +8 -4
- src/ecowitt.py +1 -0
- src/weather_ui.py +6 -2
app.py
CHANGED
|
@@ -42,9 +42,10 @@ CYCLE_CONFIG: dict[str, tuple[str, str, int]] = {
|
|
| 42 |
HORIZON_CONFIG: dict[str, int] = {"24 h": 24, "48 h": 48, "72 h": 72}
|
| 43 |
|
| 44 |
METRICS = [
|
| 45 |
-
{"col": "temp_f", "title": "Outdoor temperature", "y": "°F",
|
| 46 |
-
{"col": "
|
| 47 |
-
{"col": "
|
|
|
|
| 48 |
]
|
| 49 |
|
| 50 |
|
|
@@ -73,7 +74,10 @@ def fetch_history(cycle_type: str, resample: str, days: int) -> pd.DataFrame:
|
|
| 73 |
cfg = ecowitt.EcowittConfig.from_env()
|
| 74 |
end = datetime.now(timezone.utc).replace(tzinfo=None)
|
| 75 |
start = end - timedelta(days=days)
|
| 76 |
-
raw = ecowitt.fetch_history(
|
|
|
|
|
|
|
|
|
|
| 77 |
return ecowitt.history_to_dataframe(raw, resample=resample)
|
| 78 |
|
| 79 |
|
|
|
|
| 42 |
HORIZON_CONFIG: dict[str, int] = {"24 h": 24, "48 h": 48, "72 h": 72}
|
| 43 |
|
| 44 |
METRICS = [
|
| 45 |
+
{"col": "temp_f", "title": "Outdoor temperature", "y": "°F", "nws_col": "temp_f"},
|
| 46 |
+
{"col": "rain_in_hr", "title": "Rainfall rate", "y": "in/hr", "nws_col": None},
|
| 47 |
+
{"col": "humidity", "title": "Outdoor humidity", "y": "%", "nws_col": "humidity"},
|
| 48 |
+
{"col": "pressure_inhg", "title": "Barometric pressure", "y": "inHg", "nws_col": None},
|
| 49 |
]
|
| 50 |
|
| 51 |
|
|
|
|
| 74 |
cfg = ecowitt.EcowittConfig.from_env()
|
| 75 |
end = datetime.now(timezone.utc).replace(tzinfo=None)
|
| 76 |
start = end - timedelta(days=days)
|
| 77 |
+
raw = ecowitt.fetch_history(
|
| 78 |
+
cfg, start, end, cycle_type=cycle_type,
|
| 79 |
+
call_back="outdoor,pressure,rainfall_piezo",
|
| 80 |
+
)
|
| 81 |
return ecowitt.history_to_dataframe(raw, resample=resample)
|
| 82 |
|
| 83 |
|
src/ecowitt.py
CHANGED
|
@@ -32,6 +32,7 @@ HISTORY_FIELDS: dict[str, tuple[str, str]] = {
|
|
| 32 |
"temp_f": ("outdoor", "temperature"),
|
| 33 |
"humidity": ("outdoor", "humidity"),
|
| 34 |
"pressure_inhg": ("pressure", "relative"),
|
|
|
|
| 35 |
}
|
| 36 |
|
| 37 |
# Outdoor temp/humidity live under common.outdoor; pressure under pressure.
|
|
|
|
| 32 |
"temp_f": ("outdoor", "temperature"),
|
| 33 |
"humidity": ("outdoor", "humidity"),
|
| 34 |
"pressure_inhg": ("pressure", "relative"),
|
| 35 |
+
"rain_in_hr": ("rainfall_piezo", "rain_rate"),
|
| 36 |
}
|
| 37 |
|
| 38 |
# Outdoor temp/humidity live under common.outdoor; pressure under pressure.
|
src/weather_ui.py
CHANGED
|
@@ -76,7 +76,11 @@ def hero_markdown(
|
|
| 76 |
nws_short = ""
|
| 77 |
glyph = "🌡"
|
| 78 |
gap_str = ""
|
|
|
|
| 79 |
if nws_first is not None and not nws_first.empty:
|
|
|
|
|
|
|
|
|
|
| 80 |
row = nws_first.iloc[0] if isinstance(nws_first, pd.DataFrame) else nws_first
|
| 81 |
if isinstance(row, pd.Series):
|
| 82 |
if "temp_f" in row and pd.notna(row["temp_f"]):
|
|
@@ -92,7 +96,7 @@ def hero_markdown(
|
|
| 92 |
"| Source | Temperature |\n"
|
| 93 |
"|---|---|\n"
|
| 94 |
f"| 📡 Ecowitt (measured) | **{cur['temp_f']:.1f}°F** |\n"
|
| 95 |
-
f"| 🌎 NWS forecast for
|
| 96 |
)
|
| 97 |
return (
|
| 98 |
f"### {glyph} {place}\n\n"
|
|
@@ -233,7 +237,7 @@ def combined_figure(
|
|
| 233 |
showlegend = False # only first subplot shows legend entries
|
| 234 |
|
| 235 |
fig.update_layout(
|
| 236 |
-
height=
|
| 237 |
hovermode="x unified",
|
| 238 |
margin=dict(l=50, r=20, t=50, b=40),
|
| 239 |
legend=dict(orientation="h", yanchor="bottom", y=1.04, xanchor="right", x=1),
|
|
|
|
| 76 |
nws_short = ""
|
| 77 |
glyph = "🌡"
|
| 78 |
gap_str = ""
|
| 79 |
+
nws_hour_label = "this hour"
|
| 80 |
if nws_first is not None and not nws_first.empty:
|
| 81 |
+
idx0 = nws_first.index[0] if isinstance(nws_first, pd.DataFrame) else nws_first.name
|
| 82 |
+
if isinstance(idx0, pd.Timestamp):
|
| 83 |
+
nws_hour_label = idx0.tz_convert(tz).strftime("%-I %p %Z")
|
| 84 |
row = nws_first.iloc[0] if isinstance(nws_first, pd.DataFrame) else nws_first
|
| 85 |
if isinstance(row, pd.Series):
|
| 86 |
if "temp_f" in row and pd.notna(row["temp_f"]):
|
|
|
|
| 96 |
"| Source | Temperature |\n"
|
| 97 |
"|---|---|\n"
|
| 98 |
f"| 📡 Ecowitt (measured) | **{cur['temp_f']:.1f}°F** |\n"
|
| 99 |
+
f"| 🌎 NWS forecast for {nws_hour_label} | {nws_temp_str}{gap_str} |"
|
| 100 |
)
|
| 101 |
return (
|
| 102 |
f"### {glyph} {place}\n\n"
|
|
|
|
| 237 |
showlegend = False # only first subplot shows legend entries
|
| 238 |
|
| 239 |
fig.update_layout(
|
| 240 |
+
height=900,
|
| 241 |
hovermode="x unified",
|
| 242 |
margin=dict(l=50, r=20, t=50, b=40),
|
| 243 |
legend=dict(orientation="h", yanchor="bottom", y=1.04, xanchor="right", x=1),
|