feat(search): school type groups and a faith filter #170

Merged
tudor merged 9 commits from feat/school-type-groups-faith into main 2026-10-02 14:05:43 +00:00
Owner

Problem

The School type filter offered GIAS's 34 establishment types ("Academy converter", "Academy sponsor led", "Voluntary controlled school"…). These describe governance and funding, which a parent can't be expected to tell apart and rarely needs to. There was also no way to filter by faith, which is the real meaning behind "Voluntary aided" and "Voluntary controlled", and which applies to many academies too.

Spec: docs/superpowers/specs/2026-10-02-school-type-groups-and-faith-filter-design.md · Plan: docs/superpowers/plans/2026-10-02-school-type-groups-and-faith-filter.md

Change

School type → six groups: State school: academy or free school · State school: council-run · Independent (fee-paying) · Special school (SEND) · Sixth form or college · Alternative provision. Six rarely relevant types (higher education, secure units, online provider…) sit in no group and are reachable only under "Any school type".

New Faith filter: No religious character · Church of England · Roman Catholic · Other Christian · Jewish · Muslim · Other faith. A joint school (e.g. "Roman Catholic/Church of England") matches every faith it names. Faith goes behind More filters on desktop, and after School type in the phone sheet.

  • Backend: new backend/school_groups.py defines the groups over GIAS codes and looks them up by the translated name, which is all the API's data carries. /api/schools accepts a group key for school_type (an old raw-label link still filters exactly) plus a new faith key; an unknown faith key returns nothing. /api/filters gains school_type_groups and faiths; school_types is unchanged. No mart change, so no Airflow run is needed.
  • Frontend: FilterBar reads the two lists. Chips, counts, Clear all and analytics include faith. A URL value is matched ignoring case, and an unlisted one (an old link) shows as its own option, so the select never reads "Any" while the list is filtered. The results list's chip shows the group's label.
  • Analytics note: search_submitted.filters_active now reports type=<group key> (e.g. type=special), not raw labels. Anything grouping on type= will split at the deploy date.

Testing

  • Backend: new test_school_groups.py, which fails if any GIAS type or religion code is unaccounted for, and test_type_and_faith_filters.py. CI suite 311/311.
  • Frontend: new FilterBarTypeFaith.test.tsx; existing fixtures moved to group keys. 569/569, typecheck clean, next build passes with DATABASE_URL unset.
  • E2E: new journey "school type groups and the faith filter narrow to what they name". Against current staging it fails as expected at selecting "Special school (SEND)". It can only pass after merge.
  • Whole-branch review by a fresh reviewer: 0 Critical. Both Important findings (the results chip showing the raw key; the select reading "Any" for an old or mixed-case value) were fixed test-first.

Deferred minors: /api/filters derives the offered groups from all years, not only the latest; a valid faith key returns nothing if the religion column were ever missing; docs/LEGACY_CODE.md should list /api/filters school_types as no longer read by the UI; the new E2E journey passes trivially if its search has no special Catholic schools.

🤖 Generated with Claude Code

