Compare commits

...
9 Commits
Author SHA1 Message Date
Tudor 90b7d09086 Merge #183's review fixes into the site branch
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m26s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 52s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 26s
2026-10-05 16:39:34 +01:00
TudorandClaude Opus 5.5 f86d6f45d1 test(e2e): no grade is carried past a no-grade inspection
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m25s
PR Checks / Build Pipeline (no push) (pull_request) Canceled after 45s
PR Checks / AI Code Review (Claude) (pull_request) Canceled after 0s
Rabbsfarm (102408) must read 'Inspected · 2025' with no overall grade, and Robins Lane (104762) must say its Good was confirmed at an ungraded inspection on 18 July 2024. Both fail against staging today (no current_grade field, no confirmation line) and pass once PR #183's pipeline run and this branch are deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:33:53 +01:00
TudorandClaude Opus 5.5 5eed09dfa7 refactor(utils): drop the unused Ofsted hero chip and summary sentence
buildOfstedHeroChip and buildSchoolSummary were rendered nowhere and encoded the old carried-forward rule (and a framework value, 'ReportCard', the API never sends).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:32:53 +01:00
TudorandClaude Opus 5.5 d37baa572b fix(compare): Ofsted rows show the grade still in force and the latest visit
'Inspected' is the school's latest visit, so Washwood Heath reads 21 May 2025 rather than '3 Mar 2020 · 4+ years ago' (audit M1). The Result row names the inspection a grade came from, or says 'No overall grade'; 'Grade carried forward' and 'transitional framework' go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:32:19 +01:00
TudorandClaude Opus 5.5 9ef48da4b1 fix(school): the Ofsted section shows the grade still in force and the latest visit
The section is dated by the latest visit and headlines the grade still in force with the inspection that awarded or confirmed it, or 'No overall grade'. A later visit that isn't the grade's source gets its own line, with an ungraded outcome such as 'Standards maintained'. The primary and secondary no-grade branches merge, so the secondary page no longer drops the sixth-form and early-years judgements (audit M2), and 'Not rated' goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:31:26 +01:00
TudorandClaude Opus 5.5 a42c586cc8 fix(ofsted): badge and compare read the grade still in force
Search badges date a grade by the inspection that awarded or confirmed it (ofsted_grade_date) and say 'Inspected · year' when the latest inspection gave no grade. ofstedDisplay's kinds become graded / confirmed / no_overall_grade, read from current_grade rather than a carried-forward overall_effectiveness. lib/ofstedStatus.ts holds the two sentences the school and compare pages share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:29:54 +01:00
TudorandClaude Opus 5.5 a9e3a6a700 fix(backend): serve the current Ofsted status from fact_ofsted_latest
The list query and the batch Ofsted fetch each picked 'the latest row' of fact_ofsted_inspection themselves, and _ofsted_block carried an older ungraded grade forward when the latest graded inspection gave none. All three now read marts.fact_ofsted_latest. List rows: ofsted_grade is the grade still in force, ofsted_grade_date when it was awarded or confirmed, ofsted_date the latest visit. The ofsted block gains current_grade and latest_visit and loses grade_source; overall_effectiveness is the graded inspection's own result. A school with only an inspection stays publishable in the sitemap.

