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:
@@ -8,6 +8,7 @@
|
||||
'use client';
|
||||
|
||||
import { latestValues, verdict } from '@/lib/compareLogic';
|
||||
import { isSpecialSchool } from '@/lib/utils';
|
||||
import type { Benchmarks, ComparisonData, NationalAverages, School } from '@/lib/types';
|
||||
import { DotStrip } from '@/components/DotStrip';
|
||||
import { Cell, Chip, RowLabel, Section, SectionGrid, sectionStyles as s } from './sectionShared';
|
||||
@@ -90,15 +91,19 @@ function Strip({
|
||||
urns,
|
||||
schoolNames,
|
||||
national,
|
||||
special,
|
||||
}: {
|
||||
spec: StripSpec;
|
||||
data: Record<string, ComparisonData>;
|
||||
urns: number[];
|
||||
schoolNames: string[];
|
||||
national: Record<string, number> | undefined;
|
||||
/** Per-school special-school flag; special schools' mainstream attainment is
|
||||
* not a fair comparison, so it's dropped from the strip (no dot). */
|
||||
special: boolean[];
|
||||
}) {
|
||||
const values = latestValues(data, urns, spec.metric).map((v) =>
|
||||
v != null ? Math.round(v) : null,
|
||||
const values = latestValues(data, urns, spec.metric).map((v, i) =>
|
||||
v != null && !special[i] ? Math.round(v) : null,
|
||||
);
|
||||
const anchorValue = spec.anchorKey ? national?.[spec.anchorKey] : undefined;
|
||||
const anchor =
|
||||
@@ -135,12 +140,19 @@ export function CompareAcademics({
|
||||
}) {
|
||||
const urns = schools.map((school) => school.urn);
|
||||
const schoolNames = schools.map((school) => school.school_name);
|
||||
// Special schools / PRUs / AP: their pupils sit the same assessments but very
|
||||
// few reach the mainstream standard, so their attainment isn't a fair
|
||||
// like-for-like comparison — drop it (progress banding, which IS meaningful,
|
||||
// is kept).
|
||||
const specialFlags = schools.map((school) => isSpecialSchool(school));
|
||||
const dropSpecial = (vals: Array<number | null>) =>
|
||||
vals.map((v, i) => (specialFlags[i] ? null : v));
|
||||
const isSecondary = propIsSecondary !== undefined ? propIsSecondary : schools.some(
|
||||
(school) => data[String(school.urn)]?.school_info?.attainment_8_score != null,
|
||||
);
|
||||
|
||||
if (isSecondary) {
|
||||
const att8 = latestValues(data, urns, 'attainment_8_score');
|
||||
const att8 = dropSpecial(latestValues(data, urns, 'attainment_8_score'));
|
||||
const banding = urns.map((urn) => {
|
||||
const rows = data[String(urn)]?.yearly_data ?? [];
|
||||
for (let i = rows.length - 1; i >= 0; i--) {
|
||||
@@ -159,8 +171,8 @@ export function CompareAcademics({
|
||||
const y = rows.length ? Math.trunc(rows[rows.length - 1].year) : 0;
|
||||
return y >= 202425;
|
||||
});
|
||||
const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct');
|
||||
const ebacc = latestValues(data, urns, 'ebacc_entry_pct');
|
||||
const grade5 = dropSpecial(latestValues(data, urns, 'english_maths_strong_pass_pct'));
|
||||
const ebacc = dropSpecial(latestValues(data, urns, 'ebacc_entry_pct'));
|
||||
const att8Anchor = nationalAverages?.secondary?.attainment_8_score;
|
||||
const grade5Anchor = nationalAverages?.secondary?.english_maths_strong_pass_pct;
|
||||
const ebaccAnchor = nationalAverages?.secondary?.ebacc_entry_pct;
|
||||
@@ -272,7 +284,7 @@ export function CompareAcademics({
|
||||
}
|
||||
|
||||
const national = nationalAverages?.primary;
|
||||
const disadvantaged = latestValues(data, urns, 'rwm_expected_disadvantaged_pct');
|
||||
const disadvantaged = dropSpecial(latestValues(data, urns, 'rwm_expected_disadvantaged_pct'));
|
||||
const disadvantagedAnchor = benchmarks?.primary?.disadvantaged_rwm_expected_pct ?? null;
|
||||
// Cohort size behind the disadvantaged figure (spec §8.5): these are small
|
||||
// groups where single pupils move the percentage — show roughly how many
|
||||
@@ -307,6 +319,7 @@ export function CompareAcademics({
|
||||
urns={urns}
|
||||
schoolNames={schoolNames}
|
||||
national={national}
|
||||
special={specialFlags}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -320,6 +333,7 @@ export function CompareAcademics({
|
||||
urns={urns}
|
||||
schoolNames={schoolNames}
|
||||
national={national}
|
||||
special={specialFlags}
|
||||
/>
|
||||
))}
|
||||
<p className={styles.stripNote}>
|
||||
|
||||
Reference in New Issue
Block a user