twx-risk-severity-library

A standalone severity library — the HOW BAD catalogue for crop-risk scoring · proposal + current state · 2026-07-16

twx-risk-severity-library is a standalone, versioned, installable catalogue of severity functions: for a given peril, crop and location, how a peril's signal maps to a 0–1 loss. Most are damage curves (dose → loss), but not all — some are exposure measures (a flood/fire footprint, a floodplain zone); "curve" is one of three methods, not the whole library. It's an independent library that any risk calculation can read — ag_risk is one consumer, exactly as it reads twx-phenoweight for the WHEN. Entries come from different sources — published literature today, and calibrated to real data (yield or financial) as they mature — and can be scoped from global down to a specific location.
Status (2026-07-16) — skeleton built & pushed; curves migrated; floored at >=0.1.0 (CodeArtifact repoint pending). The twx-risk-severity-library repo is scaffolded (typed models, method registry, scope resolution, loader, build-time transforms, CLI stub) with tests green, and the 11 real curves are migrated in from ag_risk's config/severity/ — transformed to the new schema (crop / peril / metric / scope: global / status), each keeping its source + citation and any calibration block. All 11 are global scope today (ADM overrides are the future pattern); the stale floodplain_return_period example was dropped. The library is floored at >=0.1.0 and consumed by ag_risk, though [tool.uv.sources] still git-pins it to main (the CodeArtifact repoint is pending) — its in-repo config/severity/ is retired.

1. The idea — a sibling to twx-phenoweight

The model is signal → phenoweight (WHEN) → severity (HOW BAD) → combine. twx-phenoweight already owns the WHEN as a shared library. twx-risk-severity-library does the same for the HOW BAD: one place, shared across models, that turns a peril's dose into a 0–1 severity on a common loss ruler.

The severity library in context Feature data pipelines · ERA5/SEAS5 · GEE · models twx-phenoweight WHEN — timing curves twx-risk-severity-library HOW BAD — severity functions (new standalone repo) config wires them per dimension ag_risk engine severity → weight-free combine risk scores
The severity library is the HOW-BAD sibling of twx-phenoweight; the engine reads both plus the data, all wired in config.

2. What a severity function is (the data unit)

Each entry maps a peril's signal to a severity ∈ [0,1] on one shared ruler: expected fractional loss attributable to that peril in its sensitive window (0 = none, 1 = total). An entry is data, not code, and takes whatever form fits the peril — most are damage curves, but not all. The method is just how the number is produced (only the first is a curve):

MethodSeverity is…VerdictExample
curvea loss ratio from a dose-response curveDirectionaldrought (FAO-33), heat (Schlenker EDD)
fractionan affected-area footprint (exposure)Exposureflood inundation, burnt-area fraction
return_periodprobabilistic exposureExposurefloodplain 1-in-N

There is no "model emits severity" method. A detection model (disease, pest, …) is a data source — its output is turned into a curve (or exposure measure) upstream, via the feature-importance / calibration step, and stored like any other. The library only ever holds curves + exposure measures, and just reads them.

To look at — exposure perils need two inputs, not a 1-D curve. Flood (and fire) don't fit a single dose→severity curve: severity = exposure × conditional-damage. The fraction (inundation area / burn area) is the exposure and comes straight from the signal; the conditional-damage is a library curve on an intensity metricpersistence (days underwater) or depth for flood, burn severity for fire. So what the library actually stores for flood is the persistence → conditional-loss curve; the fraction is a signal-side multiplier. This needs a method richer than the 1-D fraction mode — a two-input exposure × curve method. Today's plain fraction (conditional-damage = 1, i.e. flooded = write-off) is the honest interim; the two-input version is the upgrade. Design decision to make.

Every curve carries provenance (source, citation) plus three independent axes that travel to the output and must not be conflated:

3. Where a curve comes from — the calibration pipeline

A curve isn't "literature or calibrated" — calibration is a pipeline every curve runs. Even a purely-literature library needs step 1, because raw literature arrives on incompatible y-axes (FAO-33 is fractional loss, FAO-56 a Ks coefficient, Bayer a 0–5 ordinal, Schlenker a normalised ramp):

Step 1 — harmonise onto the ruler Directional · all curves

Rescale each literature curve onto the shared loss ruler with a transform (ky_et_deficit, ordinal_to_loss, linear_slope) so a 0.6 means the same thing everywhere. This is calibration of the literature curves — it's what the model does today, and it's mandatory, or the weight-free combine compares apples to ordinals. Verdict stays Directional, status stays literature-anchored.

Step 2 — re-anchor to real consequence Validated · where data exists

The proposal's core, and the future work — but it runs upstream, not in the library. A consequence model — an MI yield model or financial / market loss — works out which features actually drove the loss, and by how much, where, and produces a re-anchored curve (a yield_fit). That finished curve, with its lineage, is deposited into the library; the library stores it and its status becomes Validated. The library never runs the fit itself — it stores results. Only some curves reach step 2; the rest stay honestly Directional.

