{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "dc_grades.json — /dc-scoreboard escalation grading harness",
  "type": "object",
  "required": ["published_at", "as_of", "legs", "anchors", "scenarios",
               "paired_legs_note", "revision_disclosure_pp", "leadlag",
               "power_nowcast"],
  "properties": {
    "published_at": {"type": "string"},
    "as_of": {"type": ["string", "null"]},
    "paired_legs_note": {
      "type": "string",
      "minLength": 1,
      "description": "The artifact's own statement of the paired-legs rule. Required and non-empty: the TS type treats it as always present and /dc-scoreboard renders it as the section lede, so an artifact omitting it would silently drop the rule's central explanation with nothing in CI to catch it."
    },
    "revision_disclosure_pp": {
      "type": ["number", "null"],
      "description": "Max |vintage-true − final-revision| carried rate across every basis at every anchor month both legs share — DERIVED from this artifact's own anchors on each publish, so it cannot contradict them (its hand-measured 0.27 predecessor did, 2.5x over). null when the legs share no comparable anchor."
    },
    "legs": {
      "type": "object",
      "description": "Keyed by leg name (\"strict\"/\"extended\" in the normal case; {} when degraded). Leg objects legally carry DIFFERENT horizon key sets under `grades` -- the strict leg publishes only h12/h24 (spec 2.1a) -- so no leg schema below requires a fixed set of horizon keys.",
      "additionalProperties": {
        "type": "object",
        "required": ["provenance", "span", "anchors_n", "contains_downturn",
                     "published_horizons", "grades"],
        "properties": {
          "provenance": {"type": "string"},
          "span": {"type": "array", "items": {"type": ["string", "null"]}},
          "anchors_n": {"type": "integer"},
          "contains_downturn": {"type": "boolean"},
          "published_horizons": {
            "type": "array",
            "items": {"type": "integer"},
            "description": "Which horizons THIS leg actually grades -- read this, never assume all four (spec: 'the strict leg publishes only h=12 and h=24')."
          },
          "grades": {
            "type": "object",
            "description": "basis_key -> horizon_key -> stats|null. No horizon_key is required to be present for every basis/leg -- a horizon absent from `published_horizons` is simply not a key here.",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": ["object", "null"],
                "required": ["n", "independent_draws", "shortfall_rate_pct",
                             "mean_shortfall_pp", "worst_shortfall_pp",
                             "bias_pp", "mae_pp"],
                "properties": {
                  "n": {"type": "integer"},
                  "independent_draws": {"type": "number"},
                  "shortfall_rate_pct": {"type": "number"},
                  "mean_shortfall_pp": {"type": "number"},
                  "worst_shortfall_pp": {"type": "number"},
                  "bias_pp": {"type": "number"},
                  "mae_pp": {"type": "number"}
                }
              }
            }
          }
        }
      }
    },
    "anchors": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["m", "leg", "bases", "realized"],
        "properties": {
          "m": {"type": "string"},
          "leg": {"type": "string"},
          "bases": {"type": "object",
                    "additionalProperties": {"type": ["number", "null"]}},
          "realized": {"type": "object",
                       "additionalProperties": {"type": ["number", "null"]}}
        }
      }
    },
    "scenarios": {
      "type": "array",
      "description": "Hindsight-selected regimes (GFC, COVID peak): rates and windows ONLY, never a grading statistic (spec 5.3) -- enforced below by validation, not convention.",
      "items": {
        "type": "object",
        "required": ["key", "label", "start_month", "end_month",
                     "annualized_pct", "hindsight_selected", "note"],
        "not": {"anyOf": [
          {"required": ["shortfall_rate_pct"]}, {"required": ["mae_pp"]},
          {"required": ["bias_pp"]}, {"required": ["n"]},
          {"required": ["independent_draws"]},
          {"required": ["mean_shortfall_pp"]},
          {"required": ["worst_shortfall_pp"]}
        ]},
        "properties": {
          "key": {"type": "string"},
          "label": {"type": "string"},
          "start_month": {"type": "string"},
          "end_month": {"type": "string"},
          "annualized_pct": {"type": ["number", "null"]},
          "hindsight_selected": {"const": true},
          "note": {"type": "string"}
        }
      }
    },
    "leadlag": {
      "type": ["object", "null"],
      "description": "P3c's lead-lag study, or null on a fully degraded run. A positive gate result (weight_stable > 0) must NEVER be rendered without `caveats` and `conclusion` alongside it -- spec 6.1 requires both to travel with the gate's literal outcome everywhere it appears, structurally, not as prose a reader can skip.",
      "required": ["mappings", "weight_covered", "weight_stable", "verdict",
                   "gate", "caveats", "conclusion"],
      "if": {
        "required": ["weight_stable"],
        "properties": {"weight_stable": {"exclusiveMinimum": 0}}
      },
      "then": {
        "properties": {
          "caveats": {
            "minItems": 1,
            "description": "Spec 6.1 enforced STRUCTURALLY, not just at the Python layer: a positive gate result (weight_stable > 0) with no caveats is exactly the misleading payload the rule exists to prevent, and a schema-conforming third producer must not be able to emit one."
          }
        }
      },
      "properties": {
        "weight_covered": {"type": "number"},
        "weight_stable": {"type": "number"},
        "verdict": {"type": "string"},
        "gate": {"type": "string"},
        "conclusion": {
          "type": "string",
          "minLength": 1,
          "description": "The standing finding -- independent of the literal gate outcome -- that no forward model is warranted on this evidence (spec 6.1). Non-empty by construction: an empty string is a missing conclusion that happens to satisfy a bare type check."
        },
        "caveats": {
          "type": "array",
          "description": "Structured caveats a consumer can render without parsing English. Empty only when no mapping cleared the gate (nothing positive to caveat).",
          "items": {
            "type": "object",
            "required": ["key", "text"],
            "properties": {
              "key": {"type": "string"},
              "text": {"type": "string"}
            }
          }
        },
        "mappings": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["driver", "driver_label", "component",
                         "component_label", "weight", "months", "span",
                         "best_lag_months", "best_correlation", "stable",
                         "first_half", "second_half", "profile"],
            "properties": {
              "driver": {"type": "string"},
              "driver_label": {"type": "string"},
              "component": {"type": "string"},
              "component_label": {"type": "string"},
              "weight": {"type": "number"},
              "months": {"type": "integer"},
              "span": {"type": "array",
                       "items": {"type": ["string", "null"]}},
              "best_lag_months": {"type": ["integer", "null"]},
              "best_correlation": {"type": ["number", "null"]},
              "stable": {"type": "boolean"},
              "first_half": {
                "type": "object",
                "required": ["best_lag_months", "best_correlation"],
                "properties": {
                  "best_lag_months": {"type": ["integer", "null"]},
                  "best_correlation": {"type": ["number", "null"]}
                }
              },
              "second_half": {
                "type": "object",
                "required": ["best_lag_months", "best_correlation"],
                "properties": {
                  "best_lag_months": {"type": ["integer", "null"]},
                  "best_correlation": {"type": ["number", "null"]}
                }
              },
              "profile": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["lag", "corr"],
                  "properties": {
                    "lag": {"type": "integer"},
                    "corr": {"type": ["number", "null"]}
                  }
                }
              }
            }
          }
        }
      }
    },
    "power_nowcast": {
      "type": ["object", "null"],
      "required": ["as_of", "months_graded", "months_dropped",
                   "dropped_months", "carry_forward_mae",
                   "best_lambda", "best_mae", "verdict", "note"],
      "properties": {
        "as_of": {"type": ["string", "null"]},
        "months_graded": {"type": "integer"},
        "months_dropped": {"type": "integer"},
        "dropped_months": {
          "type": "array",
          "items": {"type": "string"},
          "description": "Audit trail for grade_all's common-intersection reduction: months graded by at least one lambda but excluded from every MAE above (the sign guard makes per-lambda gradeable sets differ). [] when nothing was dropped. A bare count would say something was removed without saying what."
        },
        "carry_forward_mae": {"type": ["number", "null"]},
        "best_lambda": {"type": ["number", "null"]},
        "best_mae": {"type": ["number", "null"]},
        "verdict": {"enum": ["PASS", "FAIL", "INSUFFICIENT"]},
        "note": {"type": "string"}
      }
    },
    "storage_nowcast": {
      "description": "NAND spot -> storage PPI year-ratio tail gate (engine/proxygate.py, added 2026-09-28). Unlike power_nowcast this verdict is load-bearing: the DC Hardware storage component rides the tail only on PASS, and (since 2026-10-02) only while NAND is in its moving regime (tail_active).",
      "type": ["object", "null"],
      "required": ["official", "proxy", "as_of", "proxy_history_days", "months_graded",
                   "min_months", "months_dropped", "dropped_months", "carry_forward_mae",
                   "zero_lambda_mae", "best_lambda", "best_mae", "lambda_ols", "verdict",
                   "tail_active", "note", "carry_forward_max", "best_max",
                   "regime_min_move", "proxy_yoy_pct", "regime_active"],
      "properties": {
        "official": {"type": "string"},
        "proxy": {"type": "array", "items": {"type": "string"}},
        "transform": {"const": "year_ratio"},
        "smooth_days": {"type": "integer"},
        "as_of": {"type": ["string", "null"]},
        "proxy_history_days": {"type": "integer"},
        "months_graded": {"type": "integer"},
        "min_months": {"type": "integer"},
        "months_dropped": {"type": "integer"},
        "dropped_months": {"type": "array", "items": {"type": "string"}},
        "carry_forward_mae": {"type": ["number", "null"]},
        "carry_forward_max": {"type": ["number", "null"], "description": "worst |error| of carry-forward on the graded set (YoY pts) — the bound the best λ's worst month must not exceed"},
        "zero_lambda_mae": {"type": ["number", "null"]},
        "best_lambda": {"type": ["number", "null"]},
        "best_mae": {"type": ["number", "null"]},
        "best_max": {"type": ["number", "null"]},
        "lambda_ols": {"type": ["number", "null"]},
        "regime_min_move": {"type": "number", "description": "the switched rule rides the tail only while |NAND(t)/NAND(t-365d) - 1| exceeds this (added 2026-10-02)"},
        "proxy_yoy_pct": {"type": ["number", "null"], "description": "NAND spot vs a year earlier at the latest spot date, %"},
        "regime_active": {"type": "boolean"},
        "verdict": {"enum": ["PASS", "FAIL", "INSUFFICIENT"]},
        "tail_active": {"type": "boolean"},
        "note": {"type": "string"}
      }
    }
  }
}
