fix: don’t portray special schools as failing the mainstream benchmark #70

Merged
tudor merged 3 commits from fix/special-schools-not-failing into main 2026-07-20 21:14:57 +00:00
Owner

The problem

Special schools, PRUs and alternative provision teach pupils with SEND who sit the same KS2/KS4 assessments, but very few reach the mainstream "expected standard". Their headline attainment is therefore ~0% (or a very low Attainment 8), and the site compared that to the England average and painted it red.

Example — Greenmead School (a community special school): rendered as "0.0% — −62 pts below England average" with three 0% red SATs bars, portraying it as catastrophically failing against a benchmark that doesn't apply to it. The 0.0 values are real and consistent across every year (with genuine negative progress scores), so this is a framing problem, not a data glitch.

The fix

A shared isSpecialSchool() helper — detects every DfE special-school establishment type (all contain "special": Community special school, Academy special converter/sponsor led, Foundation special school, Non-maintained, Free schools special, Other independent special school) plus PRUs / alternative provision — used to drop the mainstream England comparison and "below" framing everywhere:

  • Detail (primary + secondary): a plain-English context note ("This is a special school…"); England-average delta chips, "England avg" hints, SATs national markers, the Attainment-8 "vs national" bar, and the trend chart's England overlay are all suppressed. An all-zero placeholder SATs row also hides the empty subject bar chart and the "why is combined lower" bridge.
  • Rankings / search rows (primary + secondary): the mainstream RWM / Attainment 8 stat shows "—" with no "vs national" delta, instead of "0% · −62 vs national".
  • Compare: special schools' attainment values are dropped (no misleading 0% dot / no "Below England average" chip); progress banding — a fair measure for special schools — is kept.

Belt-and-braces zero-guard: a whole-row zero attainment (special or a suppressed cohort) is treated as not-comparable, while a legitimate single 0 (e.g. 0% exceeding at a mainstream school) stays comparable.

Scope note

This changes only how special schools are framed; mainstream schools are untouched (the gates are !suppress…, no-ops when false). Rankings still include special schools but no longer show them a red mainstream delta — excluding them from mainstream-metric ordering could be a follow-up.

Tests

  • New isSpecialSchool unit tests (every DfE special type matched; no mainstream false positives).
  • New e2e journey: Greenmead shows the special-school note and no England-average comparison (validates post-deploy).
  • tsc --noEmit clean; npm test 108/108.

🤖 Generated with Claude Code

### The problem Special schools, PRUs and alternative provision teach pupils with SEND who sit the same KS2/KS4 assessments, but very few reach the mainstream "expected standard". Their headline attainment is therefore ~0% (or a very low Attainment 8), and the site compared that to the England average and painted it red. **Example — Greenmead School (a community special school):** rendered as **"0.0% — −62 pts below England average"** with three 0% red SATs bars, portraying it as catastrophically failing against a benchmark that doesn't apply to it. The `0.0` values are real and consistent across every year (with genuine negative progress scores), so this is a framing problem, not a data glitch. ### The fix A shared **`isSpecialSchool()`** helper — detects every DfE special-school establishment type (all contain "special": Community special school, Academy special converter/sponsor led, Foundation special school, Non-maintained, Free schools special, Other independent special school) plus PRUs / alternative provision — used to drop the mainstream England comparison and "below" framing everywhere: - **Detail (primary + secondary):** a plain-English context note ("This is a special school…"); England-average delta chips, "England avg" hints, SATs national markers, the Attainment-8 "vs national" bar, and the trend chart's England overlay are all suppressed. An all-zero placeholder SATs row also hides the empty subject bar chart and the "why is combined lower" bridge. - **Rankings / search rows (primary + secondary):** the mainstream RWM / Attainment 8 stat shows "—" with no "vs national" delta, instead of "0% · −62 vs national". - **Compare:** special schools' attainment values are dropped (no misleading 0% dot / no "Below England average" chip); **progress banding — a fair measure for special schools — is kept.** **Belt-and-braces zero-guard:** a whole-row zero attainment (special or a suppressed cohort) is treated as not-comparable, while a legitimate single 0 (e.g. 0% exceeding at a mainstream school) stays comparable. ### Scope note This changes only how special schools are *framed*; mainstream schools are untouched (the gates are `!suppress…`, no-ops when false). Rankings still *include* special schools but no longer show them a red mainstream delta — excluding them from mainstream-metric ordering could be a follow-up. ### Tests - New `isSpecialSchool` unit tests (every DfE special type matched; no mainstream false positives). - New e2e journey: Greenmead shows the special-school note and **no** England-average comparison (validates post-deploy). - `tsc --noEmit` clean; `npm test` 108/108. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tudor added 1 commit 2026-07-20 19:39:38 +00:00
fix: don't portray special schools as failing the mainstream benchmark
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m8s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 46s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m1s
ae6ef6860b
Special schools, PRUs and alternative provision teach pupils with SEND who
sit the same KS2/KS4 assessments but very few reach the mainstream "expected
standard". Their headline attainment is therefore ~0% (or a very low
Attainment 8), and the site was comparing that to the England average and
painting it red — e.g. Greenmead School (a community special school) rendered
as "0.0% — −62 pts below England average" with three 0% red SATs bars. That
portrays a special school as catastrophically failing against a benchmark
that doesn't fit it.

