File size: 3,140 Bytes
277cbcf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | """Forecast-based parametric insurance trigger decision.
Given a 5-day WBGT forecast, decides whether to issue no trigger,
an alert cash transfer, or a full insurance payout. Used by the
LastMileBench insurance benchmark to test forecast-driven trigger
accuracy against ERA5 actuals ("perfect forecast") and GraphCast.
The logic counts consecutive days from the start of the window
where forecast WBGT meets the heat-wave threshold. Duration +
severity determine the trigger level.
"""
from __future__ import annotations
import sys
from pathlib import Path
# Import CRE's calculate_wbgt
_CRE_ROOT = str(Path(__file__).resolve().parent.parent.parent)
if _CRE_ROOT not in sys.path:
sys.path.insert(0, _CRE_ROOT)
from src.indexing.heat_index import calculate_wbgt
def forecast_trigger_decision(
forecast_wbgt: list[float], # 5 days of forecast WBGT values
alert_duration_days: int = 2,
payout_duration_days: int = 5,
window_threshold_c: float = 35.1, # threshold for "heat wave day"
payout_severity_c: float = 30.7, # min peak WBGT for full payout (always met in practice)
) -> str:
"""Predict trigger action from a 5-day WBGT forecast.
Counts consecutive forecast days (from day 1) above window_threshold_c.
Returns 'no_trigger', 'alert_cash', or 'full_payout'.
Args:
forecast_wbgt: List of daily WBGT values for the forecast window
(typically 5 days starting from window_start + 1).
alert_duration_days: Minimum consecutive days above threshold
for an alert_cash trigger (default 2).
payout_duration_days: Minimum consecutive days above threshold
for a full_payout trigger (default 5).
window_threshold_c: WBGT threshold for counting a day as a
heat-wave day (default 35.1, the ERA5-based P90).
payout_severity_c: Minimum peak WBGT required for full payout
in addition to the duration requirement (default 30.7,
always met in practice for Dar es Salaam).
Returns:
One of 'no_trigger', 'alert_cash', or 'full_payout'.
"""
# Count consecutive days from day 1 where WBGT >= threshold
consecutive_days = 0
for wbgt in forecast_wbgt:
if wbgt >= window_threshold_c:
consecutive_days += 1
else:
break
peak_wbgt = max(forecast_wbgt) if forecast_wbgt else 0.0
if (
consecutive_days >= payout_duration_days
and peak_wbgt >= payout_severity_c
):
return "full_payout"
if consecutive_days >= alert_duration_days:
return "alert_cash"
return "no_trigger"
def compute_wbgt_from_forecast(temp_c: float, humidity_pct: float) -> float:
"""Compute WBGT from forecast temperature and humidity.
Thin wrapper around the CRE project's calculate_wbgt function,
which uses the Liljegren simplified outdoor WBGT formula.
Args:
temp_c: Air temperature in degrees Celsius.
humidity_pct: Relative humidity in percent (0-100).
Returns:
Estimated outdoor WBGT in degrees Celsius.
"""
return calculate_wbgt(temp_c, humidity_pct)
|