/** * Explore trends — the full grouped metric catalogue (nothing from the old * compare page is lost; spec §4's tier 3) driving the year-by-year chart * with its England reference line, plus the year-by-year table. Progress * metrics carry CI-based bands for the years DfE published them. */ 'use client'; import dynamic from 'next/dynamic'; import { progressBand } from '@/lib/compareLogic'; import type { ComparisonData, MetricDefinition, NationalAverages, School } from '@/lib/types'; import { formatAcademicYear, formatMetricValue, metricKind } from '@/lib/utils'; import { track } from '@/lib/analytics'; import { Chip, Section, sectionStyles as s } from './sectionShared'; import styles from './TrendsExplorer.module.css'; const ComparisonChart = dynamic( () => import('../ComparisonChart').then((m) => m.ComparisonChart), { ssr: false }, ); const PRIMARY_OPTGROUPS: { label: string; category: string }[] = [ { label: 'Expected Standard', category: 'expected' }, { label: 'Higher Standard', category: 'higher' }, { label: 'Progress Scores', category: 'progress' }, { label: 'Average Scores', category: 'average' }, { label: 'Gender Performance', category: 'gender' }, { label: 'Equity (Disadvantaged)', category: 'equity' }, { label: 'School Context', category: 'context' }, { label: 'Absence', category: 'absence' }, { label: '3-Year Trends', category: 'trends' }, ]; const SECONDARY_OPTGROUPS: { label: string; category: string }[] = [ { label: 'GCSE Performance', category: 'gcse' }, ]; export const PRIMARY_CATEGORIES = PRIMARY_OPTGROUPS.map((g) => g.category); export const SECONDARY_CATEGORIES = SECONDARY_OPTGROUPS.map((g) => g.category); const PROGRESS_CI: Record = { reading_progress: ['reading_progress_lower_ci', 'reading_progress_upper_ci'], writing_progress: ['writing_progress_lower_ci', 'writing_progress_upper_ci'], maths_progress: ['maths_progress_lower_ci', 'maths_progress_upper_ci'], }; const BAND_LABEL = { above: 'Above average', average: 'Average', below: 'Below average' } as const; export function TrendsExplorer({ schools, data, metrics, metric, onMetricChange, isPrimaryPhase, nationalAverages, }: { schools: School[]; data: Record; metrics: MetricDefinition[]; /** Controlled: the page owns the metric so the URL contract survives. */ metric: string; onMetricChange: (metric: string) => void; isPrimaryPhase: boolean; nationalAverages?: NationalAverages; }) { const allowedCategories = isPrimaryPhase ? PRIMARY_CATEGORIES : SECONDARY_CATEGORIES; const optgroups = isPrimaryPhase ? PRIMARY_OPTGROUPS : SECONDARY_OPTGROUPS; const filteredMetrics = metrics.filter((m) => allowedCategories.includes(m.category)); const metricDef = metrics.find((m) => m.key === metric); const metricLabel = metricDef?.label || metric; const nationalByYear: Record = {}; for (const entry of nationalAverages?.by_year ?? []) { const block = isPrimaryPhase ? entry.primary : entry.secondary; nationalByYear[entry.year] = block?.[metric] ?? null; } const years = [ ...new Set( schools.flatMap( (school) => data[String(school.urn)]?.yearly_data.map((d) => Math.trunc(d.year)) ?? [], ), ), ].sort((a, b) => a - b); const handleMetricChange = (next: string) => { track('compare_metric_changed', { metric: next, phase: isPrimaryPhase ? 'primary' : 'secondary' }); onMetricChange(next); }; const ciKeys = PROGRESS_CI[metric]; return (
Year-by-year trends
{metricDef?.description && {metricDef.description}}
{metric.includes('progress') && (

Progress scores measure pupils' progress from KS1 to KS2. A score of 0 equals the national average. DfE stopped publishing KS2 progress after 2022/23 (no KS1 baseline); bands use DfE's confidence intervals, not the raw score alone.

)}
{years.length > 0 && (
{schools.map((school) => ( ))} {years.map((year) => ( {schools.map((school) => { const row = data[String(school.urn)]?.yearly_data.find( (d) => Math.trunc(d.year) === year, ) as (Record & { year: number }) | undefined; const value = row?.[metric]; if (typeof value !== 'number') return ; const band = ciKeys ? progressBand( value, (row?.[ciKeys[0]] as number | null) ?? null, (row?.[ciKeys[1]] as number | null) ?? null, ) : null; return ( ); })} ))}
Year{school.school_name}
{formatAcademicYear(year)} {formatMetricValue(value, metricKind(metric))}{' '} {band && ( {BAND_LABEL[band]} )}
)}
); }