Add a shared `isSpecialSchool()` helper (detects every DfE special-school
establishment type — all contain "special" — plus PRUs / alternative
provision) and drop the mainstream England comparison + "below" framing for
these schools across every surface:

- Detail (primary + secondary): a plain-English context note explaining the
  school is special and why the comparison isn't shown; England-average delta
  chips, "England avg" hints, the SATs national markers, the Attainment-8
  "vs national" bar and the trend chart's England overlay are all suppressed.
  An all-zero placeholder SATs row hides the (empty) subject bar chart and the
  "why is combined lower" bridge.
- Rankings / search rows (primary + secondary): the mainstream RWM / Attainment
  8 stat shows "—" with no "vs national" delta, instead of "0% · −62 vs
  national".
- Compare: special schools' attainment values are dropped (no misleading dot
  at 0% / no "Below England average" chip); progress banding, which IS a fair
  measure for special schools, is kept.

Belt-and-braces zero-guard: a whole-row zero attainment (special or a
suppressed cohort) is also treated as not-comparable, while a legitimate
single 0 (e.g. 0% exceeding at a mainstream school) stays comparable.

Tests: new isSpecialSchool unit tests (every DfE special type matched, no
mainstream false positives); an e2e journey asserts Greenmead shows the
special-school note and no England-average comparison. tsc clean; 108/108 unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB

🤖 AI Code Review (Claude Code)

This PR adds special-school/PRU/AP-aware handling across school detail pages, list rows, and the compare view so that mainstream England-average benchmarks and 'below average' framing are suppressed for schools where they don't apply, backed by new unit and e2e tests. The core isSpecialSchool detection and the detail-page suppression logic are well-scoped and consistent, but the list-row components introduce a data-fidelity regression.

🟡 Minor

  • nextjs-app/components/SchoolRow.tsx: rwmComparable treats any rwm_expected_pct === 0 as non-comparable regardless of isSpecialSchool, so a genuine 0% result at a mainstream school renders as '—' (looks like missing data) instead of the real figure, and drops the trend arrow. This is stricter/inconsistent with SchoolDetailView's placeholder check, which requires all of RWM, reading, writing, and maths to be zero before suppressing.
  • nextjs-app/components/SecondarySchoolRow.tsx: Same pattern: att8Comparable hides Attainment 8 whenever the value is exactly 0.0, even for non-special schools, mis-displaying a real (if extreme) score as '—' and dropping the LA-average delta.
