scripts/loaders/miami/school_events.ts
Served verbatim from the project repository. Internal working document conventions apply: documents may reference file paths, branch names, and findings-ledger anchors from the repo.
// Load the hand-curated board-action table
// (data/cities/miami/reference/school_events.csv) → miami_school_events.
// Every row cites its document; the CSV is the audit trail and is replaced
// wholesale on each run (delete + insert), so edits to the CSV are the only
// way to change an event.
// Run: npx tsx scripts/loaders/miami/school_events.ts
import fs from "node:fs";
import path from "node:path";
import { parse } from "csv-parse/sync";
import { prisma, runMain } from "./_lib";
import { assertEventSchoolIdentity } from "./event-school-identity";
import type { MiamiEventAction } from "@prisma/client";
const CSV = path.join(process.cwd(), "data", "cities", "miami", "reference", "school_events.csv");
async function main() {
const rows = parse(fs.readFileSync(CSV, "utf-8"), { columns: true, skip_empty_lines: true }) as Record<string, string>[];
const schools = await prisma.miamiSchool.findMany({ select: { schoolNumber: true, name: true } });
const names = new Map(schools.map((school) => [school.schoolNumber, school.name]));
const data = [];
for (const r of rows) {
// Optional source name records which source identities have been checked. A
// mismatch stops the whole replacement before any existing event is deleted.
assertEventSchoolIdentity(r.school_number, r.source_school_name || "", names.get(r.school_number));
if (!names.has(r.school_number)) { console.warn(`[events] ${r.school_number} not in directory; skipped`); continue; }
data.push({
schoolNumber: r.school_number,
schoolYear: r.school_year,
action: r.action as MiamiEventAction,
detail: r.detail || null,
sourceDocUrl: r.source_doc_url || null,
sourcePage: r.source_page ? Number.parseInt(r.source_page, 10) : null,
boardItem: r.board_item || null,
decidedAt: r.decided_at ? new Date(`${r.decided_at}T00:00:00Z`) : null,
});
}
// Replace the table atomically: a failure mid-way leaves the previous rows in place.
await prisma.$transaction([prisma.miamiSchoolEvent.deleteMany({}), prisma.miamiSchoolEvent.createMany({ data })]);
console.log(`[events] loaded ${data.length} events from ${CSV}`);
}
runMain(main);