# Grid outage, by the county

A grid-outage trigger priced off how many of a county's electricity customers
were without power. The page uses the station-index model, the same
`parametric_index` trigger that prices a rain gauge or a river gauge, over a
pinned **county outage series** from Oak Ridge National Laboratory's EAGLE-I.
By the end you will have a Harris County (Houston) outage object, its history
since 2018, 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 county outage series is

Most US utilities publish a live map of their outages. EAGLE-I reads those
maps every 15 minutes and records, for each county, how many customers had no
power. Craton pins ten counties from that record, one feed each, and reduces
each day to one number: **the largest count that held for at least an hour**
(the lowest of four consecutive 15-minute runs, at the day's worst hour). A
single bad scrape cannot set a day that way. Days are the county's local
standard time, and a day the national collection was down is left blank,
never counted as zero.

Three declarations route a structure to this model:

| Member | What an outage index declares | Why |
| --- | --- | --- |
| `trigger.type` | `parametric_index` | A daily series, a window and a ladder. |
| `trigger.data_sources[0].kind` | `station_network` | The station-index model's data-source kind, as for the river gauge. The feed's own kind, `outage_daily`, is checked against it before a day is read. |
| `peril.code` | `grid_outage` | The only peril an outage series prices. A `weather_station` structure over it is refused: a count of customers without power is not a weather reading. |

The measurement is `daily_max_customers_out_1h` in `customers`. Two
statistics make sense on it:

* `peak` over a one-day window (`P1D`): the year's worst hour, the structure
  below.
* `count` with a `day_threshold`: how many days in a window reached a level,
  which is how a structure asks for duration.

---

## Harris County, end to end

One object: Houston's county, paying half the limit when 500,000 customers
are out for an hour or more, and all of it at 1,000,000. The block signs up
for an account of its own, stores the object, replays it over the record, 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": "grid outage walkthrough"}' \
  "$CEDE_BASE_URL/signup" | field api_key.secret)
export CEDE_API_KEY
auth="Authorization: Bearer $CEDE_API_KEY"

# 2. The outage-index object: the county's customers without power, the
#     year's worst hour, a ladder in customers.
cat > harris-outage.json <<'JSON'
{
  "schema_version": "0.1.0",
  "status": "draft",
  "exposure": {
    "kind": "revenue_stream",
    "currency": "USD",
    "values": { "business_interruption": { "amount": 20000000, "currency": "USD" } },
    "source_fidelity": { "unmapped_columns": [], "guessed_units": [], "ambiguous_rows": [] }
  },
  "peril": {
    "code": "grid_outage",
    "region": { "description": "Harris County, Texas (FIPS 48201).", "countries": ["US-TX"] }
  },
  "financial_structure": {
    "limit": { "amount": 5000000, "currency": "USD" },
    "attachment": { "value": 500000, "unit": "customers", "index_ref": "harris-outage" }
  },
  "trigger": {
    "type": "parametric_index",
    "index": {
      "name": "harris-outage",
      "version": "1.0.0",
      "description": "The largest number of Harris County customers without power for at least an hour, the worst day of the year.",
      "measurement": { "variable": "daily_max_customers_out_1h", "unit": "customers", "statistic": "peak" },
      "aggregation_window": { "duration": "P1D", "alignment": "rolling" },
      "thresholds": [
        { "label": "attachment", "level": 500000, "unit": "customers" },
        { "label": "exhaustion", "level": 1000000, "unit": "customers" }
      ],
      "payout_function": {
        "type": "step",
        "points": [ { "level": 500000, "payout_ratio": 0.5 },
                    { "level": 1000000, "payout_ratio": 1 } ],
        "maximum_payout_ratio": 1
      }
    },
    "data_sources": [
      { "id": "eaglei-outage-harris",
        "name": "EAGLE-I customers without power, Harris County",
        "kind": "station_network",
        "version": "2026-09-27", "vintage": "2026-09-27" }
    ]
  },
  "period": {
    "inception": "2027-01-01T00:00:00-06:00",
    "expiry": "2028-01-01T00:00:00-06:00",
    "timezone": "America/Chicago"
  }
}
JSON

object_id=$(curl -sS --fail-with-body -H "$auth" \
  -H "Content-Type: application/json" \
  --data-binary @harris-outage.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 record.
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"]:
    print(" ", row["year"], row.get("index_level"), "->", 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 = price["technical_price"]
print("technical price:", premium["amount"], premium["currency"],
      "- rate on line", premium["rate_on_line"])
print("priced by", price["model"]["name"], price["model"]["version"])
'
echo "done: the priced object is at /objects/$object_id"
```

One year of the eight pays: 2024, when Hurricane Beryl left 1,652,417 of the
county's customers without power for an hour or more on July 8. Winter Storm
Uri in February 2021 reached 370,982 on this count, short of the attachment.

**Duration instead of the peak.** The same county, written with statistic
`count`, a `day_threshold` of 250,000 customers, a seven-day window, and a
ladder paying half at 3 days and all at 5, pays in two years: 2021 (Uri held
three days over 250,000) at half, and 2024 (Beryl held seven) in full. Its
technical rate on line is 0.2995, against 0.2204 for the peak structure above,
because it pays in a second year.

---

## The pinned counties

Ten feeds, version `2026-09-27`, 2018 to 2025, CC BY 4.0 (credited to the
EAGLE-I authors in each manifest):

| County | Feed | Largest day in the record |
| --- | --- | --- |
| Harris, Texas (Houston) | `eaglei-outage-harris` | 1,652,417 on 2024-07-08 (Beryl) |
| Dallas, Texas | `eaglei-outage-dallas` | 363,388 on 2024-05-28 |
| Travis, Texas (Austin) | `eaglei-outage-travis` | 267,260 on 2021-02-16 (Uri) |
| Lee, Florida (Fort Myers) | `eaglei-outage-lee` | 454,291 on 2022-09-28 (Ian) |
| Hillsborough, Florida (Tampa) | `eaglei-outage-hillsborough` | 517,887 on 2024-10-10 (Milton) |
| Pinellas, Florida (St. Petersburg) | `eaglei-outage-pinellas` | 445,328 on 2024-10-11 (Milton) |
| Orleans Parish, Louisiana | `eaglei-outage-orleans` | 181,031 on 2021-08-30 (Ida) |
| Cook, Illinois (Chicago) | `eaglei-outage-cook` | 279,787 on 2020-08-10 (derecho) |
| King, Washington (Seattle) | `eaglei-outage-king` | 472,209 on 2024-11-20 |
| Wayne, Michigan (Detroit) | `eaglei-outage-wayne` | 309,036 on 2023-02-23 (ice storm) |

Each manifest also carries ORNL's modelled customer count for the county (as
of 2022) and the share of the state's customers EAGLE-I was collecting each
year from 2018 to 2022. The modelled count is context, not a cap: during
Milton, Pinellas reported more customers out than the model says it has.

The point lookup answers the same feeds near each county's 2020 population
center, with return levels of the county's yearly worst hour: a `GET` on
`/hazard?peril=grid_outage&lat=29.76&lon=-95.37` answers for Harris County.

---

## What a county outage burn is not

Everything below ships in `assumptions.limitations` on every result.

**It is the county's count, not your meter.** A structure pays on how many of
the county's customers were out. A site that lost power while the county's
count stayed low is basis risk, and so is the reverse.

**It is what the maps said.** EAGLE-I scrapes the maps utilities publish. A
utility it did not collect is missing from the count, and a map that stopped
updating during a storm undercounts that storm. Uri is the case to keep in
mind: the count here is well below what Texas utilities reported at the time.

**The record is eight years.** EAGLE-I goes back to late 2014, but the early
years miss whole utilities (Houston's among them) and overstate others, and
ORNL documents its coverage from 2018. A burn over eight years is a count of
one or two paying years. Read it as that.

**A day is its worst hour.** The measurement says how many customers were out
at once, not for how long; write `count` over a window when duration is what
matters.
