feat(storm-intel): damage-probability homes estimate + pinned events total in header

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
This commit is contained in:
Satyam-Rastogi
2026-05-19 02:31:57 +05:30
parent 4c32266f3d
commit 30728a0db6
2 changed files with 44 additions and 8 deletions
+23 -5
View File
@@ -873,9 +873,11 @@ const StormDetailDrawer = ({ storm, open, onClose, onFlyTo, onAssignZone, canMan
)}
{storm.estimatedHomes > 0 && (
<div className="rounded-xl bg-zinc-50 dark:bg-zinc-800/60 border border-zinc-100 dark:border-white/[0.06] p-3">
<p className="text-[10px] font-bold uppercase tracking-wider text-zinc-400 mb-1">Est. Homes</p>
<p className="text-[10px] font-bold uppercase tracking-wider text-zinc-400 mb-1">Est. Homes Affected</p>
<p className="text-xl font-black text-zinc-900 dark:text-white">{storm.estimatedHomes.toLocaleString()}</p>
<p className="text-[10px] text-zinc-400">in warning zone</p>
<p className="text-[10px] text-zinc-400">
~{storm.damagePct}% damage probability
</p>
</div>
)}
</div>
@@ -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 = () => {
<p className="text-[10px] font-bold uppercase tracking-wider text-zinc-400">Storms</p>
</div>
<div className="w-px h-8 bg-zinc-200 dark:bg-white/10" />
<div className="text-right">
<p className="text-xl font-black text-zinc-900 dark:text-white">{(totalHomes / 1000).toFixed(1)}k</p>
<p className="text-[10px] font-bold uppercase tracking-wider text-zinc-400">Homes</p>
<div className="text-right" title={pinnedHomes !== null ? 'Sum of est. damaged homes across pinned events — zones may overlap' : 'Est. homes with claimable damage across all visible events'}>
<p className={`text-xl font-black ${pinnedHomes !== null ? 'text-amber-500' : 'text-zinc-900 dark:text-white'}`}>
{pinnedHomes !== null
? (pinnedHomes >= 1000 ? `${(pinnedHomes / 1000).toFixed(1)}k` : pinnedHomes.toLocaleString())
: (totalHomes >= 1000 ? `${(totalHomes / 1000).toFixed(1)}k` : totalHomes.toLocaleString())
}
</p>
<p className="text-[10px] font-bold uppercase tracking-wider text-zinc-400">
{pinnedHomes !== null ? `Est. Homes · ${pinnedIds.size} pinned` : 'Est. Homes'}
</p>
</div>
<div className="w-px h-8 bg-zinc-200 dark:bg-white/10" />
<div className="text-right">