2026-07-13 18:45:30 +01:00
|
|
|
"""compute_benchmarks: state-school benchmarks computed from our dataset
|
|
|
|
|
(spec §5/§8.6). The disadvantaged average must be weighted by cohort size,
|
|
|
|
|
medians must ignore NaN, and only the latest year counts."""
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import pandas as pd
|
|
|
|
|
|
|
|
|
|
from backend.data_loader import compute_benchmarks
|
|
|
|
|
|
|
|
|
|
LATEST = 202425
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _df():
|
|
|
|
|
rows = [
|
|
|
|
|
# Six primary schools, latest year. Disadvantaged RWM chosen so the
|
|
|
|
|
# weighted average differs clearly from the unweighted mean:
|
|
|
|
|
# weighted = (40*100 + 60*300) / 400 = 55.0 ; unweighted mean = 50.0
|
|
|
|
|
dict(year=LATEST, attainment_8_score=np.nan, eligible_pupils=100,
|
|
|
|
|
rwm_expected_disadvantaged_pct=40.0, eal_pct=10.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=10.0, disadvantaged_pct=20.0, fsm_pct=15.0, total_pupils=200),
|
2026-07-13 18:45:30 +01:00
|
|
|
dict(year=LATEST, attainment_8_score=np.nan, eligible_pupils=300,
|
|
|
|
|
rwm_expected_disadvantaged_pct=60.0, eal_pct=20.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=14.0, disadvantaged_pct=24.0, fsm_pct=17.0, total_pupils=280),
|
2026-07-13 18:45:30 +01:00
|
|
|
dict(year=LATEST, attainment_8_score=np.nan, eligible_pupils=np.nan,
|
|
|
|
|
rwm_expected_disadvantaged_pct=99.0, eal_pct=30.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=18.0, disadvantaged_pct=30.0, fsm_pct=19.0, total_pupils=300),
|
2026-07-13 18:45:30 +01:00
|
|
|
dict(year=LATEST, attainment_8_score=np.nan, eligible_pupils=50,
|
|
|
|
|
rwm_expected_disadvantaged_pct=np.nan, eal_pct=np.nan,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=np.nan, disadvantaged_pct=np.nan, fsm_pct=np.nan, total_pupils=np.nan),
|
2026-07-13 18:45:30 +01:00
|
|
|
dict(year=LATEST, attainment_8_score=np.nan, eligible_pupils=40,
|
|
|
|
|
rwm_expected_disadvantaged_pct=np.nan, eal_pct=40.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=20.0, disadvantaged_pct=40.0, fsm_pct=21.0, total_pupils=350),
|
2026-07-13 18:45:30 +01:00
|
|
|
dict(year=LATEST, attainment_8_score=np.nan, eligible_pupils=60,
|
|
|
|
|
rwm_expected_disadvantaged_pct=np.nan, eal_pct=50.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=22.0, disadvantaged_pct=44.0, fsm_pct=23.0, total_pupils=400),
|
2026-07-13 18:45:30 +01:00
|
|
|
# Two secondary schools (attainment_8 non-null)
|
|
|
|
|
dict(year=LATEST, attainment_8_score=45.0, eligible_pupils=180,
|
|
|
|
|
rwm_expected_disadvantaged_pct=np.nan, eal_pct=15.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=12.0, disadvantaged_pct=22.0, fsm_pct=12.0, total_pupils=1000),
|
2026-07-13 18:45:30 +01:00
|
|
|
dict(year=LATEST, attainment_8_score=50.0, eligible_pupils=200,
|
|
|
|
|
rwm_expected_disadvantaged_pct=np.nan, eal_pct=25.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=16.0, disadvantaged_pct=26.0, fsm_pct=14.0, total_pupils=1200),
|
2026-07-13 18:45:30 +01:00
|
|
|
# An older-year primary row that must NOT influence anything
|
|
|
|
|
dict(year=202324, attainment_8_score=np.nan, eligible_pupils=500,
|
|
|
|
|
rwm_expected_disadvantaged_pct=1.0, eal_pct=99.0,
|
2026-07-15 17:23:40 +01:00
|
|
|
sen_support_pct=99.0, disadvantaged_pct=99.0, fsm_pct=99.0, total_pupils=9999),
|
2026-07-13 18:45:30 +01:00
|
|
|
]
|
|
|
|
|
return pd.DataFrame(rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_weighted_disadvantaged_average():
|
|
|
|
|
b = compute_benchmarks(_df())
|
|
|
|
|
# Row 3 has NaN eligible_pupils and must be excluded from the weighting.
|
|
|
|
|
assert b["primary"]["disadvantaged_rwm_expected_pct"] == 55.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_medians_ignore_nan_and_older_years():
|
|
|
|
|
b = compute_benchmarks(_df())
|
|
|
|
|
assert b["year"] == LATEST
|
|
|
|
|
# eal medians over [10,20,30,40,50] = 30
|
|
|
|
|
assert b["primary"]["eal_pct"] == 30.0
|
2026-07-15 17:23:40 +01:00
|
|
|
# fsm medians over [15,17,19,21,23] = 19
|
|
|
|
|
assert b["primary"]["fsm_pct"] == 19.0
|
2026-07-13 18:45:30 +01:00
|
|
|
# median pupils over [200,280,300,350,400] = 300
|
|
|
|
|
assert b["primary"]["median_pupils"] == 300
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_secondary_block_has_no_disadvantaged_rwm():
|
|
|
|
|
b = compute_benchmarks(_df())
|
|
|
|
|
assert "disadvantaged_rwm_expected_pct" not in b["secondary"]
|
2026-07-15 17:23:40 +01:00
|
|
|
assert b["secondary"]["fsm_pct"] == 13.0
|
2026-07-13 18:45:30 +01:00
|
|
|
assert b["secondary"]["median_pupils"] == 1100
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_provenance_string():
|
|
|
|
|
b = compute_benchmarks(_df())
|
|
|
|
|
assert b["source"] == "state-school average (computed from our dataset)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_empty_df():
|
|
|
|
|
assert compute_benchmarks(pd.DataFrame()) == {}
|