# Exam Results Taxonomy — Phase Grouping and Sixth-Form Separation **Date:** 2026-07-07 **Status:** Approved design (taxonomy/analysis only — no implementation in this doc's scope) ## Purpose Classify every exam-result metric SchoolCompare displays today into four phase groups — **Primary**, **Secondary**, **Sixth form**, **Other** — and define an authoritative rule for separating schools that have a sixth form from those that don't. This document is the reference for: 1. How the UI should group results sections and rankings by phase. 2. The future KS5 (A-level) ingestion work — the Sixth form group lists the concrete DfE metrics as placeholders with source columns. 3. Replacing the fragile `age_range contains "18"` heuristic with the GIAS `OfficialSixthForm` flag. ## 1. Grouping principle Metrics are grouped by **the key stage of the assessment**, not by the phase of the school displaying them. An all-through school (4–18) shows metrics in all three exam groups; a pure primary shows only the Primary group. | Group | Assessments | Key stage | Taken at age | Data status | |---|---|---|---|---| | **Primary** | KS2 SATs (reading, writing TA, maths, GPS, science TA) | KS2 | 10–11 (Year 6) | ✅ Live — `marts.fact_ks2_performance` | | **Secondary** | GCSEs, Attainment 8 / Progress 8, EBacc | KS4 | 15–16 (Year 11) | ✅ Live — `marts.fact_ks4_performance` | | **Sixth form** | A levels, applied general, tech levels | KS5 (16–18) | 17–18 (Year 12–13) | ⏳ Not ingested — placeholders in §4 | | **Other** | Non-exam context displayed alongside results | n/a | n/a | ✅ Live — various marts | Not covered (not displayed today, candidates for future "Other"/Primary): EYFS Good Level of Development, Year 1 Phonics check, Year 4 Multiplication Tables Check, KS1 assessments (no longer published at school level by DfE). ## 2. Metric-by-metric mapping (current site) Every key in `backend/schemas.py` `METRIC_DEFINITIONS` — the single source of truth for what the site displays — mapped to its phase group. `category` is the existing schema category; source columns are the DfE names used at ingestion (legacy performance-tables CSV for KS2, EES for KS4). ### Primary (KS2 SATs) | Metric key | Category | DfE source column | |---|---|---| | `rwm_expected_pct` | expected | `PTRWM_EXP` | | `reading_expected_pct` | expected | `PTREAD_EXP` | | `writing_expected_pct` | expected | `PTWRITTA_EXP` | | `maths_expected_pct` | expected | `PTMAT_EXP` | | `gps_expected_pct` | expected | `PTGPS_EXP` | | `science_expected_pct` | expected | `PTSCITA_EXP` | | `rwm_high_pct` | higher | `PTRWM_HIGH` | | `reading_high_pct` | higher | `PTREAD_HIGH` | | `writing_high_pct` | higher | `PTWRITTA_HIGH` | | `maths_high_pct` | higher | `PTMAT_HIGH` | | `gps_high_pct` | higher | `PTGPS_HIGH` | | `reading_progress` | progress | `READPROG` | | `writing_progress` | progress | `WRITPROG` | | `maths_progress` | progress | `MATPROG` | | `reading_avg_score` | average | `READ_AVERAGE` | | `maths_avg_score` | average | `MAT_AVERAGE` | | `gps_avg_score` | average | `GPS_AVERAGE` | | `rwm_expected_boys_pct` | gender | `PTRWM_EXP_B` | | `rwm_expected_girls_pct` | gender | `PTRWM_EXP_G` | | `rwm_high_boys_pct` | gender | `PTRWM_HIGH_B` | | `rwm_high_girls_pct` | gender | `PTRWM_HIGH_G` | | `rwm_expected_disadvantaged_pct` | equity | `PTRWM_EXP_FSM6CLA1A` | | `rwm_expected_non_disadvantaged_pct` | equity | `PTRWM_EXP_NotFSM6CLA1A` | | `disadvantaged_gap` | equity | `DIFFN_RWM_EXP` | | `reading_absence_pct` | absence | `PTREAD_AT` | | `gps_absence_pct` | absence | `PTGPS_AT` | | `maths_absence_pct` | absence | `PTMAT_AT` | | `writing_absence_pct` | absence | `PTWRITTA_AD` | | `science_absence_pct` | absence | `PTSCITA_AD` | | `rwm_expected_3yr_pct` | trends | `PTRWM_EXP_3YR` | | `reading_avg_3yr` | trends | `READ_AVERAGE_3YR` | | `maths_avg_3yr` | trends | `MAT_AVERAGE_3YR` | The absence metrics measure absence *from KS2 tests*, so they belong to Primary even though they are not attainment scores. National comparators for this group come from `marts.fact_ks2_national_averages`. ### Secondary (KS4 / GCSE) | Metric key | Category | EES source column | |---|---|---| | `attainment_8_score` | gcse | `attainment8_average` | | `progress_8_score` | gcse | `progress8_average` | | `english_maths_standard_pass_pct` | gcse | `engmath_94_percent` | | `english_maths_strong_pass_pct` | gcse | `engmath_95_percent` | | `ebacc_entry_pct` | gcse | `ebacc_entering_percent` | | `ebacc_standard_pass_pct` | gcse | `ebacc_94_percent` | | `ebacc_strong_pass_pct` | gcse | `ebacc_95_percent` | | `ebacc_avg_score` | gcse | `ebacc_aps_average` | | `gcse_grade_91_pct` | gcse | `gcse_91_percent` | Also stored in `marts.fact_ks4_performance` (and `fact_performance`) but not yet in `METRIC_DEFINITIONS` — Secondary group members when surfaced: `progress_8_lower_ci`, `progress_8_upper_ci`, `progress_8_english`, `progress_8_maths`, `progress_8_ebacc`, `progress_8_open`, `prior_attainment_avg` (KS2 baseline of the GCSE cohort), `sen_pct`. ### Sixth form (KS5) No metrics today. The secondary school detail view renders a static note ("Post-16 destination data coming soon") when the school has a sixth form. Placeholders for ingestion are specified in §4. ### Other (non-exam context) Displayed alongside results but not tied to any assessment: | Metric key / surface | Category | Source | |---|---|---| | `disadvantaged_pct` | context | KS2 CSV `PTFSM6CLA1A` | | `eal_pct` | context | KS2 CSV `PTEALGRP2` | | `sen_support_pct` | context | KS2 CSV `PSENELK` (KS4 fallback `sen_no_ehcp_pupil_percent`) | | `stability_pct` | context | KS2 CSV `PTMOBN` | | Ofsted grades incl. `sixth_form_provision` / `rc_sixth_form` | — | `marts.fact_ofsted_inspection` | | Admissions (offers, oversubscription) | — | `marts.fact_admissions` | | Finance (per-pupil spend, cost shares) | — | `marts.fact_finance` | | Deprivation (IDACI) | — | `marts.fact_deprivation` | | Pupil characteristics (census) | — | `marts.fact_pupil_characteristics` | Note: the context metrics are cohort characteristics of the KS2 cohort at source, but they are presented (and should stay presented) as school-level context, so they group as Other, not Primary. ## 3. Sixth-form separation ### Definition (authoritative) > A school **has a sixth form** iff GIAS `OfficialSixthForm (name)` = > `"Has a sixth form"` for its URN. GIAS values are `Has a sixth form`, `Does not have a sixth form`, and `Not applicable` / blank. `Not applicable` (nurseries, primaries, PRUs) maps to **false**. This field is the DfE's registry flag, updated continuously, and is the only source that correctly classifies: - 16–19 sixth-form colleges and UTCs (age ranges like `14-19`, `16-19` that the current substring heuristic misclassifies as *no* sixth form); - schools whose statutory age range extends to 18 on paper but which have no registered post-16 provision. ### Pipeline change (future work) 1. `stg_gias_establishments.sql`: add `"OfficialSixthForm (name)" as official_sixth_form`. 2. `dim_school.sql` (+ `models.py` `DimSchool`, `_marts_schema.yml`): add `has_sixth_form boolean` = `official_sixth_form = 'Has a sixth form'`. 3. Expose `has_sixth_form` on the school API payloads. ### Current heuristic — audit of `age_range` ~ "18" sites All must migrate to the `has_sixth_form` flag once exposed: | Site | Current behaviour | |---|---| | `backend/app.py:419-422` | `/api/schools?has_sixth_form=yes\|no` filters on `age_range.str.contains("18")` | | `nextjs-app/components/SecondarySchoolDetailView.tsx:101` | "Sixth form" badge + coming-soon note from `age_range?.includes('18')` | | `nextjs-app/components/FilterBar.tsx:370-372` | Filter labels hard-code "(11-18)" / "(11-16)" — labels should drop the age-range parenthetical since sixth form ≠ age range | Fallback rule: if GIAS is blank for a URN (rare; new establishments), fall back to the age-range heuristic and log the URN. ### UI separation rules - **School page**: schools with `has_sixth_form = true` show a Sixth form results section (placeholder until KS5 data lands); schools without never show it. Badge on the header as today, but driven by the flag. - **Search/rankings filter**: "With sixth form" / "Without sixth form" uses the flag; applies to secondary and all-through phases. - **Comparison**: when comparing a with-sixth-form school against one without, the Sixth form group renders "No sixth form" for the latter rather than blank cells, making the structural difference explicit. ## 4. Sixth form placeholders — future KS5 ingestion spec Source: DfE "A level and other 16 to 18 results" (EES, preferred — matches the KS4 EES tap) or legacy performance-tables `england_ks5final.csv`. Column names below are from the legacy KS5 CSV; verify against the EES release chosen at ingestion time. | Proposed metric key | Name | Legacy source column | Type | |---|---|---|---| | `alevel_aps_per_entry` | A level average points per entry | `TALLPPE_ALEV_1618` | score | | `alevel_avg_grade` | A level average grade (e.g. B-) | `TALLPPEGRD_ALEV_1618` | grade | | `academic_aps_per_entry` | Academic qualifications APS per entry | `TALLPPE_ACAD_1618` | score | | `applied_general_aps_per_entry` | Applied general APS per entry | `TALLPPE_AGEN_1618` | score | | `tech_level_aps_per_entry` | Tech level APS per entry | `TALLPPE_TLEV_1618` | score | | `english_progress_1618` | English progress (16–18, unfinished GCSE 4+) | `PROGENG_1618` | score | | `maths_progress_1618` | Maths progress (16–18) | `PROGMAT_1618` | score | | `ks5_cohort_size` | Students at end of 16–18 study | `TALLPUP_1618` | count | | `alevel_3plus_aab_pct` | % achieving AAB+ in ≥2 facilitating subjects | `TAAB2FAC_1618` | percentage | | `ks5_retention_pct` | Retention (completed main programme) | study-programme retention measure | percentage | | `ks5_destinations_pct` | Sustained education/employment destination | 16–18 destination measures dataset | percentage | Proposed landing shape mirrors KS4: `stg_ees_ks5.sql` → `int_ks5_with_lineage.sql` → `marts.fact_ks5_performance` (one row per URN per year), joined into `fact_performance`, with a `category: "sixth_form"` (or `"alevel"`) block added to `METRIC_DEFINITIONS`. ## 5. Out of scope - Any implementation (pipeline, API, or UI changes) — this is the taxonomy reference; implementation work items are §3 "Pipeline change", the heuristic migration audit, and §4 ingestion, each to be planned separately. - Middle schools (deemed secondary/primary): they follow the assessment-based grouping automatically — no special casing. - Independent schools: no DfE performance data published; unaffected.