2026-07-13 23:49:17 +01:00
/**
* Ofsted section — one visual grammar for inspection detail across all
* three regimes (legacy graded, interim carried-forward, renewed-framework
* report card). Copy comes verbatim from the reviewed mockups.
*/
'use client' ;
import {
OFSTED_LEGACY_GRADES ,
ofstedDisplay ,
rcAreaLabel ,
type OfstedDisplay ,
} from '@/lib/compareLogic' ;
import type { ComparisonData , OfstedInspection , School } from '@/lib/types' ;
2026-07-15 07:50:01 +01:00
import { Cell , Chip , Measure , Section , SectionGrid , sectionStyles as s } from './sectionShared' ;
2026-07-13 23:49:17 +01:00
const GRADE_TONE : Record < number , 'good' | 'warn' | 'bad' > = {
1 : 'good' ,
2 : 'good' ,
3 : 'warn' ,
4 : 'bad' ,
};
const RC_CODE_TONE = ( code : number ) : 'good' | 'warn' | 'bad' | 'neutral' =>
code <= 2 ? 'good' : code === 3 ? 'neutral' : code === 4 ? 'warn' : 'bad' ;
function formatInspectionDate ( iso : string | null ) : string {
if ( ! iso ) return '—' ;
const d = new Date ( iso );
if ( Number . isNaN ( d . getTime ())) return '—' ;
return d . toLocaleDateString ( 'en-GB' , { day : 'numeric' , month : 'short' , year : 'numeric' });
}
function yearsSince ( iso : string | null ) : number | null {
if ( ! iso ) return null ;
const d = new Date ( iso );
if ( Number . isNaN ( d . getTime ())) return null ;
return ( Date . now () - d . getTime ()) / ( 365.25 * 24 * 3600 * 1000 );
}
function ResultCell ({ display } : { display : OfstedDisplay }) {
if ( display . kind === 'none' ) {
return < span className = { s . small }> No inspection outcome in our dataset </ span >;
}
if ( display . kind === 'report_card' ) {
return (
<>
< strong style = {{ fontSize : '0.9rem' }}> Report card </ strong >
< span className = { s . small }> New - style inspection — no overall grade is given </ span >
</>
);
}
2026-07-15 17:23:40 +01:00
if ( display . kind === 'transitional' ) {
return (
<>
< span className = { s . badge } style = {{ backgroundColor : '#e2e8f0' , color : '#475569' }}>
No overall grade
</ span >
< span className = { s . small }>
Inspected under transitional framework ( sub - judgements only )
</ span >
</>
);
}
2026-07-13 23:49:17 +01:00
return (
<>
< span className = { ` ${ s . badge } ${ display . grade <= 2 ? s.badgeGood : display.grade === 3 ? s.badgeWarn : s.badgeBad } ` }>
{ display . gradeLabel }
</ span >
< span className = { s . small }>
{ display . carriedForward
? 'Grade carried forward from an earlier inspection (ungraded visit since)'
: 'Overall grade (older-style inspection)' }
</ span >
</>
);
}
function JudgementDetailCell ({
ofsted ,
display ,
schoolName ,
} : {
ofsted : OfstedInspection ;
display : OfstedDisplay ;
schoolName : string ;
}) {
if ( display . kind === 'report_card' ) {
const entries = Object . entries ( ofsted . report_card ?? {});
return (
< div className = { s . rcList }>
{ entries . map (([ key , entry ]) => (
< div key = { key } className = { s . rcRow }>
< span className = { s . rcArea }>{ rcAreaLabel ( key )}</ span >
< Chip tone = { RC_CODE_TONE ( entry . code )}>{ entry . label }</ Chip >
</ div >
))}
{ ofsted . rc_safeguarding_met != null && (
< div className = { s . rcRow }>
< span className = { s . rcArea }> Safeguarding </ span >
< Chip tone = { ofsted . rc_safeguarding_met ? 'good' : 'bad' }>
{ ofsted . rc_safeguarding_met ? 'Met' : 'Not met' }
</ Chip >
</ div >
)}
</ div >
);
}
const legacyAreas : Array < [ string , number | null ] > = [
[ 'Quality of education' , ofsted . quality_of_education ],
[ 'Behaviour & attitudes' , ofsted . behaviour_attitudes ],
[ 'Personal development' , ofsted . personal_development ],
[ 'Leadership & management' , ofsted . leadership_management ],
[ 'Early years provision' , ofsted . early_years_provision ],
];
const published = legacyAreas . filter (([, grade ]) => grade != null );
if ( published . length === 0 ) {
return (
< span className = { s . small }>
We don & apos ; t hold area - by - area detail for this inspection — see { schoolName } & apos ; s
Ofsted page for the full report .
</ span >
);
}
return (
< div className = { s . rcList }>
{ published . map (([ label , grade ]) => (
< div key = { label } className = { s . rcRow }>
< span className = { s . rcArea }>{ label }</ span >
< Chip tone = { GRADE_TONE [ grade as number ] ?? 'neutral' }>
{ OFSTED_LEGACY_GRADES [ grade as number ] ?? String ( grade )}
</ Chip >
</ div >
))}
</ div >
);
}
export function CompareOfsted ({
schools ,
data ,
} : {
schools : School [];
data : Record < string , ComparisonData >;
}) {
const displays = schools . map (( school ) => ofstedDisplay ( data [ String ( school . urn )] ? . ofsted ));
const kinds = new Set ( displays . map (( d ) => d . kind ). filter (( k ) => k !== 'none' ));
const mixedRegimes = kinds . size > 1 ;
return (
< Section
title = "Ofsted inspection"
how = {
<>
Ofsted is the schools inspectorate . It stopped giving a single overall grade in { ' ' }
< strong > September 2024 </ strong >; inspections between then and November 2025 kept the
area - by - area judgements without an overall grade , and from < strong > November 2025 </ strong >{ ' ' }
new inspections produce a < strong > report card </ strong > rating each area of school life on
a five - point scale .
{ mixedRegimes && (
<> A report card and an older overall grade aren & apos ; t directly comparable .</>
)}{ ' ' }
( Ofsted & apos ; s & quot ; Expected standard & quot ; rating is unrelated to the KS2 & quot ; expected
standard & quot ; test measure further down this page .)
</>
}
>
< SectionGrid schools = { schools }>
2026-07-15 07:50:01 +01:00
< Measure label = "Result" >
2026-07-13 23:49:17 +01:00
{ schools . map (( school , i ) => (
< Cell key = { school . urn } school = { school } index = { i }>
< ResultCell display = { displays [ i ]} />
</ Cell >
))}
2026-07-15 07:50:01 +01:00
</ Measure >
2026-07-13 23:49:17 +01:00
2026-07-15 07:50:01 +01:00
< Measure label = "Inspected" >
2026-07-13 23:49:17 +01:00
{ schools . map (( school , i ) => {
const ofsted = data [ String ( school . urn )] ? . ofsted ;
2026-07-16 14:48:17 +01:00
// A report card is dated by its OWN inspection date. The legacy
// inspection_date belongs to an older inspection and must never
// be shown against a report card (report cards exist only from
// Nov 2025).
const dateIso =
displays [ i ]. kind === 'report_card'
? ofsted ? . rc_inspection_date ?? null
: ofsted ? . inspection_date ?? null ;
const age = yearsSince ( dateIso );
2026-07-13 23:49:17 +01:00
return (
< Cell key = { school . urn } school = { school } index = { i }>
2026-07-16 14:48:17 +01:00
{ formatInspectionDate ( dateIso )}{ ' ' }
2026-07-13 23:49:17 +01:00
{ age != null && age > 4 && < Chip tone = "neutral" > 4 + years ago </ Chip >}
</ Cell >
);
})}
2026-07-15 07:50:01 +01:00
</ Measure >
< Measure
tip = "Older-style inspections: one rating per judgement area, where published. New-style inspections: the full report card, one rating per area of school life."
label = "Judgement detail"
>
2026-07-13 23:49:17 +01:00
{ schools . map (( school , i ) => {
const ofsted = data [ String ( school . urn )] ? . ofsted ;
return (
< Cell key = { school . urn } school = { school } index = { i }>
{ ofsted ? (
< JudgementDetailCell
ofsted = { ofsted }
display = { displays [ i ]}
schoolName = { school . school_name }
/>
) : (
< span className = { s . small }> No inspection in our dataset </ span >
)}
</ Cell >
);
})}
2026-07-15 07:50:01 +01:00
</ Measure >
< Measure
tip = "Links to the school's page on ofsted.gov.uk, where all its inspection reports are listed."
label = "Ofsted page"
>
2026-07-13 23:49:17 +01:00
{ schools . map (( school , i ) => {
const url =
data [ String ( school . urn )] ? . ofsted ? . ofsted_page_url ??
`https://reports.ofsted.gov.uk/provider/21/ ${ school . urn } ` ;
return (
< Cell key = { school . urn } school = { school } index = { i }>
< a className = { s . link } href = { url } target = "_blank" rel = "noopener noreferrer" >
{ school . school_name } & apos ; s Ofsted page →
</ a >
</ Cell >
);
})}
2026-07-15 07:50:01 +01:00
</ Measure >
2026-07-13 23:49:17 +01:00
</ SectionGrid >
</ Section >
);
}