Data Contracts¶
This page is the formal analytics contract for warehouse marts that
notebooks, scripts, and open-source integrators should rely on: grains, scope
rules, and guarantees. A contract is a named guarantee about a relation set,
bundle, or collector format; see Terminology.
OddsFox Pipeline is a prediction-market pipeline; the production Polymarket WC2026
contract is the golden mart polymarket_wc2026_market_hourly_odds. Kalshi WC2026
stage and group-winner odds are a separate documented scope. Other Polymarket
WC2026 marts (match-minute, order book, Polygon settlement) are isolated
pipelines. WC2026 FIFA fixtures/results are documented for Kalshi validation and
those isolated match pipelines, not the Polymarket golden-mart quickstart.
Model-level column docs and tests live in the dbt project. Analyst query
guidance starts at
Query the warehouse; column semantics and
join recipes live in Data dictionary. For private
oddsfox.raw.v1 snapshots and the strategy clean-data relation set, see
Strategy contracts.
Documented Marts¶
“Public” on this page historically meant a supported warehouse query contract. Prefer mart or documented mart. It does not mean that every relation is sanitized or intended for external distribution; the Polygon settlement mart has a separate allowlisted exporter.
Global Polymarket graph catalog¶
Schema: polymarket_catalog_marts
| Relation | Grain | Contract |
|---|---|---|
polymarket_graph_catalog |
One row per namespaced record_id |
Cumulative event nodes, qualifying market nodes, and event-market edges observed by completed four-pass global Gamma crawls. Rows contain deterministic textual representations and observation provenance for downstream graph construction. |
The only record types are event, market, and event_market. Node IDs are
event:<event_id> and market:<market_id>; edge IDs are
event_market:<event_id>:<market_id>. Every edge references included nodes,
every event has an edge, and qualifying orphan markets remain included.
Market qualification is cumulative and requires explicit evidence under the
manifested tradability predicate: CLOB tokens, order-book enablement,
accepting-orders time, funding time, or a deployed condition paired with ready
or funded state. Volume, active state, and a bare condition ID do not qualify.
first_observed_at, last_observed_at, latest_catalog_crawl_id, and
present_in_latest_crawl distinguish retained history from current presence.
The release contract is oddsfox.polymarket.graph-catalog.v1. It contains one
sorted Parquet mart plus manifest, schema, quality report, and checksums. It is
complete only from the first successful crawl onward; source records deleted
before that crawl are unknowable. See the
manual runbook.
Schema: polymarket_wc2026_marts
| Relation | Grain | Pipeline | Contract |
|---|---|---|---|
polymarket_wc2026_market_hourly_odds |
One row per (market_id, odds_hour_epoch) |
Polymarket WC2026 golden mart | Golden WC2026 hourly odds mart. Every market under a sticky event-volume-eligible WC2026 event (reported lifetime volume at or above the pipeline policy floor, currently $100,000 USD) with primary-outcome CLOB prices in [0, 1] (Yes when present, otherwise outcome_index 0), primary_outcome_label, full lifetime hourly OHLC history, and comprehensive market and enclosing-event metadata. |
polymarket_wc2026_market_minute_odds |
One row per (market_id, odds_minute_epoch) |
Minute odds (unified, isolated) | Minute-grain counterpart of the hourly golden mart. Match-level markets use in-game windows; futures markets use the tournament span [2026-06-11, 2026-07-19] capped by each market's close/resolution time. |
polymarket_wc2026_match_minute_odds |
One row per (odds_minute_utc, market_id) |
Match-minute odds (isolated) | Dense in-game minute OHLC for 216 group moneyline markets and 32 knockout advance/win markets across FIFA match IDs 1–104. |
polymarket_wc2026_match_order_book |
One row per (fifa_match_id, market_id, clob_token_id, snapshot_timestamp_ms, snapshot_sha256, book_side, level_rank) |
Match order book; market portrait (isolated) | Every bid and ask level from every PMXT historical L2 snapshot in the reviewed Spain–Argentina final (match 104) market window. |
polymarket_wc2026_polygon_settlement_minute_odds |
One row per (proposition_id, settlement_minute_utc) |
Polygon settlement history (isolated) | Finalized Polygon V2 settlement-time OHLC/VWAP over fixed half-open scheduled windows; exactly 39,120 dense rows. |
polymarket_wc2026_match_order_book_states and polymarket_wc2026_match_trades
are additional polymarket_wc2026_marts tables built only by the market-portrait
pipeline as bundle inputs; they are not independently documented contracts. See
Market portrait.
Analyst column guidance for the isolated Polymarket marts is in Data dictionary.
Polymarket soccer match-result minute odds¶
Schema: polymarket_soccer_marts
| Relation | Grain | Contract |
|---|---|---|
polymarket_soccer_matches |
One row per admitted soccer event | Exact canonical-soccer-tag event identity, series metadata, home/away labels, inclusive kickoff/finish bounds, timing provenance/confidence, coverage tier, and the three distinct result-market IDs. |
polymarket_soccer_match_result_minute_odds_observed |
One row per (market_id, odds_minute_epoch) |
Source Yes-token and native No-token minute OHLC for home_win, draw, or away_win; no normalization and no generated rows. Missing native No minutes stay null. |
polymarket_soccer_match_result_minute_odds |
One row per (market_id, odds_minute_epoch) |
Inclusive kickoff-to-finish minute spine. Before the first observation prices are null; later quiet minutes carry the prior close into OHLC independently for Yes and No, exposing is_observed, is_no_observed, carry age, last observed time, and observed point counts. |
polymarket_soccer_match_result_minute_odds_modeling |
One row per (market_id, odds_minute_epoch) |
Modeling-ready subset of the dense mart. A game must contain all three result markets, have non-null Yes OHLC prices for every row, have at least 99% Yes observed-minute coverage across its complete three-market spine, and have no consecutive unobserved Yes run longer than three minutes in any market. Native No prices and no_* coverage fields are diagnostic and do not admit or exclude a game. Each row includes the game-level Yes and No coverage percentages and maximum gaps. |
An event publishes only when its three roles, distinct markets, binary token
pairs, teams, and timing map without ambiguity. Every mart row is filtered
through the current registry and the latest successfully published Yes-token
audit for the exact token window; a published No-token audit participates in
source-revision dirty detection without blocking Yes admission. Raw storage
keeps both Yes and No token sides; these marts expose unmodified source
Yes-token prices and native No-token prices. No prices are never derived as
1 - Yes. Catalog completeness and CLOB price completeness are reported
separately.
The modeling mart applies its quality policy at the game grain and retains the same complete minute rows and observation flags as the dense mart. The canonical observed and dense marts remain unfiltered for auditing and other use cases. It never fills an initial missing price from a future observation; games with any such row are excluded to avoid look-ahead bias.
Monitoring contracts are views in polymarket_soccer_observability:
| Relation | Grain | Contract |
|---|---|---|
polymarket_soccer_pipeline_health |
One current row | Latest full-run state plus warning/critical counts and measured time. |
polymarket_soccer_pipeline_alerts |
One row per active condition | Stable code, severity, subject, measured/threshold values, run identity, timestamps, and remediation. |
polymarket_soccer_pipeline_trends |
One row per successful full run | Catalog, mapping, publication, coverage, duration, CPU, RSS, and storage metrics with prior-success deltas. |
The source ledger is keyed by Dagster run ID and by (run ID, step, retry
attempt). An unclean process exit remains running and becomes critical after
the configured stale-run age.
Scraper-owned soccer features¶
Pipeline does not acquire soccer results or build Elo. Scraper publishes
oddsfox.reference.v1 for source-neutral warehouse inputs and
oddsfox.scraper.soccer.pre_match_elo.v1 for Trading. Pipeline validates and
loads only the former; the Elo event-grain contract bypasses Pipeline entirely.
Polygon settlement minute odds¶
The mart is an internal audit surface, not the allowlisted technical export. It
contains eight audit-only columns in addition to the published fields below:
settlement_minute_epoch, condition_id, yes_token_id, no_token_id,
market_structure, exchange_address, manifest_sha256, and
manifest_version. A direct mart export bypasses the technical allowlist.
Complete column contract¶
Types below are the materialized DuckDB types. “Required” describes the
publication contract rather than a physical DuckDB NOT NULL constraint.
Prices are USDC.e collateral per outcome share and are validated in [0, 1].
All timestamps are UTC without a stored timezone suffix.
Identity, schedule, and provenance:
| Column | Type | Contract |
|---|---|---|
proposition_id |
VARCHAR |
Required stable authored identifier; one of 248 propositions. |
fifa_match_id |
INTEGER |
Required FIFA schedule identifier in 1..104. |
stage |
VARCHAR |
Required: group_stage, round_of_32, round_of_16, quarterfinal, semifinal, third_place, or final. |
group_name |
VARCHAR |
OpenFootball group label for group-stage matches; null for knockout matches. |
home_team |
VARCHAR |
Required independently sourced fixture home/display team; not a cross-pipeline join key. |
away_team |
VARCHAR |
Required independently sourced fixture away/display team; not a cross-pipeline join key. |
proposition_type |
VARCHAR |
Required: home_win, draw, away_win, home_advances, home_win_third_place, or home_wins_final. |
yes_represents |
VARCHAR |
Required authored meaning of the oriented Yes token. |
no_represents |
VARCHAR |
Required authored meaning of the oriented No token. |
scheduled_kickoff_at_utc |
TIMESTAMP |
Required minute-aligned scheduled kickoff from the pinned fixture source. |
analysis_window_start_at_utc |
TIMESTAMP |
Required inclusive window start; equal to scheduled kickoff. |
analysis_window_end_at_utc |
TIMESTAMP |
Required exclusive window end; start plus 150 minutes for group propositions or 210 minutes for knockout propositions. |
settlement_minute_utc |
TIMESTAMP |
Required UTC minute bucket in [analysis_window_start_at_utc, analysis_window_end_at_utc). |
settlement_minute_epoch |
BIGINT |
Required Unix seconds for settlement_minute_utc; always minute-aligned. |
elapsed_window_minute |
BIGINT |
Required zero-based scheduled-window index: 0..149 for group propositions or 0..209 for knockout propositions. |
condition_id |
VARCHAR |
Required canonical 32-byte Polygon condition ID; use with oriented token IDs for cross-pipeline reconciliation. |
yes_token_id |
VARCHAR |
Required decimal ConditionalTokens position ID oriented to yes_represents. |
no_token_id |
VARCHAR |
Required decimal ConditionalTokens position ID oriented to no_represents. |
market_structure |
VARCHAR |
Required neg_risk for the 216 group propositions or standard for the 32 knockout propositions. |
exchange_address |
VARCHAR |
Required lower-case Polygon V2 exchange address: neg-risk 0xe2222d279d744050d28e00520010520000310f59 or standard 0xe111180000d2663c0091e4f400237545b87b996b. |
manifest_sha256 |
VARCHAR |
Required SHA-256 of the complete reviewed 248-row market manifest used by the published scan. |
manifest_version |
VARCHAR |
Required semantic version of that reviewed manifest. |
Yes-side minute aggregates:
| Column | Type | Contract |
|---|---|---|
yes_open |
DECIMAL(38,18) |
First Yes normalized leg in chain order; null when yes_observed = false. |
yes_high |
DECIMAL(38,18) |
Maximum Yes normalized-leg price; null when unobserved. |
yes_low |
DECIMAL(38,18) |
Minimum Yes normalized-leg price; null when unobserved. |
yes_close |
DECIMAL(38,18) |
Last Yes normalized leg in chain order; null when unobserved. |
yes_vwap |
DECIMAL(38,18) |
sum(gross_collateral) / sum(shares), rounded deterministically half-even to 18 decimal places; null when unobserved. |
yes_normalized_fill_count |
BIGINT |
Count of normalized Yes economic legs, including derived counterparts; zero when unobserved. |
yes_derived_fill_count |
BIGINT |
Subset of normalized Yes legs derived as MINT/MERGE counterparts; between zero and yes_normalized_fill_count. |
yes_share_volume |
DECIMAL(38,6) |
Sum of normalized Yes outcome shares; zero when unobserved. |
yes_gross_collateral_volume |
DECIMAL(38,6) |
Sum of Yes gross USDC.e collateral before fees; zero when unobserved. |
yes_first_settlement_at_utc |
TIMESTAMP |
Earliest finalized event-block timestamp contributing to the minute; null when unobserved. |
yes_last_settlement_at_utc |
TIMESTAMP |
Latest finalized event-block timestamp contributing to the minute; null when unobserved. |
yes_observed |
BOOLEAN |
True when at least one normalized Yes leg exists in the minute. |
No-side minute aggregates:
| Column | Type | Contract |
|---|---|---|
no_open |
DECIMAL(38,18) |
First No normalized leg in chain order; null when no_observed = false. |
no_high |
DECIMAL(38,18) |
Maximum No normalized-leg price; null when unobserved. |
no_low |
DECIMAL(38,18) |
Minimum No normalized-leg price; null when unobserved. |
no_close |
DECIMAL(38,18) |
Last No normalized leg in chain order; null when unobserved. |
no_vwap |
DECIMAL(38,18) |
sum(gross_collateral) / sum(shares), rounded deterministically half-even to 18 decimal places; null when unobserved. |
no_normalized_fill_count |
BIGINT |
Count of normalized No economic legs, including derived counterparts; zero when unobserved. |
no_derived_fill_count |
BIGINT |
Subset of normalized No legs derived as MINT/MERGE counterparts; between zero and no_normalized_fill_count. |
no_share_volume |
DECIMAL(38,6) |
Sum of normalized No outcome shares; zero when unobserved. |
no_gross_collateral_volume |
DECIMAL(38,6) |
Sum of No gross USDC.e collateral before fees; zero when unobserved. |
no_first_settlement_at_utc |
TIMESTAMP |
Earliest finalized event-block timestamp contributing to the minute; null when unobserved. |
no_last_settlement_at_utc |
TIMESTAMP |
Latest finalized event-block timestamp contributing to the minute; null when unobserved. |
no_observed |
BOOLEAN |
True when at least one normalized No leg exists in the minute. |
Minute completeness:
| Column | Type | Contract |
|---|---|---|
minute_complete |
BOOLEAN |
Required; exactly yes_observed AND no_observed. It describes two-sided settlement activity, not finality or football-time completeness. |
minute_status |
VARCHAR |
Required mapping: both sides → both_observed, Yes only → yes_only, No only → no_only, neither → no_fills. |
OHLC chain order is (block_number, transaction_index, passive_log_index,
normalized_leg_ordinal), not event timestamp alone. first and last
settlement timestamps are the minimum and maximum contributing finalized
event-block timestamps. Derived counts are already included in normalized fill
counts and volumes; they must not be added a second time.
Internal audit release and operator-local technical export¶
polymarket_wc2026_polygon_settlement_release reads an already valid mart and
writes a new immutable SemVer audit directory below
artifacts/polygon_settlement/audit/releases/. Existing versions are never
overwritten and there is no mutable latest alias. The audit release contains:
wc2026_polygon_settlement_minute_odds.csvwc2026_polygon_settlement_markets.csvschema.jsonREADME.mdSOURCES.csvPROVENANCE.jsonQUALITY_REPORT.jsonCHANGELOG.mdDO_NOT_PUBLISH.mdCHECKSUMS.sha256
The market sidecar, full provenance, and issue-level quality report deliberately retain identifiers and locators needed for internal verification. The audit directory is internal and is excluded from repository distributions.
The standalone
export_polymarket_wc2026_polygon_settlement_minute_odds.py command consumes
only a checksum-valid immutable audit directory. It never queries the warehouse
or calls a network service. It copies the primary CSV byte-for-byte, validates the
literal 41-column allowlist and 39,120-row contract, scans for forbidden
identifiers and unsafe text, and writes a new immutable directory below
artifacts/polygon_settlement/exports/releases/.
The operator-local technical export is titled WC2026 Polygon Settlement Minute Aggregates and contains exactly:
wc2026_polygon_settlement_minute_odds.csvschema.jsonREADME.mdSOURCES.csvMANIFEST.jsonQUALITY_SUMMARY.jsonQUALITY_SUMMARY.mdCHANGELOG.mdCHECKSUMS.sha256
schema.json covers only the exported CSV and fixes column order, nullability,
units, RFC3339 UTC timestamps, DECIMAL(38,18) probability fields,
DECIMAL(38,6) volume fields, integers, booleans, enums, and the
proposition-minute grain. The analyzer disables DuckDB type inference so exact
decimal thresholds, including pair deviation 0.05, are not changed by binary
floating-point rounding.
Its manifest and quality reports contain only redacted aggregate inventory, lineage, verification, coverage, derived-fill, exact-decimal pair-deviation, and single-leg/linkability metrics. The quality reports contain no proposition IDs, per-row timestamps, token IDs, exchange/provider addresses, transaction/log/block locators, or issue-level warning rows.
The exported CSV repeats only dataset version and stable proposition semantics. It omits the eight audit-only mart fields plus wallets, transaction/log/block IDs, provider fields, raw amounts, order hashes, signatures, raw event payloads, Gamma/CLOB fields, source question prose, and pair diagnostics. This is de-identified data, not anonymous data: a sparse aggregate over a public ledger can still be reverse-linked to source transactions by time, amount, and price.
The software creates no upload operation or remote destination. Operators control the local artifact and remain responsible for their inputs and outputs.
External schema: oddsfox_reference
These tables are loaded transactionally from an immutable Scraper reference bundle. Pipeline does not acquire, parse, transform, or republish their source data, and it creates no legacy aliases.
| Relation | Grain | Pipeline | Contract |
|---|---|---|---|
international_results_wc2026_matches |
One row per match_id |
Shared (Kalshi WC2026, match-minute odds) | Scraper-published WC2026 fixture/result rows, including stage, status, score, inferred knockout advancer metadata, and source revision/hash provenance. |
international_results_wc2026_team_status |
One row per team_name |
Shared (Kalshi WC2026) | Canonical 48-team WC2026 roster and current tournament status derived from fixture/result rows. |
Schema: kalshi_wc2026_marts
| Relation | Grain | Pipeline | Contract |
|---|---|---|---|
kalshi_wc2026_stage_markets |
One row per market_ticker |
Kalshi WC2026 | Latest stage-of-elimination market snapshot with team/stage classification, progression-side pricing, and current-price status. |
kalshi_wc2026_stage_market_hourly_odds |
One row per (market_ticker, odds_hour_epoch) |
Kalshi WC2026 | Trailing contract-window hourly OHLC odds for stage markets joined to classified metadata. |
kalshi_wc2026_group_winner_markets |
One row per market_ticker |
Kalshi WC2026 | Latest group-winner market snapshot with team classification and current-price status. |
kalshi_wc2026_group_winner_market_hourly_odds |
One row per (market_ticker, odds_hour_epoch) |
Kalshi WC2026 | Trailing contract-window hourly OHLC odds for group-winner markets. |
Health And Observability¶
- Use
polymarket_wc2026_observability.polymarket_wc2026_ingestion_run_observabilityfor run-level ingestion telemetry, market-discovery provenance, request counts, and sync metrics. - Use
kalshi_wc2026_observability.kalshi_wc2026_ingestion_run_observabilityfor Kalshi run-level ingestion telemetry. - Use
kalshi_wc2026_observability.kalshi_wc2026_stage_coverageto inspect classified market coverage and hourly completeness against the pipeline policy window. - Use
kalshi_wc2026_observability.kalshi_wc2026_data_qualityfor Kalshi source-state anomalies, sparse coverage, and stale or missing live odds findings.
Current Scope Rules¶
- Kalshi WC2026 marts expose stage-of-elimination and group-winner markets
from the fixed
wc2026registry across the packaged Kalshi series tickers. Shared Kalshi thresholds live indbt/seeds/kalshi_wc2026_pipeline_policy.csv. - Polymarket WC2026 production contract:
polymarket_wc2026_market_hourly_odds(golden mart). It includes every market under a sticky event-volume-eligible WC2026 event frompolymarket_wc2026_ops.market_scope_registrythat also has primary-token hourly CLOB history. Eligible registry children with empty CLOB history are expected (never-traded props under high-volume events) and are absent from the mart by join design. Match-minute, order-book, and Polygon settlement marts are isolated pipelines documented above but not built by the golden-mart full job. - The current event admission floor is
event_min_lifetime_volume_usd = 100000indbt/seeds/polymarket_wc2026_pipeline_policy.csv. Eligibility is sticky: once an event crosses the floor it remains admitted even if later snapshots report lower lifetime volume. - Shared Polymarket WC2026 thresholds live in that seed; dbt models/tests read it and Python parity tests assert the Dagster defaults match it.
- Prices are raw primary-outcome CLOB probabilities in
[0, 1]fromint_polymarket_wc2026_primary_market_token(Yes when present, otherwiseoutcome_index0).primary_outcome_labelstates what each row's price represents. Prices are not normalized to team progression, and the mart does not classify knockout stage or canonical team. is_resolved,winning_outcome, andwinning_clob_token_idmay be null: Gamma/events/keysetand nested market payloads used by WC2026 sync do not supply those fields. Prefer Polygon settlement for hard resolution economics.is_activeandis_closedare independent Gamma flags.active = truewithclosed = trueis common on closed markets and is not a pipeline bug.- Market-level
tags/categoryare often null on Gamma WC nest payloads; preferevent_tagsfrom the enclosing event. When market tags are absent at catalog materialization, the pipeline copies enclosing-event tags onto the market payload. - Grain is one row per
(market_id, odds_hour_epoch)with full lifetime hourly history from the private incrementalint_polymarket_wc2026_token_hourly_oddsfact. Market and enclosing-event metadata come fromint_polymarket_wc2026_marketsandint_polymarket_wc2026_event_latest. - WC2026 match/result rows and their source provenance arrive in the active Scraper reference bundle. The golden hourly mart does not depend on those tables.
- Use
polymarket_wc2026_market_scope_registry_refresh,polymarket_wc2026_hourly_odds_ingest,polymarket_wc2026_dbt_build, andpolymarket_wc2026_full_pipelinefor WC2026 Dagster operations.polymarket_wc2026_dbt_buildandpolymarket_wc2026_full_pipelineselect+polymarket_wc2026_market_hourly_oddsonly. - Use
kalshi_wc2026_market_scope_registry_refresh,kalshi_wc2026_hourly_odds_ingest, andkalshi_wc2026_full_pipelinefor Kalshi WC2026 Dagster operations.kalshi_wc2026_full_pipelineconsumes the already-loaded Scraper reference schema and never refreshes a non-market source. scripts/export_polymarket_wc2026_market_hourly_odds.pyis the supported offline export for the golden mart.- Raw hourly collection is a separate temporal-foundation branch. An existing
(clobTokenId, timestamp)point is not overwritten on replay. scripts/prune_odds_history.pytrims staleodds_historyrows; see Scripts.int_polymarket_wc2026_marketsis the canonical registry-scoped market dimension for the golden mart. It admits only markets whose enclosing event is volume-eligible in the scope registry.
dbt Checks¶
uv run make dbt-build runs model builds plus generic and singular data tests for:
- Source and staging grain.
- Price sanity and OHLC bounds.
- WC2026 market scope (
accepted_valuesonscope_name). - Golden mart grain, primary-outcome token selection (Yes preferred, else
outcome_index0), and event lifetime volume floor from the WC2026 pipeline policy seed. - Reference-table grain, fixture/result cardinality, identity coverage, and source-provenance readiness.
- Observability run health (warn-level: latest run error-token regression and history coverage floor).
- Kalshi WC2026 grain, OHLC order, progression-side selection, real-team scope,
and data-quality checks from
kalshi_wc2026_pipeline_policy.csv.
Warn-level observability tests fail softly in dbt build output; treat warnings
as operator signals on real warehouses, not hard release blockers when the
disposable fixture is healthy.
Breaking change: source-first namespace reset¶
Mart, asset, job, script, and schema names now use the source-first
polymarket_wc2026 namespace. Dagster asset keys are hierarchical under
polymarket/wc2026/...; jobs, op config keys, scripts, dbt relations, and
DuckDB schemas use flat polymarket_wc2026_* names.
There are no compatibility views, env aliases, or migration shims in v0.2.x.
Delete old local warehouse files (rm oddsfox.duckdb*) and rerun quickstart
after upgrading from older layouts.
The golden hourly mart reads a private incremental hourly fact. If an existing local DuckDB warehouse still has deleted schedule/catalog marts or old relation types, reset the warehouse or drop the affected dbt schemas before rebuilding.