/** * SimilarSchoolsSection — nearby schools of the same phase and a comparable * intake. Server component; only the carousel, the compare bar and the * add-to-compare button are client-side. * * The section is allowed to say exactly what the backend matched and no more. * The lede only claims a similar intake when no card came from tier 3, and a * card's chips list what that school actually shares rather than a match it * did not earn. * * There is deliberately no "how these are chosen" panel: the method is already * visible in the lede, the chips and the distances. The single caption line is * not a method note — it is the one thing a card cannot self-correct. */ import Link from 'next/link'; import type { SimilarSchool } from '@/lib/types'; import { schoolUrl } from '@/lib/utils'; import { AddToCompareButton } from './AddToCompareButton'; import { SimilarSchoolsCarousel } from './SimilarSchoolsCarousel'; import { SimilarSchoolsCompareBar } from './SimilarSchoolsCompareBar'; import { Section } from './sectionShared'; import styles from './SimilarSchools.module.css'; const MINIMUM = 2; export function shouldRenderSimilar(similar?: SimilarSchool[] | null): boolean { return (similar?.length ?? 0) >= MINIMUM; } function metricLabel(key: string): string { return key === 'attainment_8_score' ? 'Attainment 8' : 'Reading, writing & maths'; } function formatMetric(value: number | null, key: string): string { if (value == null) return 'Not published'; return key === 'attainment_8_score' ? value.toFixed(1) : `${Math.round(value)}%`; } export function SimilarSchoolsSection({ urn, schoolName, phaseNoun, thisMetricValue, similar, }: { urn: number; schoolName: string; phaseNoun: string; thisMetricValue: number | null; similar?: SimilarSchool[] | null; }) { if (!shouldRenderSimilar(similar)) return null; const schools = similar as SimilarSchool[]; // One card matched on phase alone, so the section may not claim the set // shares an intake with this school. const loosest = Math.max(...schools.map((s) => s.tier)); const metricKey = schools[0].metric_key; return (

Similar schools nearby

{loosest >= 3 ? `Other ${phaseNoun} schools near ${schoolName}.` : `Other ${phaseNoun} schools near ${schoolName}, with a similar intake.`}

} > {schools.map((school) => (
  • {school.distance_miles} miles away

    {school.school_name}

    {[school.school_type, school.age_range && `Ages ${school.age_range}`] .filter(Boolean) .join(' · ')}

      {school.shared.map((label) => (
    • = 3 ? styles.chipLoose : styles.chip}> {label}
    • ))}

    {formatMetric(school.metric_value, school.metric_key)}

    {metricLabel(school.metric_key)}

    {thisMetricValue != null && (

    {formatMetric(thisMetricValue, metricKey)} at this school

    )}
  • ))}
    {/* The one caveat the cards cannot make on their own: a reader who takes "0.6 miles away" for the walk has been misled, and nothing else here corrects that. */}

    Distances are straight-line from this school, not road distance.

    ); }