2026-07-09 10:38:19 +01:00
|
|
|
"""Tests for the GIAS code->name dictionaries (spec 2026-07-09).
|
|
|
|
|
|
|
|
|
|
The dictionaries are generated from the live GIAS bulk CSV by
|
|
|
|
|
pipeline/scripts/generate_gias_codes.py — these tests assert the module's
|
|
|
|
|
contract, key sentinel values the marts/UI depend on, and that the pipeline
|
|
|
|
|
copy has not drifted from the canonical backend module.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import math
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from backend.gias_codes import (
|
|
|
|
|
ADMISSIONS_POLICY,
|
|
|
|
|
ESTABLISHMENT_STATUS,
|
|
|
|
|
OFFICIAL_SIXTH_FORM,
|
|
|
|
|
PHASE_OF_EDUCATION,
|
|
|
|
|
RELIGIOUS_CHARACTER,
|
|
|
|
|
SCHOOL_TYPE,
|
|
|
|
|
translate,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_translate_known_code():
|
|
|
|
|
open_code = next(c for c, n in ESTABLISHMENT_STATUS.items() if n == "Open")
|
|
|
|
|
assert translate(open_code, ESTABLISHMENT_STATUS) == "Open"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_translate_unknown_code_degrades_gracefully():
|
|
|
|
|
assert translate(9999, ESTABLISHMENT_STATUS) == "Unknown (9999)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_translate_none_and_nan_return_none():
|
|
|
|
|
assert translate(None, ESTABLISHMENT_STATUS) is None
|
|
|
|
|
assert translate(float("nan"), ESTABLISHMENT_STATUS) is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_translate_accepts_float_codes():
|
|
|
|
|
# pd.read_sql yields float columns when NULLs are present
|
|
|
|
|
open_code = next(c for c, n in ESTABLISHMENT_STATUS.items() if n == "Open")
|
|
|
|
|
assert translate(float(open_code), ESTABLISHMENT_STATUS) == "Open"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sentinel_names_present():
|
|
|
|
|
"""Names the marts/UI compare against must exist verbatim."""
|
|
|
|
|
assert "Open" in ESTABLISHMENT_STATUS.values()
|
|
|
|
|
assert "Open, but proposed to close" in ESTABLISHMENT_STATUS.values()
|
|
|
|
|
assert "Has a sixth form" in OFFICIAL_SIXTH_FORM.values()
|
|
|
|
|
assert "Primary" in PHASE_OF_EDUCATION.values()
|
|
|
|
|
assert "Secondary" in PHASE_OF_EDUCATION.values()
|
|
|
|
|
assert "Does not apply" in RELIGIOUS_CHARACTER.values()
|
|
|
|
|
assert all(len(d) > 0 for d in (
|
|
|
|
|
SCHOOL_TYPE, ESTABLISHMENT_STATUS, PHASE_OF_EDUCATION,
|
|
|
|
|
OFFICIAL_SIXTH_FORM, RELIGIOUS_CHARACTER, ADMISSIONS_POLICY,
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pipeline_copy_is_identical():
|
|
|
|
|
canonical = (REPO / "backend" / "gias_codes.py").read_text()
|
|
|
|
|
copy = (REPO / "pipeline" / "scripts" / "gias_codes.py").read_text()
|
|
|
|
|
assert canonical == copy, (
|
|
|
|
|
"pipeline/scripts/gias_codes.py has drifted from backend/gias_codes.py — "
|
|
|
|
|
"regenerate with pipeline/scripts/generate_gias_codes.py and copy the file"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_seed_matches_dictionaries():
|
|
|
|
|
import csv
|
|
|
|
|
fields = {
|
|
|
|
|
"school_type": SCHOOL_TYPE,
|
|
|
|
|
"establishment_status": ESTABLISHMENT_STATUS,
|
|
|
|
|
"phase_of_education": PHASE_OF_EDUCATION,
|
|
|
|
|
"official_sixth_form": OFFICIAL_SIXTH_FORM,
|
|
|
|
|
"religious_character": RELIGIOUS_CHARACTER,
|
|
|
|
|
"admissions_policy": ADMISSIONS_POLICY,
|
|
|
|
|
}
|
|
|
|
|
seed_path = REPO / "pipeline" / "transform" / "seeds" / "gias_code_names.csv"
|
|
|
|
|
seed: dict[str, dict[int, str]] = {k: {} for k in fields}
|
|
|
|
|
with open(seed_path, newline="") as fh:
|
|
|
|
|
for row in csv.DictReader(fh):
|
|
|
|
|
seed[row["field"]][int(row["code"])] = row["name"]
|
|
|
|
|
assert seed == fields
|
2026-07-09 22:04:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_blank_name_sentinel_codes_map_to_empty_string():
|
|
|
|
|
"""GIAS carries codes whose (name) column is blank — e.g. ReligiousCharacter
|
|
|
|
|
99 (~4k schools) and AdmissionsPolicy 9 (~5.6k schools). The old name
|
|
|
|
|
pipeline served these as empty strings; the dictionaries must reproduce
|
|
|
|
|
that ("" is falsy, so UI tag heuristics stay silent) rather than letting
|
|
|
|
|
them hit the "Unknown (<code>)" path meant for genuinely new codes."""
|
|
|
|
|
assert RELIGIOUS_CHARACTER[99] == ""
|
|
|
|
|
assert ADMISSIONS_POLICY[9] == ""
|
|
|
|
|
assert translate(99, RELIGIOUS_CHARACTER) == ""
|
|
|
|
|
assert translate(9, ADMISSIONS_POLICY) == ""
|