twx-risk-severity-libraryA 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.
>=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.
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.
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):
| Method | Severity is… | Verdict | Example |
|---|---|---|---|
curve | a loss ratio from a dose-response curve | Directional | drought (FAO-33), heat (Schlenker EDD) |
fraction | an affected-area footprint (exposure) | Exposure | flood inundation, burnt-area fraction |
return_period | probabilistic exposure | Exposure | floodplain 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.
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 metric — persistence (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:
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):
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.
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).
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/PET | drought / water dose → damage curve |
| Flood mask (Sentinel-1 backscatter drop − JRC water) | exposure fraction (new dimension) |
| VHI / VCI / TCI stressed-fraction | combined drought severity + exposure fraction |
| Red-edge / LST anomaly z-score | disease/pest dose (screening-grade, low confidence) |
| NDVI / EVI / LAI / FPAR, phenology metrics | consequence / 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.
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
pyproject/version/py.typed, consumed like phenoweight; not an in-repo folder reading repo-root config/.(crop, peril, metric, scope) where scope is an admin hierarchy; a pixel is matched to its ADM0/ADM2 via boundary geometries (a spatial join, not a string match) and gets the most specific curve available, falling back up the hierarchy to global. Verdict/status are independent fields, so a country-specific literature curve and a location-specific calibrated curve for the same peril coexist and resolve deterministically. This is the biggest gap today — curves are one-per-peril, globally applied.phenoweight), not coupled to yield-model data or infra.SeverityCurve objects; each curve records its raw source + method + verdict + status + scope, so its derivation is auditable and reproducible rather than convention-in-a-filename or hand-edited YAML.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.
twx-phenoweight (exists) | twx-risk-severity-library (proposed) | |
|---|---|---|
| Answers | WHEN a peril matters | HOW BAD it is |
| Data unit | timing kernel (Gaussian components) | severity curve (dose → 0–1 loss) |
| Keyed by | commodity · region · variable | crop · peril · metric · location |
| Packaging | standalone installable wheel | standalone installable wheel |
| Fed by | agronomic phenology | literature and calibrated consequence (yield / financial) |
| Consumed by | ag_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/.