import { DECISION_COLUMNS, HIRING_COLUMNS, type DecisionRow, type Grid, type HiringRow } from "./types"; export type ParseResult = { ok: true; rows: T[] } | { ok: false; missing: string[] }; const norm = (s: unknown) => String(s ?? "").trim().toLowerCase(); function headerIndex(header: string[], names: Record, lenient: boolean) { const idx: Record = {}; const missing: string[] = []; const keys = Object.keys(names); keys.forEach((key) => { const at = header.findIndex((h) => norm(h) === norm(names[key])); if (at >= 0) idx[key] = at; else if (lenient) idx[key] = -1; // negative control only: a missing column reads as empty, with no error else missing.push(names[key]); }); return { idx, missing }; } /** * Reads the hiring sheet by column NAME. Blank rows are skipped. With `schemaCheck` * off (tests only) there is no check, which is the * failure the check exists to prevent: a renamed column reads as empty cells, and every * arrival waits for an address that is in fact there, with no error anywhere. */ export function parseHiring(grid: Grid, schemaCheck = true): ParseResult { if (!grid.length) return { ok: false, missing: Object.values(HIRING_COLUMNS) }; const { idx, missing } = headerIndex(grid[0], HIRING_COLUMNS, !schemaCheck); if (missing.length) return { ok: false, missing }; const cell = (r: string[], k: string) => String(r[idx[k]] ?? "").trim(); const rows: HiringRow[] = []; grid.slice(1).forEach((r, i) => { if (r.every((c) => String(c ?? "").trim() === "")) return; rows.push({ rowNumber: i + 2, arrivalId: cell(r, "arrivalId"), therapist: cell(r, "therapist"), status: String(r[idx.status] ?? ""), // kept raw: normalisation is a rule, see rules.ts startDate: cell(r, "startDate"), welcomeEmail: cell(r, "welcomeEmail"), approvedBy: cell(r, "approvedBy"), }); }); return { ok: true, rows }; } export function parseDecisions(grid: Grid): ParseResult { if (!grid.length) return { ok: false, missing: Object.values(DECISION_COLUMNS) }; const { idx, missing } = headerIndex(grid[0], DECISION_COLUMNS, false); if (missing.length) return { ok: false, missing }; const rows: DecisionRow[] = []; for (const r of grid.slice(1)) { const arrivalId = String(r[idx.arrivalId] ?? "").trim(); const decision = String(r[idx.decision] ?? "").trim(); if (!arrivalId && !decision) continue; rows.push({ arrivalId, decision, by: String(r[idx.by] ?? "").trim() }); } return { ok: true, rows }; }