feat(admissions): show the last distance offered where councils publish it
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 31s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 1m12s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 2m7s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 31s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 1m12s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 2m7s
Adds the cut-off distance a parent actually asks about — "how close do we
need to live?" — end to end: a Singer tap, dbt staging and mart models, an
Airflow DAG, and a tile on both detail templates. 3,597 schools across 57
local authorities carry a figure; the rest are unchanged.
There is no national source for this. Each LA publishes its own cut-offs in
its own format, and the collected CSV is transcribed from PDFs, spreadsheets
and web pages — so most of the work here is deciding what is safe to show.
Data
* tap-uk-school-distance loads the CSV verbatim into raw. Keyed on
(urn, year, school_name), because school_name carries the admission
route: (urn, year) alone collides on 118 keys and a reload would have
silently dropped every band but one.
* stg_school_distance applies a 25 m – 25 km plausibility band. The source
contains 0.0-mile rows (published where a school filled on a higher
criterion), 1-metre cut-offs, and one reading 533 miles — ~4% of rows,
all of which would put a visibly wrong number on a live page.
* fact_admission_distance collapses routes to one row per school per year
using the furthest, and keeps route_count so the page can say the figure
is the widest of several bands rather than the one for a given child.
Serving
* Kept out of fact_admissions: that mart is EES-derived and near-complete
for England, this one covers 57 LAs, and the two refresh independently.
* Latest year only. Coverage is ragged — a school may have 2021 and 2026
and nothing between — so a history array would invite a trend line drawn
through gaps that are absences of publication, not of a cut-off.
* The Admissions section now renders on either source. 3% of the schools
that render have a cut-off and no EES admissions row, and gating on
admissions alone would have hidden the figure on those pages.
Interface
* The year travels with the figure everywhere it appears; a cut-off
detached from its admissions round is not a fact about anything.
* "Not a fixed catchment — it moves every year" sits under every instance,
because that is the inference a parent will otherwise draw.
* Replaces a hardcoded "Historical distance cut-off data is not available
for this school" that appeared on every secondary page, including the
ones whose council does publish it. The absence is now stated only when
it is real, and names the authority that would hold it.
The tint costs the muted tokens their AA margin: measured on the composited
backdrop (not the computed one, which reports the untinted card), --text-muted
falls to 4.09:1 in dark theme. The tile uses --text-secondary instead — 6.50:1
dark, 6.60:1 light.
The DAG is manual, like the other annual ones: councils publish on allocation
day, each on its own timetable, so there is no date worth scheduling against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WDvkyqqHABm4bmth2kjAxE
This commit is contained in:
1 parent
5156a85bd1
commit
88c653215d
31 files changed
+1753
-42
No files matched your search
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Last distance offered, rendered on both detail templates.
|
||||
*
|
||||
* The figure is the one number on these pages that a parent may act on — it is
|
||||
* easy to read as "we live inside the catchment, we will get a place". These
|
||||
* tests pin the things that stop it being read that way: the year is always
|
||||
* present, the caveat is always present, and the blanket "not available"
|
||||
* sentence appears only when it is actually true.
|
||||
*/
|
||||
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderSchoolDetail, renderSecondarySchoolDetail } from '../support/renderSchoolDetail';
|
||||
import { primaryFixture, secondaryFixture } from '../support/schoolFixtures';
|
||||
import type { SchoolAdmissionDistance } from '@/lib/types';
|
||||
|
||||
const cutoff = (over: Partial<SchoolAdmissionDistance> = {}): SchoolAdmissionDistance => ({
|
||||
year: 2025,
|
||||
distance_m: 500,
|
||||
route_count: 1,
|
||||
la_name: 'Camden',
|
||||
distance_unit_raw: 'miles',
|
||||
...over,
|
||||
});
|
||||
|
||||
describe('primary detail page', () => {
|
||||
it('shows the figure with the year it belongs to', () => {
|
||||
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff({ distance_m: 772.49, year: 2024 }) });
|
||||
|
||||
expect(screen.getByText('0.48 miles')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Last distance offered/)).toHaveTextContent('September 2024');
|
||||
});
|
||||
|
||||
it('never shows the figure without saying it is not a catchment', () => {
|
||||
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff() });
|
||||
|
||||
expect(screen.getByText(/not a fixed catchment/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/moves every year/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('flags that a banded school\'s figure is the widest of several routes', () => {
|
||||
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff({ route_count: 4 }) });
|
||||
|
||||
expect(screen.getByText(/4 admission routes/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nothing distance-related when the LA publishes none', () => {
|
||||
renderSchoolDetail({ ...primaryFixture, admissionDistance: null });
|
||||
|
||||
expect(screen.queryByText(/Last distance offered/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/not a fixed catchment/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('carries the figure even with no EES admissions row', () => {
|
||||
// The two sources are independent; this school has a cut-off and no
|
||||
// admissions figures. Before this feature the section did not render at all.
|
||||
renderSchoolDetail({
|
||||
...primaryFixture,
|
||||
admissions: null,
|
||||
admissionsHistory: [],
|
||||
admissionDistance: cutoff({ distance_m: 1421.05 }),
|
||||
});
|
||||
|
||||
expect(screen.getByText('0.88 miles')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('secondary detail page', () => {
|
||||
it('shows the figure with the year it belongs to', () => {
|
||||
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: cutoff({ distance_m: 3472.96 }) });
|
||||
|
||||
expect(screen.getByText('2.16 miles')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Last distance offered/)).toHaveTextContent('September 2025');
|
||||
});
|
||||
|
||||
it('drops the blanket "not available" line once a distance exists', () => {
|
||||
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: cutoff() });
|
||||
|
||||
expect(screen.queryByText(/has not published a cut-off distance/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/not a fixed catchment/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('names the authority that would hold the data when there is none', () => {
|
||||
// The old copy asserted "Historical distance cut-off data is not available
|
||||
// for this school" on every secondary page, including the ones whose
|
||||
// council does publish it.
|
||||
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: null });
|
||||
|
||||
expect(screen.getByText(/has not published a cut-off distance/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user