feat(data): integrate 9 UK government data sources via Kestra
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>
This commit is contained in:
@@ -77,7 +77,7 @@ export default async function SchoolPage({ params }: SchoolPageProps) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { school_info, yearly_data, absence_data } = data;
|
||||
const { school_info, yearly_data, absence_data, ofsted, parent_view, census, admissions, sen_detail, phonics, deprivation, finance } = data;
|
||||
|
||||
// Generate JSON-LD structured data for SEO
|
||||
const structuredData = {
|
||||
@@ -116,6 +116,14 @@ export default async function SchoolPage({ params }: SchoolPageProps) {
|
||||
schoolInfo={school_info}
|
||||
yearlyData={yearly_data}
|
||||
absenceData={absence_data}
|
||||
ofsted={ofsted ?? null}
|
||||
parentView={parent_view ?? null}
|
||||
census={census ?? null}
|
||||
admissions={admissions ?? null}
|
||||
senDetail={sen_detail ?? null}
|
||||
phonics={phonics ?? null}
|
||||
deprivation={deprivation ?? null}
|
||||
finance={finance ?? null}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -424,3 +424,120 @@
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Supplementary Data Sections ──────────────────────── */
|
||||
.supplementarySection {
|
||||
background: var(--bg-card, white);
|
||||
border: 1px solid var(--border-color, #e5dfd5);
|
||||
border-radius: 10px;
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.supplementarySubtitle {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted, #8a847a);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary, #5c564d);
|
||||
margin: 1.25rem 0 0.75rem;
|
||||
}
|
||||
|
||||
/* Ofsted */
|
||||
.ofstedHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ofstedGrade {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
border-radius: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ofstedGrade1 { background: rgba(45, 125, 125, 0.12); color: var(--accent-teal, #2d7d7d); }
|
||||
.ofstedGrade2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; }
|
||||
.ofstedGrade3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; }
|
||||
.ofstedGrade4 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); }
|
||||
|
||||
.ofstedDate {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted, #8a847a);
|
||||
}
|
||||
|
||||
.ofstedType {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted, #8a847a);
|
||||
margin-top: 0.5rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Parent View */
|
||||
.parentViewGrid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.parentViewRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.parentViewLabel {
|
||||
flex: 0 0 18rem;
|
||||
color: var(--text-secondary, #5c564d);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.parentViewBar {
|
||||
flex: 1;
|
||||
height: 0.5rem;
|
||||
background: var(--bg-secondary, #f3ede4);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.parentViewFill {
|
||||
height: 100%;
|
||||
background: var(--accent-teal, #2d7d7d);
|
||||
border-radius: 4px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
.parentViewPct {
|
||||
flex: 0 0 2.75rem;
|
||||
text-align: right;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #1a1612);
|
||||
}
|
||||
|
||||
/* Metric hint (small label below metricValue) */
|
||||
.metricHint {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted, #8a847a);
|
||||
margin-top: 0.25rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Mobile ──────────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
.supplementarySection {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.parentViewLabel {
|
||||
flex: 0 0 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,37 @@ import { useRouter } from 'next/navigation';
|
||||
import { useComparison } from '@/hooks/useComparison';
|
||||
import { PerformanceChart } from './PerformanceChart';
|
||||
import { SchoolMap } from './SchoolMap';
|
||||
import type { School, SchoolResult, AbsenceData } from '@/lib/types';
|
||||
import type {
|
||||
School, SchoolResult, AbsenceData,
|
||||
OfstedInspection, OfstedParentView, SchoolCensus,
|
||||
SchoolAdmissions, SenDetail, Phonics,
|
||||
SchoolDeprivation, SchoolFinance,
|
||||
} from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, calculateTrend } from '@/lib/utils';
|
||||
import styles from './SchoolDetailView.module.css';
|
||||
|
||||
const OFSTED_LABELS: Record<number, string> = {
|
||||
1: 'Outstanding', 2: 'Good', 3: 'Requires Improvement', 4: 'Inadequate',
|
||||
};
|
||||
|
||||
interface SchoolDetailViewProps {
|
||||
schoolInfo: School;
|
||||
yearlyData: SchoolResult[];
|
||||
absenceData: AbsenceData | null;
|
||||
ofsted: OfstedInspection | null;
|
||||
parentView: OfstedParentView | null;
|
||||
census: SchoolCensus | null;
|
||||
admissions: SchoolAdmissions | null;
|
||||
senDetail: SenDetail | null;
|
||||
phonics: Phonics | null;
|
||||
deprivation: SchoolDeprivation | null;
|
||||
finance: SchoolFinance | null;
|
||||
}
|
||||
|
||||
export function SchoolDetailView({ schoolInfo, yearlyData, absenceData }: SchoolDetailViewProps) {
|
||||
export function SchoolDetailView({
|
||||
schoolInfo, yearlyData, absenceData,
|
||||
ofsted, parentView, census, admissions, senDetail, phonics, deprivation, finance,
|
||||
}: SchoolDetailViewProps) {
|
||||
const router = useRouter();
|
||||
const { addSchool, removeSchool, isSelected } = useComparison();
|
||||
const isInComparison = isSelected(schoolInfo.urn);
|
||||
@@ -322,6 +342,209 @@ export function SchoolDetailView({ schoolInfo, yearlyData, absenceData }: School
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Ofsted Section */}
|
||||
{ofsted && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Ofsted Inspection</h2>
|
||||
<div className={styles.ofstedHeader}>
|
||||
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
|
||||
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'}
|
||||
</span>
|
||||
{ofsted.inspection_date && (
|
||||
<span className={styles.ofstedDate}>
|
||||
Inspected: {new Date(ofsted.inspection_date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ label: 'Quality of Education', value: ofsted.quality_of_education },
|
||||
{ label: 'Behaviour & Attitudes', value: ofsted.behaviour_attitudes },
|
||||
{ label: 'Personal Development', value: ofsted.personal_development },
|
||||
{ label: 'Leadership & Management', value: ofsted.leadership_management },
|
||||
...(ofsted.early_years_provision != null ? [{ label: 'Early Years', value: ofsted.early_years_provision }] : []),
|
||||
].map(({ label, value }) => value != null && (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
{OFSTED_LABELS[value]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{ofsted.inspection_type && (
|
||||
<p className={styles.ofstedType}>{ofsted.inspection_type}</p>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* What Parents Think */}
|
||||
{parentView && parentView.total_responses != null && parentView.total_responses > 0 && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>What Parents Think</h2>
|
||||
<p className={styles.supplementarySubtitle}>
|
||||
Based on {parentView.total_responses.toLocaleString()} parent responses to the Ofsted Parent View survey.
|
||||
</p>
|
||||
<div className={styles.parentViewGrid}>
|
||||
{[
|
||||
{ label: 'My child is happy here', pct: parentView.q_happy_pct },
|
||||
{ label: 'My child feels safe here', pct: parentView.q_safe_pct },
|
||||
{ label: 'Would recommend this school', pct: parentView.q_recommend_pct },
|
||||
{ label: 'Teaching is good', pct: parentView.q_teaching_pct },
|
||||
{ label: 'My child makes good progress', pct: parentView.q_progress_pct },
|
||||
{ label: 'School looks after wellbeing', pct: parentView.q_wellbeing_pct },
|
||||
{ label: 'Led and managed effectively', pct: parentView.q_leadership_pct },
|
||||
{ label: 'Behaviour is well managed', pct: parentView.q_behaviour_pct },
|
||||
{ label: 'Communicates well with parents', pct: parentView.q_communication_pct },
|
||||
].filter(q => q.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.parentViewRow}>
|
||||
<span className={styles.parentViewLabel}>{label}</span>
|
||||
<div className={styles.parentViewBar}>
|
||||
<div className={styles.parentViewFill} style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className={styles.parentViewPct}>{pct}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Admissions */}
|
||||
{admissions && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Admissions ({admissions.year})</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{admissions.published_admission_number != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Places available</div>
|
||||
<div className={styles.metricValue}>{admissions.published_admission_number}</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.total_applications != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Applications received</div>
|
||||
<div className={styles.metricValue}>{admissions.total_applications.toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.first_preference_offers_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Got first choice</div>
|
||||
<div className={styles.metricValue}>{admissions.first_preference_offers_pct}%</div>
|
||||
</div>
|
||||
)}
|
||||
{admissions.oversubscribed != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Oversubscribed</div>
|
||||
<div className={styles.metricValue}>{admissions.oversubscribed ? 'Yes' : 'No'}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Pupils & Inclusion (Census + SEN) */}
|
||||
{(census || senDetail) && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Pupils & Inclusion</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{census?.class_size_avg != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Average class size</div>
|
||||
<div className={styles.metricValue}>{census.class_size_avg.toFixed(1)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{senDetail && (
|
||||
<>
|
||||
<h3 className={styles.subSectionTitle}>Primary SEN Needs (latest year)</h3>
|
||||
<div className={styles.metricsGrid}>
|
||||
{[
|
||||
{ label: 'Speech & Language', pct: senDetail.primary_need_speech_pct },
|
||||
{ label: 'Autism (ASD)', pct: senDetail.primary_need_autism_pct },
|
||||
{ label: 'Learning Difficulties', pct: senDetail.primary_need_mld_pct },
|
||||
{ label: 'Specific Learning (Dyslexia etc.)', pct: senDetail.primary_need_spld_pct },
|
||||
{ label: 'Social, Emotional & Mental Health', pct: senDetail.primary_need_semh_pct },
|
||||
{ label: 'Physical / Sensory', pct: senDetail.primary_need_physical_pct },
|
||||
].filter(n => n.pct != null).map(({ label, pct }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={styles.metricValue}>{pct}%</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Year 1 Phonics */}
|
||||
{phonics && phonics.year1_phonics_pct != null && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Year 1 Phonics ({phonics.year})</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Reached expected standard</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year1_phonics_pct)}</div>
|
||||
</div>
|
||||
{phonics.year2_phonics_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Year 2 (re-takers) standard</div>
|
||||
<div className={styles.metricValue}>{formatPercentage(phonics.year2_phonics_pct)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Deprivation Context */}
|
||||
{deprivation && deprivation.idaci_decile != null && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Deprivation Context</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Area deprivation decile</div>
|
||||
<div className={styles.metricValue}>{deprivation.idaci_decile} / 10</div>
|
||||
<div className={styles.metricHint}>
|
||||
1 = most deprived, 10 = least deprived
|
||||
</div>
|
||||
</div>
|
||||
{deprivation.idaci_score != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>IDACI score</div>
|
||||
<div className={styles.metricValue}>{deprivation.idaci_score.toFixed(3)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Finances */}
|
||||
{finance && finance.per_pupil_spend != null && (
|
||||
<section className={styles.supplementarySection}>
|
||||
<h2 className={styles.sectionTitle}>Finances ({finance.year})</h2>
|
||||
<div className={styles.metricsGrid}>
|
||||
{finance.per_pupil_spend != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Spend per pupil</div>
|
||||
<div className={styles.metricValue}>£{Math.round(finance.per_pupil_spend).toLocaleString()}</div>
|
||||
</div>
|
||||
)}
|
||||
{finance.teacher_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>Teacher costs</div>
|
||||
<div className={styles.metricValue}>{finance.teacher_cost_pct.toFixed(1)}% of budget</div>
|
||||
</div>
|
||||
)}
|
||||
{finance.staff_cost_pct != null && (
|
||||
<div className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>All staff costs</div>
|
||||
<div className={styles.metricValue}>{finance.staff_cost_pct.toFixed(1)}% of budget</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -211,6 +211,23 @@
|
||||
color: var(--text-primary, #1a1612);
|
||||
}
|
||||
|
||||
/* ── Ofsted badge ────────────────────────────────────── */
|
||||
.ofstedBadge {
|
||||
display: inline-block;
|
||||
padding: 0.0625rem 0.375rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ofsted1 { background: rgba(45, 125, 125, 0.12); color: var(--accent-teal, #2d7d7d); }
|
||||
.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #3c8c3c; }
|
||||
.ofsted3 { background: rgba(201, 162, 39, 0.15); color: #b8920e; }
|
||||
.ofsted4 { background: rgba(224, 114, 86, 0.15); color: var(--accent-coral, #e07256); }
|
||||
|
||||
/* ── Mobile ──────────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
.row {
|
||||
|
||||
@@ -12,6 +12,13 @@ import { formatPercentage, formatProgress, calculateTrend } from '@/lib/utils';
|
||||
import { progressBand } from '@/lib/metrics';
|
||||
import styles from './SchoolRow.module.css';
|
||||
|
||||
const OFSTED_LABELS: Record<number, string> = {
|
||||
1: 'Outstanding',
|
||||
2: 'Good',
|
||||
3: 'Req. Improvement',
|
||||
4: 'Inadequate',
|
||||
};
|
||||
|
||||
interface SchoolRowProps {
|
||||
school: School;
|
||||
isLocationSearch?: boolean;
|
||||
@@ -46,7 +53,7 @@ export function SchoolRow({
|
||||
{/* Left: three content lines */}
|
||||
<div className={styles.rowContent}>
|
||||
|
||||
{/* Line 1: School name + type */}
|
||||
{/* Line 1: School name + type + Ofsted badge */}
|
||||
<div className={styles.line1}>
|
||||
<a href={`/school/${school.urn}`} className={styles.schoolName}>
|
||||
{school.school_name}
|
||||
@@ -54,6 +61,11 @@ export function SchoolRow({
|
||||
{school.school_type && (
|
||||
<span className={styles.schoolType}>{school.school_type}</span>
|
||||
)}
|
||||
{school.ofsted_grade && (
|
||||
<span className={`${styles.ofstedBadge} ${styles[`ofsted${school.ofsted_grade}`]}`}>
|
||||
{OFSTED_LABELS[school.ofsted_grade]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Line 2: Key stats */}
|
||||
|
||||
@@ -47,6 +47,102 @@ export interface School {
|
||||
|
||||
// Location search fields
|
||||
distance?: number | null;
|
||||
|
||||
// GIAS enrichment fields
|
||||
website?: string | null;
|
||||
headteacher_name?: string | null;
|
||||
capacity?: number | null;
|
||||
trust_name?: string | null;
|
||||
gender?: string | null;
|
||||
|
||||
// Ofsted (for list view — summary only)
|
||||
ofsted_grade?: 1 | 2 | 3 | 4 | null;
|
||||
ofsted_date?: string | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Supplementary Data Types (populated by Kestra data integrator)
|
||||
// ============================================================================
|
||||
|
||||
export interface OfstedInspection {
|
||||
overall_effectiveness: 1 | 2 | 3 | 4 | null;
|
||||
quality_of_education: number | null;
|
||||
behaviour_attitudes: number | null;
|
||||
personal_development: number | null;
|
||||
leadership_management: number | null;
|
||||
early_years_provision: number | null;
|
||||
previous_overall: number | null;
|
||||
inspection_date: string | null;
|
||||
inspection_type: string | null;
|
||||
}
|
||||
|
||||
export interface OfstedParentView {
|
||||
survey_date: string | null;
|
||||
total_responses: number | null;
|
||||
q_happy_pct: number | null;
|
||||
q_safe_pct: number | null;
|
||||
q_behaviour_pct: number | null;
|
||||
q_bullying_pct: number | null;
|
||||
q_communication_pct: number | null;
|
||||
q_progress_pct: number | null;
|
||||
q_teaching_pct: number | null;
|
||||
q_information_pct: number | null;
|
||||
q_curriculum_pct: number | null;
|
||||
q_future_pct: number | null;
|
||||
q_leadership_pct: number | null;
|
||||
q_wellbeing_pct: number | null;
|
||||
q_recommend_pct: number | null;
|
||||
q_sen_pct: number | null;
|
||||
}
|
||||
|
||||
export interface SchoolCensus {
|
||||
year: number;
|
||||
class_size_avg: number | null;
|
||||
ethnicity_white_pct: number | null;
|
||||
ethnicity_asian_pct: number | null;
|
||||
ethnicity_black_pct: number | null;
|
||||
ethnicity_mixed_pct: number | null;
|
||||
ethnicity_other_pct: number | null;
|
||||
}
|
||||
|
||||
export interface SchoolAdmissions {
|
||||
year: number;
|
||||
published_admission_number: number | null;
|
||||
total_applications: number | null;
|
||||
first_preference_offers_pct: number | null;
|
||||
oversubscribed: boolean | null;
|
||||
}
|
||||
|
||||
export interface SenDetail {
|
||||
year: number;
|
||||
primary_need_speech_pct: number | null;
|
||||
primary_need_autism_pct: number | null;
|
||||
primary_need_mld_pct: number | null;
|
||||
primary_need_spld_pct: number | null;
|
||||
primary_need_semh_pct: number | null;
|
||||
primary_need_physical_pct: number | null;
|
||||
primary_need_other_pct: number | null;
|
||||
}
|
||||
|
||||
export interface Phonics {
|
||||
year: number;
|
||||
year1_phonics_pct: number | null;
|
||||
year2_phonics_pct: number | null;
|
||||
}
|
||||
|
||||
export interface SchoolDeprivation {
|
||||
lsoa_code: string | null;
|
||||
idaci_score: number | null;
|
||||
idaci_decile: number | null;
|
||||
}
|
||||
|
||||
export interface SchoolFinance {
|
||||
year: number;
|
||||
per_pupil_spend: number | null;
|
||||
staff_cost_pct: number | null;
|
||||
teacher_cost_pct: number | null;
|
||||
support_staff_cost_pct: number | null;
|
||||
premises_cost_pct: number | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -152,6 +248,15 @@ export interface SchoolDetailsResponse {
|
||||
school_info: School;
|
||||
yearly_data: SchoolResult[];
|
||||
absence_data: AbsenceData | null;
|
||||
// Supplementary data (null until Kestra populates)
|
||||
ofsted: OfstedInspection | null;
|
||||
parent_view: OfstedParentView | null;
|
||||
census: SchoolCensus | null;
|
||||
admissions: SchoolAdmissions | null;
|
||||
sen_detail: SenDetail | null;
|
||||
phonics: Phonics | null;
|
||||
deprivation: SchoolDeprivation | null;
|
||||
finance: SchoolFinance | null;
|
||||
}
|
||||
|
||||
export interface ComparisonData {
|
||||
|
||||
Reference in New Issue
Block a user