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

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
This commit is contained in:
Tudor
2026-07-20 20:39:16 +01:00
co-authored by Claude Opus 4.8
parent 5e370d09f1
commit ae6ef6860b
10 changed files with 276 additions and 71 deletions
+87 -43
View File
@@ -18,7 +18,7 @@ import type {
SchoolDeprivation, SchoolFinance, NationalAverages,
} from '@/lib/types';
import {
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose, ofstedLegacyAreas,
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose, ofstedLegacyAreas, isSpecialSchool,
} from '@/lib/utils';
import { DeltaChip } from './DeltaChip';
import { summariseAdmissions } from '@/lib/compareLogic';
@@ -233,6 +233,24 @@ export function SchoolDetailView({
const hasKS4Results = latestResults != null && latestResults.attainment_8_score != null;
const hasAnyResults = hasKS2Results || hasKS4Results;
// Special schools / PRUs / AP: their pupils sit the same tests but very few
// reach the mainstream "expected standard", so a 0% headline and an England
// comparison portray them as failing against a benchmark that doesn't fit.
const isSpecial = isSpecialSchool(schoolInfo);
// Belt-and-braces: a whole-row zero attainment (special or suppressed cohort)
// is a placeholder, not a real mainstream result — suppress its comparison
// too. A single 0 (e.g. 0% exceeding at a normal school) stays comparable.
const ks2Placeholder = latestResults != null
&& latestResults.rwm_expected_pct === 0
&& (latestResults.reading_expected_pct ?? 0) === 0
&& (latestResults.writing_expected_pct ?? 0) === 0
&& (latestResults.maths_expected_pct ?? 0) === 0;
const ks4Placeholder = latestResults != null && latestResults.attainment_8_score === 0;
// Whether to drop the England-average deltas / national markers / "below"
// framing on the attainment measures.
const suppressKs2Comparison = isSpecial || ks2Placeholder;
const suppressKs4Comparison = isSpecial || ks4Placeholder;
// Build section nav items dynamically — only sections with data.
// Order is engagement-led (from section_nav_used analytics): the most-sought
// sections — results, admissions, inclusion, history — sit near the top,
@@ -624,13 +642,32 @@ export function SchoolDetailView({
{isAllThrough ? 'SATs & GCSE Results' : isSecondary ? 'GCSE Results' : 'SATs Results'} ({formatAcademicYear(latestResults.year)})
</h2>
<p className={styles.sectionSubtitle}>
{isAllThrough
? 'KS2 SATs (end of Year 6) and GCSE results (Year 11) — this school covers both. England averages shown for comparison.'
: isSecondary
? 'GCSE results for Year 11 pupils. England averages shown for comparison.'
: 'End-of-primary-school tests taken by Year 6 pupils. England averages shown for comparison.'}
{isSpecial
? (isSecondary
? 'GCSE results for Year 11 pupils.'
: 'End-of-primary-school tests taken by Year 6 pupils.')
: isAllThrough
? 'KS2 SATs (end of Year 6) and GCSE results (Year 11) — this school covers both. England averages shown for comparison.'
: isSecondary
? 'GCSE results for Year 11 pupils. England averages shown for comparison.'
: 'End-of-primary-school tests taken by Year 6 pupils. England averages shown for comparison.'}
</p>
{/* Special schools sit the same national assessments but teach pupils
with SEND, so very few reach the mainstream "expected standard".
Explain that up front and drop the England comparison below, so a
0% headline never reads as a failing grade against a benchmark
that doesn't fit the school. */}
{isSpecial && (
<div className={styles.specialNote} role="note">
<strong>This is a special school.</strong> Its pupils have special educational
needs and work towards individual targets. They sit the same national tests, but
very few reach the mainstream &ldquo;expected standard&rdquo; these measures report,
so a comparison with the England average isn&rsquo;t a meaningful guide to the
school. Where available, the progress pupils make (below) is a fairer measure.
</div>
)}
{/* ── Primary / KS2 content ── */}
{hasKS2Results && (
<>
@@ -646,7 +683,7 @@ export function SchoolDetailView({
</div>
<div className={styles.heroStatValue}>
{formatPercentage(latestResults.rwm_expected_pct)}
{primaryAvg.rwm_expected_pct != null && (
{!suppressKs2Comparison && primaryAvg.rwm_expected_pct != null && (
<DeltaChip
value={latestResults.rwm_expected_pct}
baseline={primaryAvg.rwm_expected_pct}
@@ -655,7 +692,7 @@ export function SchoolDetailView({
/>
)}
</div>
{primaryAvg.rwm_expected_pct != null && (
{!suppressKs2Comparison && primaryAvg.rwm_expected_pct != null && (
<div className={styles.heroStatHint}>England avg: {primaryAvg.rwm_expected_pct.toFixed(0)}%</div>
)}
</div>
@@ -668,7 +705,7 @@ export function SchoolDetailView({
</div>
<div className={styles.heroStatValue}>
{formatPercentage(latestResults.rwm_high_pct)}
{primaryAvg.rwm_high_pct != null && (
{!suppressKs2Comparison && primaryAvg.rwm_high_pct != null && (
<DeltaChip
value={latestResults.rwm_high_pct}
baseline={primaryAvg.rwm_high_pct}
@@ -677,14 +714,15 @@ export function SchoolDetailView({
/>
)}
</div>
{primaryAvg.rwm_high_pct != null && (
{!suppressKs2Comparison && primaryAvg.rwm_high_pct != null && (
<div className={styles.heroStatHint}>England avg: {primaryAvg.rwm_high_pct.toFixed(0)}%</div>
)}
</div>
)}
</div>
{latestResults.rwm_expected_pct != null &&
{!suppressKs2Comparison &&
latestResults.rwm_expected_pct != null &&
latestResults.reading_expected_pct != null &&
latestResults.writing_expected_pct != null &&
latestResults.maths_expected_pct != null && (
@@ -708,28 +746,34 @@ export function SchoolDetailView({
</div>
)}
<SatsChart
subjects={[
{
name: 'Reading',
expectedPct: latestResults.reading_expected_pct,
exceedingPct: latestResults.reading_high_pct,
nationalExpectedPct: primaryAvg.reading_expected_pct,
},
{
name: 'Writing',
expectedPct: latestResults.writing_expected_pct,
exceedingPct: latestResults.writing_high_pct,
nationalExpectedPct: primaryAvg.writing_expected_pct,
},
{
name: 'Maths',
expectedPct: latestResults.maths_expected_pct,
exceedingPct: latestResults.maths_high_pct,
nationalExpectedPct: primaryAvg.maths_expected_pct,
},
]}
/>
{/* All-zero placeholder rows (special / suppressed) would render as
three empty bars against the national markers — misleading, so
skip the chart. For a special school with some non-zero
subjects, keep the bars but drop the national markers. */}
{!ks2Placeholder && (
<SatsChart
subjects={[
{
name: 'Reading',
expectedPct: latestResults.reading_expected_pct,
exceedingPct: latestResults.reading_high_pct,
nationalExpectedPct: suppressKs2Comparison ? null : primaryAvg.reading_expected_pct,
},
{
name: 'Writing',
expectedPct: latestResults.writing_expected_pct,
exceedingPct: latestResults.writing_high_pct,
nationalExpectedPct: suppressKs2Comparison ? null : primaryAvg.writing_expected_pct,
},
{
name: 'Maths',
expectedPct: latestResults.maths_expected_pct,
exceedingPct: latestResults.maths_high_pct,
nationalExpectedPct: suppressKs2Comparison ? null : primaryAvg.maths_expected_pct,
},
]}
/>
)}
{/* Progress scores row */}
{(latestResults.reading_progress != null || latestResults.writing_progress != null || latestResults.maths_progress != null) && (
@@ -786,7 +830,7 @@ export function SchoolDetailView({
<MetricTooltip metricKey="attainment_8_score" />
</div>
<div className={styles.metricValue}>{latestResults.attainment_8_score.toFixed(1)}</div>
{secondaryAvg.attainment_8_score != null && (
{!suppressKs4Comparison && secondaryAvg.attainment_8_score != null && (
<div className={styles.metricHint}>England avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
)}
</div>
@@ -810,7 +854,7 @@ export function SchoolDetailView({
<MetricTooltip metricKey="english_maths_standard_pass_pct" />
</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_standard_pass_pct)}</div>
{secondaryAvg.english_maths_standard_pass_pct != null && (
{!suppressKs4Comparison && secondaryAvg.english_maths_standard_pass_pct != null && (
<div className={styles.metricHint}>England avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
)}
</div>
@@ -822,7 +866,7 @@ export function SchoolDetailView({
<MetricTooltip metricKey="english_maths_strong_pass_pct" />
</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_strong_pass_pct)}</div>
{secondaryAvg.english_maths_strong_pass_pct != null && (
{!suppressKs4Comparison && secondaryAvg.english_maths_strong_pass_pct != null && (
<div className={styles.metricHint}>England avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
)}
</div>
@@ -1076,8 +1120,8 @@ export function SchoolDetailView({
data={yearlyData}
schoolName={schoolInfo.school_name}
isSecondary={false}
nationalRwmAvg={primaryAvg.rwm_expected_pct ?? null}
nationalByYear={nationalAvg?.by_year}
nationalRwmAvg={suppressKs2Comparison ? null : (primaryAvg.rwm_expected_pct ?? null)}
nationalByYear={suppressKs2Comparison ? undefined : nationalAvg?.by_year}
/>
</div>
</>
@@ -1090,8 +1134,8 @@ export function SchoolDetailView({
data={yearlyData}
schoolName={schoolInfo.school_name}
isSecondary={true}
nationalAtt8Avg={secondaryAvg.attainment_8_score ?? null}
nationalByYear={nationalAvg?.by_year}
nationalAtt8Avg={suppressKs4Comparison ? null : (secondaryAvg.attainment_8_score ?? null)}
nationalByYear={suppressKs4Comparison ? undefined : nationalAvg?.by_year}
/>
</div>
</>
@@ -1103,9 +1147,9 @@ export function SchoolDetailView({
data={yearlyData}
schoolName={schoolInfo.school_name}
isSecondary={isSecondary}
nationalRwmAvg={isPrimary ? (primaryAvg.rwm_expected_pct ?? null) : null}
nationalAtt8Avg={isSecondary ? (secondaryAvg.attainment_8_score ?? null) : null}
nationalByYear={nationalAvg?.by_year}
nationalRwmAvg={isPrimary && !suppressKs2Comparison ? (primaryAvg.rwm_expected_pct ?? null) : null}
nationalAtt8Avg={isSecondary && !suppressKs4Comparison ? (secondaryAvg.attainment_8_score ?? null) : null}
nationalByYear={(isPrimary ? suppressKs2Comparison : suppressKs4Comparison) ? undefined : nationalAvg?.by_year}
/>
</div>
)}