feat(admissions): surface multi-year admissions trend on school detail
Build and Push Docker Images / Build Backend (FastAPI) (pull_request) Successful in 22s
Build and Push Docker Images / Build Frontend (Next.js) (pull_request) Successful in 53s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (pull_request) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (pull_request) Has been skipped

The school detail page only showed the latest admissions year. We store
every year, which is more decision-relevant for parents (the trend and its
consistency matter more than a single noisy year).

Backend now returns the full admissions_history (oldest first) alongside the
existing latest-year object. The primary SchoolDetailView gains a header
toggle ("This year | N-year trend") that swaps the Q&A for an SVG sparkline
of the first-choice offer rate. The toggle only appears when >=2 years carry
an offer rate; otherwise it falls back to the single-year card. Both views
share one CSS-grid cell so switching causes no layout shift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tudor
2026-06-19 18:41:03 +01:00
co-authored by Claude Opus 4.8
parent e7c26a83db
commit 4de7e559e9
7 changed files with 611 additions and 57 deletions
+1
View File
@@ -608,6 +608,7 @@ async def get_school_details(request: Request, urn: int):
"parent_view": supplementary.get("parent_view"),
"census": supplementary.get("census"),
"admissions": supplementary.get("admissions"),
"admissions_history": supplementary.get("admissions_history") or [],
"sen_detail": supplementary.get("sen_detail"),
"phonics": supplementary.get("phonics"),
"deprivation": supplementary.get("deprivation"),
+22 -7
View File
@@ -477,10 +477,9 @@ def get_supplementary_data(db: Session, urn: int) -> dict:
else None
)
# Admissions (latest year)
a = safe_query(FactAdmissions, "urn", "year")
result["admissions"] = (
{
# Admissions — all years, oldest first (for the multi-year trend view).
def _admissions_row(a):
return {
"year": a.year,
"school_phase": a.school_phase,
"places_offered": a.places_offered,
@@ -491,9 +490,25 @@ def get_supplementary_data(db: Session, urn: int) -> dict:
"oversubscription_ratio": a.oversubscription_ratio,
"oversubscribed": a.oversubscribed,
}
if a
else None
)
try:
admissions_rows = (
db.query(FactAdmissions)
.filter(FactAdmissions.urn == urn)
.order_by(FactAdmissions.year.asc())
.all()
)
except Exception as e:
import logging
logging.getLogger(__name__).error("admissions history query failed: %s", e)
db.rollback()
admissions_rows = []
history = [_admissions_row(a) for a in admissions_rows]
result["admissions_history"] = history
# Keep the single latest-year object for backwards-compatible consumers
# (hero chips, etc.).
result["admissions"] = history[-1] if history else None
# SEN detail — not available in current marts
result["sen_detail"] = None