feat: national average reference line now tracks per year on history chart
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 24s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m52s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 24s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m52s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Previously the dashed reference line was a flat horizontal at the latest year's national average across all historical data, implying the national figure was constant. Now the backend returns per-year averages in `by_year` and the chart maps each data year to its own national average, so the reference line correctly reflects how the national picture changed over time (including COVID recovery dip/recovery). - backend: /api/national-averages now includes `by_year` list alongside existing `year`/`primary`/`secondary` latest-year snapshot - types: NationalAverages extended with `by_year: NationalAveragesYear[]` - PerformanceChart: accepts `nationalByYear` prop; builds per-year series aligned to school data years, falling back to scalar prop if absent - SchoolDetailView + SecondarySchoolDetailView: pass `nationalAvg.by_year` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,14 +24,22 @@ import styles from './PerformanceChart.module.css';
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
|
||||
|
||||
interface NationalByYear {
|
||||
year: number;
|
||||
primary: Record<string, number>;
|
||||
secondary: Record<string, number>;
|
||||
}
|
||||
|
||||
interface PerformanceChartProps {
|
||||
data: SchoolResult[];
|
||||
schoolName: string;
|
||||
isSecondary?: boolean;
|
||||
/** National average RWM expected % — rendered as a dashed reference line */
|
||||
/** National average RWM expected % for the latest year — fallback if no by_year data */
|
||||
nationalRwmAvg?: number | null;
|
||||
/** National average Attainment 8 — rendered as a dashed reference line */
|
||||
/** National average Attainment 8 for the latest year — fallback if no by_year data */
|
||||
nationalAtt8Avg?: number | null;
|
||||
/** Per-year national averages — used to draw a changing reference line */
|
||||
nationalByYear?: NationalByYear[];
|
||||
}
|
||||
|
||||
// Academic years when SATs/GCSEs were cancelled due to COVID
|
||||
@@ -42,10 +50,30 @@ export function PerformanceChart({
|
||||
isSecondary = false,
|
||||
nationalRwmAvg,
|
||||
nationalAtt8Avg,
|
||||
nationalByYear,
|
||||
}: PerformanceChartProps) {
|
||||
const sortedData = [...data].sort((a, b) => a.year - b.year);
|
||||
const years = sortedData.map(d => formatAcademicYear(d.year));
|
||||
|
||||
// Build per-year national average series aligned to the school's data years.
|
||||
// Falls back to a flat line using the scalar prop if by_year isn't available.
|
||||
const natRefRwm: (number | null)[] = sortedData.map(d => {
|
||||
if (nationalByYear) {
|
||||
const match = nationalByYear.find(n => n.year === d.year);
|
||||
return match?.primary?.rwm_expected_pct ?? null;
|
||||
}
|
||||
return nationalRwmAvg ?? null;
|
||||
});
|
||||
const natRefAtt8: (number | null)[] = sortedData.map(d => {
|
||||
if (nationalByYear) {
|
||||
const match = nationalByYear.find(n => n.year === d.year);
|
||||
return match?.secondary?.attainment_8_score ?? null;
|
||||
}
|
||||
return nationalAtt8Avg ?? null;
|
||||
});
|
||||
const hasNatRwm = natRefRwm.some(v => v != null);
|
||||
const hasNatAtt8 = natRefAtt8.some(v => v != null);
|
||||
|
||||
// ── Trend summary (primary only) ──────────────────────────────────────
|
||||
const trendSummary = (() => {
|
||||
if (isSecondary) return null;
|
||||
@@ -114,10 +142,10 @@ export function PerformanceChart({
|
||||
hidden: true,
|
||||
yAxisID: 'y1',
|
||||
},
|
||||
...(nationalAtt8Avg != null ? [{
|
||||
...(hasNatAtt8 ? [{
|
||||
...refLineStyle,
|
||||
label: 'National average',
|
||||
data: sortedData.map(() => nationalAtt8Avg!),
|
||||
data: natRefAtt8,
|
||||
yAxisID: 'y',
|
||||
} as ChartDataset<'line'>] : []),
|
||||
] : [
|
||||
@@ -142,10 +170,10 @@ export function PerformanceChart({
|
||||
pointRadius: 3,
|
||||
yAxisID: 'y',
|
||||
},
|
||||
...(nationalRwmAvg != null ? [{
|
||||
...(hasNatRwm ? [{
|
||||
...refLineStyle,
|
||||
label: 'National average',
|
||||
data: sortedData.map(() => nationalRwmAvg!),
|
||||
data: natRefRwm,
|
||||
yAxisID: 'y',
|
||||
} as ChartDataset<'line'>] : []),
|
||||
{
|
||||
|
||||
@@ -1030,6 +1030,7 @@ export function SchoolDetailView({
|
||||
isSecondary={isSecondary}
|
||||
nationalRwmAvg={isPrimary ? (primaryAvg.rwm_expected_pct ?? null) : null}
|
||||
nationalAtt8Avg={isSecondary ? (secondaryAvg.attainment_8_score ?? null) : null}
|
||||
nationalByYear={nationalAvg?.by_year}
|
||||
/>
|
||||
</div>
|
||||
{yearlyData.length > 1 && (
|
||||
|
||||
@@ -618,6 +618,7 @@ export function SecondarySchoolDetailView({
|
||||
schoolName={schoolInfo.school_name}
|
||||
isSecondary={true}
|
||||
nationalAtt8Avg={heroAtt8Nat}
|
||||
nationalByYear={nationalAvg?.by_year}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user