/** Run with npx tsx --env-file=.env.local scripts/backfill-retention-forecasts.ts [--apply]. Defaults to a read-only preview. */ import { pbAdmin } from "../src/lib/pb"; import { buildManagementBoard } from "../src/lib/management/model"; import { planRowFromRecord, projectPlanRowFromRecord, projectRowFromRecord, rowsToSnapshot } from "../src/lib/management/store"; import { matchRetentionForecasts } from "../src/lib/management/match-retention-forecasts"; async function main() { const args = process.argv.slice(2); if (args.some((arg) => arg !== "--apply")) throw new Error("Only --apply is supported"); const apply = args.includes("--apply"); const pb = await pbAdmin(); const [plans, projectRecords] = await Promise.all([pb.collection("management_plans").getFullList({ sort: "plan_year" }), pb.collection("management_projects").getFullList()]); const projects = projectRecords.map((p) => projectRowFromRecord(p)); let changed = 0; for (const record of plans) { const plan = planRowFromRecord(record); const records = await pb.collection("management_project_plans").getFullList({ filter: `plan = "${plan.id}"` }); const board = buildManagementBoard(rowsToSnapshot({ plan, projects, projectPlans: records.map((r) => projectPlanRowFromRecord(r)), retentions: [] })); const result = matchRetentionForecasts(board, new Date().toISOString(), "Retention forecast backfill requested by user"); console.log(JSON.stringify({ year: plan.plan_year, retentionCells: result.audit.length, mode: apply ? "apply" : "preview" })); if (!result.audit.length) continue; changed += result.audit.length; if (!apply) continue; const fresh = await pb.collection("management_plans").getOne(plan.id); if ((Number(fresh.revision) || 1) !== plan.revision) throw new Error(`Plan ${plan.plan_year} changed; no update made to that plan`); const history = fresh.history && typeof fresh.history === "object" ? fresh.history : {}; await pb.collection("management_plans").update(plan.id, { history: { ...history, retentionEdits: result.retentionEdits, retentionAudit: [...(plan.history.retentionAudit ?? []), ...result.audit] }, revision: plan.revision + 1, }); const saved = planRowFromRecord(await pb.collection("management_plans").getOne(plan.id)); for (const entry of result.audit) { const value = saved.history.retentionEdits?.[entry.project]?.[String(entry.month)]; if (!value || value.forecast !== entry.value.actual || value.actual !== entry.value.actual) throw new Error(`Could not verify ${plan.plan_year} retention edit`); } } console.log(JSON.stringify({ totalRetentionCells: changed, applied: apply })); } main().catch(() => { console.error("Retention backfill failed. No income or cost records were changed."); process.exitCode = 1; });