The header's Selective flag read school_info.admissions_policy, which the detail endpoint never sent, so no school page could flag Selective while its search row did (staging E2E: The Grammar School at Leeds). The detail payload now carries it, and a contract test checks it carries every field the header's flags read. Sending it would have switched on two older copies of the tag logic #176 fixed in the rows. The Admissions section and the cut-off note both tested includes('selective'), so every non-selective secondary would have read "entry is by selective examination". The section also counted "None" as a faith: Burntwood reads "a faith-based admissions priority (None)" today. All of them now share isSelective() and hasReligiousCharacter(), which also treats "Not applicable" as no faith, as the place table already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
/**
|
|
* The facts the school header and the search rows print, so a parent reads
|
|
* the same words in the list and on the page.
|
|
*
|
|
* Every value comes from one register field. Nothing is inferred or explained
|
|
* inline, and a missing field prints nothing.
|
|
*/
|
|
|
|
import type { School } from './types';
|
|
import { hasNurseryClasses, hasReligiousCharacter, isSelective, singleSexLabel } from './utils';
|
|
|
|
/** The search filter's type groups (backend/school_groups.py), without its
|
|
* parenthesised notes: fees have a flag of their own. */
|
|
const TYPE_GROUP_LABELS: Record<string, string> = {
|
|
state: 'State school',
|
|
independent: 'Independent school',
|
|
special: 'Special school (SEND)',
|
|
post16: 'Sixth form or college',
|
|
alternative: 'Alternative provision',
|
|
};
|
|
|
|
/** The school's type in the search filter's terms, or the register's own name
|
|
* for a type in no group (secure units, online providers). */
|
|
export function schoolTypeLabel(school: Pick<School, 'type_group' | 'school_type'>): string | null {
|
|
const grouped = school.type_group ? TYPE_GROUP_LABELS[school.type_group] : undefined;
|
|
return grouped ?? (school.school_type?.trim() || null);
|
|
}
|
|
|
|
/** "condition": changes who can apply or what it costs.
|
|
* "provision": what the school offers. */
|
|
export type SchoolFlagKind = 'condition' | 'provision';
|
|
|
|
export interface SchoolFlag {
|
|
label: string;
|
|
kind: SchoolFlagKind;
|
|
}
|
|
|
|
type FlagFields = Pick<
|
|
School,
|
|
'type_group' | 'admissions_policy' | 'gender' | 'religious_denomination'
|
|
| 'nursery_provision' | 'has_sixth_form' | 'phase'
|
|
>;
|
|
|
|
/**
|
|
* Flags for what applies, in a fixed order: conditions first, then provision.
|
|
*
|
|
* Selective needs the exact value. The register files a partly selective
|
|
* school as "Non-selective", so that value is never printed.
|
|
*/
|
|
export function schoolFlags(school: FlagFields): SchoolFlag[] {
|
|
const flags: SchoolFlag[] = [];
|
|
const condition = (label: string) => flags.push({ label, kind: 'condition' });
|
|
const provision = (label: string) => flags.push({ label, kind: 'provision' });
|
|
const phase = school.phase?.trim().toLowerCase();
|
|
|
|
if (school.type_group === 'independent') condition('Fee-paying');
|
|
if (isSelective(school.admissions_policy)) condition('Selective');
|
|
const singleSex = singleSexLabel(school.gender);
|
|
if (singleSex) condition(singleSex);
|
|
if (hasReligiousCharacter(school.religious_denomination)) {
|
|
condition(school.religious_denomination!.trim());
|
|
}
|
|
if (hasNurseryClasses(school.nursery_provision) && phase !== 'nursery') provision('Nursery class');
|
|
if (school.has_sixth_form && phase !== '16 plus') provision('Sixth form');
|
|
return flags;
|
|
}
|