{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "vclaw-video-core/video/artifacts/match-highlights",
  "title": "match-highlights",
  "description": "Output of `vclaw video match-highlights`: the per-segment analysis ledger, every detected event on the source timeline, and the reels that were cut. Written to <out>/match-highlights.json (a standalone output directory, not a project artifacts/ folder). In --events-file mode the events came from a file rather than from a listing, so `segments` is empty and `passes.listing` reports zero calls.",
  "type": "object",
  "required": [
    "schemaVersion",
    "source",
    "generatedAt",
    "model",
    "sport",
    "segmentSeconds",
    "segments",
    "events",
    "reels",
    "providerCalls"
  ],
  "properties": {
    "schemaVersion": {
      "const": 1
    },
    "source": {
      "type": "string",
      "description": "Absolute path of the recording that was cut."
    },
    "generatedAt": {
      "type": "string",
      "format": "date-time"
    },
    "model": {
      "type": "string",
      "description": "Agentic Gemini model id used for every segment call."
    },
    "segmentSeconds": {
      "type": "number",
      "minimum": 60,
      "maximum": 1800
    },
    "segments": {
      "type": "array",
      "description": "One row per stream-copied segment. `start`/`end` are the REAL offsets from ffmpeg's -segment_list CSV (cuts land on keyframes), never index * segmentSeconds.",
      "items": {
        "type": "object",
        "required": [
          "index",
          "file",
          "start",
          "end",
          "status",
          "events"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "file": {
            "type": "string"
          },
          "start": {
            "type": "number",
            "minimum": 0
          },
          "end": {
            "type": "number",
            "minimum": 0
          },
          "status": {
            "type": "string",
            "enum": [
              "planned",
              "analyzed",
              "cached",
              "failed"
            ],
            "description": "`cached` reused a completed answer on disk (no provider call). `failed` is terminal and never retried."
          },
          "uploadSeconds": {
            "type": "number",
            "minimum": 0
          },
          "analysisSeconds": {
            "type": "number",
            "minimum": 0
          },
          "events": {
            "type": "integer",
            "minimum": 0
          },
          "usage": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "events": {
      "type": "array",
      "description": "Every detected event, in source-timeline seconds, ordered by start.",
      "items": {
        "type": "object",
        "required": [
          "s",
          "e",
          "segment"
        ],
        "properties": {
          "s": {
            "type": "number",
            "minimum": 0,
            "description": "Start second on the source timeline."
          },
          "e": {
            "type": "number",
            "minimum": 0,
            "description": "End second on the source timeline."
          },
          "t": {
            "type": "string",
            "description": "Event type, e.g. dot | runs | four | six | wicket | wide | noball | other."
          },
          "n": {
            "type": "string",
            "description": "Short note from the model."
          },
          "segment": {
            "type": "string",
            "description": "Segment file the event was read from."
          },
          "placement": {
            "type": "string",
            "enum": [
              "model",
              "unplaced"
            ],
            "description": "Set only by the --place pass. `model`: the pass watched the window and said where the ball is, so the window is [runUpStart - 1, deadBall + 3]. `unplaced`: it was asked and could not (nothing returned, kind \"none\", a timestamp outside its own window, or an implausibly long result), so the events file's own window stands. Absent means the pass was never asked about this event."
          },
          "lead": {
            "type": "number",
            "minimum": 0,
            "description": "Seconds of lead the --place pass gave this event's candidate window, chosen by its type (boundaries 25 s, wickets and unknown 35 s by default). Recorded so a run is reproducible and so an `unplaced` event says how much room it was given."
          }
        },
        "additionalProperties": false
      }
    },
    "reels": {
      "type": "array",
      "description": "The cut reels. A reel with `skipped` was not rendered (for example --types matched no event).",
      "items": {
        "type": "object",
        "required": [
          "name",
          "path",
          "windows",
          "seconds"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "windows": {
            "type": "integer",
            "minimum": 0
          },
          "seconds": {
            "type": "number",
            "minimum": 0
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "skipped": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "usage": {
      "type": "object",
      "description": "Token usage summed across every call in every pass: the per-segment listing calls plus the gap, judge and second-judge reels. Per-pass totals are in `passes`.",
      "additionalProperties": true
    },
    "providerCalls": {
      "type": "integer",
      "minimum": 0,
      "description": "Gemini Interactions calls actually made across every pass. Cached answers do not count; a failed call does. A --dry-run reports 0."
    },
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Non-fatal observations: dropped rows, failed segments, skipped reels."
    },
    "sport": {
      "type": "string",
      "description": "Prompt set used (SPORT_PROMPTS key), e.g. cricket."
    },
    "gapsScanned": {
      "type": "integer",
      "minimum": 0,
      "description": "Gap-pass stretches cut and scanned. A stretch that held nothing still counts."
    },
    "recovered": {
      "type": "integer",
      "minimum": 0,
      "description": "Deliveries the gap pass found that the one-pass listing had missed."
    },
    "deduped": {
      "type": "integer",
      "minimum": 0,
      "description": "Duplicate rows collapsed at the 4-second start rule."
    },
    "judged": {
      "type": "array",
      "description": "Every judge verdict, kept whether or not it survived into a reel. judge-1 rules on a candidate window; judge-2 re-examines a rejected candidate's whole stretch and carries its own timestamps.",
      "items": {
        "type": "object",
        "required": [
          "i",
          "start",
          "end",
          "boundary",
          "via"
        ],
        "properties": {
          "i": {
            "type": "integer",
            "minimum": 0,
            "description": "Window index inside the judged reel."
          },
          "start": {
            "type": "number",
            "minimum": 0
          },
          "end": {
            "type": "number",
            "minimum": 0
          },
          "boundary": {
            "type": "boolean",
            "description": "Did the event actually happen?"
          },
          "kind": {
            "type": "string",
            "description": "four | six | wicket | none."
          },
          "evidence": {
            "type": "string",
            "description": "The model's short justification."
          },
          "via": {
            "type": "string",
            "enum": [
              "judge-1",
              "judge-2"
            ]
          }
        },
        "additionalProperties": false
      }
    },
    "placed": {
      "type": "integer",
      "minimum": 0,
      "description": "--place: candidate events the model timed. Their window is [runUpStart - 1, deadBall + 2] and their `placement` is `model`."
    },
    "unplaced": {
      "type": "integer",
      "minimum": 0,
      "description": "--place: candidates it was asked about and could not time (nothing returned, kind \"none\", a bad number, or a failed call). Their original window stands and their `placement` is `unplaced`; they are never dropped."
    },
    "passes": {
      "type": "object",
      "description": "Per-pass cost and wall time, reported separately so the passes can be compared.",
      "required": [
        "listing"
      ],
      "properties": {
        "listing": {
          "type": "object",
          "required": [
            "calls",
            "seconds"
          ],
          "properties": {
            "calls": {
              "type": "integer",
              "minimum": 0,
              "description": "Interactions calls this pass made. A failed call counts: it was billed."
            },
            "seconds": {
              "type": "number",
              "minimum": 0,
              "description": "Wall-clock seconds, uploads included."
            },
            "usage": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "additionalProperties": false
        },
        "gap": {
          "type": "object",
          "required": [
            "calls",
            "seconds"
          ],
          "properties": {
            "calls": {
              "type": "integer",
              "minimum": 0,
              "description": "Interactions calls this pass made. A failed call counts: it was billed."
            },
            "seconds": {
              "type": "number",
              "minimum": 0,
              "description": "Wall-clock seconds, uploads included."
            },
            "usage": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "additionalProperties": false
        },
        "judge": {
          "type": "object",
          "required": [
            "calls",
            "seconds"
          ],
          "properties": {
            "calls": {
              "type": "integer",
              "minimum": 0,
              "description": "Interactions calls this pass made. A failed call counts: it was billed."
            },
            "seconds": {
              "type": "number",
              "minimum": 0,
              "description": "Wall-clock seconds, uploads included."
            },
            "usage": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "additionalProperties": false
        },
        "judge2": {
          "type": "object",
          "required": [
            "calls",
            "seconds"
          ],
          "properties": {
            "calls": {
              "type": "integer",
              "minimum": 0,
              "description": "Interactions calls this pass made. A failed call counts: it was billed."
            },
            "seconds": {
              "type": "number",
              "minimum": 0,
              "description": "Wall-clock seconds, uploads included."
            },
            "usage": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "additionalProperties": false
        },
        "place": {
          "type": "object",
          "required": [
            "calls",
            "seconds"
          ],
          "properties": {
            "calls": {
              "type": "integer",
              "minimum": 0,
              "description": "Interactions calls this pass made. A failed call counts: it was billed."
            },
            "seconds": {
              "type": "number",
              "minimum": 0,
              "description": "Wall-clock seconds, uploads included."
            },
            "usage": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "additionalProperties": false,
          "description": "The --place pass: one agentic call per placement reel. In --events-file mode it is the only pass that spends."
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}