Requires fact_ofsted_latest (PR #183's pipeline run) on the database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:28:30 +01:00
TudorandClaude Opus 5.5 96732c56d1 fix(pipeline): dim_school's Ofsted grade is the one still in force
dim_school.ofsted_grade (Typesense's rating) coalesced the graded grade with an older ungraded visit's, so Rabbsfarm's 2020 'remains Good' survived its 2025 no-grade inspection. It now reads int_ofsted_latest's current_grade, and ofsted_date is the latest visit. Unit tests pin both (audit C1, M1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:26:51 +01:00
Tudor befaf950a7 Merge PR 1 (fix/ofsted-current-status-pipeline, #183) into the site branch
PR 2 reads the columns PR 1 adds. Merge main back in once #183 lands.
2026-10-05 16:25:49 +01:00
26 changed files with 642 additions and 408 deletions

No files matched your search

+3 -2
View File
@@ -97,8 +97,9 @@ STATIC_SITEMAP_PATHS = ("/", "/rankings", "/compare", "/admissions")
# A page has something a search result could state if any of these is present
# in any year. Shared by _has_publishable_data and the per-school check in
# _school_sitemap_rows so the two can never drift.
_PUBLISHABLE_FIELDS = ("rwm_expected_pct", "attainment_8_score", "ofsted_grade")
# _school_sitemap_rows so the two can never drift. An inspection with no
# overall grade (ofsted_date alone) still gives a page something to state.
_PUBLISHABLE_FIELDS = ("rwm_expected_pct", "attainment_8_score", "ofsted_grade", "ofsted_date")
def _has_publishable_data(row) -> bool:
+37 -56
View File
@@ -18,7 +18,7 @@ from .config import settings
from .database import SessionLocal, engine
from .models import (
DimSchool, DimLocation, KS2Performance,
FactOfstedInspection, FactAdmissions, FactAdmissionDistance,
FactOfstedLatest, FactAdmissions, FactAdmissionDistance,
FactDeprivation, FactFinance, FactPupilCharacteristics,
FactKs4Destinations, FactKs5Destinations,
)
@@ -251,10 +251,11 @@ _MAIN_QUERY = text("""
s.website,
s.telephone,
s.nursery_provision,
foi.ofsted_grade,
foi.ofsted_date,
foi.ofsted_framework,
foi.ofsted_rc_date,
foi.current_grade AS ofsted_grade,
foi.current_grade_date AS ofsted_grade_date,
foi.latest_visit_date AS ofsted_date,
foi.framework AS ofsted_framework,
foi.rc_inspection_date AS ofsted_rc_date,
l.local_authority_name AS local_authority,
l.local_authority_code,
l.address_line1 AS address1,
@@ -335,21 +336,10 @@ _MAIN_QUERY = text("""
FROM marts.dim_school s
JOIN marts.dim_location l ON s.urn = l.urn
LEFT JOIN marts.fact_performance p ON s.urn = p.urn
LEFT JOIN (
SELECT DISTINCT ON (urn)
urn,
-- Fall back to the ungraded-inspection grade when no graded grade exists.
COALESCE(overall_effectiveness, ungraded_grade) AS ofsted_grade,
inspection_date AS ofsted_date,
framework AS ofsted_framework,
-- Report-card signal for list/map badges: non-null only when the
-- latest inspection carries report-card grades. framework is the
-- raw event grouping ("Schools - S5"), never "ReportCard", so it
-- can't be used to detect report cards.
rc_inspection_date AS ofsted_rc_date
FROM marts.fact_ofsted_inspection
ORDER BY urn, inspection_date DESC NULLS LAST
) foi ON s.urn = foi.urn
-- One current Ofsted status per school (pipeline: int_ofsted_latest): the
-- grade still in force, dated by the inspection that awarded or confirmed
-- it, and the latest visit of any kind.
LEFT JOIN marts.fact_ofsted_latest foi ON s.urn = foi.urn
ORDER BY s.school_name, p.year
""")
@@ -731,39 +721,36 @@ def compute_benchmarks(df: pd.DataFrame, census_benchmarks: dict | None = None)
}
def _iso(d):
return d.isoformat() if d else None
def _ofsted_block(o, urn: int) -> dict:
"""Serialize the latest Ofsted inspection row for API responses.
"""Serialize a fact_ofsted_latest row for API responses.
`grade_source` records where the effective overall grade came from:
a graded (Section 5) inspection, or carried forward from an ungraded
(Section 8) outcome — materially different claims a UI must be able
to distinguish. `report_card` holds coded+labelled renewed-framework
(Nov 2025) area judgements; safeguarding is a separate boolean and
never appears among the graded areas.
`current_grade` is the overall grade still in force, dated by the
inspection that awarded or confirmed it; `latest_visit` is the school's
most recent inspection of any kind. The rule lives in int_ofsted_latest
(docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md).
`overall_effectiveness` and `inspection_date` describe the graded
inspection itself and label its area judgements. `report_card` holds the
renewed-framework (Nov 2025) area judgements; safeguarding is a separate
boolean and never appears among the graded areas.
"""
if o.overall_effectiveness is not None:
grade_source = "graded"
overall = o.overall_effectiveness
elif o.ungraded_grade is not None:
# Fall back to the grade parsed from an ungraded (Section 8) outcome
# (e.g. "School remains Good") so the detail page matches the list badge.
grade_source = "ungraded_carried_forward"
overall = o.ungraded_grade
else:
grade_source = None
overall = None
block = {
"framework": o.framework,
"inspection_date": o.inspection_date.isoformat() if o.inspection_date else None,
"rc_inspection_date": (
o.rc_inspection_date.isoformat()
if getattr(o, "rc_inspection_date", None)
else None
),
"inspection_date": _iso(o.graded_inspection_date),
"rc_inspection_date": _iso(o.rc_inspection_date),
"inspection_type": o.inspection_type,
"overall_effectiveness": overall,
"grade_source": grade_source,
"overall_effectiveness": o.overall_effectiveness if o.overall_effectiveness in (1, 2, 3, 4) else None,
"current_grade": (
{"grade": o.current_grade, "date": _iso(o.current_grade_date), "basis": o.current_grade_basis}
if o.current_grade is not None else None
),
"latest_visit": (
{"date": _iso(o.latest_visit_date), "kind": o.latest_visit_kind, "outcome": o.latest_visit_outcome}
if o.latest_visit_date else None
),
"quality_of_education": o.quality_of_education,
"behaviour_attitudes": o.behaviour_attitudes,
"personal_development": o.personal_development,
@@ -1097,20 +1084,14 @@ def get_supplementary_data_batch(db: Session, urns: list[int]) -> dict:
logging.getLogger(__name__).error("batch supplementary query failed: %s", e)
db.rollback()
# Ofsted — latest inspection per URN. Ordered so the first row seen per
# URN is the most recent.
# Ofsted — the mart already holds one current status per URN.
def _ofsted():
rows = (
db.query(FactOfstedInspection)
.filter(FactOfstedInspection.urn.in_(urns))
.order_by(FactOfstedInspection.urn, FactOfstedInspection.inspection_date.desc())
db.query(FactOfstedLatest)
.filter(FactOfstedLatest.urn.in_(urns))
.all()
)
seen = set()
for o in rows:
if o.urn in seen:
continue
seen.add(o.urn)
result[o.urn]["ofsted"] = _ofsted_block(o, o.urn)
_safe(_ofsted)
+43
View File
@@ -162,6 +162,49 @@ class FactOfstedInspection(Base):
report_url = Column(Text)
class FactOfstedLatest(Base):
"""Current Ofsted status — one row per URN (pipeline: int_ofsted_latest).
`current_grade` is the overall grade still in force, dated by the
inspection that awarded or confirmed it; `latest_visit_*` is the school's
most recent inspection of any kind.
"""
__tablename__ = "fact_ofsted_latest"
__table_args__ = MARTS
urn = Column(Integer, primary_key=True)
latest_visit_date = Column(Date)
latest_visit_kind = Column(String(20))
latest_visit_outcome = Column(String(100))
current_grade = Column(Integer)
current_grade_date = Column(Date)
current_grade_basis = Column(String(20))
graded_inspection_date = Column(Date)
ungraded_inspection_date = Column(Date)
rc_inspection_date = Column(Date)
inspection_type = Column(String(100))
framework = Column(String(20))
overall_effectiveness = Column(Integer)
quality_of_education = Column(Integer)
behaviour_attitudes = Column(Integer)
personal_development = Column(Integer)
leadership_management = Column(Integer)
early_years_provision = Column(Integer)
sixth_form_provision = Column(Integer)
ungraded_outcome = Column(String(100))
ungraded_grade = Column(Integer)
rc_safeguarding_met = Column(Boolean)
rc_inclusion = Column(Integer)
rc_curriculum_teaching = Column(Integer)
rc_achievement = Column(Integer)
rc_attendance_behaviour = Column(Integer)
rc_personal_development = Column(Integer)
rc_leadership_governance = Column(Integer)
rc_early_years = Column(Integer)
rc_sixth_form = Column(Integer)
report_url = Column(Text)
class FactAdmissions(Base):
"""School admissions — one row per URN per year."""
__tablename__ = "fact_admissions"
+1
View File
@@ -574,6 +574,7 @@ SCHOOL_COLUMNS = [
"gender",
"admissions_policy",
"ofsted_grade",
"ofsted_grade_date",
"ofsted_date",
"ofsted_framework",
"ofsted_rc_date",
+3 -2
View File
@@ -12,7 +12,8 @@ from fastapi.testclient import TestClient
LATEST = 202425
CANNED_SUPPLEMENTARY = {
"ofsted": {"overall_effectiveness": 2, "grade_source": "graded",
"ofsted": {"overall_effectiveness": 2,
"current_grade": {"grade": 2, "date": "2023-01-01", "basis": "graded"},
"report_card": {}, "ofsted_page_url": "https://reports.ofsted.gov.uk/provider/21/100140"},
"census": {"year": 202526, "fsm_pct": 29.8},
"admissions": {"year": 202627, "second_preference_offers": 4},
@@ -86,7 +87,7 @@ def test_each_school_gains_supplementary_blocks(client):
body = client.get("/api/compare?urns=100140,138690").json()
for urn in ("100140", "138690"):
school = body["comparison"][urn]
assert school["ofsted"]["grade_source"] == "graded"
assert school["ofsted"]["current_grade"]["basis"] == "graded"
assert school["census"]["fsm_pct"] == 29.8
assert school["admissions"]["second_preference_offers"] == 4
assert school["admissions_history"][0]["year"] == 202627
@@ -0,0 +1,56 @@
"""The search badge reads ofsted_grade, ofsted_grade_date and ofsted_date from
list rows (nextjs-app/lib/utils.ts buildOfstedListBadge). A field the list
never sends would leave the badge without its year, or worse, fall back to
"Not yet inspected". The school page reads current_grade and latest_visit from
the ofsted block (lib/ofstedStatus.ts)."""
import numpy as np
import pandas as pd
import pytest
from fastapi.testclient import TestClient
from backend.schemas import SCHOOL_COLUMNS
def test_list_columns_include_the_status_fields():
for field in ("ofsted_grade", "ofsted_grade_date", "ofsted_date", "ofsted_rc_date"):
assert field in SCHOOL_COLUMNS
def _df() -> pd.DataFrame:
# Rabbsfarm (102408): latest inspection 17 June 2025 gave no overall grade.
return pd.DataFrame([{
"urn": 102408, "school_name": "Rabbsfarm Primary School", "phase": "Primary",
"school_type": "Community school", "local_authority": "Hillingdon",
"address": "Gordon Road, Yiewsley, UB7 8AH", "postcode": "UB7 8AH",
"latitude": 51.51, "longitude": -0.47, "year": 202425, "rwm_expected_pct": 58.0,
"total_pupils": 60, "gias_total_pupils": 616,
"ofsted_grade": np.nan, "ofsted_grade_date": None, "ofsted_date": "2025-06-17",
"ofsted_framework": "Schools - S5", "ofsted_rc_date": None,
}])
@pytest.fixture()
def client(monkeypatch):
from backend import app as app_module
monkeypatch.setattr(app_module, "load_school_data", _df)
monkeypatch.setattr(app_module, "load_latest_school_data", _df)
monkeypatch.setattr(app_module, "_place_registry", None)
return TestClient(app_module.app, raise_server_exceptions=False)
def test_search_rows_carry_the_status_fields(client):
resp = client.get("/api/schools")
assert resp.status_code == 200, resp.text
row = resp.json()["schools"][0]
assert row["ofsted_grade"] is None
assert row["ofsted_date"] == "2025-06-17"
assert "ofsted_grade_date" in row
def test_a_school_with_only_an_inspection_is_publishable():
from backend.app import _has_publishable_data
assert _has_publishable_data({"rwm_expected_pct": None, "attainment_8_score": None,
"ofsted_grade": None, "ofsted_date": "2025-06-17"})
+10 -8
View File
@@ -85,12 +85,15 @@ def _ofsted_row(urn, date, oe):
"framework", "inspection_type", "quality_of_education", "behaviour_attitudes",
"personal_development", "leadership_management", "early_years_provision",
"sixth_form_provision", "ungraded_outcome", "ungraded_grade",
"ungraded_inspection_date", "rc_inspection_date", "latest_visit_outcome",
"rc_safeguarding_met", "rc_inclusion", "rc_curriculum_teaching", "rc_achievement",
"rc_attendance_behaviour", "rc_personal_development", "rc_leadership_governance",
"rc_early_years", "rc_sixth_form", "report_url",
)}
base.update(urn=urn, inspection_date=types.SimpleNamespace(isoformat=lambda: date),
overall_effectiveness=oe, grade_source=None)
when = types.SimpleNamespace(isoformat=lambda: date)
base.update(urn=urn, graded_inspection_date=when, latest_visit_date=when,
latest_visit_kind="graded", overall_effectiveness=oe,
current_grade=oe, current_grade_date=when, current_grade_basis="graded")
return types.SimpleNamespace(**base)
@@ -114,10 +117,9 @@ def _dist_row(urn, year, distance_m, route_count=1):
def test_one_query_per_table_and_latest_row_per_urn():
rows = {
# URN 1 has two Ofsted rows; the batch must keep the most recent (2023).
"FactOfstedInspection": [
# The mart holds one current Ofsted status per URN.
"FactOfstedLatest": [
_ofsted_row(1, "2023-01-01", 2),
_ofsted_row(1, "2019-01-01", 3),
_ofsted_row(2, "2021-06-01", 1),
],
"FactAdmissions": [_adm_row(1, 202526), _adm_row(1, 202627), _adm_row(2, 202627)],
@@ -142,14 +144,14 @@ def test_one_query_per_table_and_latest_row_per_urn():
assert sorted(session.queries) == [
"FactAdmissionDistance", "FactAdmissions", "FactDeprivation",
"FactFinance", "FactKs4Destinations", "FactKs5Destinations",
"FactOfstedInspection", "FactPupilCharacteristics",
"FactOfstedLatest", "FactPupilCharacteristics",
]
# A school with no destination rows gets null, not an empty shell — the
# frontend renders the section from the block's presence.
assert out[1]["destinations"] is None
# Latest Ofsted kept per URN
# Each URN's current Ofsted status
assert out[1]["ofsted"]["overall_effectiveness"] == 2
assert out[2]["ofsted"]["overall_effectiveness"] == 1
@@ -175,7 +177,7 @@ def test_one_query_per_table_and_latest_row_per_urn():
def test_single_wrapper_matches_batch(monkeypatch):
session = _FakeSession({"FactOfstedInspection": [_ofsted_row(5, "2022-01-01", 2)]})
session = _FakeSession({"FactOfstedLatest": [_ofsted_row(5, "2022-01-01", 2)]})
single = data_loader.get_supplementary_data(session, 5)
assert single["ofsted"]["overall_effectiveness"] == 2
assert single["admissions_history"] == []
+35 -10
View File
@@ -10,7 +10,10 @@ from backend.data_loader import _admissions_row_dict, _ofsted_block
def _row(**kw):
base = dict(
framework="RC", inspection_date=None, inspection_type=None,
framework="RC", inspection_type=None,
graded_inspection_date=None, ungraded_inspection_date=None, rc_inspection_date=None,
latest_visit_date=None, latest_visit_kind=None, latest_visit_outcome=None,
current_grade=None, current_grade_date=None, current_grade_basis=None,
overall_effectiveness=None, quality_of_education=None,
behaviour_attitudes=None, personal_development=None,
leadership_management=None, early_years_provision=None,
@@ -33,20 +36,41 @@ def test_report_card_block_and_provider_url():
assert block["ofsted_page_url"] == "https://reports.ofsted.gov.uk/provider/21/100140"
def test_grade_source_graded_vs_carried_forward():
assert _ofsted_block(_row(overall_effectiveness=1), urn=1)["grade_source"] == "graded"
carried = _ofsted_block(_row(ungraded_grade=2), urn=1)
assert carried["grade_source"] == "ungraded_carried_forward"
assert carried["overall_effectiveness"] == 2
assert _ofsted_block(_row(), urn=1)["grade_source"] is None
def test_no_grade_is_carried_past_a_newer_inspection():
# Rabbsfarm (102408): the 2025 inspection gave no overall grade.
block = _ofsted_block(_row(
graded_inspection_date=date(2025, 6, 17), ungraded_inspection_date=date(2020, 2, 6),
latest_visit_date=date(2025, 6, 17), latest_visit_kind="graded",
ungraded_grade=2, ungraded_outcome="School remains Good", quality_of_education=3,
), urn=102408)
assert block["current_grade"] is None
assert block["overall_effectiveness"] is None
assert block["latest_visit"] == {"date": "2025-06-17", "kind": "graded", "outcome": None}
assert block["inspection_date"] == "2025-06-17"
assert "grade_source" not in block
def test_confirmed_grade_is_dated_by_the_confirming_visit():
block = _ofsted_block(_row(
graded_inspection_date=date(2020, 1, 7), ungraded_inspection_date=date(2024, 7, 18),
latest_visit_date=date(2024, 7, 18), latest_visit_kind="ungraded",
latest_visit_outcome="School remains Good", overall_effectiveness=2,
current_grade=2, current_grade_date=date(2024, 7, 18), current_grade_basis="confirmed",
), urn=104762)
assert block["current_grade"] == {"grade": 2, "date": "2024-07-18", "basis": "confirmed"}
assert block["overall_effectiveness"] == 2
assert block["inspection_date"] == "2020-01-07"
def test_overall_sentinel_is_not_served_as_a_grade():
assert _ofsted_block(_row(overall_effectiveness=9), urn=1)["overall_effectiveness"] is None
def test_ofsted_block_carries_rc_inspection_date():
o = _row(
ungraded_grade=2,
rc_achievement=1,
rc_inspection_date=date(2026, 2, 3),
inspection_date=date(2021, 10, 7),
graded_inspection_date=date(2021, 10, 7),
)
block = _ofsted_block(o, urn=138690)
assert block["rc_inspection_date"] == "2026-02-03"
@@ -55,7 +79,7 @@ def test_ofsted_block_carries_rc_inspection_date():
def test_ofsted_block_rc_inspection_date_none_when_absent():
o = _row(overall_effectiveness=1, inspection_date=date(2021, 10, 13))
o = _row(overall_effectiveness=1, graded_inspection_date=date(2021, 10, 13))
block = _ofsted_block(o, urn=136276)
assert block["rc_inspection_date"] is None
@@ -63,6 +87,7 @@ def test_ofsted_block_rc_inspection_date_none_when_absent():
def test_ofsted_block_keeps_existing_keys():
block = _ofsted_block(_row(overall_effectiveness=2, quality_of_education=2), urn=1)
for key in ("framework", "inspection_date", "overall_effectiveness",
"current_grade", "latest_visit",
"quality_of_education", "rc_inclusion", "report_url"):
assert key in block
+31
View File
@@ -474,6 +474,37 @@ test('a report-card school shows a Report Card badge in search results, not its
await expect(page.getByText(/Report Card ·/).first()).toBeVisible();
});
test('a school whose latest inspection gave no grade is not shown with an older grade', async ({ page }) => {
// Rabbsfarm (102408): Ofsted's 17 June 2025 inspection gave no overall
// grade and rated three areas Requires Improvement. The site used to carry
// a 2020 "remains Good" forward and print "Good · 2025" (audit C1).
const URN = 102408;
const res = await page.request.get(`/api/schools/${URN}`);
expect(res.ok()).toBeTruthy();
const ofsted = (await res.json()).ofsted;
expect(ofsted.current_grade).toBeNull();
expect(ofsted.latest_visit.date).toBe('2025-06-17');
await searchByName(page, 'Rabbsfarm Primary');
const row = page.locator(`a[href*="${URN}"]`).first();
await expect(row).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Inspected · 2025').first()).toBeVisible();
await expect(page.getByText(/Good · 2025/)).toHaveCount(0);
await page.goto(`/school/${URN}`);
const section = page.locator('#ofsted');
await expect(section.getByText('No overall grade')).toBeVisible();
await expect(section.getByText('Requires Improvement').first()).toBeVisible();
});
test('a grade confirmed at an ungraded visit says so and is dated by it', async ({ page }) => {
const URN = 104762; // Robins Lane: graded Good Jan 2020, "School remains Good" 18 July 2024
await page.goto(`/school/${URN}`);
const section = page.locator('#ofsted');
await expect(section).toBeVisible({ timeout: 15_000 });
await expect(section.getByText('Confirmed at an ungraded inspection, 18 July 2024')).toBeVisible();
});
test('school detail page renders name and performance data', async ({ page }) => {
await searchByName(page, 'primary');
const firstSchool = schoolLinks(page).first();
@@ -33,18 +33,26 @@ function ofsted(partial: Partial<OfstedInspection>): OfstedInspection {
};
}
const schools = [school(1, 'Graded School'), school(2, 'Carried School'), school(3, 'Card School')];
const schools = [school(1, 'Graded School'), school(2, 'Confirmed School'), school(3, 'Card School')];
const data: Record<string, ComparisonData> = {
'1': {
school_info: schools[0],
yearly_data: [],
ofsted: ofsted({ overall_effectiveness: 1, grade_source: 'graded' }),
ofsted: ofsted({
overall_effectiveness: 1,
current_grade: { grade: 1, date: '2021-10-07', basis: 'graded' },
latest_visit: { date: '2021-10-07', kind: 'graded', outcome: null },
}),
},
'2': {
school_info: schools[1],
yearly_data: [],
ofsted: ofsted({ overall_effectiveness: 2, grade_source: 'ungraded_carried_forward' }),
ofsted: ofsted({
overall_effectiveness: 2,
current_grade: { grade: 2, date: '2023-03-14', basis: 'confirmed' },
latest_visit: { date: '2023-03-14', kind: 'ungraded', outcome: 'School remains Good' },
}),
},
'3': {
school_info: schools[2],
@@ -65,9 +73,9 @@ describe('CompareOfsted', () => {
render(<CompareOfsted schools={schools} data={data} />);
expect(screen.getByText('Outstanding')).toBeInTheDocument();
// Carried-forward grade is shown but marked as such
// A grade confirmed at an ungraded visit says so, with that visit's date
expect(screen.getByText('Good')).toBeInTheDocument();
expect(screen.getByText(/carried forward/i)).toBeInTheDocument();
expect(screen.getByText('Confirmed at an ungraded inspection, 14 March 2023')).toBeInTheDocument();
// Report card: label present, no overall-grade badge for that school
expect(screen.getByText('Report card')).toBeInTheDocument();
expect(screen.getByText(/no overall grade/i)).toBeInTheDocument();
@@ -104,7 +112,7 @@ describe('CompareOfsted', () => {
yearly_data: [],
ofsted: ofsted({
overall_effectiveness: 2,
grade_source: 'graded',
current_grade: { grade: 2, date: '2021-10-07', basis: 'graded' },
quality_of_education: 1,
early_years_provision: 9,
sixth_form_provision: 2,
@@ -167,4 +175,26 @@ describe('CompareOfsted', () => {
expect(screen.getAllByText('Graded').length).toBe(4);
expect(screen.getAllByText('Card').length).toBe(4);
});
it('dates "Inspected" by the latest visit, not the graded inspection (Washwood Heath)', () => {
const s1 = school(7, 'Washwood Heath Academy');
render(<CompareOfsted schools={[s1]} data={{ '7': { school_info: s1, yearly_data: [], ofsted: ofsted({
inspection_date: '2020-03-03', overall_effectiveness: 2, quality_of_education: 2,
current_grade: { grade: 2, date: '2020-03-03', basis: 'graded' },
latest_visit: { date: '2025-05-21', kind: 'ungraded', outcome: 'Standards maintained' },
}) } }} />);
expect(screen.getByText(/21 May 2025/)).toBeInTheDocument();
expect(screen.queryByText('4+ years ago')).not.toBeInTheDocument();
expect(screen.getByText('Graded inspection, 3 March 2020')).toBeInTheDocument();
});
it('shows no overall grade after a no-grade inspection (Rabbsfarm)', () => {
const s1 = school(8, 'Rabbsfarm Primary School');
render(<CompareOfsted schools={[s1]} data={{ '8': { school_info: s1, yearly_data: [], ofsted: ofsted({
inspection_date: '2025-06-17', quality_of_education: 3,
current_grade: null, latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null },
}) } }} />);
expect(screen.getAllByText('No overall grade').length).toBeGreaterThan(0);
expect(screen.queryByText('Good')).not.toBeInTheDocument();
});
});
@@ -0,0 +1,84 @@
import { render, screen } from '@testing-library/react';
import { OfstedSection } from '@/components/school/OfstedSection';
import { ofstedLegacyAreas } from '@/lib/utils';
import type { OfstedInspection } from '@/lib/types';
const empty = {
framework: null, inspection_date: null, inspection_type: null, overall_effectiveness: null,
quality_of_education: null, behaviour_attitudes: null, personal_development: null,
leadership_management: null, early_years_provision: null, sixth_form_provision: null,
previous_overall: null, rc_safeguarding_met: null, rc_inclusion: null, rc_curriculum_teaching: null,
rc_achievement: null, rc_attendance_behaviour: null, rc_personal_development: null,
rc_leadership_governance: null, rc_early_years: null, rc_sixth_form: null, report_url: null,
report_card: {},
} as OfstedInspection;
// `page` mimics how each school page used to mount the section; the secondary
// page's branch hard-coded four areas and dropped the sixth form (audit M2).
function renderSection(o: Partial<OfstedInspection>, page: Record<string, unknown> = {}) {
const ofsted = { ...empty, ...o } as OfstedInspection;
render(
<OfstedSection
{...page}
ofsted={ofsted}
urn={1}
isReportCard={false}
ofstedInspectedDate={ofsted.latest_visit?.date ?? null}
oeifAllSameGrade={false}
oeifAreas={ofstedLegacyAreas(ofsted)}
/>,
);
}
it('shows no overall grade, not an older one, after a no-grade inspection (Rabbsfarm)', () => {
renderSection({
inspection_date: '2025-06-17', quality_of_education: 3, behaviour_attitudes: 3,
personal_development: 2, leadership_management: 3, early_years_provision: 2,
current_grade: null, latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null },
});
expect(screen.getByText(/Inspected 17 June 2025/)).toBeInTheDocument();
expect(screen.getByText('No overall grade')).toBeInTheDocument();
expect(screen.queryByText('Good', { selector: 'span' })).not.toBeInTheDocument();
expect(screen.queryByText('Not rated')).not.toBeInTheDocument();
expect(screen.getAllByText('Requires Improvement')).toHaveLength(3);
});
it('keeps the sixth-form judgement when there is no overall grade (audit M2)', () => {
renderSection({
inspection_date: '2025-04-01', quality_of_education: 1, behaviour_attitudes: 1,
personal_development: 1, leadership_management: 1, sixth_form_provision: 2,
current_grade: null, latest_visit: { date: '2025-04-01', kind: 'graded', outcome: null },
}, { variant: 'secondary' });
expect(screen.getByText(/sixth form/i)).toBeInTheDocument();
expect(screen.getByText('Good')).toBeInTheDocument();
});
it('dates a grade by its source and prints a later visit separately (Washwood Heath)', () => {
renderSection({
inspection_date: '2020-03-03', overall_effectiveness: 2, quality_of_education: 2,
current_grade: { grade: 2, date: '2020-03-03', basis: 'graded' },
latest_visit: { date: '2025-05-21', kind: 'ungraded', outcome: 'Standards maintained' },
});
expect(screen.getByText(/Inspected 21 May 2025/)).toBeInTheDocument();
expect(screen.getByText('Graded inspection, 3 March 2020')).toBeInTheDocument();
expect(screen.getByText(/Latest visit: Ungraded inspection, 21 May 2025: Standards maintained/)).toBeInTheDocument();
});
it('says a grade was confirmed at an ungraded visit (Robins Lane)', () => {
renderSection({
inspection_date: '2020-01-07', overall_effectiveness: 2,
current_grade: { grade: 2, date: '2024-07-18', basis: 'confirmed' },
latest_visit: { date: '2024-07-18', kind: 'ungraded', outcome: 'School remains Good' },
});
expect(screen.getByText('Confirmed at an ungraded inspection, 18 July 2024')).toBeInTheDocument();
expect(screen.queryByText(/Latest visit/)).not.toBeInTheDocument();
});
it('prints an ungraded outcome when there is no grade (Oakgrove)', () => {
renderSection({
current_grade: null,
latest_visit: { date: '2024-11-13', kind: 'ungraded', outcome: 'Standards maintained' },
});
expect(screen.getByText('No overall grade')).toBeInTheDocument();
expect(screen.getByText(/Ungraded inspection, 13 November 2024: Standards maintained/)).toBeInTheDocument();
});
+12 -15
View File
@@ -114,16 +114,13 @@ describe('ofstedDisplay', () => {
expect(d.kind).toBe('report_card');
});
it('distinguishes graded from carried-forward grades', () => {
const graded = ofstedDisplay(
ofsted({ overall_effectiveness: 1, grade_source: 'graded' }),
);
expect(graded).toMatchObject({ kind: 'graded', gradeLabel: 'Outstanding', carriedForward: false });
const carried = ofstedDisplay(
ofsted({ overall_effectiveness: 2, grade_source: 'ungraded_carried_forward' }),
);
expect(carried).toMatchObject({ kind: 'carried_forward', gradeLabel: 'Good', carriedForward: true });
it('distinguishes a graded grade from a confirmed one', () => {
expect(ofstedDisplay(ofsted({ current_grade: { grade: 1, date: '2019-10-09', basis: 'graded' },
latest_visit: { date: '2025-02-05', kind: 'ungraded', outcome: 'Some aspects not as strong' } })))
.toMatchObject({ kind: 'graded', gradeLabel: 'Outstanding', gradeDate: '2019-10-09' });
expect(ofstedDisplay(ofsted({ current_grade: { grade: 2, date: '2024-07-18', basis: 'confirmed' },
latest_visit: { date: '2024-07-18', kind: 'ungraded', outcome: 'School remains Good' } })))
.toMatchObject({ kind: 'confirmed', gradeLabel: 'Good' });
});
it('handles missing data', () => {
@@ -131,11 +128,11 @@ describe('ofstedDisplay', () => {
expect(ofstedDisplay(ofsted({})).kind).toBe('none');
});
it('identifies transitional inspections without overall grades', () => {
const transitional = ofstedDisplay(
ofsted({ overall_effectiveness: null, inspection_date: '2024-11-05' }),
);
expect(transitional.kind).toBe('transitional');
it('has no overall grade when the latest inspection gave none', () => {
// Rabbsfarm: the graded inspection's own overall is null, so no grade is
// in force even though an older ungraded visit said "remains Good".
expect(ofstedDisplay(ofsted({ current_grade: null, overall_effectiveness: null,
latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null } })).kind).toBe('no_overall_grade');
});
it('uses the four legacy grade words', () => {
@@ -0,0 +1,47 @@
import { gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus';
import type { OfstedInspection } from '@/lib/types';
const base = { report_card: {} } as unknown as OfstedInspection;
describe('gradeSourceLine', () => {
it('names the graded inspection and its date', () => {
expect(gradeSourceLine({ grade: 2, date: '2016-07-06', basis: 'graded' }))
.toBe('Graded inspection, 6 July 2016');
});
it('names the ungraded visit that confirmed it', () => {
expect(gradeSourceLine({ grade: 2, date: '2023-03-14', basis: 'confirmed' }))
.toBe('Confirmed at an ungraded inspection, 14 March 2023');
});
});
describe('latestVisitLine', () => {
it('prints the ungraded outcome', () => {
expect(latestVisitLine({ date: '2024-11-13', kind: 'ungraded', outcome: 'Standards maintained' }))
.toBe('Ungraded inspection, 13 November 2024: Standards maintained');
});
it('prints a graded visit without an outcome', () => {
expect(latestVisitLine({ date: '2025-06-17', kind: 'graded', outcome: null }))
.toBe('Graded inspection, 17 June 2025');
});
});
describe('showLatestVisitLine', () => {
it('shows a later visit than the one the grade came from', () => {
expect(showLatestVisitLine({ ...base,
current_grade: { grade: 2, date: '2020-03-03', basis: 'graded' },
latest_visit: { date: '2025-05-21', kind: 'ungraded', outcome: 'Standards maintained' } })).toBe(true);
});
it('hides it when the visit is the grade’s own source', () => {
expect(showLatestVisitLine({ ...base,
current_grade: { grade: 2, date: '2024-07-18', basis: 'confirmed' },
latest_visit: { date: '2024-07-18', kind: 'ungraded', outcome: 'School remains Good' } })).toBe(false);
});
it('shows an ungraded outcome when there is no grade', () => {
expect(showLatestVisitLine({ ...base, current_grade: null,
latest_visit: { date: '2024-11-13', kind: 'ungraded', outcome: 'Standards maintained' } })).toBe(true);
});
it('hides it for a graded visit with no grade (the title already dates it)', () => {
expect(showLatestVisitLine({ ...base, current_grade: null,
latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null } })).toBe(false);
});
});
+20 -6
View File
@@ -147,23 +147,37 @@ describe('ofstedLegacyAreas', () => {
describe('buildOfstedListBadge', () => {
it('returns grade word + year for OEIF Outstanding', () => {
const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_grade_date: '2023-11-15', ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
expect(badge.label).toBe('Outstanding · 2023');
expect(badge.cssClass).toBe('ofsted1');
});
it('returns grade word for each OEIF grade', () => {
expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_date: '2022-05-01' }).label).toBe('Good · 2022');
expect(buildOfstedListBadge({ ofsted_grade: 3, ofsted_date: '2021-01-01' }).label).toBe('Req. Improvement · 2021');
expect(buildOfstedListBadge({ ofsted_grade: 4, ofsted_date: '2020-03-01' }).label).toBe('Inadequate · 2020');
expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: '2022-05-01' }).label).toBe('Good · 2022');
expect(buildOfstedListBadge({ ofsted_grade: 3, ofsted_grade_date: '2021-01-01' }).label).toBe('Req. Improvement · 2021');
expect(buildOfstedListBadge({ ofsted_grade: 4, ofsted_grade_date: '2020-03-01' }).label).toBe('Inadequate · 2020');
});
it('returns grade word without year when date is missing', () => {
const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_date: null });
it('prints a grade without a year when its date is missing', () => {
const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: null, ofsted_date: '2025-01-01' });
expect(badge.label).toBe('Good');
expect(badge.cssClass).toBe('ofsted2');
});
it('dates a grade by the inspection that awarded it, not the latest visit', () => {
// Washwood Heath: Good from a March 2020 graded inspection; latest visit
// an ungraded one in May 2025 (audit M1).
const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_grade_date: '2020-03-03', ofsted_date: '2025-05-21' });
expect(badge.label).toBe('Good · 2020');
});
it('shows Inspected for a latest inspection that gave no grade (Rabbsfarm)', () => {
// Audit C1: this used to read "Good · 2025", from a 2020 ungraded visit.
const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_grade_date: null, ofsted_date: '2025-06-17' });
expect(badge.label).toBe('Inspected · 2025');
expect(badge.cssClass).toBe('ofstedInspected');
});
it('returns a Report Card badge when ofsted_rc_date is present', () => {
const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_rc_date: '2026-02-03' });
expect(badge.label).toBe('Report Card · 2026');
@@ -191,7 +191,8 @@ export const primaryFixture = {
personal_development: 2,
leadership_management: 2,
previous_overall: 3,
grade_source: 'graded',
current_grade: { grade: 2, date: '2023-05-17', basis: 'graded' },
latest_visit: { date: '2023-05-17', kind: 'graded', outcome: null },
}),
census,
admissions: makeAdmissions({ year: 2024 }),
@@ -295,7 +296,8 @@ export const allThroughFixture = {
behaviour_attitudes: 1,
personal_development: 1,
leadership_management: 1,
grade_source: 'graded',
current_grade: { grade: 1, date: '2022-10-04', basis: 'graded' },
latest_visit: { date: '2022-10-04', kind: 'graded', outcome: null },
}),
census,
admissions: makeAdmissions({ year: 2024, school_phase: 'Secondary' }),
@@ -78,21 +78,18 @@ export function CompareAtAGlance({
return (
<Cell key={school.urn} school={school} index={i}>
{display.kind === 'report_card' && <ReportCardChips summary={display.summary} />}
{(display.kind === 'graded' || display.kind === 'carried_forward') && (
{(display.kind === 'graded' || display.kind === 'confirmed') && (
<>
<span className={`${s.badge} ${display.grade <= 2 ? s.badgeGood : display.grade === 3 ? s.badgeWarn : s.badgeBad}`}>
{display.gradeLabel}
</span>
{display.carriedForward && <span className={s.small}>Grade carried forward</span>}
{display.kind === 'confirmed' && <span className={s.small}>Confirmed at an ungraded inspection</span>}
</>
)}
{display.kind === 'transitional' && (
<>
<span className={s.badge} style={{ backgroundColor: 'var(--bg-secondary)', color: 'var(--text-secondary)' }}>
No overall grade
</span>
<span className={s.small}>Sub-judgements only</span>
</>
{display.kind === 'no_overall_grade' && (
<span className={s.badge} style={{ backgroundColor: 'var(--bg-secondary)', color: 'var(--text-secondary)' }}>
No overall grade
</span>
)}
{display.kind === 'none' && <span className={s.small}>No inspection in our dataset</span>}
</Cell>
+14 -22
View File
@@ -1,7 +1,8 @@
/**
* Ofsted section — one visual grammar for inspection detail across all
* three regimes (legacy graded, interim carried-forward, renewed-framework
* report card). Copy comes verbatim from the reviewed mockups.
* three regimes (legacy graded, no overall grade, renewed-framework report
* card). The grade shown is the one still in force, with the inspection that
* awarded or confirmed it; "Inspected" is the latest visit.
*/
'use client';
@@ -12,7 +13,8 @@ import {
rcAreaLabel,
type OfstedDisplay,
} from '@/lib/compareLogic';
import type { ComparisonData, OfstedInspection, School } from '@/lib/types';
import { gradeSourceLine } from '@/lib/ofstedStatus';
import type { ComparisonData, OfstedCurrentGrade, OfstedInspection, School } from '@/lib/types';
import { Cell, Chip, Measure, Section, SectionGrid, sectionStyles as s } from './sectionShared';
const GRADE_TONE: Record<number, 'good' | 'warn' | 'bad'> = {
@@ -39,7 +41,7 @@ function yearsSince(iso: string | null): number | null {
return (Date.now() - d.getTime()) / (365.25 * 24 * 3600 * 1000);
}
function ResultCell({ display }: { display: OfstedDisplay }) {
function ResultCell({ display, current }: { display: OfstedDisplay; current: OfstedCurrentGrade | null }) {
if (display.kind === 'none') {
return <span className={s.small}>No inspection outcome in our dataset</span>;
}
@@ -51,15 +53,13 @@ function ResultCell({ display }: { display: OfstedDisplay }) {
</>
);
}
if (display.kind === 'transitional') {
if (display.kind === 'no_overall_grade') {
return (
<>
<span className={s.badge} style={{ backgroundColor: 'var(--bg-secondary)', color: 'var(--text-secondary)' }}>
No overall grade
</span>
<span className={s.small}>
Inspected under transitional framework (sub-judgements only)
</span>
<span className={s.small}>Ofsted stopped giving overall grades in September 2024</span>
</>
);
}
@@ -68,11 +68,7 @@ function ResultCell({ display }: { display: OfstedDisplay }) {
<span className={`${s.badge} ${display.grade <= 2 ? s.badgeGood : display.grade === 3 ? s.badgeWarn : s.badgeBad}`}>
{display.gradeLabel}
</span>
<span className={s.small}>
{display.carriedForward
? 'Grade carried forward from an earlier inspection (ungraded visit since)'
: 'Overall grade (older-style inspection)'}
</span>
{current && <span className={s.small}>{gradeSourceLine(current)}</span>}
</>
);
}
@@ -179,7 +175,7 @@ export function CompareOfsted({
<Measure label="Result">
{schools.map((school, i) => (
<Cell key={school.urn} school={school} index={i}>
<ResultCell display={displays[i]} />
<ResultCell display={displays[i]} current={data[String(school.urn)]?.ofsted?.current_grade ?? null} />
</Cell>
))}
</Measure>
@@ -187,19 +183,15 @@ export function CompareOfsted({
<Measure label="Inspected">
{schools.map((school, i) => {
const ofsted = data[String(school.urn)]?.ofsted;
// A report card is dated by its OWN inspection date. The legacy
// inspection_date belongs to an older inspection and must never
// be shown against a report card (report cards exist only from
// Nov 2025).
const dateIso =
displays[i].kind === 'report_card'
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
// The school's latest visit of any kind (a report card's own date
// when there is one), never an older inspection a grade comes from.
const dateIso = ofsted?.latest_visit?.date ?? ofsted?.rc_inspection_date ?? null;
const age = yearsSince(dateIso);
return (
<Cell key={school.urn} school={school} index={i}>
{formatInspectionDate(dateIso)}{' '}
{age != null && age > 4 && <Chip tone="neutral">4+ years ago</Chip>}
{ofsted?.latest_visit?.outcome && <span className={s.small}>{ofsted.latest_visit.outcome}</span>}
</Cell>
);
})}
+52 -61
View File
@@ -1,16 +1,18 @@
/**
* OfstedSection — shared between the primary and secondary detail pages.
*
* The two versions were ~80% identical, but that figure masked a real fork in
* the no-overall-grade case: the primary page shows a "Not rated" badge, while
* the secondary page shows a four-area OEIF panel. The disclaimer copy also
* differs slightly. Both are preserved exactly via the `variant` prop rather
* than reconciled, because this refactor must not change either page. Merging
* them is a follow-up decision for a human, not a side effect of a move.
* The headline is the school's current Ofsted status (backend:
* fact_ofsted_latest): the report card; or the overall grade still in force,
* with the inspection that awarded or confirmed it; or "No overall grade".
* A later visit that is not the grade's source gets its own line. Both pages
* render the same branches, so neither drops an area judgement (audit M2).
* Rule: docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md
*
* Server component.
*/
import { ofstedDisplay } from '@/lib/compareLogic';
import { formatOfstedDate, gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus';
import type { OfstedInspection } from '@/lib/types';
import { Section, sectionStyles as styles } from './sectionShared';
@@ -40,14 +42,13 @@ export interface OfstedSectionProps {
ofstedInspectedDate: string | null;
oeifAllSameGrade: boolean;
oeifAreas: { label: string; value: number }[];
variant?: 'primary' | 'secondary';
}
export function OfstedSection({
ofsted, urn, isReportCard, ofstedInspectedDate,
oeifAllSameGrade, oeifAreas, variant = 'primary',
oeifAllSameGrade, oeifAreas,
}: OfstedSectionProps) {
const isSecondary = variant === 'secondary';
const display = ofstedDisplay(ofsted);
return (
<Section id="ofsted">
@@ -55,7 +56,7 @@ export function OfstedSection({
{isReportCard ? 'Ofsted Report Card' : 'Ofsted Rating'}
{ofstedInspectedDate && (
<span className={styles.ofstedDate}>
{isSecondary && ' '}Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
</span>
)}
<a
@@ -98,65 +99,55 @@ export function OfstedSection({
})}
</div>
</>
) : (!isSecondary || ofsted.overall_effectiveness) ? (
/* ── Old OEIF layout ── */
) : (
<>
<div className={styles.ofstedHeader}>
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'}
</span>
{ofsted.previous_overall != null &&
ofsted.previous_overall !== ofsted.overall_effectiveness && (
<span className={styles.ofstedPrevious}>
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
</span>
{display.kind === 'graded' || display.kind === 'confirmed' ? (
<>
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${display.grade}`]}`}>
{display.gradeLabel}
</span>
{ofsted.previous_overall != null && ofsted.previous_overall !== display.grade && (
<span className={styles.ofstedPrevious}>
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
</span>
)}
</>
) : (
<span className={styles.ofstedGrade}>No overall grade</span>
)}
</div>
<p className={styles.ofstedDisclaimer}>
{ofsted.grade_source === 'ungraded_carried_forward'
? 'This overall grade is carried forward from an earlier inspection. Ofsted has since visited without issuing a new overall grade. From September 2024, Ofsted no longer makes an overall effectiveness judgement.'
: isSecondary
? 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections.'
: 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections of state-funded schools.'}
{ofsted.current_grade
? gradeSourceLine(ofsted.current_grade)
: 'Ofsted stopped giving overall grades in September 2024.'}
</p>
{oeifAllSameGrade ? (
<p className={styles.ofstedAllSame}>
Rated <strong>{OFSTED_LABELS[ofsted.overall_effectiveness!]}</strong> across all inspected areas: Quality of Teaching, Behaviour, Pupils&apos; Development and Leadership.
</p>
) : (
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
{oeifAreas.map(({ label, value }) => (
<div key={label} className={styles.metricCard}>
<div className={styles.metricLabel}>{label}</div>
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
{OFSTED_LABELS[value]}
</div>
</div>
))}
</div>
{showLatestVisitLine(ofsted) && ofsted.latest_visit && (
<p className={styles.ofstedDisclaimer}>Latest visit: {latestVisitLine(ofsted.latest_visit)}</p>
)}
</>
) : (
/* ── Secondary only: inspected since Sept 2024, no overall grade ── */
<>
<p className={styles.sectionSubtitle}>
From September 2024, Ofsted no longer gives a single overall grade.
</p>
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
{[
{ label: 'Quality of Education', value: ofsted.quality_of_education },
{ label: 'Behaviour & Attitudes', value: ofsted.behaviour_attitudes },
{ label: 'Personal Development', value: ofsted.personal_development },
{ label: 'Leadership & Management', value: ofsted.leadership_management },
].filter(({ value }) => value != null).map(({ label, value }) => (
<div key={label} className={styles.metricCard}>
<div className={styles.metricLabel}>{label}</div>
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
{OFSTED_LABELS[value!]}
</div>
{oeifAllSameGrade && (display.kind === 'graded' || display.kind === 'confirmed') ? (
<p className={styles.ofstedAllSame}>
Rated <strong>{display.gradeLabel}</strong> across all inspected areas: Quality of Teaching, Behaviour, Pupils&apos; Development and Leadership.
</p>
) : oeifAreas.length > 0 ? (
<>
{ofsted.inspection_date && ofsted.inspection_date !== ofsted.latest_visit?.date && (
<p className={styles.ofstedDisclaimer}>
Area judgements from the graded inspection, {formatOfstedDate(ofsted.inspection_date)}.
</p>
)}
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
{oeifAreas.map(({ label, value }) => (
<div key={label} className={styles.metricCard}>
<div className={styles.metricLabel}>{label}</div>
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
{OFSTED_LABELS[value]}
</div>
</div>
))}
</div>
))}
</div>
</>
) : null}
</>
)}
</Section>
@@ -55,15 +55,17 @@ export function PrimarySchoolSections({
const secondaryAvg = nationalAvg?.secondary ?? {};
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
// Report cards are dated by their own inspection (rc_inspection_date), never
// the legacy inspection_date (report cards exist only from Nov 2025).
// Report cards are dated by their own inspection (rc_inspection_date);
// anything else by the school's latest visit, never the older inspection a
// grade may come from.
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
: ofsted?.latest_visit?.date ?? null;
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
const oeifAllSameGrade =
!!ofsted &&
!isReportCard &&
ofsted.overall_effectiveness != null &&
oeifAreas.length >= 3 &&
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
@@ -77,7 +79,6 @@ export function PrimarySchoolSections({
ofstedInspectedDate={ofstedInspectedDate}
oeifAllSameGrade={oeifAllSameGrade}
oeifAreas={oeifAreas}
variant="primary"
/>
)}
@@ -63,11 +63,12 @@ export function SecondarySchoolSections({
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
const ofstedInspectedDate = isReportCard
? ofsted?.rc_inspection_date ?? null
: ofsted?.inspection_date ?? null;
: ofsted?.latest_visit?.date ?? null;
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
const oeifAllSameGrade =
!!ofsted &&
!isReportCard &&
ofsted.overall_effectiveness != null &&
oeifAreas.length >= 3 &&
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
@@ -81,7 +82,6 @@ export function SecondarySchoolSections({
ofstedInspectedDate={ofstedInspectedDate}
oeifAllSameGrade={oeifAllSameGrade}
oeifAreas={oeifAreas}
variant="secondary"
/>
)}
+14 -15
View File
@@ -100,9 +100,8 @@ export function summariseReportCard(ofsted: OfstedInspection): ReportCardSummary
export type OfstedDisplay =
| { kind: 'none' }
| { kind: 'graded'; grade: number; gradeLabel: string; carriedForward: false }
| { kind: 'carried_forward'; grade: number; gradeLabel: string; carriedForward: true }
| { kind: 'transitional' }
| { kind: 'graded' | 'confirmed'; grade: number; gradeLabel: string; gradeDate: string | null }
| { kind: 'no_overall_grade' }
| { kind: 'report_card'; summary: ReportCardSummary };
export function ofstedDisplay(
@@ -116,19 +115,19 @@ export function ofstedDisplay(
return { kind: 'report_card', summary: summariseReportCard(ofsted) };
}
const grade = ofsted.overall_effectiveness;
const gradeLabel = grade != null ? OFSTED_LEGACY_GRADES[grade] : undefined;
if (grade == null || gradeLabel === undefined) {
if (ofsted.inspection_date) {
return { kind: 'transitional' };
}
return { kind: 'none' };
// The grade still in force (backend: fact_ofsted_latest), never one carried
// past a later inspection that gave none.
const current = ofsted.current_grade;
const gradeLabel = current ? OFSTED_LEGACY_GRADES[current.grade] : undefined;
if (current && gradeLabel !== undefined) {
return {
kind: current.basis === 'confirmed' ? 'confirmed' : 'graded',
grade: current.grade,
gradeLabel,
gradeDate: current.date,
};
}
if (ofsted.grade_source === 'ungraded_carried_forward') {
return { kind: 'carried_forward', grade, gradeLabel, carriedForward: true };
}
return { kind: 'graded', grade, gradeLabel, carriedForward: false };
return ofsted.latest_visit ? { kind: 'no_overall_grade' } : { kind: 'none' };
}
// ---------------------------------------------------------------------------
+48
View File
@@ -0,0 +1,48 @@
/**
* The sentences the school page and the compare page print about where an
* Ofsted grade came from and what the latest visit was. One wording, two pages.
* Rule: docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md
*/
import type { OfstedCurrentGrade, OfstedInspection, OfstedLatestVisit } from './types';
export function formatOfstedDate(iso: string | null | undefined): string {
if (!iso) return '';
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return '';
return d.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' });
}
const VISIT_KIND: Record<OfstedLatestVisit['kind'], string> = {
report_card: 'Report card inspection',
graded: 'Graded inspection',
ungraded: 'Ungraded inspection',
};
/** "Graded inspection, 6 July 2016" or "Confirmed at an ungraded inspection, 14 March 2023". */
export function gradeSourceLine(current: OfstedCurrentGrade): string {
const when = formatOfstedDate(current.date);
const what = current.basis === 'confirmed' ? 'Confirmed at an ungraded inspection' : 'Graded inspection';
return when ? `${what}, ${when}` : what;
}
/** "Ungraded inspection, 13 November 2024: Standards maintained". */
export function latestVisitLine(visit: OfstedLatestVisit): string {
const head = `${VISIT_KIND[visit.kind]}, ${formatOfstedDate(visit.date)}`;
return visit.outcome ? `${head}: ${visit.outcome}` : head;
}
/**
* Whether the latest visit needs its own line: when it is not where the grade
* came from, or when there is no grade but an ungraded outcome to report. A
* graded visit without a grade is already dated by the section title.
*/
export function showLatestVisitLine(
ofsted: Pick<OfstedInspection, 'current_grade' | 'latest_visit'>,
): boolean {
const visit = ofsted.latest_visit;
if (!visit || visit.kind === 'report_card') return false;
const current = ofsted.current_grade;
if (current) return current.date !== visit.date;
return visit.kind === 'ungraded';
}
+23 -3
View File
@@ -76,10 +76,14 @@ export interface School {
parliamentary_constituency?: string | null;
// Ofsted (for list view — summary only)
/** The overall grade still in force; null when the latest inspection gave none. */
ofsted_grade?: 1 | 2 | 3 | 4 | null;
/** Date the grade was awarded or confirmed (null without a grade). */
ofsted_grade_date?: string | null;
/** Report-card inspection date (Nov 2025+); non-null identifies a report
* card in the list/map, where the full report_card object isn't available. */
ofsted_rc_date?: string | null;
/** The school's latest inspection of any kind. */
ofsted_date?: string | null;
ofsted_framework?: string | null;
}
@@ -95,6 +99,7 @@ export interface OfstedInspection {
rc_inspection_date?: string | null;
inspection_type: string | null;
// OEIF fields (old framework, pre-Nov 2025)
/** The graded inspection's own overall grade; never carried forward. */
overall_effectiveness: 1 | 2 | 3 | 4 | null;
quality_of_education: number | null;
behaviour_attitudes: number | null;
@@ -115,9 +120,12 @@ export interface OfstedInspection {
rc_leadership_governance: number | null;
rc_early_years: number | null;
rc_sixth_form: number | null;
/** Where the effective overall grade came from: a graded (Section 5)
* inspection, or carried forward from an ungraded (Section 8) outcome. */
grade_source?: 'graded' | 'ungraded_carried_forward' | null;
/** The overall grade still in force, dated by the inspection that awarded
* ("graded") or confirmed ("confirmed", an ungraded visit) it. Null when the
* latest inspection gave no overall grade, or for a report card. */
current_grade?: OfstedCurrentGrade | null;
/** The school's most recent inspection of any kind. */
latest_visit?: OfstedLatestVisit | null;
/** Renewed-framework (Nov 2025) area judgements, coded + labelled by the
* backend from the live-sampled Ofsted vocabulary. Empty when the school
* has no report-card inspection. Safeguarding is never included here. */
@@ -127,6 +135,18 @@ export interface OfstedInspection {
report_url?: string | null;
}
export interface OfstedCurrentGrade {
grade: 1 | 2 | 3 | 4;
date: string | null;
basis: 'graded' | 'confirmed';
}
export interface OfstedLatestVisit {
date: string;
kind: 'report_card' | 'graded' | 'ungraded';
outcome: string | null;
}
export interface ReportCardEntry {
code: number;
label: string;
+16 -180
View File
@@ -2,7 +2,7 @@
* Utility functions for SchoolCompare
*/
import type { School, MetricDefinition, OfstedInspection, SchoolAdmissions, SchoolResult } from './types';
import type { School, MetricDefinition } from './types';
// ============================================================================
// String Utilities
@@ -619,172 +619,6 @@ export function getCurrentAcademicYear(): number {
return month >= 8 ? year : year - 1;
}
// ============================================================================
// School Detail Hero Helpers
// ============================================================================
const OFSTED_OEIF_WORDS: Record<number, string> = {
1: 'Outstanding', 2: 'Good', 3: 'Requires Improvement', 4: 'Inadequate',
};
/**
* Format an Ofsted inspection date as "Month YYYY" (e.g. "November 2023").
*/
function formatOfstedMonth(date: string | null | undefined): string {
if (!date) return '';
const d = new Date(date);
if (Number.isNaN(d.getTime())) return '';
return d.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' });
}
export type HeroTone = 'teal' | 'green' | 'gold' | 'coral' | 'neutral';
export interface OfstedHeroChip {
state: 'oeif' | 'reportCard' | 'none';
title: string; // Main label (e.g. "Ofsted Outstanding", "Ofsted Report Card")
subtitle: string; // Context line (e.g. "Inspected November 2023")
detail?: string; // Optional extra line (e.g. "Safeguarding: Met")
tone: HeroTone; // Maps to dedicated hero tone classes (not badge classes)
}
/**
* Build the hero-strip Ofsted chip, branching on the inspection framework.
* Never synthesises a single overall grade for ReportCard schools.
*
* Note: the API may return ``framework`` as a literal string ``"NULL"`` for
* older inspections, so we explicitly only branch into the ReportCard layout
* when the value is exactly ``"ReportCard"``. Anything else with an
* ``overall_effectiveness`` score is treated as OEIF.
*/
export function buildOfstedHeroChip(ofsted: OfstedInspection | null | undefined): OfstedHeroChip {
if (!ofsted) {
return {
state: 'none',
title: 'Ofsted pending',
subtitle: 'No inspection on record',
tone: 'neutral',
};
}
const when = formatOfstedMonth(ofsted.inspection_date);
// ReportCard branch — only if the API explicitly says so
if (ofsted.framework === 'ReportCard') {
const safeguarding = ofsted.rc_safeguarding_met;
return {
state: 'reportCard',
title: 'Ofsted Report Card',
subtitle: when ? `Inspected ${when}` : 'New framework inspection',
detail:
safeguarding == null
? undefined
: safeguarding ? 'Safeguarding: Met' : 'Safeguarding: Not met',
tone: safeguarding === false ? 'coral' : 'green',
};
}
// Otherwise treat as OEIF (covers framework === 'OEIF', null, "NULL", etc.)
const grade = ofsted.overall_effectiveness;
if (grade && OFSTED_OEIF_WORDS[grade]) {
const oeifTone: HeroTone =
grade === 1 ? 'teal' :
grade === 2 ? 'green' :
grade === 3 ? 'gold' :
'coral';
return {
state: 'oeif',
title: `Ofsted ${OFSTED_OEIF_WORDS[grade]}`,
subtitle: when ? `Inspected ${when}` : 'Inspected',
tone: oeifTone,
};
}
return {
state: 'oeif',
title: 'Ofsted inspected',
subtitle: when ? `Inspected ${when}` : 'Inspection on record',
tone: 'neutral',
};
}
/**
* Build a one-sentence editorial summary for the school detail hero.
* Branches on Ofsted framework so Report Card schools are never described
* with an overall grade they do not have.
*/
export function buildSchoolSummary(
schoolInfo: School,
ofsted: OfstedInspection | null | undefined,
admissions: SchoolAdmissions | null | undefined,
latestResults: SchoolResult | null | undefined,
): string {
const parts: string[] = [];
// Size descriptor
const pupils = latestResults?.total_pupils ?? schoolInfo.total_pupils ?? null;
const sizeWord =
pupils == null ? '' :
pupils < 200 ? 'Small' :
pupils < 500 ? 'Mid-sized' :
'Large';
// Phase descriptor — avoid the raw code
const phase = (schoolInfo.phase ?? '').toLowerCase();
const phaseWord =
phase.includes('secondary') ? 'secondary' :
phase === 'all-through' ? 'all-through' :
phase.includes('primary') ? 'primary' :
'school';
// Religious character
const religion = schoolInfo.religious_denomination;
const religionWord =
!religion || /none|does not apply/i.test(religion) ? '' :
/roman catholic|catholic/i.test(religion) ? 'Catholic ' :
/church of england|ce|anglican/i.test(religion) ? 'Church of England ' :
/jewish/i.test(religion) ? 'Jewish ' :
/muslim|islam/i.test(religion) ? 'Muslim ' :
/hindu/i.test(religion) ? 'Hindu ' :
/sikh/i.test(religion) ? 'Sikh ' :
'';
// Locality — prefer town from address parsing (fallback to LA)
const locality = schoolInfo.town || schoolInfo.local_authority || '';
const lead = [sizeWord, religionWord + phaseWord].filter(Boolean).join(' ');
let opening = lead || 'School';
if (locality) opening += ` in ${locality}`;
parts.push(opening);
// Ofsted clause (framework-aware)
if (ofsted?.framework === 'OEIF' && ofsted.overall_effectiveness) {
parts.push(`rated ${OFSTED_OEIF_WORDS[ofsted.overall_effectiveness]} by Ofsted`);
} else if (ofsted?.framework === 'ReportCard') {
const when = formatOfstedMonth(ofsted.inspection_date);
parts.push(
when
? `most recently inspected under Ofsted's Report Card framework in ${when}`
: "recently inspected under Ofsted's new Report Card framework",
);
}
// Admissions clause
if (admissions?.oversubscribed) {
if (admissions.first_preference_offer_pct != null) {
const pct = Math.round(admissions.first_preference_offer_pct);
parts.push(
`oversubscribed (${pct}% of first-choice applicants are offered a place)`,
);
} else {
parts.push('oversubscribed');
}
} else if (admissions?.first_preference_offer_pct != null && admissions.first_preference_offer_pct >= 90) {
parts.push('most families get their first-choice offer');
}
return parts.join(', ') + '.';
}
// ─── Legacy (OEIF) sub-judgement areas ────────────────────────────────────────
export interface OfstedLegacyArea {
@@ -837,15 +671,18 @@ export interface OfstedListBadge {
* Checked FIRST so it wins over any carried-forward legacy grade — the
* list has no full report_card object, and ofsted_framework is the raw
* event grouping ("Schools - S5"), never "ReportCard".
* - OEIF school (ofsted_grade set): grade word + year, colour-keyed
* - Inspected without an overall grade (OEIF post-Sept-2024, where Ofsted no
* longer issues an overall judgement): "Inspected · YYYY" — mirrors the
* detail page's hero chip so a school never reads as both inspected and
* - Current grade (ofsted_grade set): grade word + the year it was awarded
* or confirmed (ofsted_grade_date), colour-keyed. Never the year of a later
* visit: that paired old grades with new inspections (audit C1).
* - Inspected with no grade in force (every inspection Sept 2024 – Nov 2025,
* or an ungraded visit whose outcome names no grade): "Inspected · YYYY",
* dated by the latest visit, so a school never reads as both inspected and
* "Not yet inspected"
* - No inspection on record: "Not yet inspected" in grey
*/
export function buildOfstedListBadge(school: {
ofsted_grade?: 1 | 2 | 3 | 4 | null;
ofsted_grade_date?: string | null;
ofsted_date?: string | null;
ofsted_framework?: string | null;
ofsted_rc_date?: string | null;
@@ -858,10 +695,7 @@ export function buildOfstedListBadge(school: {
return { label: `Report Card · ${rcYear}`, cssClass: 'ofstedRc' };
}
const year = school.ofsted_date
? new Date(school.ofsted_date).getFullYear()
: null;
const yearStr = year ? ` · ${year}` : '';
const yearOf = (iso?: string | null) => (iso ? new Date(iso).getFullYear() : null);
if (school.ofsted_grade) {
const labels: Record<number, string> = {
@@ -870,17 +704,19 @@ export function buildOfstedListBadge(school: {
3: 'Req. Improvement',
4: 'Inadequate',
};
const gradeYear = yearOf(school.ofsted_grade_date);
return {
label: `${labels[school.ofsted_grade]}${yearStr}`,
label: `${labels[school.ofsted_grade]}${gradeYear ? ` · ${gradeYear}` : ''}`,
cssClass: `ofsted${school.ofsted_grade}`,
};
}
// An inspection is on record (date or framework present) but carries no
// overall grade — a post-Sept-2024 OEIF inspection. Distinct from a school
// that has genuinely never been inspected.
// An inspection is on record but no overall grade is in force: every
// inspection from Sept 2024 to Nov 2025, or an ungraded visit whose outcome
// names no grade. Dated by the latest visit.
if (school.ofsted_date != null || school.ofsted_framework != null) {
return { label: `Inspected${yearStr}`, cssClass: 'ofstedInspected' };
const visitYear = yearOf(school.ofsted_date);
return { label: `Inspected${visitYear ? ` · ${visitYear}` : ''}`, cssClass: 'ofstedInspected' };
}
return { label: 'Not yet inspected', cssClass: 'ofstedPending' };
@@ -71,12 +71,12 @@ select
s.nursery_provision,
s.admissions_policy_code,
-- Latest Ofsted (populated after monthly Ofsted pipeline runs)
-- Latest Ofsted (populated after monthly Ofsted pipeline runs). The grade
-- still in force and the latest visit, from int_ofsted_latest — never a
-- grade carried past a newer inspection.
{% if ofsted_relation is not none %}
-- Prefer the graded overall effectiveness; fall back to the grade parsed
-- from the latest ungraded (Section 8) outcome when no graded grade exists.
coalesce(o.overall_effectiveness, o.ungraded_grade) as ofsted_grade,
o.inspection_date as ofsted_date,
o.current_grade as ofsted_grade,
o.latest_visit_date as ofsted_date,
o.framework as ofsted_framework
{% else %}
null::text as ofsted_grade,
@@ -0,0 +1,35 @@
version: 2
unit_tests:
- name: dim_school_ofsted_grade_is_the_one_still_in_force
description: >
Rabbsfarm (102408). Its 2025 inspection gave no overall grade, so the 2020
"School remains Good" must not reach dim_school (which feeds Typesense's
rating), and ofsted_date is the latest visit (audit C1, M1).
model: dim_school
given:
- input: ref('stg_gias_establishments')
rows:
- {urn: 102408, school_name: 'Rabbsfarm Primary School', status_code: 1, school_type_code: 1, local_authority_code: 312, phase_code: 2}
- input: ref('int_ofsted_latest')
rows:
- {urn: 102408, inspection_date: '2025-06-17', overall_effectiveness: null, ungraded_grade: 2, framework: 'Schools - S5', latest_visit_date: '2025-06-17', current_grade: null}
expect:
rows:
- {urn: 102408, ofsted_grade: null, ofsted_date: '2025-06-17', ofsted_framework: 'Schools - S5'}
- name: dim_school_ofsted_grade_keeps_its_own_date_out_of_ofsted_date
description: >
Washwood Heath (139888). Good from a 2020 graded inspection; latest visit
an ungraded one in May 2025. ofsted_date is the latest visit.
model: dim_school
given:
- input: ref('stg_gias_establishments')
rows:
- {urn: 139888, school_name: 'Washwood Heath Academy', status_code: 1, school_type_code: 28, local_authority_code: 330, phase_code: 7}
- input: ref('int_ofsted_latest')
rows:
- {urn: 139888, inspection_date: '2020-03-03', overall_effectiveness: 2, ungraded_grade: null, framework: 'Schools - S5', latest_visit_date: '2025-05-21', current_grade: 2}
expect:
rows:
- {urn: 139888, ofsted_grade: 2, ofsted_date: '2025-05-21'}