2026-07-13 23:50:29 +01:00
/**
* How children do academically — tier-1 dot strips anchored on official
* England averages, tier-2 "More measures" one tap away, equity row against
* the computed state-school benchmark. Copy verbatim from the reviewed
* mockups; teacher-assessed measures are labelled as such.
*/
'use client' ;
import { latestValues , verdict } from '@/lib/compareLogic' ;
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' ;
import styles from './CompareAcademics.module.css' ;
interface StripSpec {
label : string ;
metric : string ;
anchorKey? : string ;
tip? : string ;
min? : number ;
max? : number ;
unit? : string ;
}
const TIER1_PRIMARY : StripSpec [] = [
{
label : 'Reading, writing & maths — expected standard' ,
metric : 'rwm_expected_pct' ,
anchorKey : 'rwm_expected_pct' ,
tip : '% of Year 6 pupils reaching the expected standard in reading, writing and maths.' ,
},
{ label : 'Reading' , metric : 'reading_expected_pct' , anchorKey : 'reading_expected_pct' },
{
label : 'Writing (teacher-assessed)' ,
metric : 'writing_expected_pct' ,
anchorKey : 'writing_expected_pct' ,
tip : 'Writing is assessed by teachers, not tested.' ,
},
{ label : 'Maths' , metric : 'maths_expected_pct' , anchorKey : 'maths_expected_pct' },
{
label : 'Working at a higher standard than expected' ,
metric : 'rwm_high_pct' ,
anchorKey : 'rwm_high_pct' ,
tip : 'A high score in the reading and maths tests plus “greater depth” in teacher-assessed writing.' ,
},
];
const TIER2_PRIMARY : StripSpec [] = [
{
label : 'Grammar, punctuation & spelling — expected standard' ,
metric : 'gps_expected_pct' ,
anchorKey : 'gps_expected_pct' ,
},
{
label : 'Science — expected standard (teacher-assessed)' ,
metric : 'science_expected_pct' ,
anchorKey : 'science_expected_pct' ,
tip : 'Teacher-assessed, like writing — there has been no KS2 science test since 2009, so comparisons are indicative.' ,
},
{
label : 'Average scaled score — reading' ,
metric : 'reading_avg_score' ,
anchorKey : 'reading_avg_score' ,
min : 100 ,
max : 120 ,
unit : '' ,
},
{
label : 'Average scaled score — maths' ,
metric : 'maths_avg_score' ,
anchorKey : 'maths_avg_score' ,
min : 100 ,
max : 120 ,
unit : '' ,
},
{
label : 'Average scaled score — grammar, punctuation & spelling' ,
metric : 'gps_avg_score' ,
anchorKey : 'gps_avg_score' ,
min : 100 ,
max : 120 ,
unit : '' ,
},
];
function Strip ({
spec ,
data ,
urns ,
schoolNames ,
national ,
} : {
spec : StripSpec ;
data : Record < string , ComparisonData >;
urns : number [];
schoolNames : string [];
national : Record < string , number > | undefined ;
}) {
const values = latestValues ( data , urns , spec . metric ). map (( v ) =>
v != null ? Math . round ( v ) : null ,
);
const anchorValue = spec . anchorKey ? national ? .[ spec . anchorKey ] : undefined ;
const anchor =
anchorValue != null
? { value : anchorValue , label : `England ${ Math . round ( anchorValue ) }${ spec . unit ?? '%' } ` }
: null ;
if ( values . every (( v ) => v == null )) return null ;
return (
< DotStrip
label = { spec . label }
values = { values }
schoolNames = { schoolNames }
anchor = { anchor }
min = { spec . min ?? 0 }
max = { spec . max ?? 100 }
unit = { spec . unit ?? '%' }
tip = { spec . tip }
/>
);
}
export function CompareAcademics ({
schools ,
data ,
nationalAverages ,
benchmarks ,
2026-07-15 17:38:02 +01:00
isSecondary : propIsSecondary ,
2026-07-13 23:50:29 +01:00
} : {
schools : School [];
data : Record < string , ComparisonData >;
nationalAverages? : NationalAverages ;
benchmarks? : Benchmarks ;
2026-07-15 17:38:02 +01:00
isSecondary? : boolean ;
2026-07-13 23:50:29 +01:00
}) {
const urns = schools . map (( school ) => school . urn );
const schoolNames = schools . map (( school ) => school . school_name );
2026-07-15 17:38:02 +01:00
const isSecondary = propIsSecondary !== undefined ? propIsSecondary : schools.some (
2026-07-13 23:50:29 +01:00
( school ) => data [ String ( school . urn )] ? . school_info ? . attainment_8_score != null ,
);
if ( isSecondary ) {
const att8 = 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 -- ) {
if ( rows [ i ]. progress_8_banding ) return rows [ i ]. progress_8_banding as string ;
}
return null ;
});
const grade5 = latestValues ( data , urns , 'english_maths_strong_pass_pct' );
const ebacc = latestValues ( data , urns , 'ebacc_entry_pct' );
const att8Anchor = nationalAverages ? . secondary ? . attainment_8_score ;
return (
< Section
title = "How students do academically"
how = "GCSE results (latest year). Attainment 8 averages performance across eight subjects; Progress 8 shows how much progress students make compared with similar students nationally — the wording is DfE's own banding."
>
< SectionGrid schools = { schools }>
< RowLabel tip = "Average Attainment 8 score across eight GCSE subjects." > Attainment 8 </ RowLabel >
{ schools . map (( school , i ) => (
< Cell key = { school . urn } school = { school } index = { i }>
{ att8 [ i ] != null ? (
<>
< span className = { s . big }>{( att8 [ i ] as number ). toFixed ( 1 )}</ span >
{ att8Anchor != null && (
< span className = { s . small }> England average { att8Anchor . toFixed ( 1 )}</ span >
)}
</>
) : (
< span className = { s . small }> No data </ span >
)}
</ Cell >
))}
< RowLabel tip = "DfE's own plain-English Progress 8 label." > Progress 8 </ RowLabel >
{ schools . map (( school , i ) => (
< Cell key = { school . urn } school = { school } index = { i }>
{ banding [ i ] ? (
< Chip
tone = {
/ well above | above / i . test ( banding [ i ] as string )
? 'good'
: /well below|below/i . test ( banding [ i ] as string )
? 'warn'
: 'neutral'
}
>
{ banding [ i ]}
</ Chip >
) : (
< span className = { s . small }> No data </ span >
)}
</ Cell >
))}
< RowLabel tip = "% achieving grade 5 or above in both English and maths GCSEs." >
Grade 5 + in English & amp ; maths
</ RowLabel >
{ schools . map (( school , i ) => (
< Cell key = { school . urn } school = { school } index = { i }>
{ grade5 [ i ] != null ? ` ${ Math . round ( grade5 [ i ] as number ) } %` : < span className = { s . small }> No data </ span >}
</ Cell >
))}
< RowLabel tip = "% entering the English Baccalaureate subject combination." > EBacc entry </ RowLabel >
{ schools . map (( school , i ) => (
< Cell key = { school . urn } school = { school } index = { i }>
{ ebacc [ i ] != null ? ` ${ Math . round ( ebacc [ i ] as number ) } %` : < span className = { s . small }> No data </ span >}
</ Cell >
))}
</ SectionGrid >
</ Section >
);
}
const national = nationalAverages ? . primary ;
const disadvantaged = latestValues ( data , urns , 'rwm_expected_disadvantaged_pct' );
const disadvantagedAnchor = benchmarks ? . primary ? . disadvantaged_rwm_expected_pct ?? null ;
return (
< Section
title = "How children do academically"
how = "Results from national tests and teacher assessments at the end of Year 6 — writing is assessed by teachers, not tested. Each line runs from 0– 100%; the grey tick marks the England average, so dots to its right are above average."
>
< div className = { s . card }>
{ TIER1_PRIMARY . map (( spec ) => (
< Strip
key = { spec . metric }
spec = { spec }
data = { data }
urns = { urns }
schoolNames = { schoolNames }
national = { national }
/>
))}
< details className = { styles . moreMeasures }>
< summary > More measures — grammar , punctuation & amp ; spelling , science , average scaled scores </ summary >
{ TIER2_PRIMARY . map (( spec ) => (
< Strip
key = { spec . metric }
spec = { spec }
data = { data }
urns = { urns }
schoolNames = { schoolNames }
national = { national }
/>
))}
< p className = { styles . stripNote }>
The scaled - score strips show the 100 – 120 window of the full 80 – 120 range ; 100 is the
expected standard . Where an England tick is missing , the official figure isn & apos ; t in
our dataset yet .
</ p >
</ details >
</ div >
{ disadvantaged . some (( v ) => v != null ) && (
< SectionGrid schools = { schools }>
< RowLabel tip = "% of disadvantaged pupils (free school meals in the last 6 years, or looked after by the local authority) reaching the expected standard. Benchmark computed across state schools in our dataset. Based on smaller pupil groups, so a single pupil can move a school's figure noticeably." >
Children from lower - income families
</ RowLabel >
{ schools . map (( school , i ) => {
const value = disadvantaged [ i ];
return (
< Cell key = { school . urn } school = { school } index = { i }>
{ value != null ? (
<>
< span className = { s . big } style = {{ fontSize : '1.1rem' }}>
{ Math . round ( value )} %
</ span >{ ' ' }
{ disadvantagedAnchor != null && (
< Chip tone = { verdict ( value , disadvantagedAnchor , 5 ) === 'below' ? 'warn' : 'good' }>
{ verdict ( value , disadvantagedAnchor , 5 ) === 'above' &&
`Well above the ${ Math . round ( disadvantagedAnchor ) } % state-school average` }
{ verdict ( value , disadvantagedAnchor , 5 ) === 'close' &&
`Around the ${ Math . round ( disadvantagedAnchor ) } % state-school average` }
{ verdict ( value , disadvantagedAnchor , 5 ) === 'below' &&
`Below the ${ Math . round ( disadvantagedAnchor ) } % state-school average` }
</ Chip >
)}
</>
) : (
< span className = { s . small }> No data </ span >
)}
</ Cell >
);
})}
</ SectionGrid >
)}
</ Section >
);
}