formatAcademicYear now handles both 4-digit (2023→2023/24) and 6-digit
EES codes (202526→2025/26). Applied to all year displays: SATs, phonics,
admissions, finances, and the yearly results table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove build-integrator and build-kestra-init jobs from Gitea Actions
- Update trigger-deployment needs to only depend on remaining three builds
- Fix school website href to prepend https:// when protocol is missing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ofsted replaced single overall grades with Report Cards from Nov 2025.
Both systems are retained during the transition period.
- DB: new framework + 9 RC columns on ofsted_inspections (schema v4)
- Integrator: auto-detect OEIF vs Report Card from CSV column headers;
parse 5-level RC grades and safeguarding met/not-met
- API: expose all new fields in the ofsted response dict
- Frontend: branch on framework='ReportCard' to show safeguarding badge
+ 8-category grid; fall back to legacy OEIF layout otherwise;
always show inspection date in both layouts
- CSS: rcGrade1–5 and safeguardingMet/NotMet classes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove standalone back button div (looked out of place)
- Back button now lives in the sticky section nav bar, styled as a
bordered pill with coral accent — consistent with page design
- Fix sticky nav top offset from 0 to 3rem so it sticks below the
site-wide header instead of sliding behind it
- Increase scroll-margin-top on cards to 6rem to account for both
site header and section nav height
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UX audit round 2:
- Remove Summary Strip (duplicated Ofsted grade + parent happy/safe/recommend)
- Fold "% would recommend" into Ofsted section header
- Merge SATs Results + Subject Breakdown into one section
- Merge Results Over Time chart + Year-by-Year table into one section
- Add sticky section nav with dynamic pills based on available data
- Unify colour system: replace ad-hoc pill colours with semantic status classes
- Guard Pupils & Inclusion so it only renders with actual data
- Add year to Admissions section title
- Fix progress score 0.0 colour (was neutral gap at ±0.1, now at 0)
- Remove unused .metricTrend CSS class
Page reduced from 16 to 13 sections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The EES statistics API only exposes ~13 publications; admissions data is not
among them. Switch to the EES content API (content.explore-education-statistics.
service.gov.uk) which covers all publications.
- ees.py: add get_content_release_id() and download_release_zip_csv() that
fetch the release ZIP and extract a named CSV member from it
- admissions.py: use corrected slug (primary-and-secondary-school-applications-
and-offers), correct column names from actual CSV (school_urn,
total_number_places_offered, times_put_as_1st_preference, etc.), derive
first_preference_offers_pct from offer/application ratio, filter to primary
schools only, keep most recent year per URN
Also includes SchoolDetailView UX redesign: parent-first section ordering,
plain-English labels, national average benchmarks, progress score colour
coding, expanded header, quick summary strip, and CSS consolidation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a full data integration pipeline for enriching school profiles with
supplementary data from Ofsted, GIAS, EES, IDACI, and FBIT.
Backend:
- Bump SCHEMA_VERSION to 3; add 8 new DB tables (ofsted_inspections,
ofsted_parent_view, school_census, admissions, sen_detail, phonics,
school_deprivation, school_finance) plus GIAS columns on schools
- Expose all supplementary data via GET /api/schools/{urn}
- Enrich school list responses with ofsted_grade + ofsted_date
Integrator (new service):
- FastAPI HTTP microservice; Kestra calls POST /run/{source}
- 9 source modules: ofsted, gias, parent_view, census, admissions,
sen_detail, phonics, idaci, finance
- 9 Kestra flow YAMLs with scheduled triggers and 3× retry
Frontend:
- SchoolRow: colour-coded Ofsted badge (Outstanding/Good/RI/Inadequate)
- SchoolDetailView: 7 new sections — Ofsted sub-judgements, Parent View
survey bars, Admissions, Pupils & Inclusion / SEN, Phonics, Deprivation
Context, Finances
- types.ts: 8 new interfaces + extended School/SchoolDetailsResponse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Line 1: school name (bold) + school type (muted gray)
Line 2: R,W&M % · Progress score + band · Pupil count
Line 3: local authority · distance (location searches)
Actions (View / Add) are vertically centred on the right across all lines.
Progress uses reading score, falling back to writing then maths. Removed
the old nameScore grouping and separate meta/progress rows in favour of
the cleaner 3-line structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set explicit height:2rem, line-height:1, font-family:inherit on all children
of button group containers. Browsers apply different default line-height and
font-family to <button> vs <a>, causing height differences that persist even
with identical padding and display:inline-flex.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
<a> tags are display:inline by default and don't respect vertical padding,
while <button> is inline-block. Mixed anchor/button pairs (View/Add) rendered
at different heights despite identical padding. Apply display:inline-flex +
align-items:center to every button-styled element across SchoolRow, RankingsView,
and SchoolCard. Add border:1px solid transparent to borderless buttons so total
box size matches bordered siblings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Group school name and score together on the left using a nameScore flex
container, so the percentage sits close to the name rather than pushed to the
far right. Action buttons get slightly more padding on desktop (0.4375rem v
0.3125rem). On mobile the scoreLabel is now visible inline instead of hidden,
so the percentage reads as R,W&M not a bare number.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FilterBar was sending radius in km (e.g. 1.6) but the backend expects miles,
causing the "Showing schools within X miles" banner to display the wrong value.
Change option values to miles (0.5, 1, 3, 5, 10) and default from 1.6 to 1.
school.distance from the API is already in miles (backend haversine uses
R=3959). SchoolRow was dividing by 1609.34 giving 0.0 mi; CompactSchoolItem
was dividing by 1.60934. Both now display school.distance directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the school card grid with a scannable row list that shows 3x more
results per screen. Each row shows: school name + R,W&M % with trend,
area/type meta, and reading/writing/maths progress scores with plain-English
band labels (e.g. "above average") instead of raw numbers.
Add lib/metrics.ts as a single source of truth for plain-language metric
explanations and the progressBand() helper. Map view toggle is unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses 28 issues identified in UX audit (P0–P3 severity):
P0 — Critical:
- Fix compare URL sharing: seed ComparisonContext from SSR initialData
when localStorage is empty, making /compare?urns=... links shareable
- Remove permanently broken "Avg. Scaled Score" column from school
detail historical data table
P1 — High priority:
- Add radius selector (0.5–10 mi) to postcode search in FilterBar
- Make Add to Compare a toggle (remove) on SchoolCards
- Hide hero title/description once a search is active
- Show school count + quick-search prompts on empty landing page
- Compare empty state opens in-page school search modal directly
- Remove URN from school detail header (irrelevant to end users)
- Move map above performance chart in school detail page
- Add ← Back navigation to school detail page
- Add sort controls to search results (RWM%, distance, A–Z)
- Show metric descriptions below metric selector
- Expand ComparisonToast to list school names with per-school remove
- Add progress score explainer (0 = national average) throughout
P2 — Medium:
- Remove console.log statements from ComparisonView
- Colour-code comparison school cards to match chart line colours
- Replace plain loading text with LoadingSkeleton in ComparisonView
- Rankings empty state uses shared EmptyState component
- Rankings year filter shows actual year e.g. "2023 (Latest)"
- Rankings subtitle shows top-N count
- Add View link alongside Add button in rankings table
- Remove placeholder Privacy Policy / Terms links from footer
- Replace untappable 10px info icons with visible metric hint text
- Show active filter chips in search results header
P3 — Polish:
- Remove redundant "Home" nav link (logo already links home)
- Add / and Ctrl+K keyboard shortcut to focus search input
- Add Share button to compare page (copies URL to clipboard)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hide empty state placeholder on initial load
- Add prominent hero mode to FilterBar when no search is active
- Fix SchoolCard test TypeScript and assertion errors
- Redesign landing page with unified Omnibox search
- Add ComparisonToast for better comparison flow visibility
- Add visual 'Added' state to SchoolCard
- Add info tooltips to educational metrics
- Optimize mobile map view with Bottom Sheet
- Standardize distance display to miles
- Reduced section padding from 2rem to 1rem-1.25rem
- Reduced margin-bottom from 2rem to 1rem
- Smaller chart height (400px → 280px) and map height (400px → 250px)
- Detailed metrics now in 3-column grid layout
- Condensed font sizes and spacing throughout
- Applied design system colors consistently
- Shortened metric labels (e.g., "Expected Standard" → "Expected")
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Increased header z-index from 100 to 1000 to ensure it stays above
Leaflet map elements (which typically use z-index 400-600).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed 'equity' to 'disadvantaged' and 'trends' to '3yr' to match
the MetricDefinition category type.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added optgroup elements to organize metrics into logical categories:
- Expected Standard
- Higher Standard
- Progress Scores
- Average Scores
- Gender Performance
- Equity (Disadvantaged)
- School Context
- 3-Year Trends
Also added CSS styling for optgroup labels to match the design system.
Note: School names in rankings are already clickable links to school details.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implemented split-view map layout for postcode searches:
- List/Map toggle appears when doing location search
- Map view shows interactive map with school markers on left
- Compact school list on right with distance badges, stats, actions
- Mobile responsive: stacks vertically with map on top
- Updated School type to include distance and total_pupils fields
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Merged the hero title/description into FilterBar component to save
vertical space. The combined block has a gradient background flowing
from cream to white with the search controls below the header.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reduced padding and title size to eliminate empty feeling, added
decorative coral underline bar for visual interest, and subtle
fade-in animation on page load.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add flex layout to schoolCard for proper content distribution
- Use flex: 1 on schoolMeta to fill available space
- Change margin-top to auto on latestValue to push to bottom
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move comparison state from hook to shared context provider
- All components now share the same state instance
- Badge count updates immediately when schools are added/removed
- Add key prop to badge to re-trigger animation on count change
- Add storage event listener for cross-tab synchronization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove padding from Modal's .content wrapper (let children control)
- Remove conflicting width/max-width from SchoolSearchModal
- Modal size classes now properly control the width
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The rewrite destination was using FASTAPI_URL directly, which
replaced the entire destination including the :path* parameter.
This caused /api/compare to rewrite to just http://backend:80/api
instead of http://backend:80/api/compare.
Now properly constructs: ${FASTAPI_URL}/:path*
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Next.js rewrites are evaluated at build time, not runtime.
Without FASTAPI_URL set during build, the rewrite destination
defaults to localhost:8000 which fails in Docker.
- Add FASTAPI_URL build arg to nextjs-app/Dockerfile
- Pass build arg in docker-compose.yml
- Pass build arg in Gitea Actions workflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ComparisonView now uses fetchComparison from lib/api
- SchoolSearchModal now uses fetchSchools from lib/api
- Fixed bug in fetcher function that incorrectly sliced URLs
(url.slice(4) was removing '/com' from '/compare')
This fixes the malformed URL issue where '/api/compare' became '/apipare'.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added better UX and debugging for the comparison screen:
1. Loading state for chart section
- Shows "Loading comparison data..." when schools are selected
but data hasn't loaded yet
- Provides visual feedback to users
2. Enhanced debugging logs
- Log URNs being fetched
- Log API response status
- Log received comparison data
- Better error handling with null state on failure
3. Improved conditional rendering
- Chart shows when data is available
- Loading message shows when waiting for data
- Nothing shows when no schools selected
This helps diagnose any API issues and provides better user feedback
during data loading.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The compare screen and school search modal were not working because
they were fetching from '/api' directly instead of using the
NEXT_PUBLIC_API_URL environment variable that points to the backend.
Fixed client-side fetch calls in:
- ComparisonView: Fetch comparison data with correct API URL
- SchoolSearchModal: Search schools with correct API URL
This ensures client-side requests go to the FastAPI backend at
the configured URL (e.g., http://localhost:8000/api) rather than
trying to hit non-existent Next.js API routes.
Fixes comparison screen showing no data when schools are selected.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1. Added original favicon
- Copied favicon.svg from original frontend
- Added favicon reference to layout metadata
- Professional icon with brand colors
2. Updated logo in navigation
- Replaced emoji with proper SVG logo from original design
- Uses circular target design with crosshairs
- Matches brand identity with coral accent color
3. Removed emoji icons throughout app for professional look
- Removed 📍 (location pin) from school locations
- Removed 🏫 (school building) from school types
- Removed 🔢 from URN labels and section headings
- Kept meaningful symbols (✓, +) in buttons only
- Updated map popup button color to brand coral (#e07256)
Components updated:
- Navigation: Professional SVG logo
- HomeView: Clean location banner
- SchoolDetailView: No decorative emojis in metadata
- ComparisonView: Text-only school information
- SchoolSearchModal: Clean school listings
- LeafletMapInner: Professional map popups
Result: More polished, professional appearance suitable for
educational data platform
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The "Add to Compare" button on individual school pages was invisible
because the CSS variables --primary, --primary-dark, --success, and
--border-light were not defined in globals.css.
Added these variables mapped to the existing color palette:
- --primary: coral accent (#e07256)
- --primary-dark: dark coral (#c45a3f)
- --success: teal accent (#2d7d7d)
- --border-light: border color (#e5dfd5)
The button was already in the DOM but had no background color.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1. Show empty state by default on home page
- Don't fetch or display schools until user searches
- Show helpful message prompting users to search
- Only fetch schools when search params are present
2. Change distance search to miles
- Display 0.5, 1, and 2 mile options instead of km
- Convert miles to km when sending to API (backend expects km)
- Convert km back to miles for display in location banner
- Maintains backend compatibility while improving UX
3. Fix metric labels in rankings dropdown
- Backend returns 'name' and 'type' fields
- Frontend expects 'label' and 'format' fields
- Added transformation in fetchMetrics to map fields
- Dropdown now shows proper labels like "RWM Combined %"
instead of technical codes like "rwm_expected_pct"
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
In Next.js 15, searchParams is a Promise that must be awaited before
accessing its properties. The home page was directly accessing
searchParams.search, searchParams.local_authority, etc., which resulted
in all parameters being undefined. This caused all API calls to return
all schools regardless of search/filter parameters.
This fix brings the home page in line with the compare and rankings
pages, which already correctly await searchParams.
Fixes search, filter, and pagination functionality on the home page.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend returns metrics as an array, not an object.
- Update MetricsResponse type to use MetricDefinition[] instead of Record
- Remove Object.values() conversion in compare and rankings pages
- Fix useMetrics hook to handle array instead of object
- Fix getMetric to use array.find() instead of object indexing
Fixes empty metric dropdown on compare page.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>