Do not merge until school_data_monthly_ofsted has run on staging with #183, and do not promote before it has run on production. The backend now reads marts.fact_ofsted_latest and has no fallback (agreed in design). Without the table, the school list fails to load. dim_school and its new unit tests also need int_ofsted_latest's new columns.
Why
Audit findings C1, M1 and M2 (3 Oct 2026 accuracy audit). This is part 2 of 2. #183 computes one current Ofsted status per school in dbt; this PR makes every page read it.
C1: Rabbsfarm Primary (102408) shows "Good · 2025". Its 2025 inspection gave no overall grade and rated three areas Requires Improvement. Site-wide, 932 badges pair an old grade with a newer inspection's year, and 147 are contradicted by that inspection.
M1: "Inspected" shows the last graded inspection, not the latest visit. Washwood Heath (139888) reads "3 Mar 2020 · 4+ years ago"; Ofsted visited on 21 May 2025. 667 schools.
M2: secondary pages drop sixth-form and early-years judgements when there's no overall grade.
This branch contains #183 (merged in so it builds and tests). Merge main back in once #183 lands; the diff then shrinks to this PR's own commits.
Change
dim_school: ofsted_grade is now current_grade (Typesense's rating follows) and ofsted_date is the latest visit. New dbt unit tests in dim_school.yml failed RED on the old coalesce.
Backend:
The list query and the batch Ofsted fetch read marts.fact_ofsted_latest instead of picking "the latest row" themselves.
List rows: ofsted_grade (in force), ofsted_grade_date (new), ofsted_date (latest visit).
The ofsted block gains current_grade {grade, date, basis} and latest_visit {date, kind, outcome}, and loses grade_source. overall_effectiveness is the graded inspection's own result.
A school with only an inspection stays publishable in the sitemap.
Search and map badge: shows the grade with the year it was awarded or confirmed, else "Inspected · latest visit year".
School page:
Dated by the latest visit.
Headline: the grade with its source ("Graded inspection, 3 March 2020" / "Confirmed at an ungraded inspection, 18 July 2024"), or "No overall grade".
A "Latest visit: Ungraded inspection, 13 November 2024: Standards maintained" line when the latest visit differs from the grade's source.
The primary and secondary branches are merged (fixes M2), and "Not rated" is gone.
Compare: the "Inspected" row is the latest visit; the "Result" row names the grade's source or says "No overall grade".
Removed: buildOfstedHeroChip and buildSchoolSummary. They were unused and encoded the old rule.
E2E: Rabbsfarm reads "Inspected · 2025" with "No overall grade"; Robins Lane (104762) says its Good was confirmed on 18 July 2024. Both fail against staging today and should pass once this and #183's pipeline run are deployed.
Expected visible change
About 932 schools change from a grade badge dated by a no-grade inspection ("Good · 2025") to "Inspected · 2025".
Counts of Good and Outstanding schools on place pages fall by those schools, and Typesense's rating changes for them.
Dates beside a grade can move earlier, to the inspection that awarded it. "Inspected" dates move later, to the latest visit.
Local dbt against a throwaway Postgres, with the real 31 Aug MI rows of the spec's nine schools: 25/25, plus dim_school unit tests 2/2.
The new E2E journeys run against staging fail as expected for now (current_grade undefined; no confirmation line).
After merge (staging)
Let the daily DAG run, or trigger it, so dim_school and Typesense pick up the change.
Run the E2E journeys.
Re-run the audit's C1/M1/M2 checks against staging; expect 0.
Known limitation
dim_school.yml's unit tests run in the daily build and need int_ofsted_latest with the new columns. On a brand-new database the daily build fails until the monthly Ofsted DAG has run once. Before this change, dim_school tolerated a missing int_ofsted_latest.
> **Do not merge until `school_data_monthly_ofsted` has run on staging with #183**, and do not promote before it has run on production. The backend now reads `marts.fact_ofsted_latest` and has no fallback (agreed in design). Without the table, the school list fails to load. `dim_school` and its new unit tests also need `int_ofsted_latest`'s new columns.
## Why
Audit findings C1, M1 and M2 (3 Oct 2026 accuracy audit). This is **part 2 of 2**. #183 computes one current Ofsted status per school in dbt; this PR makes every page read it.
- **C1:** Rabbsfarm Primary (102408) shows "Good · 2025". Its 2025 inspection gave no overall grade and rated three areas Requires Improvement. Site-wide, 932 badges pair an old grade with a newer inspection's year, and 147 are contradicted by that inspection.
- **M1:** "Inspected" shows the last graded inspection, not the latest visit. Washwood Heath (139888) reads "3 Mar 2020 · 4+ years ago"; Ofsted visited on 21 May 2025. 667 schools.
- **M2:** secondary pages drop sixth-form and early-years judgements when there's no overall grade.
Spec: `docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md`. Plan: `docs/superpowers/plans/2026-10-05-ofsted-current-status.md`.
This branch contains #183 (merged in so it builds and tests). Merge `main` back in once #183 lands; the diff then shrinks to this PR's own commits.
## Change
- **`dim_school`**: `ofsted_grade` is now `current_grade` (Typesense's rating follows) and `ofsted_date` is the latest visit. New dbt unit tests in `dim_school.yml` failed RED on the old `coalesce`.
- **Backend**:
- The list query and the batch Ofsted fetch read `marts.fact_ofsted_latest` instead of picking "the latest row" themselves.
- List rows: `ofsted_grade` (in force), `ofsted_grade_date` (new), `ofsted_date` (latest visit).
- The `ofsted` block gains `current_grade {grade, date, basis}` and `latest_visit {date, kind, outcome}`, and loses `grade_source`. `overall_effectiveness` is the graded inspection's own result.
- A school with only an inspection stays publishable in the sitemap.
- **Search and map badge**: shows the grade with the year it was awarded or confirmed, else "Inspected · *latest visit year*".
- **School page**:
- Dated by the latest visit.
- Headline: the grade with its source ("Graded inspection, 3 March 2020" / "Confirmed at an ungraded inspection, 18 July 2024"), or "No overall grade".
- A "Latest visit: Ungraded inspection, 13 November 2024: Standards maintained" line when the latest visit differs from the grade's source.
- The primary and secondary branches are merged (fixes M2), and "Not rated" is gone.
- **Compare**: the "Inspected" row is the latest visit; the "Result" row names the grade's source or says "No overall grade".
- **Removed**: `buildOfstedHeroChip` and `buildSchoolSummary`. They were unused and encoded the old rule.
- **E2E**: Rabbsfarm reads "Inspected · 2025" with "No overall grade"; Robins Lane (104762) says its Good was confirmed on 18 July 2024. Both fail against staging today and should pass once this and #183's pipeline run are deployed.
## Expected visible change
- About 932 schools change from a grade badge dated by a no-grade inspection ("Good · 2025") to "Inspected · 2025".
- Counts of Good and Outstanding schools on place pages fall by those schools, and Typesense's rating changes for them.
- Dates beside a grade can move earlier, to the inspection that awarded it. "Inspected" dates move later, to the latest visit.
## Checks
- `npx tsc --noEmit && npm test`: 638 passed.
- `python -m pytest backend/tests pipeline/tests scripts/ci/tests -q`: 333 passed.
- Local dbt against a throwaway Postgres, with the real 31 Aug MI rows of the spec's nine schools: 25/25, plus `dim_school` unit tests 2/2.
- The new E2E journeys run against staging fail as expected for now (`current_grade` undefined; no confirmation line).
## After merge (staging)
1. Let the daily DAG run, or trigger it, so `dim_school` and Typesense pick up the change.
2. Run the E2E journeys.
3. Re-run the audit's C1/M1/M2 checks against staging; expect 0.
## Known limitation
`dim_school.yml`'s unit tests run in the daily build and need `int_ofsted_latest` with the new columns. On a brand-new database the daily build fails until the monthly Ofsted DAG has run once. Before this change, `dim_school` tolerated a missing `int_ofsted_latest`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Design for audit findings C1 and M1. A grade carried forward from an older
ungraded visit is shown under a newer inspection's date ("Good · 2025" for
Rabbsfarm, whose 2025 inspection gave no grade), and "Inspected" dates show the
last graded inspection rather than the latest visit. The rule moves into
int_ofsted_latest and a new fact_ofsted_latest mart, shipped as a pipeline PR
and then a backend/UI PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
int_ofsted_latest needs to know which inspection came last. Report-card-only schools are no longer dropped (123 schools, audit H3); inspection_date stays for current readers and now falls back to the report-card date, so fact_ofsted_inspection's not_null test still holds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
int_ofsted_latest now picks each school's latest visit (report card, graded or ungraded inspection) and the overall grade still in force, dated by the inspection that awarded or confirmed it. A 'School remains Good' from an older ungraded visit is no longer carried past a newer inspection that gave no grade (audit C1). Duplicate monthly rows resolve to the newest visit, so a newer report card always wins. dim_school's columns are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend will read this instead of picking the latest row of fact_ofsted_inspection itself (twice, with arbitrary ties). Schema tests and assert_ofsted_current_grade_consistent pin its invariants. Built by the monthly Ofsted DAG (int_ofsted_latest+), not the daily one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
'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>
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>
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>
int_ofsted_latest treated any report card as the latest visit, relying on report cards postdating legacy inspections. That holds today (0 of 2,451 report-card schools in the 31 Aug 2026 MI) but is now not assumed: the latest visit is the newest of the three dates. A report card still leaves no legacy grade in force, which is the spec's rule rather than an inference. New unit test newer_legacy_visit_after_a_report_card failed RED on the old logic. Review on #183.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan hard-coded one model's Co-Authored-By trailer; an executor should use the one its own session specifies. The spec's report-card rule now matches int_ofsted_latest. Review on #183.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR replaces the 'latest inspection row per school' Ofsted logic with the one-row-per-school fact_ofsted_latest mart. A grade is now no longer carried forward past a newer inspection that gave none. The backend, models, list badge, school page, compare page, dim_school and tests are all updated to match. The pipeline models it depends on (int_ofsted_latest, fact_ofsted_latest) already exist in the repo, and I found no correctness, security or data-loss problems.
🟡 Minor
nextjs-app/components/school/OfstedSection.tsx: The unit test spreads an unknown variant prop into OfstedSection, which no longer accepts it. The 'secondary' test no longer exercises a different code path. The test is misleading and could fail type-checking.
backend/data_loader.py: _MAIN_QUERY and get_supplementary_data_batch now require marts.fact_ofsted_latest to exist. The backend deploy therefore has to run after a dbt build that creates the table. If staging or production has not run that build, the main school query will fail. Make sure the pipeline runs first, or add a fallback.
## 🤖 AI Code Review (Claude Code)
The PR replaces the 'latest inspection row per school' Ofsted logic with the one-row-per-school `fact_ofsted_latest` mart. A grade is now no longer carried forward past a newer inspection that gave none. The backend, models, list badge, school page, compare page, dim_school and tests are all updated to match. The pipeline models it depends on (`int_ofsted_latest`, `fact_ofsted_latest`) already exist in the repo, and I found no correctness, security or data-loss problems.
### 🟡 Minor
- **nextjs-app/components/school/OfstedSection.tsx**: The unit test spreads an unknown `variant` prop into `OfstedSection`, which no longer accepts it. The 'secondary' test no longer exercises a different code path. The test is misleading and could fail type-checking.
- **backend/data_loader.py**: `_MAIN_QUERY` and `get_supplementary_data_batch` now require `marts.fact_ofsted_latest` to exist. The backend deploy therefore has to run after a dbt build that creates the table. If staging or production has not run that build, the main school query will fail. Make sure the pipeline runs first, or add a fallback.
tudor
merged commit c26b65246f into main2026-10-05 21:47:35 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
Audit findings C1, M1 and M2 (3 Oct 2026 accuracy audit). This is part 2 of 2. #183 computes one current Ofsted status per school in dbt; this PR makes every page read it.
Spec:
docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md. Plan:docs/superpowers/plans/2026-10-05-ofsted-current-status.md.This branch contains #183 (merged in so it builds and tests). Merge
mainback in once #183 lands; the diff then shrinks to this PR's own commits.Change
dim_school:ofsted_gradeis nowcurrent_grade(Typesense's rating follows) andofsted_dateis the latest visit. New dbt unit tests indim_school.ymlfailed RED on the oldcoalesce.marts.fact_ofsted_latestinstead of picking "the latest row" themselves.ofsted_grade(in force),ofsted_grade_date(new),ofsted_date(latest visit).ofstedblock gainscurrent_grade {grade, date, basis}andlatest_visit {date, kind, outcome}, and losesgrade_source.overall_effectivenessis the graded inspection's own result.buildOfstedHeroChipandbuildSchoolSummary. They were unused and encoded the old rule.Expected visible change
Checks
npx tsc --noEmit && npm test: 638 passed.python -m pytest backend/tests pipeline/tests scripts/ci/tests -q: 333 passed.dim_schoolunit tests 2/2.current_gradeundefined; no confirmation line).After merge (staging)
dim_schooland Typesense pick up the change.Known limitation
dim_school.yml's unit tests run in the daily build and needint_ofsted_latestwith the new columns. On a brand-new database the daily build fails until the monthly Ofsted DAG has run once. Before this change,dim_schooltolerated a missingint_ofsted_latest.🤖 Generated with Claude Code
Design for audit findings C1 and M1. A grade carried forward from an older ungraded visit is shown under a newer inspection's date ("Good · 2025" for Rabbsfarm, whose 2025 inspection gave no grade), and "Inspected" dates show the last graded inspection rather than the latest visit. The rule moves into int_ofsted_latest and a new fact_ofsted_latest mart, shipped as a pipeline PR and then a backend/UI PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>🤖 AI Code Review (Claude Code)
The PR replaces the 'latest inspection row per school' Ofsted logic with the one-row-per-school
fact_ofsted_latestmart. A grade is now no longer carried forward past a newer inspection that gave none. The backend, models, list badge, school page, compare page, dim_school and tests are all updated to match. The pipeline models it depends on (int_ofsted_latest,fact_ofsted_latest) already exist in the repo, and I found no correctness, security or data-loss problems.🟡 Minor
variantprop intoOfstedSection, which no longer accepts it. The 'secondary' test no longer exercises a different code path. The test is misleading and could fail type-checking._MAIN_QUERYandget_supplementary_data_batchnow requiremarts.fact_ofsted_latestto exist. The backend deploy therefore has to run after a dbt build that creates the table. If staging or production has not run that build, the main school query will fail. Make sure the pipeline runs first, or add a fallback.