# 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](storm-reports.md), 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](quickstart.md) 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](trigger.md) is the field-by-field
reference for the block this page writes.

---

## 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
1984. 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.

<!-- cede:runnable -->

```bash
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](feeds.md) 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.
