Commit Graph
100 Commits
Author SHA1 Message Date
Tudor SitaruandClaude Sonnet 4.6 2e3456b21b fix(compare): auto-phase tab now also syncs metric to match detected phase
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 18s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 55s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
The useEffect that auto-selects the Primary/Secondary tab was calling
setComparePhase() without updating selectedMetric. When a URL carries a
primary metric (e.g. metric=rwm_expected_pct) but the shortlisted schools
are secondary, the tab would switch to Secondary while the metric stayed
at rwm_expected_pct — which is null for all secondary schools, causing
every card to show "–" and requiring a manual tab toggle to fix.

Fix: after determining the phase from school data, check whether the
current metric belongs to that phase's category list. If not, reset to
the phase default (attainment_8_score for secondary, rwm_expected_pct
for primary). A metric that already fits the phase (e.g. the URL already
carried attainment_8_score) is preserved unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 08:57:02 +01:00
Tudor SitaruandClaude Sonnet 4.6 f05bbba613 perf: resolve all P1–P5 performance issues from code review
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 21s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
P1 (backend/data_loader.py): Add load_latest_school_data() which pre-computes
the one-row-per-school latest-year snapshot (groupby, prev-year trend merge)
once at startup instead of on every /api/schools request. get_schools route
now starts from the cached snapshot rather than rebuilding it.

S3 (backend/app.py): Wrap synchronous geocode_single_postcode() call in
asyncio.to_thread() so postcode lookups no longer block the uvicorn event
loop. Admin reload endpoint also uses to_thread for both cache primes.

P2 (nextjs-app/components/HomeView.tsx): Add mapParamsRef guard so switching
back to map view does not re-fetch 500 schools when search params haven't
changed. Reset ref on new searches so fresh data is always fetched.

P3 (nextjs-app/lib/chartSetup.ts): Extract Chart.js registration into a
shared side-effect module. ComparisonChart and PerformanceChart now import
it instead of each calling ChartJS.register() independently.

P4 (backend/database.py): Remove unnecessary db.commit() from the read-only
get_db_session() context manager — saves a DB round-trip on every request.

P5 (backend/database.py): Add pool_recycle=1800 to SQLAlchemy engine to
prevent stale TCP connections from accumulating in long-running processes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 22:45:46 +01:00
Tudor SitaruandClaude Sonnet 4.6 f6b9d650f8 feat(admissions): add admissions guide page and homepage countdown strip
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 14s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- New /admissions route with AdmissionsView client component
- Live countdowns (days until) to Primary/Secondary deadlines and Offer Days
- Step-by-step timelines for both tracks with highlighted milestone rows
- Tips section covering equal preference rule, late applications, waiting lists
- Homepage countdown strip (4 cards) between discovery chips and how-it-works
- Admissions nav link and footer link added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 17:00:21 +01:00
Tudor SitaruandClaude Opus 4.6 3327728df0 feat(secondary): apply primary visual design to secondary school detail
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 2m17s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 55s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
- GCSE top metrics (Att8, P8, Eng+Maths 4+/5+) promoted to heroStatCard
  teal-tinted cards with Playfair serif values and DeltaChip vs national
- Attainment 8 visual bar: 0–80 scale with coral national-average marker
  and pill label, mirrors the SatsChart concept for a score metric
- Progress 8 number line: −3 to +3 axis showing CI band, zero baseline,
  and a teal/coral dot for the school's score (hidden when P8 suspended)
- SEN section upgraded from plain metricCard to heroStatCard grid
- History table moved into a details/summary accordion (collapsed by
  default); PerformanceChart now lives at the top of the History section
  always visible above the disclosure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 09:21:21 +01:00
Tudor SitaruandClaude Opus 4.6 ac2d64caaf feat(home): implement redesigned homepage
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- Hero: Playfair heading with coral italic accent, teal eyebrow pill,
  richer sub-copy describing both primary and secondary coverage
