fix(admissions): switch to EES content API + correct publication slug and columns
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 50s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Build Integrator (push) Successful in 57s
Build and Push Docker Images / Build Kestra Init (push) Successful in 33s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 50s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Build Integrator (push) Successful in 57s
Build and Push Docker Images / Build Kestra Init (push) Successful in 33s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
The EES statistics API only exposes ~13 publications; admissions data is not among them. Switch to the EES content API (content.explore-education-statistics. service.gov.uk) which covers all publications. - ees.py: add get_content_release_id() and download_release_zip_csv() that fetch the release ZIP and extract a named CSV member from it - admissions.py: use corrected slug (primary-and-secondary-school-applications- and-offers), correct column names from actual CSV (school_urn, total_number_places_offered, times_put_as_1st_preference, etc.), derive first_preference_offers_pct from offer/application ratio, filter to primary schools only, keep most recent year per URN Also includes SchoolDetailView UX redesign: parent-first section ordering, plain-English labels, national average benchmarks, progress score colour coding, expanded header, quick summary strip, and CSS consolidation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,13 +15,41 @@ import type {
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolDeprivation, SchoolFinance,
|
||||
} from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, calculateTrend } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress } from '@/lib/utils';
|
||||
import styles from './SchoolDetailView.module.css';
|
||||
|
||||
const OFSTED_LABELS: Record<number, string> = {
|
||||
1: 'Outstanding', 2: 'Good', 3: 'Requires Improvement', 4: 'Inadequate',
|
||||
};
|
||||
|
||||
// 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 pillClass(pct: number | null | undefined): string {
|
||||
if (pct == null) return styles.summaryPillWarn;
|
||||
if (pct >= 80) return styles.summaryPillGood;
|
||||
if (pct >= 60) return styles.summaryPillWarn;
|
||||
return styles.summaryPillBad;
|
||||
}
|
||||
|
||||
function progressClass(val: number | null | undefined): string {
|
||||
if (val == null) return '';
|
||||
if (val > 0.1) return styles.progressPositive;
|
||||
if (val < -0.1) return styles.progressNegative;
|
||||
return '';
|
||||
}
|
||||
|
||||
interface SchoolDetailViewProps {
|
||||
schoolInfo: School;
|
||||
yearlyData: SchoolResult[];
|
||||
@@ -44,10 +72,8 @@ export function SchoolDetailView({
|
||||
const { addSchool, removeSchool, isSelected } = useComparison();
|
||||
const isInComparison = isSelected(schoolInfo.urn);
|
||||
|
||||
// Get latest results
|
||||
const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null;
|
||||
|
||||
// Handle add/remove from comparison
|
||||
const handleComparisonToggle = () => {
|
||||
if (isInComparison) {
|
||||
removeSchool(schoolInfo.urn);
|
||||
@@ -56,6 +82,13 @@ export function SchoolDetailView({
|
||||
}
|
||||
};
|
||||
|
||||
// Deprivation plain-English description
|
||||
const deprivationDesc = (decile: number) => {
|
||||
if (decile <= 3) return `This school is in one of England's most deprived areas (decile ${decile}/10). Many pupils may face additional challenges at home.`;
|
||||
if (decile <= 7) return `This school is in an area with average levels of deprivation (decile ${decile}/10).`;
|
||||
return `This school is in one of England's less deprived areas (decile ${decile}/10).`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{/* Back Navigation */}
|
||||
@@ -63,29 +96,52 @@ export function SchoolDetailView({
|
||||
<button onClick={() => router.back()} className={styles.backButton}>← Back</button>
|
||||
</div>
|
||||
|
||||
{/* Header Section */}
|
||||
{/* Header */}
|
||||
<header className={styles.header}>
|
||||
<div className={styles.headerContent}>
|
||||
<div className={styles.titleSection}>
|
||||
<h1 className={styles.schoolName}>{schoolInfo.school_name}</h1>
|
||||
<div className={styles.meta}>
|
||||
{schoolInfo.local_authority && (
|
||||
<span className={styles.metaItem}>
|
||||
{schoolInfo.local_authority}
|
||||
</span>
|
||||
<span className={styles.metaItem}>{schoolInfo.local_authority}</span>
|
||||
)}
|
||||
{schoolInfo.school_type && (
|
||||
<span className={styles.metaItem}>
|
||||
{schoolInfo.school_type}
|
||||
</span>
|
||||
<span className={styles.metaItem}>{schoolInfo.school_type}</span>
|
||||
)}
|
||||
{schoolInfo.gender && schoolInfo.gender !== 'Mixed' && (
|
||||
<span className={styles.metaItem}>{schoolInfo.gender}'s school</span>
|
||||
)}
|
||||
</div>
|
||||
{schoolInfo.address && (
|
||||
<p className={styles.address}>
|
||||
{schoolInfo.address}
|
||||
{schoolInfo.postcode && `, ${schoolInfo.postcode}`}
|
||||
{schoolInfo.address}{schoolInfo.postcode && `, ${schoolInfo.postcode}`}
|
||||
</p>
|
||||
)}
|
||||
<div className={styles.headerDetails}>
|
||||
{schoolInfo.headteacher_name && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>Headteacher:</strong> {schoolInfo.headteacher_name}
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.website && (
|
||||
<span className={styles.headerDetail}>
|
||||
<a href={schoolInfo.website} target="_blank" rel="noopener noreferrer">
|
||||
School website ↗
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
{latestResults?.total_pupils != null && (
|
||||
<span className={styles.headerDetail}>
|
||||
<strong>Pupils:</strong> {latestResults.total_pupils.toLocaleString()}
|
||||
{schoolInfo.capacity != null && ` (capacity: ${schoolInfo.capacity})`}
|
||||
</span>
|
||||
)}
|
||||
{schoolInfo.trust_name && (
|
||||
<span className={styles.headerDetail}>
|
||||
Part of <strong>{schoolInfo.trust_name}</strong>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<button
|
||||
@@ -98,78 +154,407 @@ export function SchoolDetailView({
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Latest Results Summary */}
|
||||
{latestResults && (
|
||||
<section className={styles.summary}>
|
||||
{/* Quick Summary Strip */}
|
||||
{(ofsted || parentView) && (
|
||||
<div className={styles.summaryStrip}>
|
||||
{ofsted?.overall_effectiveness != null && (
|
||||
<span className={`${styles.summaryPill} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
|
||||
Ofsted: {OFSTED_LABELS[ofsted.overall_effectiveness]}
|
||||
</span>
|
||||
)}
|
||||
{parentView?.q_happy_pct != null && (
|
||||
<span className={`${styles.summaryPill} ${pillClass(parentView.q_happy_pct)}`}>
|
||||
{Math.round(parentView.q_happy_pct)}% say child is happy
|
||||
</span>
|
||||
)}
|
||||
{parentView?.q_safe_pct != null && (
|
||||
<span className={`${styles.summaryPill} ${pillClass(parentView.q_safe_pct)}`}>
|
||||
{Math.round(parentView.q_safe_pct)}% say child is safe
|
||||
</span>
|
||||
)}
|
||||
{parentView?.q_recommend_pct != null && (
|
||||
<span className={`${styles.summaryPill} ${pillClass(parentView.q_recommend_pct)}`}>
|
||||
{Math.round(parentView.q_recommend_pct)}% would recommend
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Ofsted Rating */}
|
||||
{ofsted && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>
|
||||
Latest Results ({latestResults.year})
|
||||
Ofsted Rating
|
||||
{ofsted.inspection_date && (
|
||||
<span className={styles.ofstedDate}>
|
||||
Inspected {new Date(ofsted.inspection_date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
</span>
|
||||
)}
|
||||
<a
|
||||
href={`https://reports.ofsted.gov.uk/provider/21/${schoolInfo.urn}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.ofstedReportLink}
|
||||
>
|
||||
Full report ↗
|
||||
</a>
|
||||
</h2>
|
||||
<div className={styles.ofstedHeader}>
|
||||
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
|
||||
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'}
|
||||
</span>
|
||||
{ofsted.previous_overall != null &&
|
||||
ofsted.previous_overall !== ofsted.overall_effectiveness && (
|
||||
<span className={styles.ofstedPrevious}>
|
||||
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<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 && (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
{OFSTED_LABELS[value]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* What Parents Say */}
|
||||
{parentView && parentView.total_responses != null && parentView.total_responses > 0 && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>
|
||||
What Parents Say
|
||||
<span className={styles.responseBadge}>
|
||||
{parentView.total_responses.toLocaleString()} responses
|
||||
</span>
|
||||
</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
From the Ofsted Parent View survey — parents share their experience of this school.
|
||||
</p>
|
||||
<div className={styles.parentViewGrid}>
|
||||
{[
|
||||
{ label: 'Would recommend this school', pct: parentView.q_recommend_pct },
|
||||
{ label: 'My child is happy here', pct: parentView.q_happy_pct },
|
||||
{ label: 'My child feels safe here', pct: parentView.q_safe_pct },
|
||||
{ label: 'Teaching is good', pct: parentView.q_teaching_pct },
|
||||
{ label: 'My child makes good progress', pct: parentView.q_progress_pct },
|
||||
{ label: 'School looks after pupils\' wellbeing', pct: parentView.q_wellbeing_pct },
|
||||
{ label: 'Behaviour is well managed', pct: parentView.q_behaviour_pct },
|
||||
{ label: 'School deals well with bullying', pct: parentView.q_bullying_pct },
|
||||
{ label: 'Communicates well with parents', pct: parentView.q_communication_pct },
|
||||
].filter(q => q.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.parentViewRow}>
|
||||
<span className={styles.parentViewLabel}>{label}</span>
|
||||
<div className={styles.parentViewBar}>
|
||||
<div className={styles.parentViewFill} style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className={styles.parentViewPct}>{Math.round(pct!)}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* SATs Results */}
|
||||
{latestResults && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>SATs Results ({latestResults.year})</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
End-of-primary-school tests taken by Year 6 pupils. National averages shown for comparison.
|
||||
</p>
|
||||
<div className={styles.metricsGrid}>
|
||||
{latestResults.rwm_expected_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
RWM Expected Standard
|
||||
</div>
|
||||
<div className={styles.metricValue}>
|
||||
{formatPercentage(latestResults.rwm_expected_pct)}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{latestResults.rwm_high_pct !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
RWM Higher Standard
|
||||
</div>
|
||||
<div className={styles.metricValue}>
|
||||
{formatPercentage(latestResults.rwm_high_pct)}
|
||||
</div>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{latestResults.reading_progress !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Reading Progress
|
||||
</div>
|
||||
<div className={styles.metricValue}>
|
||||
<div className={styles.metricLabel}>Reading Progress</div>
|
||||
<div className={`${styles.metricValue} ${progressClass(latestResults.reading_progress)}`}>
|
||||
{formatProgress(latestResults.reading_progress)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{latestResults.writing_progress !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Writing Progress
|
||||
</div>
|
||||
<div className={styles.metricValue}>
|
||||
<div className={styles.metricLabel}>Writing Progress</div>
|
||||
<div className={`${styles.metricValue} ${progressClass(latestResults.writing_progress)}`}>
|
||||
{formatProgress(latestResults.writing_progress)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{latestResults.maths_progress !== null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>
|
||||
Maths Progress
|
||||
</div>
|
||||
<div className={styles.metricValue}>
|
||||
<div className={styles.metricLabel}>Maths Progress</div>
|
||||
<div className={`${styles.metricValue} ${progressClass(latestResults.maths_progress)}`}>
|
||||
{formatProgress(latestResults.maths_progress)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{(latestResults.reading_progress !== null || latestResults.writing_progress !== null || latestResults.maths_progress !== null) && (
|
||||
<p className={styles.progressNote}>Progress scores: 0 = national average. Positive = above average, negative = below average.</p>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* Map */}
|
||||
{/* Detailed Subject Breakdown */}
|
||||
{latestResults && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Subject Breakdown ({latestResults.year})</h2>
|
||||
<div className={styles.metricGroupsGrid}>
|
||||
<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</span>
|
||||
<span className={styles.metricValue}>{formatProgress(latestResults.reading_progress)}</span>
|
||||
</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>
|
||||
</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</span>
|
||||
<span className={styles.metricValue}>{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}>{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>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Year 1 Phonics */}
|
||||
{phonics && phonics.year1_phonics_pct != null && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Year 1 Phonics ({phonics.year})</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
Phonics is a key early reading skill. Children are tested at the end of Year 1.
|
||||
</p>
|
||||
<div className={styles.metricsGrid}>
|
||||
<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>
|
||||
{phonics.year2_phonics_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Year 2 pupils who retook and passed</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year2_phonics_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* School Life */}
|
||||
{(absenceData || census?.class_size_avg != null) && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>School Life</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{census?.class_size_avg != null && (
|
||||
<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>
|
||||
)}
|
||||
{absenceData?.overall_absence_rate != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Days missed (overall absence)</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(absenceData.overall_absence_rate)}</div>
|
||||
<div className={styles.metricHint}>National avg: ~{NATIONAL_AVG.overall_absence}%</div>
|
||||
</div>
|
||||
)}
|
||||
{absenceData?.persistent_absence_rate != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Regularly missing school</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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* How Hard to Get In */}
|
||||
{admissions && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>How Hard to Get Into This School</h2>
|
||||
<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.metricValue}>{admissions.published_admission_number}</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.total_applications != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Applications received</div>
|
||||
<div className={styles.metricValue}>{admissions.total_applications.toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.first_preference_offers_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Families who got their first-choice</div>
|
||||
<div className={styles.metricValue}>{admissions.first_preference_offers_pct}%</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{admissions.oversubscribed != null && (
|
||||
<div className={`${styles.admissionsBadge} ${admissions.oversubscribed ? styles.admissionsBadgeWarn : styles.admissionsBadgeGood}`}>
|
||||
{admissions.oversubscribed
|
||||
? '⚠ More applications than places last year'
|
||||
: '✓ Places were available last year'}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Pupils & Inclusion */}
|
||||
{(latestResults || senDetail) && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Pupils & Inclusion</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{latestResults?.disadvantaged_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Eligible for pupil premium</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(latestResults.disadvantaged_pct)}</div>
|
||||
<div className={styles.metricHint}>Pupils from disadvantaged backgrounds</div>
|
||||
</div>
|
||||
)}
|
||||
{latestResults?.eal_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>English as an additional language</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.metricValue}>{formatPercentage(latestResults.sen_support_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{senDetail && (
|
||||
<>
|
||||
<h3 className={styles.subSectionTitle}>Types of additional needs supported</h3>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
What proportion of pupils with additional needs have each type of support need.
|
||||
</p>
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ label: 'Speech & Language', pct: senDetail.primary_need_speech_pct },
|
||||
{ label: 'Autism (ASD)', pct: senDetail.primary_need_autism_pct },
|
||||
{ label: 'Learning Difficulties', pct: senDetail.primary_need_mld_pct },
|
||||
{ label: 'Specific Learning (e.g. Dyslexia)', pct: senDetail.primary_need_spld_pct },
|
||||
{ label: 'Social, Emotional & Mental Health', pct: senDetail.primary_need_semh_pct },
|
||||
{ label: 'Physical / Sensory', pct: senDetail.primary_need_physical_pct },
|
||||
].filter(n => n.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={styles.metricValue}>{pct}%</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Location */}
|
||||
{schoolInfo.latitude && schoolInfo.longitude && (
|
||||
<section className={styles.mapSection}>
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Location</h2>
|
||||
<div className={styles.mapContainer}>
|
||||
<SchoolMap
|
||||
@@ -181,10 +566,60 @@ export function SchoolDetailView({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Performance Over Time */}
|
||||
{/* Local Area Context */}
|
||||
{deprivation && deprivation.idaci_decile != null && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Local Area Context</h2>
|
||||
<div className={styles.deprivationDots}>
|
||||
{Array.from({ length: 10 }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`${styles.deprivationDot} ${i < deprivation.idaci_decile! ? styles.deprivationDotFilled : ''}`}
|
||||
title={`Decile ${i + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.deprivationScaleLabel}>
|
||||
<span>Most deprived</span>
|
||||
<span>Least deprived</span>
|
||||
</div>
|
||||
<p className={styles.deprivationDesc}>{deprivationDesc(deprivation.idaci_decile)}</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Finances */}
|
||||
{finance && finance.per_pupil_spend != null && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>School Finances ({finance.year})</h2>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
Per-pupil spending shows how much the school has to spend on each child's education.
|
||||
</p>
|
||||
<div className={styles.metricsGrid}>
|
||||
<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>
|
||||
{finance.teacher_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Share of budget spent on teachers</div>
|
||||
<div className={styles.metricValue}>{finance.teacher_cost_pct.toFixed(1)}%</div>
|
||||
</div>
|
||||
)}
|
||||
{finance.staff_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Share of budget spent on all staff</div>
|
||||
<div className={styles.metricValue}>{finance.staff_cost_pct.toFixed(1)}%</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Results Over Time */}
|
||||
{yearlyData.length > 0 && (
|
||||
<section className={styles.chartsSection}>
|
||||
<h2 className={styles.sectionTitle}>Performance Over Time</h2>
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Results Over Time</h2>
|
||||
<div className={styles.chartContainer}>
|
||||
<PerformanceChart
|
||||
data={yearlyData}
|
||||
@@ -194,133 +629,17 @@ export function SchoolDetailView({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Detailed Metrics */}
|
||||
{latestResults && (
|
||||
<section className={styles.detailedMetrics}>
|
||||
<h2 className={styles.sectionTitle}>Detailed Metrics</h2>
|
||||
<div className={styles.metricGroupsGrid}>
|
||||
{/* Reading Metrics */}
|
||||
<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</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}>Higher</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</span>
|
||||
<span className={styles.metricValue}>{formatProgress(latestResults.reading_progress)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.reading_avg_score !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Avg Score</span>
|
||||
<span className={styles.metricValue}>{latestResults.reading_avg_score.toFixed(1)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Writing Metrics */}
|
||||
<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</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}>Higher</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</span>
|
||||
<span className={styles.metricValue}>{formatProgress(latestResults.writing_progress)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Maths Metrics */}
|
||||
<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</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}>Higher</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</span>
|
||||
<span className={styles.metricValue}>{formatProgress(latestResults.maths_progress)}</span>
|
||||
</div>
|
||||
)}
|
||||
{latestResults.maths_avg_score !== null && (
|
||||
<div className={styles.metricRow}>
|
||||
<span className={styles.metricName}>Avg Score</span>
|
||||
<span className={styles.metricValue}>{latestResults.maths_avg_score.toFixed(1)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Absence Data */}
|
||||
{absenceData && (
|
||||
<section className={styles.absenceSection}>
|
||||
<h2 className={styles.sectionTitle}>Absence Data</h2>
|
||||
<div className={styles.absenceGrid}>
|
||||
{absenceData.overall_absence_rate !== null && (
|
||||
<div className={styles.absenceCard}>
|
||||
<div className={styles.absenceLabel}>Overall Absence Rate</div>
|
||||
<div className={styles.absenceValue}>{formatPercentage(absenceData.overall_absence_rate)}</div>
|
||||
</div>
|
||||
)}
|
||||
{absenceData.persistent_absence_rate !== null && (
|
||||
<div className={styles.absenceCard}>
|
||||
<div className={styles.absenceLabel}>Persistent Absence</div>
|
||||
<div className={styles.absenceValue}>{formatPercentage(absenceData.persistent_absence_rate)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* All Years Data Table */}
|
||||
{yearlyData.length > 0 && (
|
||||
<section className={styles.historySection}>
|
||||
<h2 className={styles.sectionTitle}>Historical Data</h2>
|
||||
{/* Historical Data Table */}
|
||||
{yearlyData.length > 1 && (
|
||||
<section className={styles.card}>
|
||||
<h2 className={styles.sectionTitle}>Year-by-Year Summary</h2>
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.dataTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Year</th>
|
||||
<th>RWM Expected</th>
|
||||
<th>RWM Higher</th>
|
||||
<th>Reading, Writing & Maths (expected %)</th>
|
||||
<th>Exceeding expected (%)</th>
|
||||
<th>Reading Progress</th>
|
||||
<th>Writing Progress</th>
|
||||
<th>Maths Progress</th>
|
||||
@@ -342,209 +661,6 @@ export function SchoolDetailView({
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Ofsted Section */}
|
||||
{ofsted && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Ofsted Inspection</h2>
|
||||
<div className={styles.ofstedHeader}>
|
||||
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
|
||||
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'}
|
||||
</span>
|
||||
{ofsted.inspection_date && (
|
||||
<span className={styles.ofstedDate}>
|
||||
Inspected: {new Date(ofsted.inspection_date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ label: 'Quality of Education', value: ofsted.quality_of_education },
|
||||
{ label: 'Behaviour & Attitudes', value: ofsted.behaviour_attitudes },
|
||||
{ label: 'Personal Development', value: ofsted.personal_development },
|
||||
{ label: 'Leadership & Management', value: ofsted.leadership_management },
|
||||
...(ofsted.early_years_provision != null ? [{ label: 'Early Years', value: ofsted.early_years_provision }] : []),
|
||||
].map(({ label, value }) => value != null && (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
{OFSTED_LABELS[value]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{ofsted.inspection_type && (
|
||||
<p className={styles.ofstedType}>{ofsted.inspection_type}</p>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* What Parents Think */}
|
||||
{parentView && parentView.total_responses != null && parentView.total_responses > 0 && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>What Parents Think</h2>
|
||||
<p className={styles.supplementarySubtitle}>
|
||||
Based on {parentView.total_responses.toLocaleString()} parent responses to the Ofsted Parent View survey.
|
||||
</p>
|
||||
<div className={styles.parentViewGrid}>
|
||||
{[
|
||||
{ label: 'My child is happy here', pct: parentView.q_happy_pct },
|
||||
{ label: 'My child feels safe here', pct: parentView.q_safe_pct },
|
||||
{ label: 'Would recommend this school', pct: parentView.q_recommend_pct },
|
||||
{ label: 'Teaching is good', pct: parentView.q_teaching_pct },
|
||||
{ label: 'My child makes good progress', pct: parentView.q_progress_pct },
|
||||
{ label: 'School looks after wellbeing', pct: parentView.q_wellbeing_pct },
|
||||
{ label: 'Led and managed effectively', pct: parentView.q_leadership_pct },
|
||||
{ label: 'Behaviour is well managed', pct: parentView.q_behaviour_pct },
|
||||
{ label: 'Communicates well with parents', pct: parentView.q_communication_pct },
|
||||
].filter(q => q.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.parentViewRow}>
|
||||
<span className={styles.parentViewLabel}>{label}</span>
|
||||
<div className={styles.parentViewBar}>
|
||||
<div className={styles.parentViewFill} style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className={styles.parentViewPct}>{pct}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Admissions */}
|
||||
{admissions && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Admissions ({admissions.year})</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{admissions.published_admission_number != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Places available</div>
|
||||
<div className={styles.metricValue}>{admissions.published_admission_number}</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.total_applications != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Applications received</div>
|
||||
<div className={styles.metricValue}>{admissions.total_applications.toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.first_preference_offers_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Got first choice</div>
|
||||
<div className={styles.metricValue}>{admissions.first_preference_offers_pct}%</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.oversubscribed != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Oversubscribed</div>
|
||||
<div className={styles.metricValue}>{admissions.oversubscribed ? 'Yes' : 'No'}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Pupils & Inclusion (Census + SEN) */}
|
||||
{(census || senDetail) && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Pupils & Inclusion</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{census?.class_size_avg != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Average class size</div>
|
||||
<div className={styles.metricValue}>{census.class_size_avg.toFixed(1)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{senDetail && (
|
||||
<>
|
||||
<h3 className={styles.subSectionTitle}>Primary SEN Needs (latest year)</h3>
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ label: 'Speech & Language', pct: senDetail.primary_need_speech_pct },
|
||||
{ label: 'Autism (ASD)', pct: senDetail.primary_need_autism_pct },
|
||||
{ label: 'Learning Difficulties', pct: senDetail.primary_need_mld_pct },
|
||||
{ label: 'Specific Learning (Dyslexia etc.)', pct: senDetail.primary_need_spld_pct },
|
||||
{ label: 'Social, Emotional & Mental Health', pct: senDetail.primary_need_semh_pct },
|
||||
{ label: 'Physical / Sensory', pct: senDetail.primary_need_physical_pct },
|
||||
].filter(n => n.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={styles.metricValue}>{pct}%</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Year 1 Phonics */}
|
||||
{phonics && phonics.year1_phonics_pct != null && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Year 1 Phonics ({phonics.year})</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Reached expected standard</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year1_phonics_pct)}</div>
|
||||
</div>
|
||||
{phonics.year2_phonics_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Year 2 (re-takers) standard</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year2_phonics_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Deprivation Context */}
|
||||
{deprivation && deprivation.idaci_decile != null && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Deprivation Context</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Area deprivation decile</div>
|
||||
<div className={styles.metricValue}>{deprivation.idaci_decile} / 10</div>
|
||||
<div className={styles.metricHint}>
|
||||
1 = most deprived, 10 = least deprived
|
||||
</div>
|
||||
</div>
|
||||
{deprivation.idaci_score != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>IDACI score</div>
|
||||
<div className={styles.metricValue}>{deprivation.idaci_score.toFixed(3)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Finances */}
|
||||
{finance && finance.per_pupil_spend != null && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Finances ({finance.year})</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{finance.per_pupil_spend != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Spend per pupil</div>
|
||||
<div className={styles.metricValue}>£{Math.round(finance.per_pupil_spend).toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
{finance.teacher_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Teacher costs</div>
|
||||
<div className={styles.metricValue}>{finance.teacher_cost_pct.toFixed(1)}% of budget</div>
|
||||
</div>
|
||||
)}
|
||||
{finance.staff_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>All staff costs</div>
|
||||
<div className={styles.metricValue}>{finance.staff_cost_pct.toFixed(1)}% of budget</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user