88 lines
3.6 KiB
JavaScript
88 lines
3.6 KiB
JavaScript
/**
|
|
* 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 };
|
|
}
|