From 30728a0db6efdd53099332a1b49313dca30c07e2 Mon Sep 17 00:00:00 2001 From: Satyam-Rastogi Date: Tue, 19 May 2026 02:31:57 +0530 Subject: [PATCH] feat(storm-intel): damage-probability homes estimate + pinned events total in header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useStormEvents: - Replace flat 1800/sq mi estimate with damage probability multiplier per type: hail ≥2.0" = 95%, 1.75" = 80%, 1.5" = 65%, 1.25" = 45%, 1.0" = 30%, <1.0" = 15%; tornado = 20% (path << polygon), flood = 25%, wind = 40%, winter = 20-30% - Expose damagePct on event object for display in drawer StormIntelPage: - pinnedHomes useMemo — sums estimatedHomes across event-pinned events - Header homes stat switches to amber + "Est. Homes · N pinned" label when events are pinned; hover tooltip notes zones may overlap - Drawer: "Est. Homes Affected" now shows "~X% damage probability" as sub-label --- src/hooks/useStormEvents.js | 24 +++++++++++++++++++++--- src/pages/StormIntelPage.jsx | 28 +++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 8 deletions(-) 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'} +