{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "mcap.schema.json",
  "title": "MCAP results for one school, all years (data/mcap/<school_code>.json)",
  "description": "JSON twin of /data/assessments.parquet, chunked one file per school plus district.json \u2014 the fact-table layer is otherwise parquet-only, and some LLMs/toolchains can only read JSON. One record per year x assessment x student group. Null/absent measures are privacy-suppressed by MSDE, NEVER zero: `bound` says what is known ('<=5%' / '>=95%' bound the true value to that range \u2014 informative, keep them; 'suppressed' = withheld outright). Keys are omitted rather than set null when a measure is not published. Per-assessment year coverage and what a missing latest year MEANS (not yet published vs discontinued) is in /data/schemas/assessments_coverage.json.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "level",
    "years",
    "n_records",
    "records"
  ],
  "properties": {
    "_about": {
      "type": "string",
      "description": "Human/LLM-readable description of this file's semantics."
    },
    "level": {
      "enum": [
        "school",
        "district"
      ],
      "description": "'district' = countywide MCPS aggregate (district.json)."
    },
    "school_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "3-digit MCPS school code; null on district.json."
    },
    "years": {
      "type": "array",
      "items": {
        "type": "integer"
      },
      "description": "Distinct years present (spring calendar year; no 2020 \u2014 testing was paused)."
    },
    "assessments": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Distinct canonical assessments present."
    },
    "n_records": {
      "type": "integer"
    },
    "records": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/record"
      }
    }
  },
  "$defs": {
    "record": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "year",
        "student_group",
        "source"
      ],
      "properties": {
        "year": {
          "type": "integer"
        },
        "subject": {
          "enum": [
            "ela",
            "math",
            "science",
            "social_studies"
          ]
        },
        "assessment": {
          "type": "string",
          "description": "Canonical assessment: ELA/Math 3-8 & 10 & All, the ELA/Math grade spans (3-5, 3-8, 6-8), Algebra 1/2, Geometry, Science 5/8/HS, Social Studies 8, American Government. MSDE's per-vintage names and the chart API's names are normalized to these; all-grades MCAP-Alternate series are '<Subject> All' with is_alt=true. NOTE: from 2022, MCAP Geometry and Algebra 2 are sat almost entirely by accelerated middle-schoolers in MCPS (2026: 3,071 of 3,091 visible Geometry testers at middle schools; Algebra 2 district n=117) \u2014 neither is a high-school-level measure."
        },
        "grade": {
          "type": "string",
          "description": "Tested grade; 'All' = the cross-grade subject aggregate. Absent for course assessments (Algebra/Geometry/Government), which have no grade."
        },
        "group_dim": {
          "enum": [
            "all",
            "race",
            "sex",
            "service",
            "race_x_service",
            "other"
          ],
          "description": "Which breakdown this record varies. 'race_x_service' is an intersection like 'Asian x FARMS'."
        },
        "student_group": {
          "type": "string",
          "description": "'All Students', a subgroup, or an intersection ('Asian x FARMS'). One canonical vocabulary across sources and years. CAVEATS: Multilingual/English Learner tested counts run higher in Math than ELA at the same grade every year (ESSA newcomer exemption) \u2014 cross-subject cohort ratios are invalid for that group; and MSDE's 2021 MCAP-ALT files misclassify race at source (41% 'Two or more races' vs 4% in 2022) \u2014 do not use 2021 ALT race splits."
        },
        "is_alt": {
          "type": "boolean",
          "description": "Present and true only for MCAP-Alternate (a different assessment for students with the most significant cognitive disabilities) \u2014 never pool with the general test."
        },
        "proficient_pct": {
          "type": "number",
          "description": "% at performance level 3-4. Absent when suppressed \u2014 see `bound`."
        },
        "pl1_pct": {
          "type": "number"
        },
        "pl2_pct": {
          "type": "number"
        },
        "pl3_pct": {
          "type": "number"
        },
        "pl4_pct": {
          "type": "number"
        },
        "pl5_pct": {
          "type": "number",
          "description": "Published by the 5-band vintages: 2019, plus 2021 high-school science. The 2019 science and 2021 HS-science files number their four published bands 2-5 (no Level 1 column exists), so an absent pl1_pct there is structural, not suppression."
        },
        "tested_count": {
          "type": "integer",
          "description": "Bulk-sourced records only \u2014 chart_api records (84% of the table) carry no counts, so volume-weighted computations run on bulk alone. Verbatim from MSDE. Two source facts to respect: course-assessment counts are volatile year-to-year at several high schools (Wheaton Algebra 1: 279 then 1,099 in 2026 \u2014 real administration shifts, present in MSDE's own files), and district tested_count exceeds the sum of published school rows by ~0.1-1.1% every year (testers with no published school row) \u2014 reconcile at one level only. 2019 Algebra 1 counted the larger PARCC-era population (18.7k district testers vs ~13k since); do not pool 2019 into trends."
        },
        "proficient_count": {
          "type": "integer",
          "description": "Bulk-sourced records only."
        },
        "bound": {
          "type": "string",
          "description": "What is known about a suppressed measure \u2014 TOTAL over suppressed records: '<=5%' or '>=95%' bound the true value to that range and are INFORMATIVE, not missing (the district Math 8 proficiency floor, '<=5%' on ~5,000 tested, holds in every published year and exists only on suppressed records); 'suppressed' = withheld by the source; 'suppressed:inferred' = withheld by inference \u2014 the fetcher skipped the cell because an archived marginal proved it suppressed (0 usable lost in 42k measured), so the source was never asked (zero records carry this value until the deferred science crawls run; declared now so it is never a silent result-set change). For ALL withheld records match 'suppressed*' or use absence of the measure. Absent exactly when the measure is published. A record can be suppressed on proficiency while still publishing tested_count \u2014 dropping suppressed records discards those counts and every informative bound. ASCII so it stays queryable; render as <= / >= or prettify to the math symbols."
        },
        "source": {
          "enum": [
            "bulk",
            "chart_api"
          ],
          "description": "bulk = MSDE bulk downloads (carries tested/proficient counts); chart_api = MSDE report-card explorer, the only source of school x subgroup for 2022+ and of race x service intersections."
        },
        "pl_scale": {
          "type": [
            "integer",
            "null"
          ],
          "enum": [
            3,
            4,
            5,
            null
          ],
          "description": "How many performance bands this row's vintage publishes: 3, 4 or 5. Proficient is the TOP TWO bands of that scale: L4+L5 at 5 (2019, plus 2021 HS science), L3+L4 at 4 (MCAP 2022+), L2+L3 at 3 (the 2021 vintage, which merged the bottom two bands) \u2014 verified to 0.1pp (one-decimal rounding) wherever the top two bands are published. A band can be bounded across a whole vintage: 2022 Science 5/8 is 4-band with Level 4 at '<=5%' on every record, so the top-two sum cannot be rebuilt from the pl<n>_pct fields there (proficient_pct \u2014 MSDE's own proficient/tested figure \u2014 remains correct). More generally, any band sitting at or under MSDE's 5% disclosure floor is published as a bound, and a band crossing that floor can look like a 'new band appearing' (district science Level 4 turned numeric in 2026 after four bounded years \u2014 a floor crossing, not a scale change). MSDE's banding does not follow calendar years, so this is derived per (year, dataset). The pl<n>_pct fields are NOT comparable across scales \u2014 prefer proficient_pct."
        }
      }
    }
  }
}