# Reference determinations

**Since:** migration 219 (written 2026-10-30). **Tables:** `reference_evaluations`, `reference_determinations`. **Writer:** `/api/workers/reference-determinations`, every 10 minutes. **Readers:** `GET /api/v1/verification/reference-determinations?month=YYYY-MM[&structure=ref-eq-los-angeles]` and `GET /api/v1/verification/reference-determinations/{cdt2-id}` (public). **Logic:** `lib/determination/reference/catalog.ts` (the terms) and `lib/determination/reference/ledger.ts` (the walk); the evaluation is determination/2 (`lib/determination/v2/`, `docs/determination-standard-v2.md`), unchanged.

## What it is

A fixed, public catalog of parametric structures, read on every USGS earthquake revision and every NHC advisory the as-issued archive receives, minutes after it arrives, with the result and the time it was reached kept beside the archived body.

The catalog (`reference-catalog-1`, 22 structures):

- **Hurricane, 11 boxes** on the coast: Texas; Louisiana and Mississippi; Alabama and the Florida Panhandle; Southwest Florida; Southeast Florida and the Keys; Northeast Florida and Georgia; the Carolinas; Mid-Atlantic; Northeast; Puerto Rico; Hawaii. The reading is the maximum 1-minute sustained wind while the center is inside the box, per the NHC public advisory as first reported, the center taken to move in a straight line between fixes no more than 12 hours apart. Tiers: 64 kt pays 25%, 96 kt 60%, 113 kt 100%.
- **Earthquake, 11 boxes** on seismic metros: Los Angeles; the San Francisco Bay Area; Puget Sound; Portland; the Wasatch Front; Anchorage; New Madrid; Charleston; the Island of Hawaii; Puerto Rico; and California statewide (tiers one half-magnitude higher). The reading is the magnitude of an earthquake whose epicenter is inside the box at a depth of 70 km or less, per ComCat as first reported, any magnitude type. Tiers: M6.0 pays 25%, M6.5 60%, M7.0 100%.

Each structure's terms for a risk year (the calendar year, as a bond's annual period) are a determination/2 specification with a `cts2-` id; the API's `catalog` lists them with their plain-language statement. The terms are the platform's own. They are no party's contract, and a determination here is monitoring, not settlement.

## Why it is hard to replicate

Be precise about what is and is not defensible here.

- **The evaluation is public.** Anyone can take the catalog and ComCat's final catalog and compute what the terms would pay on the settled record. The method is published on purpose.
- **The feed's revisions are not kept anywhere else.** ComCat revises an earthquake's magnitude, position and depth for hours after the first report — a first automatic solution, then reviewed ones — and serves only the current preferred origin. The all-day feed shows the sequence as it happens and nobody archives it. The NHC's operational advisory fixes differ from the post-season best track, and only the best track is kept in a queryable form. `reference_evaluations` holds every revision of every event as the feed served it, with the time the archive received it.
- **Terms that settle on first report settle on this record.** The market's first-report terms exist because they pay fast; their basis risk is the difference between the first report and the revision. That difference is measured here, event by event, region by region, from the day the record began — and it only accrues forward. A lag cannot be measured after the fact, and a revision that was never archived cannot be recovered.

What this is good for: a parametric structurer or buyer who needs to know, over years and by region, how often the first report and the settled record would have paid differently under standard terms; an independent determination source that was reading the product at the time, not reconstructing it; and the platform's own determination/2 path exercised continuously against real feeds, not only against a customer's trigger when one happens to be in zone.

## What the rows say — and do not

### `reference_evaluations`: every body, in zone or not

- **One row per archived body** of `usgs_earthquakes` and `nhc_current_storms`, keyed to `as_issued_products` by `product_row_id` and `content_hash`. The worker walks the archive by row id, so the table is complete by construction; a reader can prove that a body was evaluated by finding its row, and that none was skipped by the watermark.
- **`source_event_id`** is the ComCat id (`nc75459597`) or the ATCF id (`ep222026`), lower case. **`revision_index`** is 1 for the first body of the event the archive received, 2 for the next, and so on; **`is_first_report`** is `revision_index = 1`. The first body is the first the *archive* saw: for an event that began before 2026-10-28, or between two polls, it is the earliest revision held, not necessarily ComCat's first solution.
- **The observation** is what the body carried, rounded to the table's precision when first read: `magnitude` and `depth_km` to 0.01, `max_sustained_wind_kt` and `central_pressure_mb` to 0.1; `lat`/`lng` as published. The rounding happens before evaluation, so an observation reconstructed from its row is byte-identical to the one the body produced.
- **`review_status`** is ComCat's (`automatic`, `reviewed`, `deleted`); null for NHC. **`record_time`** is the origin time or the advisory time.
- **`unreadable_reason`** names a body that carried no usable observation (`no_epicenter`, `no_magnitude`, `not_earthquake:quarry blast`, `no_center`, `no_time`); it is still a row, evaluated against nothing.
- **`structures_evaluated` / `structures_in_zone` / `determinations_written`** are the catalog's coverage of this body.
- **`receipt_lag_seconds`** runs from `record_time` to the archive's `received_at`; **`evaluate_lag_seconds`** from receipt to `evaluated_at`. The archive polls USGS every 5 minutes and NHC every 30, so receipt lag is mostly the poll interval. Both are measured, never typed.

### `reference_determinations`: the terms read on a body

