From ab25d83b9d41b78fd74cdf7041b9520c4a78e833 Mon Sep 17 00:00:00 2001 From: Tudor Date: Tue, 13 Oct 2026 21:33:14 +0100 Subject: [PATCH] fix(search): no "Not yet inspected" without Ofsted's word; ISI badge (H4) Co-Authored-By: Claude Opus 5.5 --- .../__tests__/components/SchoolRow.test.tsx | 5 ++ nextjs-app/__tests__/lib/ofstedStatus.test.ts | 11 +++- nextjs-app/__tests__/lib/utils.test.ts | 64 +++++++++++-------- nextjs-app/components/LeafletMapInner.tsx | 2 +- nextjs-app/components/SchoolRow.tsx | 8 ++- nextjs-app/components/SecondarySchoolRow.tsx | 8 ++- nextjs-app/lib/ofstedStatus.ts | 14 ++++ nextjs-app/lib/types.ts | 6 +- nextjs-app/lib/utils.ts | 12 +++- 9 files changed, 92 insertions(+), 38 deletions(-) diff --git a/nextjs-app/__tests__/components/SchoolRow.test.tsx b/nextjs-app/__tests__/components/SchoolRow.test.tsx index 1911d31..46f9dcb 100644 --- a/nextjs-app/__tests__/components/SchoolRow.test.tsx +++ b/nextjs-app/__tests__/components/SchoolRow.test.tsx @@ -52,3 +52,8 @@ describe('SchoolRow shares the school page flags', () => { expect(screen.getByText("Boys' school")).toBeInTheDocument(); }); }); + +it('renders no Ofsted badge for a school with no inspection data (audit H4)', () => { + const { container } = render(); + expect(container.textContent).not.toMatch(/Not yet inspected|Inspected/); +}); diff --git a/nextjs-app/__tests__/lib/ofstedStatus.test.ts b/nextjs-app/__tests__/lib/ofstedStatus.test.ts index 86deadc..b19b4cc 100644 --- a/nextjs-app/__tests__/lib/ofstedStatus.test.ts +++ b/nextjs-app/__tests__/lib/ofstedStatus.test.ts @@ -1,4 +1,4 @@ -import { gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus'; +import { gradeSourceLine, hasReportCard, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus'; import type { OfstedInspection } from '@/lib/types'; const base = { report_card: {} } as unknown as OfstedInspection; @@ -45,3 +45,12 @@ describe('showLatestVisitLine', () => { latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null } })).toBe(false); }); }); + +it('names a short inspection as Ofsted does', () => { + expect(latestVisitLine({ date: '2023-02-01', kind: 'short', outcome: null })).toBe('Short inspection, 1 February 2023'); +}); + +it('treats a report card with only a safeguarding result as a report card (FE)', () => { + expect(hasReportCard({ rc_inspection_date: '2026-05-12', report_card: {} })).toBe(true); + expect(hasReportCard({ rc_inspection_date: null, report_card: {} })).toBe(false); +}); diff --git a/nextjs-app/__tests__/lib/utils.test.ts b/nextjs-app/__tests__/lib/utils.test.ts index ea9f983..a24d9c3 100644 --- a/nextjs-app/__tests__/lib/utils.test.ts +++ b/nextjs-app/__tests__/lib/utils.test.ts @@ -148,40 +148,40 @@ describe('ofstedLegacyAreas', () => { describe('buildOfstedListBadge', () => { it('returns grade word + year for OEIF Outstanding', () => { const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_grade_date: '2023-11-15', ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' }); - expect(badge.label).toBe('Outstanding · 2023'); - expect(badge.cssClass).toBe('ofsted1'); + expect(badge!.label).toBe('Outstanding · 2023'); + expect(badge!.cssClass).toBe('ofsted1'); }); it('returns grade word for each OEIF grade', () => { - expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: '2022-05-01' }).label).toBe('Good · 2022'); - expect(buildOfstedListBadge({ ofsted_grade: 3, ofsted_grade_date: '2021-01-01' }).label).toBe('Req. Improvement · 2021'); - expect(buildOfstedListBadge({ ofsted_grade: 4, ofsted_grade_date: '2020-03-01' }).label).toBe('Inadequate · 2020'); + expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: '2022-05-01' })!.label).toBe('Good · 2022'); + expect(buildOfstedListBadge({ ofsted_grade: 3, ofsted_grade_date: '2021-01-01' })!.label).toBe('Req. Improvement · 2021'); + expect(buildOfstedListBadge({ ofsted_grade: 4, ofsted_grade_date: '2020-03-01' })!.label).toBe('Inadequate · 2020'); }); it('prints a grade without a year when its date is missing', () => { const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: null, ofsted_date: '2025-01-01' }); - expect(badge.label).toBe('Good'); - expect(badge.cssClass).toBe('ofsted2'); + expect(badge!.label).toBe('Good'); + expect(badge!.cssClass).toBe('ofsted2'); }); it('dates a grade by the inspection that awarded it, not the latest visit', () => { // Washwood Heath: Good from a March 2020 graded inspection; latest visit // an ungraded one in May 2025 (audit M1). const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: '2020-03-03', ofsted_date: '2025-05-21' }); - expect(badge.label).toBe('Good · 2020'); + expect(badge!.label).toBe('Good · 2020'); }); it('shows Inspected for a latest inspection that gave no grade (Rabbsfarm)', () => { // Audit C1: this used to read "Good · 2025", from a 2020 ungraded visit. const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_grade_date: null, ofsted_date: '2025-06-17' }); - expect(badge.label).toBe('Inspected · 2025'); - expect(badge.cssClass).toBe('ofstedInspected'); + expect(badge!.label).toBe('Inspected · 2025'); + expect(badge!.cssClass).toBe('ofstedInspected'); }); it('returns a Report Card badge when ofsted_rc_date is present', () => { const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_rc_date: '2026-02-03' }); - expect(badge.label).toBe('Report Card · 2026'); - expect(badge.cssClass).toBe('ofstedRc'); + expect(badge!.label).toBe('Report Card · 2026'); + expect(badge!.cssClass).toBe('ofstedRc'); }); it('a report card wins over a carried-forward legacy grade', () => { @@ -194,34 +194,46 @@ describe('buildOfstedListBadge', () => { ofsted_framework: 'Schools - S5', ofsted_rc_date: '2026-02-03', }); - expect(badge.label).toBe('Report Card · 2026'); - expect(badge.cssClass).toBe('ofstedRc'); + expect(badge!.label).toBe('Report Card · 2026'); + expect(badge!.cssClass).toBe('ofstedRc'); }); it('returns an "Inspected" badge for an OEIF inspection with no overall grade (post-Sept-2024)', () => { // Inspected after Sept 2024: inspection on record (date + framework) but // Ofsted no longer issues an overall grade. Must NOT read as "Not yet inspected". const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: '2024-11-01', ofsted_framework: 'OEIF' }); - expect(badge.label).toBe('Inspected · 2024'); - expect(badge.cssClass).toBe('ofstedInspected'); + expect(badge!.label).toBe('Inspected · 2024'); + expect(badge!.cssClass).toBe('ofstedInspected'); }); it('returns an "Inspected" badge without a year when the grade is missing and date is absent but a record exists', () => { const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_framework: 'OEIF' }); - expect(badge.label).toBe('Inspected'); - expect(badge.cssClass).toBe('ofstedInspected'); + expect(badge!.label).toBe('Inspected'); + expect(badge!.cssClass).toBe('ofstedInspected'); }); - it('returns pending badge when no grade and no inspection on record', () => { - const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_framework: null }); - expect(badge.label).toBe('Not yet inspected'); - expect(badge.cssClass).toBe('ofstedPending'); + it('says "Not yet inspected" only when Ofsted lists the school uninspected (audit H4)', () => { + const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_listed: true }); + expect(badge).toEqual({ label: 'Not yet inspected', cssClass: 'ofstedPending' }); }); - it('returns pending badge when all fields are undefined', () => { - const badge = buildOfstedListBadge({}); - expect(badge.label).toBe('Not yet inspected'); - expect(badge.cssClass).toBe('ofstedPending'); + it('names ISI for a school GIAS says ISI inspects', () => { + expect(buildOfstedListBadge({ ofsted_date: null, inspectorate: 'ISI', ofsted_listed: false })) + .toEqual({ label: 'Inspected by ISI', cssClass: 'ofstedPending' }); + }); + + it('an Ofsted record wins over ISI', () => { + expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: '2022-03-01', inspectorate: 'ISI' })!.label) + .toBe('Good · 2022'); + }); + + it('gives no badge without any inspection data', () => { + expect(buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_framework: null })).toBeNull(); + expect(buildOfstedListBadge({ ofsted_listed: null, inspectorate: 'Not applicable' })).toBeNull(); + }); + + it('gives no badge when all fields are undefined (audit H4)', () => { + expect(buildOfstedListBadge({})).toBeNull(); }); }); diff --git a/nextjs-app/components/LeafletMapInner.tsx b/nextjs-app/components/LeafletMapInner.tsx index ded0513..c8a36a7 100644 --- a/nextjs-app/components/LeafletMapInner.tsx +++ b/nextjs-app/components/LeafletMapInner.tsx @@ -104,7 +104,7 @@ function cardHtml(school: School, ctx: CardContext): string { ? `${metric ? ' · ' : ''}${school.total_pupils.toLocaleString('en-GB')} pupils` : ''; return `
-
${escapeHtml(badge.label)}${type}
+
${badge ? `${escapeHtml(badge.label)}` : ''}${type}
${metric || pupils ? `
${metric}${pupils}
` : ''}
View diff --git a/nextjs-app/components/SchoolRow.tsx b/nextjs-app/components/SchoolRow.tsx index f250975..578b90c 100644 --- a/nextjs-app/components/SchoolRow.tsx +++ b/nextjs-app/components/SchoolRow.tsx @@ -66,9 +66,11 @@ export function SchoolRow({ {school.school_name} - - {ofstedBadge.label} - + {ofstedBadge && ( + + {ofstedBadge.label} + + )}
{/* Line 2: Context tags */} diff --git a/nextjs-app/components/SecondarySchoolRow.tsx b/nextjs-app/components/SecondarySchoolRow.tsx index bc3f48e..da5fa8f 100644 --- a/nextjs-app/components/SecondarySchoolRow.tsx +++ b/nextjs-app/components/SecondarySchoolRow.tsx @@ -67,9 +67,11 @@ export function SecondarySchoolRow({ {school.school_name} - - {ofstedBadge.label} - + {ofstedBadge && ( + + {ofstedBadge.label} + + )}
{/* Line 2: Context tags */} diff --git a/nextjs-app/lib/ofstedStatus.ts b/nextjs-app/lib/ofstedStatus.ts index f9164a5..bdf02b8 100644 --- a/nextjs-app/lib/ofstedStatus.ts +++ b/nextjs-app/lib/ofstedStatus.ts @@ -17,8 +17,22 @@ const VISIT_KIND: Record = { report_card: 'Report card inspection', graded: 'Graded inspection', ungraded: 'Ungraded inspection', + short: 'Short inspection', }; +/** fact_ofsted_latest.framework for Ofsted's further education and skills records (audit H4). */ +export const FE_FRAMEWORK = 'Further education and skills'; + +/** + * A report card is in force. Checked by its date as well as its area labels: + * an FE report card carries a safeguarding result and no school areas. + */ +export function hasReportCard( + ofsted: Pick, +): boolean { + return !!ofsted.rc_inspection_date || Object.keys(ofsted.report_card ?? {}).length > 0; +} + /** "Graded inspection, 6 July 2016" or "Confirmed at an ungraded inspection, 14 March 2023". */ export function gradeSourceLine(current: OfstedCurrentGrade): string { const when = formatOfstedDate(current.date); diff --git a/nextjs-app/lib/types.ts b/nextjs-app/lib/types.ts index 39ab734..d90adb7 100644 --- a/nextjs-app/lib/types.ts +++ b/nextjs-app/lib/types.ts @@ -86,6 +86,10 @@ export interface School { /** The school's latest inspection of any kind. */ ofsted_date?: string | null; ofsted_framework?: string | null; + /** The inspectorate GIAS names ("ISI", "Ofsted", "Not applicable"). Audit H4. */ + inspectorate?: string | null; + /** Whether Ofsted's own data lists the school, inspected or not. Audit H4. */ + ofsted_listed?: boolean | null; } // ============================================================================ @@ -143,7 +147,7 @@ export interface OfstedCurrentGrade { export interface OfstedLatestVisit { date: string; - kind: 'report_card' | 'graded' | 'ungraded'; + kind: 'report_card' | 'graded' | 'ungraded' | 'short'; outcome: string | null; } diff --git a/nextjs-app/lib/utils.ts b/nextjs-app/lib/utils.ts index 9d3440d..b9ae6e4 100644 --- a/nextjs-app/lib/utils.ts +++ b/nextjs-app/lib/utils.ts @@ -678,7 +678,9 @@ export interface OfstedListBadge { * or an ungraded visit whose outcome names no grade): "Inspected · YYYY", * dated by the latest visit, so a school never reads as both inspected and * "Not yet inspected" - * - No inspection on record: "Not yet inspected" in grey + * - No inspection on record (audit H4): "Inspected by ISI" when GIAS names + * ISI; "Not yet inspected" only when Ofsted's own data lists the school; + * otherwise no badge, never a claim the data cannot support. */ export function buildOfstedListBadge(school: { ofsted_grade?: 1 | 2 | 3 | 4 | null; @@ -686,7 +688,9 @@ export function buildOfstedListBadge(school: { ofsted_date?: string | null; ofsted_framework?: string | null; ofsted_rc_date?: string | null; -}): OfstedListBadge { + inspectorate?: string | null; + ofsted_listed?: boolean | null; +}): OfstedListBadge | null { // A report card wins over any carried-forward legacy grade — signalled by // ofsted_rc_date. ofsted_framework is the raw event grouping ("Schools - // S5"), never "ReportCard", so it can't detect report cards. @@ -719,7 +723,9 @@ export function buildOfstedListBadge(school: { return { label: `Inspected${visitYear ? ` · ${visitYear}` : ''}`, cssClass: 'ofstedInspected' }; } - return { label: 'Not yet inspected', cssClass: 'ofstedPending' }; + if (school.inspectorate === 'ISI') return { label: 'Inspected by ISI', cssClass: 'ofstedPending' }; + if (school.ofsted_listed) return { label: 'Not yet inspected', cssClass: 'ofstedPending' }; + return null; } // ============================================================================