feat(api): compare endpoint enrichment — supplementary blocks, national averages, benchmarks, report-card labels #34

Merged
tudor merged 7 commits from feat/compare-api-enrichment into main 2026-07-13 21:58:51 +00:00
Owner

Backend PR (2 of 3) for the compare-screen redesign — exposes the PR #32 data through the API per spec §6/§8. All response changes are additive; existing consumers and e2e journeys are untouched.

What /api/compare now returns (additions only)

{
  "comparison": {
    "<urn>": {
      "school_info": { ... },          // unchanged
      "yearly_data": [ ... ],          // unchanged + new columns (progress CIs, KS4 banding/gaps)
      "ofsted":   { ..., "grade_source": "graded|ungraded_carried_forward",
                    "report_card": { "rc_achievement": {"code":2,"label":"Strong standard"}, ... },
                    "ofsted_page_url": "https://reports.ofsted.gov.uk/provider/21/<urn>" },
      "census": { ... }, "admissions": { ...incl. 2nd/3rd-pref + cross-LA... },
      "admissions_history": [ ... ], "deprivation": { ... }
    }
  },
  "national_averages": { ... },        // same payload as /api/national-averages (shared helper, not duplicated)
  "benchmarks": {                      // computed, never labelled as official
    "source": "state-school average (computed from our dataset)",
    "primary": { "disadvantaged_rwm_expected_pct": ..., "eal_pct": ..., "sen_support_pct": ..., "disadvantaged_pct": ..., "median_pupils": ... },
    "secondary": { ... }
  }
}

