{
  "$ref": "#/definitions/Polls",
  "definitions": {
    "Polls": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "description": "Unique stable identifier, e.g. \"kantar-cz-2025-02-01\"."
          },
          "region": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1
                }
              }
            ],
            "description": "Region(s) this poll covers. ISO 3166-1 alpha-2 (country) or ISO 3166-2 (sub-national) codes."
          },
          "type": {
            "type": "string",
            "description": "Type of election being measured. Canonical values: \"parliamentary\", \"presidential\", \"municipal\", \"regional\", \"european\", \"referendum\"."
          },
          "pollster": {
            "type": "string",
            "description": "ID or name of the polling agency. Should reference Pollster.id when a pollsters.json reference file is maintained."
          },
          "sponsors": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1
                }
              }
            ],
            "description": "Organisation(s) that commissioned the poll. String for single sponsor; array for multiple."
          },
          "url": {
            "anyOf": [
              {
                "type": "string",
                "format": "uri"
              },
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uri"
                }
              }
            ],
            "description": "URL(s) of the published source(s) for these results."
          },
          "published_at": {
            "type": "string",
            "description": "Date (or datetime) results were publicly released (ISO 8601). Use the earliest known publication date."
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "description": "First day of fieldwork (ISO 8601 date)."
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "description": "Last day of fieldwork (ISO 8601 date)."
          },
          "central_date": {
            "type": "string",
            "description": "Representative date for time-series placement (ISO 8601). Typically the fieldwork midpoint."
          },
          "sample_size": {
            "anyOf": [
              {
                "type": "integer",
                "exclusiveMinimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "Number of respondents interviewed. Null if not published."
          },
          "population": {
            "type": "string",
            "description": "Population the poll is intended to represent. Example: \"18+\", \"registered voters\", \"likely voters\""
          },
          "method": {
            "type": "string",
            "description": "How the poll was conducted. Example: \"100% CATI\", \"CAWI\", \"CAPI/CAWI mixed\""
          },
          "margin_of_error": {
            "type": "string",
            "description": "Overall margin of error. Free string. Examples: \"±3%\", \"2.8 pp\""
          },
          "outputs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "output_type": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Type of output. Canonical values: \"core\", \"potential\", \"model\", \"participation\". Other values allowed.\n\nNote: \"model\" here means the polling agency's own weighted/adjusted figures — not anything produced by the aggregation pipeline. Poll-of-polls aggregates and Monte Carlo outputs belong in EstimateSnapshot."
                },
                "tags": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Descriptive tags for filtering and grouping. Examples: [\"parties\", \"main\"], [\"candidates\", \"first-round\"]"
                },
                "sub_population": {
                  "type": "string",
                  "description": "Sub-population this output covers, if narrower than the poll-level population. Example: \"Definite voters\""
                },
                "margin_of_error": {
                  "type": "string",
                  "description": "Margin of error for this output. Free string — agencies report in different formats. Examples: \"±3%\", \"2.8 pp\""
                },
                "lower_cut_percent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Choices below this percent threshold are not individually reported by the agency. Example: 2.0 means parties under 2% are suppressed."
                },
                "under_lower_cut": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Named choices that were included in fieldwork but fell below lower_cut_percent and are not listed in results."
                },
                "results": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "choice_id": {
                        "type": "string",
                        "minLength": 1,
                        "description": "ID of the choice (party, coalition) or candidate. References Choice.id or Candidate.id."
                      },
                      "value_percent": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Stated voting intention as a percentage of valid responses (0–100). Raw reported figure — not adjusted or modelled by the aggregation pipeline."
                      }
                    },
                    "required": [
                      "choice_id",
                      "value_percent"
                    ],
                    "additionalProperties": false,
                    "description": "A single choice's result within one poll output.\n\nLayer: source / ingestion (raw agency-reported data)\n\nThese are values exactly as the polling agency published them. A missing entry means the choice was not reported (possibly suppressed by lower_cut_percent). Derived/normalised values produced by the compute layer are stored in DerivedResult instead."
                  },
                  "description": "Per-choice voting intention figures for this output. May be empty when the output records only aggregate figures (e.g. a participation output with only undecided_percent)."
                },
                "others_percent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Aggregate percentage for parties/candidates not individually listed in results (e.g. combined \"other parties\" figure reported by the agency)."
                },
                "undecided_percent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Percentage of respondents who are undecided or answered \"don't know\"."
                },
                "abstention_percent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Percentage of respondents who plan to abstain / will not vote."
                },
                "blank_percent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Percentage of respondents who plan to cast a blank or null ballot."
                },
                "extras": {
                  "type": "object",
                  "additionalProperties": {},
                  "description": "Extension point for additional fields."
                }
              },
              "required": [
                "output_type",
                "results"
              ],
              "additionalProperties": false,
              "description": "One \"view\" of results within a poll.\n\nLayer: source / ingestion (raw agency-reported data)\n\nA single poll may publish multiple outputs, e.g.: \"core\" (definite voters), \"potential\" (might vote), \"model\" (agency's own weighted figures), \"participation\" (turnout figures).\n\nIMPORTANT — scope boundary: PollOutput represents data exactly as the polling agency publishes it. output_type=\"model\" means the agency's own internal model, not the aggregation pipeline's model. Poll-of-polls aggregates, Monte Carlo simulations, and other computed estimates are stored in EstimateSnapshot — a completely separate schema that is never nested inside Poll."
            },
            "minItems": 1,
            "description": "Poll outputs — one or more views of the results. At minimum one output with type \"core\" or \"model\" is expected. output_type=\"model\" refers to the agency's own model, not the aggregation pipeline's model (see EstimateSnapshot for that)."
          },
          "extras": {
            "type": "object",
            "additionalProperties": {},
            "description": "Extension point for additional fields."
          }
        },
        "required": [
          "id",
          "region",
          "outputs"
        ],
        "additionalProperties": false,
        "description": "A single published opinion poll.\n\nLayer: source / ingestion (raw agency-reported data)\n\nContains data exactly as reported by the polling agency — nothing here is computed or adjusted by the aggregation pipeline.\n\nData flow: Poll (source) → ScenarioSnapshot (compute) → EstimateSnapshot (aggregate).\n\nNote: output_type=\"model\" in a PollOutput means the agency's own weighted/adjusted figures. The aggregation pipeline's model outputs belong in EstimateSnapshot."
      },
      "description": "Array of Poll objects."
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}