Files
school_compare/docs/superpowers/specs/2026-07-07-exam-phase-taxonomy-design.md
2026-07-07 10:43:08 +01:00

11 KiB
Raw Permalink Blame History

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 (418) 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 1011 (Year 6) Live — marts.fact_ks2_performance
Secondary GCSEs, Attainment 8 / Progress 8, EBacc KS4 1516 (Year 11) Live — marts.fact_ks4_performance
Sixth form A levels, applied general, tech levels KS5 (1618) 1718 (Year 1213) 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:

  • 1619 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 (implemented 2026-07-07)

  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.

Implemented in feat/gias-sixth-form-flag — see docs/superpowers/plans/2026-07-07-gias-sixth-form-flag.md.

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 (1618, unfinished GCSE 4+) PROGENG_1618 score
maths_progress_1618 Maths progress (1618) PROGMAT_1618 score
ks5_cohort_size Students at end of 1618 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 1618 destination measures dataset percentage

Proposed landing shape mirrors KS4: stg_ees_ks5.sqlint_ks5_with_lineage.sqlmarts.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.