{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/infersports-dev/json-schema/infersports-dev-opening-line-result-schema.json",
  "title": "OpeningLineResult",
  "description": "One-call NL→opening: resolve a fixture and return each book's opening line + current price.\n\n``status``: ok | ambiguous | not_found (same go/no-go contract as get_sharp_line — never\nguesses an ambiguous fixture). The opening is the **true market open** for every market, so\nopen→current is real movement: for **1x2** a price move; for **totals/AH** compare `line`\n(opening) vs `current_line` to read the line move, and the prices alongside. Best-effort\nupstream: a market with no opening yet on file is omitted.",
  "x-generated": "2026-11-12",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/infersports-dev-openapi.json#/components/schemas/OpeningLineResult",
  "properties": {
    "status": {
      "type": "string",
      "title": "Status"
    },
    "query": {
      "type": "string",
      "title": "Query"
    },
    "summary": {
      "type": "string",
      "title": "Summary"
    },
    "match": {
      "anyOf": [
        {
          "$ref": "#/$defs/MatchSummary"
        },
        {
          "type": "null"
        }
      ]
    },
    "lines": {
      "items": {
        "$ref": "#/$defs/OpeningLine"
      },
      "type": "array",
      "title": "Lines"
    },
    "alternatives": {
      "items": {
        "$ref": "#/$defs/MatchAlternative"
      },
      "type": "array",
      "title": "Alternatives"
    },
    "ask_user": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Ask User"
    },
    "as_of": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "As Of"
    },
    "stale": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Stale",
      "description": "Freshness of the current quotes (W4); null when no match resolved."
    },
    "source": {
      "type": "string",
      "title": "Source",
      "description": "Provenance of the opening odds."
    },
    "decision": {
      "$ref": "#/$defs/Decision",
      "description": "Pre-computed go/no-go (W3): safe_to_proceed + suggested next_action."
    }
  },
  "type": "object",
  "required": [
    "status",
    "query",
    "summary",
    "decision"
  ],
  "$defs": {
    "Decision": {
      "properties": {
        "safe_to_proceed": {
          "type": "boolean",
          "title": "Safe To Proceed",
          "description": "True when the result is unambiguous enough to act on without asking the user."
        },
        "confidence": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Confidence",
          "description": "Resolution confidence 0–1 (null when not applicable)."
        },
        "ask_user": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Ask User",
          "description": "A disambiguation question to put to the user; null when safe_to_proceed."
        },
        "next_action": {
          "anyOf": [
            {
              "$ref": "#/$defs/NextAction"
            },
            {
              "type": "null"
            }
          ],
          "description": "Suggested next call; null when the caller must ask first or nothing follows."
        }
      },
      "type": "object",
      "required": [
        "safe_to_proceed"
      ],
      "title": "Decision",
      "description": "Pre-computed go/no-go for the caller (W3): we decide whether it's safe to proceed so the\nagent doesn't have to reason it out (and risk guessing). Branch on ``safe_to_proceed``:\ntrue → act on the result without asking; false → put ``ask_user`` to the human, or follow\n``next_action`` when there's a clear recovery step (e.g. nothing resolved → list fixtures)."
    },
    "MatchAlternative": {
      "properties": {
        "event_id": {
          "type": "string",
          "title": "Event Id"
        },
        "label": {
          "type": "string",
          "title": "Label",
          "description": "'Home vs Away' label for disambiguation."
        },
        "confidence": {
          "type": "number",
          "title": "Confidence"
        }
      },
      "type": "object",
      "required": [
        "event_id",
        "label",
        "confidence"
      ],
      "title": "MatchAlternative",
      "description": "A runner-up fixture when a query is ambiguous."
    },
    "MatchSummary": {
      "properties": {
        "event_id": {
          "type": "string",
          "title": "Event Id",
          "description": "Canonical event id (evt_…); pass to get_match_odds / compare_lines."
        },
        "sport": {
          "type": "string",
          "title": "Sport",
          "description": "football | basketball"
        },
        "league": {
          "type": "string",
          "title": "League",
          "description": "League name, e.g. 'English Premier League'."
        },
        "home_team": {
          "type": "string",
          "title": "Home Team"
        },
        "away_team": {
          "type": "string",
          "title": "Away Team"
        },
        "scheduled_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Scheduled At",
          "description": "Kickoff, ISO-8601 UTC."
        },
        "scheduled_at_local": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Scheduled At Local",
          "description": "Kickoff in the requested timezone (ISO-8601); null when no timezone was given."
        },
        "status": {
          "type": "string",
          "title": "Status",
          "description": "scheduled | live | finished"
        },
        "score": {
          "anyOf": [
            {
              "$ref": "#/$defs/Score"
            },
            {
              "type": "null"
            }
          ],
          "description": "Current score when live, else null."
        },
        "red_cards": {
          "anyOf": [
            {
              "$ref": "#/$defs/Score"
            },
            {
              "type": "null"
            }
          ],
          "description": "Red cards per side; null when not tracked by upstream."
        },
        "clock": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Clock",
          "description": "Match clock when live, humanized for display: e.g. '1st half 25'', '2nd half 47'' (the real minute, not estimated) or 'half-time'; null pre-match or when the feed omits it. Basketball clocks (e.g. 'Q2 4:12') pass through as-is."
        },
        "confidence": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Confidence",
          "description": "Fuzzy-match confidence 0–1 (find_match only; null elsewhere)."
        },
        "summary": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Summary",
          "description": "One-line human-readable status, e.g. \"Man City vs Arsenal — LIVE 1h 23 2-1.\" or \"… — Kicks off 18:00 UTC on Jun 01.\" — ready to read to a user."
        }
      },
      "type": "object",
      "required": [
        "event_id",
        "sport",
        "league",
        "home_team",
        "away_team",
        "status"
      ],
      "title": "MatchSummary",
      "description": "A fixture flattened for agent consumption (teams as names, optional live score)."
    },
    "NextAction": {
      "properties": {
        "tool": {
          "type": "string",
          "title": "Tool",
          "description": "Tool to call next, e.g. 'get_match_odds' | 'compare_lines' | 'list_today_matches'."
        },
        "args": {
          "additionalProperties": {
            "type": "string"
          },
          "type": "object",
          "title": "Args",
          "description": "Ready-to-use arguments for that call, e.g. {'event_id': 'evt_…'}."
        },
        "reason": {
          "type": "string",
          "title": "Reason",
          "description": "Why this is the suggested next step."
        }
      },
      "type": "object",
      "required": [
        "tool",
        "reason"
      ],
      "title": "NextAction",
      "description": "The single best next call to make, pre-computed so the agent doesn't have to plan it (W3)."
    },
    "OpeningLine": {
      "properties": {
        "bookmaker": {
          "type": "string",
          "title": "Bookmaker"
        },
        "market_type": {
          "type": "string",
          "title": "Market Type",
          "description": "1x2 | asian_handicap | totals"
        },
        "period": {
          "type": "string",
          "title": "Period",
          "description": "full_time | half_time"
        },
        "line": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Line",
          "description": "The OPENING line (null for 1x2). For totals/AH this is the line the market opened at; it may differ from `current_line` — that difference IS the line movement."
        },
        "handicap_components": {
          "anyOf": [
            {
              "items": {
                "type": "number"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "title": "Handicap Components",
          "description": "Quarter-line split of the opening line, when applicable."
        },
        "opening": {
          "additionalProperties": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "integer"
              }
            ]
          },
          "type": "object",
          "title": "Opening",
          "description": "Opening (first-seen, frozen) prices in the requested format."
        },
        "current": {
          "anyOf": [
            {
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "integer"
                  }
                ]
              },
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Current",
          "description": "Current prices (null if no current quote). 1x2 is same line; for totals/AH read alongside `current_line`, as the line may have moved since open — diff for movement."
        },
        "current_line": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Current Line",
          "description": "Line the CURRENT quote sits at (null for 1x2 or when no current quote). Differs from `line` when the market moved since open."
        },
        "current_handicap_components": {
          "anyOf": [
            {
              "items": {
                "type": "number"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "title": "Current Handicap Components",
          "description": "Quarter-line split of the current line, when applicable."
        },
        "opened_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Opened At",
          "description": "When the upstream first recorded this opening (ISO-8601 UTC)."
        },
        "format": {
          "type": "string",
          "title": "Format",
          "default": "decimal"
        }
      },
      "type": "object",
      "required": [
        "bookmaker",
        "market_type",
        "period",
        "opening"
      ],
      "title": "OpeningLine",
      "description": "A book's opening (first-seen) quote paired with its current quote for movement reading."
    },
    "Score": {
      "properties": {
        "home": {
          "type": "integer",
          "title": "Home"
        },
        "away": {
          "type": "integer",
          "title": "Away"
        }
      },
      "type": "object",
      "required": [
        "home",
        "away"
      ],
      "title": "Score"
    }
  }
}