/** * Canonical systems — the GLOBAL area vocabulary every project's Systems can * map onto (milestone 307). * * The mapping is an ASSOCIATION, never a rename: a project's System keeps the * name the project gave it, and `canonical_id` only records which shared area * it is an instance of. An unmapped System is fully usable — the catalog is a * convergence aid, not a gate. */ import { apiGet, apiPost, apiPatch, apiPut } from "@/api/client"; export interface CanonicalSystem { id: number; name: string; /** The match key: lowercase, "&" folded to "and", punctuation collapsed. */ slug: string; description: string | null; order_index: number; created_at: string | null; updated_at: string | null; } /** * A suggested mapping. `basis` is the whole point of showing it: * - `exact` — the names differ only in spelling. Mechanical. * - `overlap` — they share a meaningful word. A judgment call the reviewer is * making, and it must never be presented as if it were the first. */ export interface CanonicalMatch { id: number; name: string; basis: "exact" | "overlap"; score?: number; } export interface MappingProposal { system_id: number; system_name: string; canonical_id: number; canonical_name: string; basis: "exact" | "overlap"; score: number; } export async function listCanonicalSystems(): Promise { const data = await apiGet<{ canonical_systems: CanonicalSystem[] }>( "/api/canonical-systems", ); return data.canonical_systems; } /** Admin only — a global list anyone can extend stops being shared. */ export async function createCanonicalSystem(data: { name: string; description?: string; }): Promise { return apiPost("/api/canonical-systems", data); } export async function updateCanonicalSystem( id: number, data: Partial<{ name: string; description: string; order_index: number }>, ): Promise { return apiPatch(`/api/canonical-systems/${id}`, data); } /** Proposals for a project's UNMAPPED Systems. Reads only — nothing applied. */ export async function proposeMappings(projectId: number): Promise { const data = await apiGet<{ proposals: MappingProposal[] }>( `/api/projects/${projectId}/canonical-proposals`, ); return data.proposals; } /** Apply or clear one mapping. `null` unmaps. */ export async function mapSystem( systemId: number, canonicalId: number | null, ): Promise<{ id: number; canonical_id: number | null }> { return apiPut(`/api/systems/${systemId}/canonical`, { canonical_id: canonicalId }); }