TudorandClaude Opus 5 45a3e7fb8f
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m10s
fix(design): correct the font assertion and make the Leaflet overrides win
Two follow-ups from watching the staging E2E run and re-auditing the live site.

The typeface gate failed, but the site was right. Staging now serves
"Schibsted Grotesk", "Schibsted Grotesk Fallback", …, -apple-system, sans-serif
— exactly what it should. My assertion was /Times|serif$/, and `serif$` matches
the tail of `sans-serif`, so a healthy stack could never pass. It now checks
the FIRST family in the stack (the only one actually requested) and matches
Times as a whole word. Also asserts running prose resolves to Literata, which
the earlier version never covered.

The Leaflet overrides from the previous fix never took effect. leaflet.css is
imported from a client component, so its chunk loads after globals.css; at
equal specificity (both bare `.leaflet-container`) the later sheet wins.
Verified on the live page: my rule sat at stylesheet index 0, Leaflet's at
index 3, and the attribution link was still #0078A8. Prefixing the overrides
with `html` takes them to 0,1,1, which beats a bare class regardless of load
order — injecting that on the live page turned the background to
rgb(233,232,226) and the link to rgb(88,74,155).

The palette gate would have caught this, except it skipped everything inside
.leaflet-container to avoid flagging OSM tile imagery — which also skipped
Leaflet's own chrome. Narrowed the exemption to .leaflet-tile-pane so the map
controls are now covered.

Also dropped the duplicated family name from the font tokens: next/font
already expands the variable to the family plus its metric-matched fallback,
so the stack was naming Schibsted Grotesk twice. It looked like a safety net
but wasn't — an unresolvable var() with no fallback invalidates the whole
declaration, so the literal after it never gets a turn.

Verified against live staging: body and headings resolve to Schibsted Grotesk,
prose to Literata, the search input is --text-primary rather than pure black,
and the higher-specificity Leaflet rules win when injected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 14:04:36 +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
2026-01-07 16:20:49 +00:00
2026-04-01 15:05:21 +01:00

Primary School Compass 🧒📚

A modern web application for comparing primary school (KS2) performance data in Wandsworth and Merton over the last 5 years. Built with FastAPI and vanilla JavaScript with Chart.js visualizations.

Python FastAPI License

Features

  • 📊 Interactive Charts - Visualize KS2 performance trends over time
  • 🔍 Smart Search - Find primary schools by name in Wandsworth & Merton
  • ⚖️ Side-by-Side Comparison - Compare up to 5 schools simultaneously
  • 🏆 Rankings - View top-performing primary schools by various KS2 metrics
  • 📱 Responsive Design - Works beautifully on desktop and mobile

Key Metrics (KS2)

The application tracks these Key Stage 2 performance indicators:

Metric Description
Reading Progress Progress in reading from KS1 to KS2
Writing Progress Progress in writing from KS1 to KS2
Maths Progress Progress in maths from KS1 to KS2
Reading Expected % Percentage meeting expected standard in reading
Writing Expected % Percentage meeting expected standard in writing
Maths Expected % Percentage meeting expected standard in maths
Reading, Writing & Maths Combined % Percentage meeting expected standard in all three subjects

Quick Start

1. Clone and Setup

cd school_results

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

2. Run the Application

# Start the server
python -m uvicorn backend.app:app --reload --port 8000

Then open http://localhost:8000 in your browser.

The app will run with sample data by default, showing 110 primary schools (66 in Wandsworth, 44 in Merton) with 5 years of KS2 performance data.

3. (Optional) Use Real Data

To use real UK school performance data:

  1. Visit Compare School Performance - Download Data

  2. Download Key Stage 2 data for the years you want (2019-2024)

    • Select "Key Stage 2" as the data type
  3. Place the CSV files in the data/ folder

  4. Restart the server - it will automatically load and filter to Wandsworth & Merton schools

Note: The app only displays schools in Wandsworth and Merton. Data from other areas will be filtered out.

