feat: similar schools nearby on the detail page #150

Merged
tudor merged 12 commits from feat/similar-schools-nearby into main 2026-09-22 05:53:07 +00:00
Showing only changes of commit 83dc5ae5dc - Show all commits
@@ -163,12 +163,25 @@ reader to assume otherwise.
| `metric_key` | `rwm_expected_pct` or `attainment_8_score` — see below |
| `metric_year` | the year the figure is from |
The metric follows the template the page is rendering, not the neighbour's own
phase, so a row of cards never mixes two scales. Primary and **all-through**
pages use `rwm_expected_pct`, matching `PrimarySchoolSections`, which is the
template all-through schools render with; secondary pages use
`attainment_8_score`. Where the neighbour has no value for that key, the card
reads "Not published" rather than falling back to the other key.
The metric follows the phase side the school was *matched* on, not the
neighbour's own phase, so a row of cards never mixes two scales. The secondary
side uses `attainment_8_score`; the primary side uses `rwm_expected_pct`. Where
the neighbour has no value for that key, the card reads "Not published" rather
than falling back to the other key.
Which side a school takes is decided once, in
`similar_schools.is_secondary_phase`, by membership of `PHASE_GROUPS["secondary"]`
minus all-through — never by testing for the substring "secondary", which misses
`16 plus` (GIAS phase 6) and hands a sixth-form college the primary bucket.
All-through is the exception in the other direction: `PHASE_GROUPS` lists it on
both sides, but it takes the primary metric.
This is usually the same thing as "the template the page renders", but not
always. `computeSchoolFlags` decides the template with that same substring test,
so a `16 plus` school renders `PrimarySchoolSections` while being matched —
correctly — against secondaries. The section therefore takes its lede noun from
the school's own phase rather than from its template, or it would print "Other
primary schools near <sixth form college>" above a row of secondaries.
`tier` is carried explicitly rather than inferred from the contents of
`shared`, because the frontend needs it for two separate decisions — whether the
@@ -355,6 +368,8 @@ synthetic frame rather than live marts:
- tiers stop relaxing once three are found: four tier-1 matches never open tier 2
- more than six qualifying schools returns the six nearest
- an all-through school is offered on both phase sides
- a `16 plus` school is matched against secondaries and colleges, never primaries
- `is_secondary_phase` and `PHASE_GROUPS` agree on every GIAS phase value
- fewer than two qualifying schools returns `[]`
- distances match a hand-computed haversine for a known pair