# Forecast Data Contract

**Consequential Races · Oregon Governor Forecast**

The application has four data layers:

```text
immutable source snapshots
        ↓
reviewed normalized records
        ↓
versioned model inputs
        ↓
forecast artifacts, run-bound analysis, and separate archives
```

CSV is used for small reviewed datasets. JSON is used for source manifests, model configuration, and published forecast artifacts. Dates use ISO 8601. Timestamps include a UTC offset.

## `data/results/oregon_2022_governor_county.csv`

One row per county and candidate.

| Field | Meaning |
|---|---|
| `election_date` | Election date |
| `county` | Canonical county name |
| `candidate_id` | Stable candidate identifier |
| `candidate_name` | Ballot name |
| `party` | Ballot party or nonaffiliated label |
| `votes` | Certified votes |
| `source_id` | Entry in the source registry |

## `data/polls/polls.csv`

One row per distinct poll sample and ballot test.

| Field | Meaning |
|---|---|
| `poll_id` | Stable identifier |
| `pollster` | Polling organization |
| `sponsor` | Sponsor or funder |
| `partisan_sponsor` | `D`, `R`, `O`, or blank |
| `field_start`, `field_end` | Field dates |
| `published_at` | Release timestamp/date |
| `population` | `A`, `RV`, or `LV` |
| `sample_size` | Sample size for this ballot test |
| `mode` | Phone, text-to-web, online panel, mixed, etc. |
| `candidate_order` | Disclosed order when known |
| `question_text` | Exact or faithfully transcribed ballot question |
| `kotek`, `drazan`, `other`, `undecided` | Percent on the original scale |
| `source_id` | Original release in the source registry |
| `review_status` | Review-queue state |
| `review_note` | Inclusion, penalty, exclusion, or supersession rationale |

## `data/fundamentals/fundamentals.csv`

Long-form dated observations.

| Field | Meaning |
|---|---|
| `series_id` | Stable feature identifier |
| `as_of` | Date the value describes |
| `released_at` | Date it became knowable |
| `value` | Numeric value |
| `unit` | Points, percent, dollars, index, etc. |
| `source_id` | Source registry entry |
| `vintage` | Original or revised release identifier |

## `data/context/oregon-governor-2026/signals.csv`

Verified observations that are useful to readers and candidate-feature research but have not earned a forecast coefficient. These rows are deliberately separate from `fundamentals.csv`, and validation rejects any model status other than `shadow_only` or `commentary_only`.

Each row records a stable signal and series ID, category, as-of and release timestamps, numeric value and unit, source ID, related-sample ID, review status, model status, and an explanatory note. The published artifact sets `forecast_effect` to exactly `0` for every row. Related-sample IDs prevent approval and ballot responses from the same respondents from being described as independent evidence.

## `data/context/<race-id>/assessments.json`

This reviewed, race-scoped ledger turns source-backed context into careful directional commentary without changing the forecast. Its reusable JSON Schema is `data/context/context-assessments.schema.json`; every file uses `context-assessments.v1`, a stable `race_id`, review status, an artifact `as_of` date, and one or more assessments.

Each assessment keeps evidence and interpretation in separate objects:

- `observed_evidence.summary` states only what the reviewed sources or signals establish;
- `observed_evidence.evidence_source_ids` is non-empty and resolves against `data/sources.json`;
- `observed_evidence.current_signal_ids` may be empty for source-backed historical evidence, but every listed ID must resolve against the race's current signal ledger;
- `observed_evidence.related_sample_ids` declares shared samples, including every related-sample ID carried by a linked signal;
- `analyst_inference.mechanism`, `interpretation`, `counterweight_or_caveat`, and `what_would_change_this` contain the explicitly labeled human inference.

The remaining fields record a stable `id` and `family`, `direction` (`candidate`, `neutral`, `mixed`, or `unclear`), configured target candidate when the direction is candidate-specific, qualitative `confidence`, a `dependency_group`, overlapping model-component paths, an explicit double-count warning, review cadence, record status, and record `as_of` date. Candidate-directed assessments must name a configured candidate; every other direction must set the target to `null`. If two assessments reuse a current signal, validation requires the same dependency group so consumers cannot treat the resulting interpretations as independent evidence.

