Data • Analytics • Evaluated AI

Make every denominator explicit

Search contracts and table schemas, then inspect how definitions handle missing values.

Generated from maintained Markdown, contracts and recorded evidence. Repository guides

Find a definition or column

11 definitions or schemas shown

game_draw_rateVersioned contract
{
  "id": "game_draw_rate",
  "version": "1.0.0",
  "grain": "one provider/game_id",
  "inputs": [
    "fact_game"
  ],
  "filters": "rated Standard games, excluding source-marked bots, completed results only",
  "numerator": "count of eligible games with result 1/2-1/2",
  "denominator": "count of eligible games with result in 1-0, 0-1, 1/2-1/2",
  "formula": "numerator / denominator",
  "null_rules": "unknown results excluded and counted; empty denominator yields null",
  "units": "fraction",
  "result_schema": {
    "numerator": "integer",
    "denominator": "integer",
    "value": "number|null",
    "unknown_results": "integer",
    "marked_bots": "integer"
  },
  "caveats": [
    "Historical foundation corpus is not evidence about contemporary players.",
    "Excluding marked bots does not establish all other players are human.",
    "No opening, time-pressure, or causal claims are supported by this metric."
  ]
}
opening_usageVersioned contract
{
  "id": "opening_usage",
  "version": "1.0.0",
  "grain": "one provider/game_id in the observed bounded prefix",
  "inputs": [
    "fact_game.source_opening",
    "fact_game.result",
    "fact_game.marked_bot"
  ],
  "classification": "source opening family is the trimmed text before the first colon in the provider tag; no local position classifier",
  "numerator": "eligible games in selected source opening family",
  "denominator": "eligible games with a nonempty source opening family",
  "eligible": "rated Standard accepted games with completed result, excluding source-marked bots",
  "null_rules": "missing/empty source opening excluded from denominator and reported separately",
  "units": "fraction",
  "caveats": [
    "The 100,000-game prefix is a convenience sample, not an August 2026 population sample.",
    "Opening tags are provider supplied; opening choice is confounded."
  ]
}
opening_player_scoreVersioned contract
{
  "id": "opening_player_score",
  "version": "1.0.0",
  "grain": "one player-game at selected color, opening family, exact time control, and rating band",
  "inputs": [
    "fact_game",
    "fact_player_game"
  ],
  "filters": "completed rated Standard, no source-marked bot, known source opening, known focal rating; half-open rating band",
  "win_rate": "wins / (wins + draws + losses)",
  "score_rate": "(wins + 0.5 * draws) / (wins + draws + losses)",
  "null_rules": "unknown result and missing focal rating excluded and counted in coverage",
  "units": "fraction",
  "low_support_threshold_games": 100,
  "caveats": [
    "Descriptive association in the observed prefix only.",
    "Do not infer a causal advantage from opening choice.",
    "Focal-player games are dependent across players and opponents; M4 uncertainty method must account for this."
  ]
}
opening_adjusted_scoreVersioned contract
{
  "id": "opening_adjusted_score",
  "version": "1.0.0",
  "grain": "one focal player-game in a selected source Opening family",
  "filters": "completed non-marked-bot games; exact color, half-open player rating band, exact base+increment, two named source opening families; recorded opponent rating required for adjustment",
  "score": "wins plus 0.5 times draws divided by eligible player-games",
  "strata": "player rating minus recorded opponent rating: below -100, -100 to 99, and 100 or more",
  "common_weights": "pooled eligible player-game counts over strata represented in both openings, normalized to sum to one",
  "adjusted_result": "within-stratum score rates for each opening averaged using identical common weights",
  "interval": "400 seeded focal-player cluster bootstrap repetitions within each opening; percentile 2.5 and 97.5 bounds among valid resamples",
  "low_support": "fewer than 100 eligible player-games is a project warning",
  "null_rules": "missing opponent rating is excluded from adjustment but remains in raw score; a missing common stratum yields no adjusted rate",
  "units": "score fraction",
  "caveats": [
    "Observed ordered prefix only; no month-wide inference.",
    "Association, not opening-choice causation.",
    "Bootstrap does not model opponent dependence or archive-prefix selection."
  ]
}
opening_positionsVersioned contract
{
  "version": "1.1.0",
  "name": "observed_white_opening_positions",
  "source_grain": "checked source snapshot \u00d7 provider \u00d7 game ID \u00d7 White decision ply",
  "analysis_grain": "visit snapshot \u00d7 cohort \u00d7 optional recorded opening family \u00d7 canonical position",
  "position_key_version": "standard-fen-first-four-legal-ep-1.0",
  "position_key": "Piece placement, side to move, castling rights, and en passant only if legal; first four normalized FEN fields. SHA-256 truncated to 24 hex characters; retained key and collision check.",
  "history": "Full FEN, full UCI prefix and repeated-position flag remain in local visit shards. Draw counters and repetition history are not part of the recognition key.",
  "default_window": {
    "learner_color": "white",
    "min_ply": 6,
    "max_ply": 20,
    "step": 2
  },
  "game_count": "A game contributes once per canonical position within the window; the earliest visit supplies its route and continuation.",
  "frequency": "Distinct games reaching the position / all eligible games in that cohort and, when selected, recorded opening family. Not a personal encounter estimate.",
  "opening_family": "Recorded game opening tag before its first colon. ECO is retained separately. Labels do not define or merge board positions.",
  "recurring": "At least two distinct games reach the exact position.",
  "acyclic_route_count": "Distinct complete UCI arrival prefixes with no repeated canonical position anywhere in that prefix. Looped prefixes are counted in game frequency but not in this route count.",
  "transposing": "At least two distinct acyclic arrival prefixes reach the exact position.",
  "ranking": "Descending distinct-game count, with position ID as deterministic tie breaker; up to 20 recurring and 20 transposing positions per view.",
  "route_share": "Games with this earliest arrival prefix / position games; up to three non-looped examples are published, so displayed shares need not sum to one.",
  "continuation_share": "Games choosing this legal next White move at the earliest visit / position games, including games without a next move in the denominator. Report next_move_games separately. Up to five moves are published.",
  "top_set_coverage": "Union of distinct games encountering any first-N frequency-ranked position / eligible view games, N=1..20. Overlapping position frequencies must not be added.",
  "endpoint_compression": "At a fixed ply, 1 \u2212 distinct canonical endpoint positions / distinct UCI prefixes. Exclude any prefix with a repeated canonical position. Descriptive convergence, not a measured learning benefit.",
  "publication": "Separate Elite and broader public cohorts. Publish all opening-family inventory counts, plus all-opening rankings and up to twelve largest known families with at least 1000 eligible games. Local hashes bind retained inputs, extraction plan, parser implementation and artifacts.",
  "limits": "Elite is a curated November 2025 month; public sources are nine ordered first-day archive prefixes. Frequency is not move quality, strategic importance, or causal evidence. No engine scoring, personal weighting, drills, or live provider requests.",
  "alternative_route_share": "1 - dominant acyclic route games / acyclic position games; acyclic position games = position games - repetition-prefix games. Null for zero acyclic games. Route cards retain all position games as denominator.",
  "coverage_curve": "For each N=1..20 in the frequency ranking, union distinct games reaching any first-N position / eligible view games. additional_games is the difference from N-1. This is not a greedy coverage ranking.",
  "opening_label_breadth": "Count all distinct known recorded families and all distinct known ECO codes separately; unknown-family games reported separately. Five displayed family/ECO pairs are examples, not exhaustive breadth. Labels can reflect later play.",
  "route_remainder": "Other acyclic route games + repetition-prefix games + top-three acyclic route games = position games.",
  "public_lenses": {
    "d4-g3": {
      "label": "1.d4 with g3",
      "moves": [
        "d2d4",
        "g2g3"
      ]
    },
    "d4-g3-catalan": {
      "label": "Catalan-style fianchetto",
      "moves": [
        "d2d4",
        "c2c4",
        "g2g3",
        "f1g2",
        "e7e6",
        "d7d5"
      ]
    },
    "d4-g3-kings-indian": {
      "label": "King's Indian fianchetto",
      "moves": [
        "d2d4",
        "c2c4",
        "g2g3",
        "f1g2",
        "g8f6",
        "g7g6",
        "f8g7",
        "d7d6"
      ]
    }
  },
  "lens_policy": "First White move d4; all required UCI moves present by max_ply. Curated move-sequence facets, not exhaustive taxonomy. Denominator is matching games; include eligible positions before and after g3. No personal histories.",
  "scatter_scope": "Published ranked positions in the selected view only, deduplicated by position ID; not a corpus-wide distribution."
}
local_user_opening_analysisVersioned contract
{
  "id": "local_user_opening_analysis",
  "version": "1.0.0",
  "input": "Plain UTF-8 multi-game PGN exported from Chess.com, Lichess or another source. Mainline legal replay from the initial Standard position; variations/comments/NAGs do not contribute visits.",
  "limits": {
    "bytes": 10485760,
    "games": 5000,
    "plies": 1000,
    "variationDepth": 32,
    "files": 20,
    "worker_seconds": 60
  },
  "privacy": "Files and normalized games remain in the browser worker for the session. No account connection, API downloads, model calls, server uploads or persistent storage. Clearing/reloading terminates the worker. Report download is explicit and contains summaries rather than raw PGNs.",
  "eligibility": "Completed known results and legal Standard mainlines with named players. Reject variants, setup/FEN games, unfinished or conflicting results, duplicate headers and illegal mainlines. Do not impose reference-corpus rating floors. Missing ratings and casual games are supported; unknown metadata stays unknown.",
  "identity": "Provider + native game ID from recognized Site/Link URLs. Chess.com live/daily namespaces stay separate; Lichess uses the first eight game-ID characters. Conflicting provider/native-ID headers are rejected. Generic fallback is SHA256 of normalized player names, date and UCI mainline; source-file SHA256 is separate.",
  "deduplication": "Semantic fingerprint of normalized players, UCI moves and completed result excludes comments, variations, NAGs and other optional metadata. Same identity with equal fingerprint counts once; any differing fingerprint withholds all copies of that identity as conflicts. Provider identities never collapse across platforms. First copy supplies optional metadata.",
  "reconciliation": "Framed records = accepted unique games + duplicate copies + conflicting copies + rejected records. Failed framing imports no partial file and its unframed records are outside that count. Invalid UTF-8, byte/file/game/ply/variation-depth caps, cancellation or timeout retain no partial session.",
  "player_selection": "Explicit selected player per provider; NFC lowercase matching within that provider. A matching name on another provider is not automatically the same person. White first; matched Black games and unmatched-player records are reported separately.",
  "personal_denominator": "All selected valid completed Standard White games after explicit date/time-control/rated filters, including short games. Date-filtered undated games are excluded visibly. An optional minimum-20-ply comparison filter has its own exclusion count.",
  "position_identity": "First four canonical FEN fields with legal en passant; SHA256 truncated to 24 hex characters, matching the checked public reference. Preserve full FEN separately. Draw counters/history are not part of the recognition key.",
  "window": {
    "learner_color": "white",
    "min_ply": 6,
    "max_ply": 20,
    "step": 2
  },
  "position_metrics": "One earliest eligible visit per game/position. Frequency = position games / selected White games. Acyclic route count excludes any repeated-position prefix. Alternative-route share = 1 - largest acyclic route game count / (position games - repetition-prefix games), null for zero acyclic games.",
  "reference_scope": "Selected first-N published study positions, N=1..20, for one cohort/view/ranking. Coverage is the union of selected White games reaching those boards. No match means not in this selected set, not unknown in the full corpus or out of theory.",
  "route_comparison": "First differing move while the selected published route examples still define a next move, within the first 20 plies. An example ending stops comparison; it is not a departure. A later canonical White decision matching a selected board counts as re-entry. This is not a move-quality or mistake metric.",
  "metadata": "Date is a valid UTCDate or Date header, otherwise null; do not infer UTC time. Rated status uses explicit Rated true/false or Rated/Casual/Unrated Event prefixes; Chess.com branding alone does not establish rated status. TimeControl is retained without guessed speed/rating comparability.",
  "limits_of_inference": "Personal samples and public reference cohorts differ. Coverage is a descriptive count of the selected reference set. No engine evaluation, learning-gain claim, causal result comparison, username downloads or private-game authentication. Generic fingerprints cannot resolve every missing-ID ambiguity."
}
clock_pressure_error_proxyVersioned contract
{
  "id": "clock_pressure_error_proxy",
  "version": "1.0.0",
  "grain": "one selected game x ply, stratified by clock-before bucket",
  "sampling": "game ID SHA256 first eight bytes modulo 20 equals zero; deterministic 5% selection from the bounded prefix, independent of annotations",
  "eligible": "non-first move by a player in a zero-increment rated Standard game with valid prior same-player clock and a completed result, excluding source-marked bots",
  "buckets_seconds": [
    "[0,10)",
    "[10,30)",
    "[30,60)",
    "[60,infinity)"
  ],
  "evaluable": "eligible move with source-provided before and after centipawn evaluations; mate transitions excluded and counted separately",
  "deterioration": "max(0, mover-perspective cp_before minus cp_after); source cp scores are White-perspective, invert both for Black",
  "numerator": "evaluable eligible moves with deterioration >= 200 centipawns",
  "denominator": "evaluable eligible moves in bucket",
  "coverage_denominator": "all eligible moves in bucket",
  "null_rules": "first same-player move, missing/invalid clock, missing evaluations and mate transitions never become zero error",
  "units": "fraction",
  "caveats": [
    "Exploratory proxy, not an official Lichess blunder label.",
    "Existing evaluation coverage is selected and source quality varies.",
    "The prefix and move sample do not represent all August games; no causal time-pressure claim."
  ]
}
evaluation_coverageVersioned contract
{
  "id": "evaluation_coverage",
  "version": "1.0.0",
  "grain": "one selected game x ply, by clock-before bucket",
  "inputs": [
    "fact_move"
  ],
  "filters": "same eligible move and deterministic game sample as clock_pressure_error_proxy",
  "numerator": "eligible moves with comparable before and after source centipawn evaluations",
  "denominator": "all eligible moves in bucket",
  "null_rules": "missing source evals and mate transitions are non-evaluable, never zero deterioration",
  "units": "fraction",
  "caveats": [
    "Source evaluation availability can depend on which games were analyzed.",
    "Coverage is conditional on the observed prefix and deterministic move-game sample."
  ]
}
fact_game schemaStorage contract
ColumnType
providerVARCHAR
game_idVARCHAR
source_ordinalBIGINT
resultVARCHAR
ratedBOOLEAN
variantVARCHAR
utc_dateDATE
played_atTIMESTAMPTZ
timestamp_precisionVARCHAR
time_controlVARCHAR
base_secondsBIGINT
increment_secondsBIGINT
source_openingVARCHAR
source_ecoVARCHAR
terminationVARCHAR
marked_botBOOLEAN
ply_countBIGINT
headers_jsonVARCHAR
record_fingerprintVARCHAR
fact_player_game schemaStorage contract
ColumnType
providerVARCHAR
game_idVARCHAR
colorVARCHAR
player_keyVARCHAR
ratingBIGINT
opponent_ratingBIGINT
scoreDOUBLE
fact_move schemaStorage contract
ColumnType
providerVARCHAR
game_idVARCHAR
plyBIGINT
colorVARCHAR
uciVARCHAR
clock_after_secondsDOUBLE
clock_before_secondsDOUBLE
clock_bucketVARCHAR
eval_before_cp_whiteBIGINT
eval_after_cp_whiteBIGINT
eval_before_mate_whiteBIGINT
eval_after_mate_whiteBIGINT
centipawn_deteriorationBIGINT
error_proxyBOOLEAN
phase_proxyVARCHAR