## Problem The School type filter offered GIAS's 34 establishment types ("Academy converter", "Academy sponsor led", "Voluntary controlled school"…). These describe governance and funding, which a parent can't be expected to tell apart and rarely needs to. There was also no way to filter by faith, which is the real meaning behind "Voluntary aided" and "Voluntary controlled", and which applies to many academies too. Spec: `docs/superpowers/specs/2026-10-02-school-type-groups-and-faith-filter-design.md` · Plan: `docs/superpowers/plans/2026-10-02-school-type-groups-and-faith-filter.md` ## Change **School type → six groups:** State school: academy or free school · State school: council-run · Independent (fee-paying) · Special school (SEND) · Sixth form or college · Alternative provision. Six rarely relevant types (higher education, secure units, online provider…) sit in no group and are reachable only under "Any school type". **New Faith filter:** No religious character · Church of England · Roman Catholic · Other Christian · Jewish · Muslim · Other faith. A joint school (e.g. "Roman Catholic/Church of England") matches every faith it names. Faith goes behind More filters on desktop, and after School type in the phone sheet. - **Backend:** new `backend/school_groups.py` defines the groups over GIAS codes and looks them up by the translated name, which is all the API's data carries. `/api/schools` accepts a group key for `school_type` (an old raw-label link still filters exactly) plus a new `faith` key; an unknown faith key returns nothing. `/api/filters` gains `school_type_groups` and `faiths`; `school_types` is unchanged. No mart change, so no Airflow run is needed. - **Frontend:** FilterBar reads the two lists. Chips, counts, Clear all and analytics include faith. A URL value is matched ignoring case, and an unlisted one (an old link) shows as its own option, so the select never reads "Any" while the list is filtered. The results list's chip shows the group's label. - **Analytics note:** `search_submitted.filters_active` now reports `type=<group key>` (e.g. `type=special`), not raw labels. Anything grouping on `type=` will split at the deploy date. ## Testing - Backend: new `test_school_groups.py`, which fails if any GIAS type or religion code is unaccounted for, and `test_type_and_faith_filters.py`. CI suite 311/311. - Frontend: new `FilterBarTypeFaith.test.tsx`; existing fixtures moved to group keys. 569/569, typecheck clean, `next build` passes with `DATABASE_URL` unset. - E2E: new journey **"school type groups and the faith filter narrow to what they name"**. Against current staging it fails as expected at selecting "Special school (SEND)". It can only pass after merge. - Whole-branch review by a fresh reviewer: 0 Critical. Both Important findings (the results chip showing the raw key; the select reading "Any" for an old or mixed-case value) were fixed test-first. **Deferred minors:** `/api/filters` derives the offered groups from all years, not only the latest; a valid faith key returns nothing if the religion column were ever missing; `docs/LEGACY_CODE.md` should list `/api/filters` `school_types` as no longer read by the UI; the new E2E journey passes trivially if its search has no special Catholic schools. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tudor added 7 commits 2026-10-02 11:01:27 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(search): show applied type and faith by label, in any case
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
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 1m18s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 22s
84caee9f72
The results list's chip printed the school type group's key ("post16")
rather than its label. A URL value that no option spelled the same way
(an old link's raw GIAS type, or a key in another case) left the select
reading "Any school type" while the list stayed filtered, and choosing
"Any" changed nothing. Values now match options ignoring case, as the
API does, and an unlisted one is shown as an option of its own.

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

🤖 AI Code Review (Claude Code)

Adds six parent-facing school type groups and a faith filter. The grouping lives in a new backend module that looks groups up by GIAS name, and /api/filters returns the new option lists. The frontend gets a Faith select, group labels on chips, and fallback handling for old raw-label links and mixed-case keys. The logic looks correct, including missing or blank religions counting as 'none', unknown keys matching nothing, and old links still working. Test coverage is thorough and nothing blocks the merge.

🟡 Minor

  • docs/superpowers/specs/2026-10-02-school-type-groups-and-faith-filter-design.md: The /api/schools section still says it filters on school_type_group and on a faith_groups column. The implementation has no such columns and filters by name at request time through _names_in_group. The architecture section was corrected but this section was not, so the spec contradicts the code.
  • nextjs-app/tests/components/FilterBarTypeFaith.test.tsx: The file ends with stray trailing blank lines. ResultsToolbar.test.tsx also gains a trailing blank line. This is a cosmetic lint nit.
## 🤖 AI Code Review (Claude Code) Adds six parent-facing school type groups and a faith filter. The grouping lives in a new backend module that looks groups up by GIAS name, and `/api/filters` returns the new option lists. The frontend gets a Faith select, group labels on chips, and fallback handling for old raw-label links and mixed-case keys. The logic looks correct, including missing or blank religions counting as 'none', unknown keys matching nothing, and old links still working. Test coverage is thorough and nothing blocks the merge. ### 🟡 Minor - **docs/superpowers/specs/2026-10-02-school-type-groups-and-faith-filter-design.md**: The `/api/schools` section still says it filters on `school_type_group` and on a `faith_groups` column. The implementation has no such columns and filters by name at request time through `_names_in_group`. The architecture section was corrected but this section was not, so the spec contradicts the code. - **nextjs-app/__tests__/components/FilterBarTypeFaith.test.tsx**: The file ends with stray trailing blank lines. `ResultsToolbar.test.tsx` also gains a trailing blank line. This is a cosmetic lint nit.
tudor added 1 commit 2026-10-02 12:25:20 +00:00
docs(spec): describe the API filters as they are built
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 1m18s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 26s
214c80663e
The /api/schools section still described filtering on stored
school_type_group and faith_groups columns, which the architecture
correction had already dropped: the filters look up the name columns at
request time. Also trims a trailing blank line from two test files.

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

🤖 AI Code Review (Claude Code)

Adds parent-facing school type groups and a faith filter. A new backend/school_groups.py maps GIAS names to groups, /api/schools gains a faith param and a group-key branch for school_type, and /api/filters gains school_type_groups and faiths. FilterBar gets a Faith select and grouped type options. Old raw-label links, missing or blank religion, unknown keys and mixed case are all handled and tested, and I found no correctness, security or deploy problems that should block the merge.

🟡 Minor

  • nextjs-app/components/HomeView.tsx: The results-list chip row shows school_type (now labelled) but has no chip for the new faith param. A faith-filtered result list therefore shows no indication in that row. FilterBar's own chips do cover faith, so this is only an inconsistency.
  • backend/tests/test_school_groups.py: The test imports yaml to read dbt_project.yml. If pyyaml is not in requirements.txt, the whole module fails at collection in CI. The plan itself notes this uncertainty. Confirm that pyyaml is a declared dependency, or add it.
  • e2e/tests/journeys.spec.ts: The new journey cannot pass until the change is deployed to staging, because it selects the new option labels. If the staging E2E gate also runs on pre-merge PRs, this test will fail there. The PR says it runs post-merge only, so verify that.
## 🤖 AI Code Review (Claude Code) Adds parent-facing school type groups and a faith filter. A new `backend/school_groups.py` maps GIAS names to groups, `/api/schools` gains a `faith` param and a group-key branch for `school_type`, and `/api/filters` gains `school_type_groups` and `faiths`. FilterBar gets a Faith select and grouped type options. Old raw-label links, missing or blank religion, unknown keys and mixed case are all handled and tested, and I found no correctness, security or deploy problems that should block the merge. ### 🟡 Minor - **nextjs-app/components/HomeView.tsx**: The results-list chip row shows `school_type` (now labelled) but has no chip for the new `faith` param. A faith-filtered result list therefore shows no indication in that row. FilterBar's own chips do cover faith, so this is only an inconsistency. - **backend/tests/test_school_groups.py**: The test imports `yaml` to read `dbt_project.yml`. If `pyyaml` is not in `requirements.txt`, the whole module fails at collection in CI. The plan itself notes this uncertainty. Confirm that `pyyaml` is a declared dependency, or add it. - **e2e/tests/journeys.spec.ts**: The new journey cannot pass until the change is deployed to staging, because it selects the new option labels. If the staging E2E gate also runs on pre-merge PRs, this test will fail there. The PR says it runs post-merge only, so verify that.
tudor added 1 commit 2026-10-02 13:27:54 +00:00
refactor(search): drop the applied-filter row above the results
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
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 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 21s
49ac96b487
The row repeated the search, local authority and school type above the
results list, and nothing else: no phase, distance, gender, sixth form,
admissions or faith. The filter bar already shows every one of them, in
its selects and More filters count on desktop and as removable chips on
phones, so the row was a partial second copy.

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

🤖 AI Code Review (Claude Code)

Adds parent-facing school type groups and a faith filter. Groups are defined over GIAS codes in a new backend module and looked up by translated name. /api/schools gets the faith param and a group-key branch for school_type, and /api/filters gets two new keys. The frontend adds the Faith select, group-based School type options, and compatibility for old raw-label links. The change looks sound: missing, blank and unknown names are handled, old links still work, and the frontend tolerates an API without the new keys. I found no correctness, security or deploy problems.

✅ No issues found.

## 🤖 AI Code Review (Claude Code) Adds parent-facing school type groups and a faith filter. Groups are defined over GIAS codes in a new backend module and looked up by translated name. `/api/schools` gets the `faith` param and a group-key branch for `school_type`, and `/api/filters` gets two new keys. The frontend adds the Faith select, group-based School type options, and compatibility for old raw-label links. The change looks sound: missing, blank and unknown names are handled, old links still work, and the frontend tolerates an API without the new keys. I found no correctness, security or deploy problems. ✅ No issues found.
tudor merged commit fb3ef7d2b9 into main 2026-10-02 14:05:43 +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#170