{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://hydro-look.vercel.app/api/schema/forecast.schema.json",
  "title": "forecast.json",
  "description": "Mazar's level at 7 to 90 days as p10/p50/p90, days to each threshold under analogue years, and the backtest that scores it.",
  "type": "object",
  "required": [
    "schema_version",
    "generated_at",
    "data_date",
    "run_id",
    "origin_date",
    "site",
    "disclaimer",
    "model",
    "current",
    "thresholds",
    "forecast",
    "days_to_threshold",
    "backtest",
    "crisis_check",
    "license",
    "attribution",
    "see_also"
  ],
  "properties": {
    "schema_version": {
      "type": "integer",
      "const": 1,
      "description": "The format version. Changes only when a field is removed, renamed or retyped; added fields do not change it."
    },
    "generated_at": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$",
      "description": "When the document was written, UTC."
    },
    "data_date": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "The Ecuadorian calendar day the document's readings describe."
    },
    "run_id": {
      "type": "string"
    },
    "origin_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "The day the forecast stands on; equal to data_date."
    },
    "site": {
      "type": "string"
    },
    "variable": {
      "type": "string"
    },
    "units": {
      "type": "string"
    },
    "disclaimer": {
      "type": "string"
    },
    "model": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "version": {
          "type": "string"
        },
        "backtest_origins": {
          "type": "integer"
        }
      },
      "required": [
        "id",
        "label",
        "version",
        "backtest_origins"
      ]
    },
    "current": {
      "type": "object",
      "properties": {
        "level_masl": {
          "type": "number"
        },
        "observed_on": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "storage_above_lowest_threshold_hm3": {
          "type": [
            "number",
            "null"
          ]
        },
        "surface_area_km2": {
          "type": "number"
        },
        "release_stance_m3s": {
          "type": "number"
        }
      },
      "required": [
        "level_masl",
        "observed_on",
        "storage_above_lowest_threshold_hm3",
        "surface_area_km2",
        "release_stance_m3s"
      ]
    },
    "thresholds": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "level_masl": {
            "type": "number"
          },
          "source": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "published",
              "unverified"
            ]
          },
          "note": {
            "type": "string"
          },
          "metres_above": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "level_masl",
          "source",
          "status",
          "note",
          "metres_above"
        ]
      }
    },
    "forecast": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "horizon_days": {
            "type": "integer"
          },
          "target_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "p10": {
            "type": "number"
          },
          "p50": {
            "type": "number"
          },
          "p90": {
            "type": "number"
          },
          "median_backtest_residual_m": {
            "type": [
              "number",
              "null"
            ]
          },
          "ensemble": {
            "type": "object",
            "properties": {
              "p10": {
                "type": "number"
              },
              "p90": {
                "type": "number"
              },
              "n": {
                "type": "integer"
              },
              "years": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "p10",
              "p90",
              "n",
              "years"
            ]
          },
          "model": {
            "type": "string"
          }
        },
        "required": [
          "horizon_days",
          "target_date",
          "p10",
          "p50",
          "p90",
          "ensemble"
        ]
      }
    },
    "days_to_threshold": {
      "type": "object",
      "properties": {
        "horizon_days": {
          "type": "integer"
        },
        "note": {
          "type": "string"
        },
        "thresholds": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "threshold": {
                "type": "string"
              },
              "level_masl": {
                "type": "number"
              },
              "status": {
                "type": "string"
              },
              "across_all_analogue_years": {
                "type": "object",
                "properties": {
                  "analogue_years": {
                    "type": "integer"
                  },
                  "years_that_cross": {
                    "type": "integer"
                  },
                  "p10_days": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "p50_days": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "p90_days": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "analogue_years",
                  "years_that_cross",
                  "p10_days",
                  "p50_days",
                  "p90_days"
                ]
              },
              "scenarios": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "scenario": {
                      "type": "string"
                    },
                    "analogYear": {
                      "type": "integer"
                    },
                    "inflowMeanM3s": {
                      "type": "number"
                    },
                    "crossesOn": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                    },
                    "days": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "minimumLevelMasl": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "scenario",
                    "analogYear",
                    "inflowMeanM3s",
                    "crossesOn",
                    "days",
                    "minimumLevelMasl"
                  ]
                }
              }
            },
            "required": [
              "threshold",
              "level_masl",
              "status",
              "across_all_analogue_years",
              "scenarios"
            ]
          }
        }
      },
      "required": [
        "horizon_days",
        "note",
        "thresholds"
      ]
    },
    "backtest": {
      "type": "object",
      "properties": {
        "report": {
          "type": "string"
        },
        "horizons": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "horizon_days": {
                "type": "integer"
              },
              "n": {
                "type": "integer"
              },
              "mae_m": {
                "type": "number"
              },
              "skill_vs_persistence": {
                "type": "number"
              },
              "coverage_p10_p90": {
                "type": "number"
              }
            },
            "required": [
              "horizon_days",
              "n",
              "mae_m",
              "skill_vs_persistence",
              "coverage_p10_p90"
            ]
          }
        }
      },
      "required": [
        "report",
        "horizons"
      ]
    },
    "crisis_check": {
      "type": "object",
      "properties": {
        "threshold_masl": {
          "type": "number"
        },
        "origins_considered": {
          "type": "integer"
        },
        "false_alarms_p50": {
          "type": "integer"
        },
        "episodes": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "crossed_on": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "p50_lead_time_days": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "p10_lead_time_days": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "crossed_on",
              "p50_lead_time_days",
              "p10_lead_time_days"
            ]
          }
        }
      },
      "required": [
        "threshold_masl",
        "origins_considered",
        "false_alarms_p50",
        "episodes"
      ]
    },
    "precipitation_basin": {
      "type": "object",
      "description": "Which basin's ERA5 precipitation the Mazar run read: the verified catchment centroid once its history is adequate, the provisional point until then. Additive; absent before 2026-09-23.",
      "properties": {
        "basin": {
          "type": "string"
        },
        "verified_centroid": {
          "type": "boolean",
          "description": "True when basin is the verified centroid; false on the provisional fallback."
        },
        "era5_days": {
          "type": "integer",
          "minimum": 0
        },
        "share_since_1990": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Share of days since 1990-01-01 present in the basin's ERA5 series."
        },
        "fallback_reason": {
          "type": [
            "string",
            "null"
          ],
          "description": "Why the verified centroid was not used; null when it was."
        }
      },
      "required": [
        "basin",
        "verified_centroid",
        "era5_days",
        "share_since_1990",
        "fallback_reason"
      ]
    },
    "inflow_forecasts": {
      "type": "object",
      "description": "Mean inflow forecasts for the plants whose level is not worth forecasting (ENHANCEMENTS §5.3). A horizon is published only where the published rung (`backtest.model`: since 2026-09-25 the ensemble, the mean of the analogue years and climatology, plus a river forecast where one is validated) beats persistence and climatology on the backtest; the others carry the reason. Additive; absent before 2026-09-23.",
      "properties": {
        "report": {
          "type": "string"
        },
        "note": {
          "type": "string"
        },
        "plants": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "site": {
                "type": "string"
              },
              "origin_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "variable": {
                "type": "string"
              },
              "target": {
                "type": "string"
              },
              "precip_basin": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The basin whose rain conditions the analogue years; null when its ERA5 history is not adequate."
              },
              "rain_conditioned": {
                "type": "boolean"
              },
              "horizons": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "horizon_days": {
                      "type": "integer"
                    },
                    "published": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string"
                    },
                    "backtest": {
                      "type": "object",
                      "properties": {
                        "n": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "mae_m3s": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "persistence_mae_m3s": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "climatology_mae_m3s": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "coverage_p10_p90": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "n",
                        "mae_m3s",
                        "persistence_mae_m3s",
                        "climatology_mae_m3s",
                        "coverage_p10_p90"
                      ]
                    },
                    "target_date": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                      "description": "Present only on a published horizon."
                    },
                    "p10": {
                      "type": "number",
                      "description": "m³/s; present only on a published horizon."
                    },
                    "p50": {
                      "type": "number",
                      "description": "m³/s; present only on a published horizon."
                    },
                    "p90": {
                      "type": "number",
                      "description": "m³/s; present only on a published horizon."
                    },
                    "ensemble_years": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "rain_conditioned": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "horizon_days",
                    "published",
                    "reason",
                    "backtest"
                  ]
                }
              }
            },
            "required": [
              "site",
              "origin_date",
              "variable",
              "target",
              "precip_basin",
              "rain_conditioned",
              "horizons"
            ]
          }
        }
      },
      "required": [
        "report",
        "note",
        "plants"
      ]
    },
    "scorecard": {
      "$ref": "#/$defs/scorecard"
    },
    "license": {
      "$ref": "#/$defs/license"
    },
    "attribution": {
      "$ref": "#/$defs/attribution"
    },
    "see_also": {
      "$ref": "#/$defs/see_also"
    }
  },
  "$defs": {
    "license": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "const": "MIT"
        },
        "code_url": {
          "type": "string",
          "pattern": "^https?://"
        },
        "data": {
          "type": "string"
        },
        "data_es": {
          "type": "string"
        }
      },
      "required": [
        "code",
        "code_url",
        "data",
        "data_es"
      ]
    },
    "attribution": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "provides": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "pattern": "^https?://"
          },
          "terms": {
            "type": "string"
          },
          "terms_es": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "provides",
          "url",
          "terms",
          "terms_es"
        ]
      }
    },
    "see_also": {
      "type": "object",
      "required": [
        "schema",
        "documentation"
      ],
      "description": "Absolute URLs to the other documents, this document's schema and the documentation page.",
      "additionalProperties": {
        "type": "string",
        "pattern": "^https?://"
      }
    },
    "deprecated": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "description": "Fields kept for one more schema version, each with what replaces it."
    },
    "scorecard": {
      "type": "object",
      "description": "How the published level forecasts (Mazar's level on the target day, m) did once their horizons passed (ENHANCEMENTS §5.1): every published run scored under the model and version that made it, the last run per origin and version only. Additive; absent in documents written before 2026-09-23.",
      "properties": {
        "generated_at": {
          "type": "string",
          "description": "When the scorecard was computed, UTC."
        },
        "observed_through": {
          "type": [
            "string",
            "null"
          ],
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "The last day with an observation to score against."
        },
        "target": {
          "type": "string",
          "description": "The quantity scored, in words."
        },
        "units": {
          "type": "string",
          "enum": [
            "m",
            "GWh/day"
          ]
        },
        "runs_considered": {
          "type": "integer",
          "minimum": 0
        },
        "runs_superseded": {
          "type": "integer",
          "minimum": 0,
          "description": "Runs left out because a later run for the same origin and version replaced them."
        },
        "rows_scored": {
          "type": "integer",
          "minimum": 0
        },
        "rows_pending": {
          "type": "integer",
          "minimum": 0,
          "description": "Published rows whose target date is after observed_through."
        },
        "rows_excluded": {
          "type": "integer",
          "minimum": 0,
          "description": "Rows whose date passed with no usable observation."
        },
        "method": {
          "type": "string"
        },
        "by_horizon": {
          "type": "array",
          "description": "One row per model, model version and horizon with at least one scored row.",
          "items": {
            "type": "object",
            "properties": {
              "model_id": {
                "type": "string"
              },
              "model_version": {
                "type": "string"
              },
              "horizon_days": {
                "type": "integer"
              },
              "n": {
                "type": "integer",
                "minimum": 0
              },
              "mae": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Mean absolute error of the p50, in units."
              },
              "bias": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Mean of observed minus p50, in units."
              },
              "n_band": {
                "type": "integer",
                "minimum": 0,
                "description": "Rows that carried a p10 and a p90."
              },
              "coverage_p10_p90": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Share of banded rows observed inside p10–p90, 0–1."
              },
              "pinball_mean": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Mean pinball loss over p10, p50 and p90."
              },
              "first_origin": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "last_origin": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "model_id",
              "model_version",
              "horizon_days",
              "n",
              "mae",
              "bias",
              "n_band",
              "coverage_p10_p90",
              "pinball_mean",
              "first_origin",
              "last_origin"
            ]
          }
        },
        "recent": {
          "type": "array",
          "description": "The most recent scored rows, newest target date first.",
          "items": {
            "type": "object",
            "properties": {
              "run_id": {
                "type": "string"
              },
              "model_id": {
                "type": "string"
              },
              "model_version": {
                "type": "string"
              },
              "origin_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "horizon_days": {
                "type": "integer"
              },
              "target_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "p10": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "p50": {
                "type": "number"
              },
              "p90": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "observed": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "error": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Observed minus p50."
              },
              "in_band": {
                "type": [
                  "boolean",
                  "null"
                ]
              }
            },
            "required": [
              "run_id",
              "model_id",
              "model_version",
              "origin_date",
              "horizon_days",
              "target_date",
              "p10",
              "p50",
              "p90",
              "observed",
              "error",
              "in_band"
            ]
          }
        }
      },
      "required": [
        "generated_at",
        "observed_through",
        "target",
        "units",
        "runs_considered",
        "runs_superseded",
        "rows_scored",
        "rows_pending",
        "rows_excluded",
        "method",
        "by_horizon",
        "recent"
      ]
    }
  }
}
