Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
184 lines
7.1 KiB
TypeScript
184 lines
7.1 KiB
TypeScript
/**
|
|
* Who goes there — the school's community from the latest census plus GIAS
|
|
* facts. Benchmark chips use the computed state-school averages and must
|
|
* carry their provenance wording (never "England average" for computed
|
|
* figures). Copy verbatim from the reviewed mockups.
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { verdict } from '@/lib/compareLogic';
|
|
import type { Benchmarks, ComparisonData, School } from '@/lib/types';
|
|
import { Cell, Chip, RowLabel, Section, SectionGrid, sectionStyles as s } from './sectionShared';
|
|
|
|
function pctSplit(part: number | null | undefined, total: number | null | undefined): string | null {
|
|
if (part == null || total == null || total === 0) return null;
|
|
return `${Math.round((part / total) * 100)}%`;
|
|
}
|
|
|
|
export function CompareCommunity({
|
|
schools,
|
|
data,
|
|
benchmarks,
|
|
}: {
|
|
schools: School[];
|
|
data: Record<string, ComparisonData>;
|
|
benchmarks?: Benchmarks;
|
|
}) {
|
|
const isSecondary = schools.some(
|
|
(school) => data[String(school.urn)]?.school_info?.attainment_8_score != null,
|
|
);
|
|
const bench = isSecondary ? benchmarks?.secondary : benchmarks?.primary;
|
|
|
|
const fsmChip = (value: number | null) => {
|
|
if (value == null || bench?.disadvantaged_pct == null) return null;
|
|
const v = verdict(value, bench.disadvantaged_pct, 3);
|
|
return (
|
|
<Chip tone="neutral">
|
|
{v === 'above' && 'Above the state-school average'}
|
|
{v === 'close' && 'About the state-school average'}
|
|
{v === 'below' && 'Below the state-school average'}
|
|
</Chip>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Section
|
|
title="Who goes there"
|
|
how="The school's community, from the latest school census. State-school averages are computed from our dataset and shown for context — there's no “right” number here."
|
|
>
|
|
<SectionGrid schools={schools}>
|
|
<RowLabel>Pupils on roll</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const info = data[String(school.urn)]?.school_info as (School & { gias_total_pupils?: number | null; capacity?: number | null }) | undefined;
|
|
const census = data[String(school.urn)]?.census;
|
|
const pupils = census?.total_pupils ?? info?.gias_total_pupils ?? null;
|
|
const capacity = info?.capacity ?? null;
|
|
let capNote: string | null = null;
|
|
if (pupils != null && capacity != null && capacity > 0) {
|
|
capNote =
|
|
pupils >= capacity
|
|
? `${capacity.toLocaleString('en-GB')} places — at or above capacity`
|
|
: `of ${capacity.toLocaleString('en-GB')} places (${Math.round((pupils / capacity) * 100)}% full)`;
|
|
}
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{pupils != null ? (
|
|
<>
|
|
{pupils.toLocaleString('en-GB')}
|
|
{capNote && <span className={s.small}>{capNote}</span>}
|
|
</>
|
|
) : (
|
|
<span className={s.small}>No data</span>
|
|
)}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel>Girls / boys</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const census = data[String(school.urn)]?.census;
|
|
const girls = pctSplit(census?.female_pupils, census?.total_pupils);
|
|
const boys = pctSplit(census?.male_pupils, census?.total_pupils);
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{girls && boys ? `${girls} / ${boys}` : <span className={s.small}>No data</span>}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel tip="% of pupils eligible for free school meals — a common measure of how many pupils come from lower-income families. Benchmark computed across state schools in our dataset.">
|
|
Free school meals
|
|
</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const fsm = data[String(school.urn)]?.census?.fsm_pct ?? null;
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{fsm != null ? (
|
|
<>
|
|
{Math.round(fsm)}% {fsmChip(fsm)}
|
|
</>
|
|
) : (
|
|
<span className={s.small}>No data</span>
|
|
)}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel tip="% of pupils whose first language is known or believed to be other than English. State-school average computed from our dataset.">
|
|
English as an additional language
|
|
</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const eal = data[String(school.urn)]?.census?.eal_pct ?? null;
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{eal != null ? `${Math.round(eal)}%` : <span className={s.small}>No data</span>}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel tip="% of pupils receiving SEN support (not including EHC plans). A high figure can mean the school hosts specialist provision — often a strength, not a warning sign. State-school average computed from our dataset.">
|
|
Extra learning support (SEN)
|
|
</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const rows = data[String(school.urn)]?.yearly_data ?? [];
|
|
let sen: number | null = null;
|
|
for (let r = rows.length - 1; r >= 0; r--) {
|
|
if (rows[r].sen_support_pct != null) {
|
|
sen = rows[r].sen_support_pct;
|
|
break;
|
|
}
|
|
}
|
|
const high =
|
|
sen != null && bench?.sen_support_pct != null && sen >= bench.sen_support_pct * 1.75;
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{sen != null ? (
|
|
<>
|
|
{Math.round(sen)}% {high && <Chip tone="neutral">Well above average</Chip>}
|
|
</>
|
|
) : (
|
|
<span className={s.small}>No data</span>
|
|
)}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel>Faith character</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const info = data[String(school.urn)]?.school_info;
|
|
const faith = info?.religious_denomination;
|
|
const none = !faith || faith === 'Does not apply' || faith === 'None';
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{none ? 'None' : faith}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel>Ages</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const info = data[String(school.urn)]?.school_info;
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{info?.age_range || <span className={s.small}>No data</span>}
|
|
</Cell>
|
|
);
|
|
})}
|
|
|
|
<RowLabel>Run by</RowLabel>
|
|
{schools.map((school, i) => {
|
|
const info = data[String(school.urn)]?.school_info;
|
|
const trust = info?.trust_name;
|
|
const la = info?.local_authority ?? school.local_authority;
|
|
return (
|
|
<Cell key={school.urn} school={school} index={i}>
|
|
{trust ? trust : la ? `${la} council` : <span className={s.small}>No data</span>}
|
|
</Cell>
|
|
);
|
|
})}
|
|
</SectionGrid>
|
|
</Section>
|
|
);
|
|
}
|