fix(detail): Ofsted sentinel codes, sixth form, carried-forward labels (Batch A) #64
@@ -9,6 +9,7 @@ import {
|
|||||||
isValidPostcode,
|
isValidPostcode,
|
||||||
debounce,
|
debounce,
|
||||||
buildOfstedListBadge,
|
buildOfstedListBadge,
|
||||||
|
ofstedLegacyAreas,
|
||||||
metricKind,
|
metricKind,
|
||||||
shortName,
|
shortName,
|
||||||
computeYBounds,
|
computeYBounds,
|
||||||
@@ -111,6 +112,35 @@ describe('debounce', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ofstedLegacyAreas', () => {
|
||||||
|
it('returns only real grades (1-4) and drops sentinel/null values', () => {
|
||||||
|
const areas = ofstedLegacyAreas({
|
||||||
|
quality_of_education: 1,
|
||||||
|
behaviour_attitudes: 2,
|
||||||
|
personal_development: null,
|
||||||
|
leadership_management: 9, // sentinel "not applicable" — must be dropped
|
||||||
|
early_years_provision: 9, // secondary school — not applicable
|
||||||
|
sixth_form_provision: 2,
|
||||||
|
});
|
||||||
|
expect(areas.map((a) => a.label)).toEqual([
|
||||||
|
'Quality of Teaching',
|
||||||
|
'Behaviour in School',
|
||||||
|
'Sixth Form',
|
||||||
|
]);
|
||||||
|
expect(areas.every((a) => a.value >= 1 && a.value <= 4)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes the sixth-form judgement where present (was previously dropped)', () => {
|
||||||
|
const areas = ofstedLegacyAreas({ sixth_form_provision: 1 });
|
||||||
|
expect(areas).toEqual([{ label: 'Sixth Form', value: 1 }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes Early Years for a primary school', () => {
|
||||||
|
const areas = ofstedLegacyAreas({ quality_of_education: 2, early_years_provision: 1 });
|
||||||
|
expect(areas.map((a) => a.label)).toContain('Early Years (Reception)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('buildOfstedListBadge', () => {
|
describe('buildOfstedListBadge', () => {
|
||||||
it('returns grade word + year for OEIF Outstanding', () => {
|
it('returns grade word + year for OEIF Outstanding', () => {
|
||||||
const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
|
const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import type {
|
|||||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||||
} from '@/lib/types';
|
} from '@/lib/types';
|
||||||
import {
|
import {
|
||||||
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose,
|
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose, ofstedLegacyAreas,
|
||||||
} from '@/lib/utils';
|
} from '@/lib/utils';
|
||||||
import { DeltaChip } from './DeltaChip';
|
import { DeltaChip } from './DeltaChip';
|
||||||
|
|
||||||
@@ -285,17 +285,12 @@ export function SchoolDetailView({
|
|||||||
: ofsted?.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 oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
|
||||||
if (!ofsted || isReportCard) return false;
|
const oeifAllSameGrade =
|
||||||
const subs = [
|
!!ofsted &&
|
||||||
ofsted.quality_of_education,
|
!isReportCard &&
|
||||||
ofsted.behaviour_attitudes,
|
oeifAreas.length >= 3 &&
|
||||||
ofsted.personal_development,
|
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
|
||||||
ofsted.leadership_management,
|
|
||||||
...(ofsted.early_years_provision != null ? [ofsted.early_years_provision] : []),
|
|
||||||
].filter((v): v is number => v != null);
|
|
||||||
return subs.length >= 3 && subs.every(v => v === ofsted.overall_effectiveness);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Label shown in the mobile "section" menu button — the section in view.
|
// Label shown in the mobile "section" menu button — the section in view.
|
||||||
const activeNavLabel = (navItems.find((n) => n.id === activeSection) ?? navItems[0])?.label ?? '';
|
const activeNavLabel = (navItems.find((n) => n.id === activeSection) ?? navItems[0])?.label ?? '';
|
||||||
@@ -583,7 +578,9 @@ export function SchoolDetailView({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className={styles.ofstedDisclaimer}>
|
<p className={styles.ofstedDisclaimer}>
|
||||||
From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections of state-funded schools.
|
{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.'
|
||||||
|
: 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections of state-funded schools.'}
|
||||||
</p>
|
</p>
|
||||||
{oeifAllSameGrade ? (
|
{oeifAllSameGrade ? (
|
||||||
<p className={styles.ofstedAllSame}>
|
<p className={styles.ofstedAllSame}>
|
||||||
@@ -591,15 +588,7 @@ export function SchoolDetailView({
|
|||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.metricsGrid}>
|
<div className={styles.metricsGrid}>
|
||||||
{[
|
{oeifAreas.map(({ label, value }) => (
|
||||||
{ label: 'Quality of Teaching', value: ofsted.quality_of_education },
|
|
||||||
{ label: 'Behaviour in School', value: ofsted.behaviour_attitudes },
|
|
||||||
{ label: 'Pupils\' Wider Development', value: ofsted.personal_development },
|
|
||||||
{ label: 'School Leadership', value: ofsted.leadership_management },
|
|
||||||
...(ofsted.early_years_provision != null
|
|
||||||
? [{ label: 'Early Years (Reception)', value: ofsted.early_years_provision }]
|
|
||||||
: []),
|
|
||||||
].map(({ label, value }) => value != null && (
|
|
||||||
<div key={label} className={styles.metricCard}>
|
<div key={label} className={styles.metricCard}>
|
||||||
<div className={styles.metricLabel}>{label}</div>
|
<div className={styles.metricLabel}>{label}</div>
|
||||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import type {
|
|||||||
SchoolAdmissions, SenDetail, Phonics,
|
SchoolAdmissions, SenDetail, Phonics,
|
||||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||||
} from '@/lib/types';
|
} from '@/lib/types';
|
||||||
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose } from '@/lib/utils';
|
import { formatPercentage, formatProgress, formatAcademicYear, formatAgeRange, isProposedToClose, ofstedLegacyAreas } from '@/lib/utils';
|
||||||
import { DeltaChip } from './DeltaChip';
|
import { DeltaChip } from './DeltaChip';
|
||||||
import { track, getNavigationSource } from '@/lib/analytics';
|
import { track, getNavigationSource } from '@/lib/analytics';
|
||||||
import styles from './SecondarySchoolDetailView.module.css';
|
import styles from './SecondarySchoolDetailView.module.css';
|
||||||
@@ -201,17 +201,12 @@ export function SecondarySchoolDetailView({
|
|||||||
: ofsted?.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 oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
|
||||||
if (!ofsted || isReportCard) return false;
|
const oeifAllSameGrade =
|
||||||
const subs = [
|
!!ofsted &&
|
||||||
ofsted.quality_of_education,
|
!isReportCard &&
|
||||||
ofsted.behaviour_attitudes,
|
oeifAreas.length >= 3 &&
|
||||||
ofsted.personal_development,
|
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
|
||||||
ofsted.leadership_management,
|
|
||||||
...(ofsted.early_years_provision != null ? [ofsted.early_years_provision] : []),
|
|
||||||
].filter((v): v is number => v != null);
|
|
||||||
return subs.length >= 3 && subs.every(v => v === ofsted.overall_effectiveness);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// National Attainment 8 baseline for the "Results Over Time" chart.
|
// National Attainment 8 baseline for the "Results Over Time" chart.
|
||||||
const heroAtt8Nat = secondaryAvg.attainment_8_score ?? null;
|
const heroAtt8Nat = secondaryAvg.attainment_8_score ?? null;
|
||||||
@@ -404,7 +399,9 @@ export function SecondarySchoolDetailView({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className={styles.ofstedDisclaimer}>
|
<p className={styles.ofstedDisclaimer}>
|
||||||
From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections.
|
{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.'
|
||||||
|
: 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections.'}
|
||||||
</p>
|
</p>
|
||||||
{oeifAllSameGrade ? (
|
{oeifAllSameGrade ? (
|
||||||
<p className={styles.ofstedAllSame}>
|
<p className={styles.ofstedAllSame}>
|
||||||
@@ -412,15 +409,7 @@ export function SecondarySchoolDetailView({
|
|||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.metricsGrid}>
|
<div className={styles.metricsGrid}>
|
||||||
{[
|
{oeifAreas.map(({ label, value }) => (
|
||||||
{ label: 'Quality of Teaching', value: ofsted.quality_of_education },
|
|
||||||
{ label: 'Behaviour in School', value: ofsted.behaviour_attitudes },
|
|
||||||
{ label: 'Pupils\' Wider Development', value: ofsted.personal_development },
|
|
||||||
{ label: 'School Leadership', value: ofsted.leadership_management },
|
|
||||||
...(ofsted.early_years_provision != null
|
|
||||||
? [{ label: 'Early Years (Reception)', value: ofsted.early_years_provision }]
|
|
||||||
: []),
|
|
||||||
].map(({ label, value }) => value != null && (
|
|
||||||
<div key={label} className={styles.metricCard}>
|
<div key={label} className={styles.metricCard}>
|
||||||
<div className={styles.metricLabel}>{label}</div>
|
<div className={styles.metricLabel}>{label}</div>
|
||||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||||
|
|||||||
@@ -680,6 +680,42 @@ export function buildSchoolSummary(
|
|||||||
return parts.join(', ') + '.';
|
return parts.join(', ') + '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Legacy (OEIF) sub-judgement areas ────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface OfstedLegacyArea {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The published OEIF sub-judgement areas for the legacy Ofsted layout, in
|
||||||
|
* display order. Only real grades (1–4) are returned: Ofsted's sentinel
|
||||||
|
* codes for "not applicable / no judgement" (9, and any 0/8 variants) and
|
||||||
|
* nulls are filtered out, so a cryptic "9" never renders as a rating.
|
||||||
|
* Sixth Form provision is included where a school has one — it was
|
||||||
|
* previously dropped from the detail grid entirely.
|
||||||
|
*/
|
||||||
|
export function ofstedLegacyAreas(ofsted: {
|
||||||
|
quality_of_education?: number | null;
|
||||||
|
behaviour_attitudes?: number | null;
|
||||||
|
personal_development?: number | null;
|
||||||
|
leadership_management?: number | null;
|
||||||
|
early_years_provision?: number | null;
|
||||||
|
sixth_form_provision?: number | null;
|
||||||
|
}): OfstedLegacyArea[] {
|
||||||
|
const candidates: Array<[string, number | null | undefined]> = [
|
||||||
|
['Quality of Teaching', ofsted.quality_of_education],
|
||||||
|
['Behaviour in School', ofsted.behaviour_attitudes],
|
||||||
|
["Pupils' Wider Development", ofsted.personal_development],
|
||||||
|
['School Leadership', ofsted.leadership_management],
|
||||||
|
['Early Years (Reception)', ofsted.early_years_provision],
|
||||||
|
['Sixth Form', ofsted.sixth_form_provision],
|
||||||
|
];
|
||||||
|
return candidates
|
||||||
|
.filter((c): c is [string, number] => c[1] != null && c[1] >= 1 && c[1] <= 4)
|
||||||
|
.map(([label, value]) => ({ label, value }));
|
||||||
|
}
|
||||||
|
|
||||||
// ─── List-level Ofsted badge ──────────────────────────────────────────────────
|
// ─── List-level Ofsted badge ──────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface OfstedListBadge {
|
export interface OfstedListBadge {
|
||||||
|
|||||||
Reference in New Issue
Block a user