/** * Client for the places API. * * Two namespaces, matching the backend: towns and localities share * /schools/[place]; authorities take /schools/authority/[la]. 67 town names * collide with an authority name and neither set contains the other, so one * namespace would publish near-duplicate pages. */ import type { School } from '@/lib/types'; export interface PlaceSummary { kind: string; slug: string; name: string; count: number; /** Phases that clear the threshold on their own, so the page links * variants that exist rather than 404s. Absent on the registry listing. */ phases?: string[]; } export interface PlaceDetail { place: PlaceSummary & { parent_authority: string | null }; schools: School[]; averages: { rwm_expected_pct: number | null; attainment_8_score: number | null; }; } export function placeUrl(kind: string, slug: string, phase?: string): string { const base = kind === 'authority' ? `/schools/authority/${slug}` : kind === 'outcode' ? `/schools/near/${slug}` : `/schools/${slug}`; return phase ? `${base}/${phase}` : base; } /** An authority name as it appears in a URL. */ export function authoritySlug(name: string): string { return name.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-'); } const API = process.env.FASTAPI_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'; export async function fetchPlaces(): Promise { const res = await fetch(`${API}/places`, { next: { revalidate: 604800 } }); if (!res.ok) return []; return (await res.json()).places ?? []; } export async function fetchPlace( kind: string, slug: string, phase?: string, ): Promise { const q = phase ? `?phase=${encodeURIComponent(phase)}` : ''; const res = await fetch(`${API}/places/${kind}/${slug}${q}`, { next: { revalidate: 604800 } }); if (!res.ok) return null; return res.json(); }