{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/lumify-ai/json-schema/lumify-ai-intelligence-response-schema.json",
  "title": "IntelligenceResponse",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/lumify-ai-openapi.json#/components/schemas/IntelligenceResponse",
  "properties": {
    "event_id": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Event Id",
      "description": "Lumify event ID this intelligence describes. Present on the standalone endpoint; omitted when inlined under an event via include_intelligence."
    },
    "available": {
      "type": "boolean",
      "title": "Available",
      "description": "False when no predictive `bets[]` have been computed for this event yet. `forecasts[]` can still populate on MLB / NFL / NCAAF / NBA / NCAAB / NHL / tennis. The request is free only when `available` is false and `forecasts` is empty.",
      "default": false
    },
    "odds_source": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Odds Source",
      "description": "Bookmaker `bets[].market` prices were sourced from. For predictive intelligence this is the assessment's market_book (not a live `?bookmaker=` overlay)."
    },
    "sport": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Sport",
      "description": "Sport slug for this event."
    },
    "league": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "League",
      "description": "League slug for this event, if any."
    },
    "players": {
      "additionalProperties": true,
      "type": "object",
      "title": "Players",
      "description": "Home/away (or p1/p2) participant identification, keyed by role: {role: {name, player_id, team_id}}.",
      "default": {}
    },
    "has_recommend": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Has Recommend",
      "description": "True when at least one bet meets the recommendation threshold; false when none do; null when intelligence has not been computed for this event. For predictive-framework sports a recommendation requires a non-null `tier` derived from `edge`, so this is false while Edge is not yet published (current MLB/soccer/tennis Stage 1)."
    },
    "match_overview": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Match Overview",
      "description": "Natural-language pre-game preview of the matchup — form, context, and what to watch. Written before kickoff by a Search-backed overlay on every event sport (MLB, NFL, NCAAF, NBA, NCAAB, NHL, soccer, tennis); sport-shaped queries so tennis does not invent pitchers. The request path only reads the stored row. Null when the pre-kickoff job has not run or the fixture was not eligible."
    },
    "rationale": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Rationale",
      "description": "Event-level factual pre-game matchup chips (injuries, recent form, lineup/availability news — sport-shaped). Written before kickoff; the request path only reads the stored row. Null when not generated."
    },
    "intelligence_updated_at": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Intelligence Updated At",
      "description": "ISO-8601 UTC timestamp of the most recent change anywhere in this payload — the maximum of the per-bet `computed_at` values for predictive-framework sports. Because individual markets are only rewritten when they move, use the per-bet `computed_at` to reason about a specific bet rather than this event-level maximum."
    },
    "bets": {
      "items": {
        "$ref": "#/$defs/BetIntelligence"
      },
      "type": "array",
      "title": "Bets",
      "description": "One entry per priced bet token on the predictive intelligence surface. Outcomes of the same market sum to 1.",
      "default": []
    },
    "forecasts": {
      "items": {
        "$ref": "#/$defs/ForecastWager"
      },
      "type": "array",
      "title": "Forecasts",
      "description": "Forecasted Over/Under player-prop wagers for this event from the same rate model as `GET /v1/intelligence/forecasts`. `selected` marks the top-conviction rows that also appear on the daily slate; the rest of the event catalog is included with `selected: false`. Empty when the sport is outside MLB / NFL / NCAAF / NBA / NCAAB / NHL / tennis, or no strictly-matched two-way main is in range (props) / no ranked two-way moneyline (tennis). Same wager object as the slate — see `ForecastWager`.",
      "default": []
    },
    "matchup": {
      "anyOf": [
        {
          "additionalProperties": true,
          "type": "object"
        },
        {
          "type": "null"
        }
      ],
      "title": "Matchup",
      "description": "Not returned. Use `GET /v1/events/{id}/stats` for pitcher/lineup Data."
    }
  },
  "additionalProperties": true,
  "type": "object",
  "$defs": {
    "BetIntelligence": {
      "properties": {
        "bet_type": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Bet Type",
          "description": "Canonical bet token, e.g. ML_HOME/ML_AWAY/ML_DRAW (soccer), ML_P1/ML_P2 (head-to-head sports), SPREAD_HOME/SPREAD_AWAY, OVER, UNDER."
        },
        "player_role": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Player Role",
          "description": "'home'/'away' for team sports or 'p1'/'p2' for head-to-head sports. Null for match-level tokens (OVER, UNDER, ML_DRAW), which are not about either side."
        },
        "player_id": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Player Id",
          "description": "Player ID when this bet is about an individual player; null for team bets and match-level tokens (OVER, UNDER, ML_DRAW)."
        },
        "team_id": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Team Id",
          "description": "Team ID when this bet is about a team; null for individual-player bets and match-level tokens (OVER, UNDER, ML_DRAW). A draw is not a bet on either team, so ML_DRAW carries no team_id — safe to sum exposure by team_id across bets without double-counting the draw."
        },
        "player_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Player Name",
          "description": "Display name of the player or team this bet is about; null for match-level tokens (OVER, UNDER, ML_DRAW). Duplicates `players[player_role].name` for convenience when flattening bets[]."
        },
        "market": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Market",
          "description": "Market quote used for this bet: {price: American odds, line: handicap/total line or null for moneyline, book: bookmaker slug when known}. For predictive-framework sports `book` is always populated from the assessment; for legacy sports it may be omitted. Present for every sport."
        },
        "computed_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Computed At",
          "description": "ISO-8601 UTC timestamp of when this bet's numbers last materially changed — not when they were last checked. The publisher re-runs on a schedule but only rewrites a row when the price, line, or probability moves beyond a tolerance, so an older `computed_at` means 'unchanged since', not 'stale'. Values legitimately differ between bets on the same event because each market moves independently."
        },
        "tier": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Tier",
          "description": "Recommendation tier: 'very_high', 'strong', 'moderate', or 'avoid'. Null whenever `edge` is null — a tier ranks a bet against its price."
        },
        "confidence_score": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Confidence Score",
          "description": "Not published. Use `probability`."
        },
        "confidence_score_pre_validator": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Confidence Score Pre Validator",
          "description": "Not published."
        },
        "coverage": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Coverage",
          "description": "Not published."
        },
        "signals": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Signals",
          "description": "Not published."
        },
        "validator": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Validator",
          "description": "Not published."
        },
        "narrative": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Narrative",
          "description": "Pick-defense rationale for a high-confidence main-line side — why THIS token hits, not a match preview or stat recap. Present only when Lumify made a pick on this token (`forecasts[]` row with `selected: true`, matching `bet_type`, and a research note). Null on the opposite side, on coin-flip markets, and when no pick was made."
        },
        "rationale": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "title": "Rationale",
          "description": "Not published. Event-level context chips live on the top-level `rationale` of IntelligenceResponse.",
          "default": []
        },
        "attribution": {
          "items": {
            "type": "string"
          },
          "type": "array",
          "title": "Attribution",
          "description": "Not published.",
          "default": []
        },
        "probability": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Probability",
          "description": "Published probability for this outcome, 0-1. Outcomes of the same market (e.g. ML_HOME + ML_DRAW + ML_AWAY) sum to 1. Present on the customer surface for MLB, soccer, and tennis."
        },
        "interval": {
          "anyOf": [
            {
              "items": {
                "type": "number"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "title": "Interval",
          "description": "[lo, hi] band around `probability`, 0-1. Read it as 'how much evidence backs this number', not as a statistical confidence interval. A freshly-opened, unmoved line gets a wider band than a heavily-traded one. Thin evidence widens this band rather than removing the response. Present on the customer surface for MLB, soccer, and tennis."
        },
        "p_model": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "P Model",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Fitted model probability before blending with the market."
        },
        "p_market": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "P Market",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). De-vigged market probability before blending; use `probability` / `fair_price` instead."
        },
        "blend_w": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Blend W",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Weight on the model when blending with the market."
        },
        "fair_price": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Fair Price",
          "description": "American-odds fair price implied by `probability` — the vig-free line. Comparing it to `market.price` gives the bookmaker's margin on this side. Predictive-framework sports only."
        },
        "edge": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Edge",
          "description": "Expected profit per 1 unit staked at `market.price`, i.e. `probability x decimal_odds - 1`. Positive means the price pays more than the probability justifies. Null when no model-backed edge is published (current MLB/soccer/tennis Stage 1). Present on the customer surface — null today, reserved for future recommend gating."
        },
        "sufficiency": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Sufficiency",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Evidence strength 0-1 used internally to set interval width."
        },
        "phase": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Phase",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Pipeline stage (`quant` / `full`)."
        },
        "model_version": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Model Version",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Parameter-set identifier for traceability."
        },
        "drivers": {
          "items": {
            "additionalProperties": true,
            "type": "object"
          },
          "type": "array",
          "title": "Drivers",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Signed contributions to probability used in the assessment pipeline.",
          "default": []
        },
        "alignment": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Alignment",
          "description": "Internal — omitted from customer payloads (MLB/soccer/tennis). Qualitative-overlay agreement detail."
        },
        "fair": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Fair",
          "description": "Sharp-consensus fair price for this outcome: {probability, books, n_books, is_consensus}. `probability` is distinct from `p_market` (the *priced* book's own de-vigged probability) and from `fair_price` (American odds implied by the published `probability`, above) — it is the sharp-book reference a cross-book price gap is measured against (MLS: Pinnacle / `SOCCER_SHARP_BOOKS`; MLB: Pinnacle+Circa / `MLB_SHARP_BOOKS`). `is_consensus` is false whenever `n_books` < 2. Populated for MLS and MLB; null for other leagues until their market plane is wired. Predictive-framework sports only."
        },
        "edges_by_book": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Edges By Book",
          "description": "Price gap versus the sharp-reference fair probability (`fair.probability`), keyed by bookmaker — `fair.probability × decimal_odds − 1` at each book's posted price. A line-shopping metric: rank soft books by these gaps (largest gap = furthest above the sharp reference). Populated for MLS (retail soft books for Price — Edge archive stays FanDuel/Hard Rock) and MLB (retail soft books: DK/FD/BetMGM/Caesars/Bet365/Hard Rock/BetOnline — Price surface; Edge allowlist remains independence-cleared DK/FD only). Null for other leagues or markets with no soft quote. Predictive-framework sports only."
        },
        "best": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Best",
          "description": "Highest price-gap book across `edges_by_book`: {book, price, edge, quote_age_seconds}. Same metric as `edges_by_book`: price gap versus a de-vigged sharp reference. On MLB, `best_*` is Tier C / informational line-shopping even when the gap is positive (DK/FD ML and RL/totals are hard-null for Edge). Use `best.book`/`best.price` as the line-shopping target. `quote_age_seconds` is `best.book`'s posted-price age at publish — quotes older than 30 minutes are excluded. Populated for MLS and MLB; null otherwise or when `edges_by_book` is empty. Predictive-framework sports only."
        },
        "ev": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Ev",
          "description": "**Beta.** Customer-facing expected-value estimate: {beta: true, book, price, ev_pct, kelly_fraction, quote_age_seconds, n_books}. `ev_pct` is the selected book's price gap × 100 (`fair.probability × decimal − 1`); `kelly_fraction` is the full-Kelly stake, capped at 1.0. Not a new measurement — re-packages the same sharp-fair price gap as `best`/`edges_by_book`. Main-line tokens (moneyline, spreads, totals; tennis totals unpublished). A single sharp book (n=1 Pinnacle) is a valid fair reference; soccer and tennis never have a two-book sharp consensus. Gaps ≤ 0 or above 25% (stale-line artifact) are dropped. When `best` is a suppressed MLB moneyline book (DraftKings/FanDuel proven null, or BetMGM/Bet365/BetOnline unscreened), the next eligible book in `edges_by_book` is used. Spreads/totals do not inherit that suppression. Unverified market-consensus estimate — see docs/internal/sports/PREDICTIVE_CLAIM_LADDER.md."
        }
      },
      "additionalProperties": true,
      "type": "object",
      "title": "BetIntelligence"
    },
    "ForecastDriver": {
      "properties": {
        "id": {
          "type": "string",
          "title": "Id",
          "description": "Namespaced driver id. Player-prop rows: typically `player.l10_rate` — the recent-form counting-stat rate that fed the Poisson step (also `player.season_rate` when L10 is too thin). Main-line rows would use a different namespace (e.g. a rating or surface split) once a main-line forecast model ships."
        },
        "input": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Input",
          "description": "Measured rate/rating behind this driver, in the units native to the underlying model (e.g. 0.27 runs/game, 7.4 strikeouts/start for the prop rate model). This is λ before the Poisson vs the posted line on prop rows."
        },
        "effect": {
          "type": "number",
          "title": "Effect",
          "description": "Signed shift in `p_hit` versus a baseline case on the same line, in probability units. Positive / `direction=up` means this driver lifts the chosen outcome. 0.0 on yardage markets (league means are diluted by backups)."
        },
        "direction": {
          "type": "string",
          "title": "Direction",
          "description": "`up` when `effect` > 0, `down` when `effect` < 0, `neutral` at zero."
        }
      },
      "additionalProperties": true,
      "type": "object",
      "required": [
        "id",
        "effect",
        "direction"
      ],
      "title": "ForecastDriver"
    },
    "ForecastWager": {
      "properties": {
        "event_id": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Event Id",
          "description": "Lumify event ID. Present on the daily slate (`GET /v1/intelligence/forecasts`); omitted on `GET /v1/events/{id}/intelligence` `forecasts[]` (the event is the path)."
        },
        "sport": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Sport",
          "description": "Sport slug. Slate only."
        },
        "league": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "League",
          "description": "League slug, if any. Slate only."
        },
        "commence_time": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Commence Time",
          "description": "ISO-8601 UTC scheduled start. Slate only."
        },
        "home_team": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Home Team",
          "description": "Home team display name. Slate only."
        },
        "away_team": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Away Team",
          "description": "Away team display name. Slate only."
        },
        "player": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Player",
          "description": "Player display name from the priced line. Null on main-line rows with no player attribution (`bet_type` is a match-level token like OVER/UNDER/ML_DRAW)."
        },
        "player_id": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Player Id",
          "description": "Lumify player ID. Bound by exact / Jr-stripped name match to the priced line. Null on main-line rows with no player attribution."
        },
        "bet_type": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Bet Type",
          "description": "Main-line token when this row is a main-line forecast — `ML_P1`/`ML_P2`/`ML_HOME`/`ML_AWAY`/`ML_DRAW`, `SPREAD_P1`/`SPREAD_P2`/`SPREAD_HOME`/`SPREAD_AWAY`, or `OVER`/`UNDER` (same vocabulary as `bets[].bet_type` and `list_ev`). Null on player-prop rows — that's the discriminator between the two row shapes. Tennis sets `ML_P1`/`ML_P2` (moneyline, ranking Bradley-Terry), `SPREAD_P1`/`SPREAD_P2` (game handicap), and `OVER`/`UNDER` (total games — soft-book game-unit lines only, never Pinnacle's set-unit total). NFL / NCAAF / MLB / soccer set the same tokens from a high-confidence assessment lean (one side per market; coin flips are not emitted as picks)."
        },
        "player_role": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Player Role",
          "description": "`home`/`away` for team sports or `p1`/`p2` for head-to-head sports, set only alongside a non-null `bet_type`. Null for player-prop rows and for match-level main-line tokens (OVER, UNDER, ML_DRAW)."
        },
        "market": {
          "type": "string",
          "title": "Market",
          "description": "Player-prop rows: prop category — counting stats. MLB: strikeouts_pitcher, hits, rbis, runs, strikeouts_batter, hits_allowed, earned_runs, outs_recorded. NFL/NCAAF: passing_yards, rushing_yards, receiving_yards, receptions, passing_tds, rushing_tds, touchdowns (anytime-TD combo: rushing + receiving + defensive TDs), rush_rec_yards, pass_rush_yards. NBA/NCAAB: points, rebounds, assists, steals, blocks, threes_made. NHL: goals, hockey_assists, shots_on_goal. Main-line rows (when `bet_type` is set): the main-line family — `h2h`, `spreads`, or `totals`."
        },
        "line": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Line",
          "description": "Posted line the model was scored against. Player-prop and `totals` main-line rows: Over/Under number. `spreads` main-line rows: the spread number. Null on `h2h`/moneyline rows."
        },
        "side": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Side",
          "description": "Player-prop rows: `over` or `under` — the more likely side given the player's shrunken rate versus `line`. The model always picks one when the line is in range. Null on main-line rows — read the picked outcome from `bet_type` instead."
        },
        "p_hit": {
          "type": "number",
          "title": "P Hit",
          "description": "Model probability (0–1) that the picked outcome (`side` on prop rows, `bet_type` on main-line rows) hits. Prop rows: built from the player's shrunken counting-stat rate (Poisson vs the posted Over/Under). Optional `research` may nudge a prop's `p_hit` by at most ±0.03; the chosen side stays the same."
        },
        "interval": {
          "items": {
            "type": "number"
          },
          "type": "array",
          "title": "Interval",
          "description": "`[lo, hi]` stated band around `p_hit`. Wider when `sufficiency` is low (thin recent sample). A stated evidence band, not a statistical confidence interval."
        },
        "sufficiency": {
          "type": "number",
          "title": "Sufficiency",
          "description": "Evidence weight on 0–1: n / (n + 12), where n is the recent-form game count (typically L10). A player with 10 recent games has sufficiency ≈ 0.45 — about half the rate is player-observed, half prior. The wager still ships at any sufficiency; this field tells you how much of `p_hit` is earned from this player's own boxes."
        },
        "reliability": {
          "type": "string",
          "title": "Reliability",
          "description": "Badge on this sport×market cell: `emerging`, `moderate`, `high`, or `informational`. v0 ships `emerging` for every wager; cells promote as a scored history accumulates."
        },
        "conviction": {
          "type": "number",
          "title": "Conviction",
          "description": "Board rank key on 0–1: |p_hit − 0.5| × 2 × sufficiency × research multiplier. A 0.76 call on a thin sample ranks below a 0.62 call on a long one. Highest conviction first on the daily slate."
        },
        "model_version": {
          "type": "string",
          "title": "Model Version",
          "description": "Scorer version, e.g. `forecast-v0`."
        },
        "selected": {
          "type": "boolean",
          "title": "Selected",
          "description": "True when this wager is on the daily board (top conviction, up to 3 per event / 25 on the slate). On `GET /intelligence` the array includes both selected rows and the rest of the event catalog."
        },
        "books": {
          "additionalProperties": true,
          "type": "object",
          "title": "Books",
          "description": "American price for the picked outcome, keyed by bookmaker slug (e.g. `{draftkings: -453}`). Posted market price for the side/bet_type the model picked — use it to see what books are charging for that outcome."
        },
        "drivers": {
          "items": {
            "$ref": "#/$defs/ForecastDriver"
          },
          "type": "array",
          "title": "Drivers",
          "description": "Signed contributions that moved `p_hit`. Read `input` as the measured rate/rating behind the pick and `effect` as how far it lifts (or lowers) the chosen outcome versus a baseline on the same line.",
          "default": []
        },
        "research": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Research",
          "description": "Optional Deep Research overlay when the background job has written a row for this wager — every wager on the daily board (`selected: true`), plus other high-probability wagers (`p_hit` ≥ 0.80) in the catalog: `{stance, conviction, note}`. `stance` is `validate` / `neutral` / `invalidate`. `note` is the wager-level why (the fact the model cannot see) — on a selected main-line row it is also copied to `bets[].narrative`. Validate / invalidate rows always carry a note. Validate nudges `p_hit` up by at most 0.03 and lifts conviction. Invalidate drops the wager from `forecasts[]` and the daily board — it is not a recommended pick. The chosen `side` stays the same. Null means read `p_hit` from the rate model alone — missing research is fail-open, not a 400."
        }
      },
      "additionalProperties": true,
      "type": "object",
      "required": [
        "market",
        "p_hit",
        "interval",
        "sufficiency",
        "reliability",
        "conviction",
        "model_version",
        "selected",
        "books"
      ],
      "title": "ForecastWager"
    }
  }
}