feat: Add city weather query service with city resolution, forecast processing, and weather summary generation.

This commit is contained in:
2569718930@qq.com
2026-03-11 10:30:46 +08:00
parent b3f46430ad
commit d2a40462c5
2 changed files with 65 additions and 17 deletions
+24 -2
View File
@@ -103,7 +103,8 @@ def _derive_mgm_daily_highs_from_hourly(
if not isinstance(hourly, list) or not hourly: if not isinstance(hourly, list) or not hourly:
return {} return {}
daily_highs: Dict[str, float] = {} samples: List[Tuple[str, float]] = []
parsed_datetimes: List[datetime] = []
local_tz = timezone(timedelta(seconds=int(fallback_utc_offset))) local_tz = timezone(timedelta(seconds=int(fallback_utc_offset)))
for row in hourly: for row in hourly:
if not isinstance(row, dict): if not isinstance(row, dict):
@@ -119,6 +120,9 @@ def _derive_mgm_daily_highs_from_hourly(
dt = datetime.fromisoformat(raw_time.replace("Z", "+00:00")) dt = datetime.fromisoformat(raw_time.replace("Z", "+00:00"))
if dt.tzinfo is not None: if dt.tzinfo is not None:
dt = dt.astimezone(local_tz) dt = dt.astimezone(local_tz)
else:
dt = dt.replace(tzinfo=local_tz)
parsed_datetimes.append(dt)
date_key = dt.strftime("%Y-%m-%d") date_key = dt.strftime("%Y-%m-%d")
except Exception: except Exception:
if len(raw_time) >= 10 and raw_time[4] == "-" and raw_time[7] == "-": if len(raw_time) >= 10 and raw_time[4] == "-" and raw_time[7] == "-":
@@ -129,6 +133,24 @@ def _derive_mgm_daily_highs_from_hourly(
if not date_key: if not date_key:
continue continue
samples.append((date_key, temp))
if not samples:
return {}
# Guardrail: do not derive "daily highs" from short intraday snippets.
if parsed_datetimes:
parsed_datetimes.sort()
horizon_hours = (
parsed_datetimes[-1] - parsed_datetimes[0]
).total_seconds() / 3600.0
if horizon_hours < 30:
return {}
elif len(samples) < 24:
return {}
daily_highs: Dict[str, float] = {}
for date_key, temp in samples:
prev = daily_highs.get(date_key) prev = daily_highs.get(date_key)
daily_highs[date_key] = temp if prev is None else max(prev, temp) daily_highs[date_key] = temp if prev is None else max(prev, temp)
@@ -163,7 +185,7 @@ def _append_future_forecast_lines(
if mgm_value is not None: if mgm_value is not None:
mgm_display = f"{float(mgm_value):.1f}" mgm_display = f"{float(mgm_value):.1f}"
future_forecasts.append( future_forecasts.append(
f"{d[5:]}: {t}{temp_symbol} | 🇺🇸 <b>MGM: {mgm_display}{temp_symbol}</b>" f"{d[5:]}: {t}{temp_symbol} | <b>MGM: {mgm_display}{temp_symbol}</b>"
) )
else: else:
future_forecasts.append(f"{d[5:]}: {t}{temp_symbol}") future_forecasts.append(f"{d[5:]}: {t}{temp_symbol}")
+27 -1
View File
@@ -768,9 +768,30 @@ class WeatherDataCollector:
# 5. Fallback for daily_forecasts from hourly data # 5. Fallback for daily_forecasts from hourly data
if not results.get("daily_forecasts") and results.get("hourly"): if not results.get("daily_forecasts") and results.get("hourly"):
# Guardrail: avoid treating short intraday snippets as full-day highs.
hourly_rows = results.get("hourly") or []
parsed_times = []
for h in hourly_rows:
t = str(h.get("time") or "")
if "T" not in t:
continue
try:
parsed_times.append(datetime.fromisoformat(t.replace("Z", "+00:00")))
except Exception:
continue
horizon_hours = 0.0
if len(parsed_times) >= 2:
parsed_times.sort()
horizon_hours = (
parsed_times[-1] - parsed_times[0]
).total_seconds() / 3600.0
if len(hourly_rows) >= 24 or horizon_hours >= 30:
from collections import defaultdict from collections import defaultdict
daily_max = defaultdict(list) daily_max = defaultdict(list)
for h in results["hourly"]: for h in hourly_rows:
t = h.get("time", "") t = h.get("time", "")
temp = h.get("temp") temp = h.get("temp")
if t and temp is not None: if t and temp is not None:
@@ -785,6 +806,11 @@ class WeatherDataCollector:
f"📋 MGM daily_forecasts (from hourly fallback): " f"📋 MGM daily_forecasts (from hourly fallback): "
f"{dict(results['daily_forecasts'])}" f"{dict(results['daily_forecasts'])}"
) )
else:
logger.info(
"📋 Skip MGM daily_forecasts hourly fallback: "
f"hourly points={len(hourly_rows)}, horizon={horizon_hours:.1f}h"
)
return results if "current" in results else None return results if "current" in results else None
except Exception as e: except Exception as e: