Skip to content
CRATON Docs Get a free API key

Data

Wildfire, by the acre

A wildfire trigger priced off the fires that were mapped. This page uses the same model as the storm-report box, a parametric_cat_in_a_box trigger over a pinned extract, with the box written in peril.region. Here the extract is MTBS's mapped wildfires and the ladder is in burned acres. By the end you will have a wildfire object for the country around Paradise, California, its forty-one-year history, and a technical price for it, all from one block.

Read the quickstart first if you have not: it explains CEDE_BASE_URL, the Authorization: Bearer header and the job pattern every Craton verb answers with. The trigger object is the field-by-field reference for the block this page writes.


On this page

What a wildfire box is

Monitoring Trends in Burn Severity (MTBS), run by the USGS and the USDA Forest Service, maps every large US wildfire from Landsat imagery after it burns: fires reported at 1,000 acres or more in the west and 500 in the east, back to

  1. Craton pins extracts of its national table for five western regions,

    one row per fire: the single point MTBS publishes for the fire's burn boundary, the ignition date, and the burned acres inside that boundary. A wildfire box asks: what was the largest fire whose point lies inside this box, year by year?

Three declarations route a structure to this model, and each is re-checked against the pinned bytes before any of them is read:

Member What a wildfire box declares Why
trigger.type parametric_cat_in_a_box A box and a ladder.
trigger.data_sources[0].kind storm_report_set The member that routes to the report model. A mapped fire is the same shape of record as a storm report: a place, a time, a value.
peril.code wildfire It must match the feed. A wildfire structure over a hail feed, or a hail structure over a wildfire feed, is refused by name.

The index is four decisions, the same four as the storm-report box:

  • The measurement is burned_area_acres in acres, with statistic: "max".
  • The window is the year, written P1Y, and a fire belongs to the year it was ignited.
  • The ladder is a step function in acres, with a threshold labeled attachment at its lowest level.
  • The box must sit inside the extract. A box reaching outside it is refused with the edge it crossed.

For each year, the largest fire inside the box sets the year's level, the ladder turns it into a payout ratio, and the burn rate is the mean ratio over every year, the years with no large fire included at zero.


Paradise and the Feather River country, end to end

One object: a box over Butte County and its neighbors (39.4N to 40.3N, 122.2W to 121.0W), paying half the limit when a fire of 100,000 acres or more has its point inside the box and all of it at 250,000 acres. The block signs up for an account of its own, stores the object, replays it over every year of the extract, and prices the same terms.

Run this in an empty directory.

Shell Runs in CI
set -euo pipefail

: "${CEDE_BASE_URL:?export CEDE_BASE_URL first, see the quickstart}"

# Read one field out of a JSON document on stdin: `field a.b.0.c`.
field() {
  python3 -c 'import json, sys
document = json.load(sys.stdin)
for step in sys.argv[1].split("."):
    document = document[int(step)] if step.isdigit() else document[step]
print(document)' "$1"
}

# Poll a job until it stops moving, leaving its last document in a file.
wait_for() {
  local job_id="$1" into="$2" state=unknown
  for _ in $(seq 1 300); do
    curl -sS --fail-with-body -H "$auth" "$CEDE_BASE_URL/jobs/$job_id" > "$into"
    state=$(field status < "$into")
    case "$state" in succeeded|failed) break ;; esac
    sleep 0.2
  done
  if [ "$state" != succeeded ]; then
    printf 'the job did not succeed: %s\n' "$(cat "$into")" >&2
    return 1
  fi
}

# 1. An account of this block's own.
CEDE_API_KEY=$(curl -sS --fail-with-body \
  -H "Content-Type: application/json" \
  -d '{"label": "wildfire walkthrough"}' \
  "$CEDE_BASE_URL/signup" | field api_key.secret)
export CEDE_API_KEY
auth="Authorization: Bearer $CEDE_API_KEY"

# 2. The wildfire-box object. The box is the peril region; the ladder is in
#     acres, the unit the wildfire feed publishes.
cat > butte-wildfire.json <<'JSON'
{
  "schema_version": "0.1.0",
  "status": "draft",
  "exposure": {
    "kind": "index",
    "source_fidelity": { "unmapped_columns": [], "guessed_units": [], "ambiguous_rows": [] }
  },
  "peril": {
    "code": "wildfire",
    "region": {
      "description": "Butte County wildfire box: 39.4-40.3N, 122.2-121.0W.",
      "bounding_geometry": {
        "type": "Polygon",
        "coordinates": [[[-122.2, 39.4], [-121.0, 39.4], [-121.0, 40.3],
                         [-122.2, 40.3], [-122.2, 39.4]]]
      }
    }
  },
  "financial_structure": {
    "limit": { "amount": 10000000, "currency": "USD" },
    "attachment": { "value": 100000, "unit": "acres", "index_ref": "butte-wildfire-box" }
  },
  "trigger": {
    "type": "parametric_cat_in_a_box",
    "index": {
      "name": "butte-wildfire-box",
      "version": "1.0.0",
      "description": "Largest mapped wildfire inside the Butte County box, per year, by burned acres.",
      "measurement": { "variable": "burned_area_acres", "unit": "acres", "statistic": "max" },
      "aggregation_window": { "duration": "P1Y" },
      "thresholds": [
        { "label": "attachment", "level": 100000, "unit": "acres" },
        { "label": "exhaustion", "level": 250000, "unit": "acres" }
      ],
      "payout_function": {
        "type": "step",
        "points": [ { "level": 100000, "payout_ratio": 0.5 },
                    { "level": 250000, "payout_ratio": 1 } ],
        "maximum_payout_ratio": 1
      }
    },
    "data_sources": [
      { "id": "mtbs-wildfire-northern-california",
        "name": "MTBS mapped wildfires, Northern California extract",
        "kind": "storm_report_set",
        "version": "2026-09-27", "vintage": "2026-09-27" }
    ]
  },
  "period": {
    "inception": "2027-01-01T00:00:00-08:00",
    "expiry": "2028-01-01T00:00:00-08:00",
    "timezone": "America/Los_Angeles"
  }
}
JSON

