fix: show the Ofsted grade still in force and the latest visit (C1/M1/M2, part 2 of 2) #184

Merged
tudor merged 9 commits from fix/ofsted-current-status-site into main 2026-10-05 21:47:35 +00:00
5 changed files with 147 additions and 69 deletions
Showing only changes of commit 9ef48da4b1 - Show all commits

No files matched your search

@@ -0,0 +1,84 @@
import { render, screen } from '@testing-library/react';
import { OfstedSection } from '@/components/school/OfstedSection';
import { ofstedLegacyAreas } from '@/lib/utils';
import type { OfstedInspection } from '@/lib/types';
const empty = {
framework: null, inspection_date: null, inspection_type: null, overall_effectiveness: null,
quality_of_education: null, behaviour_attitudes: null, personal_development: null,
leadership_management: null, early_years_provision: null, sixth_form_provision: null,
previous_overall: null, rc_safeguarding_met: null, rc_inclusion: null, rc_curriculum_teaching: null,
rc_achievement: null, rc_attendance_behaviour: null, rc_personal_development: null,
rc_leadership_governance: null, rc_early_years: null, rc_sixth_form: null, report_url: null,
report_card: {},
} as OfstedInspection;
// `page` mimics how each school page used to mount the section; the secondary
// page's branch hard-coded four areas and dropped the sixth form (audit M2).
function renderSection(o: Partial<OfstedInspection>, page: Record<string, unknown> = {}) {
const ofsted = { ...empty, ...o } as OfstedInspection;
render(
<OfstedSection
{...page}
ofsted={ofsted}
urn={1}
isReportCard={false}
ofstedInspectedDate={ofsted.latest_visit?.date ?? null}
oeifAllSameGrade={false}
oeifAreas={ofstedLegacyAreas(ofsted)}
/>,
);
}
it('shows no overall grade, not an older one, after a no-grade inspection (Rabbsfarm)', () => {
renderSection({
inspection_date: '2025-06-17', quality_of_education: 3, behaviour_attitudes: 3,
personal_development: 2, leadership_management: 3, early_years_provision: 2,
current_grade: null, latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null },
});
expect(screen.getByText(/Inspected 17 June 2025/)).toBeInTheDocument();
expect(screen.getByText('No overall grade')).toBeInTheDocument();
expect(screen.queryByText('Good', { selector: 'span' })).not.toBeInTheDocument();
expect(screen.queryByText('Not rated')).not.toBeInTheDocument();
expect(screen.getAllByText('Requires Improvement')).toHaveLength(3);
});
it('keeps the sixth-form judgement when there is no overall grade (audit M2)', () => {
renderSection({
inspection_date: '2025-04-01', quality_of_education: 1, behaviour_attitudes: 1,
personal_development: 1, leadership_management: 1, sixth_form_provision: 2,
current_grade: null, latest_visit: { date: '2025-04-01', kind: 'graded', outcome: null },
}, { variant: 'secondary' });
expect(screen.getByText(/sixth form/i)).toBeInTheDocument();
expect(screen.getByText('Good')).toBeInTheDocument();
});
it('dates a grade by its source and prints a later visit separately (Washwood Heath)', () => {
renderSection({
inspection_date: '2020-03-03', overall_effectiveness: 2, quality_of_education: 2,
current_grade: { grade: 2, date: '2020-03-03', basis: 'graded' },
latest_visit: { date: '2025-05-21', kind: 'ungraded', outcome: 'Standards maintained' },
});
expect(screen.getByText(/Inspected 21 May 2025/)).toBeInTheDocument();
expect(screen.getByText('Graded inspection, 3 March 2020')).toBeInTheDocument();
expect(screen.getByText(/Latest visit: Ungraded inspection, 21 May 2025: Standards maintained/)).toBeInTheDocument();
});
it('says a grade was confirmed at an ungraded visit (Robins Lane)', () => {
renderSection({
inspection_date: '2020-01-07', overall_effectiveness: 2,
current_grade: { grade: 2, date: '2024-07-18', basis: 'confirmed' },
latest_visit: { date: '2024-07-18', kind: 'ungraded', outcome: 'School remains Good' },
});
expect(screen.getByText('Confirmed at an ungraded inspection, 18 July 2024')).toBeInTheDocument();
expect(screen.queryByText(/Latest visit/)).not.toBeInTheDocument();
});
it('prints an ungraded outcome when there is no grade (Oakgrove)', () => {
renderSection({
current_grade: null,
latest_visit: { date: '2024-11-13', kind: 'ungraded', outcome: 'Standards maintained' },
});
expect(screen.getByText('No overall grade')).toBeInTheDocument();
expect(screen.getByText(/Ungraded inspection, 13 November 2024: Standards maintained/)).toBeInTheDocument();
});
@@ -191,7 +191,8 @@ export const primaryFixture = {
personal_development: 2,
leadership_management: 2,
previous_overall: 3,
grade_source: 'graded',
current_grade: { grade: 2, date: '2023-05-17', basis: 'graded' },
latest_visit: { date: '2023-05-17', kind: 'graded', outcome: null },
}),
census,
admissions: makeAdmissions({ year: 2024 }),
@@ -295,7 +296,8 @@ export const allThroughFixture = {
behaviour_attitudes: 1,
personal_development: 1,
leadership_management: 1,
grade_source: 'graded',
current_grade: { grade: 1, date: '2022-10-04', basis: 'graded' },
latest_visit: { date: '2022-10-04', kind: 'graded', outcome: null },
}),
census,
admissions: makeAdmissions({ year: 2024, school_phase: 'Secondary' }),
+52 -61
View File
@@ -1,16 +1,18 @@
/**
* OfstedSection — shared between the primary and secondary detail pages.
*
* The two versions were ~80% identical, but that figure masked a real fork in
* the no-overall-grade case: the primary page shows a "Not rated" badge, while
* the secondary page shows a four-area OEIF panel. The disclaimer copy also
* differs slightly. Both are preserved exactly via the `variant` prop rather
* than reconciled, because this refactor must not change either page. Merging
* them is a follow-up decision for a human, not a side effect of a move.
* The headline is the school's current Ofsted status (backend:
* fact_ofsted_latest): the report card; or the overall grade still in force,
* with the inspection that awarded or confirmed it; or "No overall grade".
* A later visit that is not the grade's source gets its own line. Both pages
* render the same branches, so neither drops an area judgement (audit M2).
* Rule: docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md
*
* Server component.
*/
import { ofstedDisplay } from '@/lib/compareLogic';
import { formatOfstedDate, gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus';
import type { OfstedInspection } from '@/lib/types';
import { Section, sectionStyles as styles } from './sectionShared';
@@ -40,14 +42,13 @@ export interface OfstedSectionProps {
ofstedInspectedDate: string | null;
oeifAllSameGrade: boolean;
oeifAreas: { label: string; value: number }[];
variant?: 'primary' | 'secondary';
}
export function OfstedSection({
ofsted, urn, isReportCard, ofstedInspectedDate,
oeifAllSameGrade, oeifAreas, variant = 'primary',
oeifAllSameGrade, oeifAreas,
}: OfstedSectionProps) {
const isSecondary = variant === 'secondary';
const display = ofstedDisplay(ofsted);
return (
<Section id="ofsted">
@@ -55,7 +56,7 @@ export function OfstedSection({
{isReportCard ? 'Ofsted Report Card' : 'Ofsted Rating'}
{ofstedInspectedDate && (
<span className={styles.ofstedDate}>
{isSecondary && ' '}Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
</span>
)}
<a
@@ -98,65 +99,55 @@ export function OfstedSection({
})}
</div>
</>
) : (!isSecondary || ofsted.overall_effectiveness) ? (
/* ── Old OEIF layout ── */
) : (
<>
<div className={styles.ofstedHeader}>
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'}
</span>
{ofsted.previous_overall != null &&
ofsted.previous_overall !== ofsted.overall_effectiveness && (
<span className={styles.ofstedPrevious}>
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
</span>
{display.kind === 'graded' || display.kind === 'confirmed' ? (
<>
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${display.grade}`]}`}>
{display.gradeLabel}
</span>
{ofsted.previous_overall != null && ofsted.previous_overall !== display.grade && (
<span className={styles.ofstedPrevious}>
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
</span>
)}
</>
) : (
<span className={styles.ofstedGrade}>No overall grade</span>
)}
</div>
<p className={styles.ofstedDisclaimer}>
{ofsted.grade_source === 'ungraded_carried_forward'
? 'This overall grade is carried forward from an earlier inspection. Ofsted has since visited without issuing a new overall grade. From September 2024, Ofsted no longer makes an overall effectiveness judgement.'
: isSecondary
? 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections.'
: 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections of state-funded schools.'}
{ofsted.current_grade
? gradeSourceLine(ofsted.current_grade)
: 'Ofsted stopped giving overall grades in September 2024.'}
</p>
{oeifAllSameGrade ? (
<p className={styles.ofstedAllSame}>
Rated <strong>{OFSTED_LABELS[ofsted.overall_effectiveness!]}</strong> across all inspected areas: Quality of Teaching, Behaviour, Pupils&apos; Development and Leadership.
</p>
) : (
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
{oeifAreas.map(({ label, value }) => (
<div key={label} className={styles.metricCard}>
<div className={styles.metricLabel}>{label}</div>
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
{OFSTED_LABELS[value]}
</div>
</div>
))}
</div>
{showLatestVisitLine(ofsted) && ofsted.latest_visit && (
<p className={styles.ofstedDisclaimer}>Latest visit: {latestVisitLine(ofsted.latest_visit)}</p>
)}
</>
) : (
/* ── Secondary only: inspected since Sept 2024, no overall grade ── */
<>
<p className={styles.sectionSubtitle}>
From September 2024, Ofsted no longer gives a single overall grade.
</p>
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
{[
{ label: 'Quality of Education', value: ofsted.quality_of_education },
{ label: 'Behaviour & Attitudes', value: ofsted.behaviour_attitudes },
{ label: 'Personal Development', value: ofsted.personal_development },
{ label: 'Leadership & Management', value: ofsted.leadership_management },
].filter(({ value }) => value != null).map(({ label, value }) => (
<div key={label} className={styles.metricCard}>
<div className={styles.metricLabel}>{label}</div>
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
{OFSTED_LABELS[value!]}
</div>
{oeifAllSameGrade && (display.kind === 'graded' || display.kind === 'confirmed') ? (
<p className={styles.ofstedAllSame}>
Rated <strong>{display.gradeLabel}</strong> across all inspected areas: Quality of Teaching, Behaviour, Pupils&apos; Development and Leadership.
</p>
) : oeifAreas.length > 0 ? (
<>
{ofsted.inspection_date && ofsted.inspection_date !== ofsted.latest_visit?.date && (
<p className={styles.ofstedDisclaimer}>
Area judgements from the graded inspection, {formatOfstedDate(ofsted.inspection_date)}.
</p>
)}
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
{oeifAreas.map(({ label, value }) => (
<div key={label} className={styles.metricCard}>
<div className={styles.metricLabel}>{label}</div>
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
{OFSTED_LABELS[value]}
</div>
</div>
))}
</div>
))}
</div>
</>
) : null}
</>
)}
</Section>
@@ -55,15 +55,17 @@ export function PrimarySchoolSections({
const secondaryAvg = nationalAvg?.secondary ?? {};
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
// Report cards are dated by their own inspection (rc_inspection_date), never
// the legacy inspection_date (report cards exist only from Nov 2025).
// 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.
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
: ofsted?.latest_visit?.date ?? null;
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
const oeifAllSameGrade =
!!ofsted &&
!isReportCard &&
ofsted.overall_effectiveness != null &&
oeifAreas.length >= 3 &&
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
@@ -77,7 +79,6 @@ export function PrimarySchoolSections({
ofstedInspectedDate={ofstedInspectedDate}
oeifAllSameGrade={oeifAllSameGrade}
oeifAreas={oeifAreas}
variant="primary"
/>
)}
@@ -63,11 +63,12 @@ export function SecondarySchoolSections({
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
: ofsted?.latest_visit?.date ?? null;
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
const oeifAllSameGrade =
!!ofsted &&
!isReportCard &&
ofsted.overall_effectiveness != null &&
oeifAreas.length >= 3 &&
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
@@ -81,7 +82,6 @@ export function SecondarySchoolSections({
ofstedInspectedDate={ofstedInspectedDate}
oeifAllSameGrade={oeifAllSameGrade}
oeifAreas={oeifAreas}
variant="secondary"
/>
)}