diff --git a/nextjs-app/__tests__/components/CompareAcademics.test.tsx b/nextjs-app/__tests__/components/CompareAcademics.test.tsx
new file mode 100644
index 0000000..b748755
--- /dev/null
+++ b/nextjs-app/__tests__/components/CompareAcademics.test.tsx
@@ -0,0 +1,65 @@
+/**
+ * Secondary academics: every headline number carries its England anchor and a
+ * verdict chip (expert sign-off SF1 — the grade-5 and EBacc rows previously
+ * rendered as bare numbers, breaking the "anchored against England" promise).
+ */
+
+import { render, screen, within } from '@testing-library/react';
+
+import { CompareAcademics } from '@/components/compare/CompareAcademics';
+import type { ComparisonData, NationalAverages, School } from '@/lib/types';
+
+function school(urn: number, name: string): School {
+ return { urn, school_name: name, attainment_8_score: 58.7 } as School;
+}
+
+function data(urn: number): ComparisonData {
+ return {
+ school_info: school(urn, 'Test High'),
+ yearly_data: [
+ {
+ year: 202425,
+ attainment_8_score: 58.7,
+ english_maths_strong_pass_pct: 30,
+ ebacc_entry_pct: 10,
+ },
+ ] as ComparisonData['yearly_data'],
+ ofsted: null,
+ census: null,
+ admissions: null,
+ admissions_history: [],
+ deprivation: null,
+ };
+}
+
+const NATIONAL: NationalAverages = {
+ year: 202425,
+ primary: {},
+ secondary: {
+ attainment_8_score: 46.0,
+ english_maths_strong_pass_pct: 45.4,
+ ebacc_entry_pct: 40.5,
+ },
+ by_year: [],
+};
+
+test('grade-5 and EBacc rows show the England anchor and a Below chip when under it', () => {
+ const s = school(137086, 'Bishop Stopford School');
+ render(
+ ,
+ );
+
+ // The official anchors appear (45.4% and 40.5%), not just the school numbers.
+ expect(screen.getByText(/England average 45%/)).toBeInTheDocument();
+ expect(screen.getByText(/England average 41%/)).toBeInTheDocument();
+
+ // 30% grade-5 and 10% EBacc are both well below their anchors → Below chips.
+ // Attainment 8 (58.7 vs 46.0) is above → at least one "Above" chip too.
+ expect(screen.getAllByText(/Below England average/).length).toBeGreaterThanOrEqual(2);
+ expect(screen.getAllByText(/Above England average/).length).toBeGreaterThanOrEqual(1);
+});
diff --git a/nextjs-app/app/globals.css b/nextjs-app/app/globals.css
index 8eb0779..1b66d32 100644
--- a/nextjs-app/app/globals.css
+++ b/nextjs-app/app/globals.css
@@ -183,10 +183,15 @@ body {
}
/* Secondary: teal outline — supporting actions (+ Compare) */
+/* NOTE: a duplicate `.btn` block further down this file sets `border: none`,
+ which wins over the base `.btn`'s `1px solid transparent`. The outline
+ variants below therefore declare the full `border` shorthand explicitly so
+ they don't depend on the base border-width — otherwise `border-color` alone
+ has no width and the outline never renders (buttons read as plain text). */
.btn-secondary {
background: transparent;
color: var(--accent-teal);
- border-color: var(--accent-teal);
+ border: 1px solid var(--accent-teal);
}
.btn-secondary:hover:not(:disabled) {
background: var(--accent-teal-bg);
@@ -196,7 +201,7 @@ body {
.btn-tertiary {
background: var(--bg-secondary);
color: var(--text-secondary);
- border-color: var(--border-color);
+ border: 1px solid var(--border-color);
}
.btn-tertiary:hover:not(:disabled) {
background: var(--border-color);
@@ -207,7 +212,7 @@ body {
.btn-active {
background: var(--accent-teal-bg);
color: var(--accent-teal);
- border-color: var(--accent-teal);
+ border: 1px solid var(--accent-teal);
}
.btn-active:hover:not(:disabled) {
background: transparent;
diff --git a/nextjs-app/components/compare/CompareAcademics.tsx b/nextjs-app/components/compare/CompareAcademics.tsx
index 08de1f0..6660a81 100644
--- a/nextjs-app/components/compare/CompareAcademics.tsx
+++ b/nextjs-app/components/compare/CompareAcademics.tsx
@@ -162,6 +162,21 @@ export function CompareAcademics({
const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct');
const ebacc = latestValues(data, urns, 'ebacc_entry_pct');
const att8Anchor = nationalAverages?.secondary?.attainment_8_score;
+ const grade5Anchor = nationalAverages?.secondary?.english_maths_strong_pass_pct;
+ const ebaccAnchor = nationalAverages?.secondary?.ebacc_entry_pct;
+
+ // Every headline number gets its England anchor + verdict chip, so the
+ // "anchored against the England average" promise holds for the grade-5
+ // and EBacc rows too, not just Attainment 8.
+ const anchorChip = (value: number | null, anchor: number | null | undefined, tol: number) => {
+ if (value == null || anchor == null) return null;
+ const v = verdict(value, anchor, tol);
+ return (
+
+ {v === 'above' ? 'Above' : v === 'below' ? 'Below' : 'Close to'} England average
+
+ );
+ };
return (
{att8[i] != null ? (
<>
- {(att8[i] as number).toFixed(1)}
+ {(att8[i] as number).toFixed(1)}{' '}
+ {anchorChip(att8[i], att8Anchor, 2)}
{att8Anchor != null && (
England average {att8Anchor.toFixed(1)}
)}
@@ -216,14 +232,38 @@ export function CompareAcademics({
{schools.map((school, i) => (
|
- {grade5[i] != null ? `${Math.round(grade5[i] as number)}%` : No data}
+ {grade5[i] != null ? (
+ <>
+
+ {Math.round(grade5[i] as number)}%
+ {' '}
+ {anchorChip(grade5[i], grade5Anchor, 3)}
+ {grade5Anchor != null && (
+ England average {Math.round(grade5Anchor)}%
+ )}
+ >
+ ) : (
+ No data
+ )}
|
))}
EBacc entry
{schools.map((school, i) => (
- {ebacc[i] != null ? `${Math.round(ebacc[i] as number)}%` : No data}
+ {ebacc[i] != null ? (
+ <>
+
+ {Math.round(ebacc[i] as number)}%
+ {' '}
+ {anchorChip(ebacc[i], ebaccAnchor, 3)}
+ {ebaccAnchor != null && (
+ England average {Math.round(ebaccAnchor)}%
+ )}
+ >
+ ) : (
+ No data
+ )}
|
))}