{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.mosticare.org/threat-map/feed-trends.schema.json",
  "title": "Mosticare disease-trend feed",
  "description": "Machine-readable JSON Schema for /threat-map/feed/trends.json — Mosticare's multi-year vector-borne-disease TREND feed for the European Union and EEA. The trend counterpart to the point-in-time incidence feed (feed.schema.json). Published by Mosticare (NGO) under CC BY 4.0, sourced from ECDC. Each series carries the sourced year-by-year totals, a derived summary, and answer-bearing questions. ECDC is the primary source for the complete European total.",
  "type": "object",
  "required": [
    "feed",
    "version",
    "publisher",
    "description",
    "license",
    "license_url",
    "temporal_coverage",
    "variables_measured",
    "measurement_technique",
    "is_based_on",
    "refresh_cadence",
    "last_updated",
    "series_count",
    "series"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "format": "uri",
      "description": "Self-reference to this JSON Schema document. Required by strict-validator AI ingestion paths."
    },
    "feed": {
      "type": "string",
      "const": "mosticare-disease-trends",
      "description": "Canonical feed identifier. Always 'mosticare-disease-trends' for this feed."
    },
    "version": {
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+$",
      "description": "Semantic version of the feed schema (semver). Breaking changes to the envelope/series/point shape bump the major."
    },
    "publisher": {
      "type": "object",
      "required": ["name", "url", "type"],
      "additionalProperties": false,
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "url": { "type": "string", "format": "uri" },
        "type": { "type": "string", "enum": ["NGO", "Government", "Academic", "Commercial"] }
      },
      "description": "Authority publishing the feed."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "description": "Human-readable feed description."
    },
    "license": {
      "type": "string",
      "minLength": 1,
      "description": "SPDX-compatible license identifier (e.g. 'CC BY 4.0')."
    },
    "license_url": {
      "type": "string",
      "format": "uri",
      "description": "Canonical license URL."
    },
    "temporal_coverage": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}/\\d{4}-\\d{2}-\\d{2}$",
      "description": "ISO-8601 time interval (start/end) spanned by all series points."
    },
    "variables_measured": {
      "type": "array",
      "description": "The measurement dimensions the trend series report.",
      "items": {
        "type": "object",
        "required": ["name", "description", "unit"],
        "additionalProperties": false,
        "properties": {
          "name": { "type": "string", "minLength": 1 },
          "description": { "type": "string", "minLength": 1 },
          "unit": { "type": "string", "minLength": 1 }
        }
      }
    },
    "measurement_technique": {
      "type": "string",
      "minLength": 1,
      "description": "Method by which the data is produced (re-publication of upstream annual surveillance on one consistent definition)."
    },
    "is_based_on": {
      "type": "array",
      "description": "Upstream source datasets the trend series are built from (honest attribution).",
      "items": {
        "type": "object",
        "required": ["name", "authority", "url"],
        "additionalProperties": false,
        "properties": {
          "name": { "type": "string", "minLength": 1 },
          "authority": { "type": "string", "minLength": 1 },
          "url": { "type": "string", "format": "uri" }
        }
      }
    },
    "refresh_cadence": {
      "type": "object",
      "required": ["minimum", "target", "next_review"],
      "additionalProperties": false,
      "properties": {
        "minimum": {
          "type": "string",
          "enum": ["hourly", "daily", "weekly", "monthly", "quarterly", "annually"],
          "description": "Worst-case refresh frequency. Consumers can budget polling against this."
        },
        "target": {
          "type": "string",
          "enum": ["hourly", "daily", "weekly", "monthly", "quarterly", "annually"],
          "description": "Aspirational refresh frequency."
        },
        "next_review": {
          "type": "string",
          "format": "date",
          "description": "ISO-8601 date on which the publisher commits to re-review the feed."
        }
      }
    },
    "last_updated": {
      "type": "string",
      "format": "date",
      "description": "ISO-8601 date the feed envelope was last verified or refreshed."
    },
    "series_count": {
      "type": "integer",
      "minimum": 0,
      "description": "Total number of series. MUST equal series.length."
    },
    "series": {
      "type": "array",
      "items": { "$ref": "#/$defs/trendSeries" },
      "description": "Array of per-disease multi-year trend series."
    }
  },
  "$defs": {
    "trendSeries": {
      "type": "object",
      "required": [
        "disease",
        "disease_code",
        "region_scope",
        "metric",
        "unit",
        "temporal_coverage",
        "source_authority",
        "caveat",
        "point_count",
        "points",
        "summary",
        "questions"
      ],
      "additionalProperties": false,
      "properties": {
        "disease": { "type": "string", "minLength": 1, "description": "Disease name (e.g. 'West Nile virus')." },
        "disease_code": { "type": "string", "minLength": 1, "description": "Short canonical disease code (e.g. 'WNV')." },
        "region_scope": { "type": "string", "minLength": 1, "description": "Geographic scope of the case metric (e.g. 'EU/EEA')." },
        "metric": { "type": "string", "minLength": 1, "description": "What the per-year case count measures." },
        "unit": { "type": "string", "minLength": 1, "description": "Unit of the case metric (e.g. 'cases')." },
        "temporal_coverage": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}/\\d{4}-\\d{2}-\\d{2}$",
          "description": "ISO-8601 interval spanned by this series' points."
        },
        "source_authority": { "type": "string", "minLength": 1, "description": "Primary surveillance authority for the series." },
        "caveat": { "type": "string", "minLength": 1, "description": "Standing honesty caveat for the metric (surveillance/case-definition limits, weather-driven volatility)." },
        "point_count": { "type": "integer", "minimum": 0, "description": "Number of points in the series. MUST equal points.length." },
        "points": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/trendPoint" },
          "description": "Year-by-year sourced totals, strictly ascending by year. Dense series (e.g. West Nile virus) have consecutive years; episodic series (e.g. chikungunya clusters) may have gaps."
        },
        "summary": { "$ref": "#/$defs/trendSummary" },
        "questions": {
          "type": "array",
          "items": { "$ref": "#/$defs/trendQuestion" },
          "description": "Generated, answer-bearing trend Q&A derived from the series."
        }
      }
    },
    "trendPoint": {
      "type": "object",
      "required": [
        "year",
        "eu_eea_cases",
        "eu_eea_deaths",
        "all_europe_cases",
        "eu_eea_is_subtotal",
        "source_url",
        "source_authority",
        "last_updated"
      ],
      "additionalProperties": false,
      "properties": {
        "year": { "type": "integer", "minimum": 2000, "description": "Calendar year of the totals." },
        "eu_eea_cases": {
          "type": "integer",
          "minimum": 1,
          "description": "Reported locally-acquired (autochthonous) human cases in EU/EEA Member States for the year, per ECDC. A year is only a point when it has cases (>= 1); years with no reported cases are omitted rather than recorded as 0."
        },
        "eu_eea_deaths": {
          "type": ["integer", "null"],
          "minimum": 0,
          "description": "Deaths among locally-acquired EU/EEA cases where ECDC published the figure; null when not separately reported for the year — do NOT interpret null as zero."
        },
        "all_europe_cases": {
          "type": ["integer", "null"],
          "minimum": 0,
          "description": "ECDC's wider 'Europe' (EU/EEA + EU-neighbouring) locally-acquired total where reported; null otherwise. For 2024–2025 this is ECDC's headline figure of which eu_eea_cases is the Member-State subtotal."
        },
        "eu_eea_is_subtotal": {
          "type": "boolean",
          "description": "true when eu_eea_cases is a computed subtotal of ECDC's per-country breakdown rather than an ECDC-published EU/EEA headline (2024, 2025)."
        },
        "source_url": { "type": "string", "format": "uri", "description": "Authoritative ECDC source URL for the year." },
        "source_authority": { "type": "string", "minLength": 1, "description": "Source authority name for the year (ECDC)." },
        "last_updated": { "type": "string", "format": "date", "description": "ISO-8601 date the year's figure was last verified." },
        "notes": { "type": "string", "description": "Optional per-year provenance / case-definition notes." }
      }
    },
    "trendSummary": {
      "type": "object",
      "required": [
        "first_year",
        "last_year",
        "count",
        "mean_cases",
        "peak",
        "trough",
        "latest",
        "recent_above_mean_years",
        "answer"
      ],
      "additionalProperties": false,
      "properties": {
        "first_year": { "type": "integer", "minimum": 2000 },
        "last_year": { "type": "integer", "minimum": 2000 },
        "count": { "type": "integer", "minimum": 0, "description": "Number of years in the series. MUST equal point_count." },
        "mean_cases": { "type": "integer", "minimum": 0, "description": "Rounded mean of eu_eea_cases across the series." },
        "peak": { "$ref": "#/$defs/yearCases", "description": "Year and case count of the highest season." },
        "trough": { "$ref": "#/$defs/yearCases", "description": "Year and case count of the lowest season." },
        "latest": { "$ref": "#/$defs/yearCases", "description": "Year and case count of the most-recent season." },
        "recent_above_mean_years": {
          "type": "array",
          "items": { "type": "integer", "minimum": 2000 },
          "description": "Most-recent unbroken run of seasons above the series mean (elevated burden)."
        },
        "answer": {
          "type": "string",
          "minLength": 1,
          "description": "Honest, caveated 'is it increasing?' narrative — the same text rendered on the topic page. Reports the shape, names peak/trough/latest, never asserts causation."
        }
      }
    },
    "yearCases": {
      "type": "object",
      "required": ["year", "cases"],
      "additionalProperties": false,
      "properties": {
        "year": { "type": "integer", "minimum": 2000 },
        "cases": { "type": "integer", "minimum": 1 }
      }
    },
    "trendQuestion": {
      "type": "object",
      "required": ["question", "answer"],
      "additionalProperties": false,
      "properties": {
        "question": { "type": "string", "minLength": 1, "description": "Natural-language trend query." },
        "answer": { "type": "string", "minLength": 1, "description": "Answer-first, self-contained, quotable statement." }
      }
    }
  }
}