- **One row per (structure, body)** when the body's observation was inside a zone of the structure, or when it changed the structure's standing outcome for the event (a storm's track can cross a box between two fixes that are both outside it). An earthquake outside every box, a storm fix outside every box with nothing crossed: no row.
- **The outcome is cumulative over the event:** every earlier observation of the event (from `reference_evaluations`) is counted with this one, as the determination/2 evaluator defines. For a first-report structure the earliest received observation of each record stands, so a later revision produces the same outcome; **`reading_this_revision`** keeps the revision's own figure beside it. That pair — the standing first-report reading and what the revision said — is the record.
- **`status`, `payout_pct`, `reading`, `tier_index`, `tier_threshold`, `deciding_zone_id`** are the determination/2 outcome. **`evidence`** is the full determination/2 evidence: the normalized terms with their geometry and `cts2-` id, every observation counted, the outcome with its per-observation assessments and crossings, the statement, the disclaimer and a caveat naming the reference structure. **`determination_id`** is `cdt2-` + the SHA-256 of the canonical evidence; `GET /api/v1/verification/reference-determinations/{id}` returns the record with the verifier's findings on it.
- **`receipt_lag_seconds`** and **`determine_lag_seconds`** as above, the latter from receipt to `determined_at`.
- **`method_version`** (`reference-determinations-1`) marks the catalog and the method together. A changed catalog is a new version; the worker writes new rows under it and the old rows stay. A catalog entry is never edited or removed once rows exist under it.

## How the worker walks the archive

`as_issued_products` is append-only and its `id` is insertion order. Every 10 minutes the worker reads the highest `product_row_id` in `reference_evaluations` under the current method version (0 when none), then reads bodies with `source IN ('usgs_earthquakes', 'nhc_current_storms')` and `id >` that, oldest first, 150 at a time. For each batch it loads every earlier evaluation of the batch's events (the history each observation is counted with) and the standing outcome of every structure for those events, then reads each body in order — later bodies in the batch see earlier ones — and writes the evaluations first, then the determinations, so a determination never exists without the evaluation it was read on. 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.

## Verifying a determination

```
GET /api/v1/verification/reference-determinations/cdt2-<hash>
```

returns the determination/2 record, `verification` (the offline verifier's findings: the id recomputed from the evidence, the spec id recomputed from the terms, the evaluation re-run on the record's own observations and compared with what it claims), and `archive.body`, the URL of the archived product the determination was read on. Offline: `verifyDeterminationV2(record)` from `lib/determination/v2/verify.ts` on the JSON, and `GET /api/v1/archive/products/{content_hash}` for the body, whose coordinates, magnitude (to 0.01) and origin time match the record's own observation.

```sql
-- The revision record of one earthquake
SELECT revision_index, is_first_report, review_status, magnitude, magnitude_type, depth_km, lat, lng,
       record_time, received_at, receipt_lag_seconds, structures_in_zone, determinations_written
FROM reference_evaluations
WHERE source_event_id = 'nc75459597' AND method_version = 'reference-determinations-1'
ORDER BY product_row_id;
```

## Over the record

**Since:** migration 223 (written 2026-10-31). **Table:** `reference_replays`. **Writer:** `/api/workers/reference-replay`, daily 02:30Z. **Readers:** `GET /api/v1/verification/reference-determinations/history[?structure=]`; the "over the record" table on `/track-record`. **Logic:** `lib/determination/reference/replay.ts`.

The live record above is days old and says something only when a product lands in a box. What anyone asks first of standard terms is what they would have paid. Each structure's terms are therefore replayed over every year of the historical record by the same evaluator that settles the live determinations (`lib/determination/v2/backtest.ts`, the trigger backtest): the risk period moved to each year and settled once, one payout per year, the greatest tier reached. Kept per structure, catalog version, replay version and record end year; computed only when missing, so once a year and after a catalog change.

What the record is, and is not — each row's caveats say the same:

- **Hurricane** is replayed on IBTrACS North Atlantic, whose USA fields are NHC's HURDAT2 best track, from the year the wind record is complete. The terms name the public advisory as first reported; no archive of historical advisories is held, so the row says `substituted`. Operational and best-track positions and winds differ. IBTrACS's own three-hourly points are set aside (`interpolated_points_set_aside`). The **Hawaii** hurricane box is `outside_record`: the track record is the Atlantic basin, and no result is given rather than a zero.
- **Earthquake** is replayed on `historical_earthquakes`, ComCat for the United States and territories, each event as ComCat settled it — not the first report the live terms read. The catalog stores no magnitude type.
- `paying_years / years_of_record` is the share of the record, not a probability; `expected_annual_payout_pct` is the record's mean payout, not an expected loss; no paying year is a floor on rarity (`minimum_return_period_years`), never a rate.

```sql
SELECT structure_id, status, from_year, to_year, years_of_record, paying_years, expected_annual_payout_pct, substituted
FROM reference_replays WHERE catalog_version = 'reference-catalog-1' ORDER BY peril, structure_id;
```

## Sealed daily

Every reference determination id is sealed into the platform's daily Merkle digest beside customers' determinations (`/api/workers/determination-log`, one UTC day in arrears; roots at `GET /api/v1/determinations/log`). `GET /api/v1/verification/reference-determinations/{id}` returns the period's root and the inclusion proof under `sealed` once the day is sealed, and `GET /api/v1/determinations/{id}/proof` answers for the same id. A reader who records a root cannot be shown a different set of determinations for that day afterward: that is the mechanism that makes "the platform had this reading on this day" a claim about the past rather than a claim about the database. Before migration 219 is applied the digest worker reads the reference table as empty and seals the day without it.

## What it is not

- Not a settlement determination, and not anyone's trigger. The catalog is the platform's; customers' triggers are evaluated by `trigger-eval` against their own terms (`docs/determination-standard-v2.md`).
- Not ComCat's first solution in every case: the record's "first report" is the first revision the archive received, which the row's `revision_index` and `received_at` make plain.
- Not the best track. NHC observations are the operational advisory fix; the post-season best track is a different product and the catalog does not name it.
- Not a forecast or a loss figure. Readings are magnitudes and winds as published.
