fix(school): send the school page its admissions policy, and read it exactly #179

Merged
tudor merged 2 commits from fix/school-page-selective-flag into main 2026-10-02 22:56:36 +00:00
Owner

Fixes the staging E2E failure after #177 merged (a selective school is flagged Selective, on its page and in search), and a live Admissions-section bug the fix would otherwise have made worse.

Root cause

The header's Selective flag reads school_info.admissions_policy. The detail endpoint (/api/schools/{urn}) never sent that field: on staging both Tiffin and The Grammar School at Leeds return school_info without it. The list API does send it, so the search row said Selective and the school page couldn't. My Jest fixtures set the field directly, which hid the gap. The E2E journey caught it.

Why it is more than one line

Sending the field switches on two older copies of the tag logic #176 fixed in the search rows:

  • Admissions section (SecondaryAdmissionsSection): includes('selective'), so every non-selective secondary would have started reading "entry to this school is by selective examination". It also counted "None" as a faith, and that half is live now: Burntwood's Admissions section reads "Faith priority: this school has a faith-based admissions priority (None)."
  • Cut-off note (describeCutoffAbsence): the same substring test. The distance feature is off on staging, but it would have misread every comprehensive once the feature is on.

All three callers now share isSelective() (exact match) and hasReligiousCharacter(). The latter now also treats "Not applicable" as no faith, matching the rule the place table (PlaceView's NO_FAITH) already documents. No includes('selective') is left in the frontend.

Tests

  • Backend: test_school_page_flag_fields.py checks the detail payload carries every field the header's flags read, so this class of gap fails CI. 305 pass.
  • Jest: Admissions-section notes (selective, non-selective, None, Does not apply, Church of England); describeCutoffAbsence with "Non-selective"; "Not applicable" as no faith. 72 suites, 629 pass; tsc clean.
  • E2E: a non-selective, no-faith secondary's Admissions section makes neither claim. It fails against staging today on "Faith priority:". The failing Selective journey from #177 should pass once this deploys.
  • Merges cleanly with #178.

Leftover, not changed: components/SchoolCard.tsx has the same "None" check but nothing imports it.

🤖 Generated with Claude Code

Fixes the staging E2E failure after #177 merged (`a selective school is flagged Selective, on its page and in search`), and a live Admissions-section bug the fix would otherwise have made worse. ## Root cause The header's Selective flag reads `school_info.admissions_policy`. The detail endpoint (`/api/schools/{urn}`) **never sent that field**: on staging both Tiffin and The Grammar School at Leeds return `school_info` without it. The list API does send it, so the search row said Selective and the school page couldn't. My Jest fixtures set the field directly, which hid the gap. The E2E journey caught it. ## Why it is more than one line Sending the field switches on two older copies of the tag logic #176 fixed in the search rows: - **Admissions section** (`SecondaryAdmissionsSection`): `includes('selective')`, so every non-selective secondary would have started reading *"entry to this school is by selective examination"*. It also counted "None" as a faith, and that half is **live now**: Burntwood's Admissions section reads *"Faith priority: this school has a faith-based admissions priority (None)."* - **Cut-off note** (`describeCutoffAbsence`): the same substring test. The distance feature is off on staging, but it would have misread every comprehensive once the feature is on. All three callers now share `isSelective()` (exact match) and `hasReligiousCharacter()`. The latter now also treats "Not applicable" as no faith, matching the rule the place table (`PlaceView`'s `NO_FAITH`) already documents. No `includes('selective')` is left in the frontend. ## Tests - Backend: `test_school_page_flag_fields.py` checks the detail payload carries **every field the header's flags read**, so this class of gap fails CI. 305 pass. - Jest: Admissions-section notes (selective, non-selective, None, Does not apply, Church of England); `describeCutoffAbsence` with "Non-selective"; "Not applicable" as no faith. 72 suites, 629 pass; `tsc` clean. - E2E: a non-selective, no-faith secondary's Admissions section makes neither claim. It **fails against staging today** on "Faith priority:". The failing Selective journey from #177 should pass once this deploys. - Merges cleanly with #178. Leftover, not changed: `components/SchoolCard.tsx` has the same "None" check but nothing imports it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tudor added 2 commits 2026-10-02 22:43:57 +00:00
The header's Selective flag read school_info.admissions_policy, which the
detail endpoint never sent, so no school page could flag Selective while
its search row did (staging E2E: The Grammar School at Leeds). The detail
payload now carries it, and a contract test checks it carries every field
the header's flags read.

Sending it would have switched on two older copies of the tag logic #176
fixed in the rows. The Admissions section and the cut-off note both tested
includes('selective'), so every non-selective secondary would have read
"entry is by selective examination". The section also counted "None" as a
faith: Burntwood reads "a faith-based admissions priority (None)" today.
All of them now share isSelective() and hasReligiousCharacter(), which also
treats "Not applicable" as no faith, as the place table already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(e2e): a no-faith comprehensive makes no Selective or faith claim
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m13s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 19s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m20s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 16s
da5d63593f
The Admissions section of a non-selective secondary with no religious
character must say neither "Selective:" nor "Faith priority:". Run against
staging before the fix, it fails on "Faith priority" (Burntwood's "(None)").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

🤖 AI Code Review (Claude Code)

Adds admissions_policy to the school detail API payload so the header's Selective flag works on school pages. It also replaces substring checks with an exact isSelective() helper, so 'Non-selective' is no longer read as selective. The helper also treats 'Not applicable' as no faith. Tests are added at the backend, unit and e2e levels. The change looks healthy.

🟡 Minor

  • e2e/tests/journeys.spec.ts: The new e2e test checks s.religious_denomination !== 'None' on search results. It assumes the search API returns that field and that the value is exactly 'None'. It also only checks the first 8 results. If the field is missing or has other casing, the test silently skips instead of failing, so it may never run.
  • nextjs-app/components/school/SecondaryAdmissionsSection.tsx: Before this change, the fallback only excluded 'Does not apply'. It now also hides the Faith priority note for 'Not applicable', 'None' and blank values. This is intended, but it is a behaviour change that is only covered by tests on the section component.
## 🤖 AI Code Review (Claude Code) Adds admissions_policy to the school detail API payload so the header's Selective flag works on school pages. It also replaces substring checks with an exact isSelective() helper, so 'Non-selective' is no longer read as selective. The helper also treats 'Not applicable' as no faith. Tests are added at the backend, unit and e2e levels. The change looks healthy. ### 🟡 Minor - **e2e/tests/journeys.spec.ts**: The new e2e test checks `s.religious_denomination !== 'None'` on search results. It assumes the search API returns that field and that the value is exactly 'None'. It also only checks the first 8 results. If the field is missing or has other casing, the test silently skips instead of failing, so it may never run. - **nextjs-app/components/school/SecondaryAdmissionsSection.tsx**: Before this change, the fallback only excluded 'Does not apply'. It now also hides the Faith priority note for 'Not applicable', 'None' and blank values. This is intended, but it is a behaviour change that is only covered by tests on the section component.
tudor merged commit 423b27140c into main 2026-10-02 22:56:36 +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#179