fix(api): inspectorate and Ofsted listing in payloads; Ofsted links that resolve (H4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TudorandClaude Opus 5.5 committed 2026-10-13 21:32:06 +01:00
1 parent 2b5e681482
commit 69da9d84db
7 files changed
+89 -6

No files matched your search

+8
View File
@@ -1048,6 +1048,11 @@ async def get_school_details(request: Request, urn: int):
"age_range": latest.get("age_range", ""),
"has_sixth_form": latest.get("has_sixth_form"),
"nursery_provision": latest.get("nursery_provision"),
# What the Ofsted badge may say when there is no inspection record
# (audit H4): the inspectorate GIAS names, and whether Ofsted's own
# data lists the school at all.
"inspectorate": latest.get("inspectorate"),
"ofsted_listed": latest.get("ofsted_listed"),
# The header's Selective flag reads it (lib/schoolFacts).
"admissions_policy": latest.get("admissions_policy"),
"status": latest.get("status"),
@@ -1210,6 +1215,9 @@ async def compare_schools(
# schools get entrance-test framing, never the distance template.
"admissions_policy": convert_to_native(latest.get("admissions_policy")),
"has_sixth_form": convert_to_native(latest.get("has_sixth_form")),
# Audit H4: what compare may say when there is no inspection record.
"inspectorate": convert_to_native(latest.get("inspectorate")),
"ofsted_listed": convert_to_native(latest.get("ofsted_listed")),
"capacity": convert_to_native(latest.get("capacity")),
"gias_total_pupils": convert_to_native(latest.get("gias_total_pupils")),
"trust_name": convert_to_native(latest.get("trust_name")),
+8 -1
View File
@@ -251,6 +251,8 @@ _MAIN_QUERY = text("""
s.website,
s.telephone,
s.nursery_provision,
s.inspectorate,
s.ofsted_listed,
foi.current_grade AS ofsted_grade,
foi.current_grade_date AS ofsted_grade_date,
foi.latest_visit_date AS ofsted_date,
@@ -376,7 +378,8 @@ for _old, _new in _LEGACY_NAME_REPLACEMENTS:
_MAIN_QUERY_LEGACY_NAMES = text(_MAIN_QUERY_LEGACY_NAMES)
# Fallback used when marts predate the optional GIAS/location detail columns
# (telephone, nursery provision, county, parliamentary constituency) — i.e. the
# (telephone, nursery provision, county, parliamentary constituency, and the
# inspectorate / Ofsted-listing columns added for audit H4) — i.e. the
# nightly dbt pipeline hasn't rebuilt the mart yet on this DB. Keeps each column
# present as NULL so the whole data load degrades gracefully instead of failing.
_EXTRA_OPTIONAL_COLUMNS = (
@@ -384,6 +387,8 @@ _EXTRA_OPTIONAL_COLUMNS = (
"nursery_provision",
"county",
"parliamentary_constituency",
"inspectorate",
"ofsted_listed",
)
_MAIN_QUERY_NO_EXTRA_COLS = str(_MAIN_QUERY)
for _col, _tbl in (
@@ -391,6 +396,8 @@ for _col, _tbl in (
("nursery_provision", "s"),
("county", "l"),
("parliamentary_constituency", "l"),
("inspectorate", "s"),
("ofsted_listed", "s"),
):
_src = f"{_tbl}.{_col},"
assert _src in _MAIN_QUERY_NO_EXTRA_COLS, (
+6 -3
View File
@@ -39,6 +39,9 @@ def report_card_labels(ofsted: dict) -> dict:
def ofsted_page_url(urn: int) -> str:
"""The school's page on ofsted.gov.uk (all its reports live there —
we never deep-link an individual report)."""
return f"https://reports.ofsted.gov.uk/provider/21/{urn}"
"""The school's page on ofsted.gov.uk (all its reports live there — we
never deep-link an individual report). Ofsted's provider pages sit under a
category (21 primary, 23 secondary, 25 special, 40 FE colleges, …); this
address redirects to the right one, where provider/21 404s for every
non-primary school."""
return f"https://reports.ofsted.gov.uk/inspection-reports/find-inspection-report/provider/ELS/{urn}"
+2
View File
@@ -578,6 +578,8 @@ SCHOOL_COLUMNS = [
"ofsted_date",
"ofsted_framework",
"ofsted_rc_date",
"inspectorate",
"ofsted_listed",
"latitude",
"longitude",
]
@@ -0,0 +1,63 @@
"""Audit H4: every payload that feeds an Ofsted badge carries the inspectorate
GIAS names and whether Ofsted's own data lists the school, so the site never
says "Not yet inspected" on the strength of missing data."""
import pandas as pd
import pytest
from fastapi.testclient import TestClient
def _df():
base = dict(year=202425, local_authority='Cambridgeshire', local_authority_code=873,
postcode='CB1 1AA', latitude=52.2, longitude=0.12, address='1 High St',
status='Open', town='Cambridge')
return pd.DataFrame([
dict(base, urn=136083, school_name='Abbey College Cambridge', phase='Secondary',
school_type='Other independent school', inspectorate='ISI', ofsted_listed=False),
dict(base, urn=150031, school_name='Abbas and Templecombe Church School', phase='Primary',
school_type='Academy converter', inspectorate='Not applicable', ofsted_listed=True),
])
@pytest.fixture()
def client(monkeypatch):
from backend import app as app_module
from backend import database as database_module
def _no_db():
raise RuntimeError('no database in this test')
monkeypatch.setattr(app_module, 'load_latest_school_data', _df)
monkeypatch.setattr(app_module, 'load_school_data', _df)
monkeypatch.setattr(app_module, 'get_supplementary_data', lambda db, urn: {})
monkeypatch.setattr(app_module, '_place_registry', None)
monkeypatch.setattr(database_module, 'SessionLocal', _no_db)
return TestClient(app_module.app, raise_server_exceptions=False)
def test_the_list_carries_inspectorate_and_listing(client):
resp = client.get('/api/schools?page_size=50')
assert resp.status_code == 200, resp.text
by_urn = {s['urn']: s for s in resp.json()['schools']}
assert by_urn[136083]['inspectorate'] == 'ISI'
assert by_urn[150031]['ofsted_listed'] is True
def test_the_school_page_carries_them(client):
info = client.get('/api/schools/136083').json()['school_info']
assert info['inspectorate'] == 'ISI'
assert info['ofsted_listed'] is False
def test_compare_carries_them(client):
body = client.get('/api/compare?urns=136083,150031').json()
assert body['comparison']['150031']['school_info']['ofsted_listed'] is True
assert body['comparison']['136083']['school_info']['inspectorate'] == 'ISI'
def test_a_mart_built_before_the_columns_still_loads():
# Missing columns make the main query retry with NULLs (data_loader).
from backend import data_loader
assert {'inspectorate', 'ofsted_listed'} <= set(data_loader._EXTRA_OPTIONAL_COLUMNS)
fallback = str(data_loader._MAIN_QUERY_NO_EXTRA_COLS)
assert 'NULL AS inspectorate,' in fallback and 'NULL AS ofsted_listed,' in fallback
+1 -1
View File
@@ -42,4 +42,4 @@ def test_unknown_code_is_skipped_not_crashed():
def test_provider_url():
assert ofsted_page_url(138690) == "https://reports.ofsted.gov.uk/provider/21/138690"
assert ofsted_page_url(138690) == "https://reports.ofsted.gov.uk/inspection-reports/find-inspection-report/provider/ELS/138690"
@@ -33,7 +33,7 @@ def test_report_card_block_and_provider_url():
assert block["report_card"]["rc_achievement"]["label"] == "Strong standard"
assert "rc_safeguarding_met" not in block["report_card"]
assert block["rc_safeguarding_met"] is True
assert block["ofsted_page_url"] == "https://reports.ofsted.gov.uk/provider/21/100140"
assert block["ofsted_page_url"] == "https://reports.ofsted.gov.uk/inspection-reports/find-inspection-report/provider/ELS/100140"
def test_no_grade_is_carried_past_a_newer_inspection():