locate adress on map and find hail risk

This commit is contained in:
Mayur Shinde
2026-06-09 16:15:21 +05:30
parent 716d096fc8
commit 110790d5ae
8 changed files with 522 additions and 21 deletions
@@ -0,0 +1,95 @@
/**
* hailProbability.js — conservative historical hail-risk model (spec §6).
*
* IMPORTANT framing (spec §6): the output is a HISTORICAL hazard estimate for the
* surrounding area, NOT a property-level forecast or guarantee. The model computes
* a RANGE (confidence band), not one overconfident number, and stores every
* assumption so the report can be defended.
*
* lambda (λ) = deduped hail events / years
* P(at least one hail event over N years) = 1 - e^(-λN)
*
* Pure function — no I/O. Fed by /api/storm-history (existing storm module, used
* read-only). Returns the hail_risk_snapshot shape (spec §12).
*/
function clamp(n, lo, hi) { return Math.max(lo, Math.min(hi, n)); }
function pct(n) { return Math.round(n * 100); }
/**
* @param {object} input
* eventCount {number} deduped hail events observed in the window
* windowYears {number} historical window analyzed
* radiusMiles {number} search radius around the property
* geocodeConfidence {'high'|'moderate'|'low'}
* maxHailInches {number|null}
* lastEventDate {string|null} ISO date of most recent hail event
* source {string} human-readable data source label
* generatedAt {string} ISO timestamp the snapshot was frozen
*/
export function computeHailRisk(input) {
const {
eventCount = 0,
windowYears = 5,
radiusMiles = 20,
geocodeConfidence = 'moderate',
maxHailInches = null,
lastEventDate = null,
source = 'NOAA / IEM Local Storm Reports',
generatedAt = new Date().toISOString(),
} = input || {};
const years = Math.max(1, windowYears);
const count = Math.max(0, eventCount);
// Annualized rate (events/year).
const lambda = count / years;
// Poisson confidence band using event-count standard error (√count).
const se = Math.sqrt(count);
const lambdaLow = Math.max(0, (count - se) / years);
const lambdaHigh = (count + se) / years;
const prob = (lam, n) => 1 - Math.exp(-lam * n);
const p10Base = prob(lambda, 10);
const p10Low = prob(lambdaLow, 10);
const p10High = prob(lambdaHigh, 10);
// Confidence score (0..1): sample size + window length + geocode precision.
const sampleScore = clamp(count / 8, 0, 1); // ~8+ events → strong
const windowScore = clamp(years / 10, 0, 1); // 10y window → full
const geoScore = geocodeConfidence === 'high' ? 1 : geocodeConfidence === 'moderate' ? 0.6 : 0.3;
const confidenceValue = +(0.4 * sampleScore + 0.3 * windowScore + 0.3 * geoScore).toFixed(2);
const confidenceLabel = confidenceValue >= 0.66 ? 'High' : confidenceValue >= 0.4 ? 'Moderate' : 'Low';
// Coarse exposure tier used downstream by the scoring engine (Phase 4).
const exposureTier = p10Base >= 0.66 ? 'high' : p10Base >= 0.33 ? 'moderate' : 'low';
return {
// hail_risk_snapshot fields (spec §12)
source,
windowYears: years,
radiusMiles,
eventCount: count,
annualRate: +lambda.toFixed(3),
maxHailInches,
lastEventDate,
probability10yr: { low: pct(p10Low), base: pct(p10Base), high: pct(p10High) },
confidence: confidenceLabel,
confidenceValue,
exposureTier,
generatedAt,
// Customer-facing wording (spec §6) — historical, never a forecast.
wording: {
annualRate: count === 0
? 'No qualifying hail events were found in the available reports for this area.'
: `This area has averaged about ${lambda.toFixed(1)} observed hail event(s) per year in the available dataset.`,
probability: count === 0
? 'There is not enough historical hail activity nearby to estimate a meaningful probability.'
: `Based on historical reports, the chance of at least one hail event in the next 10 years is estimated at ${pct(p10Low)}${pct(p10High)}%, assuming future patterns resemble the past.`,
confidence: `Confidence: ${confidenceLabel}. This is based on county-level and nearby reports within ~${radiusMiles} miles, not a roof-specific inspection.`,
lastUpdated: `Storm data reflects reports through ${new Date(generatedAt).toLocaleDateString()}.`,
},
};
}