"""Selection rules for the "similar schools nearby" section. The hard filters encode claims the section is not allowed to make — that a selective school is an alternative to a non-selective one, that a special school is comparable to a mainstream one, or that a Girls school is an option for a Boys school's reader. They never relax. The soft preferences describe how close the intake is, and they do — but only far enough to reach a usable set, never far enough to fill the last of the six slots. """ import numpy as np import pandas as pd from backend.similar_schools import select_similar # Roughly 0.7 miles apart in latitude at this longitude. BASE_LAT, BASE_LON = 51.5000, -0.1000 def _row(urn, name, **overrides): base = { "urn": urn, "school_name": name, "local_authority": "Testshire", "school_type": "Community school", "phase": "Primary", "age_range": "4-11", "status": "Open", "gender": "Mixed", "religious_denomination": "None", "admissions_policy": "Not applicable", "latitude": BASE_LAT, "longitude": BASE_LON, "year": 202425, "rwm_expected_pct": 70.0, "attainment_8_score": np.nan, } base.update(overrides) return base def _frame(*rows): return pd.DataFrame(list(rows)) def _at(miles): """A latitude `miles` north of BASE_LAT.""" return BASE_LAT + miles / 69.0 def test_returns_nearest_same_phase_schools(): frame = _frame( _row(100001, "Subject"), _row(100002, "Near", latitude=_at(0.5)), _row(100003, "Mid", latitude=_at(1.0)), _row(100004, "Far", latitude=_at(2.0)), ) result = select_similar(frame, 100001, is_secondary=False) assert [s["urn"] for s in result] == [100002, 100003, 100004] assert result[0]["distance_miles"] == 0.5 def test_excludes_the_subject_school(): frame = _frame( _row(100001, "Subject"), _row(100002, "A", latitude=_at(0.5)), _row(100003, "B", latitude=_at(0.6)), ) assert 100001 not in {s["urn"] for s in select_similar(frame, 100001, is_secondary=False)} def test_selective_never_meets_non_selective(): frame = _frame( _row(100001, "Grammar", phase="Secondary", admissions_policy="Selective"), _row(100002, "Comp A", phase="Secondary", admissions_policy="Non-selective", latitude=_at(0.5)), _row(100003, "Comp B", phase="Secondary", admissions_policy="Non-selective", latitude=_at(0.6)), ) assert select_similar(frame, 100001, is_secondary=True) == [] reverse = select_similar(frame, 100002, is_secondary=True) assert 100001 not in {s["urn"] for s in reverse} def test_special_schools_match_only_each_other(): frame = _frame( _row(100001, "Special", school_type="Community special school"), _row(100002, "Mainstream A", latitude=_at(0.5)), _row(100003, "Mainstream B", latitude=_at(0.6)), ) assert select_similar(frame, 100001, is_secondary=False) == [] assert select_similar(frame, 100002, is_secondary=False) == [] def test_boys_never_meets_girls(): frame = _frame( _row(100001, "Boys School", gender="Boys"), _row(100002, "Girls School", gender="Girls", latitude=_at(0.5)), _row(100003, "Mixed School", gender="Mixed", latitude=_at(0.6)), _row(100004, "Another Mixed", gender="Mixed", latitude=_at(0.7)), ) urns = {s["urn"] for s in select_similar(frame, 100001, is_secondary=False)} assert 100002 not in urns assert urns == {100003, 100004} def test_closed_schools_and_missing_coordinates_are_dropped(): frame = _frame( _row(100001, "Subject"), _row(100002, "Closed", status="Closed", latitude=_at(0.5)), _row(100003, "No coords", latitude=np.nan, longitude=np.nan), _row(100004, "Good A", latitude=_at(0.6)), _row(100005, "Good B", latitude=_at(0.7)), ) assert {s["urn"] for s in select_similar(frame, 100001, is_secondary=False)} == {100004, 100005} def test_tiers_relax_faith_before_gender(): frame = _frame( _row(100001, "Subject", gender="Boys", religious_denomination="Roman Catholic"), # Tier 1: same gender and same faith. _row(100002, "Tier one", gender="Boys", religious_denomination="Roman Catholic", latitude=_at(2.0)), # Tier 2: same gender, different faith — closer, but a weaker match. _row(100003, "Tier two", gender="Boys", religious_denomination="None", latitude=_at(0.5)), # Tier 3: mixed gender, different faith. _row(100004, "Tier three", gender="Mixed", religious_denomination="None", latitude=_at(0.6)), ) result = select_similar(frame, 100001, is_secondary=False) tier_by_urn = {s["urn"]: s["tier"] for s in result} assert tier_by_urn == {100002: 1, 100003: 2, 100004: 3} # Selected by tier, displayed by distance. assert [s["urn"] for s in result] == [100003, 100004, 100002] def test_caps_at_six_taking_the_nearest(): frame = _frame( _row(100001, "Subject"), *[_row(100010 + n, f"Peer {n}", latitude=_at(0.1 * (n + 1))) for n in range(7)], ) result = select_similar(frame, 100001, is_secondary=False) assert len(result) == 6 # The seventh-nearest is the one dropped, not an arbitrary one. assert 100016 not in {s["urn"] for s in result} def test_tiers_stop_once_enough_are_found(): """Four tier-1 matches are a usable set, so tier 2 is never opened — even though it holds a school that is closer than any of them.""" frame = _frame( _row(100001, "Subject", religious_denomination="Roman Catholic"), _row(100002, "RC one", religious_denomination="Roman Catholic", latitude=_at(0.5)), _row(100003, "RC two", religious_denomination="Roman Catholic", latitude=_at(0.6)), _row(100004, "RC three", religious_denomination="Roman Catholic", latitude=_at(0.7)), _row(100005, "RC four", religious_denomination="Roman Catholic", latitude=_at(0.8)), # Closer than every one of them, but only a tier-2 match. _row(100006, "Secular and nearer", religious_denomination="None", latitude=_at(0.2)), ) result = select_similar(frame, 100001, is_secondary=False) assert 100006 not in {s["urn"] for s in result} assert len(result) == 4 assert all(s["tier"] == 1 for s in result) def test_a_school_is_never_taken_twice(): frame = _frame( _row(100001, "Subject"), _row(100002, "A", latitude=_at(0.5)), _row(100003, "B", latitude=_at(0.6)), ) result = select_similar(frame, 100001, is_secondary=False) assert len(result) == len({s["urn"] for s in result}) def test_fewer_than_two_matches_returns_empty(): frame = _frame( _row(100001, "Subject"), _row(100002, "Only neighbour", latitude=_at(0.5)), ) assert select_similar(frame, 100001, is_secondary=False) == [] def test_beyond_the_widest_radius_is_not_offered(): frame = _frame( _row(100001, "Subject"), _row(100002, "A", latitude=_at(11.0)), _row(100003, "B", latitude=_at(12.0)), ) assert select_similar(frame, 100001, is_secondary=False) == [] def test_all_through_is_offered_on_both_phase_sides(): frame = _frame( _row(100001, "Primary subject", phase="Primary"), _row(100002, "All through", phase="All-through", latitude=_at(0.5)), _row(100003, "Primary peer", phase="Primary", latitude=_at(0.6)), ) assert 100002 in {s["urn"] for s in select_similar(frame, 100001, is_secondary=False)} secondary = _frame( _row(100010, "Secondary subject", phase="Secondary"), _row(100002, "All through", phase="All-through", latitude=_at(0.5)), _row(100011, "Secondary peer", phase="Secondary", latitude=_at(0.6)), ) assert 100002 in {s["urn"] for s in select_similar(secondary, 100010, is_secondary=True)} def test_chips_state_only_what_the_tier_earned(): frame = _frame( _row(100001, "Subject", phase="Secondary", gender="Mixed", religious_denomination="None", admissions_policy="Non-selective"), _row(100002, "Full match", phase="Secondary", gender="Mixed", religious_denomination="None", admissions_policy="Non-selective", latitude=_at(0.5)), _row(100003, "Faith differs", phase="Secondary", gender="Mixed", religious_denomination="Church of England", admissions_policy="Non-selective", latitude=_at(0.6)), ) by_urn = {s["urn"]: s for s in select_similar(frame, 100001, is_secondary=True)} assert by_urn[100002]["shared"] == ["Mixed", "Non-selective", "No religious character"] assert by_urn[100003]["shared"] == ["Mixed", "Non-selective"] def test_tier_three_chip_is_the_plain_phase(): frame = _frame( _row(100001, "Subject", gender="Boys"), _row(100002, "A", gender="Mixed", latitude=_at(0.5)), _row(100003, "B", gender="Mixed", latitude=_at(0.6)), ) result = select_similar(frame, 100001, is_secondary=False) assert all(s["shared"] == ["Primary school"] for s in result) def test_metric_follows_the_template_not_the_neighbour(): frame = _frame( _row(100001, "Subject", phase="Secondary", attainment_8_score=50.0), _row(100002, "A", phase="Secondary", attainment_8_score=52.8, latitude=_at(0.5)), _row(100003, "B", phase="Secondary", attainment_8_score=np.nan, latitude=_at(0.6)), ) by_urn = {s["urn"]: s for s in select_similar(frame, 100001, is_secondary=True)} assert by_urn[100002]["metric_key"] == "attainment_8_score" assert by_urn[100002]["metric_value"] == 52.8 assert by_urn[100002]["metric_year"] == 202425 assert by_urn[100003]["metric_value"] is None def test_values_are_json_safe_native_types(): frame = _frame( _row(100001, "Subject"), _row(100002, "A", latitude=_at(0.5)), _row(100003, "B", latitude=_at(0.6)), ) for school in select_similar(frame, 100001, is_secondary=False): assert isinstance(school["urn"], int) assert isinstance(school["distance_miles"], float) assert not isinstance(school["metric_value"], np.generic)