2026-01-06 17:22:39 +00:00
|
|
|
"""
|
2026-03-27 09:29:27 +00:00
|
|
|
SQLAlchemy models — all tables live in the marts schema, built by dbt.
|
|
|
|
|
Read-only: the pipeline writes to these tables; the backend only reads.
|
2026-01-06 17:22:39 +00:00
|
|
|
"""
|
|
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
from sqlalchemy import Column, Integer, String, Float, Boolean, Date, Text, Index
|
2026-01-16 10:23:02 +00:00
|
|
|
|
2026-01-06 17:22:39 +00:00
|
|
|
from .database import Base
|
|
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
MARTS = {"schema": "marts"}
|
2026-01-06 17:22:39 +00:00
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
|
|
|
|
|
class DimSchool(Base):
|
|
|
|
|
"""Canonical school dimension — one row per active URN."""
|
|
|
|
|
__tablename__ = "dim_school"
|
|
|
|
|
__table_args__ = MARTS
|
|
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
2026-01-06 17:22:39 +00:00
|
|
|
school_name = Column(String(255), nullable=False)
|
2026-03-27 09:29:27 +00:00
|
|
|
phase = Column(String(100))
|
2026-01-06 17:22:39 +00:00
|
|
|
school_type = Column(String(100))
|
2026-03-27 09:29:27 +00:00
|
|
|
academy_trust_name = Column(String(255))
|
|
|
|
|
academy_trust_uid = Column(String(20))
|
|
|
|
|
religious_character = Column(String(100))
|
|
|
|
|
gender = Column(String(20))
|
2026-01-06 17:22:39 +00:00
|
|
|
age_range = Column(String(20))
|
2026-03-27 09:29:27 +00:00
|
|
|
capacity = Column(Integer)
|
|
|
|
|
total_pupils = Column(Integer)
|
|
|
|
|
headteacher_name = Column(String(200))
|
|
|
|
|
website = Column(String(255))
|
|
|
|
|
telephone = Column(String(30))
|
|
|
|
|
status = Column(String(50))
|
|
|
|
|
nursery_provision = Column(Boolean)
|
|
|
|
|
admissions_policy = Column(String(50))
|
|
|
|
|
# Denormalised Ofsted summary (updated by monthly pipeline)
|
|
|
|
|
ofsted_grade = Column(Integer)
|
|
|
|
|
ofsted_date = Column(Date)
|
|
|
|
|
ofsted_framework = Column(String(20))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DimLocation(Base):
|
|
|
|
|
"""School location — address, lat/lng from easting/northing (BNG→WGS84)."""
|
|
|
|
|
__tablename__ = "dim_location"
|
|
|
|
|
__table_args__ = MARTS
|
|
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
|
|
|
|
address_line1 = Column(String(255))
|
|
|
|
|
address_line2 = Column(String(255))
|
2026-01-06 17:22:39 +00:00
|
|
|
town = Column(String(100))
|
2026-03-27 09:29:27 +00:00
|
|
|
county = Column(String(100))
|
|
|
|
|
postcode = Column(String(20))
|
|
|
|
|
local_authority_code = Column(Integer)
|
|
|
|
|
local_authority_name = Column(String(100))
|
|
|
|
|
parliamentary_constituency = Column(String(100))
|
|
|
|
|
urban_rural = Column(String(50))
|
|
|
|
|
easting = Column(Integer)
|
|
|
|
|
northing = Column(Integer)
|
2026-01-06 17:22:39 +00:00
|
|
|
latitude = Column(Float)
|
|
|
|
|
longitude = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
# geom is a PostGIS geometry — not mapped to SQLAlchemy (accessed via raw SQL)
|
2026-03-24 11:44:04 +00:00
|
|
|
|
2026-01-06 17:22:39 +00:00
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
class KS2Performance(Base):
|
|
|
|
|
"""KS2 attainment — one row per URN per year (includes predecessor data)."""
|
|
|
|
|
__tablename__ = "fact_ks2_performance"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_ks2_urn_year", "urn", "year"),
|
|
|
|
|
MARTS,
|
|
|
|
|
)
|
2026-01-06 17:22:39 +00:00
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
urn = Column(Integer, primary_key=True)
|
|
|
|
|
year = Column(Integer, primary_key=True)
|
|
|
|
|
source_urn = Column(Integer)
|
2026-01-06 17:22:39 +00:00
|
|
|
total_pupils = Column(Integer)
|
|
|
|
|
eligible_pupils = Column(Integer)
|
2026-03-27 09:29:27 +00:00
|
|
|
# Core attainment
|
2026-01-06 17:22:39 +00:00
|
|
|
rwm_expected_pct = Column(Float)
|
|
|
|
|
rwm_high_pct = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
reading_expected_pct = Column(Float)
|
2026-01-06 17:22:39 +00:00
|
|
|
reading_high_pct = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
reading_avg_score = Column(Float)
|
2026-01-06 17:22:39 +00:00
|
|
|
reading_progress = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
writing_expected_pct = Column(Float)
|
|
|
|
|
writing_high_pct = Column(Float)
|
2026-01-06 17:22:39 +00:00
|
|
|
writing_progress = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
maths_expected_pct = Column(Float)
|
|
|
|
|
maths_high_pct = Column(Float)
|
2026-01-06 17:22:39 +00:00
|
|
|
maths_avg_score = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
maths_progress = Column(Float)
|
|
|
|
|
gps_expected_pct = Column(Float)
|
|
|
|
|
gps_high_pct = Column(Float)
|
2026-01-06 17:22:39 +00:00
|
|
|
gps_avg_score = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
science_expected_pct = Column(Float)
|
|
|
|
|
# Absence
|
2026-01-16 09:58:11 +00:00
|
|
|
reading_absence_pct = Column(Float)
|
|
|
|
|
writing_absence_pct = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
maths_absence_pct = Column(Float)
|
|
|
|
|
gps_absence_pct = Column(Float)
|
2026-01-16 09:58:11 +00:00
|
|
|
science_absence_pct = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
# Gender
|
2026-01-06 17:22:39 +00:00
|
|
|
rwm_expected_boys_pct = Column(Float)
|
|
|
|
|
rwm_high_boys_pct = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
rwm_expected_girls_pct = Column(Float)
|
2026-01-06 17:22:39 +00:00
|
|
|
rwm_high_girls_pct = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
# Disadvantaged
|
2026-01-06 17:22:39 +00:00
|
|
|
rwm_expected_disadvantaged_pct = Column(Float)
|
|
|
|
|
rwm_expected_non_disadvantaged_pct = Column(Float)
|
|
|
|
|
disadvantaged_gap = Column(Float)
|
2026-03-27 09:29:27 +00:00
|
|
|
# Context
|
|
|
|
|
disadvantaged_pct = Column(Float)
|
|
|
|
|
eal_pct = Column(Float)
|
|
|
|
|
sen_support_pct = Column(Float)
|
|
|
|
|
sen_ehcp_pct = Column(Float)
|
|
|
|
|
stability_pct = Column(Float)
|
2026-01-16 10:23:02 +00:00
|
|
|
|
2026-03-24 11:44:04 +00:00
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
class FactOfstedInspection(Base):
|
|
|
|
|
"""Full Ofsted inspection history — one row per inspection."""
|
|
|
|
|
__tablename__ = "fact_ofsted_inspection"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_ofsted_urn_date", "urn", "inspection_date"),
|
|
|
|
|
MARTS,
|
|
|
|
|
)
|
2026-03-24 11:44:04 +00:00
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
2026-03-27 09:29:27 +00:00
|
|
|
inspection_date = Column(Date, primary_key=True)
|
|
|
|
|
inspection_type = Column(String(100))
|
2026-03-25 13:03:04 +00:00
|
|
|
framework = Column(String(20))
|
2026-03-24 11:44:04 +00:00
|
|
|
overall_effectiveness = Column(Integer)
|
|
|
|
|
quality_of_education = Column(Integer)
|
|
|
|
|
behaviour_attitudes = Column(Integer)
|
|
|
|
|
personal_development = Column(Integer)
|
|
|
|
|
leadership_management = Column(Integer)
|
2026-03-27 09:29:27 +00:00
|
|
|
early_years_provision = Column(Integer)
|
|
|
|
|
sixth_form_provision = Column(Integer)
|
|
|
|
|
rc_safeguarding_met = Column(Boolean)
|
2026-03-25 13:03:04 +00:00
|
|
|
rc_inclusion = Column(Integer)
|
|
|
|
|
rc_curriculum_teaching = Column(Integer)
|
|
|
|
|
rc_achievement = Column(Integer)
|
|
|
|
|
rc_attendance_behaviour = Column(Integer)
|
|
|
|
|
rc_personal_development = Column(Integer)
|
|
|
|
|
rc_leadership_governance = Column(Integer)
|
2026-03-27 09:29:27 +00:00
|
|
|
rc_early_years = Column(Integer)
|
|
|
|
|
rc_sixth_form = Column(Integer)
|
|
|
|
|
report_url = Column(Text)
|
2026-03-24 11:44:04 +00:00
|
|
|
|
|
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
class FactParentView(Base):
|
|
|
|
|
"""Ofsted Parent View survey — latest per school."""
|
|
|
|
|
__tablename__ = "fact_parent_view"
|
|
|
|
|
__table_args__ = MARTS
|
2026-03-24 11:44:04 +00:00
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
|
|
|
|
survey_date = Column(Date)
|
|
|
|
|
total_responses = Column(Integer)
|
2026-03-27 09:29:27 +00:00
|
|
|
q_happy_pct = Column(Float)
|
|
|
|
|
q_safe_pct = Column(Float)
|
|
|
|
|
q_behaviour_pct = Column(Float)
|
|
|
|
|
q_bullying_pct = Column(Float)
|
|
|
|
|
q_communication_pct = Column(Float)
|
|
|
|
|
q_progress_pct = Column(Float)
|
|
|
|
|
q_teaching_pct = Column(Float)
|
|
|
|
|
q_information_pct = Column(Float)
|
|
|
|
|
q_curriculum_pct = Column(Float)
|
|
|
|
|
q_future_pct = Column(Float)
|
|
|
|
|
q_leadership_pct = Column(Float)
|
|
|
|
|
q_wellbeing_pct = Column(Float)
|
|
|
|
|
q_recommend_pct = Column(Float)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FactAdmissions(Base):
|
|
|
|
|
"""School admissions — one row per URN per year."""
|
|
|
|
|
__tablename__ = "fact_admissions"
|
2026-03-24 11:44:04 +00:00
|
|
|
__table_args__ = (
|
2026-03-27 09:29:27 +00:00
|
|
|
Index("ix_admissions_urn_year", "urn", "year"),
|
|
|
|
|
MARTS,
|
2026-03-24 11:44:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
|
|
|
|
year = Column(Integer, primary_key=True)
|
2026-03-27 09:29:27 +00:00
|
|
|
school_phase = Column(String(50))
|
|
|
|
|
published_admission_number = Column(Integer)
|
2026-03-24 11:44:04 +00:00
|
|
|
total_applications = Column(Integer)
|
2026-03-27 09:29:27 +00:00
|
|
|
first_preference_applications = Column(Integer)
|
|
|
|
|
first_preference_offers = Column(Integer)
|
|
|
|
|
first_preference_offer_pct = Column(Float)
|
2026-03-24 11:44:04 +00:00
|
|
|
oversubscribed = Column(Boolean)
|
2026-03-27 09:29:27 +00:00
|
|
|
admissions_policy = Column(String(100))
|
2026-03-24 11:44:04 +00:00
|
|
|
|
|
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
class FactDeprivation(Base):
|
|
|
|
|
"""IDACI deprivation index — one row per URN."""
|
|
|
|
|
__tablename__ = "fact_deprivation"
|
|
|
|
|
__table_args__ = MARTS
|
2026-03-24 11:44:04 +00:00
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
2026-03-27 09:29:27 +00:00
|
|
|
lsoa_code = Column(String(20))
|
|
|
|
|
idaci_score = Column(Float)
|
|
|
|
|
idaci_decile = Column(Integer)
|
2026-03-24 11:44:04 +00:00
|
|
|
|
|
|
|
|
|
2026-03-27 09:29:27 +00:00
|
|
|
class FactFinance(Base):
|
|
|
|
|
"""FBIT financial benchmarking — one row per URN per year."""
|
|
|
|
|
__tablename__ = "fact_finance"
|
2026-03-24 11:44:04 +00:00
|
|
|
__table_args__ = (
|
2026-03-27 09:29:27 +00:00
|
|
|
Index("ix_finance_urn_year", "urn", "year"),
|
|
|
|
|
MARTS,
|
2026-03-24 11:44:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
urn = Column(Integer, primary_key=True)
|
|
|
|
|
year = Column(Integer, primary_key=True)
|
2026-03-27 09:29:27 +00:00
|
|
|
per_pupil_spend = Column(Float)
|
|
|
|
|
staff_cost_pct = Column(Float)
|
|
|
|
|
teacher_cost_pct = Column(Float)
|
2026-03-24 11:44:04 +00:00
|
|
|
support_staff_cost_pct = Column(Float)
|
|
|
|
|
premises_cost_pct = Column(Float)
|