Scope is a separate axis from all of this. Both a Directional (step-1) and a Validated (step-2) curve can sit at any scope — ADM0 (country) → ADM2 (district) → location — e.g. Bayer maize thresholds calibrated for Kenya (ADM0) are country-specific but still Directional. So the library does not split "literature = global, calibrated = local"; it keys by scope (resolved via ADM boundaries) and carries verdict/status as fields (see §5).

How data gets into the severity library Literature → harmonise onto ruler (step 1 · Directional) Consequence model MI yield model / financial-market loss Feature importance which features drove loss — and where Fit → curve location-tagged, Validated twx-risk- severity-library catalogue keyed by crop · peril · metric · location provenance · verdict · status
Every curve is harmonised onto the ruler (step 1, Directional); some are then re-anchored to consequence (step 2, Validated). Scope (global↔location) is a separate axis, carried as a field.
Why this leverages MI directly: Market Insights already runs yield models that learn which features matter. That feature-importance work is exactly the input a calibrated severity curve needs — we reuse it to decide which peril/feature dominates where, and to anchor the curve's magnitude to observed loss. The severity library is the place that knowledge is encoded and versioned, per location, rather than re-derived per model.

4. What feeds the curves — signals, exposure, and consequence

The tf_environmental_factors_preparation GEE workflow is a ready source of pluggable inputs, and it feeds the library on both sides — new peril signals and the consequence data used to calibrate:

Signal (GEE source)Role for a severity curve
LST / EDD / VPD / degree-days (MODIS, GRIDMET, ERA5-Land)heat dose → damage curve
SPI / SPEI / EDDI / SMAP soil moisture / ESI = 1−ET/PETdrought / water dose → damage curve
Flood mask (Sentinel-1 backscatter drop − JRC water)exposure fraction (new dimension)
VHI / VCI / TCI stressed-fractioncombined drought severity + exposure fraction
Red-edge / LST anomaly z-scoredisease/pest dose (screening-grade, low confidence)
NDVI / EVI / LAI / FPAR, phenology metricsconsequence / validation — an outcome proxy that calibrates curves, not a driver

Notable: the workflow's anomaly.py Vietnam-coffee cascade is essentially a working severity engine already — it turns each layer into a robust z-anomaly, maps z → [0,1] stress, and phenology-weights it. It's a concrete template for producing curve inputs, and its vegetation outputs are exactly the consequence data the calibration path needs.

5. Proposed structure — mirror twx-phenoweight

Package it like phenoweight: a standalone, versioned, typed, installable library consumed as a dependency (not vendored in-tree). Curves ship as package data; a location override dir is env-configurable.

# twx-risk-severity-library/
pyproject.toml            # standalone package, pinned deps, py.typed, CLI entry point
src/severity_library/
  models.py               # typed SeverityCurve + SeverityCatalog (to_dict/from_dict)
  modes.py                # registry: curve · fraction · return_period
  build.py                # OPTIONAL build-time: (re)generate a stored curve from raw + method (harmonise). NOT called at read time.
  resolve.py              # scope: ADM0 → ADM2 → location, most-specific wins (resolved via ADM boundaries); verdict/status independent
  io.py                   # load_catalog — read data/ (or $SEVERITY_LIBRARY_DIR)
  py.typed
  cli.py                  # check · show · plot · validate-lineage · coverage (stub)
  data/                   # the 11 curves migrated from ag_risk — finished, ruler-native, ALL global scope today
    maize_water_deficit_fao33.yaml        # drought · Directional · global
    maize_heat_edd_schlenker.yaml         # heat · Directional · global
    … 9 more (maize drought/heat/cold/water/veg + coffee)
    # ADM0 / ADM2 overrides (e.g. …__KE.yaml) are the future pattern — none yet

The five structural upgrades vs today's in-repo severity module

6. How ag_risk consumes it (unchanged interface)

A config signal declares its kind (curve · fraction · return_period); the curve then resolves automatically by (crop, dimension, signal.name) — there is no per-signal severity_function name. Resolution becomes scope-aware (most-specific curve wins). Config lists the features/curves; each month the engine reads the library + phenoweight + the pipeline data and combines them — now producing scores calibrated to a location wherever a calibrated curve exists, and falling back up the ADM hierarchy to a global literature curve elsewhere. No engine change, no combine change. Two moves come with it: ag_risk's current in-model harmonisation (severity/calibrate.py) is pulled out — the model just reads finished curves from the library — and each run pins the severity-library version so a rerun reproduces exactly.

7. The parallel, at a glance

twx-phenoweight (exists)twx-risk-severity-library (proposed)
AnswersWHEN a peril mattersHOW BAD it is
Data unittiming kernel (Gaussian components)severity curve (dose → 0–1 loss)
Keyed bycommodity · region · variablecrop · peril · metric · location
Packagingstandalone installable wheelstandalone installable wheel
Fed byagronomic phenologyliterature and calibrated consequence (yield / financial)
Consumed byag_risk (and other MI models)ag_risk (and other MI models)

Sources reviewed: phenoweight package (v0.1.1); ag_risk/severity/* — the model now lives in Model Foundry (model_foundry/models/ag_risk/); tf_environmental_factors_preparation (GEE workflow). The library is floored at twx-risk-severity-library>=0.1.0, though the workspace still git-pins it to main (the CodeArtifact repoint is pending), no longer an in-repo config/severity/.