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 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 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:
peakover a one-day window (P1D): the year's worst hour, the structure below.countwith aday_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.
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.