{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schema.govfresh.com/v1/elections/contest.schema.json",
  "title": "Contest",
  "description": "One race or question on the ballot: an office to fill, or a measure to decide. The unit at which results are actually reported. electoralSystem is required because a result cannot be interpreted without it - 40% of the vote wins outright under first-past-the-post, triggers a runoff under a two-round system, and means something different again under a party list. A profile that assumed one system would not travel outside the country it was written in.",
  "type": "object",
  "properties": {
    "@context": {},
    "@id": {
      "type": "string",
      "format": "iri",
      "description": "This contest's own dereferenceable identifier."
    },
    "@type": {
      "const": "Contest"
    },
    "name": {
      "type": "string",
      "description": "The contest's title as it appears on the ballot, e.g. 'Mayor' or 'Measure A'."
    },
    "description": {
      "type": "string",
      "description": "A plain-language summary of what's being decided, when the name alone isn't enough."
    },
    "identifier": {
      "type": "array",
      "description": "Any scheme-qualified identifiers for this contest beyond its `@id`.",
      "items": {
        "$ref": "../_core/identifier.schema.json"
      }
    },
    "election": {
      "$ref": "../_core/reference.schema.json",
      "description": "The `Election` this contest is part of."
    },
    "contestType": {
      "type": "string",
      "description": "termCode from the `ContestType` code list.",
      "enum": [
        "office",
        "ballotMeasure"
      ]
    },
    "electoralSystem": {
      "type": "string",
      "description": "How votes translate into outcomes. termCode from the ElectoralSystem code list.",
      "enum": [
        "fptp",
        "blockVote",
        "twoRound",
        "partyList",
        "stv",
        "mmp",
        "parallel",
        "rankedChoice",
        "approval",
        "indirect"
      ]
    },
    "post": {
      "$ref": "../_core/reference.schema.json",
      "description": "The Post being filled, from the org profile. This is the join that closes the loop: an election fills an office, and the winner's Role in that office begins."
    },
    "representsJurisdiction": {
      "$ref": "../_core/reference.schema.json",
      "description": "The district or ward the contest covers, where it is not the whole area."
    },
    "seatsToFill": {
      "type": "integer",
      "minimum": 1,
      "description": "More than one under block vote, party list and STV."
    },
    "candidacy": {
      "type": "array",
      "description": "The candidacies standing in this contest, where it fills an office rather than deciding a measure.",
      "items": {
        "$ref": "candidacy.schema.json"
      }
    },
    "ballotOption": {
      "type": "array",
      "description": "For a ballot measure: the options put to voters. Kept separate from candidacy because a proposition is not a person.",
      "items": {
        "type": "object",
        "properties": {
          "@type": {
            "const": "BallotOption"
          },
          "name": {
            "type": "string"
          },
          "voteCount": {
            "type": "integer",
            "minimum": 0
          },
          "prevailed": {
            "type": "boolean"
          }
        },
        "required": [
          "@type",
          "name"
        ],
        "additionalProperties": false
      }
    },
    "validVotes": {
      "type": "integer",
      "minimum": 0,
      "description": "Ballots counted toward the result. With `invalidVotes`, sums to `totalVotes`."
    },
    "invalidVotes": {
      "type": "integer",
      "minimum": 0,
      "description": "Spoiled and blank ballots. Reported separately because they are a real signal, and folding them into the total makes every share slightly wrong."
    },
    "totalVotes": {
      "type": "integer",
      "minimum": 0,
      "description": "All ballots cast in this contest, valid and invalid."
    },
    "sourceDataset": {
      "type": "string",
      "format": "iri",
      "description": "The `Dataset` this record was published as part of."
    },
    "registeredVoters": {
      "type": "integer",
      "minimum": 0,
      "description": "The electorate for this contest. Held at contest level as well as election level because the register is drawn per constituency, and turnout is only meaningful against the electorate that could actually vote in that race."
    }
  },
  "required": [
    "@id",
    "@type",
    "name",
    "election",
    "contestType",
    "electoralSystem"
  ],
  "additionalProperties": true
}
