Replaces the hand-drawn SVG landscape with the illustration supplied by the project owner. Master is assets/hero-source.png; everything under public/brand/hero-* comes from scripts/build-hero-images.js and should never be hand-edited. LAYOUT The artwork is composed as a full-bleed hero: it reserves an empty cream area down its left side for the headline. The old hero was a two-column grid with art in the right 0.88fr, which would have cropped that reserved area off and shrunk the scene into a thumbnail — the one thing the picture is built not to be. So the panel is now a single layered block: artwork behind, copy on top, and below the one-column breakpoint the artwork leaves the background and becomes a band under the search. Overlaying does not survive down to phone widths — the panel gets too narrow for the copy to stay inside the cream, so the scrim would have to cover nearly the whole image and you would be left with a tinted rectangle. Hence the switch at 860px rather than a single treatment stretched across every width. CONTRAST The copy sits on a gradient of --hero-ground, a new token sampled from the artwork's own cream (#FDF9F3) rather than from Sand. Sand is eight to thirteen points darker per channel, which leaves a visible seam straight down the hero. The scrim exists because the artwork is a fixed image on a fluid panel: past some width the headline would otherwise land on hillside green. Measured on rendered pixels, sampling background up to 120px beyond the right edge of each line, so a longer line still has margin: light title 14.36:1 body 6.52:1 dark title 11.01:1 body 6.44:1 The worst light case is hillside green showing through the scrim at 6.52:1. TWO CROPS The slot is two shapes: roughly 2.1:1–2.7:1 behind the desktop panel, and 2.6:1–4.9:1 as the band. A single file under object-fit: cover centre-crops, and at the band's extreme that slices a strip through the scene and loses the schoolhouse — exactly how the drawn hero failed on phones. <picture> switches crop, not just resolution: the band file is pre-cropped around the school and is already near 2.6:1, with the subject at ~65% across so squeezing toward 4.9:1 crops the empty sides instead. WEIGHT 1.6 MB PNG in, AVIF and WebP out; a browser fetches exactly one file — about 23–33 kB on desktop, 9–18 kB on a phone. The JPEG is only the <img> fallback. `sizes` describes the real panel box (max-width 1400 minus padding) rather than 100vw, which over-requested a candidate at every width on the LCP element. DARK THEME A raster cannot be re-graded token by token the way the drawing was, but it would reproduce the same failure — a bright illustration is the brightest object on a near-black page. It is dimmed in CSS to read as dusk, and the scrim fades it into the dark panel rather than into cream. TESTS The two illustration journeys are replaced by three, each covering a failure that still renders a valid-looking page: the artwork actually loading (naturalWidth, not src), the crop switching at the breakpoint, a modern format winning negotiation, and the dark theme dimming it. public/brand/CREDITS.md records provenance for every asset in that folder. The hero's licence line is marked unconfirmed — that one needs the owner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
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:
-
Download Key Stage 2 data for the years you want (2019-2024)
- Select "Key Stage 2" as the data type
-
Place the CSV files in the
data/folder -
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