See the helper script for more details:

python scripts/download_data.py

Project Structure

school_results/
├── backend/
│   └── app.py           # FastAPI application with all API endpoints
├── frontend/
│   ├── index.html       # Main HTML page
│   ├── styles.css       # Styling (warm, editorial design)
│   └── app.js           # Frontend JavaScript
├── data/
│   └── .gitkeep         # Place CSV data files here
├── scripts/
│   └── download_data.py # Helper for downloading/processing data
├── requirements.txt     # Python dependencies
└── README.md

API Endpoints

Endpoint Description
GET /api/schools List schools with optional search/filter
GET /api/schools/{urn} Get detailed data for a specific school
GET /api/compare?urns=... Compare multiple schools
GET /api/rankings Get school rankings by metric
GET /api/filters Get available filter options
GET /api/metrics Get available performance metrics

Example API Usage

# Search for schools
curl "http://localhost:8000/api/schools?search=academy"

# Get school details
curl "http://localhost:8000/api/schools/100001"

# Compare schools
curl "http://localhost:8000/api/compare?urns=100001,100002,100003"

# Get rankings
curl "http://localhost:8000/api/rankings?metric=rwm_expected_pct&year=2024"

Data Format

If using your own CSV data, ensure it includes these columns (or similar):

Column Type Description
URN Integer Unique Reference Number
SCHNAME String School name
LA String Local Authority (must be Wandsworth or Merton)
READPROG Float Reading progress score
WRITPROG Float Writing progress score
MATPROG Float Maths progress score
PTRWM_EXP Float % meeting expected standard in reading, writing & maths
PTREAD_EXP Float % meeting expected standard in reading
PTWRIT_EXP Float % meeting expected standard in writing
PTMAT_EXP Float % meeting expected standard in maths

The application normalizes column names automatically and filters to only show Wandsworth and Merton schools.

Technology Stack

  • Backend: FastAPI (Python) - High-performance async API framework
  • Frontend: Vanilla JavaScript with Chart.js
  • Styling: Custom CSS with CSS variables for theming
  • Data: Pandas for CSV processing

Design Philosophy

The UI features a warm, editorial design inspired by quality publications:

  • Typography: DM Sans for body text, Playfair Display for headings
  • Color Palette: Warm cream background with coral and teal accents
  • Interactions: Smooth animations and hover effects
  • Charts: Clean, readable data visualizations

Development

# Run with auto-reload
python -m uvicorn backend.app:app --reload --port 8000

# Or run directly
python backend/app.py

Coverage

This application is specifically designed for:

  • School Phase: Primary schools only (Key Stage 2)
  • Geographic Area: Wandsworth and Merton (London boroughs)
  • Time Period: Last 5 years of data (2020-2024)

Note: 2021 data shows as unavailable because SATs were cancelled due to COVID-19.

Data Source

Data is sourced from the UK Government's Compare School Performance service, which provides official school performance data for England.

Important: When using real data, please comply with the terms of use and data protection regulations.

Scheduled Jobs

Geocoding Schools (Cron Job)

School postcodes are geocoded by a scheduled job, not on-demand. This improves performance and reduces API calls.

Setup the cron job (runs weekly on Sunday at 2am):

# Edit crontab
crontab -e

# Add this line (adjust paths as needed):
0 2 * * 0 cd /path/to/school_compare && /path/to/venv/bin/python scripts/geocode_schools.py >> /var/log/geocode_schools.log 2>&1

Manual run:

# Geocode only schools missing coordinates
python scripts/geocode_schools.py

# Force re-geocode all schools
python scripts/geocode_schools.py --force

License

MIT License - feel free to use this project for educational purposes.


Built with ❤️ for Wandsworth & Merton families

S
Description
No description provided
Readme
43 MiB
Languages
TypeScript 52.5%
Python 27.5%
CSS 16.5%
HTML 2.8%
JavaScript 0.4%
Other 0.3%