diff --git a/nextjs-app/lib/utils.ts b/nextjs-app/lib/utils.ts index e14492f..75c873a 100644 --- a/nextjs-app/lib/utils.ts +++ b/nextjs-app/lib/utils.ts @@ -2,7 +2,7 @@ * Utility functions for SchoolCompare */ -import type { School, MetricDefinition, OfstedInspection, SchoolAdmissions, SchoolResult } from './types'; +import type { School, MetricDefinition } from './types'; // ============================================================================ // String Utilities @@ -619,172 +619,6 @@ export function getCurrentAcademicYear(): number { return month >= 8 ? year : year - 1; } -// ============================================================================ -// School Detail Hero Helpers -// ============================================================================ - -const OFSTED_OEIF_WORDS: Record = { - 1: 'Outstanding', 2: 'Good', 3: 'Requires Improvement', 4: 'Inadequate', -}; - -/** - * Format an Ofsted inspection date as "Month YYYY" (e.g. "November 2023"). - */ -function formatOfstedMonth(date: string | null | undefined): string { - if (!date) return ''; - const d = new Date(date); - if (Number.isNaN(d.getTime())) return ''; - return d.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); -} - -export type HeroTone = 'teal' | 'green' | 'gold' | 'coral' | 'neutral'; - -export interface OfstedHeroChip { - state: 'oeif' | 'reportCard' | 'none'; - title: string; // Main label (e.g. "Ofsted Outstanding", "Ofsted Report Card") - subtitle: string; // Context line (e.g. "Inspected November 2023") - detail?: string; // Optional extra line (e.g. "Safeguarding: Met") - tone: HeroTone; // Maps to dedicated hero tone classes (not badge classes) -} - -/** - * Build the hero-strip Ofsted chip, branching on the inspection framework. - * Never synthesises a single overall grade for ReportCard schools. - * - * Note: the API may return ``framework`` as a literal string ``"NULL"`` for - * older inspections, so we explicitly only branch into the ReportCard layout - * when the value is exactly ``"ReportCard"``. Anything else with an - * ``overall_effectiveness`` score is treated as OEIF. - */ -export function buildOfstedHeroChip(ofsted: OfstedInspection | null | undefined): OfstedHeroChip { - if (!ofsted) { - return { - state: 'none', - title: 'Ofsted pending', - subtitle: 'No inspection on record', - tone: 'neutral', - }; - } - - const when = formatOfstedMonth(ofsted.inspection_date); - - // ReportCard branch — only if the API explicitly says so - if (ofsted.framework === 'ReportCard') { - const safeguarding = ofsted.rc_safeguarding_met; - return { - state: 'reportCard', - title: 'Ofsted Report Card', - subtitle: when ? `Inspected ${when}` : 'New framework inspection', - detail: - safeguarding == null - ? undefined - : safeguarding ? 'Safeguarding: Met' : 'Safeguarding: Not met', - tone: safeguarding === false ? 'coral' : 'green', - }; - } - - // Otherwise treat as OEIF (covers framework === 'OEIF', null, "NULL", etc.) - const grade = ofsted.overall_effectiveness; - if (grade && OFSTED_OEIF_WORDS[grade]) { - const oeifTone: HeroTone = - grade === 1 ? 'teal' : - grade === 2 ? 'green' : - grade === 3 ? 'gold' : - 'coral'; - return { - state: 'oeif', - title: `Ofsted ${OFSTED_OEIF_WORDS[grade]}`, - subtitle: when ? `Inspected ${when}` : 'Inspected', - tone: oeifTone, - }; - } - - return { - state: 'oeif', - title: 'Ofsted inspected', - subtitle: when ? `Inspected ${when}` : 'Inspection on record', - tone: 'neutral', - }; -} - -/** - * Build a one-sentence editorial summary for the school detail hero. - * Branches on Ofsted framework so Report Card schools are never described - * with an overall grade they do not have. - */ -export function buildSchoolSummary( - schoolInfo: School, - ofsted: OfstedInspection | null | undefined, - admissions: SchoolAdmissions | null | undefined, - latestResults: SchoolResult | null | undefined, -): string { - const parts: string[] = []; - - // Size descriptor - const pupils = latestResults?.total_pupils ?? schoolInfo.total_pupils ?? null; - const sizeWord = - pupils == null ? '' : - pupils < 200 ? 'Small' : - pupils < 500 ? 'Mid-sized' : - 'Large'; - - // Phase descriptor — avoid the raw code - const phase = (schoolInfo.phase ?? '').toLowerCase(); - const phaseWord = - phase.includes('secondary') ? 'secondary' : - phase === 'all-through' ? 'all-through' : - phase.includes('primary') ? 'primary' : - 'school'; - - // Religious character - const religion = schoolInfo.religious_denomination; - const religionWord = - !religion || /none|does not apply/i.test(religion) ? '' : - /roman catholic|catholic/i.test(religion) ? 'Catholic ' : - /church of england|ce|anglican/i.test(religion) ? 'Church of England ' : - /jewish/i.test(religion) ? 'Jewish ' : - /muslim|islam/i.test(religion) ? 'Muslim ' : - /hindu/i.test(religion) ? 'Hindu ' : - /sikh/i.test(religion) ? 'Sikh ' : - ''; - - // Locality — prefer town from address parsing (fallback to LA) - const locality = schoolInfo.town || schoolInfo.local_authority || ''; - - const lead = [sizeWord, religionWord + phaseWord].filter(Boolean).join(' '); - let opening = lead || 'School'; - if (locality) opening += ` in ${locality}`; - parts.push(opening); - - // Ofsted clause (framework-aware) - if (ofsted?.framework === 'OEIF' && ofsted.overall_effectiveness) { - parts.push(`rated ${OFSTED_OEIF_WORDS[ofsted.overall_effectiveness]} by Ofsted`); - } else if (ofsted?.framework === 'ReportCard') { - const when = formatOfstedMonth(ofsted.inspection_date); - parts.push( - when - ? `most recently inspected under Ofsted's Report Card framework in ${when}` - : "recently inspected under Ofsted's new Report Card framework", - ); - } - - // Admissions clause - if (admissions?.oversubscribed) { - if (admissions.first_preference_offer_pct != null) { - const pct = Math.round(admissions.first_preference_offer_pct); - parts.push( - `oversubscribed (${pct}% of first-choice applicants are offered a place)`, - ); - } else { - parts.push('oversubscribed'); - } - } else if (admissions?.first_preference_offer_pct != null && admissions.first_preference_offer_pct >= 90) { - parts.push('most families get their first-choice offer'); - } - - return parts.join(', ') + '.'; -} - // ─── Legacy (OEIF) sub-judgement areas ──────────────────────────────────────── export interface OfstedLegacyArea {