bitsofchris Claude Opus 4.7 (1M context) commited on
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>

Files changed (3) hide show
  1. app.py +8 -4
  2. src/ecowitt.py +1 -0
  3. 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", "nws_col": "temp_f"},
46
- {"col": "humidity", "title": "Outdoor humidity", "y": "%", "nws_col": "humidity"},
47
- {"col": "pressure_inhg", "title": "Barometric pressure", "y": "inHg", "nws_col": None},
 
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(cfg, start, end, cycle_type=cycle_type, call_back="outdoor,pressure")
 
 
 
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 this hour | {nws_temp_str}{gap_str} |"
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=720,
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),