Why the denominator matters

This worked illustration starts with the authored demo: 3 proxy errors among 6 evaluable moves. Change the number of moves with missing evaluations; they change coverage, while the proxy rate stays tied to evaluable moves.

Proxy rate
3 / 6 = 50.0%
Evaluation coverage
6 / 8 = 75.0%

Other slider values are hypothetical examples, not new observations or model results.

Metric registry

contracts/game_draw_rate.json is the versioned definition and result schema. src/chess_analytics/metrics/draw_rate.sql is the reviewed SQL implementation.

Game draw rate 1.0.0: completed eligible drawn games / completed eligible games, one count per provider/game ID. Eligible = rated Standard, excluding marked bots. Unknown results are excluded and reported separately; an empty denominator is null. Marked bots and unknown results are separate non-overlapping exclusion counts (bot status takes precedence). Missing rating does not exclude a draw-rate record.

Result includes exact numerator/denominator, fraction, metric/contract versions, dataset snapshot ID, source hash and caveats. The tiny fixture's manually specified expected result is 4/20. A separate header-tally script verifies the real corpus without using the parser, analytical SQL or DuckDB; it does not verify move legality. Only compare its denominator directly when ingestion reports zero quarantine/conflicts.

This is a descriptive foundation-data check. No uncertainty/population claim, opening comparison, time-pressure analysis or model benchmark is attached to it.