- Discovery: geolocation "Schools near me" button (reverse-geocodes via
  postcodes.io → /?postcode=…&radius=1), plus Start exploring chips
  linking to /rankings and /compare
- How it works: 3-card grid showing miniature real-UI previews for
  Performance (primary SATs cascade + secondary Att8 bar), Ofsted
  inspection card, and side-by-side Compare table
- Editorial: text column + factbox (totalSchools, LA count, coverage
  dates) rendered inside a white card below the how-it-works section
- Footer: expanded to 3 columns (brand blurb, Product, Resources);
  links updated to / /rankings /compare and real gov.uk/ofsted URLs
- All new sections visible only on landing (no search active)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 21:02:18 +01:00
Tudor SitaruandClaude Opus 4.6 bfff24fa5f style(detail): apply hero card style to Pupils & Inclusion
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Renames the RWM-specific .rwmHero* classes to generic .heroStat*
and reuses them on the three Pupils & Inclusion headline cards
(disadvantaged, EAL, SEN support). Grid switches to auto-fit to
accommodate both 2- and 3-card layouts. SEN primary-need breakdown
stays on the dense .metricCard style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 14:31:31 +01:00
Tudor SitaruandClaude Opus 4.6 34cd8ad26e style(sats): restyle RWM hero cards to match approved mockup
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 48s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Top two combined cards now use a teal-tinted background with a
2.1rem Playfair serif teal value and a compact uppercase label —
consistent with the hero treatment shown in the design mockup.
Scoped via new .rwmHero* classes so other metric cards on the page
keep their existing style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 14:01:45 +01:00
Tudor SitaruandClaude Opus 4.6 a27b9abd9f style(sats): tighten cascade bars to match approved mockup
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 52s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Bars drop from 22px to 14px with softer 4px radius, percentages render
in Playfair serif to match the rest of the detail page typography,
national-average line opacity bumped from 25% to 35% for legibility.
Subject name and ruler proportions also aligned with the mockup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 13:52:00 +01:00
Tudor SitaruandClaude Opus 4.6 045dbc65b7 feat(sats): add "why is combined lower?" bridge between hero and cascade
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 2m20s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Explains the intersection semantics of RWM combined — a pupil is only
counted if they met the bar in all three subjects — with a math line
showing the per-subject percentages collapsing to the combined figure.
Only renders when all four values are present; national-average pill
markers on the cascade are untouched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 13:30:11 +01:00
Tudor SitaruandClaude Opus 4.6 35deedcc16 feat(admissions): promote verdict to headline, move above Q&A
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
The Oversubscribed badge + "Demand exceeds capacity" text lived at
the bottom of the tile as an afterthought. It's the headline finding —
a parent should read it first, then let the Q&A supply the detail.

Replace the footer badge with a Playfair Display sentence directly
under the section title: "This school is oversubscribed." (state word
coloured coral for oversubscribed, teal for undersubscribed). The
"Demand exceeds capacity" / "Supply meets demand" line sits below as
quiet muted text — present for context but not competing with the
headline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 11:03:57 +01:00
Tudor SitaruandClaude Opus 4.6 5abab067a1 feat(admissions): replace bar + metric cards with Q&A tile
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m4s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
The "How Hard to Get Into This School" tile mixed a progress bar
(places vs first-choice) with three text metric cards, making the
data feel fragmented and hiding the real narrative. The progress bar
also broke visually when undersubscribed and didn't scale to different
school sizes.

Replace with a typographic Q&A list that answers the questions
parents actually ask — "How many places were offered?", "How many
families wanted this school first?", "How many got their first
choice?", "How many applied in total?" — with a verdict footer
(Oversubscribed / Not oversubscribed + one-sentence explanation).

The third row now uses first_preference_offers (already in the API
response) to show "27 of 42 (64.3%)" instead of just the percentage,
giving the raw count parents actually want.