## 🤖 AI Code Review (Claude Code) This PR adds special-school/PRU/AP-aware handling across school detail pages, list rows, and the compare view so that mainstream England-average benchmarks and 'below average' framing are suppressed for schools where they don't apply, backed by new unit and e2e tests. The core `isSpecialSchool` detection and the detail-page suppression logic are well-scoped and consistent, but the list-row components introduce a data-fidelity regression. ### 🟡 Minor - **nextjs-app/components/SchoolRow.tsx**: `rwmComparable` treats any `rwm_expected_pct === 0` as non-comparable regardless of `isSpecialSchool`, so a genuine 0% result at a mainstream school renders as '—' (looks like missing data) instead of the real figure, and drops the trend arrow. This is stricter/inconsistent with SchoolDetailView's placeholder check, which requires all of RWM, reading, writing, and maths to be zero before suppressing. - **nextjs-app/components/SecondarySchoolRow.tsx**: Same pattern: `att8Comparable` hides Attainment 8 whenever the value is exactly 0.0, even for non-special schools, mis-displaying a real (if extreme) score as '—' and dropping the LA-average delta.
tudor added 1 commit 2026-07-20 20:58:15 +00:00
fix(review): don't suppress a genuine mainstream 0 in the rows
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 6s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m48s
de943ded19
Review feedback: the rankings/search rows treated any exactly-0 attainment as
not comparable regardless of school type, so a genuine 0 at a mainstream
school rendered as "—" (looks like missing data) and dropped its delta —
stricter and inconsistent with SchoolDetailView's placeholder check.

- SchoolRow: match SchoolDetailView's ks2Placeholder signature — suppress only
  when ALL of RWM + reading + writing + maths are 0 (special/suppressed
  signature), not on a bare rwm === 0. A genuine 0% combined (some pupils met
  individual subjects but not all three) is not all-zero, so it stays
  comparable and shows its real figure + trend.
- SecondarySchoolRow: Attainment 8 is a single 0–80 score with no subject
  breakdown to form an all-zero signature, so key off establishment type only
  (drop the bare att8 === 0 guard). A genuine (if extreme) 0.0 shows its value
  + LA delta.
- For consistency, apply the same to the detail views: drop the bare
  attainment_8_score === 0 guard (KS4 keys off isSpecial only); KS2 keeps the
  all-four-subjects-zero placeholder signature.

Special schools / PRUs / AP are still handled via isSpecialSchool everywhere.
tsc clean; 108/108 unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB

🤖 AI Code Review (Claude Code)

This PR adds special-school/PRU/AP detection (isSpecialSchool) and threads it through the school list rows, detail views, and compare page to suppress the mainstream 'vs England average' framing (deltas, national markers, trend charts) for schools where that benchmark doesn't apply, replacing it with an explanatory note. The change is well-tested (unit tests for isSpecialSchool, two new e2e tests) and the placeholder-zero vs. genuine-zero distinction for KS2 is handled carefully; overall the diff is in good health with no severe issues found.

🟡 Minor

  • nextjs-app/lib/utils.ts: The explanatory note copy shown alongside isSpecialSchool()-matched schools says 'Its pupils have special educational needs' for all matched types, but the function also matches pupil referral units and alternative provision, whose pupils are not necessarily SEND pupils — this mischaracterizes those schools even though suppressing the England-average comparison is reasonable for all three.
  • nextjs-app/components/SchoolRow.tsx: The year-over-year trend arrow (based on prev_rwm_expected_pct) is now gated on rwmComparable, which bundles in isSpecialSchool/placeholder-zero checks. This conflates the 'vs England average' comparison (the actual target of this fix) with a same-school year-over-year trend, hiding a still-meaningful trend indicator for special schools with genuine non-zero data.
  • nextjs-app/components/SecondarySchoolDetailView.module.css: The .specialNote CSS block is duplicated verbatim between SchoolDetailView.module.css and SecondarySchoolDetailView.module.css rather than shared, risking visual drift on future tweaks.
