"""Tests for the GIAS-driven has_sixth_form flag (spec 2026-07-07 §3). The filter and payloads must use dim_school.has_sixth_form, not the old age_range-contains-"18" substring heuristic. The key regression case is a 16-19 sixth-form college: flag true, but "16-19" contains no "18". """ import numpy as np import pandas as pd import pytest from fastapi.testclient import TestClient def _schools_df() -> pd.DataFrame: """Latest-year snapshot rows as produced by load_latest_school_data.""" base = { "local_authority": "Testshire", "school_type": "Academy", "phase": "Secondary", "address": "1 Test Street", "town": "Testtown", "postcode": "TS1 1AA", "religious_denomination": None, "gender": "Mixed", "admissions_policy": None, "ofsted_grade": np.nan, "ofsted_date": None, "ofsted_framework": None, "latitude": 51.5, "longitude": -0.1, "year": 202425, "total_pupils": 1000, "rwm_expected_pct": np.nan, "attainment_8_score": 50.0, } return pd.DataFrame( [ # 11-18 school WITH a registered sixth form {**base, "urn": 100001, "school_name": "Alpha High", "age_range": "11-18", "has_sixth_form": True}, # 16-19 college: old heuristic said NO ("16-19" has no "18"), # GIAS flag says YES — must appear in the yes-filter results {**base, "urn": 100002, "school_name": "Beta Sixth Form College", "age_range": "16-19", "has_sixth_form": True}, # 11-18 age range on paper but NO registered sixth form: # old heuristic said YES, GIAS flag says NO {**base, "urn": 100003, "school_name": "Gamma Academy", "age_range": "11-18", "has_sixth_form": False}, # Missing flag (pipeline not yet re-run) — must not crash, # must not match the yes-filter {**base, "urn": 100004, "school_name": "Delta School", "age_range": "11-16", "has_sixth_form": None}, ] ) @pytest.fixture() def client(monkeypatch): from backend import app as app_module monkeypatch.setattr(app_module, "load_latest_school_data", _schools_df) monkeypatch.setattr(app_module, "load_school_data", _schools_df) monkeypatch.setattr(app_module, "get_supplementary_data", lambda db, urn: {}) return TestClient(app_module.app, raise_server_exceptions=False) def _urns(resp): return sorted(s["urn"] for s in resp.json()["schools"]) def test_filter_yes_uses_flag_not_age_range(client): resp = client.get("/api/schools?has_sixth_form=yes") assert resp.status_code == 200, resp.text # 16-19 college included; 11-18-without-sixth-form excluded assert _urns(resp) == [100001, 100002] def test_filter_no_uses_flag_not_age_range(client): resp = client.get("/api/schools?has_sixth_form=no") assert resp.status_code == 200, resp.text # Gamma (flag false) and Delta (flag missing => not true) assert _urns(resp) == [100003, 100004] def test_list_payload_includes_flag(client): resp = client.get("/api/schools") assert resp.status_code == 200, resp.text by_urn = {s["urn"]: s for s in resp.json()["schools"]} assert by_urn[100002]["has_sixth_form"] is True assert by_urn[100003]["has_sixth_form"] is False assert by_urn[100004]["has_sixth_form"] is None def test_detail_payload_includes_flag(client): resp = client.get("/api/schools/100002") assert resp.status_code == 200, resp.text assert resp.json()["school_info"]["has_sixth_form"] is True