feat: weather card — precip probability %, condition text, unit-aware wind
- Fetch precipitation_probability_max from Open-Meteo (replaces precip_sum in the card display — probability is more useful at a glance than mm) - Show WMO condition description text on each forecast day - Convert wind speed to mph when temp unit is F; pass wind_unit in response - Display 💧 X% chance of rain; 💨 X mph/km/h wind per day Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ interface ForecastDay {
|
|||||||
condition: string
|
condition: string
|
||||||
high: number
|
high: number
|
||||||
low: number
|
low: number
|
||||||
precip_mm: number
|
precip_probability: number | null
|
||||||
windspeed_max: number
|
windspeed_max: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ interface WeatherData {
|
|||||||
today_low: number | null
|
today_low: number | null
|
||||||
yesterday_high: number | null
|
yesterday_high: number | null
|
||||||
yesterday_low: number | null
|
yesterday_low: number | null
|
||||||
|
wind_unit?: string
|
||||||
forecast: ForecastDay[]
|
forecast: ForecastDay[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,10 +88,11 @@ const fetchedAtLabel = computed(() => {
|
|||||||
<div v-for="day in weather.forecast" :key="day.day" class="weather-forecast-day">
|
<div v-for="day in weather.forecast" :key="day.day" class="weather-forecast-day">
|
||||||
<span class="forecast-day-name">{{ day.day }}</span>
|
<span class="forecast-day-name">{{ day.day }}</span>
|
||||||
<span class="forecast-icon">{{ weatherIcon(day.condition) }}</span>
|
<span class="forecast-icon">{{ weatherIcon(day.condition) }}</span>
|
||||||
|
<span class="forecast-condition">{{ day.condition }}</span>
|
||||||
<span class="forecast-temps">{{ day.high }}° / {{ day.low }}°</span>
|
<span class="forecast-temps">{{ day.high }}° / {{ day.low }}°</span>
|
||||||
<span v-if="day.precip_mm > 0" class="forecast-precip">💧 {{ day.precip_mm }}mm</span>
|
<span v-if="day.precip_probability != null && day.precip_probability > 0" class="forecast-precip">💧 {{ day.precip_probability }}%</span>
|
||||||
<span v-else class="forecast-precip forecast-precip--dry">—</span>
|
<span v-else class="forecast-precip forecast-precip--dry">💧 —</span>
|
||||||
<span class="forecast-wind">💨 {{ day.windspeed_max }}km/h</span>
|
<span class="forecast-wind">💨 {{ day.windspeed_max }} {{ weather.wind_unit ?? 'km/h' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,6 +188,14 @@ const fetchedAtLabel = computed(() => {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forecast-condition {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.forecast-temps {
|
.forecast-temps {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ interface WeatherData {
|
|||||||
today_low: number | null
|
today_low: number | null
|
||||||
yesterday_high: number | null
|
yesterday_high: number | null
|
||||||
yesterday_low: number | null
|
yesterday_low: number | null
|
||||||
forecast: { day: string; condition: string; high: number; low: number; precip_mm: number; windspeed_max: number }[]
|
wind_unit?: string
|
||||||
|
forecast: { day: string; condition: string; high: number; low: number; precip_probability: number | null; windspeed_max: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ NOMINATIM_URL = "https://nominatim.openstreetmap.org/search"
|
|||||||
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
|
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
|
||||||
OPEN_METEO_DAILY = (
|
OPEN_METEO_DAILY = (
|
||||||
"temperature_2m_max,temperature_2m_min,precipitation_sum,"
|
"temperature_2m_max,temperature_2m_min,precipitation_sum,"
|
||||||
"weathercode,windspeed_10m_max"
|
"precipitation_probability_max,weathercode,windspeed_10m_max"
|
||||||
)
|
)
|
||||||
|
|
||||||
# WMO weather code → description (subset; covers the most common codes)
|
# WMO weather code → description (subset; covers the most common codes)
|
||||||
@@ -42,12 +42,14 @@ def parse_forecast(raw: dict) -> list[dict]:
|
|||||||
"""Convert Open-Meteo daily response into a clean list of day dicts."""
|
"""Convert Open-Meteo daily response into a clean list of day dicts."""
|
||||||
daily = raw.get("daily", {})
|
daily = raw.get("daily", {})
|
||||||
dates = daily.get("time", [])
|
dates = daily.get("time", [])
|
||||||
|
precip_prob = daily.get("precipitation_probability_max", [])
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"date": dates[i],
|
"date": dates[i],
|
||||||
"temp_max": daily.get("temperature_2m_max", [])[i],
|
"temp_max": daily.get("temperature_2m_max", [])[i],
|
||||||
"temp_min": daily.get("temperature_2m_min", [])[i],
|
"temp_min": daily.get("temperature_2m_min", [])[i],
|
||||||
"precip_mm": daily.get("precipitation_sum", [])[i],
|
"precip_mm": daily.get("precipitation_sum", [])[i],
|
||||||
|
"precip_probability": precip_prob[i] if i < len(precip_prob) else None,
|
||||||
"weathercode": daily.get("weathercode", [])[i],
|
"weathercode": daily.get("weathercode", [])[i],
|
||||||
"description": describe_weathercode(daily.get("weathercode", [])[i]),
|
"description": describe_weathercode(daily.get("weathercode", [])[i]),
|
||||||
"windspeed_max": daily.get("windspeed_10m_max", [])[i],
|
"windspeed_max": daily.get("windspeed_10m_max", [])[i],
|
||||||
@@ -119,10 +121,13 @@ def parse_weather_card_data(
|
|||||||
yesterday_day = next((d for d in days if d["date"] == yesterday_str), None)
|
yesterday_day = next((d for d in days if d["date"] == yesterday_str), None)
|
||||||
future_days = [d for d in days if d["date"] > today_str][:5]
|
future_days = [d for d in days if d["date"] > today_str][:5]
|
||||||
|
|
||||||
|
imperial = temp_unit == "F"
|
||||||
|
|
||||||
def to_temp(c: float) -> int:
|
def to_temp(c: float) -> int:
|
||||||
if temp_unit == "F":
|
return round(c * 9 / 5 + 32) if imperial else round(c)
|
||||||
return round(c * 9 / 5 + 32)
|
|
||||||
return round(c)
|
def to_wind(kmh: float) -> int:
|
||||||
|
return round(kmh * 0.621371) if imperial else round(kmh)
|
||||||
|
|
||||||
def day_label(date_str: str) -> str:
|
def day_label(date_str: str) -> str:
|
||||||
from datetime import date as _date
|
from datetime import date as _date
|
||||||
@@ -131,6 +136,8 @@ def parse_weather_card_data(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return date_str
|
return date_str
|
||||||
|
|
||||||
|
wind_unit = "mph" if imperial else "km/h"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"location": getattr(cache_row, "location_label", ""),
|
"location": getattr(cache_row, "location_label", ""),
|
||||||
"fetched_at": cache_row.fetched_at.isoformat(),
|
"fetched_at": cache_row.fetched_at.isoformat(),
|
||||||
@@ -140,14 +147,15 @@ def parse_weather_card_data(
|
|||||||
"today_low": to_temp(today_day["temp_min"]) if today_day else None,
|
"today_low": to_temp(today_day["temp_min"]) if today_day else None,
|
||||||
"yesterday_high": to_temp(yesterday_day["temp_max"]) if yesterday_day else None,
|
"yesterday_high": to_temp(yesterday_day["temp_max"]) if yesterday_day else None,
|
||||||
"yesterday_low": to_temp(yesterday_day["temp_min"]) if yesterday_day else None,
|
"yesterday_low": to_temp(yesterday_day["temp_min"]) if yesterday_day else None,
|
||||||
|
"wind_unit": wind_unit,
|
||||||
"forecast": [
|
"forecast": [
|
||||||
{
|
{
|
||||||
"day": day_label(d["date"]),
|
"day": day_label(d["date"]),
|
||||||
"condition": d["description"],
|
"condition": d["description"],
|
||||||
"high": to_temp(d["temp_max"]),
|
"high": to_temp(d["temp_max"]),
|
||||||
"low": to_temp(d["temp_min"]),
|
"low": to_temp(d["temp_min"]),
|
||||||
"precip_mm": round(d["precip_mm"], 1),
|
"precip_probability": d["precip_probability"],
|
||||||
"windspeed_max": round(d["windspeed_max"]),
|
"windspeed_max": to_wind(d["windspeed_max"]),
|
||||||
}
|
}
|
||||||
for d in future_days
|
for d in future_days
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user