feat(utils): add buildOfstedListBadge helper and fetchNationalAverages

- Add ofsted_framework field to School type
- Add OfstedListBadge interface and buildOfstedListBadge pure function to utils.ts
- Add fetchNationalAverages API function that calls GET /api/national-averages
- Add test suite for buildOfstedListBadge (all 6 new tests pass)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tudor Sitaru
2026-04-13 13:52:05 +01:00
parent 6d02d366ce
commit 8a6758b591
4 changed files with 107 additions and 0 deletions
+39
View File
@@ -8,6 +8,7 @@ import {
calculateTrend,
isValidPostcode,
debounce,
buildOfstedListBadge,
} from '@/lib/utils';
describe('formatPercentage', () => {
@@ -102,3 +103,41 @@ describe('debounce', () => {
jest.useRealTimers();
});
describe('buildOfstedListBadge', () => {
it('returns grade word + year for OEIF Outstanding', () => {
const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
expect(badge.label).toBe('Outstanding · 2023');
expect(badge.cssClass).toBe('ofsted1');
});
it('returns grade word for each OEIF grade', () => {
expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_date: '2022-05-01' }).label).toBe('Good · 2022');
expect(buildOfstedListBadge({ ofsted_grade: 3, ofsted_date: '2021-01-01' }).label).toBe('Req. Improvement · 2021');
expect(buildOfstedListBadge({ ofsted_grade: 4, ofsted_date: '2020-03-01' }).label).toBe('Inadequate · 2020');
});
it('returns grade word without year when date is missing', () => {
const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_date: null });
expect(badge.label).toBe('Good');
expect(badge.cssClass).toBe('ofsted2');
});
it('returns Report Card badge when framework is ReportCard', () => {
const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: '2025-11-01', ofsted_framework: 'ReportCard' });
expect(badge.label).toBe('Report Card · 2025');
expect(badge.cssClass).toBe('ofstedRc');
});
it('returns pending badge when no grade and no ReportCard framework', () => {
const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_framework: null });
expect(badge.label).toBe('Not yet inspected');
expect(badge.cssClass).toBe('ofstedPending');
});
it('returns pending badge when all fields are undefined', () => {
const badge = buildOfstedListBadge({});
expect(badge.label).toBe('Not yet inspected');
expect(badge.cssClass).toBe('ofstedPending');
});
});
+21
View File
@@ -15,6 +15,7 @@ import type {
RankingsParams,
APIError,
LAaveragesResponse,
NationalAverages,
} from './types';
// ============================================================================
@@ -261,6 +262,26 @@ export async function fetchLAaverages(
return handleResponse<LAaveragesResponse>(response);
}
/**
* Fetch official DfE KS2 national averages (primary) and computed KS4 secondary averages.
* Returns latest year snapshot plus per-year history for chart reference lines.
*/
export async function fetchNationalAverages(
options: RequestInit = {}
): Promise<NationalAverages> {
const url = `${API_BASE_URL}/national-averages`;
const response = await fetch(url, {
...options,
next: {
revalidate: 3600,
...options.next,
},
});
return handleResponse<NationalAverages>(response);
}
/**
* Fetch database statistics and info
*/
+1
View File
@@ -67,6 +67,7 @@ export interface School {
// Ofsted (for list view — summary only)
ofsted_grade?: 1 | 2 | 3 | 4 | null;
ofsted_date?: string | null;
ofsted_framework?: string | null;
}
// ============================================================================
+46
View File
@@ -570,3 +570,49 @@ export function buildSchoolSummary(
return parts.join(', ') + '.';
}
// ─── List-level Ofsted badge ──────────────────────────────────────────────────
export interface OfstedListBadge {
/** Display text for the badge (e.g. "Outstanding · 2023", "Report Card · 2025") */
label: string;
/** CSS module class key — one of: ofsted1 | ofsted2 | ofsted3 | ofsted4 | ofstedRc | ofstedPending */
cssClass: string;
}
/**
* Build the Ofsted badge for a school card in the list/map view.
* Three states:
* - OEIF school (ofsted_grade set): grade word + year, colour-keyed
* - ReportCard school (ofsted_framework === 'ReportCard'): "Report Card · YYYY" in purple
* - No inspection: "Not yet inspected" in grey
*/
export function buildOfstedListBadge(school: {
ofsted_grade?: number | null;
ofsted_date?: string | null;
ofsted_framework?: string | null;
}): OfstedListBadge {
const year = school.ofsted_date
? new Date(school.ofsted_date).getFullYear()
: null;
const yearStr = year ? ` · ${year}` : '';
if (school.ofsted_grade) {
const labels: Record<number, string> = {
1: 'Outstanding',
2: 'Good',
3: 'Req. Improvement',
4: 'Inadequate',
};
return {
label: `${labels[school.ofsted_grade]}${yearStr}`,
cssClass: `ofsted${school.ofsted_grade}`,
};
}
if (school.ofsted_framework === 'ReportCard') {
return { label: `Report Card${yearStr}`, cssClass: 'ofstedRc' };
}
return { label: 'Not yet inspected', cssClass: 'ofstedPending' };
}