refactor: rename similar → nearby, so the code says what the section does

The section ranks on distance and is headed "Other schools nearby", but every
identifier still called it "similar" — the exact drift that leaves a later
reader trusting a name over the behaviour.

Mechanical: files, the module, the payload key, the type, the components, the
prop. No behaviour change; the suites are unchanged in count and still green.
Free to do now because #150 has not merged, so the payload key rename needs no
lockstep deploy. Uses of "similar" that are ordinary English — progress
measures compared to similar pupils, and unrelated comments — are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
TudorandClaude Opus 5 committed 2026-09-22 13:06:32 +01:00
1 parent 5c0ccc693d
commit cd6a45bf7d
15 files changed
+102 -102

No files matched your search

+6 -6
View File
@@ -41,7 +41,7 @@ from .data_loader import get_data_info as get_db_info
from . import flags
from .places import build_place_index, build_place_registry, places_for_urn
from .schemas import METRIC_DEFINITIONS, PHASE_GROUPS, RANKING_COLUMNS, SCHOOL_COLUMNS
from .similar_schools import select_similar
from .nearby_schools import select_nearby
from .utils import clean_for_json, convert_to_native
# Values to exclude from filter dropdowns (empty strings, non-applicable labels)
@@ -266,10 +266,10 @@ def _places_payload(urn: int) -> list[dict]:
return payload
def _similar_schools_payload(urn: int) -> list[dict]:
def _nearby_schools_payload(urn: int) -> list[dict]:
"""The nearest eligible schools this page may offer, closest first.
Phase and reach are read from the school's own row inside select_similar,
Phase and reach are read from the school's own row inside select_nearby,
so nothing here can hand it a phase that disagrees with the data.
Wrapped: a failure in selection must never 500 a page that is otherwise
@@ -277,12 +277,12 @@ def _similar_schools_payload(urn: int) -> list[dict]:
section simply does not render.
"""
try:
return select_similar(load_latest_school_data(), int(urn))
return select_nearby(load_latest_school_data(), int(urn))
except Exception:
import logging
logging.getLogger(__name__).exception(
"Similar schools selection failed for urn=%s", urn
"Nearby schools selection failed for urn=%s", urn
)
return []
@@ -1000,7 +1000,7 @@ async def get_school_details(request: Request, urn: int):
# The nearest eligible schools, closest first. Always present on a
# build with this code; the frontend treats absent and empty
# identically, which is what lets the two images deploy independently.
"similar_schools": _similar_schools_payload(urn),
"nearby_schools": _nearby_schools_payload(urn),
"yearly_data": clean_for_json(school_data),
# Supplementary data (null if not yet populated by Kestra)
"ofsted": supplementary.get("ofsted"),
@@ -183,7 +183,7 @@ def _shared(subject: pd.Series, candidate: pd.Series, is_secondary: bool) -> lis
return shared
def select_similar(frame: pd.DataFrame, urn: int) -> list[dict]:
def select_nearby(frame: pd.DataFrame, urn: int) -> list[dict]:
"""The nearest eligible schools, closest first — at most MAX_SCHOOLS, and
none at all below MINIMUM.
+1 -1
View File
@@ -536,7 +536,7 @@ RANKING_COLUMNS = [
# include. All-through schools appear in both primary and secondary results,
# which is why this is a set per phase rather than a single string comparison.
#
# Lives here rather than in app.py because similar_schools.py needs it too, and
# Lives here rather than in app.py because nearby_schools.py needs it too, and
# importing app from there would be a cycle.
PHASE_GROUPS: dict[str, set[str]] = {
"primary": {"primary", "middle deemed primary", "all-through"},
@@ -15,10 +15,10 @@ card and never reorders the row.
import numpy as np
import pandas as pd
from backend.similar_schools import (
from backend.nearby_schools import (
is_secondary_phase,
radius_miles,
select_similar,
select_nearby,
)
BASE_LAT, BASE_LON = 51.5000, -0.1000
@@ -66,7 +66,7 @@ def test_returns_nearest_first():
_row(100003, "Near", latitude=_at(0.4)),
_row(100004, "Far", latitude=_at(1.8)),
)
result = select_similar(frame, 100001)
result = select_nearby(frame, 100001)
assert [s["urn"] for s in result] == [100003, 100002, 100004]
assert result[0]["distance_miles"] == 0.4
@@ -81,7 +81,7 @@ def test_a_faith_match_never_outranks_a_closer_school():
_row(100004, "Sacred Heart RC", religious_denomination="Roman Catholic", latitude=_at(1.2)),
_row(100005, "St Peter's RC", religious_denomination="Roman Catholic", latitude=_at(1.6)),
)
result = select_similar(frame, 100001)
result = select_nearby(frame, 100001)
assert result[0]["urn"] == 100002, "the nearest school leads, whatever its intake"
assert [s["distance_miles"] for s in result] == sorted(s["distance_miles"] for s in result)
@@ -98,7 +98,7 @@ def test_the_nearest_eligible_school_is_always_shown():
for n in range(6)
],
)
assert select_similar(frame, 100001)[0]["urn"] == 100002
assert select_nearby(frame, 100001)[0]["urn"] == 100002
def test_caps_at_six_taking_the_nearest():
@@ -106,7 +106,7 @@ def test_caps_at_six_taking_the_nearest():
_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)
result = select_nearby(frame, 100001)
assert len(result) == 6
assert 100016 not in {s["urn"] for s in result}, "the seventh-nearest is the one dropped"
@@ -116,7 +116,7 @@ def test_fewer_than_two_matches_returns_empty():
_row(100001, "Subject"),
_row(100002, "Only neighbour", latitude=_at(0.5)),
)
assert select_similar(frame, 100001) == []
assert select_nearby(frame, 100001) == []
def test_excludes_the_subject_school():
@@ -125,7 +125,7 @@ def test_excludes_the_subject_school():
_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)}
assert 100001 not in {s["urn"] for s in select_nearby(frame, 100001)}
def test_a_school_is_never_listed_twice():
@@ -134,7 +134,7 @@ def test_a_school_is_never_listed_twice():
_row(100002, "A", latitude=_at(0.5)),
_row(100003, "B", latitude=_at(0.6)),
)
result = select_similar(frame, 100001)
result = select_nearby(frame, 100001)
assert len(result) == len({s["urn"] for s in result})
@@ -151,7 +151,7 @@ def test_primary_does_not_reach_past_two_miles():
)
# One inside the cap is below the minimum, so nothing renders at all —
# a primary with nothing within two miles has no nearby schools.
assert select_similar(frame, 100001) == []
assert select_nearby(frame, 100001) == []
def test_secondary_reaches_further_than_primary():
@@ -160,7 +160,7 @@ def test_secondary_reaches_further_than_primary():
_row(100002, "A", phase="Secondary", latitude=_at(3.0)),
_row(100003, "B", phase="Secondary", latitude=_at(5.5)),
)
assert {s["urn"] for s in select_similar(frame, 100001)} == {100002, 100003}
assert {s["urn"] for s in select_nearby(frame, 100001)} == {100002, 100003}
def test_the_cap_follows_the_phase():
@@ -183,8 +183,8 @@ def test_selective_never_meets_non_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) == []
assert 100001 not in {s["urn"] for s in select_similar(frame, 100002)}
assert select_nearby(frame, 100001) == []
assert 100001 not in {s["urn"] for s in select_nearby(frame, 100002)}
def test_special_schools_match_only_each_other():
@@ -193,8 +193,8 @@ def test_special_schools_match_only_each_other():
_row(100002, "Mainstream A", latitude=_at(0.5)),
_row(100003, "Mainstream B", latitude=_at(0.6)),
)
assert select_similar(frame, 100001) == []
assert select_similar(frame, 100002) == []
assert select_nearby(frame, 100001) == []
assert select_nearby(frame, 100002) == []
def test_boys_never_meets_girls():
@@ -204,7 +204,7 @@ def test_boys_never_meets_girls():
_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)}
urns = {s["urn"] for s in select_nearby(frame, 100001)}
assert 100002 not in urns
assert urns == {100003, 100004}
@@ -217,7 +217,7 @@ def test_closed_schools_and_missing_coordinates_are_dropped():
_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)} == {100004, 100005}
assert {s["urn"] for s in select_nearby(frame, 100001)} == {100004, 100005}
def test_all_through_is_offered_on_both_phase_sides():
@@ -226,14 +226,14 @@ def test_all_through_is_offered_on_both_phase_sides():
_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)}
assert 100002 in {s["urn"] for s in select_nearby(frame, 100001)}
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)}
assert 100002 in {s["urn"] for s in select_nearby(secondary, 100010)}
def test_sixteen_plus_is_matched_against_secondary_not_primary():
@@ -248,7 +248,7 @@ def test_sixteen_plus_is_matched_against_secondary_not_primary():
_row(100003, "Nearby College", phase="16 plus", latitude=_at(0.6)),
_row(100004, "Nearby Primary", phase="Primary", latitude=_at(0.1)),
)
result = select_similar(frame, 100001)
result = select_nearby(frame, 100001)
urns = {s["urn"] for s in result}
assert 100004 not in urns, "a primary school is not a peer for a sixth form"
assert urns == {100002, 100003}
@@ -278,7 +278,7 @@ def test_shared_lists_only_what_is_actually_shared():
_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)}
by_urn = {s["urn"]: s for s in select_nearby(frame, 100001)}
assert by_urn[100002]["shared"] == ["Mixed", "Non-selective", "No religious character"]
assert by_urn[100003]["shared"] == ["Mixed", "Non-selective"]
@@ -289,7 +289,7 @@ def test_a_shared_faith_is_named():
_row(100002, "Also RC", religious_denomination="Roman Catholic", latitude=_at(0.4)),
_row(100003, "Secular", religious_denomination="None", latitude=_at(0.5)),
)
by_urn = {s["urn"]: s for s in select_similar(frame, 100001)}
by_urn = {s["urn"]: s for s in select_nearby(frame, 100001)}
assert "Roman Catholic" in by_urn[100002]["shared"]
assert by_urn[100003]["shared"] == ["Mixed"]
@@ -300,7 +300,7 @@ def test_shared_is_empty_when_nothing_is_shared():
_row(100002, "A", gender="Mixed", religious_denomination="None", latitude=_at(0.4)),
_row(100003, "B", gender="Mixed", religious_denomination="Church of England", latitude=_at(0.5)),
)
assert all(s["shared"] == [] for s in select_similar(frame, 100001))
assert all(s["shared"] == [] for s in select_nearby(frame, 100001))
def test_no_tier_is_reported_because_there_are_no_tiers():
@@ -309,7 +309,7 @@ def test_no_tier_is_reported_because_there_are_no_tiers():
_row(100002, "A", latitude=_at(0.4)),
_row(100003, "B", latitude=_at(0.5)),
)
assert all("tier" not in s for s in select_similar(frame, 100001))
assert all("tier" not in s for s in select_nearby(frame, 100001))
def test_metric_follows_the_phase_side_not_the_neighbour():
@@ -318,7 +318,7 @@ def test_metric_follows_the_phase_side_not_the_neighbour():
_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)}
by_urn = {s["urn"]: s for s in select_nearby(frame, 100001)}
assert by_urn[100002]["metric_key"] == "attainment_8_score"
assert by_urn[100002]["metric_value"] == 52.8
assert by_urn[100002]["metric_year"] == 202425
@@ -331,7 +331,7 @@ def test_values_are_json_safe_native_types():
_row(100002, "A", latitude=_at(0.5)),
_row(100003, "B", latitude=_at(0.6)),
)
for school in select_similar(frame, 100001):
for school in select_nearby(frame, 100001):
assert isinstance(school["urn"], int)
assert isinstance(school["distance_miles"], float)
assert not isinstance(school["metric_value"], np.generic)
@@ -366,7 +366,7 @@ def client(monkeypatch):
def test_detail_payload_carries_nearby_schools(client):
resp = client.get("/api/schools/100001")
assert resp.status_code == 200, resp.text
similar = resp.json()["similar_schools"]
similar = resp.json()["nearby_schools"]
assert [s["school_name"] for s in similar] == ["Neighbour A", "Neighbour B"]
assert similar[0]["metric_key"] == "rwm_expected_pct"
@@ -377,7 +377,7 @@ def test_a_failure_in_selection_does_not_break_the_page(client, monkeypatch):
def _explode(*args, **kwargs):
raise ValueError("selection blew up")
monkeypatch.setattr(app_module, "select_similar", _explode)
monkeypatch.setattr(app_module, "select_nearby", _explode)
resp = client.get("/api/schools/100001")
assert resp.status_code == 200, resp.text
assert resp.json()["similar_schools"] == []
assert resp.json()["nearby_schools"] == []