M3 metrics use the checked partial analytical snapshot and are descriptive only:

These definitions are versioned, and M3's independent raw-PGN check confirms selected arithmetic. Source annotations are not independently engine-verified. No monthly, population, or causal interpretation is warranted.

M4 adds opening adjusted score: common pooled weights across three focal-minus-opponent rating-difference strata, with a seeded focal-player cluster bootstrap. The M4 dashboard and analyst tool use the same implementation. Its interval describes dependence within the observed prefix, not uncertainty for all August games.

Governed semantic layer

ADR 0085 gives each core metric one dbt 1.12 semantic definition. query_metric uses it only when the current mart matches the analytical snapshot's source and the mart's model hash matches the project. Any other case, including a failed semantic query, keeps the reviewed SQL. The result keys stay the ones the evidence IDs already hash. compare_openings calls query_metric, so it uses the same path.

Metric Model Rule
eligible_games games view Sum of rated Standard completed games, excluding source-marked bots
game_draw_rate games drawn_games / eligible_games. That ratio is the returned value
opening_usage games Games in one family / games with a known family. No ratio metric; the analyst divides two known_opening_games queries
opening_player_score player_games view opening_player_score and win_rate are the returned rates. The rating filter, time control, and low_support stay in Python

The family expression is nullif(trim(split_part(source_opening, ':', 1)), ''). player_games copies opening family and time control from the game onto each player row. game_key is the foreign entity.

