fix(search): tag only what the register records, and count the whole school #176

Merged
tudor merged 3 commits from fix/search-row-facts into main 2026-10-02 22:05:56 +00:00
Owner

Fixes the search-row and pupil-count errors found while reviewing the school header (proposal). All were visible on staging.

Row tags

  • Every non-selective secondary was tagged "Selective". detectAdmissionsTag used includes('selective'), which "Non-selective" passes (Burntwood, Graveney). It now needs an exact Selective.
  • Schools with no religious character got a faith tag. Both rows excluded only Does not apply, so None showed as "Faith priority" in the secondary row (Putney High) or as a bare "None" chip in the primary row (Abacus Belsize Primary). New hasReligiousCharacter() treats both no-faith values as no faith.

Pupil counts

  • Search showed the GCSE year group as "pupils". The list API's total_pupils came from fact_performance, which is the results cohort: for a secondary, Year 11 alone. Burntwood showed 245 in search and 1,462 on its page. The list and place payloads now carry the register's whole-school count (gias_total_pupils), and nothing when the register has none. Map popups and the compare basket read the same field.
  • The header and the wellbeing section fell back to the same results figure when the census had no record. They now fall back to the register count. SchoolDetailShell took yearlyData only for that fallback, so the prop is gone. SchoolResult stays in the shell's type import to avoid a conflict with #175. The header redesign PR removes it.

E2E

  • Three journeys: a non-selective secondary's row has no Selective tag; a school recorded with no religious character has no faith tag; the list and the school page agree on a secondary's pupil count. Run against staging before the fix, all three fail (Selective count 1, faith tag count 1, 3129 ≠ 3231).
  • Nine journeys asked the list API for per_page, which it ignores (it reads page_size), so each got 25 rows whatever it asked for.

Checks

  • Jest: 69 suites, 584 tests pass. tsc --noEmit clean.
  • Backend: 299 tests pass (new test_whole_school_pupils.py).
  • Merges cleanly with #175 in either order (checked by merging both branches).

🤖 Generated with Claude Code

Fixes the search-row and pupil-count errors found while reviewing the school header ([proposal](https://claude.ai/artifact/FTVpkEFLmJXVfCGKzqWQrd)). All were visible on staging. ## Row tags - **Every non-selective secondary was tagged "Selective".** `detectAdmissionsTag` used `includes('selective')`, which "Non-selective" passes (Burntwood, Graveney). It now needs an exact `Selective`. - **Schools with no religious character got a faith tag.** Both rows excluded only `Does not apply`, so `None` showed as "Faith priority" in the secondary row (Putney High) or as a bare "None" chip in the primary row (Abacus Belsize Primary). New `hasReligiousCharacter()` treats both no-faith values as no faith. ## Pupil counts - **Search showed the GCSE year group as "pupils".** The list API's `total_pupils` came from `fact_performance`, which is the results cohort: for a secondary, Year 11 alone. Burntwood showed 245 in search and 1,462 on its page. The list and place payloads now carry the register's whole-school count (`gias_total_pupils`), and nothing when the register has none. Map popups and the compare basket read the same field. - The header and the wellbeing section fell back to the same results figure when the census had no record. They now fall back to the register count. `SchoolDetailShell` took `yearlyData` only for that fallback, so the prop is gone. `SchoolResult` stays in the shell's type import to avoid a conflict with #175. The header redesign PR removes it. ## E2E - Three journeys: a non-selective secondary's row has no Selective tag; a school recorded with no religious character has no faith tag; the list and the school page agree on a secondary's pupil count. Run against staging before the fix, **all three fail** (`Selective` count 1, faith tag count 1, `3129 ≠ 3231`). - Nine journeys asked the list API for `per_page`, which it ignores (it reads `page_size`), so each got 25 rows whatever it asked for. ## Checks - Jest: 69 suites, 584 tests pass. `tsc --noEmit` clean. - Backend: 299 tests pass (new `test_whole_school_pupils.py`). - Merges cleanly with #175 in either order (checked by merging both branches). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tudor added 3 commits 2026-10-02 21:51:59 +00:00
The secondary row tested the admissions policy with includes('selective'),
which "Non-selective" passes, so every comprehensive (Burntwood, Graveney)
was tagged Selective. It now needs an exact "Selective".

Both rows excluded only "Does not apply" from the religious character, so a
school recorded as "None" got "Faith priority" (Putney High) or a bare
"None" chip (Abacus Belsize Primary). hasReligiousCharacter() treats both of
the register's no-faith values as no faith.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fact_performance's total_pupils is the cohort a year's results were
measured on. For a secondary that is Year 11 alone, and the list API sent
it as the card's "pupils": Burntwood showed 245 in search and 1,462 on its
page. The list and place payloads now carry the register's whole-school
count, and nothing when the register has none. Map popups and the compare
basket read the same field.

The header and the wellbeing section fell back to the same results figure
when the census had no record. They now fall back to the register count.
The shell took yearlyData only for that fallback, so the prop is gone and
the results array no longer ships to the client for the chrome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(e2e): journeys for row tags and pupil counts, and ask for page_size
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m13s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 17s
8020191832
Three journeys pin the fixes: a non-selective secondary's row has no
Selective tag, a school recorded with no religious character has no faith
tag, and the list and the school page agree on a secondary's pupil count.
Run against staging before the fix, all three fail.

The list API reads page_size. Nine journeys asked for per_page, which it
ignores, so each got the default 25 rows whatever it asked for.

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

🤖 AI Code Review (Claude Code)

The PR makes search cards, map popups and place rows show the register's whole-school pupil count (gias_total_pupils) instead of the GCSE cohort. It also fixes the false 'Selective' and 'Faith priority' tags and removes the cohort fallback from the school page header. The e2e tests now send page_size instead of per_page. The change is small and consistent, with unit and e2e tests for each fix, and I found no correctness, security or deploy problems.

🟡 Minor

  • nextjs-app/components/school/SchoolDetailShell.tsx: Removing yearlyData and latestResults may leave the SchoolResult import unused in this file, and latestResults possibly unused in WellbeingSection.tsx. This could fail an unused-import lint or tsc check, so it is worth confirming that lint passes.
## 🤖 AI Code Review (Claude Code) The PR makes search cards, map popups and place rows show the register's whole-school pupil count (`gias_total_pupils`) instead of the GCSE cohort. It also fixes the false 'Selective' and 'Faith priority' tags and removes the cohort fallback from the school page header. The e2e tests now send `page_size` instead of `per_page`. The change is small and consistent, with unit and e2e tests for each fix, and I found no correctness, security or deploy problems. ### 🟡 Minor - **nextjs-app/components/school/SchoolDetailShell.tsx**: Removing `yearlyData` and `latestResults` may leave the `SchoolResult` import unused in this file, and `latestResults` possibly unused in `WellbeingSection.tsx`. This could fail an unused-import lint or `tsc` check, so it is worth confirming that lint passes.
tudor merged commit c931d1078c into main 2026-10-02 22:05:56 +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#176