{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://trainmatricx.com/samples/football-event-data.schema.json",
  "title": "Train Matricx football event data (tm-football-1.x)",
  "description": "JSON Schema for football (soccer) match event files. Free to copy and adapt. Required fields are the minimum a model or analysis pipeline needs; optional fields add video alignment for computer vision training. One file covers one match video file. Explained at https://trainmatricx.com/blog/football-event-data-json-schema-data-dictionary",
  "type": "object",
  "required": [
    "match",
    "events"
  ],
  "properties": {
    "_note": {
      "type": "string",
      "description": "Free-text note, e.g. that a file is a synthetic example."
    },
    "match": {
      "$ref": "#/$defs/match",
      "description": "File-wide header: match, coordinate system, versions and video source."
    },
    "events": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/event"
      },
      "description": "All events in the file, in time order."
    }
  },
  "$defs": {
    "match": {
      "type": "object",
      "required": [
        "match_id",
        "home_team",
        "away_team",
        "coordinate_system",
        "schema_version"
      ],
      "properties": {
        "match_id": {
          "type": "string",
          "description": "Stable ID for the match. Never reused."
        },
        "competition": {
          "type": "string",
          "description": "Competition or league name."
        },
        "home_team": {
          "type": "string",
          "description": "Home team name as it should appear downstream."
        },
        "away_team": {
          "type": "string",
          "description": "Away team name as it should appear downstream."
        },
        "period": {
          "type": "integer",
          "minimum": 1,
          "description": "Set when the file covers a single period."
        },
        "coordinate_system": {
          "$ref": "#/$defs/coordinate_system",
          "description": "How x and y are defined for every event in the file."
        },
        "schema_version": {
          "type": "string",
          "pattern": "^tm-football-1\\.[0-9]+$",
          "description": "Version of this schema the file was written against."
        },
        "guideline_version": {
          "type": "string",
          "description": "Version of the event definitions (tagging guidelines) used. Separate from schema_version: definitions can change without the file format changing."
        },
        "package": {
          "type": "string",
          "description": "Scope of tagging, e.g. Key Events, Full Events, Complete."
        },
        "video": {
          "$ref": "#/$defs/video_source",
          "description": "Video file the frame numbers refer to. Required for computer vision training data."
        }
      }
    },
    "coordinate_system": {
      "type": "object",
      "required": [
        "name",
        "origin",
        "x_axis",
        "units"
      ],
      "properties": {
        "name": {
          "type": "string",
          "examples": [
            "pitch_105x68_m"
          ],
          "description": "Short name for the coordinate system."
        },
        "origin": {
          "type": "string",
          "examples": [
            "centre mark"
          ],
          "description": "Where (0, 0) is."
        },
        "x_axis": {
          "type": "string",
          "description": "Which way +x points. State it per team and per period, or say it is fixed.",
          "examples": [
            "toward home team's attacking goal"
          ]
        },
        "y_axis": {
          "type": "string",
          "examples": [
            "toward home team's left touchline"
          ],
          "description": "Which way +y points."
        },
        "units": {
          "type": "string",
          "enum": [
            "metres",
            "yards",
            "percent"
          ],
          "description": "Units of x and y. Use metres for anything that computes distance or speed."
        },
        "pitch_length": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Real pitch length in the stated units, when known."
        },
        "pitch_width": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Real pitch width in the stated units, when known."
        }
      }
    },
    "video_source": {
      "type": "object",
      "required": [
        "file",
        "fps"
      ],
      "description": "The exact video file the frame numbers refer to. A re-encoded copy can shift frames.",
      "properties": {
        "file": {
          "type": "string",
          "description": "File name of the exact video the frame numbers refer to."
        },
        "fps": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Frame rate of that file."
        },
        "sha256": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$",
          "description": "SHA-256 of the video file, so a re-encoded copy can be detected."
        },
        "kickoff_frame": {
          "type": "integer",
          "minimum": 0,
          "description": "Frame at which the period covered by this file kicks off, so match clock and video can be reconciled."
        }
      }
    },
    "point": {
      "type": "object",
      "required": [
        "x",
        "y"
      ],
      "properties": {
        "x": {
          "type": "number",
          "description": "Position along the pitch length, in coordinate_system units."
        },
        "y": {
          "type": "number",
          "description": "Position across the pitch width, in coordinate_system units."
        }
      },
      "additionalProperties": false,
      "description": "A pitch position."
    },
    "event": {
      "type": "object",
      "required": [
        "event_id",
        "timestamp",
        "period",
        "team",
        "player_jersey",
        "event_group",
        "sub_event",
        "outcome",
        "qa"
      ],
      "properties": {
        "event_id": {
          "type": "integer",
          "minimum": 1,
          "description": "Unique within the match, in time order."
        },
        "timestamp": {
          "type": "string",
          "pattern": "^[0-9]{2}:[0-5][0-9]:[0-5][0-9]\\.[0-9]{2}$",
          "description": "Match clock within the period, HH:MM:SS.cc. Restarts at 00:00:00.00 at the start of every period."
        },
        "period": {
          "type": "integer",
          "minimum": 1,
          "description": "1 and 2 for halves, 3 and 4 for extra time, 5 for a penalty shoot-out."
        },
        "team": {
          "type": "string",
          "enum": [
            "home",
            "away"
          ],
          "description": "Team that performed the event."
        },
        "player_jersey": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "maximum": 99,
          "description": "null when no single player performed the event, or when identity could not be confirmed. Never guessed."
        },
        "player_id": {
          "type": [
            "string",
            "null"
          ],
          "description": "Stable ID from the client's roster, when one exists."
        },
        "event_group": {
          "type": "string",
          "description": "Level 2 of the taxonomy, e.g. Passes, Reception, Defensive Action."
        },
        "sub_event": {
          "type": "string",
          "description": "Level 3 of the taxonomy, e.g. Short pass, Interception."
        },
        "outcome": {
          "type": "string",
          "description": "Result of the event, from a controlled list defined in the tagging guidelines.",
          "examples": [
            "complete",
            "incomplete",
            "controlled",
            "won",
            "lost",
            "on_target",
            "off_target",
            "successful",
            "unsuccessful",
            "parried",
            "awarded",
            "retained"
          ]
        },
        "start": {
          "$ref": "#/$defs/point",
          "description": "Where the event starts. For on-ball events, the ball position at contact."
        },
        "end": {
          "$ref": "#/$defs/point",
          "description": "Where the event ends, for events that move the ball (passes, carries, shots)."
        },
        "qualifiers": {
          "type": "object",
          "description": "Level 4 detail. Keys depend on the event group and the package.",
          "additionalProperties": {
            "type": [
              "string",
              "number",
              "boolean",
              "null"
            ]
          }
        },
        "related_event_ids": {
          "type": "array",
          "items": {
            "type": "integer",
            "minimum": 1
          },
          "description": "Links a pass to its reception, a shot to its save, a duel to the opponent's side of it."
        },
        "frame": {
          "type": "integer",
          "minimum": 0,
          "description": "Frame index in match.video.file where the event happens (ball contact for on-ball events)."
        },
        "video_time_s": {
          "type": "number",
          "minimum": 0,
          "description": "Seconds from the start of match.video.file. Redundant with frame/fps; useful as a check."
        },
        "qa": {
          "$ref": "#/$defs/qa",
          "description": "Review status of this event."
        }
      }
    },
    "qa": {
      "type": "object",
      "required": [
        "reviewed",
        "audited"
      ],
      "description": "Where the event stands in the review process. reviewed = checked by a specialist reviewer after the primary annotator. audited = checked again by a senior auditor.",
      "properties": {
        "reviewed": {
          "type": "boolean",
          "description": "Checked by a specialist reviewer after the primary annotator."
        },
        "audited": {
          "type": "boolean",
          "description": "Also checked by a senior auditor."
        },
        "changed_in_review": {
          "type": "boolean",
          "description": "True if a reviewer or auditor corrected the primary annotator's label."
        },
        "flag": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            null,
            "identity_uncertain",
            "location_estimated",
            "off_camera",
            "definition_query"
          ],
          "description": "Why an event needs attention downstream, if it does."
        }
      }
    }
  }
}