fix(detail): render & correctly date Ofsted report cards #62

Merged
tudor merged 1 commits from fix/detail-report-card-render-date into main 2026-07-18 18:13:26 +00:00
3 changed files with 66 additions and 10 deletions
+27
View File
@@ -94,6 +94,33 @@ test('school detail page renders name and performance data', async ({ page }) =>
await expect(page.locator('canvas:visible').first()).toBeVisible({ timeout: 15_000 }); await expect(page.locator('canvas:visible').first()).toBeVisible({ timeout: 15_000 });
}); });
test('a report-card school shows its report card, dated to the report-card inspection', async ({ page }) => {
// Detail views detected report cards via `framework`, which the API never
// sets to "ReportCard" — so report-card schools rendered as legacy ratings
// dated to a pre-Nov-2025 inspection. Detection now keys off the report_card
// object and dates it with rc_inspection_date.
const RC_URN = 138690; // Barclay Primary — has a Nov-2025+ report card
const res = await page.request.get(`/api/schools/${RC_URN}`);
expect(res.ok()).toBeTruthy();
const ofsted = (await res.json()).ofsted;
test.skip(
!ofsted?.report_card || Object.keys(ofsted.report_card).length === 0,
'precondition: chosen URN must currently have a report card',
);
const rcYear = new Date(ofsted.rc_inspection_date).getFullYear();
const legacyYear = new Date(ofsted.inspection_date).getFullYear();
await page.goto(`/school/${RC_URN}`);
const ofstedSection = page.locator('#ofsted');
// Detection fixed: rendered as a Report Card, not a legacy "Ofsted Rating".
await expect(ofstedSection.getByText('Ofsted Report Card')).toBeVisible({ timeout: 15_000 });
// Dating fixed: dated to the report-card inspection, never the legacy one.
await expect(ofstedSection.getByText(new RegExp(`Inspected .*${rcYear}`))).toBeVisible();
if (legacyYear !== rcYear) {
await expect(ofstedSection.getByText(new RegExp(`Inspected .*${legacyYear}`))).toHaveCount(0);
}
});
test('school with no performance data still gets a working detail page', async ({ page }) => { test('school with no performance data still gets a working detail page', async ({ page }) => {
// Schools without KS2/KS4 results (special post-16 institutions, sixth-form // Schools without KS2/KS4 results (special post-16 institutions, sixth-form
// centres, PRUs) used to 500 in the API — NaN GIAS fields broke JSON // centres, PRUs) used to 500 in the API — NaN GIAS fields broke JSON
+20 -5
View File
@@ -269,9 +269,24 @@ export function SchoolDetailView({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [navItems.map(n => n.id).join(',')]); }, [navItems.map(n => n.id).join(',')]);
// A report card is identified by the presence of report-card area
// judgements, NOT by `framework` — the API sets `framework` to the raw
// event grouping (e.g. "Schools - S5") even for report-card schools, so
// the old `framework === 'ReportCard'` test never matched and report cards
// were rendered as legacy ratings dated to a pre-Nov-2025 inspection.
const isReportCard = !!(
ofsted?.report_card && Object.keys(ofsted.report_card).length > 0
);
// A report card is dated by its own inspection (rc_inspection_date); the
// legacy inspection_date belongs to an older inspection and must never
// date a report card (report cards exist only from Nov 2025).
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
// ── Ofsted: detect if all OEIF sub-grades match the overall ─────────── // ── Ofsted: detect if all OEIF sub-grades match the overall ───────────
const oeifAllSameGrade = (() => { const oeifAllSameGrade = (() => {
if (!ofsted || ofsted.framework === 'ReportCard') return false; if (!ofsted || isReportCard) return false;
const subs = [ const subs = [
ofsted.quality_of_education, ofsted.quality_of_education,
ofsted.behaviour_attitudes, ofsted.behaviour_attitudes,
@@ -507,10 +522,10 @@ export function SchoolDetailView({
{ofsted && ( {ofsted && (
<section id="ofsted" className={styles.card}> <section id="ofsted" className={styles.card}>
<h2 className={styles.sectionTitle}> <h2 className={styles.sectionTitle}>
{ofsted.framework === 'ReportCard' ? 'Ofsted Report Card' : 'Ofsted Rating'} {isReportCard ? 'Ofsted Report Card' : 'Ofsted Rating'}
{ofsted.inspection_date && ( {ofstedInspectedDate && (
<span className={styles.ofstedDate}> <span className={styles.ofstedDate}>
Inspected {new Date(ofsted.inspection_date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })} Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
</span> </span>
)} )}
<a <a
@@ -525,7 +540,7 @@ export function SchoolDetailView({
</a> </a>
</h2> </h2>
{ofsted.framework === 'ReportCard' ? ( {isReportCard ? (
/* ── New Report Card layout ── */ /* ── New Report Card layout ── */
<> <>
<p className={styles.ofstedDisclaimer}> <p className={styles.ofstedDisclaimer}>
@@ -186,9 +186,23 @@ export function SecondarySchoolDetailView({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [navItems.map(n => n.id).join(',')]); }, [navItems.map(n => n.id).join(',')]);
// A report card is identified by the presence of report-card area
// judgements, NOT by `framework` — the API sets `framework` to the raw
// event grouping (e.g. "Schools - S5") even for report-card schools, so
// the old `framework === 'ReportCard'` test never matched and report cards
// were rendered as legacy ratings dated to a pre-Nov-2025 inspection.
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).
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
// ── Ofsted: detect if all OEIF sub-grades match the overall ─────────── // ── Ofsted: detect if all OEIF sub-grades match the overall ───────────
const oeifAllSameGrade = (() => { const oeifAllSameGrade = (() => {
if (!ofsted || ofsted.framework === 'ReportCard') return false; if (!ofsted || isReportCard) return false;
const subs = [ const subs = [
ofsted.quality_of_education, ofsted.quality_of_education,
ofsted.behaviour_attitudes, ofsted.behaviour_attitudes,
@@ -332,10 +346,10 @@ export function SecondarySchoolDetailView({
{ofsted && ( {ofsted && (
<section id="ofsted" className={styles.card}> <section id="ofsted" className={styles.card}>
<h2 className={styles.sectionTitle}> <h2 className={styles.sectionTitle}>
{ofsted.framework === 'ReportCard' ? 'Ofsted Report Card' : 'Ofsted Rating'} {isReportCard ? 'Ofsted Report Card' : 'Ofsted Rating'}
{ofsted.inspection_date && ( {ofstedInspectedDate && (
<span className={styles.ofstedDate}> <span className={styles.ofstedDate}>
{' '}Inspected {new Date(ofsted.inspection_date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })} {' '}Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
</span> </span>
)} )}
<a <a
@@ -349,7 +363,7 @@ export function SecondarySchoolDetailView({
Ofsted reports Ofsted reports
</a> </a>
</h2> </h2>
{ofsted.framework === 'ReportCard' ? ( {isReportCard ? (
<> <>
<p className={styles.ofstedDisclaimer}> <p className={styles.ofstedDisclaimer}>
From November 2025, Ofsted replaced single overall grades with Report Cards rating schools across several areas. From November 2025, Ofsted replaced single overall grades with Report Cards rating schools across several areas.