/** * useWizardHailRisk — produces a frozen hail-risk snapshot for the wizard. * * Pipeline (all read-only against existing services): * 1. geocode the address (module-local geocode.js → Nominatim) * 2. GET /api/storm-history (EXISTING storm module endpoint — not modified) * 3. run computeHailRisk() (module-local Poisson model, spec §6) * * Returns { loading, error, property, snapshot }. The caller freezes `snapshot` * into the session (spec §12 hail_risk_snapshots) so the report stays immutable. */ import { useState, useEffect } from 'react'; import { geocodeAddress } from '../api/geocode'; import { computeHailRisk } from '../engine/hailProbability'; export function useWizardHailRisk({ address, property, enabled = true, stormSettings = {} }) { const [loading, setLoading] = useState(enabled); const [error, setError] = useState(null); const [resolvedProperty, setResolvedProperty] = useState(property || null); const [snapshot, setSnapshot] = useState(null); const radiusMiles = stormSettings.radiusMiles ?? 20; // storm-history caps months at 60 (5 years); window honesty is stored in the snapshot. const months = 60; const windowYears = months / 12; useEffect(() => { if (!enabled) return; let cancelled = false; setLoading(true); setError(null); (async () => { try { // 1. Resolve coordinates (reuse stored property if already geocoded). let prop = property; if (!prop || prop.lat == null || prop.lon == null) { prop = await geocodeAddress(address); } if (cancelled) return; setResolvedProperty(prop); // 2. Existing keyless storm endpoint (read-only). let hailCount = 0; let maxHail = null; let lastEventDate = null; let source = 'NOAA / IEM Local Storm Reports'; try { const res = await fetch( `/api/storm-history?lat=${prop.lat}&lng=${prop.lon}&months=${months}&radiusMi=${Math.min(radiusMiles, 50)}` ); if (res.ok) { const data = await res.json(); hailCount = data?.summary?.byType?.hail ?? 0; maxHail = data?.summary?.maxHail ?? null; lastEventDate = data?.summary?.lastEvent?.date ?? null; } } catch { // Leave defaults (count 0) — computeHailRisk handles the empty case honestly. } if (cancelled) return; // 3. Conservative Poisson model. const snap = computeHailRisk({ eventCount: hailCount, windowYears, radiusMiles, geocodeConfidence: prop.confidence, maxHailInches: maxHail, lastEventDate, source, generatedAt: new Date().toISOString(), }); if (!cancelled) setSnapshot(snap); } catch (e) { if (!cancelled) setError(e.message || 'Could not load storm history.'); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [address, enabled, radiusMiles]); return { loading, error, property: resolvedProperty, snapshot }; }