Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
/**
|
||
* Getting a place — admissions framed the way the expert review requires:
|
||
* total applications are "named on N forms" (any preference rank, not
|
||
* head-to-head), one consistent chip metric (first-preference success),
|
||
* equal-preference and offers-vs-intake explanations up front.
|
||
*/
|
||
|
||
'use client';
|
||
|
||
import { summariseAdmissions } from '@/lib/compareLogic';
|
||
import type { ComparisonData, School } from '@/lib/types';
|
||
import { CHART_COLORS } from '@/lib/utils';
|
||
import { Cell, Chip, RowLabel, Section, SectionGrid, sectionStyles as s } from './sectionShared';
|
||
|
||
export function CompareAdmissions({
|
||
schools,
|
||
data,
|
||
}: {
|
||
schools: School[];
|
||
data: Record<string, ComparisonData>;
|
||
}) {
|
||
const rows = schools.map((school) => data[String(school.urn)]?.admissions ?? null);
|
||
const anyData = rows.some(Boolean);
|
||
const entryYear = rows.find(Boolean)?.year;
|
||
const entryLabel = entryYear
|
||
? `September ${String(entryYear).slice(0, 4)} entry`
|
||
: 'the most recent admissions round';
|
||
|
||
if (!anyData) {
|
||
return (
|
||
<Section title="Getting a place" how="No admissions data is available for these schools yet.">
|
||
<></>
|
||
</Section>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Section
|
||
title="Getting a place"
|
||
how={
|
||
<>
|
||
From the most recent admissions round ({entryLabel}). "First choice" means
|
||
families who ranked the school top of their application form — officially a "first
|
||
preference". Schools never see your ranking: places are decided only by the
|
||
school's admission criteria, so listing a school lower down never hurts your chances.
|
||
These are National Offer Day offers — waiting lists and appeals can change the final
|
||
intake.
|
||
</>
|
||
}
|
||
>
|
||
<SectionGrid schools={schools}>
|
||
<RowLabel tip="How many application forms named the school at any preference rank — not the number of families competing head-to-head for a place.">
|
||
Interest in the school
|
||
</RowLabel>
|
||
{schools.map((school, i) => {
|
||
const a = rows[i];
|
||
return (
|
||
<Cell key={school.urn} school={school} index={i}>
|
||
{a?.total_applications != null && a?.places_offered != null ? (
|
||
<>
|
||
Named on <strong>{a.total_applications.toLocaleString('en-GB')}</strong> forms ·{' '}
|
||
<strong>{a.places_offered.toLocaleString('en-GB')}</strong> places
|
||
</>
|
||
) : (
|
||
<span className={s.small}>No data</span>
|
||
)}
|
||
</Cell>
|
||
);
|
||
})}
|
||
|
||
<RowLabel>First-choice families offered a place</RowLabel>
|
||
{schools.map((school, i) => {
|
||
const summary = summariseAdmissions(rows[i]);
|
||
return (
|
||
<Cell key={school.urn} school={school} index={i}>
|
||
{summary.firstPrefPct != null ? (
|
||
<>
|
||
<strong>{summary.firstPrefPct}%</strong>{' '}
|
||
{summary.chip && summary.chip.tone === 'warn' && (
|
||
<Chip tone="warn">{summary.chip.text}</Chip>
|
||
)}
|
||
<span className={s.barMini}>
|
||
<i
|
||
style={{
|
||
width: `${summary.firstPrefPct}%`,
|
||
background: CHART_COLORS[i % CHART_COLORS.length],
|
||
}}
|
||
/>
|
||
</span>
|
||
</>
|
||
) : (
|
||
<span className={s.small}>No data</span>
|
||
)}
|
||
</Cell>
|
||
);
|
||
})}
|
||
|
||
<RowLabel>What this means</RowLabel>
|
||
{schools.map((school, i) => {
|
||
const a = rows[i];
|
||
const summary = summariseAdmissions(a);
|
||
let text: string | null = null;
|
||
if (summary.firstPrefPct != null) {
|
||
if (summary.firstPrefPct >= 100) {
|
||
text = `Every family who put ${school.school_name} first got a place.`;
|
||
} else if (summary.firstPrefPct >= 90) {
|
||
text = `Nearly every family who put ${school.school_name} first got a place.`;
|
||
} else if (a?.oversubscribed) {
|
||
text =
|
||
'More first-choice applications than places — check the school’s admission criteria (for most non-faith primaries, distance decides).';
|
||
} else {
|
||
text = `${summary.firstPrefPct}% of first-choice families received an offer.`;
|
||
}
|
||
}
|
||
return (
|
||
<Cell key={school.urn} school={school} index={i}>
|
||
{text ? <span className={s.small}>{text}</span> : <span className={s.small}>—</span>}
|
||
</Cell>
|
||
);
|
||
})}
|
||
</SectionGrid>
|
||
</Section>
|
||
);
|
||
}
|