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>
This commit is contained in:
Tudor Sitaru
2026-04-14 09:45:43 +01:00
parent 24ba65c829
commit 6d685b7e8a
7 changed files with 19 additions and 14 deletions
+1 -1
View File
@@ -419,7 +419,7 @@ def get_supplementary_data(db: Session, urn: int) -> dict:
{
"year": a.year,
"school_phase": a.school_phase,
"published_admission_number": a.published_admission_number,
"places_offered": a.places_offered,
"total_applications": a.total_applications,
"first_preference_applications": a.first_preference_applications,
"first_preference_offers": a.first_preference_offers,
+1 -1
View File
@@ -179,7 +179,7 @@ class FactAdmissions(Base):
urn = Column(Integer, primary_key=True)
year = Column(Integer, primary_key=True)
school_phase = Column(String(50))
published_admission_number = Column(Integer)
places_offered = Column(Integer)
total_applications = Column(Integer)
first_preference_applications = Column(Integer)
first_preference_offers = Column(Integer)
+6 -6
View File
@@ -787,15 +787,15 @@ export function SchoolDetailView({
{admissions && (
<section id="admissions" className={styles.card}>
<h2 className={styles.sectionTitle}>How Hard to Get Into This School ({formatAcademicYear(admissions.year)})</h2>
{admissions.first_preference_applications != null && admissions.published_admission_number != null && (
{admissions.first_preference_applications != null && admissions.places_offered != null && (
<div className={styles.admissionsBarWrap}>
<div className={styles.admissionsBarLabel}>
<strong>{admissions.published_admission_number}</strong> places for <strong>{admissions.first_preference_applications}</strong> first-choice applications
<strong>{admissions.places_offered}</strong> places offered for <strong>{admissions.first_preference_applications}</strong> first-choice applications
</div>
<div className={styles.admissionsBarTrack}>
<div
className={`${styles.admissionsBarFill} ${admissions.oversubscribed ? styles.admissionsBarOversubscribed : styles.admissionsBarUndersubscribed}`}
style={{ width: `${Math.min(100, (admissions.published_admission_number / admissions.first_preference_applications) * 100)}%` }}
style={{ width: `${Math.min(100, (admissions.places_offered / admissions.first_preference_applications) * 100)}%` }}
/>
</div>
</div>
@@ -808,10 +808,10 @@ export function SchoolDetailView({
</div>
)}
<div className={styles.metricsGrid}>
{admissions.published_admission_number != null && (
{admissions.places_offered != null && (
<div className={styles.metricCard}>
<div className={styles.metricLabel}>{isSecondary ? 'Year 7' : 'Reception'} places per year</div>
<div className={styles.metricValue}>{admissions.published_admission_number}</div>
<div className={styles.metricLabel}>{isSecondary ? 'Year 7' : 'Reception'} places offered</div>
<div className={styles.metricValue}>{admissions.places_offered}</div>
</div>
)}
{admissions.total_applications != null && (
@@ -641,10 +641,10 @@ export function SecondarySchoolDetailView({
)}
<div className={styles.metricsGrid}>
{admissions.published_admission_number != null && (
{admissions.places_offered != null && (
<div className={styles.metricCard}>
<div className={styles.metricLabel}>Year 7 places per year (PAN)</div>
<div className={styles.metricValue}>{admissions.published_admission_number}</div>
<div className={styles.metricLabel}>Year 7 places offered</div>
<div className={styles.metricValue}>{admissions.places_offered}</div>
</div>
)}
{admissions.total_applications != null && (
+2 -1
View File
@@ -131,7 +131,8 @@ export interface SchoolCensus {
export interface SchoolAdmissions {
year: number;
school_phase?: string | null;
published_admission_number: number | null;
/** Number of places the school offered in this admissions round (not PAN — EES doesn't expose PAN). */
places_offered: number | null;
total_applications: number | null;
first_preference_applications?: number | null;
first_preference_offers?: number | null;
@@ -4,7 +4,7 @@ select
urn,
year,
school_phase,
published_admission_number,
places_offered,
total_applications,
first_preference_applications,
first_preference_offers,
@@ -18,7 +18,11 @@ renamed as (
entry_year,
-- Places and offers
{{ safe_numeric('total_number_places_offered') }}::integer as published_admission_number,
-- places_offered: number of places the school actually offered in this
-- year's admissions round. NOT the Published Admission Number (PAN),
-- which is the school's published capacity — EES does not expose PAN
-- at school level, so we use the count of offers as the best proxy.
{{ safe_numeric('total_number_places_offered') }}::integer as places_offered,
{{ safe_numeric('number_preferred_offers') }}::integer as total_offers,
{{ safe_numeric('number_1st_preference_offers') }}::integer as first_preference_offers,
{{ safe_numeric('number_2nd_preference_offers') }}::integer as second_preference_offers,