Outside this layer Why it stays out
clock_pressure_error_proxy, evaluation_coverage They read fact_move, which dbt does not build
opening_adjusted_score Seeded cluster bootstrap
Zero denominator Stays null in the analyst

Offline check, after python -m chess_analytics.marts transform:

export DBT_PROFILES_DIR=dbt DBT_PROJECT_DIR=dbt DBT_TARGET=local
export CHESSLAB_DBT_PATH=/tmp/warehouse.duckdb
dbt parse --project-dir dbt --profiles-dir dbt --target-path /tmp/dbt-target
python -m chess_analytics.analyst.semantic validate-configs /tmp/dbt-target/semantic_manifest.json
python -m chess_analytics.analyst.semantic smoke-draw-rate path/to/mart/warehouse.duckdb path/to/mart/semantic_manifest.json

The compiled draw-rate statement divides sum(drawn_game) by nullif(sum(eligible_game), 0) on games and does not constrain utc_date. The replay plan evals/replay_plans/governed_draw_rate.json asks the analyst the same question.

Opening-position metrics

Transpositions uses the checked opening-position publication, with definitions in opening_positions.json.

Metric Numerator / definition Denominator / interpretation
Frequency Distinct games reaching a canonical board at an eligible White decision Eligible games in selected cohort, recorded family, or public lens
Acyclic routes Distinct complete earliest-arrival UCI prefixes without repetition Count, not a percentage or measure of move quality
Alternative-route share Acyclic position games outside the largest route Position games minus repetition-prefix games; null if zero
Study-set coverage Union of games hitting at least one first-N ranked board Eligible view games; a game counts once across the set
Marginal coverage Newly covered games when the next ranked board is added Report count as well as total coverage
Endpoint compression 1 minus distinct endpoints / distinct acyclic routes at one ply Same nonrepeating visits at that ply; descriptive convergence
Recorded-label breadth Complete distinct known families and known ECO codes, counted separately Recorded game tags can reflect later play; five examples are not exhaustive

