+
+ {/* Trust rule (spec §6): historical, not a forecast */}
+
+ This is a historical hazard estimate for your area — not a forecast or
+ guarantee for your specific home. Final assessment requires an on-site inspection.
+
+
+ );
+};
+
+export default HailRiskSnapshot;
diff --git a/src/modules/estimateWizard/components/PropertyMap.jsx b/src/modules/estimateWizard/components/PropertyMap.jsx
new file mode 100644
index 0000000..f5549cf
--- /dev/null
+++ b/src/modules/estimateWizard/components/PropertyMap.jsx
@@ -0,0 +1,48 @@
+/**
+ * PropertyMap — small Leaflet preview of the geocoded property + risk radius.
+ * Uses the same default-marker-icon fix the rest of the app uses (Maps.jsx).
+ */
+import React from 'react';
+import { MapContainer, TileLayer, Marker, Circle } from 'react-leaflet';
+import 'leaflet/dist/leaflet.css';
+import L from 'leaflet';
+import icon from 'leaflet/dist/images/marker-icon.png';
+import iconShadow from 'leaflet/dist/images/marker-shadow.png';
+
+const DefaultIcon = L.icon({
+ iconUrl: icon,
+ shadowUrl: iconShadow,
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+});
+L.Marker.prototype.options.icon = DefaultIcon;
+
+const PropertyMap = ({ lat, lon, radiusMiles = 20, height = 220 }) => {
+ if (lat == null || lon == null) return null;
+ const center = [lat, lon];
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default PropertyMap;
diff --git a/src/modules/estimateWizard/components/WizardShell.jsx b/src/modules/estimateWizard/components/WizardShell.jsx
index fe6f3cf..f3789a3 100644
--- a/src/modules/estimateWizard/components/WizardShell.jsx
+++ b/src/modules/estimateWizard/components/WizardShell.jsx
@@ -15,6 +15,7 @@ import ChoiceCards from './ChoiceCards';
import StartStep from './steps/StartStep';
import AddressStep from './steps/AddressStep';
import PremiumStep from './steps/PremiumStep';
+import HailRiskStep from './steps/HailRiskStep';
import PlaceholderStep from './steps/PlaceholderStep';
// Step registry — later phases swap PlaceholderStep entries for real components.
@@ -22,7 +23,7 @@ const STEP_COMPONENTS = {
start: StartStep,
address: AddressStep,
premium: PremiumStep,
- hailRisk: (props) => ,
+ hailRisk: HailRiskStep,
options: (props) => ,
addons: (props) => ,
recommendation: (props) => ,
diff --git a/src/modules/estimateWizard/components/steps/AddressStep.jsx b/src/modules/estimateWizard/components/steps/AddressStep.jsx
index 8ae650f..991968c 100644
--- a/src/modules/estimateWizard/components/steps/AddressStep.jsx
+++ b/src/modules/estimateWizard/components/steps/AddressStep.jsx
@@ -1,15 +1,30 @@
/**
* AddressStep — Screen 2 "Property validation" (spec §4).
- * Phase 1: confirm/edit the address captured at Start.
- * Phase 2 will add geocoding (lat/lon, county, timezone) + a Leaflet map preview
- * and populate property_profiles via /api/properties/validate-address.
+ * Geocodes the address (module-local Nominatim geocoder) and shows a Leaflet map
+ * preview, populating property_profiles (spec §12). Hail history is pulled in the
+ * next step from the existing /api/storm-history endpoint.
*/
-import React from 'react';
-import { MapPin, Map as MapIcon } from 'lucide-react';
+import React, { useState } from 'react';
+import { MapPin, Loader2, CheckCircle2, Search } from 'lucide-react';
import { useEstimateWizard } from '../../EstimateWizardContext';
+import { geocodeAddress } from '../../api/geocode';
+import PropertyMap from '../PropertyMap';
const AddressStep = () => {
- const { inputs, setInput } = useEstimateWizard();
+ const { inputs, setInput, session, patchSession, logEvent } = useEstimateWizard();
+ const [locating, setLocating] = useState(false);
+ const property = session.property;
+
+ const handleLocate = async () => {
+ setLocating(true);
+ try {
+ const prop = await geocodeAddress(inputs.address);
+ patchSession({ property: prop, hailSnapshot: null }); // re-locating invalidates a stale snapshot
+ logEvent('address_geocoded', { confidence: prop.confidence, fallback: prop.fallback });
+ } finally {
+ setLocating(false);
+ }
+ };
return (
- We’ll confirm the location on a map and pull your area’s historical storm data in the next step.
-
-
+ {property ? (
+ <>
+
+
+
+
+ {property.fallback
+ ? 'We placed an approximate pin for your area. Your rep will confirm the exact location.'
+ : <>Located: {property.displayName}>}
+
+
+ >
+ ) : (
+
+
+
+ Tap Locate to confirm your home on the map and pull local storm history.
+
+
+ )}
Final roof measurements are validated during your inspection — nothing here is binding.
diff --git a/src/modules/estimateWizard/components/steps/HailRiskStep.jsx b/src/modules/estimateWizard/components/steps/HailRiskStep.jsx
new file mode 100644
index 0000000..a09143a
--- /dev/null
+++ b/src/modules/estimateWizard/components/steps/HailRiskStep.jsx
@@ -0,0 +1,83 @@
+/**
+ * HailRiskStep — Screen 6 "Hail risk" (spec §6).
+ * Runs geocode → /api/storm-history → Poisson model, freezes the snapshot into
+ * the session (spec §12), and renders the historical-only risk card + map.
+ */
+import React, { useEffect } from 'react';
+import { Loader2, AlertCircle } from 'lucide-react';
+import { useEstimateWizard } from '../../EstimateWizardContext';
+import { useWizardHailRisk } from '../../hooks/useWizardHailRisk';
+import PropertyMap from '../PropertyMap';
+import HailRiskSnapshot from '../HailRiskSnapshot';
+
+const HailRiskStep = () => {
+ const { inputs, session, patchSession, tenantConfig, logEvent } = useEstimateWizard();
+
+ // Reuse a previously frozen snapshot if the user navigates back/forward.
+ const alreadyHave = !!session.hailSnapshot;
+
+ const { loading, error, property, snapshot } = useWizardHailRisk({
+ address: inputs.address,
+ property: session.property,
+ enabled: !alreadyHave,
+ stormSettings: tenantConfig.stormSettings,
+ });
+
+ // Freeze property + snapshot into the session once resolved.
+ useEffect(() => {
+ if (alreadyHave) return;
+ if (snapshot && property) {
+ patchSession({ property, hailSnapshot: snapshot });
+ logEvent('hail_snapshot_generated', {
+ exposureTier: snapshot.exposureTier,
+ p10Base: snapshot.probability10yr.base,
+ confidence: snapshot.confidence,
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [snapshot, property, alreadyHave]);
+
+ const shownProperty = session.property || property;
+ const shownSnapshot = session.hailSnapshot || snapshot;
+
+ if (loading && !shownSnapshot) {
+ return (
+
+
+
+ Checking historical storm reports for your area…
+