docs: exam results phase taxonomy and sixth-form separation spec
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user