feat: add secondary school support with KS4 data and metric tooltips
Some checks failed
Build and Push Docker Images / Build Frontend (Next.js) (push) Has been cancelled
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Has been cancelled
Build and Push Docker Images / Trigger Portainer Update (push) Has been cancelled
Build and Push Docker Images / Build Backend (FastAPI) (push) Has been cancelled
Some checks failed
Build and Push Docker Images / Build Frontend (Next.js) (push) Has been cancelled
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Has been cancelled
Build and Push Docker Images / Trigger Portainer Update (push) Has been cancelled
Build and Push Docker Images / Build Backend (FastAPI) (push) Has been cancelled
- Backend: replace INNER JOIN ks2 with UNION ALL (ks2 + ks4) so primary and secondary schools both appear in the main DataFrame - Backend: add /api/national-averages endpoint computing means from live data, replacing the hardcoded NATIONAL_AVG constant on the frontend - Backend: add phase filter param to /api/schools; return phases from /api/filters; fix hardcoded "phase": "Primary" in school detail endpoint - Backend: add KS4 metric definitions (Attainment 8, Progress 8, EBacc, English & Maths pass rates) to METRIC_DEFINITIONS and RANKING_COLUMNS - Frontend: SchoolDetailView is now phase-aware — secondary schools show a GCSE Results section (Att8, P8, E&M, EBacc) instead of SATs; phonics tab hidden for secondary; admissions says Year 7 instead of Year 3; history table shows KS4 columns; chart datasets switch for secondary - Frontend: new MetricTooltip component (CSS-only ⓘ icon) backed by METRIC_EXPLANATIONS — added to RWM, GPS, SEN, EAL, IDACI, progress scores and all KS4 metrics throughout SchoolDetailView and SchoolCard - Frontend: METRIC_EXPLANATIONS extended with KS4 terms (Attainment 8, Progress 8, EBacc) and previously missing terms (SEN, EHCP, EAL, IDACI) - Frontend: SchoolCard expands "RWM" to "Reading, Writing & Maths" and shows Attainment 8 / English & Maths Grade 4+ for secondary schools - Frontend: FilterBar adds Phase dropdown (Primary / Secondary / All-through) - Frontend: HomeView hero copy updated; compact list shows phase-aware metric - Global metadata updated to remove "primary only" framing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,15 +5,17 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useComparison } from '@/hooks/useComparison';
|
||||
import { PerformanceChart } from './PerformanceChart';
|
||||
import { SchoolMap } from './SchoolMap';
|
||||
import { MetricTooltip } from './MetricTooltip';
|
||||
import type {
|
||||
School, SchoolResult, AbsenceData,
|
||||
OfstedInspection, OfstedParentView, SchoolCensus,
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolDeprivation, SchoolFinance,
|
||||
SchoolDeprivation, SchoolFinance, NationalAverages,
|
||||
} from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, formatAcademicYear } from '@/lib/utils';
|
||||
import styles from './SchoolDetailView.module.css';
|
||||
@@ -37,19 +39,6 @@ const RC_CATEGORIES = [
|
||||
{ key: 'rc_sixth_form' as const, label: 'Sixth Form' },
|
||||
];
|
||||
|
||||
// 2023 national averages for context
|
||||
const NATIONAL_AVG = {
|
||||
rwm_expected: 60,
|
||||
rwm_high: 8,
|
||||
reading_expected: 73,
|
||||
writing_expected: 71,
|
||||
maths_expected: 73,
|
||||
phonics_yr1: 79,
|
||||
overall_absence: 6.7,
|
||||
persistent_absence: 22,
|
||||
class_size: 27,
|
||||
per_pupil_spend: 6000,
|
||||
};
|
||||
|
||||
function progressClass(val: number | null | undefined): string {
|
||||
if (val == null) return '';
|
||||
@@ -82,6 +71,23 @@ export function SchoolDetailView({
|
||||
|
||||
const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null;
|
||||
|
||||
// Phase detection
|
||||
const phase = schoolInfo.phase ?? '';
|
||||
const isSecondary = phase.toLowerCase().includes('secondary') || phase.toLowerCase() === 'all-through';
|
||||
const isPrimary = !isSecondary;
|
||||
|
||||
// National averages (fetched dynamically so they stay current)
|
||||
const [nationalAvg, setNationalAvg] = useState<NationalAverages | null>(null);
|
||||
useEffect(() => {
|
||||
fetch('/api/national-averages')
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(data => { if (data) setNationalAvg(data); })
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const primaryAvg = nationalAvg?.primary ?? {};
|
||||
const secondaryAvg = nationalAvg?.secondary ?? {};
|
||||
|
||||
const handleComparisonToggle = () => {
|
||||
if (isInComparison) {
|
||||
removeSchool(schoolInfo.urn);
|
||||
@@ -108,13 +114,18 @@ export function SchoolDetailView({
|
||||
const hasFinance = finance != null && finance.per_pupil_spend != null;
|
||||
const hasLocation = schoolInfo.latitude != null && schoolInfo.longitude != null;
|
||||
|
||||
// Determine whether this school has KS2 or KS4 results to show
|
||||
const hasKS2Results = latestResults != null && latestResults.rwm_expected_pct != null;
|
||||
const hasKS4Results = latestResults != null && latestResults.attainment_8_score != null;
|
||||
const hasAnyResults = hasKS2Results || hasKS4Results;
|
||||
|
||||
// Build section nav items dynamically — only sections with data
|
||||
const navItems: { id: string; label: string }[] = [];
|
||||
if (ofsted) navItems.push({ id: 'ofsted', label: 'Ofsted' });
|
||||
if (parentView && parentView.total_responses != null && parentView.total_responses > 0)
|
||||
navItems.push({ id: 'parents', label: 'Parents' });
|
||||
if (latestResults) navItems.push({ id: 'sats', label: 'SATs' });
|
||||
if (hasPhonics) navItems.push({ id: 'phonics', label: 'Phonics' });
|
||||
if (hasAnyResults) navItems.push({ id: 'results', label: isSecondary ? 'GCSEs' : 'SATs' });
|
||||
if (hasPhonics && isPrimary) navItems.push({ id: 'phonics', label: 'Phonics' });
|
||||
if (hasSchoolLife) navItems.push({ id: 'school-life', label: 'School Life' });
|
||||
if (admissions) navItems.push({ id: 'admissions', label: 'Admissions' });
|
||||
if (hasInclusionData) navItems.push({ id: 'inclusion', label: 'Pupils' });
|
||||
@@ -328,135 +339,259 @@ export function SchoolDetailView({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* SATs Results (merged with Subject Breakdown) */}
|
||||
{latestResults && (
|
||||
<section id="sats" className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>SATs Results ({formatAcademicYear(latestResults.year)})</h2>
|
||||
{/* Results Section (SATs for primary, GCSEs for secondary) */}
|
||||
{hasAnyResults && latestResults && (
|
||||
<section id="results" className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>
|
||||
{isSecondary ? 'GCSE Results' : 'SATs Results'} ({formatAcademicYear(latestResults.year)})
|
||||
</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
End-of-primary-school tests taken by Year 6 pupils. National averages shown for comparison.
|
||||
{isSecondary
|
||||
? 'GCSE results for Year 11 pupils. National averages shown for comparison.'
|
||||
: 'End-of-primary-school tests taken by Year 6 pupils. National averages shown for comparison.'}
|
||||
</p>
|
||||
|
||||
{/* Headline numbers: RWM combined */}
|
||||
<div className={styles.metricsGrid}>
|
||||
{latestResults.rwm_expected_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Reading, Writing & Maths combined</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.rwm_expected_pct)}</div>
|
||||
<div className={styles.metricHint}>National avg: {NATIONAL_AVG.rwm_expected}%</div>
|
||||
{/* ── Primary / KS2 content ── */}
|
||||
{hasKS2Results && (
|
||||
<>
|
||||
<div className={styles.metricsGrid}>
|
||||
{latestResults.rwm_expected_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Reading, Writing & Maths combined
|
||||
<MetricTooltip metricKey="rwm_expected_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.rwm_expected_pct)}</div>
|
||||
{primaryAvg.rwm_expected_pct != null && (
|
||||
<div className={styles.metricHint}>National avg: {primaryAvg.rwm_expected_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{latestResults.rwm_high_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Exceeding expected level (Reading, Writing & Maths)
|
||||
<MetricTooltip metricKey="rwm_high_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.rwm_high_pct)}</div>
|
||||
{primaryAvg.rwm_high_pct != null && (
|
||||
<div className={styles.metricHint}>National avg: {primaryAvg.rwm_high_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{latestResults.rwm_high_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Exceeding expected level (RWM)</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.rwm_high_pct)}</div>
|
||||
<div className={styles.metricHint}>National avg: {NATIONAL_AVG.rwm_high}%</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Per-subject detail table */}
|
||||
<div className={styles.metricGroupsGrid} style={{ marginTop: '1rem' }}>
|
||||
<div className={styles.metricGroup}>
|
||||
<h3 className={styles.metricGroupTitle}>Reading</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.reading_expected_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Expected level</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_expected_pct)}</span>
|
||||
<div className={styles.metricGroupsGrid} style={{ marginTop: '1rem' }}>
|
||||
<div className={styles.metricGroup}>
|
||||
<h3 className={styles.metricGroupTitle}>Reading</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.reading_expected_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Expected level</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_expected_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_high_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Exceeding</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_high_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_progress !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
Progress score
|
||||
<MetricTooltip metricKey="reading_progress" />
|
||||
</span>
|
||||
<span className={`${styles.metricValue} ${progressClass(latestResults.reading_progress)}`}>
|
||||
{formatProgress(latestResults.reading_progress)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_avg_score !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
Average score
|
||||
<MetricTooltip metricKey="reading_avg_score" />
|
||||
</span>
|
||||
<span className={styles.metricValue}>{latestResults.reading_avg_score.toFixed(1)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.metricGroup}>
|
||||
<h3 className={styles.metricGroupTitle}>Writing</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.writing_expected_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Expected level</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_expected_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.writing_high_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Exceeding</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_high_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.writing_progress !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
Progress score
|
||||
<MetricTooltip metricKey="writing_progress" />
|
||||
</span>
|
||||
<span className={`${styles.metricValue} ${progressClass(latestResults.writing_progress)}`}>
|
||||
{formatProgress(latestResults.writing_progress)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.metricGroup}>
|
||||
<h3 className={styles.metricGroupTitle}>Maths</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.maths_expected_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Expected level</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_expected_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_high_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Exceeding</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_high_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_progress !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
Progress score
|
||||
<MetricTooltip metricKey="maths_progress" />
|
||||
</span>
|
||||
<span className={`${styles.metricValue} ${progressClass(latestResults.maths_progress)}`}>
|
||||
{formatProgress(latestResults.maths_progress)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_avg_score !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
Average score
|
||||
<MetricTooltip metricKey="maths_avg_score" />
|
||||
</span>
|
||||
<span className={styles.metricValue}>{latestResults.maths_avg_score.toFixed(1)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(latestResults.reading_progress !== null || latestResults.writing_progress !== null || latestResults.maths_progress !== null) && (
|
||||
<p className={styles.progressNote}>
|
||||
Progress scores measure how much pupils improved compared to similar schools nationally. Above 0 = better than average, below 0 = below average.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── Secondary / KS4 content ── */}
|
||||
{hasKS4Results && (
|
||||
<>
|
||||
<div className={styles.metricsGrid}>
|
||||
{latestResults.attainment_8_score !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Attainment 8
|
||||
<MetricTooltip metricKey="attainment_8_score" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{latestResults.attainment_8_score.toFixed(1)}</div>
|
||||
{secondaryAvg.attainment_8_score != null && (
|
||||
<div className={styles.metricHint}>National avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_high_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Exceeding</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_high_pct)}</span>
|
||||
{latestResults.progress_8_score !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Progress 8
|
||||
<MetricTooltip metricKey="progress_8_score" />
|
||||
</div>
|
||||
<div className={`${styles.metricValue} ${progressClass(latestResults.progress_8_score)}`}>
|
||||
{formatProgress(latestResults.progress_8_score)}
|
||||
</div>
|
||||
<div className={styles.metricHint}>0 = national average</div>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_progress !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Progress score</span>
|
||||
<span className={`${styles.metricValue} ${progressClass(latestResults.reading_progress)}`}>
|
||||
{formatProgress(latestResults.reading_progress)}
|
||||
</span>
|
||||
{latestResults.english_maths_standard_pass_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
English & Maths Grade 4+
|
||||
<MetricTooltip metricKey="english_maths_standard_pass_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_standard_pass_pct)}</div>
|
||||
{secondaryAvg.english_maths_standard_pass_pct != null && (
|
||||
<div className={styles.metricHint}>National avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_avg_score !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Average score</span>
|
||||
<span className={styles.metricValue}>{latestResults.reading_avg_score.toFixed(1)}</span>
|
||||
{latestResults.english_maths_strong_pass_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
English & Maths Grade 5+
|
||||
<MetricTooltip metricKey="english_maths_strong_pass_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_strong_pass_pct)}</div>
|
||||
{secondaryAvg.english_maths_strong_pass_pct != null && (
|
||||
<div className={styles.metricHint}>National avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.metricGroup}>
|
||||
<h3 className={styles.metricGroupTitle}>Writing</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.writing_expected_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Expected level</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_expected_pct)}</span>
|
||||
{/* EBacc */}
|
||||
{(latestResults.ebacc_entry_pct !== null || latestResults.ebacc_standard_pass_pct !== null) && (
|
||||
<>
|
||||
<h3 className={styles.subSectionTitle} style={{ marginTop: '1rem' }}>
|
||||
English Baccalaureate (EBacc)
|
||||
<MetricTooltip metricKey="ebacc_entry_pct" />
|
||||
</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.ebacc_entry_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Pupils entered for EBacc</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.ebacc_entry_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.ebacc_standard_pass_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
EBacc Grade 4+
|
||||
<MetricTooltip metricKey="ebacc_standard_pass_pct" />
|
||||
</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.ebacc_standard_pass_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.ebacc_strong_pass_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>
|
||||
EBacc Grade 5+
|
||||
<MetricTooltip metricKey="ebacc_strong_pass_pct" />
|
||||
</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.ebacc_strong_pass_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{latestResults.writing_high_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Exceeding</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_high_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.writing_progress !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Progress score</span>
|
||||
<span className={`${styles.metricValue} ${progressClass(latestResults.writing_progress)}`}>
|
||||
{formatProgress(latestResults.writing_progress)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.metricGroup}>
|
||||
<h3 className={styles.metricGroupTitle}>Maths</h3>
|
||||
<div className={styles.metricTable}>
|
||||
{latestResults.maths_expected_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Expected level</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_expected_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_high_pct !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Exceeding</span>
|
||||
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_high_pct)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_progress !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Progress score</span>
|
||||
<span className={`${styles.metricValue} ${progressClass(latestResults.maths_progress)}`}>
|
||||
{formatProgress(latestResults.maths_progress)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_avg_score !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Average score</span>
|
||||
<span className={styles.metricValue}>{latestResults.maths_avg_score.toFixed(1)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(latestResults.reading_progress !== null || latestResults.writing_progress !== null || latestResults.maths_progress !== null) && (
|
||||
<p className={styles.progressNote}>
|
||||
Progress scores measure how much pupils improved compared to similar schools nationally. Above 0 = better than average, below 0 = below average.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Year 1 Phonics */}
|
||||
{hasPhonics && phonics && (
|
||||
{/* Year 1 Phonics — primary only */}
|
||||
{hasPhonics && isPrimary && phonics && (
|
||||
<section id="phonics" className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Year 1 Phonics ({formatAcademicYear(phonics.year)})</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
@@ -466,7 +601,7 @@ export function SchoolDetailView({
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Passed the phonics check</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year1_phonics_pct)}</div>
|
||||
<div className={styles.metricHint}>National avg: ~{NATIONAL_AVG.phonics_yr1}%</div>
|
||||
<div className={styles.metricHint}>Phonics is a key early reading skill tested at end of Year 1</div>
|
||||
</div>
|
||||
{phonics.year2_phonics_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
@@ -487,21 +622,31 @@ export function SchoolDetailView({
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Average class size</div>
|
||||
<div className={styles.metricValue}>{census.class_size_avg.toFixed(1)}</div>
|
||||
<div className={styles.metricHint}>National avg: ~{NATIONAL_AVG.class_size} pupils</div>
|
||||
<div className={styles.metricHint}>Average number of pupils per class</div>
|
||||
</div>
|
||||
)}
|
||||
{absenceData?.overall_absence_rate != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Days missed (overall absence)</div>
|
||||
<div className={styles.metricLabel}>
|
||||
Days missed (overall absence)
|
||||
<MetricTooltip metricKey="overall_absence_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(absenceData.overall_absence_rate)}</div>
|
||||
<div className={styles.metricHint}>National avg: ~{NATIONAL_AVG.overall_absence}%</div>
|
||||
{primaryAvg.overall_absence_pct != null && (
|
||||
<div className={styles.metricHint}>National avg: ~{primaryAvg.overall_absence_pct.toFixed(1)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{absenceData?.persistent_absence_rate != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Regularly missing school</div>
|
||||
<div className={styles.metricLabel}>
|
||||
Regularly missing school
|
||||
<MetricTooltip metricKey="persistent_absence_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(absenceData.persistent_absence_rate)}</div>
|
||||
<div className={styles.metricHint}>National avg: ~{NATIONAL_AVG.persistent_absence}%. Missing 10%+ of sessions.</div>
|
||||
{primaryAvg.persistent_absence_pct != null && (
|
||||
<div className={styles.metricHint}>National avg: ~{primaryAvg.persistent_absence_pct.toFixed(0)}%</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -515,7 +660,7 @@ export function SchoolDetailView({
|
||||
<div className={styles.metricsGrid}>
|
||||
{admissions.published_admission_number != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Year 3 places per year</div>
|
||||
<div className={styles.metricLabel}>{isSecondary ? 'Year 7' : 'Year 3'} places per year</div>
|
||||
<div className={styles.metricValue}>{admissions.published_admission_number}</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -556,13 +701,19 @@ export function SchoolDetailView({
|
||||
)}
|
||||
{latestResults?.eal_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>English as an additional language</div>
|
||||
<div className={styles.metricLabel}>
|
||||
English as an additional language
|
||||
<MetricTooltip metricKey="eal_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.eal_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
{latestResults?.sen_support_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Pupils with additional needs (SEN support)</div>
|
||||
<div className={styles.metricLabel}>
|
||||
Pupils receiving SEN support
|
||||
<MetricTooltip metricKey="sen_support_pct" />
|
||||
</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.sen_support_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -610,7 +761,10 @@ export function SchoolDetailView({
|
||||
{/* Local Area Context */}
|
||||
{hasDeprivation && deprivation && (
|
||||
<section id="local-area" className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Local Area Context</h2>
|
||||
<h2 className={styles.sectionTitle}>
|
||||
Local Area Context
|
||||
<MetricTooltip metricKey="idaci_decile" />
|
||||
</h2>
|
||||
<div className={styles.deprivationDots}>
|
||||
{Array.from({ length: 10 }, (_, i) => (
|
||||
<div
|
||||
@@ -639,7 +793,7 @@ export function SchoolDetailView({
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Total spend per pupil per year</div>
|
||||
<div className={styles.metricValue}>£{Math.round(finance.per_pupil_spend!).toLocaleString()}</div>
|
||||
<div className={styles.metricHint}>National avg: ~£{NATIONAL_AVG.per_pupil_spend.toLocaleString()}</div>
|
||||
<div className={styles.metricHint}>How much the school has to spend on each pupil annually</div>
|
||||
</div>
|
||||
{finance.teacher_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
@@ -665,6 +819,7 @@ export function SchoolDetailView({
|
||||
<PerformanceChart
|
||||
data={yearlyData}
|
||||
schoolName={schoolInfo.school_name}
|
||||
isSecondary={isSecondary}
|
||||
/>
|
||||
</div>
|
||||
{yearlyData.length > 1 && (
|
||||
@@ -675,22 +830,44 @@ export function SchoolDetailView({
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Year</th>
|
||||
<th>Reading, Writing & Maths (expected %)</th>
|
||||
<th>Exceeding expected (%)</th>
|
||||
<th>Reading Progress</th>
|
||||
<th>Writing Progress</th>
|
||||
<th>Maths Progress</th>
|
||||
{isSecondary ? (
|
||||
<>
|
||||
<th>Attainment 8</th>
|
||||
<th>Progress 8</th>
|
||||
<th>English & Maths Grade 4+</th>
|
||||
<th>English & Maths Grade 5+</th>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<th>Reading, Writing & Maths (expected %)</th>
|
||||
<th>Exceeding expected (%)</th>
|
||||
<th>Reading Progress</th>
|
||||
<th>Writing Progress</th>
|
||||
<th>Maths Progress</th>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{yearlyData.map((result) => (
|
||||
<tr key={result.year}>
|
||||
<td className={styles.yearCell}>{formatAcademicYear(result.year)}</td>
|
||||
<td>{result.rwm_expected_pct !== null ? formatPercentage(result.rwm_expected_pct) : '-'}</td>
|
||||
<td>{result.rwm_high_pct !== null ? formatPercentage(result.rwm_high_pct) : '-'}</td>
|
||||
<td>{result.reading_progress !== null ? formatProgress(result.reading_progress) : '-'}</td>
|
||||
<td>{result.writing_progress !== null ? formatProgress(result.writing_progress) : '-'}</td>
|
||||
<td>{result.maths_progress !== null ? formatProgress(result.maths_progress) : '-'}</td>
|
||||
{isSecondary ? (
|
||||
<>
|
||||
<td>{result.attainment_8_score !== null ? result.attainment_8_score.toFixed(1) : '-'}</td>
|
||||
<td>{result.progress_8_score !== null ? formatProgress(result.progress_8_score) : '-'}</td>
|
||||
<td>{result.english_maths_standard_pass_pct !== null ? formatPercentage(result.english_maths_standard_pass_pct) : '-'}</td>
|
||||
<td>{result.english_maths_strong_pass_pct !== null ? formatPercentage(result.english_maths_strong_pass_pct) : '-'}</td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<td>{result.rwm_expected_pct !== null ? formatPercentage(result.rwm_expected_pct) : '-'}</td>
|
||||
<td>{result.rwm_high_pct !== null ? formatPercentage(result.rwm_high_pct) : '-'}</td>
|
||||
<td>{result.reading_progress !== null ? formatProgress(result.reading_progress) : '-'}</td>
|
||||
<td>{result.writing_progress !== null ? formatProgress(result.writing_progress) : '-'}</td>
|
||||
<td>{result.maths_progress !== null ? formatProgress(result.maths_progress) : '-'}</td>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user