| """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 |
|
|
| |
| _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], |
| alert_duration_days: int = 2, |
| payout_duration_days: int = 5, |
| window_threshold_c: float = 35.1, |
| payout_severity_c: float = 30.7, |
| ) -> 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'. |
| """ |
| |
| 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) |
|
|