The two cohort denominators stay separate. Lens frequencies use only matching games; family filters use recorded-family game counts. Repetition arrivals contribute to position frequency but not acyclic routes. Route-card shares use all position games, while alternative-route share uses acyclic games. Draw counters and repetition history are outside the canonical recognition key.

In the Elite report, the leading Sicilian board has 8,465 games and eight routes, but 99.29% of acyclic arrivals use one route. The QGD example has 6,431 games and 28 routes, with only 33.28% on its leading route. Frequency and route count alone would hide that difference. Their alternative-route shares are 0.71% and 66.72%.

Charts and rankings describe selected public games. They do not establish learning improvement, personal encounter rates, engine quality, or causal outcome differences.

Local imported-game metrics

The import contract defines a separate personal population. Selected valid completed Standard White games include short games by default; matched Black games, unmatched players and explicit filter exclusions are reported separately. No public rating floor is silently applied.

Metric Definition Interpretation
Personal board frequency Earliest eligible board visits / selected White games Plies 6–20; short games remain in the denominator
Study-set coverage Union of White games reaching the first N selected published boards / selected White games N=1..20; selected set only, not full-corpus novelty
Personal alternative-route share 1 − leading acyclic route games / acyclic position games Repetition-prefix arrivals excluded; null for no acyclic arrivals
First departure and re-entry First differing move while selected examples define a continuation; later selected canonical-board hit Stops at example endpoints; not move quality or a mistake count

