{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://polls.karbach.digital/data/schemas/wahl_ltw.schema.json",
  "title": "LTW-Wahl-Snapshot (polls v4)",
  "description": "Schema fuer Landtagswahl-Snapshots. Pflicht: 17 Felder; Optional: ai_analysis + wahlkreise[N] + actual_results + turnout (historische Vergleichswerte). Sprint 1 — 2026-05-19.",
  "type": "object",
  "required": [
    "election",
    "parties",
    "party_results",
    "seat_results",
    "coalitions",
    "coalitions_stability",
    "strongest",
    "source",
    "updated_at",
    "methodology",
    "poll_history",
    "input_polls",
    "quantiles_100",
    "days_until_election",
    "view_mode_hint",
    "party_colors"
  ],
  "properties": {
    "election": {
      "type": "object",
      "required": [
        "id",
        "name",
        "state",
        "date",
        "seats",
        "method",
        "threshold"
      ],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z]{2,3}-\\d{4}$",
          "description": "z.B. bw-2026, sl-2027, hh-2030, rlp-2026"
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "state": {
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        },
        "date": {
          "type": "string",
          "format": "date"
        },
        "seats": {
          "type": "integer",
          "minimum": 30,
          "maximum": 250,
          "description": "Landtagsgroesse, z.B. 80 fuer Bremen, 203 fuer Bayern"
        },
        "method": {
          "type": "string",
          "enum": [
            "sainte_lague",
            "dhondt",
            "hare_niemeyer"
          ]
        },
        "threshold": {
          "type": "number",
          "minimum": 0,
          "maximum": 10
        }
      }
    },
    "parties": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "minItems": 3,
      "uniqueItems": true
    },
    "party_results": {
      "type": "object",
      "additionalProperties": {
        "type": "object",
        "required": [
          "ci95_pct",
          "entry_prob",
          "median_pct",
          "poll_avg",
          "quantiles_100",
          "sigma"
        ],
        "properties": {
          "poll_avg": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "median_pct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "sigma": {
            "type": "number",
            "minimum": 0
          },
          "entry_prob": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "ci95_pct": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2
          },
          "quantiles_100": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 101,
            "maxItems": 101
          }
        }
      }
    },
    "seat_results": {
      "type": "object",
      "additionalProperties": {
        "type": "object",
        "required": [
          "ci95",
          "mean",
          "median"
        ],
        "properties": {
          "median": {
            "type": "integer",
            "minimum": 0
          },
          "mean": {
            "type": "number",
            "minimum": 0
          },
          "ci95": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "minItems": 2,
            "maxItems": 2
          }
        }
      }
    },
    "coalitions": {
      "type": "object",
      "additionalProperties": {
        "type": "number",
        "minimum": 0,
        "maximum": 100
      }
    },
    "coalitions_stability": {
      "type": "object",
      "additionalProperties": {
        "oneOf": [
          {
            "type": "null"
          },
          {
            "type": "object",
            "required": [
              "median_surplus",
              "p10_surplus",
              "p90_surplus",
              "solo_majority_prob"
            ],
            "properties": {
              "median_surplus": {
                "type": "integer"
              },
              "p10_surplus": {
                "type": "integer"
              },
              "p90_surplus": {
                "type": "integer"
              },
              "solo_majority_prob": {
                "type": "number",
                "minimum": 0,
                "maximum": 100
              },
              "member_seats_when_active": {
                "type": "object",
                "description": "S-4.5: pro Koalitions-Mitglied mediane Sitzzahl ueber valid-Iterationen.",
                "additionalProperties": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          }
        ]
      }
    },
    "strongest": {
      "type": "object",
      "additionalProperties": {
        "type": "number",
        "minimum": 0,
        "maximum": 100
      }
    },
    "source": {
      "type": "string",
      "minLength": 1
    },
    "updated_at": {
      "type": "string"
    },
    "methodology": {
      "type": "object",
      "required": [
        "version"
      ],
      "properties": {
        "version": {
          "type": "string"
        }
      }
    },
    "poll_history": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "date",
          "results",
          "institute"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "institute": {
            "type": "string"
          },
          "results": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          }
        }
      }
    },
    "input_polls": {
      "type": "integer",
      "minimum": 0
    },
    "quantiles_100": {
      "type": "object",
      "additionalProperties": {
        "type": "array",
        "items": {
          "type": "number"
        },
        "minItems": 101,
        "maxItems": 101
      }
    },
    "days_until_election": {
      "type": "integer"
    },
    "view_mode_hint": {
      "type": "string",
      "enum": [
        "qualitative",
        "quantitative",
        "early"
      ]
    },
    "party_colors": {
      "type": "object",
      "additionalProperties": {
        "type": "string",
        "pattern": "^#[0-9a-fA-F]{3,8}$"
      }
    },
    "ai_analysis": {
      "type": "object",
      "properties": {
        "text": {
          "type": "string"
        }
      }
    },
    "actual_results": {
      "type": "object",
      "description": "Historisches Ergebnis der letzten LTW dieses Landes — optional, nur fuer Wahlen mit Pre-Election-Vergleich verfuegbar.",
      "additionalProperties": {
        "type": "number",
        "minimum": 0,
        "maximum": 100
      }
    },
    "turnout": {
      "type": "number",
      "minimum": 0,
      "maximum": 100,
      "description": "Historische Wahlbeteiligung der letzten LTW in %"
    },
    "wahlkreise": {
      "type": "array",
      "description": "LTW-Wahlkreise (optional, je nach Bundesland). Saarland: 52 WKs (Legacy-Format mit id+results). Bayern: 91 WKs. Manche LTWs haben keine WK-Direkt-Sim — dann Feld weglassen. Sprint 4 S-4.1 vereinheitlicht das Format auf BTW-Pattern (nr+name+erst+zweit).",
      "items": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "nr": {
            "type": "integer",
            "minimum": 1
          },
          "id": {
            "type": "string",
            "description": "Legacy-WK-ID (Saarland-Pattern, z.B. WK1-41100)"
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "state": {
            "type": "string"
          },
          "erst": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "zweit": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "results": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "Legacy-Pattern: nur ein results-Dict statt erst/zweit (Sl-LTW)"
          },
          "erst_winner": {
            "type": [
              "string",
              "null"
            ]
          },
          "zweit_winner": {
            "type": [
              "string",
              "null"
            ]
          },
          "winner": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      }
    },
    "confidence_score": {
      "oneOf": [
        {
          "type": "object",
          "description": "v4.4 Tier1-d: Per-Wahl-Konfidenz-Score (1-10, eine Nachkommastelle)",
          "properties": {
            "value": {
              "type": "number",
              "minimum": 1.0,
              "maximum": 10.0
            },
            "scale": {
              "type": "string"
            },
            "label": {
              "type": "string"
            },
            "components": {
              "type": "object",
              "additionalProperties": {
                "type": "number",
                "minimum": 0.0,
                "maximum": 1.0
              }
            },
            "inputs": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "additionalProperties": false
        },
        {
          "type": "null"
        }
      ],
      "description": "v4.4: Per-Wahl-Konfidenz-Score (null bei vergangenen Wahlen)."
    }
  },
  "additionalProperties": true
}