Each row is independently null-gated; rows stack vertically under
480px so the Playfair numeral stays legible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 10:01:19 +01:00
Tudor SitaruandClaude Opus 4.6 6d685b7e8a refactor(admissions): rename published_admission_number to places_offered
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 18s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 46s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Failing after 13s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
The staging model aliased EES's total_number_places_offered column as
published_admission_number, but PAN is the school's published capacity
(not exposed by EES at school level) — what we actually have is the
count of places offered in a given admissions round. The misnomer
propagated to the mart, SQLAlchemy model, API response, TS types, and
UI copy ("places per year", "(PAN)").

Rename end-to-end and fix the UI labels:
  - "29 places for 42 first-choice applications"
      → "29 places offered for 42 first-choice applications"
  - "Reception/Year 7 places per year"
      → "Reception/Year 7 places offered"
  - drop the misleading "(PAN)" suffix in the secondary view

Also add a comment in stg_ees_admissions clarifying this is the number
of places offered, not PAN. Requires dbt to rebuild fact_admissions
(marts are materialized as tables) before the backend can start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 09:45:43 +01:00
Tudor SitaruandClaude Opus 4.6 24ba65c829 fix(detail): scale SATs cascade bars against full chart width
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Labels were sharing a flex row with the bar, so the bar's width %
was computed against the flex remainder after the label rather than
the full chart area. A 96% bar rendered around the 75% ruler mark,
and mobile was worse. Move labels to a header row above each bar and
give the bar a full-width track, so X% now aligns with X% on the ruler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 08:40:38 +01:00
Tudor SitaruandClaude Opus 4.6 3bf2e8f262 feat(detail): replace SATs text tables with cascade bar charts, add admissions bar and history accordion
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 19s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 46s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Redesign the School Details page for better parent comprehension:
- New SatsChart component: horizontal cascade bars with ruler scale and
  national average marker (teal/coral palette matching site theme)
- Admissions section: visual progress bar showing 1st-preference demand
  vs available places, colour-coded by oversubscription status
- Historical data: collapse raw year-by-year table behind a disclosure
  element while keeping the performance line chart always visible
- EAL metric: add national average comparison via DeltaChip (backend now
  includes eal_pct in national averages endpoint)
- New formatWithSuppression utility for null/suppressed data handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 21:22:24 +01:00
Tudor Sitaru 8ce34b3ecc fix(list): read ofsted grade from fact_ofsted_inspection directly, fix dim_school schema lookup
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 18s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m9s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
dim_school.sql was checking for int_ofsted_latest in target.schema (wrong schema)
due to the custom generate_schema_name macro using literal schema names. The
model lives in 'intermediate', so ofsted_grade/date/framework were always NULL
in dim_school, causing all list cards to show 'Not yet inspected'.

Fix 1: data_loader.py joins marts.fact_ofsted_inspection with DISTINCT ON to
get latest inspection per school — no pipeline re-run needed.

