MEGAINFRA-WEBAPP/scripts/backfill-retention-forecasts.ts

40 lines
2.8 KiB
TypeScript
Raw Normal View History

/** 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; });