Commit Graph

27 Commits

Author SHA1 Message Date
Tudor Sitaru 675601869b feat(detail): show pupil gender split on school detail pages
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 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Upgrades the existing "Pupils" stat to include a compact split bar and
percentage hint for mixed schools (single-sex schools already carry a
"Boys's/Girls's school" badge, so the split would be redundant).

Wires fact_pupil_characteristics into the API: new SQLAlchemy model and
a real census block in /api/schools/{urn} replacing the prior null stub.

On the primary detail page the inline "Pupils: 241" text is replaced by
a richer block (display number + bar + "52% girls · 48% boys"). On the
secondary detail page the existing "Total pupils" hero stat card grows
the bar and hint beneath the number. Both fall back to the previous
text-only rendering when census gender data is missing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 22:36:33 +01:00
Tudor Sitaru 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 Sitaru 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 Sitaru 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 9ebb421307 fix(utils): tighten ofsted_grade type in buildOfstedListBadge 2026-04-13 13:56:58 +01:00
Tudor Sitaru 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 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 Sitaru 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 Sitaru 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 Sitaru 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 Sitaru 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 Sitaru 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 784febc162 feat(seo): add school name to URLs, fix sticky nav, collapse compare widget
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 57s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
- URLs now /school/138267-school-name instead of /school/138267
- Bare URN URLs redirect to canonical slug (backward compat)
- Remove overflow-x:hidden that broke sticky tab nav on secondary pages
- ComparisonToast starts collapsed — user must click to open

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 12:41:28 +01:00
tudor 1d22877aec feat(ux): 8 UX improvements — simpler home, advanced filters, phase tabs, 4-line rows
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 48s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m13s
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
1. Simpler home page: only search box on landing, no filter dropdowns
2. Advanced filters: hidden behind toggle on results page, auto-open if active
3. Per-school phase rendering: each row renders based on its own data
4. Taller 4-line rows with context line (type, age range, denomination, gender)
5. Result-scoped filters: dropdown values reflect current search results
6. Fix blank filter values: exclude empty strings and "Not applicable"
7. Rankings: Primary/Secondary phase tabs with phase-specific metrics
8. Compare: Primary/Secondary tabs with school counts and phase metrics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 08:57:06 +01:00
tudor e8175561d5 updates for secondary schools
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 46s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m15s
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
2026-03-28 22:36:00 +00:00
tudor 5eff9af69c feat: add secondary school support with KS4 data and metric tooltips
Build and Push Docker Images / Build Frontend (Next.js) (push) Has been cancelled
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Has been cancelled
Build and Push Docker Images / Trigger Portainer Update (push) Has been cancelled
Build and Push Docker Images / Build Backend (FastAPI) (push) Has been cancelled
- Backend: replace INNER JOIN ks2 with UNION ALL (ks2 + ks4) so primary
  and secondary schools both appear in the main DataFrame
- Backend: add /api/national-averages endpoint computing means from live
  data, replacing the hardcoded NATIONAL_AVG constant on the frontend
- Backend: add phase filter param to /api/schools; return phases from
  /api/filters; fix hardcoded "phase": "Primary" in school detail endpoint
- Backend: add KS4 metric definitions (Attainment 8, Progress 8, EBacc,
  English & Maths pass rates) to METRIC_DEFINITIONS and RANKING_COLUMNS
- Frontend: SchoolDetailView is now phase-aware — secondary schools show
  a GCSE Results section (Att8, P8, E&M, EBacc) instead of SATs; phonics
  tab hidden for secondary; admissions says Year 7 instead of Year 3;
  history table shows KS4 columns; chart datasets switch for secondary
- Frontend: new MetricTooltip component (CSS-only ⓘ icon) backed by
  METRIC_EXPLANATIONS — added to RWM, GPS, SEN, EAL, IDACI, progress
  scores and all KS4 metrics throughout SchoolDetailView and SchoolCard
- Frontend: METRIC_EXPLANATIONS extended with KS4 terms (Attainment 8,
  Progress 8, EBacc) and previously missing terms (SEN, EHCP, EAL, IDACI)
