Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17bd4d5a5e | ||
|
|
452ec77449 | ||
|
|
aa87fa917d | ||
|
|
fdda52ff0a |
@@ -9,6 +9,7 @@ import {
|
||||
isValidPostcode,
|
||||
debounce,
|
||||
buildOfstedListBadge,
|
||||
ofstedLegacyAreas,
|
||||
metricKind,
|
||||
shortName,
|
||||
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', () => {
|
||||
it('returns grade word + year for OEIF Outstanding', () => {
|
||||
const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Line } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import '@/lib/chartSetup';
|
||||
import { formatAcademicYear } from '@/lib/utils';
|
||||
import { fillAcademicYears } from '@/lib/compareChartData';
|
||||
import type { SchoolAdmissions } from '@/lib/types';
|
||||
import styles from './AdmissionsTrendChart.module.css';
|
||||
|
||||
@@ -17,13 +18,22 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
|
||||
const pts = history.filter((h) => h.first_preference_offer_pct != null);
|
||||
if (pts.length < 2) return null;
|
||||
|
||||
const labels = pts.map((p) => formatAcademicYear(p.year));
|
||||
const values = pts.map((p) => p.first_preference_offer_pct as number);
|
||||
const lastIdx = pts.length - 1;
|
||||
// Gap-honest axis: every academic year between the first and last data point
|
||||
// appears, so a missing admissions year renders as a real gap (spanGaps:false)
|
||||
// rather than compressing time between distant years.
|
||||
const axisYears = fillAcademicYears(pts.map((p) => p.year));
|
||||
const byYear = new Map(pts.map((p) => [p.year, p.first_preference_offer_pct as number]));
|
||||
const labels = axisYears.map(formatAcademicYear);
|
||||
const values: (number | null)[] = axisYears.map((y) => byYear.get(y) ?? null);
|
||||
const present = values
|
||||
.map((v, i) => (v != null ? i : -1))
|
||||
.filter((i) => i >= 0);
|
||||
const lastIdx = present[present.length - 1];
|
||||
|
||||
// Auto-scale with headroom so variation is visible, clamped to 0–100.
|
||||
const lo = Math.min(...values);
|
||||
const hi = Math.max(...values);
|
||||
const numeric = values.filter((v): v is number => v != null);
|
||||
const lo = Math.min(...numeric);
|
||||
const hi = Math.max(...numeric);
|
||||
const padded = Math.max(5, Math.round((hi - lo) * 0.25));
|
||||
const yMin = Math.max(0, Math.floor((lo - padded) / 5) * 5);
|
||||
const yMax = Math.min(100, Math.ceil((hi + padded) / 5) * 5);
|
||||
@@ -69,15 +79,16 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
|
||||
label: 'First-choice offer rate',
|
||||
data: values,
|
||||
clip: false as const,
|
||||
spanGaps: false,
|
||||
borderColor: '#e07256',
|
||||
backgroundColor: 'rgba(224,114,86,0.10)',
|
||||
borderWidth: 2.5,
|
||||
tension: 0.3,
|
||||
fill: true,
|
||||
pointRadius: pts.map((_, i) => (i === lastIdx ? 5 : 3)),
|
||||
pointRadius: values.map((_, i) => (i === lastIdx ? 5 : 3)),
|
||||
pointBackgroundColor: '#e07256',
|
||||
pointBorderColor: '#fff',
|
||||
pointBorderWidth: pts.map((_, i) => (i === lastIdx ? 2 : 0)),
|
||||
pointBorderWidth: values.map((_, i) => (i === lastIdx ? 2 : 0)),
|
||||
pointHoverRadius: 6,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ChartOptions, ChartDataset } from 'chart.js';
|
||||
import '@/lib/chartSetup';
|
||||
import type { SchoolResult } from '@/lib/types';
|
||||
import { formatAcademicYear } from '@/lib/utils';
|
||||
import { fillAcademicYears } from '@/lib/compareChartData';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { track } from '@/lib/analytics';
|
||||
import styles from './PerformanceChart.module.css';
|
||||
@@ -35,8 +36,6 @@ interface PerformanceChartProps {
|
||||
nationalByYear?: NationalByYear[];
|
||||
}
|
||||
|
||||
const COVID_YEARS = new Set([201920, 202021]);
|
||||
|
||||
// Mobile chip definitions: which datasets render when each chip is active.
|
||||
// `series` keys reference the dataset labels so we can filter cleanly.
|
||||
type ChipId = 'expected' | 'higher' | 'progress' | 'attainment8' | 'em_pass' | 'progress8';
|
||||
@@ -67,21 +66,33 @@ export function PerformanceChart({
|
||||
nationalByYear,
|
||||
}: PerformanceChartProps) {
|
||||
const sortedData = [...data].sort((a, b) => a.year - b.year);
|
||||
const years = sortedData.map(d => formatAcademicYear(d.year));
|
||||
|
||||
// Gap-honest year axis: every academic year between the first and last data
|
||||
// point appears, so cancelled/unpublished years (2019/20, 2020/21, and — for
|
||||
// KS2 — 2021/22) render as real gaps rather than compressed time. School
|
||||
// lines never bridge these gaps (spanGaps:false below).
|
||||
const axisYears = fillAcademicYears(sortedData.map(d => d.year));
|
||||
const byYear = new Map(sortedData.map(d => [d.year, d]));
|
||||
const col = (key: keyof SchoolResult): (number | null)[] =>
|
||||
axisYears.map(y => {
|
||||
const v = byYear.get(y)?.[key];
|
||||
return typeof v === 'number' ? v : null;
|
||||
});
|
||||
const years = axisYears.map(formatAcademicYear);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// ── Build per-year national averages ─────────────────────────────────
|
||||
const natRefRwm: (number | null)[] = sortedData.map(d => {
|
||||
// ── Build per-year national averages (aligned to the filled axis) ────
|
||||
const natRefRwm: (number | null)[] = axisYears.map(y => {
|
||||
if (nationalByYear) {
|
||||
const match = nationalByYear.find(n => n.year === d.year);
|
||||
const match = nationalByYear.find(n => n.year === y);
|
||||
return match?.primary?.rwm_expected_pct ?? null;
|
||||
}
|
||||
return nationalRwmAvg ?? null;
|
||||
});
|
||||
const natRefAtt8: (number | null)[] = sortedData.map(d => {
|
||||
const natRefAtt8: (number | null)[] = axisYears.map(y => {
|
||||
if (nationalByYear) {
|
||||
const match = nationalByYear.find(n => n.year === d.year);
|
||||
const match = nationalByYear.find(n => n.year === y);
|
||||
return match?.secondary?.attainment_8_score ?? null;
|
||||
}
|
||||
return nationalAtt8Avg ?? null;
|
||||
@@ -107,10 +118,11 @@ export function PerformanceChart({
|
||||
return `${arrow} Reading, Writing & Maths peaked at ${bestPct}% (${formatAcademicYear(best.year)}), currently ${latestPct}%`;
|
||||
})();
|
||||
|
||||
const hasCovidGap = isSecondary
|
||||
? false
|
||||
: COVID_YEARS.size > 0 &&
|
||||
[...COVID_YEARS].some(y => !sortedData.find(d => d.year === y));
|
||||
// A gap year is any filled axis year the school has no results row for —
|
||||
// exactly the cancelled/unpublished years the fill introduced. Drives the
|
||||
// honest note below (KS2 and KS4 have different gap stories).
|
||||
const gapYears = axisYears.filter(y => !byYear.has(y));
|
||||
const hasGap = gapYears.length > 0;
|
||||
|
||||
// ── Datasets (full set; mobile filters them via the active chip) ─────
|
||||
const refLineStyle = {
|
||||
@@ -120,13 +132,14 @@ export function PerformanceChart({
|
||||
borderDash: [6, 4] as number[],
|
||||
pointRadius: 0,
|
||||
tension: 0,
|
||||
spanGaps: false as const,
|
||||
order: 10,
|
||||
};
|
||||
|
||||
const allDatasets: ChartDataset<'line'>[] = isSecondary ? [
|
||||
{
|
||||
label: 'Attainment 8',
|
||||
data: sortedData.map(d => d.attainment_8_score),
|
||||
data: col('attainment_8_score'),
|
||||
borderColor: '#2d7d7d',
|
||||
backgroundColor: 'rgba(45,125,125,0.08)',
|
||||
borderWidth: 2.5,
|
||||
@@ -137,7 +150,7 @@ export function PerformanceChart({
|
||||
},
|
||||
{
|
||||
label: 'English & Maths Grade 4+',
|
||||
data: sortedData.map(d => d.english_maths_standard_pass_pct),
|
||||
data: col('english_maths_standard_pass_pct'),
|
||||
borderColor: '#c9a227',
|
||||
backgroundColor: 'rgba(201,162,39,0.08)',
|
||||
borderWidth: 1.5,
|
||||
@@ -147,7 +160,7 @@ export function PerformanceChart({
|
||||
},
|
||||
{
|
||||
label: 'Progress 8',
|
||||
data: sortedData.map(d => d.progress_8_score),
|
||||
data: col('progress_8_score'),
|
||||
borderColor: 'rgb(139,92,246)',
|
||||
backgroundColor: 'rgba(139,92,246,0.08)',
|
||||
borderWidth: 1.5,
|
||||
@@ -165,7 +178,7 @@ export function PerformanceChart({
|
||||
] : [
|
||||
{
|
||||
label: 'Reading, Writing & Maths expected %',
|
||||
data: sortedData.map(d => d.rwm_expected_pct),
|
||||
data: col('rwm_expected_pct'),
|
||||
borderColor: '#2d7d7d',
|
||||
backgroundColor: 'rgba(45,125,125,0.08)',
|
||||
borderWidth: 2.5,
|
||||
@@ -176,7 +189,7 @@ export function PerformanceChart({
|
||||
},
|
||||
{
|
||||
label: 'Exceeding expected level',
|
||||
data: sortedData.map(d => d.rwm_high_pct),
|
||||
data: col('rwm_high_pct'),
|
||||
borderColor: '#c9a227',
|
||||
backgroundColor: 'rgba(201,162,39,0.08)',
|
||||
borderWidth: 1.5,
|
||||
@@ -192,7 +205,7 @@ export function PerformanceChart({
|
||||
} as ChartDataset<'line'>] : []),
|
||||
{
|
||||
label: 'Reading progress',
|
||||
data: sortedData.map(d => d.reading_progress),
|
||||
data: col('reading_progress'),
|
||||
borderColor: 'rgb(59,130,246)',
|
||||
backgroundColor: 'rgba(59,130,246,0.08)',
|
||||
borderWidth: 1.5,
|
||||
@@ -203,7 +216,7 @@ export function PerformanceChart({
|
||||
},
|
||||
{
|
||||
label: 'Writing progress',
|
||||
data: sortedData.map(d => d.writing_progress),
|
||||
data: col('writing_progress'),
|
||||
borderColor: 'rgb(139,92,246)',
|
||||
backgroundColor: 'rgba(139,92,246,0.08)',
|
||||
borderWidth: 1.5,
|
||||
@@ -214,7 +227,7 @@ export function PerformanceChart({
|
||||
},
|
||||
{
|
||||
label: 'Maths progress',
|
||||
data: sortedData.map(d => d.maths_progress),
|
||||
data: col('maths_progress'),
|
||||
borderColor: 'rgb(236,72,153)',
|
||||
backgroundColor: 'rgba(236,72,153,0.08)',
|
||||
borderWidth: 1.5,
|
||||
@@ -275,6 +288,8 @@ export function PerformanceChart({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
// Never bridge missing years — cancelled/unpublished years are real gaps.
|
||||
datasets: { line: { spanGaps: false } },
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
@@ -326,6 +341,7 @@ export function PerformanceChart({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
datasets: { line: { spanGaps: false } },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
@@ -353,7 +369,10 @@ export function PerformanceChart({
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: 10 }, autoSkip: false },
|
||||
// With the gap-honest axis (more year labels) autoSkip keeps the
|
||||
// phone axis readable; the broken line still shows where a year is
|
||||
// missing even when its tick label is skipped.
|
||||
ticks: { font: { size: 10 }, autoSkip: true, maxTicksLimit: 5, maxRotation: 0 },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -416,9 +435,11 @@ export function PerformanceChart({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasCovidGap && (
|
||||
{hasGap && (
|
||||
<p className={styles.covidNote}>
|
||||
* No data for 2019/20 or 2020/21 — national assessments were cancelled due to COVID-19.
|
||||
{isSecondary
|
||||
? "School-level GCSE figures for 2019/20 and 2020/21 weren't published (COVID grading) where the line breaks — the England average is shown where available."
|
||||
: "No national tests were held in 2019/20 and 2020/21 (COVID), and DfE didn't publish school-level figures for 2021/22 — the England average is shown for those years where available."}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} from '@/lib/types';
|
||||
import {
|
||||
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose,
|
||||
formatPercentage, formatProgress, formatAcademicYear, isProposedToClose, ofstedLegacyAreas,
|
||||
} from '@/lib/utils';
|
||||
import { DeltaChip } from './DeltaChip';
|
||||
|
||||
@@ -285,17 +285,12 @@ export function SchoolDetailView({
|
||||
: ofsted?.inspection_date ?? null;
|
||||
|
||||
// ── Ofsted: detect if all OEIF sub-grades match the overall ───────────
|
||||
const oeifAllSameGrade = (() => {
|
||||
if (!ofsted || isReportCard) return false;
|
||||
const subs = [
|
||||
ofsted.quality_of_education,
|
||||
ofsted.behaviour_attitudes,
|
||||
ofsted.personal_development,
|
||||
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);
|
||||
})();
|
||||
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
|
||||
const oeifAllSameGrade =
|
||||
!!ofsted &&
|
||||
!isReportCard &&
|
||||
oeifAreas.length >= 3 &&
|
||||
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
|
||||
|
||||
// Label shown in the mobile "section" menu button — the section in view.
|
||||
const activeNavLabel = (navItems.find((n) => n.id === activeSection) ?? navItems[0])?.label ?? '';
|
||||
@@ -583,7 +578,9 @@ export function SchoolDetailView({
|
||||
)}
|
||||
</div>
|
||||
<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>
|
||||
{oeifAllSameGrade ? (
|
||||
<p className={styles.ofstedAllSame}>
|
||||
@@ -591,15 +588,7 @@ export function SchoolDetailView({
|
||||
</p>
|
||||
) : (
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ 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 && (
|
||||
{oeifAreas.map(({ label, value }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
|
||||
@@ -1049,8 +1049,12 @@
|
||||
font-size: 1.85rem;
|
||||
}
|
||||
|
||||
/* On mobile let the chart container flow naturally — PerformanceChart's
|
||||
own .chartWrapper carries the definite canvas height (220px) plus the
|
||||
chip strip above it. A fixed 220px here double-constrained the two and
|
||||
clipped the chips onto the plot area. */
|
||||
.chartContainer {
|
||||
height: 220px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.dataTable {
|
||||
|
||||
@@ -23,7 +23,7 @@ import type {
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} 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 { track, getNavigationSource } from '@/lib/analytics';
|
||||
import styles from './SecondarySchoolDetailView.module.css';
|
||||
@@ -201,17 +201,12 @@ export function SecondarySchoolDetailView({
|
||||
: ofsted?.inspection_date ?? null;
|
||||
|
||||
// ── Ofsted: detect if all OEIF sub-grades match the overall ───────────
|
||||
const oeifAllSameGrade = (() => {
|
||||
if (!ofsted || isReportCard) return false;
|
||||
const subs = [
|
||||
ofsted.quality_of_education,
|
||||
ofsted.behaviour_attitudes,
|
||||
ofsted.personal_development,
|
||||
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);
|
||||
})();
|
||||
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
|
||||
const oeifAllSameGrade =
|
||||
!!ofsted &&
|
||||
!isReportCard &&
|
||||
oeifAreas.length >= 3 &&
|
||||
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
|
||||
|
||||
// National Attainment 8 baseline for the "Results Over Time" chart.
|
||||
const heroAtt8Nat = secondaryAvg.attainment_8_score ?? null;
|
||||
@@ -404,7 +399,9 @@ export function SecondarySchoolDetailView({
|
||||
)}
|
||||
</div>
|
||||
<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>
|
||||
{oeifAllSameGrade ? (
|
||||
<p className={styles.ofstedAllSame}>
|
||||
@@ -412,15 +409,7 @@ export function SecondarySchoolDetailView({
|
||||
</p>
|
||||
) : (
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ 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 && (
|
||||
{oeifAreas.map(({ label, value }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
|
||||
@@ -680,6 +680,42 @@ export function buildSchoolSummary(
|
||||
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 ──────────────────────────────────────────────────
|
||||
|
||||
export interface OfstedListBadge {
|
||||
|
||||
Reference in New Issue
Block a user