terraflow.drought¶
Impact-labeled drought-loss prediction benchmark. Predicts insured drought loss (USDA RMA Cause of Loss) from within-season climate/vegetation anomalies. See the Drought benchmark guide for the task, splits, and reference results.
Configuration¶
config
¶
Configuration model for the drought-impact benchmark.
The benchmark predicts insured drought loss (USDA RMA Cause of Loss) from within-season climate/vegetation predictors. This module defines the single Pydantic config that drives the whole build so runs are reproducible and fingerprintable.
RMA Cause of Loss ingest¶
rma
¶
Ingest USDA RMA Cause of Loss Summary-of-Business files.
The Cause of Loss Historical Data Files are public, pipe-delimited flat files (one ZIP per year, 1989–present) published at https://www.rma.usda.gov/tools-reports/summary-of-business/cause-loss
Each record is broken down by year × state × county × commodity × insurance-plan × coverage ×
stage × cause-of-loss × month-of-loss. The 30-field record layout is stable across the benchmark's
year range (verified 2000 & 2012 → exactly 30 fields). Fields are space-padded within the pipe
delimiters and numerics are zero-/dot-padded (e.g. 00.0000000000), so string fields are
stripped and numeric fields coerced.
col_url(year)
¶
download_col_years(years, dest_dir, *, overwrite=False)
¶
Download (and cache) the Cause of Loss ZIP for each year into dest_dir.
Returns the list of local ZIP paths. Existing files are reused unless overwrite=True.
Source code in terraflow/drought/rma.py
load_col(rma_dir, years, *, states=None, commodity=None)
¶
Load and concatenate Cause of Loss records for years from rma_dir.
Accepts either colsom_YYYY.zip or the extracted colsomYY.txt in rma_dir. states /
commodity filters are pushed down into each file for speed.
Source code in terraflow/drought/rma.py
parse_col_file(path, *, states=None, commodity=None)
¶
Parse a single Cause of Loss file (.txt or .zip) into a tidy DataFrame.
String fields are stripped; numeric fields are coerced. Adds a 5-digit GEOID (state FIPS +
county FIPS) matching the flashdry predictor tables. Optional states / commodity filters
are applied before the (costly) strip/convert of every column, which keeps whole-corpus loads
fast when only one crop/region is needed.
Source code in terraflow/drought/rma.py
Labels¶
labels
¶
Build county-crop-year drought-loss labels from parsed RMA Cause of Loss records.
The impact label is what makes this benchmark distinct from severity (USDM D2+) and yield (CY-Bench/SustainBench) benchmarks: it is the insured drought loss actually paid out.
:func:build_labels aggregates Cause of Loss into per-(GEOID, year) numerators (drought/total
indemnity) plus a fallback loss-experience liability. :func:finalize_targets then computes the
primary targets once the true total insured liability (from the Summary-of-Business coverage file)
and planted acres are joined:
drought_indemnity= Σ indemnity where cause_of_loss_desc ∈ drought_causestotal_indemnity= Σ indemnity over all causesdrought_share= drought_indemnity / total_indemnity (clean, in [0, 1])drought_loss_ratio= drought_indemnity / total insured liability (primary regression target)significant_drought_loss= drought_loss_ratio ≥ threshold (binary target)insured_acre_fraction= insured acres / NASS planted acres (coverage-bias column)
Using the Summary-of-Business total liability (all policies) as the denominator removes the >1 artifact of the Cause-of-Loss loss-experience liability (only loss-experiencing policies).
build_labels(col, cfg)
¶
Aggregate parsed Cause of Loss records into per-(GEOID, year) numerators.
Produces drought/total indemnity, the loss-experience (Cause-of-Loss) liability as a fallback
denominator col_liability, and the bounded drought_share. The primary
drought_loss_ratio / significant_drought_loss targets are computed by
:func:finalize_targets once the true Summary-of-Business total liability is joined.
Source code in terraflow/drought/labels.py
finalize_targets(df, cfg)
¶
Compute the final targets on the fully-joined benchmark frame.
Uses the true Summary-of-Business total_liability as the drought-loss-ratio denominator when
present (removing the loss-experience >1 artifact), else falls back to col_liability. Adds the
insured_acre_fraction coverage-bias column when both insured and planted acres are available.
Source code in terraflow/drought/labels.py
Predictors¶
predictors
¶
Aggregate flashdry within-season predictors to one vector per (GEOID, crop-year).
The flashdry feature_table.parquet is sub-annual (growing-season, ~biweekly). For an
early-warning task we aggregate each base feature over the season up to cutoff_doy — so a
model only sees signal available by, e.g., end of July — into {mean, min, max, last} summaries.
Two feature families are tagged so baselines can select them:
- climate/vegetation — the 30 deseasonalized *_anom features + NDVI_anom_z
- severity — dm_gte_d2 / dm_class (USDM drought severity). Aggregating these lets us
build a severity-only baseline and quantify how much within-season climate signal adds on top
of the (strong) USDM severity indicator for predicting realized drought loss.
aggregate_predictors(feature_table, cfg)
¶
Aggregate within-season predictors up to cfg.cutoff_doy per (GEOID, year).
Returns one row per (GEOID, year) with {mean, min, max, last} of every climate and severity
feature, plus n_obs (observations before the cutoff) and n_stress_weeks (vegetation
≤ −1 SD). last is the value at the largest DOY at or before the cutoff.
Source code in terraflow/drought/predictors.py
Splits¶
splits
¶
Reproducible evaluation splits for the drought-impact benchmark.
Three complementary regimes, all deterministic from the config (no RNG): - temporal — held-out years (incl. the 2012 extreme + recent 2022/2023) test extrapolation to unseen seasons. - spatial — leave-one-state-out blocks test spatial transfer (guards against spatially autocorrelated leakage; finer lon/lat grids are a follow-up). - loyo — leave-one-year-out folds for a stability estimate across all years.
Split definitions (not enumerated rows) are serialized so the artifact is small and the masks are re-derivable from any benchmark table.
describe_splits(cfg)
¶
JSON-serializable split definition (re-derivable, not enumerated).
Source code in terraflow/drought/splits.py
loyo_folds(df)
¶
Leave-one-year-out folds: (year, train_mask, test_mask).
Source code in terraflow/drought/splits.py
spatial_block_ids(df)
¶
spatial_folds(df)
¶
Leave-one-state-out folds: (state, train_mask, test_mask).
Source code in terraflow/drought/splits.py
temporal_masks(df, cfg)
¶
(train_mask, test_mask) for the official temporal split.
Source code in terraflow/drought/splits.py
Dataset assembly¶
dataset
¶
Assemble the drought-impact benchmark table and load it back.
Pipeline: parse RMA Cause of Loss → numerator labels → join the true total insured liability (Summary-of-Business coverage file) and NASS planted acres → aggregate flashdry predictors → LEFT-join predictors ⋈ labels on (GEOID, year) → finalize the drought-loss-ratio + binary + coverage targets. County-years present in the predictor panel but absent from Cause of Loss are genuine insured-loss negatives (drought_loss_ratio = 0, not significant) and are retained and filled.
Writes benchmark.parquet + manifest.json (config snapshot + input fingerprints + a
deterministic build fingerprint) + splits.json.
assemble_benchmark(cfg, *, write=True, nass_api_key=None)
¶
Build the benchmark table (and, by default, persist artifacts under cfg.output_dir).
Source code in terraflow/drought/dataset.py
build_fingerprint(cfg, input_digests)
¶
Deterministic build fingerprint over the canonical config + input file digests.
Source code in terraflow/drought/dataset.py
load_benchmark(output_dir)
¶
Load a previously assembled benchmark.parquet.
Source code in terraflow/drought/dataset.py
Baselines & metrics¶
baselines
¶
Baseline models for the drought-impact benchmark.
Three tiers, to make the leaderboard interpretable: - naive — constant train mean, and per-county historical mean (the bar any model must beat). - severity-only — a model on the USDM severity aggregates alone. USDM D2+ is a strong baseline for loss; the point of isolating it is to quantify how much within-season climate signal adds on top (and that it is available earlier in the season for early warning). - climate ML — Ridge / RandomForest / GradientBoosting on the within-season anomaly features.
All estimators are seeded (random_state=0, n_jobs=1) so the leaderboard is deterministic.
CountyHistoryBaseline
¶
Predict each county's historical (training) mean target, falling back to the global mean.
MeanBaseline
¶
Predict the constant training mean of the target.
feature_columns(which)
¶
Feature column names for a feature set: 'climate', 'severity', or 'all'.
Source code in terraflow/drought/baselines.py
feature_matrix(df, which)
¶
Numeric feature matrix (NaN → 0.0; anomalies are z-scores, so 0 is neutral).
metrics
¶
Evaluation metrics for the drought-impact benchmark.
Regression (continuous drought_loss_ratio): R², RMSE, and Spearman rank correlation — the
rank metric is the robust headline because the loss-experience ratio has a heavy tail and can
exceed 1 (see :mod:terraflow.drought.labels).
Classification (binary significant_drought_loss): ROC-AUC, average precision (PR-AUC — the
positive class is imbalanced, so PR-AUC is the honest headline), and Brier score.
classification_metrics(y_true, y_score)
¶
ROC-AUC, average precision (PR-AUC), Brier. AUC/AP are NaN if only one class present.
Source code in terraflow/drought/metrics.py
regression_metrics(y_true, y_pred)
¶
R², RMSE, Spearman ρ. Returns NaNs gracefully for degenerate inputs.
Source code in terraflow/drought/metrics.py
Evaluation¶
evaluate
¶
Run the drought-impact benchmark leaderboard.
Headline = the official temporal split (train on early years, test on held-out years incl. the 2012 extreme). Also reports a spatial leave-one-state-out summary for the climate models. The severity-only baseline is included in both to expose the severity≠impact gap.
Writes evaluate_report.json and leaderboard.csv to the run's output directory.
run_leaderboard(benchmark, cfg, *, write_dir=None)
¶
Compute the full leaderboard; optionally persist report + CSV to write_dir.