Both validation and publication require `model_status: commentary_only` and the numeric value `forecast_effect: 0`. A string such as `"0"` is invalid. The forecast engine does not read these assessments while calculating margins, shares, uncertainty, or probabilities. The public artifact exposes them under a stable `context_assessments` envelope and repeats an aggregate zero effect so consumers cannot mistake direction or confidence for a numeric adjustment. The data loader and validation CLI accept a stable `--race-id`, which selects the two ledgers under `data/context/<race-id>/`. Forecast generation remains explicitly limited to Oregon until a second race has a complete input manifest, output/archive paths, and movement adapter; the CLI rejects other race IDs rather than mixing their context with Oregon inputs.

## `data/events/events.json`

Each event includes `event_id`, event and publication timestamps, headline, category, candidates, mechanism, status, source IDs, summary, model treatment, and optional versioned intervention metadata.

## `data/bridge/johnson_transfer_evidence.json`

An auditable evidence record for the production Johnson-transfer prior. It distinguishes observed values, derived proxy statistics, identification limits, and the model parameter selected from them. The evidence records cover:

- AP VoteCast's individual-level relative-favorability comparison among self-reported or intending Johnson voters, explicitly labeled as a proxy rather than a direct second-choice measure; and
- Clout's initial three-candidate ballot and forced Kotek–Drazan ballot from the same survey sample, explicitly labeled as aggregate movement without a respondent-level cross-tab.

The production parameter is Kotek's share among Johnson-origin voters **conditional on choosing Kotek or Drazan**. Its truncated-normal distribution has mean `0.48`, standard deviation `0.16`, lower bound `0.05`, and upper bound `0.85`. A separately drawn Johnson-to-major rate controls how much of the Johnson-origin bloc enters that two-candidate allocation, so the remainder stays residual. The production bridge retains the 2022 Kotek and Drazan source blocs at 100% and does not separately estimate their retention or crossover; those possibilities appear only in the non-probabilistic stress diagnostic.

The file stores derived statistics and source provenance, not raw AP VoteCast public-use microdata. Reproduce and validate the committed evidence artifact with:

```sh
python3 scripts/analyze_johnson_transfer.py
```

## `data/sources.json`

Each source includes:

- stable `source_id`;
- title, publisher, and canonical URL;
- source type and authority tier;
- retrieval timestamp;
- as-of date;
- local snapshot path when retained;
- SHA-256 hash when a snapshot is retained;
- notes and supersession metadata.

## `config/model.json`

Contains candidate IDs, election date, update timezone, prior and poll-model parameters, simulation count, display rounding, model version, methodology version, and validation state. The Johnson-transfer configuration records the evidence status, a truncated-normal Kotek share conditional on a Johnson-origin voter choosing a major candidate, and a separate Johnson-to-major rate with its own evidence status and basis. That rate is currently an unvalidated judgmental participation assumption. The configuration does not encode Kotek/Drazan major-bloc retention or crossover.

## `public/data/races/index.json`

The publication-level race registry contains a stable `race_id`, docket code, title, jurisdiction, office, election date, publication status, summary, permanent page URL, and namespaced forecast URL for every active race. The homepage renders this registry, so adding a contest does not change an existing race's URL.

Every race also declares `analyst_take_url` and an independent `analyst_take_release` record. The release record names the exact content-addressed archive file and directory, the SHA-256 of the exact public sidecar bytes, and an approval SHA-256 over the canonical sidecar object. These values are intentionally separate from forecast `input_hashes`: commentary is never a model input and never changes the immutable forecast artifact.

## `public/data/races/<race-id>/analyst-take.json`

The strict owner-authorized `analyst-take.v3` sidecar is bound to `race_id`, `forecast_run_id`, the forecast artifact's embedded SHA-256, and the exact forecast `data_cutoff`. It retains `status: reviewed` as a workflow-state literal, but that status does not assert human editorial review. It repeats `forecast_effect: 0` and `official_forecast_unchanged: true`. An authorized sidecar contains:

