diff --git a/backend/src/lib/summary.js b/backend/src/lib/summary.js index 9283f8e..f541a44 100644 --- a/backend/src/lib/summary.js +++ b/backend/src/lib/summary.js @@ -27,6 +27,13 @@ export function summarizeToday(points, nowHour) { const rainFraction = rainPoints.length / points.length const snowFraction = snowPoints.length / points.length + // precip_prob is a separate field from the icon (see the frontend fix this + // replaces) — an hour's icon can be non-rain while it still carries a real, if + // slim, numeric chance. Worth surfacing in the headline rather than a flat "Dry" + // that silently disagrees with a "5%" sitting right there on an hour card. + const precipValues = points.map(p => p.precip_prob).filter(v => v != null) + const maxPrecip = precipValues.length ? Math.max(...precipValues) : null + const laterPoints = points.filter(p => hourOf(p) >= nowHour) const earlierPoints = points.filter(p => hourOf(p) < nowHour) const laterWetFraction = laterPoints.length @@ -38,7 +45,7 @@ export function summarizeToday(points, nowHour) { if (snowFraction > 0.3) { headline = 'Snow expected today' } else if (rainPoints.length === 0 && thunderPoints.length === 0) { - headline = 'Dry today' + headline = maxPrecip != null && maxPrecip >= 15 ? 'Mostly dry, slim chance of rain' : 'Dry today' } else if (rainPoints.some(isHeavyRain) && rainFraction > 0.5) { headline = 'Heavy rain today' } else if (rainFraction > 0.6) { diff --git a/frontend/src/pages/Display.tsx b/frontend/src/pages/Display.tsx index 2286120..da37a09 100644 --- a/frontend/src/pages/Display.tsx +++ b/frontend/src/pages/Display.tsx @@ -33,12 +33,6 @@ function compassFromDeg(deg: number): string { return COMPASS[Math.round(deg / 45) % 8] } -// Below this, a rain% reads as noise, not information — and worse, it can visibly -// contradict the "Dry today" headline (which is icon-based, not precip_prob-based: -// an hour can have a non-rain icon and still carry a token 5% chance). Hiding trivial -// percentages keeps what's shown consistent with what "Dry today" is actually saying. -const MIN_PRECIP_DISPLAY = 15 - function roundTemp(c: number): string { return `${Math.round(c)}°` } @@ -61,15 +55,14 @@ function AstroRow({ astro }: { astro?: Astro }) { // the text size for today's hourly cards; the next-2-days cards stay at the // smaller default so today still reads as the primary/detailed view. function PointMeta({ point, large }: { point: ForecastPoint; large?: boolean }) { - const showPrecip = point.precip_prob != null && point.precip_prob >= MIN_PRECIP_DISPLAY - if (!showPrecip && point.wind_speed_mph == null) return null + if (point.precip_prob == null && point.wind_speed_mph == null) return null const iconSize = large ? 13 : 10 return ( - {showPrecip && ( + {point.precip_prob != null && ( - {Math.round(point.precip_prob!)}% + {Math.round(point.precip_prob)}% )} {point.wind_speed_mph != null && ( @@ -172,8 +165,8 @@ export default function Display() { {data.summary && {data.summary.headline}} {currentPoint.feels_like_c != null && <>Feels like {roundTemp(currentPoint.feels_like_c)}} - {currentPoint.feels_like_c != null && currentPoint.precip_prob != null && currentPoint.precip_prob >= MIN_PRECIP_DISPLAY && ' · '} - {currentPoint.precip_prob != null && currentPoint.precip_prob >= MIN_PRECIP_DISPLAY && <>{Math.round(currentPoint.precip_prob)}% chance of rain} + {currentPoint.feels_like_c != null && currentPoint.precip_prob != null && ' · '} + {currentPoint.precip_prob != null && <>{Math.round(currentPoint.precip_prob)}% chance of rain} {data.summary?.note && {data.summary.note}}