object_id=$(curl -sS --fail-with-body -H "$auth" \
  -H "Content-Type: application/json" \
  --data-binary @butte-wildfire.json "$CEDE_BASE_URL/objects" | field id)
structure=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL/objects/$object_id/structures")
backtest_path=$(printf '%s' "$structure" | field links.backtest)

# 3. Replay it over every year of the extract.
backtest_job=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL$backtest_path" | field id)
wait_for "$backtest_job" backtest-job.json
artifact_path=$(field result.backtest.links.self < backtest-job.json)
curl -sS --fail-with-body -H "$auth" "$CEDE_BASE_URL$artifact_path" > backtest.json

python3 -c '
import json
artifact = json.load(open("backtest.json"))
statistics = artifact["statistics"]
print("paying years:", statistics["triggering_years"], "of", statistics["years"])
for row in artifact["years"]:
    if row["triggered"]:
        peak = row["peak_report"]
        print(" ", row["year"], row["max_value"], row["index_unit"],
              "fire", peak["event_id"], "->", row["payout_ratio"])
print("replayed by", artifact["model"]["name"], artifact["model"]["version"])
'

# 4. Price the same terms.
price_job=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL/objects/$object_id/price" | field id)
wait_for "$price_job" price-job.json

python3 -c '
import json
price = json.load(open("price-job.json"))["result"]["price"]
premium, assumptions = price["technical_price"], price["assumptions"]
selection = assumptions["event_selection"]
print("technical price:", premium["amount"], premium["currency"],
      "- rate on line", premium["rate_on_line"])
print("fires inside the box:", selection["reports_in_box"],
      "of", selection["reports_in_extract"], "in the extract")
print("priced by", price["model"]["name"], price["model"]["version"])
'
echo "done: the priced object is at /objects/$object_id"

If the last line printed, every paying year above names the fire that set it by its MTBS event id, which you can look up on mtbs.gov.

Five of the forty-one years pay: Campbell (1990) and Camp (2018) at half the limit, and North Complex (2020), Dixie (2021) and Park (2024) at all of it. Three of the five are in the last five years of the record, and the burn is an average over all forty-one. Whether the next five years look more like the last five or like the whole record is a judgment this price does not make for you; the backtest above is where to see it.


The pinned extracts

Five feeds, one per western region, each wide enough to hold the boxes you would price inside it:

Region Box Feed Fires Largest
Northern California 37.0 to 42.0N, 124.5 to 119.0W mtbs-wildfire-northern-california 972 August Complex, 2020
Southern California 32.5 to 37.0N, 121.0 to 114.1W mtbs-wildfire-southern-california 908 Thomas, 2017
Pacific Northwest (Oregon and Washington) 42.0 to 49.0N, 124.8 to 116.5W mtbs-wildfire-pacific-northwest 1698 Long Draw, 2012
Colorado 37.0 to 41.0N, 109.1 to 102.0W mtbs-wildfire-colorado 391 Cameron Peak, 2020
Southwest (Arizona and New Mexico) 31.3 to 37.0N, 114.1 to 103.0W mtbs-wildfire-southwest 1786 Wallow, 2011

All are version 2026-09-27, fires ignited 1984 to 2024, public domain, with MTBS credited as its data asks. The pinned data feeds lists each one's measurement. The record stops at 2024 because MTBS maps with a lag, and the later years were still incomplete when these extracts were cut.

The point lookup answers the same feeds: a GET on /hazard?peril=wildfire&lat=39.76&lon=-121.62 counts the fires around Paradise at 1,000, 10,000, 50,000, 100,000 and 250,000 acres and names the largest.


What a mapped-fire burn is not

Everything below ships in assumptions.limitations on every result.

Only large fires are in the record. MTBS maps fires reported at 1,000 acres or more in the west. A fire of a few hundred acres that took a neighborhood with it is not in these feeds, so a trigger that should respond to small, destructive fires cannot be written on them.

A fire is a point. Each fire sits at the one point MTBS publishes for its burn boundary. A fire that burned into the box from a point outside it is not counted, and a fire counted in the box may have burned mostly outside it. The larger the fire relative to the box, the larger that basis risk: Dixie burned nearly a million acres, and its point is one corner of that.

Acres are the mapped boundary. The acreage is the area inside the burn boundary, unburned islands included, so it is larger than the ground that actually burned.

A year pays once. The largest fire of the year is the one the year pays on; a second fire adds nothing, and reinstatements are not priced.

This page as Markdown, byte for byte: /wildfire.md