Files
school_compare/nextjs-app/components/school/ResultsSection.tsx
T
TudorandClaude Opus 5.5 d1688ac150
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 19s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m20s
copy(web): replace em dashes in public copy
Rewrites every visitor-facing string that used an em dash, choosing the
punctuation for what the dash was doing: a colon before a list or
explanation, a comma for an aside, a full stop between two thoughts,
parentheses for an aside mid-sentence. Covers page titles and meta
descriptions, the home and admissions guide copy, school page headings
and notes, the compare page, metric labels and tooltips.

Two rewrites also fix the sentence around them: the closure banner no
longer repeats "proposed for closure", and the cut-off caveat's list of
priorities now parses.

A lone dash marking a missing value in a table cell stays: it is a data
convention, not prose. A Jest guard walks the source with the TypeScript
parser and fails on any other em dash in a string or JSX text node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:04:27 +01:00

304 lines
15 KiB
TypeScript

/**
* ResultsSection — KS2 SATs (and, for all-through schools, the KS4 block).
* Primary and all-through pages. Server component.
*/
import type { School, SchoolResult } from '@/lib/types';
import { formatPercentage, formatProgress, formatAcademicYear } from '@/lib/utils';
import { MetricTooltip } from '../MetricTooltip';
import { DeltaChip } from '../DeltaChip';
import { SpecialSchoolNote } from '../SpecialSchoolNote';
import { Section, sectionStyles as styles, progressClass } from './sectionShared';
import { SatsChart } from './charts';
export function ResultsSection({
latestResults, schoolInfo, primaryAvg, secondaryAvg,
isAllThrough, isSecondary, isSpecial, hasKS2Results, hasKS4Results,
ks2Placeholder, suppressKs2Comparison, suppressKs4Comparison,
}: {
latestResults: SchoolResult;
schoolInfo: School;
primaryAvg: Record<string, number>;
secondaryAvg: Record<string, number>;
isAllThrough: boolean;
isSecondary: boolean;
isSpecial: boolean;
hasKS2Results: boolean;
hasKS4Results: boolean;
ks2Placeholder: boolean;
suppressKs2Comparison: boolean;
suppressKs4Comparison: boolean;
}) {
return (
<section id="results" className={styles.card}>
<h2 className={styles.sectionTitle}>
{isAllThrough ? 'SATs & GCSE Results' : isSecondary ? 'GCSE Results' : 'SATs Results'} ({formatAcademicYear(latestResults.year)})
</h2>
<p className={styles.sectionSubtitle}>
{isSpecial
? (isSecondary
? 'GCSE results for Year 11 pupils.'
: 'End-of-primary-school tests taken by Year 6 pupils.')
: isAllThrough
? 'This school covers both KS2 SATs (end of Year 6) and GCSE results (Year 11). England averages shown for comparison.'
: isSecondary
? 'GCSE results for Year 11 pupils. England averages shown for comparison.'
: 'End-of-primary-school tests taken by Year 6 pupils. England averages shown for comparison.'}
</p>
{/* Explains up front why the England comparison is dropped below, so
a 0% headline never reads as a failing grade against a benchmark
that doesn't fit. Type-aware copy (special vs PRU vs AP). */}
<SpecialSchoolNote school={schoolInfo} />
{/* ── Primary / KS2 content ── */}
{hasKS2Results && (
<>
{isAllThrough && (
<h3 className={styles.subSectionTitle}>Primary: KS2 SATs (Year 6)</h3>
)}
<div className={styles.heroStatGrid}>
{latestResults.rwm_expected_pct !== null && (
<div className={styles.heroStatCard}>
<div className={styles.heroStatLabel}>
Reading, Writing &amp; Maths combined
<MetricTooltip metricKey="rwm_expected_pct" />
</div>
<div className={styles.heroStatValue}>
{formatPercentage(latestResults.rwm_expected_pct)}
{!suppressKs2Comparison && primaryAvg.rwm_expected_pct != null && (
<DeltaChip
value={latestResults.rwm_expected_pct}
baseline={primaryAvg.rwm_expected_pct}
unit="pts"
size="sm"
/>
)}
</div>
{!suppressKs2Comparison && primaryAvg.rwm_expected_pct != null && (
<div className={styles.heroStatHint}>England avg: {primaryAvg.rwm_expected_pct.toFixed(0)}%</div>
)}
</div>
)}
{latestResults.rwm_high_pct !== null && (
<div className={styles.heroStatCard}>
<div className={styles.heroStatLabel}>
Exceeding expected level (Reading, Writing &amp; Maths)
<MetricTooltip metricKey="rwm_high_pct" />
</div>
<div className={styles.heroStatValue}>
{formatPercentage(latestResults.rwm_high_pct)}
{!suppressKs2Comparison && primaryAvg.rwm_high_pct != null && (
<DeltaChip
value={latestResults.rwm_high_pct}
baseline={primaryAvg.rwm_high_pct}
unit="pts"
size="sm"
/>
)}
</div>
{!suppressKs2Comparison && primaryAvg.rwm_high_pct != null && (
<div className={styles.heroStatHint}>England avg: {primaryAvg.rwm_high_pct.toFixed(0)}%</div>
)}
</div>
)}
</div>
{!suppressKs2Comparison &&
latestResults.rwm_expected_pct != null &&
latestResults.reading_expected_pct != null &&
latestResults.writing_expected_pct != null &&
latestResults.maths_expected_pct != null && (
<div className={styles.rwmBridge}>
<span className={styles.rwmBridgeIcon} aria-hidden="true">?</span>
<div className={styles.rwmBridgeBody}>
<div className={styles.rwmBridgeText}>
Why is combined lower? A pupil is only counted if they met the bar in{' '}
<strong>all three</strong> subjects. Some passed reading but not writing; some passed writing but not maths.
</div>
<div className={styles.rwmBridgeMath}>
<span>Reading <strong>{latestResults.reading_expected_pct.toFixed(0)}%</strong></span>
<span className={styles.rwmBridgeMathSep}>·</span>
<span>Writing <strong>{latestResults.writing_expected_pct.toFixed(0)}%</strong></span>
<span className={styles.rwmBridgeMathSep}>·</span>
<span>Maths <strong>{latestResults.maths_expected_pct.toFixed(0)}%</strong></span>
<span className={styles.rwmBridgeMathSep}>→</span>
<span>All three <strong>{latestResults.rwm_expected_pct.toFixed(0)}%</strong></span>
</div>
</div>
</div>
)}
{/* All-zero placeholder rows (special / suppressed) would render as
three empty bars against the national markers — misleading, so
skip the chart. For a special school with some non-zero
subjects, keep the bars but drop the national markers. */}
{!ks2Placeholder && (
<SatsChart
subjects={[
{
name: 'Reading',
expectedPct: latestResults.reading_expected_pct,
exceedingPct: latestResults.reading_high_pct,
nationalExpectedPct: suppressKs2Comparison ? null : primaryAvg.reading_expected_pct,
nationalExceedingPct: suppressKs2Comparison ? null : primaryAvg.reading_high_pct,
},
{
name: 'Writing',
expectedPct: latestResults.writing_expected_pct,
exceedingPct: latestResults.writing_high_pct,
nationalExpectedPct: suppressKs2Comparison ? null : primaryAvg.writing_expected_pct,
// Writing's higher level is teacher-assessed "greater depth".
nationalExceedingPct: suppressKs2Comparison ? null : primaryAvg.writing_gd_pct,
},
{
name: 'Maths',
expectedPct: latestResults.maths_expected_pct,
exceedingPct: latestResults.maths_high_pct,
nationalExpectedPct: suppressKs2Comparison ? null : primaryAvg.maths_expected_pct,
nationalExceedingPct: suppressKs2Comparison ? null : primaryAvg.maths_high_pct,
},
]}
/>
)}
{/* Progress scores row */}
{(latestResults.reading_progress != null || latestResults.writing_progress != null || latestResults.maths_progress != null) && (
<div className={styles.progressScoresRow}>
<h3 className={styles.subSectionTitle}>Progress Scores</h3>
<div className={styles.progressScoresGrid}>
{latestResults.reading_progress != null && (
<div className={styles.progressScoreItem}>
<span className={styles.progressScoreLabel}>Reading</span>
<span className={`${styles.progressScoreValue} ${progressClass(latestResults.reading_progress)}`}>
{formatProgress(latestResults.reading_progress)}
</span>
</div>
)}
{latestResults.writing_progress != null && (
<div className={styles.progressScoreItem}>
<span className={styles.progressScoreLabel}>Writing</span>
<span className={`${styles.progressScoreValue} ${progressClass(latestResults.writing_progress)}`}>
{formatProgress(latestResults.writing_progress)}
</span>
</div>
)}
{latestResults.maths_progress != null && (
<div className={styles.progressScoreItem}>
<span className={styles.progressScoreLabel}>Maths</span>
<span className={`${styles.progressScoreValue} ${progressClass(latestResults.maths_progress)}`}>
{formatProgress(latestResults.maths_progress)}
</span>
</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 && (
<>
{isAllThrough && (
<h3 className={styles.subSectionTitle} style={{ marginTop: '1.5rem' }}>Secondary: GCSEs (Year 11)</h3>
)}
<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>
{!suppressKs4Comparison && secondaryAvg.attainment_8_score != null && (
<div className={styles.metricHint}>England avg: {secondaryAvg.attainment_8_score.toFixed(1)}</div>
)}
</div>
)}
{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.english_maths_standard_pass_pct !== null && (
<div className={styles.metricCard}>
<div className={styles.metricLabel}>
English &amp; Maths Grade 4+
<MetricTooltip metricKey="english_maths_standard_pass_pct" />
</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_standard_pass_pct)}</div>
{!suppressKs4Comparison && secondaryAvg.english_maths_standard_pass_pct != null && (
<div className={styles.metricHint}>England avg: {secondaryAvg.english_maths_standard_pass_pct.toFixed(0)}%</div>
)}
</div>
)}
{latestResults.english_maths_strong_pass_pct !== null && (
<div className={styles.metricCard}>
<div className={styles.metricLabel}>
English &amp; Maths Grade 5+
<MetricTooltip metricKey="english_maths_strong_pass_pct" />
</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.english_maths_strong_pass_pct)}</div>
{!suppressKs4Comparison && secondaryAvg.english_maths_strong_pass_pct != null && (
<div className={styles.metricHint}>England avg: {secondaryAvg.english_maths_strong_pass_pct.toFixed(0)}%</div>
)}
</div>
)}
</div>
{/* 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>
</>
)}
</>
)}
</section>
);
}