Also: /api/national-averages now exposes gps_expected_pct / gps_high_pct / science_expected_pct (they were in the mart but not in the endpoint's metric list — this was why the England GPS/science ticks looked missing).

Notable decisions

  • Report-card labels are pinned to the live-sampled Ofsted vocabulary ("Needs attention", not the draft's "Attention needed"); safeguarding is excluded from graded areas by construction (backend/ofsted_codes.py, fully tested).
  • grade_source distinguishes a graded inspection from a carried-forward ungraded grade (expert-review requirement §8.4).
  • Benchmarks use a cohort-weighted average for disadvantaged attainment and medians for context measures, tagged with an explicit provenance string (§8.6).
  • One dbt ride-along: fact_performance (the combined mart the backend reads) now threads the PR #32 columns through — they were in fact_ks2/ks4_performance but not the merged mart.
  • Supplementary failures degrade to null blocks with HTTP 200 (same defensive pattern as the detail endpoint).

Tests

14 new tests (44 total): ofsted codes, supplementary enrichment, benchmarks math (hand-checkable weighted average), compare-endpoint shape + degradation, GPS/science exposure.

Deploy notes

  • New fields return NULL/empty until the DAGs have run on the target environment (already done on staging; prod needs its DAG runs after promotion).
  • No e2e changes: no user-facing behaviour changes — the frontend PR carries the journey updates.

Post-merge staging check

curl -s "https://stx.schoolcompare.co.uk/api/compare?urns=138690,100140" → expect the new keys, benchmarks.primary.disadvantaged_rwm_expected_pct ≈ 45–47, and report-card blocks (empty until a school has a Nov-2025 inspection).

🤖 Generated with Claude Code

https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB

Backend PR (2 of 3) for the compare-screen redesign — exposes the PR #32 data through the API per spec §6/§8. All response changes are **additive**; existing consumers and e2e journeys are untouched. ## What `/api/compare` now returns (additions only) ```jsonc { "comparison": { "<urn>": { "school_info": { ... }, // unchanged "yearly_data": [ ... ], // unchanged + new columns (progress CIs, KS4 banding/gaps) "ofsted": { ..., "grade_source": "graded|ungraded_carried_forward", "report_card": { "rc_achievement": {"code":2,"label":"Strong standard"}, ... }, "ofsted_page_url": "https://reports.ofsted.gov.uk/provider/21/<urn>" }, "census": { ... }, "admissions": { ...incl. 2nd/3rd-pref + cross-LA... }, "admissions_history": [ ... ], "deprivation": { ... } } }, "national_averages": { ... }, // same payload as /api/national-averages (shared helper, not duplicated) "benchmarks": { // computed, never labelled as official "source": "state-school average (computed from our dataset)", "primary": { "disadvantaged_rwm_expected_pct": ..., "eal_pct": ..., "sen_support_pct": ..., "disadvantaged_pct": ..., "median_pupils": ... }, "secondary": { ... } } } ``` Also: `/api/national-averages` now exposes `gps_expected_pct` / `gps_high_pct` / `science_expected_pct` (they were in the mart but not in the endpoint's metric list — this was why the England GPS/science ticks looked missing). ## Notable decisions - **Report-card labels** are pinned to the live-sampled Ofsted vocabulary ("Needs attention", not the draft's "Attention needed"); safeguarding is excluded from graded areas by construction (`backend/ofsted_codes.py`, fully tested). - **`grade_source`** distinguishes a graded inspection from a carried-forward ungraded grade (expert-review requirement §8.4). - **Benchmarks** use a cohort-weighted average for disadvantaged attainment and medians for context measures, tagged with an explicit provenance string (§8.6). - One dbt ride-along: `fact_performance` (the combined mart the backend reads) now threads the PR #32 columns through — they were in `fact_ks2/ks4_performance` but not the merged mart. - Supplementary failures degrade to null blocks with HTTP 200 (same defensive pattern as the detail endpoint). ## Tests 14 new tests (44 total): ofsted codes, supplementary enrichment, benchmarks math (hand-checkable weighted average), compare-endpoint shape + degradation, GPS/science exposure. ## Deploy notes - New fields return NULL/empty until the DAGs have run on the target environment (already done on staging; prod needs its DAG runs after promotion). - No e2e changes: no user-facing behaviour changes — the frontend PR carries the journey updates. ## Post-merge staging check `curl -s "https://stx.schoolcompare.co.uk/api/compare?urns=138690,100140"` → expect the new keys, `benchmarks.primary.disadvantaged_rwm_expected_pct` ≈ 45–47, and report-card blocks (empty until a school has a Nov-2025 inspection). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
tudor added 7 commits 2026-07-13 17:49:17 +00:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
feat(api): compare endpoint carries supplementary blocks, national averages and benchmarks
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m45s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 22s
PR Checks / Build Frontend (no push) (pull_request) Successful in 51s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 37s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 3m12s
c0f31a5941
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB

🤖 AI Code Review (Claude Code)

This PR wires previously-added mart columns (KS2 progress CIs, KS4 banding/disadvantage gaps, extended admissions fields) through the backend, adds Ofsted report-card label translation and a computed state-school benchmarks helper, and enriches /api/compare with per-school supplementary blocks plus top-level national_averages/benchmarks — all additive to existing response shapes. The change is well-tested (new unit tests for compute_benchmarks, ofsted_codes, and compare enrichment), follows existing defensive patterns for optional DB lookups, and the accompanying dbt model change correctly threads the new columns through fact_performance without altering the join logic.

No issues found.

## 🤖 AI Code Review (Claude Code) This PR wires previously-added mart columns (KS2 progress CIs, KS4 banding/disadvantage gaps, extended admissions fields) through the backend, adds Ofsted report-card label translation and a computed state-school benchmarks helper, and enriches /api/compare with per-school supplementary blocks plus top-level national_averages/benchmarks — all additive to existing response shapes. The change is well-tested (new unit tests for compute_benchmarks, ofsted_codes, and compare enrichment), follows existing defensive patterns for optional DB lookups, and the accompanying dbt model change correctly threads the new columns through fact_performance without altering the join logic. ✅ No issues found.
tudor merged commit 4a8e798c64 into main 2026-07-13 21:58:51 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tudor/school_compare#34