Compare commits

..
Author SHA1 Message Date
TudorandClaude Fable 5 4e6be0ce65 fix(detail): banded first-choice interpretation + plain-English P8 absence copy
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 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 24s
Batch C (points 7-10):
- Point 7: the admissions section shows the first-choice offer rate but no
  sense of how severe a low rate is. Reuse the compare screen's banded
  summariseAdmissions ('More than half of first choices missed out' etc.)
  as an interpretive line under the tiles. (Selective/faith framing was
  already present via admissionsTag.)
- Point 8: replace the jargon P8-suspended banner ('not used for
  accountability purposes ... treat with caution') with the compare
  screen's plain-English 'no KS2 baseline (COVID)' explanation.
- Points 9 (disadvantaged cohort sizes) and 10 (FSM benchmark) don't apply
  to the detail page: it shows disadvantaged composition, not disadvantaged
  attainment, and no FSM-vs-benchmark comparison — so there's no small-cohort
  caveat or cross-definition fallback to fix here.
- Also fixed a stray lowercase 'national avg' provenance string Batch B
  missed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
2026-07-20 12:03:23 +01:00
tudor 452ec77449 Merge pull request 'fix(detail): 'England average' provenance labelling (Batch B)' (#65) from fix/detail-provenance-anchoring into main
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 13s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 48s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 1s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 43s
Reviewed-on: #65
2026-07-20 07:52:55 +00:00
tudor aa87fa917d Merge pull request 'fix(detail): Ofsted sentinel codes, sixth form, carried-forward labels (Batch A)' (#64) from fix/detail-ofsted-correctness into main
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 14s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 49s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 1s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 42s
Reviewed-on: #64
2026-07-20 06:21:06 +00:00
TudorandClaude Fable 5 e36125b24a fix(detail): label official DfE figures as 'England average' (provenance)
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m0s
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 43s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 13s
Batch B (points 5-6): the detail page called every national figure a
'National avg'. All of them come from the official-DfE national-averages
marts (KS2/KS4 headlines), so they are England averages — relabel to
match the compare screen's provenance convention ('England average' for
official figures; the detail page has no computed benchmarks, so no
'state-school average (computed)' label is needed). Point 6 (anchoring
every number) is already satisfied on the detail page via DeltaChip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
2026-07-20 07:19:22 +01:00
2 changed files with 26 additions and 17 deletions
+20 -12
View File
@@ -21,6 +21,7 @@ import {
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose, ofstedLegacyAreas,
} from '@/lib/utils';
import { DeltaChip } from './DeltaChip';
import { summariseAdmissions } from '@/lib/compareLogic';
const PerformanceChart = dynamic(
() => import('./PerformanceChart').then((m) => m.PerformanceChart),
@@ -85,6 +86,10 @@ export function SchoolDetailView({
// Trend toggle only appears with ≥2 years carrying an offer rate.
const admissionsOfferYears = admissionsHistory.filter((h) => h.first_preference_offer_pct != null).length;
const showAdmissionsTrend = admissionsOfferYears >= 2;
// Banded interpretation of the first-choice offer rate ("More than half of
// first choices missed out" etc.) — the same banding the compare screen
// uses, so a low offer rate reads as how severe it actually is.
const admissionsSummary = summariseAdmissions(admissions);
// Only the section links scroll horizontally; Back and "All" stay pinned.
const sectionLinksRef = useRef<HTMLDivElement | null>(null);
const [sectionNavAtEnd, setSectionNavAtEnd] = useState(false);
@@ -611,8 +616,8 @@ export function SchoolDetailView({
</h2>
<p className={styles.sectionSubtitle}>
{isSecondary
? 'GCSE results for Year 11 pupils. National averages shown for comparison.'
: 'End-of-primary-school tests taken by Year 6 pupils. National averages shown for comparison.'}
? '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>
{/* ── Primary / KS2 content ── */}
@@ -637,7 +642,7 @@ export function SchoolDetailView({
)}
</div>
{primaryAvg.rwm_expected_pct != null && (
<div className={styles.heroStatHint}>National avg: {primaryAvg.rwm_expected_pct.toFixed(0)}%</div>
<div className={styles.heroStatHint}>England avg: {primaryAvg.rwm_expected_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -659,7 +664,7 @@ export function SchoolDetailView({
)}
</div>
{primaryAvg.rwm_high_pct != null && (
<div className={styles.heroStatHint}>National avg: {primaryAvg.rwm_high_pct.toFixed(0)}%</div>
<div className={styles.heroStatHint}>England avg: {primaryAvg.rwm_high_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -765,7 +770,7 @@ export function SchoolDetailView({
</div>
<div className={styles.metricValue}>{latestResults.attainment_8_score.toFixed(1)}</div>
{secondaryAvg.attainment_8_score != null && (
<div className={styles.metricHint}>National avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
<div className={styles.metricHint}>England avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
)}
</div>
)}
@@ -789,7 +794,7 @@ export function SchoolDetailView({
</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_standard_pass_pct)}</div>
{secondaryAvg.english_maths_standard_pass_pct != null && (
<div className={styles.metricHint}>National avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
<div className={styles.metricHint}>England avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -801,7 +806,7 @@ export function SchoolDetailView({
</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_strong_pass_pct)}</div>
{secondaryAvg.english_maths_strong_pass_pct != null && (
<div className={styles.metricHint}>National avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
<div className={styles.metricHint}>England avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -906,6 +911,9 @@ export function SchoolDetailView({
</div>
)}
</dl>
{admissionsSummary.chip && (
<p className={styles.admissionsTrendSummary}>{admissionsSummary.chip.text}</p>
)}
</div>
{/* Multi-year trend */}
@@ -941,7 +949,7 @@ export function SchoolDetailView({
<DeltaChip value={latestResults.disadvantaged_pct} baseline={primaryAvg.disadvantaged_pct} unit="pts" size="sm" />
)}
</div>
<div className={styles.heroStatHint}>Pupils from disadvantaged backgrounds{primaryAvg.disadvantaged_pct != null ? ` · national avg: ${primaryAvg.disadvantaged_pct.toFixed(0)}%` : ''}</div>
<div className={styles.heroStatHint}>Pupils from disadvantaged backgrounds{primaryAvg.disadvantaged_pct != null ? ` · England avg: ${primaryAvg.disadvantaged_pct.toFixed(0)}%` : ''}</div>
</div>
)}
{latestResults?.eal_pct != null && (
@@ -957,7 +965,7 @@ export function SchoolDetailView({
)}
</div>
{primaryAvg.eal_pct != null && (
<div className={styles.heroStatHint}>National avg: {primaryAvg.eal_pct.toFixed(0)}%</div>
<div className={styles.heroStatHint}>England avg: {primaryAvg.eal_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -974,7 +982,7 @@ export function SchoolDetailView({
)}
</div>
{primaryAvg.sen_support_pct != null && (
<div className={styles.heroStatHint}>National avg: {primaryAvg.sen_support_pct.toFixed(0)}%</div>
<div className={styles.heroStatHint}>England avg: {primaryAvg.sen_support_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -1146,7 +1154,7 @@ export function SchoolDetailView({
</div>
<div className={styles.metricValue}>{formatPercentage(absenceData.overall_absence_rate)}</div>
{primaryAvg.overall_absence_pct != null && (
<div className={styles.metricHint}>National avg: ~{primaryAvg.overall_absence_pct.toFixed(1)}%</div>
<div className={styles.metricHint}>England avg: ~{primaryAvg.overall_absence_pct.toFixed(1)}%</div>
)}
</div>
)}
@@ -1158,7 +1166,7 @@ export function SchoolDetailView({
</div>
<div className={styles.metricValue}>{formatPercentage(absenceData.persistent_absence_rate)}</div>
{primaryAvg.persistent_absence_pct != null && (
<div className={styles.metricHint}>National avg: ~{primaryAvg.persistent_absence_pct.toFixed(0)}%</div>
<div className={styles.metricHint}>England avg: ~{primaryAvg.persistent_absence_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -452,12 +452,13 @@ export function SecondarySchoolDetailView({
GCSE Results ({formatAcademicYear(latestResults.year)})
</h2>
<p className={styles.sectionSubtitle}>
GCSE results for Year 11 pupils. National averages shown for comparison.
GCSE results for Year 11 pupils. England averages shown for comparison.
</p>
{p8Suspended && (
<div className={styles.p8Banner}>
Progress 8 scores for 2024/25 are not used for accountability purposes following the KS2 assessment disruption. Treat with caution.
Progress 8 isn&apos;t published for 2024/25: this GCSE year group sat no KS2 tests
(COVID), so DfE has no starting point to measure their progress from.
</div>
)}
@@ -481,7 +482,7 @@ export function SecondarySchoolDetailView({
)}
</div>
{secondaryAvg.attainment_8_score != null && (
<div className={styles.heroStatHint}>National avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
<div className={styles.heroStatHint}>England avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
)}
</div>
)}
@@ -521,7 +522,7 @@ export function SecondarySchoolDetailView({
)}
</div>
{secondaryAvg.english_maths_strong_pass_pct != null && (
<div className={styles.heroStatHint}>National avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
<div className={styles.heroStatHint}>England avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
)}
</div>
)}
@@ -543,7 +544,7 @@ export function SecondarySchoolDetailView({
)}
</div>
{secondaryAvg.english_maths_standard_pass_pct != null && (
<div className={styles.heroStatHint}>National avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
<div className={styles.heroStatHint}>England avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
)}
</div>
)}