diff --git a/src/hooks/useStormEvents.js b/src/hooks/useStormEvents.js index 42ce19d..49e42a4 100644 --- a/src/hooks/useStormEvents.js +++ b/src/hooks/useStormEvents.js @@ -111,9 +111,7 @@ function featureToEvent(feature, idx) { ? Math.round((new Date(expireTime) - new Date(issueTime)) / 60000) : null; - const areaSqMi = Math.round(polygonAreaSqMi(polygon)); - // Suburban DFW density ~1 800 homes/sq mi — conservative (polygons often include open land) - const estHomes = Math.round(areaSqMi * 1800); + const areaSqMi = Math.round(polygonAreaSqMi(polygon)); const type = ph === 'TO' ? 'tornado' : ph === 'FF' ? 'flood' @@ -128,6 +126,25 @@ function featureToEvent(feature, idx) { : ph === 'FF' ? 'moderate' : 'significant'; + // Damage probability: fraction of homes in the warning zone likely to have claimable damage. + // Tornado path is far narrower than the warning polygon, so we discount heavily. + // Hail probability scales with hail size — golf ball (2"+) damages nearly everything it hits. + const damagePct = + ph === 'TO' ? 20 + : ph === 'FF' ? 25 + : ph === 'WS' || ph === 'BZ' ? 30 + : ph === 'WW' || ph === 'IS' ? 20 + : hailIn === null ? 40 // SV wind only + : hailIn >= 2.0 ? 95 + : hailIn >= 1.75 ? 80 + : hailIn >= 1.5 ? 65 + : hailIn >= 1.25 ? 45 + : hailIn >= 1.0 ? 30 + : 15; // <1" — cosmetic, rarely claimed + + // Homes in zone × suburban DFW density (1 800/sq mi) × damage probability + const estHomes = Math.round(areaSqMi * 1800 * (damagePct / 100)); + return { id: `IEM-${WFO}-${ph}-${p.year ?? ''}-${p.eventid ?? idx}`, date: issueTime || new Date().toISOString(), @@ -142,6 +159,7 @@ function featureToEvent(feature, idx) { areaName: derivedAreaName(rawCoords, ph), county: 'Collin', areaSqMi, + damagePct, estimatedHomes: estHomes > 0 ? estHomes : null, description: `${PH_LABEL[ph] ?? ph} — NWS ${WFO}`, source: 'IEM/NWS', diff --git a/src/pages/StormIntelPage.jsx b/src/pages/StormIntelPage.jsx index d1a1b6f..c255da7 100644 --- a/src/pages/StormIntelPage.jsx +++ b/src/pages/StormIntelPage.jsx @@ -873,9 +873,11 @@ const StormDetailDrawer = ({ storm, open, onClose, onFlyTo, onAssignZone, canMan )} {storm.estimatedHomes > 0 && (
-

Est. Homes

+

Est. Homes Affected

{storm.estimatedHomes.toLocaleString()}

-

in warning zone

+

+ ~{storm.damagePct}% damage probability +

)} @@ -1273,6 +1275,15 @@ const StormIntelPage = () => { const { forecast, loading: forecastLoading, alertDay, hasStormAlert } = useStormForecast(); const { reports } = useStormReports(); + // Homes affected by pinned events (sum; note polygons may overlap) + const pinnedHomes = useMemo(() => { + if (pinnedIds.size === 0) return null; + return [...pinnedIds] + .map(id => events.find(e => e.id === id)) + .filter(Boolean) + .reduce((sum, e) => sum + (e.estimatedHomes ?? 0), 0); + }, [pinnedIds, events]); + // Text search filter const displayEvents = useMemo(() => { if (!searchQuery.trim()) return events; @@ -1458,9 +1469,16 @@ const StormIntelPage = () => {

Storms

-
-

{(totalHomes / 1000).toFixed(1)}k

-

Homes

+
+

+ {pinnedHomes !== null + ? (pinnedHomes >= 1000 ? `${(pinnedHomes / 1000).toFixed(1)}k` : pinnedHomes.toLocaleString()) + : (totalHomes >= 1000 ? `${(totalHomes / 1000).toFixed(1)}k` : totalHomes.toLocaleString()) + } +

+

+ {pinnedHomes !== null ? `Est. Homes · ${pinnedIds.size} pinned` : 'Est. Homes'} +