## 🤖 AI Code Review (Claude Code) This PR adds special-school/PRU/AP detection (isSpecialSchool) and threads it through the school list rows, detail views, and compare page to suppress the mainstream 'vs England average' framing (deltas, national markers, trend charts) for schools where that benchmark doesn't apply, replacing it with an explanatory note. The change is well-tested (unit tests for isSpecialSchool, two new e2e tests) and the placeholder-zero vs. genuine-zero distinction for KS2 is handled carefully; overall the diff is in good health with no severe issues found. ### 🟡 Minor - **nextjs-app/lib/utils.ts**: The explanatory note copy shown alongside isSpecialSchool()-matched schools says 'Its pupils have special educational needs' for all matched types, but the function also matches pupil referral units and alternative provision, whose pupils are not necessarily SEND pupils — this mischaracterizes those schools even though suppressing the England-average comparison is reasonable for all three. - **nextjs-app/components/SchoolRow.tsx**: The year-over-year trend arrow (based on prev_rwm_expected_pct) is now gated on rwmComparable, which bundles in isSpecialSchool/placeholder-zero checks. This conflates the 'vs England average' comparison (the actual target of this fix) with a same-school year-over-year trend, hiding a still-meaningful trend indicator for special schools with genuine non-zero data. - **nextjs-app/components/SecondarySchoolDetailView.module.css**: The .specialNote CSS block is duplicated verbatim between SchoolDetailView.module.css and SecondarySchoolDetailView.module.css rather than shared, risking visual drift on future tweaks.
tudor added 1 commit 2026-07-20 21:09:35 +00:00
fix(review): accurate PRU/AP copy, unbundle same-school trend, de-dupe note CSS
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 46s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 12s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m38s
0b36eace9d
Three review points on the special-schools change:

1. Copy accuracy — the context note said "Its pupils have special educational
   needs" for every isSpecialSchool() match, but the helper also matches pupil
   referral units and alternative provision, whose pupils are educated outside
   a mainstream setting (e.g. after exclusion) and are not necessarily SEND.
   Extracted a shared <SpecialSchoolNote> with type-aware copy: SEND wording
   only for genuine special schools; PRUs/AP get their own accurate wording.

2. Same-school trend was conflated with the England comparison — SchoolRow's
   year-over-year trend arrow (and the school's own figure) were gated on the
   same flag that drops the vs-England delta, hiding a still-meaningful trend
   for special schools with real data. Split the two: the school's OWN RWM
   figure + trend show whenever there's a real value (special schools
   included; only a placeholder all-zero row is hidden); only the vs-England
   delta is additionally dropped for special/PRU/AP. Mirrored in
   SecondarySchoolRow (own Attainment 8 shown; only the vs-LA delta dropped).

3. De-duplicated the .specialNote CSS (was copy-pasted between the two detail
   view module files) into SpecialSchoolNote.module.css, owned by the shared
   component so it can't drift.

New SpecialSchoolNote unit tests assert SEND wording for special schools and
NOT for PRUs/AP. tsc clean; 112/112 unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB

🤖 AI Code Review (Claude Code)

This PR adds a frontend-only feature that detects special schools, PRUs, and alternative provision settings (via a new isSpecialSchool() utility) and suppresses the misleading mainstream England/LA-average comparison across detail pages, search-result rows, and the compare tool, replacing it with an explanatory note. The change is well-tested (unit tests, component tests, and a new e2e test) and internally consistent; no backend, CI, or deploy files are touched, so there is no data-loss, security, or production-deploy risk.

🟡 Minor

  • nextjs-app/components/compare/CompareAcademics.tsx: dropSpecial() nulls a special school's own attainment figures entirely in the compare view (rendering 'No data'), whereas the detail pages and search-result rows keep the school's real own-figure and only suppress the benchmark comparison. This is an inconsistent treatment of the same underlying data across views and could misrepresent a special school as having no results when it does.
## 🤖 AI Code Review (Claude Code) This PR adds a frontend-only feature that detects special schools, PRUs, and alternative provision settings (via a new isSpecialSchool() utility) and suppresses the misleading mainstream England/LA-average comparison across detail pages, search-result rows, and the compare tool, replacing it with an explanatory note. The change is well-tested (unit tests, component tests, and a new e2e test) and internally consistent; no backend, CI, or deploy files are touched, so there is no data-loss, security, or production-deploy risk. ### 🟡 Minor - **nextjs-app/components/compare/CompareAcademics.tsx**: dropSpecial() nulls a special school's own attainment figures entirely in the compare view (rendering 'No data'), whereas the detail pages and search-result rows keep the school's real own-figure and only suppress the benchmark comparison. This is an inconsistent treatment of the same underlying data across views and could misrepresent a special school as having no results when it does.
tudor merged commit 993822d769 into main 2026-07-20 21:14:57 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tudor/school_compare#70