# Exposure at issuance

**Since:** migration 218 (written 2026-10-30). **Table:** `warning_exposures`. **Writer:** `/api/workers/warning-exposure`, every 10 minutes. **Reader:** `GET /api/v1/verification/warning-exposure?month=YYYY-MM[&event=Tornado%20Warning]` (public). **Logic:** `lib/warnings/exposure-at-issuance.ts`; the arithmetic is `compute_warning_exposure` (218), which is `compute_housing_exposure_v2` (198) plus the polygon's area on the spheroid.

## What it is

For every NWS warning polygon the as-issued archive receives — Tornado Warning, Severe Thunderstorm Warning, Flash Flood Warning and the other polygon products, every office, every version of every warning — one row: the 2020 Census housing under the polygon, its area, the issuing office and the P-VTEC fields, and two measured lags: from the Weather Service sending the alert to the archive receiving it, and from receipt to this figure being computed. Each row names the archived body it was computed over (`product_row_id`, `content_hash`), which `GET /api/v1/archive/products/{hash}` returns byte for byte.

It is the fifth item of the data-asset program (archive, hindcast, ledger, outcomes). The ledger weights a tornado warning by the homes under it, but only when a surveyed tornado later touched it. Nobody measures the other warnings at the moment they are issued.

## Why it is hard to replicate

Be precise about what is and is not defensible here.

- **The figure is recomputable.** Anyone with the Iowa Environmental Mesonet's polygon archive and the 2020 tracts can compute the homes under any past warning. The method is published on purpose.
- **The record of when the platform had it is not.** A row says: this polygon, as issued (hash), was received N seconds after it was sent and measured M seconds after that. That is a forward-only record. It cannot be produced later for a warning nobody was measuring at the time, and it cannot be moved earlier without changing the hash chain under it (the archive refuses UPDATE and DELETE).
- **So the asset is the lags and the hash, accrued over time**, not the arithmetic. The longer the record runs, the more warnings it covers that nobody else measured as they happened. That is the same shape as the archive itself: worth little this week, compounding monthly.

What this is good for: an early-warning or parametric buyer who needs to know, defensibly, what was under a warning when it was issued (not after the survey), by office and by month; and the platform's own calibration — exposure at issuance is the denominator the ledger's verified-warning housing becomes a numerator over.

## What a row says — and does not

- **`housing_units`** is 2020 Census housing under the polygon: tracts weighted by developed land (NLCD) and areal overlap, the same `tract_areal_overlap_v1` as every exposure figure on the platform. Homes, not people; not a damage estimate; not a forecast.
- **`area_km2`** is the polygon's area on the WGS84 spheroid.
- **`warning_key`** is `office.phenomenon.significance.etn.year` from the P-VTEC (e.g. `KEWX.TO.W.0041.2026`); every version of a warning shares it. Summaries count a warning once by its **first received version** (the lowest archive row id); `housing_max` takes the largest version. An alert without a VTEC is keyed by its own id.
- **`action`** is the VTEC action: `NEW` (issued), `CON` (continued, often with a smaller polygon), `COR` (corrected), `EXT`/`EXA`/`EXB` (extended), `CAN`/`EXP` (cancelled/expired). The archive stores each as its own body; each is a row.
- **`receipt_lag_seconds`** runs from the alert's `sent` to the archive's `received_at`. The archive polls each NWS feed every 5 to 30 minutes, so this is mostly the poll interval, not the network. **`compute_lag_seconds`** runs from receipt to `computed_at` (the worker runs every 10 minutes). Both are measured, never typed. A negative receipt lag means the product's clock ran behind the database's.
- **A row with `housing_units` null** was received but could not be measured at the time (the RPC failed or timed out). It is kept as it was and not recomputed: the row says the platform could not measure it then, which is also a fact about then.
- **Only polygon products are rows.** Zone-based alerts (Tornado and Severe Thunderstorm Watches, Flood Watches, advisories, winter and heat products) carry no polygon in the NWS API and are not measured. The worker skips them and moves its watermark past them.
- **`METHOD_VERSION`** (`warning-exposure-1`) marks the arithmetic. A change is a new version; the worker writes new rows under it and the old rows stay.
- **The record begins 2026-10-28**, when the as-issued archive began. Nothing earlier is backfilled, and nothing can be: a lag cannot be measured after the fact.

## How the worker walks the archive

`as_issued_products` is append-only and its `id` is insertion order. The worker reads the highest `product_row_id` it has written under the current method version (0 when none), then reads bodies with `source = 'nws_alerts'`, `id > watermark` and a non-null geometry, oldest first, 150 at a time, computing each and writing the rows; it loops while a pass fills, within a 90-second budget, and carries any backlog to the next run. Nothing is skipped and nothing is read twice. A read failure writes nothing; an upsert failure stops the pass with the rows written so far counted.

## Verifying a row

```sql
SELECT event, office, action, sent, received_at, computed_at,
       receipt_lag_seconds, compute_lag_seconds, housing_units, area_km2, content_hash
FROM warning_exposures
WHERE warning_key = 'KEWX.TO.W.0041.2026'
ORDER BY product_row_id;
```

Then `GET /api/v1/archive/products/{content_hash}` returns the polygon that was measured; hash the canonical body again and it matches; `SELECT * FROM compute_warning_exposure('<that geometry>'::jsonb)` returns the same figure.

## What it is not

- Not a verdict. Nothing here says whether the warning verified; the ledger (`docs/warning-ledger.md`) does that, and the two join on `warning_key` ↔ the ledger's `warning_source_id` (the alert id) through the archive.
- Not people, value or damage. Housing units only; the platform's dollar paths are separate and hurricane-only.
- Not the NWS's own "population under warning" figures, which it does not publish per warning.
