Skip to content

Entity: DeliveryConfig

Definition

DeliveryConfig is a frozen Pydantic model nested inside ExperimentConfig that governs the DELIVER stage. It specifies the public-facing model name written into every delivery CSV, the set of conformal prediction interval levels to materialise as column pairs, and two post-transform flags that enforce the domain invariant that prediction uncertainty should not grow as the season progresses.

Class definition: config.py:432.

Kind

Frozen Pydantic model (model_config = {"frozen": True}). Validated at config load; no runtime mutation.

Source of truth

market_insights_models/src/commodity_hindcast/config.py:432–480

Consumed exclusively by stages/run_deliver.py:deliver_experiment (run_deliver.py:56–88) and by the equivalent forecast delivery path.

Key attributes

Field Type Default Description
model_public_name str — (required) Client-facing label written to the model column of every delivery CSV (e.g. TFFS_V0). Has no default; every commodity YAML must declare it.
ci_levels tuple[float, ...] (0.80, 0.95) Conformal interval levels to materialise. Each level produces a lower_{pct} / upper_{pct} column pair (e.g. 0.80lower_80, upper_80). Must be a subset of SUPPORTED_CI_LEVELS.
enforce_ci_narrowing bool True When True, CI band widths are clamped to never increase as init_date advances within a year. Enforces the season-progression invariant. Disable only for diagnostic runs that need raw calibration output.
drop_frozen_tail bool True When True, trailing init_date rows where mean stops changing are removed before writing. Prevents redundant frozen vintages from appearing in the CSV.

SUPPORTED_CI_LEVELS

The canonical allowed set is defined at config.py:47:

SUPPORTED_CI_LEVELS: tuple[float, ...] = (0.50, 0.68, 0.80, 0.90, 0.95, 0.99)

The _validate_ci_levels validator (config.py:459) rejects any level outside (0, 1) or outside this set. Levels are always stored sorted ascending.

ci_column_names()

Helper method at config.py:471:

def ci_column_names(self) -> tuple[str, ...]:
    # (0.80, 0.95) → ("lower_80", "upper_80", "lower_95", "upper_95")

Called by deliver_experiment to derive the column order passed to build_delivery_column_order.

Real-world values (corn config)

From configs/corn_usa.yaml:

delivery:
    model_public_name: TFFS_V0
    ci_levels: [0.5, 0.68, 0.80, 0.90, 0.95]

enforce_ci_narrowing and drop_frozen_tail are omitted and take their True defaults.

Connection to the delivery pipeline

cli deliver <run_dir> calls deliver_experiment(run_dir) in stages/run_deliver.py.

The delivery stage:

  1. Loads ExperimentResult.from_run_dir(run_dir), which reads config_resolved.yaml and discovers walk-forward prediction artefacts.
  2. Reads result.config.delivery to extract ci_levels, ci_column_names(), enforce_ci_narrowing, and drop_frozen_tail (run_deliver.py:56–59).
  3. Iterates three ADM levels — ("ADM0", "ADM1", "ADM2") (run_deliver.py:69) — and for each: a. Converts fold predictions to DeliveryRow objects via walk_forward_preds_to_delivery_rows(result, level, ci_levels, mode="hindcast"). b. Validates the row collection as a HindcastDelivery. c. Pipes the resulting DataFrame through apply_delivery_post_transforms(enforce_narrowing=..., drop_frozen=...) from lib/transform_utils.py. d. Writes Treefera_{experiment_key}_{ADM}_Hindcast_{YYYYMMDD}.csv under run_dir/delivery/.

DeliveryConfig therefore controls:

  • Which CI columns appear in the output (via ci_levels).
  • What the model column contains (via model_public_name).
  • Whether the enforce_ci_narrowing and drop_frozen_tail post-transforms are applied.

The ADM levels themselves (ADM0, ADM1, ADM2) are fixed by deliver_experiment and are not configurable via DeliveryConfig.

Relationship to PostprocessConfig

Conformal calibration is fitted during the POSTPROCESS stage and controlled by PostprocessConfig.conformalise (config.py:532). DeliveryConfig.ci_levels selects which of those fitted levels to write into delivery columns; the two configs are complementary and must be consistent. If a level in ci_levels was never calibrated in postprocess, CalibrationResult.predict_interval will fail.

PR-331 — P90 bands added

PR-331 (merged 2026-04-27) fixed two delivery bugs:

  1. weather_correction_bu_ac was always null — fixed by including sim_yield_kg_ha_detrended in the area-weighted aggregation.
  2. Wheat, cotton, and soybean configs were missing the 0.90 level — fixed by adding 0.90 to their ci_levels lists.

The CI band machinery is fully dynamic: adding a level to ci_levels causes ci_column_names() to produce the new column pair, and the delivery stage writes it without any code changes. Removing a level eliminates the columns silently.

Lifecycle

  1. Declared in a commodity YAML under the delivery: key; parsed when ExperimentConfig is constructed.
  2. Written verbatim into run_dir/config_resolved.yaml for reproducibility.
  3. Read by deliver_experiment at deliver time; used to parameterise walk_forward_preds_to_delivery_rows and apply_delivery_post_transforms.
  4. Not modified after config construction (frozen=True).

Relationships

  • Owned by ExperimentConfig as field delivery: DeliveryConfig (config.py:700).
  • Controls column content of DeliveryRow (ci_levelslower_* / upper_* columns; model_public_namemodel column).
  • Coordinates with PostprocessConfig.conformalise to ensure fitted CI levels are available at delivery time.
  • ci_column_names() feeds build_delivery_column_order in delivery/schemas.py.

Concepts and pipelines

  • Delivery pipeline (pipeline, to be written).
  • Conformal prediction intervals (concept, to be written).
  • CI narrowing and frozen-tail transforms (concept, to be written).

PRs and commits

  • PR-331 — added P90 bands to wheat / cotton / soybean configs; fixed weather_correction_bu_ac population.

Open questions

  • enforce_ci_narrowing and drop_frozen_tail default to True but are never explicitly set in any YAML config (they rely on Pydantic defaults). A deliberate override would silence the transform; documenting when each should be disabled for diagnostic runs would be useful.
  • model_public_name (TFFS_V0) encodes a version. There is no validation that the name changes when the model changes; version governance is currently manual.
  • The ADM output levels (ADM0, ADM1, ADM2) are hardcoded in deliver_experiment. A future extension might make them configurable here.