feat(school-detail): page-wide improvements across 5 sections
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

History chart
- Strip the redundant school-name title (already the page heading)
- Default to 2 visible lines: Reading, Writing & Maths expected % (teal,
  bold) + Exceeding (gold, lighter); progress score lines hidden by
  default, togglable via legend
- Add dashed national average reference line for RWM (primary) or
  Attainment 8 (secondary) so the school's trajectory is always in
  context
- Add trend summary chip above the chart computed from the data
  ("↓ Peaked at 90% (2016/17), currently 70%")
- Add COVID footnote when 2019/20 and 2020/21 data is absent

Ofsted section
- Collapse the four identical "Outstanding / Outstanding / Outstanding /
  Outstanding" boxes into a single prose line when all sub-grades match
  the overall verdict; show individual cards only when grades differ

SATs sub-metrics
- DeltaChip vs national average on Expected level row for Reading,
  Writing and Maths (national averages already in the API response)

Admissions
- Fix label: "Year 3 places per year" → "Reception places per year" for
  primary schools

Pupils & Inclusion
- DeltaChip + national avg hint on Eligible for pupil premium and
  Pupils receiving SEN support (both keys present in /api/national-averages)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tudor Sitaru
2026-04-08 15:13:13 +01:00
parent 41cefeedf6
commit dfa8058efc
4 changed files with 269 additions and 144 deletions
@@ -1,9 +1,34 @@
.chartOuter {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.trendSummary {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-secondary, #5c564d);
padding: 0.5rem 0.875rem;
background: var(--bg-secondary, #f3ede4);
border-left: 3px solid var(--accent-teal, #2d7d7d);
border-radius: 0 4px 4px 0;
align-self: flex-start;
}
.chartWrapper { .chartWrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
} }
.covidNote,
.chartHint {
font-size: 0.75rem;
color: var(--text-muted, #8a847a);
margin: 0;
font-style: italic;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.chartWrapper { .chartWrapper {
font-size: 0.875rem; font-size: 0.875rem;
+160 -120
View File
@@ -16,217 +16,257 @@ import {
Tooltip, Tooltip,
Legend, Legend,
ChartOptions, ChartOptions,
ChartDataset,
} from 'chart.js'; } from 'chart.js';
import type { SchoolResult } from '@/lib/types'; import type { SchoolResult } from '@/lib/types';
import { formatAcademicYear } from '@/lib/utils'; import { formatAcademicYear } from '@/lib/utils';
import styles from './PerformanceChart.module.css'; import styles from './PerformanceChart.module.css';
// Register Chart.js components ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
interface PerformanceChartProps { interface PerformanceChartProps {
data: SchoolResult[]; data: SchoolResult[];
schoolName: string; schoolName: string;
isSecondary?: boolean; isSecondary?: boolean;
/** National average RWM expected % — rendered as a dashed reference line */
nationalRwmAvg?: number | null;
/** National average Attainment 8 — rendered as a dashed reference line */
nationalAtt8Avg?: number | null;
} }
export function PerformanceChart({ data, schoolName, isSecondary = false }: PerformanceChartProps) { // Academic years when SATs/GCSEs were cancelled due to COVID
// Sort data by year const COVID_YEARS = new Set([201920, 202021]);
export function PerformanceChart({
data,
isSecondary = false,
nationalRwmAvg,
nationalAtt8Avg,
}: PerformanceChartProps) {
const sortedData = [...data].sort((a, b) => a.year - b.year); const sortedData = [...data].sort((a, b) => a.year - b.year);
const years = sortedData.map(d => formatAcademicYear(d.year)); const years = sortedData.map(d => formatAcademicYear(d.year));
// Prepare datasets — phase-aware // ── Trend summary (primary only) ──────────────────────────────────────
const datasets = isSecondary ? [ const trendSummary = (() => {
if (isSecondary) return null;
const rwm = sortedData.filter(d => d.rwm_expected_pct != null);
if (rwm.length < 2) return null;
const latest = rwm[rwm.length - 1];
const prev = rwm[rwm.length - 2];
const best = rwm.reduce((a, b) => (b.rwm_expected_pct! > a.rwm_expected_pct! ? b : a));
const latestPct = Math.round(latest.rwm_expected_pct!);
const bestPct = Math.round(best.rwm_expected_pct!);
const delta = latest.rwm_expected_pct! - prev.rwm_expected_pct!;
const arrow = delta > 1 ? '↑' : delta < -1 ? '↓' : '→';
if (best.year === latest.year) {
return `${arrow} Best year on record — ${latestPct}% Reading, Writing & Maths`;
}
return `${arrow} Peaked at ${bestPct}% (${formatAcademicYear(best.year)}), currently ${latestPct}%`;
})();
// ── COVID gap note ─────────────────────────────────────────────────────
const hasCovidGap = isSecondary
? false
: COVID_YEARS.size > 0 &&
[...COVID_YEARS].some(y => !sortedData.find(d => d.year === y));
// ── Datasets ──────────────────────────────────────────────────────────
const refLineStyle = {
borderColor: 'rgba(90,80,70,0.35)',
backgroundColor: 'transparent',
borderWidth: 1.5,
borderDash: [6, 4] as number[],
pointRadius: 0,
tension: 0,
order: 10,
};
const datasets: ChartDataset<'line'>[] = isSecondary ? [
{ {
label: 'Attainment 8', label: 'Attainment 8',
data: sortedData.map(d => d.attainment_8_score), data: sortedData.map(d => d.attainment_8_score),
borderColor: 'rgb(59, 130, 246)', borderColor: '#2d7d7d',
backgroundColor: 'rgba(59, 130, 246, 0.1)', backgroundColor: 'rgba(45,125,125,0.08)',
borderWidth: 2.5,
tension: 0.3, tension: 0.3,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y', yAxisID: 'y',
}, },
{ {
label: 'English & Maths Grade 4+', label: 'English & Maths Grade 4+',
data: sortedData.map(d => d.english_maths_standard_pass_pct), data: sortedData.map(d => d.english_maths_standard_pass_pct),
borderColor: 'rgb(16, 185, 129)', borderColor: '#c9a227',
backgroundColor: 'rgba(16, 185, 129, 0.1)', backgroundColor: 'rgba(201,162,39,0.08)',
borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3,
yAxisID: 'y', yAxisID: 'y',
}, },
{ {
label: 'Progress 8', label: 'Progress 8',
data: sortedData.map(d => d.progress_8_score), data: sortedData.map(d => d.progress_8_score),
borderColor: 'rgb(245, 158, 11)', borderColor: 'rgb(139,92,246)',
backgroundColor: 'rgba(245, 158, 11, 0.1)', backgroundColor: 'rgba(139,92,246,0.08)',
borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3,
hidden: true,
yAxisID: 'y1', yAxisID: 'y1',
}, },
...(nationalAtt8Avg != null ? [{
...refLineStyle,
label: 'National average',
data: sortedData.map(() => nationalAtt8Avg!),
yAxisID: 'y',
} as ChartDataset<'line'>] : []),
] : [ ] : [
{ {
label: 'Reading, Writing & Maths Expected %', label: 'Reading, Writing & Maths expected %',
data: sortedData.map(d => d.rwm_expected_pct), data: sortedData.map(d => d.rwm_expected_pct),
borderColor: 'rgb(59, 130, 246)', borderColor: '#2d7d7d',
backgroundColor: 'rgba(59, 130, 246, 0.1)', backgroundColor: 'rgba(45,125,125,0.08)',
borderWidth: 2.5,
tension: 0.3, tension: 0.3,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y',
}, },
{ {
label: 'Reading, Writing & Maths Higher %', label: 'Exceeding expected level',
data: sortedData.map(d => d.rwm_high_pct), data: sortedData.map(d => d.rwm_high_pct),
borderColor: 'rgb(16, 185, 129)', borderColor: '#c9a227',
backgroundColor: 'rgba(16, 185, 129, 0.1)', backgroundColor: 'rgba(201,162,39,0.08)',
borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3,
yAxisID: 'y',
}, },
...(nationalRwmAvg != null ? [{
...refLineStyle,
label: 'National average',
data: sortedData.map(() => nationalRwmAvg!),
yAxisID: 'y',
} as ChartDataset<'line'>] : []),
{ {
label: 'Reading Progress', label: 'Reading progress',
data: sortedData.map(d => d.reading_progress), data: sortedData.map(d => d.reading_progress),
borderColor: 'rgb(245, 158, 11)', borderColor: 'rgb(59,130,246)',
backgroundColor: 'rgba(245, 158, 11, 0.1)', backgroundColor: 'rgba(59,130,246,0.08)',
borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3,
hidden: true,
yAxisID: 'y1', yAxisID: 'y1',
}, },
{ {
label: 'Writing Progress', label: 'Writing progress',
data: sortedData.map(d => d.writing_progress), data: sortedData.map(d => d.writing_progress),
borderColor: 'rgb(139, 92, 246)', borderColor: 'rgb(139,92,246)',
backgroundColor: 'rgba(139, 92, 246, 0.1)', backgroundColor: 'rgba(139,92,246,0.08)',
borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3,
hidden: true,
yAxisID: 'y1', yAxisID: 'y1',
}, },
{ {
label: 'Maths Progress', label: 'Maths progress',
data: sortedData.map(d => d.maths_progress), data: sortedData.map(d => d.maths_progress),
borderColor: 'rgb(236, 72, 153)', borderColor: 'rgb(236,72,153)',
backgroundColor: 'rgba(236, 72, 153, 0.1)', backgroundColor: 'rgba(236,72,153,0.08)',
borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3,
hidden: true,
yAxisID: 'y1', yAxisID: 'y1',
}, },
]; ];
const chartData = {
labels: years,
datasets,
};
const options: ChartOptions<'line'> = { const options: ChartOptions<'line'> = {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
interaction: { interaction: { mode: 'index', intersect: false },
mode: 'index' as const,
intersect: false,
},
plugins: { plugins: {
legend: { legend: {
position: 'top' as const, position: 'top',
labels: { labels: {
usePointStyle: true, usePointStyle: true,
padding: 15, padding: 14,
font: { font: { size: 12 },
size: 12, filter: item => item.text !== 'National average' || true,
},
},
title: { display: false },
tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)',
padding: 12,
titleFont: { size: 13 },
bodyFont: { size: 12 },
callbacks: {
label: ctx => {
const label = ctx.dataset.label ?? '';
if (ctx.parsed.y == null) return label;
const isProgress = ctx.dataset.yAxisID === 'y1';
const suffix = isProgress ? '' : '%';
const val = ctx.parsed.y.toFixed(1);
return `${label}: ${val}${suffix}`;
}, },
}, },
}, },
title: {
display: true,
text: `${schoolName} - Performance Over Time`,
font: {
size: 16,
weight: 'bold',
},
padding: {
bottom: 20,
},
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
titleFont: {
size: 14,
},
bodyFont: {
size: 13,
},
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
if (context.dataset.yAxisID === 'y1') {
// Progress scores
label += context.parsed.y.toFixed(1);
} else {
// Percentages
label += context.parsed.y.toFixed(1) + '%';
}
}
return label;
}
}
},
}, },
scales: { scales: {
y: { y: {
type: 'linear' as const, type: 'linear',
display: true, display: true,
position: 'left' as const, position: 'left',
title: { title: {
display: true, display: true,
text: isSecondary ? 'Score / Percentage (%)' : 'Percentage (%)', text: isSecondary ? 'Score / %' : 'Percentage (%)',
font: { font: { size: 11 },
size: 12,
weight: 'bold',
},
}, },
min: 0, min: 0,
max: isSecondary ? undefined : 100, max: isSecondary ? undefined : 100,
grid: { grid: { color: 'rgba(0,0,0,0.05)' },
color: 'rgba(0, 0, 0, 0.05)', ticks: { font: { size: 11 } },
},
}, },
y1: { y1: {
type: 'linear' as const, type: 'linear',
display: true, display: true,
position: 'right' as const, position: 'right',
title: { title: {
display: true, display: true,
text: isSecondary ? 'Progress 8 Score' : 'Progress Score', text: isSecondary ? 'Progress 8' : 'Progress score',
font: { font: { size: 11 },
size: 12,
weight: 'bold',
},
},
grid: {
drawOnChartArea: false,
}, },
grid: { drawOnChartArea: false },
ticks: { font: { size: 11 } },
}, },
x: { x: {
grid: { grid: { display: false },
display: false, ticks: { font: { size: 11 } },
},
title: {
display: true,
text: 'Year',
font: {
size: 12,
weight: 'bold',
},
},
}, },
}, },
}; };
return ( return (
<div className={styles.chartWrapper}> <div className={styles.chartOuter}>
<Line data={chartData} options={options} /> {trendSummary && (
<div className={styles.trendSummary}>{trendSummary}</div>
)}
<div className={styles.chartWrapper}>
<Line data={{ labels: years, datasets }} options={options} />
</div>
{hasCovidGap && (
<p className={styles.covidNote}>
* No data for 2019/20 or 2020/21 national assessments were cancelled due to COVID-19.
</p>
)}
{!isSecondary && (
<p className={styles.chartHint}>
Progress scores (Reading, Writing, Maths) are hidden by default click them in the legend to show.
</p>
)}
</div> </div>
); );
} }
@@ -635,6 +635,17 @@
} }
/* Progress note */ /* Progress note */
.ofstedAllSame {
font-size: 0.9375rem;
color: var(--text-secondary, #5c564d);
margin: 0.5rem 0 0;
line-height: 1.5;
}
.ofstedAllSame strong {
color: var(--text-primary, #1a1612);
}
.progressNote { .progressNote {
margin-top: 0.75rem; margin-top: 0.75rem;
font-size: 0.8rem; font-size: 0.8rem;
+73 -24
View File
@@ -138,6 +138,19 @@ export function SchoolDetailView({
if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' }); if (hasFinance) navItems.push({ id: 'finances', label: 'Finances' });
if (yearlyData.length > 0) navItems.push({ id: 'history', label: 'History' }); if (yearlyData.length > 0) navItems.push({ id: 'history', label: 'History' });
// ── Ofsted: detect if all OEIF sub-grades match the overall ───────────
const oeifAllSameGrade = (() => {
if (!ofsted || ofsted.framework === 'ReportCard') 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);
})();
// ── Hero: framework-aware signal chip + narrative summary ───────────── // ── Hero: framework-aware signal chip + narrative summary ─────────────
const ofstedHeroChip = buildOfstedHeroChip(ofsted); const ofstedHeroChip = buildOfstedHeroChip(ofsted);
const heroSummary = buildSchoolSummary(schoolInfo, ofsted, admissions, latestResults); const heroSummary = buildSchoolSummary(schoolInfo, ofsted, admissions, latestResults);
@@ -385,24 +398,30 @@ export function SchoolDetailView({
<strong>{Math.round(parentView.q_recommend_pct)}%</strong> of parents would recommend this school ({parentView.total_responses.toLocaleString()} responses) <strong>{Math.round(parentView.q_recommend_pct)}%</strong> of parents would recommend this school ({parentView.total_responses.toLocaleString()} responses)
</p> </p>
)} )}
<div className={styles.metricsGrid}> {oeifAllSameGrade ? (
{[ <p className={styles.ofstedAllSame}>
{ label: 'Quality of Teaching', value: ofsted.quality_of_education }, Rated <strong>{OFSTED_LABELS[ofsted.overall_effectiveness!]}</strong> across all inspected areas Quality of Teaching, Behaviour, Pupils&apos; Development and Leadership.
{ label: 'Behaviour in School', value: ofsted.behaviour_attitudes }, </p>
{ label: 'Pupils\' Wider Development', value: ofsted.personal_development }, ) : (
{ label: 'School Leadership', value: ofsted.leadership_management }, <div className={styles.metricsGrid}>
...(ofsted.early_years_provision != null {[
? [{ label: 'Early Years (Reception)', value: ofsted.early_years_provision }] { label: 'Quality of Teaching', value: ofsted.quality_of_education },
: []), { label: 'Behaviour in School', value: ofsted.behaviour_attitudes },
].map(({ label, value }) => value != null && ( { label: 'Pupils\' Wider Development', value: ofsted.personal_development },
<div key={label} className={styles.metricCard}> { label: 'School Leadership', value: ofsted.leadership_management },
<div className={styles.metricLabel}>{label}</div> ...(ofsted.early_years_provision != null
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}> ? [{ label: 'Early Years (Reception)', value: ofsted.early_years_provision }]
{OFSTED_LABELS[value]} : []),
].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>
</div> ))}
))} </div>
</div> )}
</> </>
)} )}
</section> </section>
@@ -513,7 +532,12 @@ export function SchoolDetailView({
{latestResults.reading_expected_pct !== null && ( {latestResults.reading_expected_pct !== null && (
<div className={styles.metricRow}> <div className={styles.metricRow}>
<span className={styles.metricName}>Expected level</span> <span className={styles.metricName}>Expected level</span>
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_expected_pct)}</span> <span className={styles.metricValue}>
{formatPercentage(latestResults.reading_expected_pct)}
{primaryAvg.reading_expected_pct != null && (
<DeltaChip value={latestResults.reading_expected_pct} baseline={primaryAvg.reading_expected_pct} unit="pts" size="sm" />
)}
</span>
</div> </div>
)} )}
{latestResults.reading_high_pct !== null && ( {latestResults.reading_high_pct !== null && (
@@ -551,7 +575,12 @@ export function SchoolDetailView({
{latestResults.writing_expected_pct !== null && ( {latestResults.writing_expected_pct !== null && (
<div className={styles.metricRow}> <div className={styles.metricRow}>
<span className={styles.metricName}>Expected level</span> <span className={styles.metricName}>Expected level</span>
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_expected_pct)}</span> <span className={styles.metricValue}>
{formatPercentage(latestResults.writing_expected_pct)}
{primaryAvg.writing_expected_pct != null && (
<DeltaChip value={latestResults.writing_expected_pct} baseline={primaryAvg.writing_expected_pct} unit="pts" size="sm" />
)}
</span>
</div> </div>
)} )}
{latestResults.writing_high_pct !== null && ( {latestResults.writing_high_pct !== null && (
@@ -580,7 +609,12 @@ export function SchoolDetailView({
{latestResults.maths_expected_pct !== null && ( {latestResults.maths_expected_pct !== null && (
<div className={styles.metricRow}> <div className={styles.metricRow}>
<span className={styles.metricName}>Expected level</span> <span className={styles.metricName}>Expected level</span>
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_expected_pct)}</span> <span className={styles.metricValue}>
{formatPercentage(latestResults.maths_expected_pct)}
{primaryAvg.maths_expected_pct != null && (
<DeltaChip value={latestResults.maths_expected_pct} baseline={primaryAvg.maths_expected_pct} unit="pts" size="sm" />
)}
</span>
</div> </div>
)} )}
{latestResults.maths_high_pct !== null && ( {latestResults.maths_high_pct !== null && (
@@ -792,7 +826,7 @@ export function SchoolDetailView({
<div className={styles.metricsGrid}> <div className={styles.metricsGrid}>
{admissions.published_admission_number != null && ( {admissions.published_admission_number != null && (
<div className={styles.metricCard}> <div className={styles.metricCard}>
<div className={styles.metricLabel}>{isSecondary ? 'Year 7' : 'Year 3'} places per year</div> <div className={styles.metricLabel}>{isSecondary ? 'Year 7' : 'Reception'} places per year</div>
<div className={styles.metricValue}>{admissions.published_admission_number}</div> <div className={styles.metricValue}>{admissions.published_admission_number}</div>
</div> </div>
)} )}
@@ -820,8 +854,13 @@ export function SchoolDetailView({
{latestResults?.disadvantaged_pct != null && ( {latestResults?.disadvantaged_pct != null && (
<div className={styles.metricCard}> <div className={styles.metricCard}>
<div className={styles.metricLabel}>Eligible for pupil premium</div> <div className={styles.metricLabel}>Eligible for pupil premium</div>
<div className={styles.metricValue}>{formatPercentage(latestResults.disadvantaged_pct)}</div> <div className={styles.metricValue}>
<div className={styles.metricHint}>Pupils from disadvantaged backgrounds</div> {formatPercentage(latestResults.disadvantaged_pct)}
{primaryAvg.disadvantaged_pct != null && (
<DeltaChip value={latestResults.disadvantaged_pct} baseline={primaryAvg.disadvantaged_pct} unit="pts" size="sm" />
)}
</div>
<div className={styles.metricHint}>Pupils from disadvantaged backgrounds{primaryAvg.disadvantaged_pct != null ? ` · national avg: ${primaryAvg.disadvantaged_pct.toFixed(0)}%` : ''}</div>
</div> </div>
)} )}
{latestResults?.eal_pct != null && ( {latestResults?.eal_pct != null && (
@@ -839,7 +878,15 @@ export function SchoolDetailView({
Pupils receiving SEN support Pupils receiving SEN support
<MetricTooltip metricKey="sen_support_pct" /> <MetricTooltip metricKey="sen_support_pct" />
</div> </div>
<div className={styles.metricValue}>{formatPercentage(latestResults.sen_support_pct)}</div> <div className={styles.metricValue}>
{formatPercentage(latestResults.sen_support_pct)}
{primaryAvg.sen_support_pct != null && (
<DeltaChip value={latestResults.sen_support_pct} baseline={primaryAvg.sen_support_pct} unit="pts" size="sm" />
)}
</div>
{primaryAvg.sen_support_pct != null && (
<div className={styles.metricHint}>National avg: {primaryAvg.sen_support_pct.toFixed(0)}%</div>
)}
</div> </div>
)} )}
</div> </div>
@@ -945,6 +992,8 @@ export function SchoolDetailView({
data={yearlyData} data={yearlyData}
schoolName={schoolInfo.school_name} schoolName={schoolInfo.school_name}
isSecondary={isSecondary} isSecondary={isSecondary}
nationalRwmAvg={isPrimary ? (primaryAvg.rwm_expected_pct ?? null) : null}
nationalAtt8Avg={isSecondary ? (secondaryAvg.attainment_8_score ?? null) : null}
/> />
</div> </div>
{yearlyData.length > 1 && ( {yearlyData.length > 1 && (