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
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:
@@ -23,7 +23,7 @@ import type {
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas, isSpecialSchool } from '@/lib/utils';
|
||||
import { DeltaChip } from './DeltaChip';
|
||||
import { track, getNavigationSource } from '@/lib/analytics';
|
||||
import styles from './SecondarySchoolDetailView.module.css';
|
||||
@@ -107,6 +107,14 @@ export function SecondarySchoolDetailView({
|
||||
const p8Suspended = latestResults != null && latestResults.year >= 202425;
|
||||
const hasResults = latestResults?.attainment_8_score != null;
|
||||
|
||||
// Special schools / PRUs / AP sit the same GCSEs but teach pupils with SEND,
|
||||
// so their headline attainment is far below the mainstream average by design.
|
||||
// Drop the England comparison + "below" framing so the page doesn't portray
|
||||
// them as failing against a benchmark that doesn't fit. (Also guards a
|
||||
// placeholder all-zero Attainment 8.)
|
||||
const isSpecial = isSpecialSchool(schoolInfo);
|
||||
const suppressComparison = isSpecial || (latestResults != null && latestResults.attainment_8_score === 0);
|
||||
|
||||
const admissionsTag = (() => {
|
||||
const policy = schoolInfo.admissions_policy?.toLowerCase() ?? '';
|
||||
if (policy.includes('selective')) return 'Selective';
|
||||
@@ -452,9 +460,18 @@ export function SecondarySchoolDetailView({
|
||||
GCSE Results ({formatAcademicYear(latestResults.year)})
|
||||
</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
GCSE results for Year 11 pupils. England averages shown for comparison.
|
||||
GCSE results for Year 11 pupils.{!suppressComparison && ' England averages shown for comparison.'}
|
||||
</p>
|
||||
|
||||
{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 GCSEs, but their
|
||||
headline attainment is far below the mainstream average by design, so a comparison
|
||||
with the England average isn’t a meaningful guide to the school.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{p8Suspended && (
|
||||
<div className={styles.p8Banner}>
|
||||
Progress 8 isn't published for 2024/25: this GCSE year group sat no KS2 tests
|
||||
@@ -472,7 +489,7 @@ export function SecondarySchoolDetailView({
|
||||
</div>
|
||||
<div className={styles.heroStatValue}>
|
||||
{latestResults.attainment_8_score.toFixed(1)}
|
||||
{secondaryAvg.attainment_8_score != null && (
|
||||
{!suppressComparison && secondaryAvg.attainment_8_score != null && (
|
||||
<DeltaChip
|
||||
value={latestResults.attainment_8_score}
|
||||
baseline={secondaryAvg.attainment_8_score}
|
||||
@@ -481,7 +498,7 @@ export function SecondarySchoolDetailView({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{secondaryAvg.attainment_8_score != null && (
|
||||
{!suppressComparison && secondaryAvg.attainment_8_score != null && (
|
||||
<div className={styles.heroStatHint}>England avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -512,7 +529,7 @@ export function SecondarySchoolDetailView({
|
||||
</div>
|
||||
<div className={styles.heroStatValue}>
|
||||
{formatPercentage(latestResults.english_maths_strong_pass_pct)}
|
||||
{secondaryAvg.english_maths_strong_pass_pct != null && (
|
||||
{!suppressComparison && secondaryAvg.english_maths_strong_pass_pct != null && (
|
||||
<DeltaChip
|
||||
value={latestResults.english_maths_strong_pass_pct}
|
||||
baseline={secondaryAvg.english_maths_strong_pass_pct}
|
||||
@@ -521,7 +538,7 @@ export function SecondarySchoolDetailView({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{secondaryAvg.english_maths_strong_pass_pct != null && (
|
||||
{!suppressComparison && secondaryAvg.english_maths_strong_pass_pct != null && (
|
||||
<div className={styles.heroStatHint}>England avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -534,7 +551,7 @@ export function SecondarySchoolDetailView({
|
||||
</div>
|
||||
<div className={styles.heroStatValue}>
|
||||
{formatPercentage(latestResults.english_maths_standard_pass_pct)}
|
||||
{secondaryAvg.english_maths_standard_pass_pct != null && (
|
||||
{!suppressComparison && secondaryAvg.english_maths_standard_pass_pct != null && (
|
||||
<DeltaChip
|
||||
value={latestResults.english_maths_standard_pass_pct}
|
||||
baseline={secondaryAvg.english_maths_standard_pass_pct}
|
||||
@@ -543,15 +560,17 @@ export function SecondarySchoolDetailView({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{secondaryAvg.english_maths_standard_pass_pct != null && (
|
||||
{!suppressComparison && secondaryAvg.english_maths_standard_pass_pct != null && (
|
||||
<div className={styles.heroStatHint}>England avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Attainment 8 visual bar (0–80 scale) */}
|
||||
{latestResults.attainment_8_score != null && (
|
||||
{/* Attainment 8 visual bar (0–80 scale). This viz is explicitly
|
||||
"school vs national", so it's dropped for special schools where
|
||||
that comparison isn't meaningful. */}
|
||||
{!suppressComparison && latestResults.attainment_8_score != null && (
|
||||
<div className={styles.att8Viz}>
|
||||
<div className={styles.att8VizLabel}>Attainment 8 — school vs national</div>
|
||||
<div className={styles.att8VizTrack}>
|
||||
@@ -743,8 +762,8 @@ export function SecondarySchoolDetailView({
|
||||
data={yearlyData}
|
||||
schoolName={schoolInfo.school_name}
|
||||
isSecondary={true}
|
||||
nationalAtt8Avg={heroAtt8Nat}
|
||||
nationalByYear={nationalAvg?.by_year}
|
||||
nationalAtt8Avg={suppressComparison ? null : heroAtt8Nat}
|
||||
nationalByYear={suppressComparison ? undefined : nationalAvg?.by_year}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user