Fix 2: dim_school.sql uses schema='intermediate' so future dbt runs correctly
denormalise the Ofsted summary into dim_school.
2026-04-13 14:51:14 +01:00
Tudor Sitaru 9c50c49e1f fix(map): add effect deps, escape HTML in popup, document Att8 delta threshold
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 24s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
2026-04-13 14:32:55 +01:00
Tudor SitaruandClaude Sonnet 4.6 177571f411 feat(map): rebuild popup as mini card with Ofsted badge and headline metric
Replaces the bare Leaflet popup with a mini card showing school name,
3-state Ofsted badge (OEIF grade / ReportCard / pending), phase tag,
headline metric (Att8 for secondary, RWM% for primary) with delta vs
LA/national average, and a styled View Details button. Threads
nationalAvgRwm and laAverages from HomeView → SchoolMap → LeafletMapInner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:29:55 +01:00
Tudor Sitaru 51310160a8 fix(home): suppress unused nationalAvgRwm param, add ofstedPending badge branch 2026-04-13 14:26:55 +01:00
Tudor SitaruandClaude Sonnet 4.6 2c13b21360 feat(home): fetch national averages, wire to SchoolRow and CompactSchoolItem
- 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>
2026-04-13 14:17:34 +01:00
Tudor Sitaru ad2fe5bbef fix(list): remove no-op null coerce and stale comment in SecondarySchoolRow 2026-04-13 14:11:05 +01:00
Tudor SitaruandClaude Sonnet 4.6 58f8eae997 feat(list): remove eng&maths stat, use buildOfstedListBadge in SecondarySchoolRow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:08:16 +01:00
Tudor Sitaru 44fdcfa18b feat(list): redesign primary school card — single metric, vs-national delta, fix label 2026-04-13 13:59:26 +01:00
Tudor Sitaru b1e025d468 style: add ofstedRc, ofstedPending, vsNational CSS classes to row modules 2026-04-13 13:58:46 +01:00
Tudor Sitaru 9ebb421307 fix(utils): tighten ofsted_grade type in buildOfstedListBadge 2026-04-13 13:56:58 +01:00
Tudor SitaruandClaude Sonnet 4.6 8a6758b591 feat(utils): add buildOfstedListBadge helper and fetchNationalAverages
- 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>
2026-04-13 13:52:05 +01:00
Tudor Sitaru 6d02d366ce feat(api): expose ofsted_framework in school list response 2026-04-13 13:45:50 +01:00
Tudor SitaruandClaude Sonnet 4.6 fe31be34a0 fix(secondary): expose GIAS total_pupils in school_info API response
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 22s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 48s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-09 16:15:31 +01:00
Tudor SitaruandClaude Sonnet 4.6 109fa14ccb fix(secondary): use GIAS total_pupils for school roll, not KS4 exam cohort
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 16s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-09 16:04:41 +01:00
Tudor SitaruandClaude Sonnet 4.6 06bf53ac26 fix(dag): remove invalid --select flag from meltano run
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 54s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-09 14:48:31 +01:00
Tudor SitaruandClaude Sonnet 4.6 dc66e22d4d feat: ingest official DfE KS2 national averages from EES data catalogue
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 19s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m24s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-09 14:40:33 +01:00
Tudor SitaruandClaude Sonnet 4.6 a3cfffa4d0 feat: national average reference line now tracks per year on history chart
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 24s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m52s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-09 13:55:14 +01:00
Tudor SitaruandClaude Sonnet 4.6 23f881b797 feat(secondary): apply hero design language to secondary school detail view
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 46s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-04-08 21:05:33 +01:00
Tudor SitaruandClaude Sonnet 4.6 e625addc3b fix(school-detail): move active-section useEffect after navItems declaration
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Failing after 13s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
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>
2026-04-08 15:59:01 +01:00
Tudor SitaruandClaude Sonnet 4.6 536a166b35 feat(school-detail): highlight active section in sticky nav on scroll
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 43s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
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>
2026-04-08 15:42:20 +01:00
Tudor SitaruandClaude Sonnet 4.6 e72345bad5 refactor(school-detail): remove hero summary, red oversubscribed chip
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 47s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-04-08 15:27:03 +01:00
Tudor SitaruandClaude Opus 4.6 dfa8058efc feat(school-detail): page-wide improvements across 5 sections
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-08 15:13:13 +01:00
Tudor SitaruandClaude Opus 4.6 41cefeedf6 polish(school-detail): align hero stat numbers on a shared baseline
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 14s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 8s
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>
2026-04-08 12:00:45 +01:00
Tudor SitaruandClaude Opus 4.6 1e5c66d6ab fix(admissions): correct first_preference_offer_pct in dbt staging
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 18s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-08 11:29:40 +01:00
Tudor SitaruandClaude Opus 4.6 3458195865 polish(school-detail): align hero chips to fixed width, free hero summary
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 44s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-04-08 11:18:07 +01:00
Tudor SitaruandClaude Opus 4.6 24b3688df0 polish(school-detail): tighten hero layout and drop redundant chip
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 43s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-04-08 11:04:01 +01:00
Tudor SitaruandClaude Opus 4.6 2d6e39eebc fix(school-detail): hero Ofsted chip mislabels OEIF schools as Report Card
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 45s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-08 10:44:37 +01:00
Tudor SitaruandClaude Opus 4.6 c749d72a6a feat(school-detail): editorial hero with signal chips, at-a-glance stats, summary
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 15s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 51s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-04-08 10:32:33 +01:00
Tudor SitaruandClaude Opus 4.6 f053b35c6f test(dim_school): downgrade phase not_null to warn
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 45s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m16s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-07 22:12:57 +01:00
Tudor SitaruandClaude Opus 4.6 ca5f6a962c fix(dim_school): expand phase inference with name-based fallback
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 45s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m10s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-07 21:15:54 +01:00
Tudor SitaruandClaude Opus 4.6 ed244ef743 fix(mobile): address iPhone layout issues across rankings, detail, compare
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 14s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 45s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-04-07 20:29:15 +01:00
Tudor Sitaru ce46db7dbe shortening placeholder text
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
2026-04-07 16:17:56 +01:00
Tudor SitaruandClaude Opus 4.6 a562f408d2 refactor: expand RWM to "Reading, Writing & Maths" in user-facing text
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 24s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 52s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m51s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-07 15:53:52 +01:00
Tudor SitaruandClaude Opus 4.6 5b025b98bd fix(dim_school): use case-insensitive comparison for phase inference
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m6s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-04-02 15:33:04 +01:00
Tudor SitaruandClaude Opus 4.6 4c3c3c882d fix(dim_school): infer phase from age range for independent schools
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m9s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-01 16:18:52 +01:00
Tudor SitaruandClaude Opus 4.6 d591d8e66b fix(utils): handle null year in formatAcademicYear
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 48s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 16:07:28 +01:00
Tudor SitaruandClaude Opus 4.6 4db36b9099 feat(ui): add phase indicators to school list rows
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-01 15:47:51 +01:00
Tudor Sitaru cacbeeb068 fixing backend image
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 17s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 46s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
2026-04-01 15:05:21 +01:00
Tudor SitaruandClaude Opus 4.6 d5f6366c28 fix(years): format academic years as 2016/17 across all views, remove legacy frontend and data
Build and Push Docker Images / Build Backend (FastAPI) (push) Failing after 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
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>
2026-04-01 14:58:13 +01:00
Tudor SitaruandClaude Opus 4.6 2b757e556d fix(legacy-ks2): strip % suffix from percentage values
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m11s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m37s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-04-01 13:07:51 +01:00
Tudor SitaruandClaude Opus 4.6 fbd1de9220 fix(dag): add stg_legacy_ks2 to annual EES dbt build selector
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 33s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m11s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m29s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 11:27:29 +01:00
Tudor SitaruandClaude Opus 4.6 fba8e74b72 refactor(legacy-ks2): use explicit year→URL mapping instead of base URL pattern
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m9s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 32s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-03-31 22:44:11 +01:00
Tudor SitaruandClaude Opus 4.6 6d4962639c feat(legacy-ks2): add stream for pre-COVID KS2 data (2015-2019)
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 46s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m17s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 2m26s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-03-31 14:36:41 +01:00
Tudor SitaruandClaude Opus 4.6 fc011c6547 fix(tap-uk-ees): case-insensitive URN column matching for older census files
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m48s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-03-30 22:36:16 +01:00
Tudor SitaruandClaude Sonnet 4.6 752abd69a5 fix(tap-uk-ees): inject time_period from release slug when absent in CSV
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m8s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m37s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-03-30 21:59:24 +01:00
Tudor SitaruandClaude Sonnet 4.6 570c2b689e fix(tap-uk-ees): handle plain list response from releases endpoint
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 33s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m6s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m45s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 21:47:14 +01:00
Tudor SitaruandClaude Sonnet 4.6 17617137ea fix(data-info): drop NaN years before converting to int
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 47s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 32s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 21:41:11 +01:00
Tudor SitaruandClaude Sonnet 4.6 9a1572ea20 feat(tap-uk-ees): fetch all historical releases, not just latest
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m9s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 1m42s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-03-30 21:37:26 +01:00
Tudor d4abb56c22 feat(ui): redesign landing page search and empty states
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 42s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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
2026-03-05 13:00:34 +00:00
Tudor 2b808959c5 style(ux): make filter bar full width to align with layout
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
2026-03-05 09:40:37 +00:00
Tudor ad7380dba5 feat(ux): implement UX audit recommendations
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m10s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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
2026-03-05 09:33:47 +00:00
Tudor SitaruandClaude Sonnet 4.6 6a95445f5e Add Umami analytics script to Next.js layout
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m46s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:52:53 +00:00
Tudor SitaruandClaude Sonnet 4.6 8c60614023 Fix CSP to allow Umami analytics and remove stale GA directives
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 58s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m48s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:43:51 +00:00
Tudor Sitaru 4c4070841c adding correct tracking link
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 57s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m55s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
2026-02-20 15:30:30 +00:00
Tudor SitaruandClaude Sonnet 4.6 9b55320aa7 Replace Google Analytics and cookie consent banner with Umami analytics
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 3m18s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m56s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:17:00 +00:00
TudorandClaude Opus 4.5 ec61e16c9d Condense school detail page layout for better space efficiency
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-02-04 12:14:28 +00:00
Tudor 3cab49a2b3 Removing duplicate footer entries
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m8s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
2026-02-04 12:11:42 +00:00
TudorandClaude Opus 4.5 c0f44cd29d Fix header z-index to prevent map overlap when scrolling
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-04 12:06:48 +00:00
TudorandClaude Opus 4.5 cc4e95b383 Fix metric category names to match TypeScript types
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
Changed 'equity' to 'disadvantaged' and 'trends' to '3yr' to match
the MetricDefinition category type.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:53:26 +00:00
TudorandClaude Opus 4.5 2a39cfca82 Group metrics dropdown by category in rankings and comparison views
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 1m1s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
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>
2026-02-04 11:50:13 +00:00
TudorandClaude Opus 4.5 5e296b6e5c Fix backend API to return location_info instead of search_location
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 57s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m17s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-04 11:38:56 +00:00
TudorandClaude Opus 4.5 85709d99ca Condense spacing throughout the website for denser layout
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Reduced padding, margins, and gaps across all components:
- Header: smaller logo, tighter navigation
- FilterBar: compact hero, smaller inputs and toggles
- SchoolCard: reduced padding, smaller fonts and metrics
- HomeView: tighter grid gaps, smaller section headers
- Map view: condensed compact list items

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:18:39 +00:00
TudorandClaude Opus 4.5 1b0d6edb98 Add map view for location search results
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-04 10:05:31 +00:00
TudorandClaude Opus 4.5 ea6820f1c4 Combine hero and filter sections into unified search block
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m17s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-04 09:54:27 +00:00
TudorandClaude Opus 4.5 1b9220d51b Redesign hero section to be more compact with coral accent
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 39s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m16s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-04 09:29:06 +00:00
TudorandClaude Opus 4.5 05c667e6d3 Fix latestValue block to stick to bottom of school cards
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-02-03 14:55:01 +00:00
TudorandClaude Opus 4.5 200fccb615 Fix comparison badge to update in real-time across components
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-02-03 14:26:23 +00:00
TudorandClaude Opus 4.5 18964a34a2 Add visual polish and micro-interactions for editorial feel
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m15s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Phase 1 - Critical Fixes:
- EmptyState: warm palette, coral button, Playfair Display title
- Pagination: design system colors, coral active state
- LoadingSkeleton: warm shimmer with coral tint

