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.
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)
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>
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>
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes the search-row and pupil-count errors found while reviewing the school header (proposal). All were visible on staging.
Row tags
detectAdmissionsTagusedincludes('selective'), which "Non-selective" passes (Burntwood, Graveney). It now needs an exactSelective.Does not apply, soNoneshowed as "Faith priority" in the secondary row (Putney High) or as a bare "None" chip in the primary row (Abacus Belsize Primary). NewhasReligiousCharacter()treats both no-faith values as no faith.Pupil counts
total_pupilscame fromfact_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.SchoolDetailShelltookyearlyDataonly for that fallback, so the prop is gone.SchoolResultstays in the shell's type import to avoid a conflict with #175. The header redesign PR removes it.E2E
Selectivecount 1, faith tag count 1,3129 ≠ 3231).per_page, which it ignores (it readspage_size), so each got 25 rows whatever it asked for.Checks
tsc --noEmitclean.test_whole_school_pupils.py).🤖 Generated with Claude Code
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>🤖 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 sendpage_sizeinstead ofper_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
yearlyDataandlatestResultsmay leave theSchoolResultimport unused in this file, andlatestResultspossibly unused inWellbeingSection.tsx. This could fail an unused-import lint ortsccheck, so it is worth confirming that lint passes.