- Frontend: SchoolCard expands "RWM" to "Reading, Writing & Maths" and
  shows Attainment 8 / English & Maths Grade 4+ for secondary schools
- Frontend: FilterBar adds Phase dropdown (Primary / Secondary / All-through)
- Frontend: HomeView hero copy updated; compact list shows phase-aware metric
- Global metadata updated to remove "primary only" framing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:59:40 +00:00
tudor cd1c649d0f fix(frontend): format 6-digit EES academic year codes correctly
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m5s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
formatAcademicYear now handles both 4-digit (2023→2023/24) and 6-digit
EES codes (202526→2025/26). Applied to all year displays: SATs, phonics,
admissions, finances, and the yearly results table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 18:30:37 +00:00
tudor 1c49a135c4 feat(ofsted): add Report Card system support alongside legacy OEIF grades
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 47s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m11s
Build and Push Docker Images / Build Integrator (push) Successful in 58s
Build and Push Docker Images / Build Kestra Init (push) Successful in 32s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Ofsted replaced single overall grades with Report Cards from Nov 2025.
Both systems are retained during the transition period.

- DB: new framework + 9 RC columns on ofsted_inspections (schema v4)
- Integrator: auto-detect OEIF vs Report Card from CSV column headers;
  parse 5-level RC grades and safeguarding met/not-met
- API: expose all new fields in the ofsted response dict
- Frontend: branch on framework='ReportCard' to show safeguarding badge
  + 8-category grid; fall back to legacy OEIF layout otherwise;
  always show inspection date in both layouts
- CSS: rcGrade1–5 and safeguardingMet/NotMet classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:03:04 +00:00
tudor dd49ef28b2 feat(data): integrate 9 UK government data sources via Kestra
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 47s
Build and Push Docker Images / Trigger Portainer Update (push) Has been cancelled
Build and Push Docker Images / Build Frontend (Next.js) (push) Has been cancelled
Adds a full data integration pipeline for enriching school profiles with
supplementary data from Ofsted, GIAS, EES, IDACI, and FBIT.

Backend:
- Bump SCHEMA_VERSION to 3; add 8 new DB tables (ofsted_inspections,
  ofsted_parent_view, school_census, admissions, sen_detail, phonics,
  school_deprivation, school_finance) plus GIAS columns on schools
- Expose all supplementary data via GET /api/schools/{urn}
- Enrich school list responses with ofsted_grade + ofsted_date

Integrator (new service):
- FastAPI HTTP microservice; Kestra calls POST /run/{source}
- 9 source modules: ofsted, gias, parent_view, census, admissions,
  sen_detail, phonics, idaci, finance
- 9 Kestra flow YAMLs with scheduled triggers and 3× retry

Frontend:
- SchoolRow: colour-coded Ofsted badge (Outstanding/Good/RI/Inadequate)
- SchoolDetailView: 7 new sections — Ofsted sub-judgements, Parent View
  survey bars, Admissions, Pupils & Inclusion / SEN, Phonics, Deprivation
  Context, Finances
- types.ts: 8 new interfaces + extended School/SchoolDetailsResponse

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:44:04 +00:00
tudor 0f29397253 feat(ui): replace card grid with row-based results and plain-language metric labels
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m2s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Replace the school card grid with a scannable row list that shows 3x more
results per screen. Each row shows: school name + R,W&M % with trend,
area/type meta, and reading/writing/maths progress scores with plain-English
band labels (e.g. "above average") instead of raw numbers.

Add lib/metrics.ts as a single source of truth for plain-language metric
explanations and the progressBand() helper. Map view toggle is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 22:32:33 +00:00
Tudor 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
Tudor 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
Tudor 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
Tudor 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
Tudor 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
Tudor a2611369c3 Fix API URL for server-side vs client-side requests
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
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
Use FASTAPI_URL for SSR (internal Docker network: http://backend:80/api)
Use NEXT_PUBLIC_API_URL for browser requests (http://localhost:8000/api)

Fixes ECONNREFUSED error during server-side rendering.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 21:23:45 +00:00
Tudor ff7f5487e6 Complete Next.js migration with SSR and Docker deployment
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m26s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 1m48s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
- 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>
2026-02-02 20:34:35 +00:00