fix(data): drop the Welsh school GIAS does not type as Welsh #109

Merged
tudor merged 1 commits from fix/welsh-establishment-leak into main 2026-08-20 21:59:39 +00:00
5 changed files with 69 additions and 0 deletions

No files matched your search

+26
View File
@@ -1545,6 +1545,32 @@ test('searching a Welsh authority by name returns no schools', async ({ page })
expect(body.total ?? (body.schools ?? []).length).toBe(0); expect(body.total ?? (body.schools ?? []).length).toBe(0);
}); });
test('a Welsh school GIAS does not type as Welsh is gone too', async ({ page }) => {
// Beechwood College, Sully, CF64 5SE. GIAS types it "Special post 16
// institution" (32), not "Welsh establishment" (30), so the establishment
// type filter alone left it behind — it was the last Welsh school standing.
const res = await page.request.get('/api/schools?local_authority=Vale%20of%20Glamorgan&page_size=1');
expect(res.ok()).toBeTruthy();
const body = await res.json();
expect(body.total ?? (body.schools ?? []).length).toBe(0);
const page404 = await page.goto('/school/142458-beechwood-college');
expect(page404?.status()).toBe(404);
});
test('English schools with Welsh postcodes are kept', async ({ page }) => {
// Royal Mail postcode areas straddle the border: these Gloucestershire
// schools carry NP16/NP25 postcodes but are English schools with English
// data. A postcode-based filter would have wrongly deleted them, so this
// guards the fix from being "simplified" into one.
for (const urn of [115639, 115655, 137382]) {
const res = await page.request.get(`/api/schools/${urn}`);
expect(res.ok(), `URN ${urn} should still resolve`).toBeTruthy();
const { school_info } = await res.json();
expect(school_info.local_authority).toBe('Gloucestershire');
}
});
test('a Welsh school URL 404s while an English one still resolves', async ({ page }) => { test('a Welsh school URL 404s while an English one still resolves', async ({ page }) => {
// Paired on purpose: the Welsh assertion alone would also pass if the whole // Paired on purpose: the Welsh assertion alone would also pass if the whole
// site were down, which is the failure this test most needs to distinguish. // site were down, which is the failure this test most needs to distinguish.
+13
View File
@@ -24,6 +24,19 @@ clean-targets: ["target", "dbt_packages"]
vars: vars:
non_england_school_type_codes: [25, 26, 30, 37] non_england_school_type_codes: [25, 26, 30, 37]
# Type alone is not enough. Beechwood College (URN 142458, Sully, CF64 5SE)
# is a Welsh school that GIAS types as a Special post 16 institution (32)
# rather than a Welsh establishment (30), so the type filter let it through.
# LA code is the reliable discriminator: GIAS gives the 22 Welsh unitary
# authorities the contiguous block 660-681, which English authorities never
# use (they occupy 201-384 and 800-938).
#
# Filtering on postcode would be wrong: Redbrook, Tutshill, Wyedean and two
# other Gloucestershire schools carry NP16/NP25 postcodes because Royal Mail
# areas straddle the border, and they are English schools with English data.
welsh_la_code_min: 660
welsh_la_code_max: 681
models: models:
school_compare: school_compare:
staging: staging:
@@ -37,3 +37,5 @@ where s.status_code in (1, 3)
-- coalesce, not a bare NOT IN: a null type code would make the predicate -- coalesce, not a bare NOT IN: a null type code would make the predicate
-- null and drop the row silently. Unknown type is not grounds for exclusion. -- null and drop the row silently. Unknown type is not grounds for exclusion.
and coalesce(s.school_type_code, -1) not in ({{ var('non_england_school_type_codes') | join(', ') }}) and coalesce(s.school_type_code, -1) not in ({{ var('non_england_school_type_codes') | join(', ') }})
-- Welsh schools GIAS does not type as Welsh establishments (see vars).
and coalesce(s.local_authority_code, -1) not between {{ var('welsh_la_code_min') }} and {{ var('welsh_la_code_max') }}
@@ -96,3 +96,5 @@ where s.status_code in (1, 3)
-- coalesce, not a bare NOT IN: a null type code would make the predicate -- coalesce, not a bare NOT IN: a null type code would make the predicate
-- null and drop the row silently. Unknown type is not grounds for exclusion. -- null and drop the row silently. Unknown type is not grounds for exclusion.
and coalesce(s.school_type_code, -1) not in ({{ var('non_england_school_type_codes') | join(', ') }}) and coalesce(s.school_type_code, -1) not in ({{ var('non_england_school_type_codes') | join(', ') }})
-- Welsh schools GIAS does not type as Welsh establishments (see vars).
and coalesce(s.local_authority_code, -1) not between {{ var('welsh_la_code_min') }} and {{ var('welsh_la_code_max') }}
@@ -26,3 +26,29 @@ select
from {{ ref('dim_location') }} l from {{ ref('dim_location') }} l
left join {{ ref('dim_school') }} s on l.urn = s.urn left join {{ ref('dim_school') }} s on l.urn = s.urn
where s.urn is null where s.urn is null
union all
-- Belt and braces on the Welsh LA code range.
--
-- The range 660-681 is documented GIAS structure, not something this project
-- can verify from its own data — local_authority_code is not exposed anywhere
-- the app can assert on. So the range does the filtering and the authority
-- NAME does the checking: if 660-681 is ever wrong or incomplete, a Welsh
-- authority reappears here and the pipeline fails loudly rather than the
-- corpus quietly regrowing a Welsh tail.
--
-- Beechwood College (142458) is why this exists: a Welsh school GIAS types as
-- a Special post 16 institution, which the type filter alone let through.
select
l.urn,
l.local_authority_name as school_name,
null::integer as school_type_code
from {{ ref('dim_location') }} l
where l.local_authority_name in (
'Isle of Anglesey', 'Gwynedd', 'Conwy', 'Denbighshire', 'Flintshire',
'Wrexham', 'Powys', 'Ceredigion', 'Pembrokeshire', 'Carmarthenshire',
'Swansea', 'Neath Port Talbot', 'Bridgend', 'Vale of Glamorgan',
'Rhondda Cynon Taf', 'Merthyr Tydfil', 'Caerphilly', 'Blaenau Gwent',
'Torfaen', 'Monmouthshire', 'Newport', 'Cardiff'
)