src/programs/snap-negative/seed-generator-workflow.ts
Linked repository file from this build.
import { captureReceivedSnapshot } from "@/features/reviews/received-snapshot";
import { autoGenerateMemo } from "@/features/reviews/downstream";
import { demoActor, demoRoster } from "@/lib/auth/roster";
import { actorRef } from "@/lib/auth/actor";
import { workflowLabels } from "@/features/reviews/types";
import { normalizeReviewAnswers } from "./field-catalog";
import type { Scenario, SnapReview } from "./types";
const fixtureMemoTypes = [
"under_threshold",
"info",
"timeliness_info",
"benefit_error",
"timeliness_finding",
] as const;
export function emptySeedScenario(): Scenario {
return {
populated: false,
householdSize: 0,
income: [],
medicalDeductionCents: 0,
dependentCareCents: 0,
childSupportCents: 0,
rentCents: 0,
taxesInsuranceCents: 0,
installationCents: 0,
otherShelterCents: 0,
sua: "none",
elderlyDisabled: false,
categoricalEligibility: false,
homeless: false,
};
}
/** Seed approved stages directly. Fixture choices do not define live PEC routing policy. */
export function seedWorkflow(review: SnapReview, asOf: string, variant: number) {
const examiner = actorRef(demoRoster.find((actor) => actor.staffId === review.examiner)!);
const supervisor = actorRef(demoRoster.find((actor) => actor.staffId === review.supervisor)!);
const manager = actorRef(demoActor("program_manager"));
const pec = actorRef(demoActor("pec"));
const date = review.facts.answers.dateCompleted || review.facts.answers.dateAssigned;
const at = new Date(
Math.min(Date.parse(`${date}T12:00:00.000Z`), Date.parse(asOf)),
).toISOString();
const id = `seed-${review.sampleMonth}-${review.reviewNumber}`;
review.facts.answers.dateCleared = "";
const imported = captureReceivedSnapshot(review);
imported.outcome = "";
delete imported.dropKind;
imported.facts.final = undefined;
imported.facts.members = [];
imported.facts.inputsRevision = 0;
imported.facts.answers = normalizeReviewAnswers({
actionType: review.facts.answers.actionType,
telephone: review.facts.answers.telephone,
actualAddress: review.facts.answers.actualAddress,
dateAssigned: review.facts.answers.dateAssigned,
});
imported.facts.scenarios = {
eligibility: emptySeedScenario(),
compI: emptySeedScenario(),
compII: emptySeedScenario(),
};
review.receivedSnapshots = { examiner: imported };
if (review.state !== "new_active")
review.receivedSnapshots.supervisor = captureReceivedSnapshot(review);
review.activity.push({
id: `${id}-stage`,
kind: "transition",
actor: examiner,
createdAt: at,
description: `Synthetic fixture: ${workflowLabels[review.state]}. Approval stages are test data, not a PEC defect classification.`,
to: review.state,
});
if (review.state === "needs_changes") {
const note = {
id: `${id}-correction`,
actor: supervisor,
createdAt: at,
body: "Synthetic correction request: verify the recorded reason against the case record, then resubmit.",
fieldLink: "recordedReason",
};
review.notes.push(note);
Object.assign(review.activity[0]!, {
actor: supervisor,
from: "awaiting_review",
noteId: note.id,
});
return;
}
if (review.state === "new_active" || review.state === "awaiting_review") return;
review.supervisorApproved = { actor: supervisor, at };
review.activity[0]!.actor = review.state === "awaiting_pec_approval" ? supervisor : manager;
if (review.outcome === "drop") {
review.receivedSnapshots.program_manager = captureReceivedSnapshot(review);
if (review.state === "dropped") {
review.dropApproved = { actor: manager, at };
review.facts.answers.dateCleared = at.slice(0, 10);
}
return;
}
if (review.state === "awaiting_pec_approval") return;
review.pecApproved = { actor: pec, at };
review.pecDecision = {
decision: "approve",
actor: pec,
createdAt: at,
policyNote: "Synthetic PEC approval for workflow testing. No live defect map is implied.",
};
review.receivedSnapshots.program_manager = captureReceivedSnapshot(review);
const memo = autoGenerateMemo(review, pec, { id: `${id}-memo`, at });
review.memos = [memo];
// Explicit fixture classification allows CAO/release screens to be exercised.
memo.type = fixtureMemoTypes[variant % fixtureMemoTypes.length];
memo.body = `SYNTHETIC TEST LETTER. Memo type is a fixture choice.\n\n${memo.body}`;
if (review.state === "awaiting_manager") return;
memo.outboundAt = at;
review.memoApproved = { actor: manager, at };
const deadline = new Date(at);
deadline.setUTCDate(deadline.getUTCDate() + (review.policy?.memoResponseDays ?? 30));
review.caoResponseDeadline = deadline.toISOString().slice(0, 10);
if (review.state === "awaiting_cao_response" && variant % 3 === 0) return;
const expired = review.caoResponseDeadline < asOf.slice(0, 10);
const status =
review.state === "released_to_reporting"
? expired && variant % 2 === 0
? "no_response"
: "agree"
: variant % 3 === 1
? "dispute"
: "agree";
review.caoResponse = {
status,
actor: manager,
at: asOf,
rootCause: status === "agree" ? "Synthetic case processing error." : "",
note: "Synthetic CAO response. The 30-day fallback deadline is a fixture setting.",
};
if (review.state === "released_to_reporting") {
review.activity[0]!.createdAt = asOf;
review.errorSummaryReleased = { actor: manager, at: asOf };
review.facts.answers.dateCleared = asOf.slice(0, 10);
}
}