Phase 2 - Signature Patterns:
- Navigation: sliding underline hover effect on links
- globals.css: increased noise texture opacity for paper feel
- RankingsView: alternating row backgrounds
- HomeView: decorative coral bar under section headings

Phase 3 - Polish:
- SchoolCard: SVG trend icons replacing unicode arrows
- RankingsView: styled metallic rank badges replacing emoji medals

Phase 4 - Micro-interactions:
- Navigation badge: pop animation when count changes
- HomeView grid: staggered entry animation for cards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:12:48 +00:00
TudorandClaude Opus 4.5 d22275bfe0 Fix modal width mismatch with content
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m15s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-02-03 13:03:47 +00:00
TudorandClaude Opus 4.5 51b081d9e0 Style Modal and SchoolSearchModal with warm editorial palette
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m9s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
- Modal: Warm overlay, rounded corners, Playfair Display title,
  coral close button hover, warm scrollbar colors
- SchoolSearchModal: Coral focus states, gold warning banner,
  coral add buttons, warm result item styling with hover effects

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:17:06 +00:00
TudorandClaude Opus 4.5 53e11aca82 Fix: Append :path* to FASTAPI_URL in rewrites
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-03 11:00:18 +00:00
TudorandClaude Opus 4.5 a3966e0c31 Fix: Pass FASTAPI_URL as build arg for Next.js rewrites
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-03 10:46:03 +00:00
TudorandClaude Opus 4.5 0e698d38d9 Fix: Use centralized API functions instead of manual URL construction
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m14s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
- 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>
2026-02-03 10:27:45 +00:00
TudorandClaude Opus 4.5 c2ec067495 Apply warm editorial design system across all components
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m19s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m22s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Replace generic blue/gray colors with warm editorial palette:
- Navigation: coral active states, branded logo colors
- Footer: navy background, gold section titles
- FilterBar: coral search button and focus states
- SchoolCard: coral left accent on hover, teal/coral buttons
- HomeView: gradient hero section, Playfair Display headings
- RankingsView: gold top-3 highlights, warm table styling
- ComparisonView: teal card borders, coral buttons