Counts reconcile imports, duplicates, conflicts, rejections and failed files. Unknown date/time/rated metadata stays unknown. Personal results do not change public reports. See the local game guide.

Data dictionary

The explicit storage types are in contracts/tables.json.

Reconciliation: seen = accepted + duplicate + excluded + quarantine + conflict. For conflicts, the whole candidate is withheld, even though first occurrences are present in staging. Failed framing/byte-limit runs may have only a lower-bound scanned byte count (last yielded record); no failed run claims full reconciliation.

Source manifests/run records live in JSON during M1; warehouse dimensions, dbt lineage and recovery records are added in M2.

M3 adds fact_move with the explicit types in m3_tables.json: one selected provider/game ID and ply, side to move, SAN/UCI, after-move clock and source evaluation, previous same-player clock, and derived before/after centipawn values. Missing annotation values stay null; mate scores are separate from centipawns. A selected zero-ply game has no fact_move row but remains in the sample manifest. The M3 source manifest and move manifest explicitly mark the archive partial and retain source/implementation/contract hashes. Only the observed prefix, not the full publisher archive, has a verified compressed-byte hash.

Opening-position publication

Local visit shards retain source/provider/game identity, cohort, decision ply, full FEN, canonical position key and SHA-derived ID, complete UCI prefix, a repeated-position-prefix flag, next move, and recorded family/ECO. Per-position aggregation uses a game's earliest eligible visit. The compact tracked report publishes bounded rankings and examples; it is not the full visit database.

Each view records its game denominator, distinct/recurring/transposing board counts, positions and frequency rankings. Each ranking includes a cumulative union-coverage curve with covered games, share and additional games for N=1..20. Position details retain acyclic games, leading-route games, alternative-route share, route remainder, complete known-family/ECO breadth and unknown-family counts. Public lenses have versioned move-sequence rules and matching-game denominators; white_g3_played_games distinguishes arrivals before and after g3.

The report, configuration, contract, source and implementation hashes are checked by the site builder. Local full FEN preserves draw counters; the recognition key uses pieces, turn, castling and legal en passant only.

Session-only imported games

Imported PGN records are held in a browser worker, separate from the immutable public corpus. Grain: one completed legal Standard game per provider/native ID or provider/fallback semantic identity. Fields include normalized players, date, result, optional rated/time-control metadata, full legal UCI mainline and White visits at plies 6–20. Each visit retains full FEN, canonical first-four-field key, 24-character SHA-derived ID, route, repetition-prefix flag and next move.

File SHA256 identifies bytes separately from semantic game identity. Equivalent annotation copies count once; differing semantic fingerprints withhold all copies of that identity. Explicit player selection is scoped to the provider. An exported summary contains selected aliases, filters, source hashes and derived metrics and routes, rather than raw PGNs/comments. Records are not persisted or published. See the contract and guide.