TudorandClaude Opus 5 5c0ccc693d fix: order nearby schools by distance, not by how alike they are
Reported from staging: a Catholic primary showed six Catholic primaries, none
of them close enough to be a real option, and omitted the community school
down the road.

Three causes, compounding. Ranking put tier before distance, so a faith match
at 2.9 miles outranked a community school at 0.3. The ENOUGH=3 stopping rule —
added so a cap of six would not drag in weak distant matches — filled the row
from the best tier before it ever widened, which is what made every card
Catholic. And a 3-mile tier-1 radius is sane for a secondary and most of a city
for a primary, whose catchments are routinely under a mile.

The premise was backwards. For a parent, distance is a constraint and intake is
a preference; a school beyond a primary catchment is not a weaker option, it is
not an option. So distance now decides the order and nothing else does. The
hard filters are untouched — they were always where the defensibility lived.
Similarity survives as chips on the card: reported, so a reader applies their
own weighting, rather than ranked, so we apply ours for them.

Reach is capped per phase (primary 2, secondary 6, post-16 10) as a sanity
bound, not a target: ordering already handles density, so the cap only decides
what happens where an area is sparse. A primary with nothing inside two miles
now renders no section, which is the honest answer.

Deleted: the tier system, the stopping rule, the tier-dependent lede, the
`tier` field, the tier-3 fallback chip and its style. select_similar also stops
taking is_secondary — it reads the phase from the subject's own row, so no
caller can hand it one that disagrees with the data.

The heading is now "Other schools nearby". The hard filters still guarantee a
comparable set, but nothing ranks on likeness, so the heading no longer says it
does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 13:06:32 +01:00
2026-01-06 19:05:22 +00:00
2026-04-07 16:17:56 +01:00
2026-01-07 16:20:49 +00:00

SchoolCompare

SchoolCompare compares schools across England: primary (KS2), secondary (KS4), all-through and post-16 provision, with coverage depending on the source dataset. It provides school search, postcode maps, comparisons, rankings, place pages, Ofsted information, admissions and destination measures. Editorial content lives in a Payload CMS blog.

Start here

Repository map

Path Responsibility
backend/ FastAPI routes, cached school data, read-only SQLAlchemy mappings, feature flags
nextjs-app/ Next.js App Router, React UI, Payload CMS, frontend tests
pipeline/plugins/extractors/ Custom Singer taps for GIAS, EES, Ofsted and other datasets
pipeline/transform/ dbt staging/intermediate models, marts, seeds and data tests
pipeline/dags/ Airflow extraction, transformation and publication workflows
pipeline/scripts/ Search indexing, code generation and operational diagnostics
e2e/ Playwright journeys against a running environment
.gitea/workflows/ PR checks, staging deployment and manual production promotion
scripts/ CI review tooling and historical data utilities; see the legacy inventory
docs/superpowers/, mockups/ Design history and prototypes, not application entry points

Runtime

The public site is Next.js, not the FastAPI root page. Browser /api/* requests pass through a Next.js route handler to FastAPI. Server-rendered pages call FastAPI directly using FASTAPI_URL, including its /api suffix.

PostgreSQL/PostGIS stores school data. Meltano/Singer extracts source data; dbt builds marts.*; FastAPI reads those tables. Typesense serves text search and autocomplete. Payload runs inside Next.js and owns a separate payload database schema and uploaded media.

There is no automatic CSV import or sample dataset on startup. A working school-data environment needs populated marts from the pipeline or an approved database snapshot. See development before choosing a setup.

Validation

cd nextjs-app
npm ci
npm run typecheck
npm test -- --runInBand

Backend checks, pipeline validation, runtime versions and E2E requirements are listed in DEVELOPMENT.md. No npm run lint script is currently defined.

Deployment

Work on a feature branch and open a PR. Merging to main builds images and deploys staging. Production promotion is a separate, human-triggered Gitea workflow. Use DEPLOY.md and the Portainer compose files as the operational references. The generic compose examples still reference :latest, which the current release workflow does not publish.

S
Description
No description provided
Readme
53 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 56.2%
Python 25.7%
CSS 13.2%
HTML 4.1%
JavaScript 0.6%
Other 0.2%