Consistent use of CSS variables and Playfair Display serif font
for headings throughout.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 10:11:23 +00:00
TudorandClaude Sonnet 4.5 04ba09ab3b Add loading state and debugging for comparison chart
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-02 22:47:37 +00:00
TudorandClaude Sonnet 4.5 f04e383ea3 Fix: Use correct API base URL for client-side fetches
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-02 22:42:21 +00:00
TudorandClaude Sonnet 4.5 19e5199443 Improve professional appearance: logo, favicon, and remove emoji icons
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-02 22:34:14 +00:00
TudorandClaude Sonnet 4.5 2e62853b70 Fix: Add missing CSS variables for Add to Compare button
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m14s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-02 22:27:42 +00:00
TudorandClaude Sonnet 4.5 1c0e6298f2 Fix: Improve UX with empty state, miles, and metric labels
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
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>
2026-02-02 22:21:55 +00:00
TudorandClaude Sonnet 4.5 b3fc55faf6 Fix: Await searchParams in home page for Next.js 15 compatibility
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-02 22:09:17 +00:00
TudorandClaude Sonnet 4.5 4dc0c10c9d Fix metrics API response structure
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
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>
2026-02-02 22:00:07 +00:00
TudorandClaude Sonnet 4.5 d90661f2c2 Fix useFilters hook to match API response structure
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m14s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Remove nested .filters access to match updated FiltersResponse type.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 21:54:37 +00:00
TudorandClaude Sonnet 4.5 148e46ae6a Fix filters API response structure
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 59s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
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>
2026-02-02 21:51:08 +00:00
TudorandClaude Sonnet 4.5 ef4932b553 Match original warm editorial design
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m9s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- 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>
2026-02-02 21:39:40 +00:00
TudorandClaude Sonnet 4.5 9ba49106f8 Fix SchoolsResponse fallback structure
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Use correct top-level pagination properties (page, page_size, total, total_pages)
instead of nested pagination object.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 21:34:24 +00:00
TudorandClaude Sonnet 4.5 0571bf3450 Add error handling and fallbacks for API failures
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 58s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
- 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>
2026-02-02 21:28:50 +00:00