fix(school, compare): ISI, not-yet-inspected and further-education states (H4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ab25d83b9d
commit
9feb37b974
12 files changed
+143
-16
No files matched your search
@@ -69,6 +69,18 @@ const data: Record<string, ComparisonData> = {
|
||||
};
|
||||
|
||||
describe('CompareOfsted', () => {
|
||||
it('words a school without an Ofsted record from the evidence (audit H4)', () => {
|
||||
const isi = { ...school(4, 'ISI School'), inspectorate: 'ISI' } as School;
|
||||
const listed = { ...school(5, 'New Academy'), ofsted_listed: true } as School;
|
||||
render(<CompareOfsted schools={[isi, listed]} data={{
|
||||
'4': { school_info: isi, yearly_data: [] } as unknown as ComparisonData,
|
||||
'5': { school_info: listed, yearly_data: [] } as unknown as ComparisonData,
|
||||
}} />);
|
||||
expect(screen.getAllByText('Inspected by ISI').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('Not yet inspected').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText(/No inspection outcome in our dataset/)).toBeNull();
|
||||
});
|
||||
|
||||
it('renders the three regimes without inventing an overall grade for report cards', () => {
|
||||
render(<CompareOfsted schools={schools} data={data} />);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { InspectionNotice } from '@/components/school/InspectionNotice';
|
||||
|
||||
it('names ISI and links its reports for an ISI school (audit H4)', () => {
|
||||
render(<InspectionNotice school={{ urn: 136083, inspectorate: 'ISI', ofsted_listed: false }} />);
|
||||
expect(screen.getByText('Inspected by the Independent Schools Inspectorate (ISI)')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /ISI reports/ })).toHaveAttribute('href', 'https://www.isi.net/reports/');
|
||||
});
|
||||
|
||||
it('says "Not yet inspected" for a school Ofsted lists without an inspection', () => {
|
||||
render(<InspectionNotice school={{ urn: 150031, inspectorate: 'Not applicable', ofsted_listed: true }} />);
|
||||
expect(screen.getByText('Not yet inspected')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /Ofsted reports/ })).toHaveAttribute(
|
||||
'href', 'https://reports.ofsted.gov.uk/inspection-reports/find-inspection-report/provider/ELS/150031');
|
||||
});
|
||||
|
||||
it('renders nothing without inspection data, including an unknown listing', () => {
|
||||
const { container } = render(<InspectionNotice school={{ urn: 1, inspectorate: null, ofsted_listed: null }} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
@@ -82,3 +82,14 @@ it('prints an ungraded outcome when there is no grade (Oakgrove)', () => {
|
||||
expect(screen.getByText('No overall grade')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Ungraded inspection, 13 November 2024: Standards maintained/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('labels a further education record and prints its short inspection (audit H4)', () => {
|
||||
renderSection({
|
||||
framework: 'Further education and skills',
|
||||
current_grade: { grade: 2, date: '2019-03-05', basis: 'graded' },
|
||||
latest_visit: { date: '2023-02-01', kind: 'short', outcome: null },
|
||||
});
|
||||
expect(screen.getByText('Further education and skills')).toBeInTheDocument();
|
||||
expect(screen.getByText('Good')).toBeInTheDocument();
|
||||
expect(screen.getByText('Latest visit: Short inspection, 1 February 2023')).toBeInTheDocument();
|
||||
});
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
summariseAdmissions,
|
||||
summariseReportCard,
|
||||
verdict,
|
||||
noInspectionLabel,
|
||||
} from '@/lib/compareLogic';
|
||||
import type { OfstedInspection, SchoolAdmissions } from '@/lib/types';
|
||||
|
||||
@@ -359,3 +360,14 @@ describe('basket limits per comparison group', () => {
|
||||
expect(kept).toEqual([0, 1, 2, 3, 4, 7, 8, 9]);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows an FE report card with only a safeguarding result as a report card (audit H4)', () => {
|
||||
const display = ofstedDisplay(ofsted({ rc_inspection_date: '2026-05-12', rc_safeguarding_met: true, report_card: {} }));
|
||||
expect(display.kind).toBe('report_card');
|
||||
});
|
||||
|
||||
it('words the no-record compare cell from the evidence', () => {
|
||||
expect(noInspectionLabel({ inspectorate: 'ISI' })).toBe('Inspected by ISI');
|
||||
expect(noInspectionLabel({ ofsted_listed: true })).toBe('Not yet inspected');
|
||||
expect(noInspectionLabel({ ofsted_listed: null })).toBe('No inspection in our dataset');
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
summariseAdmissions,
|
||||
verdict,
|
||||
type ReportCardSummary,
|
||||
noInspectionLabel,
|
||||
} from '@/lib/compareLogic';
|
||||
import type { Benchmarks, ComparisonData, NationalAverages, School } from '@/lib/types';
|
||||
import { Cell, Chip, Measure, Section, SectionGrid, sectionStyles as s } from './sectionShared';
|
||||
@@ -91,7 +92,7 @@ export function CompareAtAGlance({
|
||||
No overall grade
|
||||
</span>
|
||||
)}
|
||||
{display.kind === 'none' && <span className={s.small}>No inspection in our dataset</span>}
|
||||
{display.kind === 'none' && <span className={s.small}>{noInspectionLabel(data[String(school.urn)]?.school_info ?? school)}</span>}
|
||||
</Cell>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ofstedDisplay,
|
||||
rcAreaLabel,
|
||||
type OfstedDisplay,
|
||||
noInspectionLabel,
|
||||
} from '@/lib/compareLogic';
|
||||
import { gradeSourceLine } from '@/lib/ofstedStatus';
|
||||
import type { ComparisonData, OfstedCurrentGrade, OfstedInspection, School } from '@/lib/types';
|
||||
@@ -41,9 +42,11 @@ function yearsSince(iso: string | null): number | null {
|
||||
return (Date.now() - d.getTime()) / (365.25 * 24 * 3600 * 1000);
|
||||
}
|
||||
|
||||
function ResultCell({ display, current }: { display: OfstedDisplay; current: OfstedCurrentGrade | null }) {
|
||||
function ResultCell({ display, current, school }: {
|
||||
display: OfstedDisplay; current: OfstedCurrentGrade | null; school: School;
|
||||
}) {
|
||||
if (display.kind === 'none') {
|
||||
return <span className={s.small}>No inspection outcome in our dataset</span>;
|
||||
return <span className={s.small}>{noInspectionLabel(school)}</span>;
|
||||
}
|
||||
if (display.kind === 'report_card') {
|
||||
return (
|
||||
@@ -175,7 +178,7 @@ export function CompareOfsted({
|
||||
<Measure label="Result">
|
||||
{schools.map((school, i) => (
|
||||
<Cell key={school.urn} school={school} index={i}>
|
||||
<ResultCell display={displays[i]} current={data[String(school.urn)]?.ofsted?.current_grade ?? null} />
|
||||
<ResultCell display={displays[i]} current={data[String(school.urn)]?.ofsted?.current_grade ?? null} school={data[String(school.urn)]?.school_info ?? school} />
|
||||
</Cell>
|
||||
))}
|
||||
</Measure>
|
||||
@@ -213,7 +216,7 @@ export function CompareOfsted({
|
||||
schoolName={school.school_name}
|
||||
/>
|
||||
) : (
|
||||
<span className={s.small}>No inspection in our dataset</span>
|
||||
<span className={s.small}>{noInspectionLabel(data[String(school.urn)]?.school_info ?? school)}</span>
|
||||
)}
|
||||
</Cell>
|
||||
);
|
||||
@@ -228,7 +231,7 @@ export function CompareOfsted({
|
||||
{schools.map((school, i) => {
|
||||
const url =
|
||||
data[String(school.urn)]?.ofsted?.ofsted_page_url ??
|
||||
`https://reports.ofsted.gov.uk/provider/21/${school.urn}`;
|
||||
`https://reports.ofsted.gov.uk/inspection-reports/find-inspection-report/provider/ELS/${school.urn}`;
|
||||
return (
|
||||
<Cell key={school.urn} school={school} index={i}>
|
||||
{/* Short visible label to save space on mobile (the coloured
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* InspectionNotice — the school page's inspection section when there is no
|
||||
* Ofsted inspection record (audit H4): the inspectorate GIAS names (ISI), or
|
||||
* Ofsted's own "not yet inspected" when its data lists the school. Nothing
|
||||
* otherwise: no claim the data cannot support. Server component.
|
||||
*/
|
||||
|
||||
import type { School } from '@/lib/types';
|
||||
import { Section, sectionStyles as styles } from './sectionShared';
|
||||
|
||||
const OFSTED_REPORTS = 'https://reports.ofsted.gov.uk/inspection-reports/find-inspection-report/provider/ELS/';
|
||||
const ISI_REPORTS = 'https://www.isi.net/reports/';
|
||||
|
||||
export function InspectionNotice({ school }: { school: Pick<School, 'urn' | 'inspectorate' | 'ofsted_listed'> }) {
|
||||
if (school.inspectorate === 'ISI') {
|
||||
return (
|
||||
<Section id="ofsted">
|
||||
<h2 className={styles.sectionTitle}>
|
||||
Inspection
|
||||
<a href={ISI_REPORTS} target="_blank" rel="noopener noreferrer" className={styles.ofstedReportLink}
|
||||
data-umami-event="external_link_clicked" data-umami-event-target="isi">
|
||||
ISI reports ↗
|
||||
</a>
|
||||
</h2>
|
||||
<p className={styles.ofstedDisclaimer}>Inspected by the Independent Schools Inspectorate (ISI)</p>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
if (school.ofsted_listed) {
|
||||
return (
|
||||
<Section id="ofsted">
|
||||
<h2 className={styles.sectionTitle}>
|
||||
Ofsted Rating
|
||||
<a href={`${OFSTED_REPORTS}${school.urn}`} target="_blank" rel="noopener noreferrer"
|
||||
className={styles.ofstedReportLink} data-umami-event="external_link_clicked" data-umami-event-target="ofsted">
|
||||
Ofsted reports ↗
|
||||
</a>
|
||||
</h2>
|
||||
<div className={styles.ofstedHeader}>
|
||||
<span className={styles.ofstedGrade}>Not yet inspected</span>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import { ofstedDisplay } from '@/lib/compareLogic';
|
||||
import { formatOfstedDate, gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus';
|
||||
import { FE_FRAMEWORK, formatOfstedDate, gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus';
|
||||
import type { OfstedInspection } from '@/lib/types';
|
||||
import { Section, sectionStyles as styles } from './sectionShared';
|
||||
|
||||
@@ -71,6 +71,10 @@ export function OfstedSection({
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{ofsted.framework === FE_FRAMEWORK && (
|
||||
<p className={styles.ofstedDisclaimer}>{FE_FRAMEWORK}</p>
|
||||
)}
|
||||
|
||||
{isReportCard ? (
|
||||
/* ── New Report Card layout ── */
|
||||
<>
|
||||
|
||||
@@ -19,6 +19,8 @@ import type {
|
||||
import { ofstedLegacyAreas } from '@/lib/utils';
|
||||
import type { SchoolFlags } from '@/lib/schoolSections';
|
||||
import { OfstedSection } from './OfstedSection';
|
||||
import { InspectionNotice } from './InspectionNotice';
|
||||
import { hasReportCard } from '@/lib/ofstedStatus';
|
||||
import { ResultsSection } from './ResultsSection';
|
||||
import { AdmissionsSection } from './AdmissionsSection';
|
||||
import { DistanceSection } from './DistanceSection';
|
||||
@@ -54,7 +56,8 @@ export function PrimarySchoolSections({
|
||||
const primaryAvg = nationalAvg?.primary ?? {};
|
||||
const secondaryAvg = nationalAvg?.secondary ?? {};
|
||||
|
||||
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
|
||||
// By date as well as area labels: an FE report card has only a safeguarding result (audit H4).
|
||||
const isReportCard = !!ofsted && hasReportCard(ofsted);
|
||||
// Report cards are dated by their own inspection (rc_inspection_date);
|
||||
// anything else by the school's latest visit, never the older inspection a
|
||||
// grade may come from.
|
||||
@@ -71,7 +74,7 @@ export function PrimarySchoolSections({
|
||||
|
||||
return (
|
||||
<>
|
||||
{ofsted && (
|
||||
{ofsted ? (
|
||||
<OfstedSection
|
||||
ofsted={ofsted}
|
||||
urn={schoolInfo.urn}
|
||||
@@ -80,7 +83,7 @@ export function PrimarySchoolSections({
|
||||
oeifAllSameGrade={oeifAllSameGrade}
|
||||
oeifAreas={oeifAreas}
|
||||
/>
|
||||
)}
|
||||
) : <InspectionNotice school={schoolInfo} />}
|
||||
|
||||
{flags.hasAnyResults && flags.latestResults && (
|
||||
<ResultsSection
|
||||
|
||||
@@ -19,6 +19,8 @@ import type {
|
||||
import { ofstedLegacyAreas } from '@/lib/utils';
|
||||
import type { SecondaryFlags } from '@/lib/schoolSections';
|
||||
import { OfstedSection } from './OfstedSection';
|
||||
import { InspectionNotice } from './InspectionNotice';
|
||||
import { hasReportCard } from '@/lib/ofstedStatus';
|
||||
import { GcseSection } from './GcseSection';
|
||||
import { DestinationsSection } from './DestinationsSection';
|
||||
import { Post16DestinationsSection } from './Post16DestinationsSection';
|
||||
@@ -60,7 +62,8 @@ export function SecondarySchoolSections({
|
||||
}: SecondarySchoolSectionsProps) {
|
||||
const secondaryAvg = nationalAvg?.secondary ?? {};
|
||||
|
||||
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
|
||||
// By date as well as area labels: an FE report card has only a safeguarding result (audit H4).
|
||||
const isReportCard = !!ofsted && hasReportCard(ofsted);
|
||||
const ofstedInspectedDate = isReportCard
|
||||
? ofsted?.rc_inspection_date ?? null
|
||||
: ofsted?.latest_visit?.date ?? null;
|
||||
@@ -74,7 +77,7 @@ export function SecondarySchoolSections({
|
||||
|
||||
return (
|
||||
<div className={styles.secondaryScope}>
|
||||
{ofsted && (
|
||||
{ofsted ? (
|
||||
<OfstedSection
|
||||
ofsted={ofsted}
|
||||
urn={schoolInfo.urn}
|
||||
@@ -83,7 +86,7 @@ export function SecondarySchoolSections({
|
||||
oeifAllSameGrade={oeifAllSameGrade}
|
||||
oeifAreas={oeifAreas}
|
||||
/>
|
||||
)}
|
||||
) : <InspectionNotice school={schoolInfo} />}
|
||||
|
||||
{flags.hasResults && flags.latestResults && (
|
||||
<GcseSection
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* methodology instead of thresholding point estimates.
|
||||
*/
|
||||
|
||||
import type { OfstedInspection, SchoolAdmissions } from './types';
|
||||
import { hasReportCard } from './ofstedStatus';
|
||||
import type { OfstedInspection, School, SchoolAdmissions } from './types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Verdicts against an anchor (England average or state-school benchmark)
|
||||
@@ -104,6 +105,16 @@ export type OfstedDisplay =
|
||||
| { kind: 'no_overall_grade' }
|
||||
| { kind: 'report_card'; summary: ReportCardSummary };
|
||||
|
||||
/**
|
||||
* The compare cell when a school has no Ofsted record (audit H4): ISI per GIAS,
|
||||
* Ofsted's own "not yet inspected", or plainly that we hold nothing.
|
||||
*/
|
||||
export function noInspectionLabel(school: Pick<School, 'inspectorate' | 'ofsted_listed'>): string {
|
||||
if (school.inspectorate === 'ISI') return 'Inspected by ISI';
|
||||
if (school.ofsted_listed) return 'Not yet inspected';
|
||||
return 'No inspection in our dataset';
|
||||
}
|
||||
|
||||
export function ofstedDisplay(
|
||||
ofsted: OfstedInspection | null | undefined,
|
||||
): OfstedDisplay {
|
||||
@@ -111,7 +122,7 @@ export function ofstedDisplay(
|
||||
|
||||
// A report card is the newest inspection format; when present it wins —
|
||||
// never derive or prefer an overall grade alongside it.
|
||||
if (ofsted.report_card && Object.keys(ofsted.report_card).length > 0) {
|
||||
if (hasReportCard(ofsted)) {
|
||||
return { kind: 'report_card', summary: summariseReportCard(ofsted) };
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ export interface School {
|
||||
// ============================================================================
|
||||
|
||||
export interface OfstedInspection {
|
||||
framework: 'OEIF' | 'ReportCard' | null;
|
||||
/** Ofsted's event grouping (e.g. "Schools - S5"), or "Further education and skills" for FE records (audit H4). */
|
||||
framework: string | null;
|
||||
inspection_date: string | null;
|
||||
/** Start date of the report-card inspection itself (Nov 2025+); null otherwise. */
|
||||
rc_inspection_date?: string | null;
|
||||
|
||||
Reference in new issue
Block a user