- Add nationalAvgRwm state fetched from /api/national-averages on mount
- Pass nationalAvgRwm to SchoolRow (vs-national delta now active in list view)
- Pass nationalAvgRwm to SchoolMap (prop accepted, threaded to Task 7)
- Redesign CompactSchoolItem: Ofsted badge + single headline metric + delta
- Fix stray backslash in SchoolRow.module.css .vsNationalFlat selector
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ofsted_framework field to School type
- Add OfstedListBadge interface and buildOfstedListBadge pure function to utils.ts
- Add fetchNationalAverages API function that calls GET /api/national-averages
- Add test suite for buildOfstedListBadge (all 6 new tests pass)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The school_info object was missing total_pupils entirely, so the frontend
always fell back to the KS4 exam cohort from yearly_data. Now selects
s.total_pupils (GIAS NumberOfPupils — full school roll) as gias_total_pupils
in the main query and exposes it as total_pupils on school_info.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
latestResults.total_pupils in KS4 data is the Year 11 exam cohort (~1/7
of the school), not the full school roll. Prefer schoolInfo.total_pupils
(sourced from GIAS NumberOfPupils) which is the statutory census headcount.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
meltano run does not support --select; the full tap-uk-ees run already
includes EESKs2NationalStream so no separate task is needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces computed means from our school dataset with the published DfE
national headline figures for the KS2 chart reference line.
- tap-uk-ees: new EESKs2NationalStream fetches the stable EES data-catalogue
CSV (one row per year, England national total, AllSchools filter)
- dbt staging: stg_ees_ks2_national normalises columns, casts to float,
filters to years >= 201617
- dbt mart: fact_ks2_national_averages — one row per year, official figures
- backend/models: Ks2NationalAverage SQLAlchemy model
- backend/app: /api/national-averages queries the mart for KS2 by_year;
secondary by_year stays computed (no DfE KS4 national dataset yet)
- DAG: extract_ks2_national task added to school_data_annual_ees,
runs in parallel with the main EES extract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the dashed reference line was a flat horizontal at the latest
year's national average across all historical data, implying the national
figure was constant. Now the backend returns per-year averages in `by_year`
and the chart maps each data year to its own national average, so the
reference line correctly reflects how the national picture changed over time
(including COVID recovery dip/recovery).
- backend: /api/national-averages now includes `by_year` list alongside
existing `year`/`primary`/`secondary` latest-year snapshot
- types: NationalAverages extended with `by_year: NationalAveragesYear[]`
- PerformanceChart: accepts `nationalByYear` prop; builds per-year series
aligned to school data years, falling back to scalar prop if absent
- SchoolDetailView + SecondarySchoolDetailView: pass `nationalAvg.by_year`
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bump school name to clamp(2rem,5vw,3.25rem) Playfair Display
- Add hero signal chips strip (framework-aware Ofsted + coral Oversubscribed)
- Add at-a-glance stats row: Att8 with delta vs national, Ofsted serif tile, first-choice rate
- Active section highlighting in sticky nav via IntersectionObserver
- Collapse OEIF Ofsted section to prose when all sub-grades match overall
- Pass nationalAtt8Avg reference line to PerformanceChart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TypeScript compile error: 'navItems' used before its declaration.
The IntersectionObserver useEffect referenced navItems in its dep array
but was placed above the navItems const declaration. Move it to just
after navItems is built.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses IntersectionObserver on each section element. Multiple thresholds
(0, 0.1, 0.25, 0.5, 0.75, 1.0) track the intersection ratio of every
section simultaneously; whichever has the highest visible ratio at any
moment becomes the active item. rootMargin offsets for the sticky nav
height so a section is only considered active once it's genuinely in
view beneath the bar.
Active link gets .sectionNavLinkActive — coral background + white text,
matching the phase tab active style used elsewhere in the product.
Observer is cleaned up on unmount.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Drop the auto-generated italic summary sentence from the hero — adds
little beyond what the chips and stats already convey.
- Oversubscribed hero chip: tone-gold → tone-coral so it reads as a
warning rather than a neutral highlight.
- Remove unused buildSchoolSummary import and .heroSummary CSS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
History chart
- Strip the redundant school-name title (already the page heading)
- Default to 2 visible lines: Reading, Writing & Maths expected % (teal,
bold) + Exceeding (gold, lighter); progress score lines hidden by
default, togglable via legend
- Add dashed national average reference line for RWM (primary) or
Attainment 8 (secondary) so the school's trajectory is always in
context
- Add trend summary chip above the chart computed from the data
("↓ Peaked at 90% (2016/17), currently 70%")
- Add COVID footnote when 2019/20 and 2020/21 data is absent
Ofsted section
- Collapse the four identical "Outstanding / Outstanding / Outstanding /
Outstanding" boxes into a single prose line when all sub-grades match
the overall verdict; show individual cards only when grades differ
SATs sub-metrics
- DeltaChip vs national average on Expected level row for Reading,
Writing and Maths (national averages already in the API response)
Admissions
- Fix label: "Year 3 places per year" → "Reception places per year" for
primary schools
Pupils & Inclusion
- DeltaChip + national avg hint on Eligible for pupil premium and
Pupils receiving SEN support (both keys present in /api/national-averages)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The three at-a-glance stats were misaligned — the serif "Outstanding" tile
sat noticeably above the "70%" and "64%" numerals because the serif variant
used a smaller font. That pushed its label row ("INSPECTED NOVEMBER 2023")
up too, breaking the horizontal rhythm across the row.
- Give .heroStatNumber and .heroStatNumberSerif a shared min-height tied
to the largest clamp value, plus display: flex; align-items: flex-end.
Content bottom-aligns inside the box, so every stat's label sits at the
same Y regardless of how tall the actual glyph is.
- Bump the serif variant up slightly (1.75rem → 2.25rem clamp) so it
feels closer in weight to the numerals while still leaving room for
longer words like "Requires Improvement".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The staging model was mapping EES column ``proportion_1stprefs_v_totaloffers``
straight onto ``first_preference_offer_pct``. That raw column is not a
percentage — it is a ratio of first-preference applications to total offers
(an oversubscription indicator, >1 means oversubscribed), so OLQH rendered
as "1%" when the true first-choice success rate is 27/42 = 64%.
The frontend display code is not at fault and is not patched here —
data-quality issues must be fixed at the source.
- stg_ees_admissions: compute ``first_preference_offer_pct`` as
``100 * number_1st_preference_offers / times_put_as_1st_preference`` —
of families who listed this school first, the % that received an offer
(0–100). Guard against divide-by-zero.
- stg_ees_admissions: expose the legitimate EES ratio as the new column
``oversubscription_ratio`` (1st-preference applications per place) for
future use, clearly named.
- fact_admissions, FactAdmissions model, data_loader: propagate the new
``oversubscription_ratio`` column.
- SchoolAdmissions type: document both columns inline.
- buildSchoolSummary: reword the oversubscription clause so it reads
sensibly across the whole 0–100 range (no more "just 64%").
- Hero chip subtitle: clearer phrasing "X% of first-choice applicants
offered a place".
Requires a dbt run of stg_ees_admissions and fact_admissions on deploy
so the new column materialises.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- heroChip: swap ``min-width: 180px; flex: 0 1 auto`` for
``flex: 0 0 240px`` so every chip in the strip is the same width
regardless of content. Title gets nowrap + ellipsis as insurance
against accidental overflow.
- heroChipTitle / Sub / Detail: align line-heights (1.3 / 1.4 / 1.4)
so an OEIF chip (title + sub) and a Report Card chip
(title + sub + detail) sit on the same vertical rhythm.
- heroSummary: drop the 64ch max-width — the sentence should read at
the natural hero width.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drop the "Above national average" chip — the DeltaChip under the 70% /
Attainment 8 number already carries the same signal, so the chip was
duplicative and added noise.
- At-a-glance stats: switch from grid(auto-fit) to flex with a fixed
3rem column gap so the numbers cluster at the start of the row rather
than spreading across the full width of the header card.
- Add to Compare button: larger padding, bumped font, soft shadow, and
self-align centre so it sits in balance with the bigger headline
rather than floating tiny in the top-right corner.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The API returns ``framework`` as the literal string "NULL" for older OEIF
inspections (it comes from the upstream ``event_type_grouping`` column),
not real null. The original render path checks ``=== 'ReportCard'`` and
correctly treats anything else as OEIF — but buildOfstedHeroChip inverted
that and treated anything not exactly equal to ``'OEIF'`` as Report Card,
so OLQH (inspected Nov 2023, Outstanding) was being labelled as a Report
Card school in the hero strip and the at-a-glance tile.
- Invert the helper: only branch into Report Card when framework is
explicitly ``'ReportCard'``; treat OEIF / null / "NULL" / anything else
as OEIF, and require ``overall_effectiveness`` to render the grade word.
- Replace the toneClass field (which reused .ofstedGrade{N} / .rcGrade{N}
badge classes and dragged in their backgrounds) with a clean tone enum
``teal | green | gold | coral | neutral``. The serif Ofsted heroStat
picked up the badge background and rendered as a green box around
"Report Card" — gone now.
- Hero chip backgrounds use color-mix() against the tone variable so all
five tones share one rule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Elevates the primary school detail hero from a flat report header into a
scannable editorial block. Parents can read the headline signal in seconds.
- A1: bump .schoolName to clamp(2rem, 5vw, 3.25rem) Playfair.
- A2: framework-aware signal chip strip via new buildOfstedHeroChip() helper.
Branches on ofsted.framework so Report Card schools never show a fake
overall grade — they get "Ofsted Report Card" + inspection date +
Safeguarding: Met/Not met. OEIF schools keep the grade word.
- A3: oversized Playfair stats — Reading, Writing & Maths % (primary) or
Attainment 8 (secondary) with inline DeltaChip vs national, Ofsted
verdict with tone colouring, and first-choice offer rate.
- B1: italic serif one-sentence summary via buildSchoolSummary() helper,
also framework-aware so Report Card schools are described by framework,
not a synthetic grade.
- C1: new DeltaChip component reused in the two headline KS2 metric cards
(rwm_expected_pct, rwm_high_pct).
All copy uses "Reading, Writing & Maths" in full. Secondary detail view
untouched in this slice.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The new phase inference can legitimately leave ~1100 independent schools with
null phase (no GIAS phase, no statutory ages, name gives no hint). That's a
known data quality gap, not a pipeline failure — the UI already handles null
by showing no pill. Downgrade the test to warn so it stays visible in dbt
output without blocking the DAG.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The case-insensitive "Not Applicable" fix caught schools where GIAS publishes
statutory ages, but some independent schools leave those blank too — they fall
through every branch and end up with null phase and no pill in the UI.
Add a third tier that infers phase from the school name
(Primary/Infant/Junior/Prep vs Secondary/High/Grammar/Senior/Upper) and also
normalise "Not Applicable" handling with trim() + "unknown"/"" exclusion, so
the final else branch can safely return null instead of the catch-all string.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- rankings: hide Type/Action columns on mobile so metric value stays visible;
ensure filter selects and table wrapper stay within viewport
- school detail: add min-width:0 / max-width:100% containment so internal
overflow-x wrappers actually clip rather than pushing the page wider;
explicit line-height on Ofsted grade badges to fix glyph clipping
- compare: sticky first column on the Detailed Comparison table so the Year
labels remain visible while horizontally scrolling school columns
- search: shorten placeholder to "School name or postcode" so it fits mobile
input width
- globals: overflow-x:clip safety net on .main wrapper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expand the abbreviation in metric names (backend schemas), the home page
sort dropdown, README/QA docs, and pipeline comments. Short_name fields
and the compact row/map-card labels remain abbreviated for space.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GIAS provides 'Not Applicable' (capital A) but the check used 'Not applicable',
so the case-sensitive != matched true and skipped the age-range inference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Independent schools have phase='Not applicable' in GIAS. Now infer
phase from statutory age range: <=11 → Primary, >=11 → Secondary,
spans both → All-through. Falls back to original value if no age data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add coloured left-border and phase label pill to visually differentiate
school phases (Primary, Secondary, All-through, Post-16, Nursery) in
search result lists. Colours are accessible (WCAG AA) and don't clash
with existing Ofsted/trend semantic colours.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply formatAcademicYear to all year displays in ComparisonChart, ComparisonView,
PerformanceChart, and RankingsView. Remove old vanilla JS frontend and CSV data
directory — both superseded by the Next.js app and Meltano pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Old DfE CSVs encode percentages as "57%" not "57". The safe_numeric
macro rejects non-numeric strings, so strip the suffix before emitting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The file hosting uses non-deterministic URLs, so replace legacy_ks2_base_url
+ legacy_ks2_years with a single legacy_ks2_urls object mapping year codes
to download URLs. Configure the 4 pre-COVID years in meltano.yml.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add LegacyKS2Stream to tap-uk-ees: downloads old DfE england_ks2final.csv
files from a configurable base URL, maps 318-column wide format to the
same schema as stg_ees_ks2 output
- Add stg_legacy_ks2.sql staging model with safe_numeric casts
- Add legacy_ks2 source to _stg_sources.yml
- Update int_ks2_with_lineage.sql to union EES + legacy data
- Configurable via legacy_ks2_base_url and legacy_ks2_years tap settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Older census CSVs use 'URN' (uppercase) while the stream expects 'urn'.
Normalise the column name before filtering and emitting records.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Older census (and other) files don't include a time_period column.
Derive it from the release slug (e.g. '2022-23' → '202223') and inject
it into records so the required Singer schema field is always present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add get_all_release_ids() to paginate /publications/{slug}/releases and
iterate over every release in get_records(). Add latest_only config flag
(default false) to restore single-release behaviour for daily runs.
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>
The frontend expects location_info with coordinates array, but backend was
returning search_location with lat/lng keys. This fix enables the map toggle
to appear for location-based searches.
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>
Backend returns filters directly at top level, not wrapped in 'filters' property.
Update FiltersResponse type and page components to match actual API response.
Fixes empty dropdowns for school types and local authorities.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Copy complete original styles.css (1900+ lines) to globals.css
- Add Google Fonts (DM Sans and Playfair Display) via next/font
- Use CSS variables for fonts
- Restore warm color palette (#faf7f2 bg, coral/teal accents)
- Restore noise overlay texture
- Restore all original animations and transitions
- Match original card styles, buttons, modals
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add try-catch blocks to all page components
- Provide empty data fallbacks when API calls fail
- Use optional chaining for safer property access
- Log errors for debugging
Fixes 'Cannot read properties of undefined' errors.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Migrate from vanilla JavaScript SPA to Next.js 16 with App Router
- Add server-side rendering for all pages (Home, Compare, Rankings)
- Create individual school pages with dynamic routing (/school/[urn])
- Implement Chart.js and Leaflet map integrations
- Add comprehensive SEO with sitemap, robots.txt, and JSON-LD
- Set up Docker multi-service architecture (PostgreSQL, FastAPI, Next.js)
- Update CI/CD pipeline to build both backend and frontend images
- Fix Dockerfile to include devDependencies for TypeScript compilation
- Add Jest testing configuration
- Implement performance optimizations (code splitting, caching)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
On startup, the app now checks if the database schema version matches
the code. If there's a mismatch or no version exists, it automatically
runs a full data migration before starting.
- Add backend/version.py with SCHEMA_VERSION constant
- Add backend/migration.py with extracted migration logic
- Add SchemaVersion model to track DB version
- Add version check functions to database.py
- Update app.py lifespan to use check_and_migrate_if_needed()
- Simplify migrate_csv_to_db.py to use shared logic
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Display test absence percentages (reading, maths, GPS, writing, science)
in a new section in the school modal. Requires database re-import.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add orange Compare button alongside Details button
- Toggle to Remove when school is in comparison
- Stack buttons vertically with consistent sizing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move location-info banner above map view as full-width bar
- Set fixed height for map view container with equal map/list heights
- Add z-index to map to prevent overlap with sticky header
- Update mobile responsive styles for consistent heights
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add compact school list items on right side of map view
- Show school name, distance, type, authority, RWM %, and pupils
- Click list item to center map and highlight marker
- Click map marker to scroll and highlight list item
- Add "Details" button to open school modal from list
- Store markers by URN for map centering functionality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When searching by location, users can now toggle between list view
(school cards grid) and a split map view showing:
- Interactive map on left with all school markers
- Scrollable school list on right
- Blue marker for search location, default markers for schools
- Clicking a marker highlights and scrolls to the corresponding card
Mobile responsive with stacked layout on smaller screens.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Progress scores aren't available for 2023-24 and 2024-25 due to KS1
SATs being cancelled in 2020-2021. Now the modal finds and displays
progress scores from the most recent year they're available, with
the correct year shown in the header.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Display the data year for Progress Scores and School Context sections
in the school details modal, matching the existing KS2 Results format.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace footer note with a contact form that emails contact@schoolcompare.co.uk
via FormSubmit.co. Keep only the data source attribution. Update CSP to allow
form submissions to FormSubmit.co and add responsive styling for the form.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make charts taller on mobile with better aspect ratios (0.9 for <480px, 1.1 for <768px)
- Shorten chart title and dataset labels on mobile
- Add responsive font sizes for legend, title, and axis ticks
- Add mobile-specific styling for chart container, stats grid, and modal
- Add extra-small screen breakpoint (480px) for very narrow devices
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>