- a deterministic thesis that identifies the official forecast leader, the current polling direction, and the uncertainty boundary;
- one 1,100–2,600-character, four-paragraph model-authored `summary` covering the race state and current polling, reviewed context, both candidates' evidence-based needs, and what to watch, in that order;
- one deterministic, byte-matchable path for every major candidate;
- a strict `section_evidence_ids` map for those four summary paragraphs, its exact ordered global evidence-ID union, and exact source IDs; and
- visible provenance separating deterministic fields from free prose, including provider, configured and returned model identities, response identifier and creation time, reasoning effort, capture source, `composition: model_generated_free_prose`, `fidelity: model_output_verbatim`, `summary_authorship: model_authored_verbatim`, prompt/packet/output/summary hashes, and `oversight: owner_authorized_internal_use` under `internal-use-owner-authorization.v1`, with the authorizing role and timestamp.

The raw `grounded-race-summary.v3` object contains the model-authored summary, the four section evidence arrays, and their ordered global union. It is accepted only after the qualitative postcondition validator rejects new numbers, unknown or misplaced evidence, unsupported candidate/component directions, advocacy, campaign tactics, scenario outcomes, context presented as forecast math, or unsupported polling detail. The public prose is the raw model `summary` field rendered without semantic rewriting. Promotion verifies the exact prompt, packet, raw-output, and visible-summary hashes and exact raw-to-projected field equality before recording the owner's explicit authorization for internal use; the public sidecar deliberately omits `raw_model_output` while retaining its hash and the evidence projection. Owner authorization accepts the documented residual AI risk and is not a claim that the prose received human editorial review.

If generation is unavailable or owner authorization has not been recorded, publication continues to accept the current-run `analyst-take.v2` `status: unavailable` sidecar during migration. That sidecar contains no AI prose; the resolver emits the audited deterministic fallback, exposes deterministic provenance, and suppresses the AI-authored label. V2 sidecars with `status: reviewed` are not accepted as free-prose summaries. Missing, malformed, stale, wrong-cutoff, wrong-artifact, unauthorized, or unsupported-race sidecars fail the build rather than silently falling back.

Each release revision is archived at `data/analyst-takes/archive/<race-id>/<approval-sha256>.json`; using the full canonical digest preserves multiple authorized revisions for the same forecast run without filename collisions. The current public sidecar must byte-match the exact archive file registered in the race index. The digest covers snapshot bindings, prose, evidence and source identifiers, zero-effect declarations, and provenance, while the byte hash also detects formatting-level drift.

A repository forecast release automatically replaces a prior-run Take with an explicit current-run unavailable sidecar. Promotion is a separate two-stage operation: the trusted operator capture command calls the fixed OpenAI Responses endpoint directly and creates only a private unreviewed draft outside the repository, then `scripts/promote_analyst_take.py` validates the assembled candidate with the shared TypeScript guard. Only an explicit `--authorize-internal-use` invocation with `--authorized-at` may record owner authorization and write the immutable archive, current pointer, and release digests. The deployed site has no publication-summary generation route or credential.

## `public/data/races/oregon-governor-2026/forecast.json`

The generated public artifact contains:

- run metadata and warnings;
- candidate probabilities and vote-share summaries;
- nowcast and election forecast;
- polls, fundamentals, and combined component estimates;
- the five-stage 2022–2026 bridge;
- probability-history points;
- the exact polls in the current average, plus a reviewed `poll_ledger` that retains age-based exclusions and their reasons;
- poll trend series;
- evidence-ledger entries;
- verified context signals with an explicit zero forecast effect;
- reviewed directional context assessments, with observed evidence separated from analyst inference and an explicit zero forecast effect;
- an accuracy ledger showing which model layers are validated, sensitivity-only, shadow-only, or awaiting an end-to-end backtest;
- data quality and source links;
- reproducibility metadata.

These fields support a reusable five-part per-race brief: (1) registry and run summary, (2) current accepted polling, (3) election forecast and closeness, (4) reviewed directional commentary, and (5) conditional candidate paths. Parts four and five must not be folded into forecast math. Before summarizing context or constructing candidate paths, a consumer uses `dependency_group` as the evidence-family key so correlated assessments are not treated as independent confirmation. Context confidence never implies an effect size, and candidate paths do not have path-specific probabilities.

The web application treats this file as read-only.
