fix(compare): per-school P8 explanation; disadvantaged cohort from the same yearly row
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
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 47s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m24s

Review findings: (1) the 'no KS2 baseline (COVID)' explanation was derived
from the basket-wide max year, so a school with an unrelated data gap
borrowed it from a neighbour with 2024/25 data — now judged per school on
its own latest year. (2) The '~N disadvantaged pupils' cohort multiplied
eligible_pupils and disadvantaged_pct resolved independently across years
— now both come from the same yearly row that supplies the displayed
percentage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
This commit is contained in:
TudorandClaude Fable 5 committed 2026-07-17 17:53:10 +01:00
1 parent f3fa12806b
commit 200a97d0b9
1 file changed
+22 -14
@@ -150,13 +150,15 @@ export function CompareAcademics({
}); });
// DfE stopped publishing Progress 8 from 2024/25: those GCSE year groups // DfE stopped publishing Progress 8 from 2024/25: those GCSE year groups
// sat no KS2 tests (COVID), so there is no baseline to measure progress // sat no KS2 tests (COVID), so there is no baseline to measure progress
// from. A bare "No data" reads as a gap on our side — say why. // from. A bare "No data" reads as a gap on our side — say why. Judged
const latestYear = urns.reduce((max, urn) => { // PER SCHOOL on its own latest data year: a school whose data simply
// stops earlier (an unrelated gap) must not borrow the COVID explanation
// from a neighbour that does have 2024/25 data.
const p8NotPublished = urns.map((urn) => {
const rows = data[String(urn)]?.yearly_data ?? []; const rows = data[String(urn)]?.yearly_data ?? [];
const y = rows.length ? Math.trunc(rows[rows.length - 1].year) : 0; const y = rows.length ? Math.trunc(rows[rows.length - 1].year) : 0;
return Math.max(max, y); return y >= 202425;
}, 0); });
const p8NotPublished = latestYear >= 202425;
const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct'); const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct');
const ebacc = latestValues(data, urns, 'ebacc_entry_pct'); const ebacc = latestValues(data, urns, 'ebacc_entry_pct');
const att8Anchor = nationalAverages?.secondary?.attainment_8_score; const att8Anchor = nationalAverages?.secondary?.attainment_8_score;
@@ -198,7 +200,7 @@ export function CompareAcademics({
> >
{banding[i]} {banding[i]}
</Chip> </Chip>
) : p8NotPublished ? ( ) : p8NotPublished[i] ? (
<span className={s.small}> <span className={s.small}>
Not published — this GCSE year group sat no KS2 tests (COVID), so DfE has no Not published — this GCSE year group sat no KS2 tests (COVID), so DfE has no
baseline to measure progress from baseline to measure progress from
@@ -234,15 +236,21 @@ export function CompareAcademics({
const disadvantagedAnchor = benchmarks?.primary?.disadvantaged_rwm_expected_pct ?? null; const disadvantagedAnchor = benchmarks?.primary?.disadvantaged_rwm_expected_pct ?? null;
// Cohort size behind the disadvantaged figure (spec §8.5): these are small // Cohort size behind the disadvantaged figure (spec §8.5): these are small
// groups where single pupils move the percentage — show roughly how many // groups where single pupils move the percentage — show roughly how many
// pupils the figure rests on. // pupils the figure rests on. Taken from the SAME yearly row that supplies
const eligible = latestValues(data, urns, 'eligible_pupils'); // the displayed percentage: resolving eligible_pupils and the
const disadvantagedShare = latestValues(data, urns, 'disadvantaged_pct'); // disadvantaged share independently could mix years and misstate the
const cohorts = urns.map((_, i) => { // cohort behind the figure.
const n = eligible[i]; const cohorts = urns.map((urn) => {
const share = disadvantagedShare[i]; const rows = data[String(urn)]?.yearly_data ?? [];
if (n == null || share == null) return null; for (let i = rows.length - 1; i >= 0; i--) {
const cohort = Math.round((n * share) / 100); const row = rows[i];
if (row.rwm_expected_disadvantaged_pct != null) {
if (row.eligible_pupils == null || row.disadvantaged_pct == null) return null;
const cohort = Math.round((row.eligible_pupils * row.disadvantaged_pct) / 100);
return cohort > 0 ? cohort : null; return cohort > 0 ? cohort : null;
}
}
return null;
}); });
return ( return (