Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc85254ad2 | ||
|
|
d02a323cdc | ||
|
|
32f8a02862 | ||
|
|
7e3096c881 | ||
|
|
1f80571b1f |
@@ -41,6 +41,30 @@ describe('buildCompareChart', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignores a no-results school whose only row has a null year', () => {
|
||||||
|
// A school with no performance rows comes back from /api/compare with a
|
||||||
|
// single phantom yearly_data row (LEFT JOIN) where year and every metric
|
||||||
|
// are null. That null year must NOT pollute the axis: Math.trunc(null) is
|
||||||
|
// 0, and filling from year 0 blows past the real years, blanking every
|
||||||
|
// school's line. Regression guard for "add a no-data school → chart empty".
|
||||||
|
const withNoData = {
|
||||||
|
...THREE_SCHOOLS,
|
||||||
|
'4': school(4, [[null as unknown as number, null]]),
|
||||||
|
};
|
||||||
|
const list = [...SCHOOL_LIST, { urn: 4, school_name: 'School 4' }];
|
||||||
|
const chart = buildCompareChart(withNoData, list, 'rwm_expected_pct');
|
||||||
|
|
||||||
|
// The real years still drive the axis; the phantom year 0 is gone.
|
||||||
|
expect(chart.years).toContain(201819);
|
||||||
|
expect(chart.years).toContain(202425);
|
||||||
|
expect(chart.years).not.toContain(0);
|
||||||
|
// The three real schools still render their lines.
|
||||||
|
for (const urn of [1, 2, 3]) {
|
||||||
|
const ds = chart.schoolDatasets[urn - 1];
|
||||||
|
expect(ds.data.some((v) => v != null)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('handles float years from the API (202425.0 style)', () => {
|
it('handles float years from the API (202425.0 style)', () => {
|
||||||
const floaty = {
|
const floaty = {
|
||||||
'1': school(1, [[201819.0 as number, 80], [202425.0 as number, 85]]),
|
'1': school(1, [[201819.0 as number, 80], [202425.0 as number, 85]]),
|
||||||
|
|||||||
@@ -85,6 +85,14 @@ export default function RootLayout({
|
|||||||
data-performance="true"
|
data-performance="true"
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
/>
|
/>
|
||||||
|
{/* Heatmap / session recorder — same data-domains guard so staging
|
||||||
|
(same image, different host) never feeds the heatmap. */}
|
||||||
|
<Script
|
||||||
|
src="https://analytics.schoolcompare.co.uk/recorder.js"
|
||||||
|
data-website-id="d7fb0c95-bb6c-4336-8209-bd10077e50dd"
|
||||||
|
data-domains="schoolcompare.co.uk,www.schoolcompare.co.uk"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body className={`${dmSans.variable} ${playfairDisplay.variable}`}>
|
<body className={`${dmSans.variable} ${playfairDisplay.variable}`}>
|
||||||
<div className="noise-overlay" />
|
<div className="noise-overlay" />
|
||||||
|
|||||||
@@ -855,7 +855,10 @@
|
|||||||
/* Charts Section */
|
/* Charts Section */
|
||||||
.chartContainer {
|
.chartContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
/* Taller on desktop so the trend lines have vertical room to separate
|
||||||
|
and read clearly. Mobile overrides this to height:auto below (the
|
||||||
|
max-width:768px query), so this only affects desktop. */
|
||||||
|
height: 380px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -490,7 +490,10 @@
|
|||||||
/* ── Charts & Map ────────────────────────────────────── */
|
/* ── Charts & Map ────────────────────────────────────── */
|
||||||
.chartContainer {
|
.chartContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
/* Taller on desktop so the trend lines have vertical room to separate
|
||||||
|
and read clearly. Mobile overrides this to height:auto below (the
|
||||||
|
max-width:768px query), so this only affects desktop. */
|
||||||
|
height: 380px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,17 @@
|
|||||||
|
|
||||||
import type { ComparisonData } from './types';
|
import type { ComparisonData } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A yearly row only counts once it carries a real academic year. A school with
|
||||||
|
* no performance data still comes back from /api/compare with a single phantom
|
||||||
|
* row (the dim_school LEFT JOIN) where `year` is null — and Math.trunc(null) is
|
||||||
|
* 0, which would seed the axis at year 0 and, via fillAcademicYears, blow past
|
||||||
|
* every real year and blank all schools' lines. Drop those rows up front.
|
||||||
|
*/
|
||||||
|
function hasYear(row: { year: number }): boolean {
|
||||||
|
return typeof row.year === 'number' && Number.isFinite(row.year);
|
||||||
|
}
|
||||||
|
|
||||||
/** 201819 → 201920 (academic-year arithmetic on YYYYYY codes). */
|
/** 201819 → 201920 (academic-year arithmetic on YYYYYY codes). */
|
||||||
function nextAcademicYear(year: number): number {
|
function nextAcademicYear(year: number): number {
|
||||||
const start = Math.floor(year / 100);
|
const start = Math.floor(year / 100);
|
||||||
@@ -66,14 +77,19 @@ export function buildCompareChart(
|
|||||||
nationalByYear?: Record<number, number | null | undefined>,
|
nationalByYear?: Record<number, number | null | undefined>,
|
||||||
): CompareChart {
|
): CompareChart {
|
||||||
const rawYears = schools.flatMap(
|
const rawYears = schools.flatMap(
|
||||||
(s) => comparisonData[String(s.urn)]?.yearly_data.map((d) => Math.trunc(d.year)) ?? [],
|
(s) =>
|
||||||
|
comparisonData[String(s.urn)]?.yearly_data.filter(hasYear).map((d) => Math.trunc(d.year)) ??
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
const years = fillAcademicYears(rawYears);
|
const years = fillAcademicYears(rawYears);
|
||||||
|
|
||||||
const schoolDatasets: CompareChartSeries[] = schools.map((school, schoolIndex) => {
|
const schoolDatasets: CompareChartSeries[] = schools.map((school, schoolIndex) => {
|
||||||
const rows = comparisonData[String(school.urn)]?.yearly_data ?? [];
|
const rows = comparisonData[String(school.urn)]?.yearly_data ?? [];
|
||||||
const byYear = new Map<number, Record<string, unknown>>();
|
const byYear = new Map<number, Record<string, unknown>>();
|
||||||
for (const row of rows) byYear.set(Math.trunc(row.year), row as unknown as Record<string, unknown>);
|
for (const row of rows) {
|
||||||
|
if (!hasYear(row)) continue;
|
||||||
|
byYear.set(Math.trunc(row.year), row as unknown as Record<string, unknown>);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
label: school.school_name,
|
label: school.school_name,
|
||||||
data: years.map((year) => {
|
data: years.map((year) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user