{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://brandhaardjes.nl/data/schema/v1.json",
  "title": "Brandhaardjes events",
  "description": "All visible events of brandhaardjes.nl (/data/events.json). Version 1. Fields may be added within a version; a field that changes meaning or goes away gets a new version. The grades are explained on https://brandhaardjes.nl/methode/.",
  "type": "object",
  "required": ["schema", "version", "events"],
  "additionalProperties": false,
  "properties": {
    "schema": { "const": "https://brandhaardjes.nl/data/schema/v1.json" },
    "version": { "const": 1 },
    "events": { "type": "array", "items": { "$ref": "#/$defs/event" } }
  },
  "$defs": {
    "publicId": {
      "type": "string",
      "pattern": "^BH-[0-9]{4}-[0-9]{6,}$",
      "description": "Stable identifier to cite, from the year the event was first seen and its number"
    },
    "localTime": {
      "type": "string",
      "format": "date-time",
      "description": "RFC 3339 time with the Dutch UTC offset"
    },
    "event": {
      "type": "object",
      "required": ["id", "slug", "title", "summary", "type", "start", "startUtc", "sources", "code", "reliability", "credibility", "independentSources", "disruption", "disruptionBasis"],
      "additionalProperties": false,
      "properties": {
        "id": { "$ref": "#/$defs/publicId" },
        "slug": { "type": "string", "description": "Path of the event page: /events/<slug>/ (Dutch) or /en/events/<slug>/" },
        "title": { "$ref": "#/$defs/translated" },
        "summary": { "$ref": "#/$defs/translated", "description": "Written automatically by a language model from the sources; check the sources" },
        "type": { "enum": ["demonstratie", "optocht", "rellen", "blokkade", "bezetting", "staking", "bezoek", "herdenking", "overig"] },
        "start": { "$ref": "#/$defs/localTime" },
        "startUtc": { "type": "string", "format": "date-time", "description": "The start in UTC" },
        "end": { "$ref": "#/$defs/localTime" },
        "allDay": { "type": "boolean", "description": "Only the date is known; start is midnight" },
        "place": { "type": "string", "description": "Town" },
        "gemeente": { "type": "string", "description": "Municipality (CBS name)" },
        "provincie": { "type": "string" },
        "lat": { "type": "number", "minimum": 50, "maximum": 54 },
        "lon": { "type": "number", "minimum": 3, "maximum": 8 },
        "precision": {
          "enum": ["exact", "adres", "plaats", "gemeente"],
          "description": "How exact lat/lon are: given by the source, a geocoded address or street, the centre of the town, or the centre of the municipality. Missing for events from before this field existed."
        },
        "sources": { "type": "integer", "minimum": 0, "description": "Number of source items" },
        "code": { "type": "string", "pattern": "^[A-F][1-6]$", "description": "Admiralty code: reliability letter and credibility number" },
        "reliability": { "enum": ["A", "B", "C", "D", "E", "F"], "description": "Reliability of the best source: A completely reliable … F cannot be judged" },
        "credibility": { "type": "integer", "minimum": 1, "maximum": 6, "description": "1 confirmed, 2 probably true, 3 possibly true, 4 doubtful, 5 improbable, 6 cannot be judged" },
        "independentSources": { "type": "integer", "minimum": 0, "description": "Sources from different organisations (registrable domains)" },
        "disruption": { "enum": ["laag", "midden", "hoog"], "description": "Public disruption: a base per type, raised by the signals. Never based on the cause of a protest." },
        "disruptionBasis": { "enum": ["verwacht", "gemeld"], "description": "verwacht: only announcements; gemeld: a source reported after the start" },
        "signals": {
          "type": "array",
          "uniqueItems": true,
          "items": { "enum": ["geweld", "vernieling", "arrestaties", "me_inzet", "noodverordening", "wegblokkade", "verkeershinder", "ontruiming", "grote_opkomst", "tegendemonstratie"] },
          "description": "What the sources state about the event"
        },
        "cancelled": { "type": "boolean" },
        "disputed": { "type": "boolean", "description": "Sources contradict each other" },
        "campaign": { "type": "string", "description": "Campaign name or hashtag as written in a source" },
        "series": {
          "type": "object",
          "required": ["id", "count"],
          "additionalProperties": false,
          "properties": {
            "id": { "type": "string", "pattern": "^BH-S-[0-9]{4}-[0-9]{4,}$" },
            "count": { "type": "integer", "minimum": 2 }
          },
          "description": "The same action repeated at the same place on other days"
        },
        "related": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["id", "slug", "kind"],
            "additionalProperties": false,
            "properties": {
              "id": { "$ref": "#/$defs/publicId" },
              "slug": { "type": "string" },
              "kind": { "enum": ["campagne", "tegendemonstratie", "escalatie", "plek"] }
            }
          },
          "description": "Distinct events that look related: same campaign, counter-protest, riots afterwards, or earlier at the same spot. Similar, not necessarily organised by the same people."
        }
      }
    },
    "translated": {
      "type": "object",
      "required": ["nl", "en"],
      "additionalProperties": false,
      "properties": { "nl": { "type": "string" }, "en": { "type": "string" } }
    }
  }
}
