Diverse scenario engine: 15 weighted staffing situations replace crisis-every-refresh

Simulation now uses weighted random selection across 4 priority tiers:
- Urgent (walkoff, quarantine, no-show), High (new client, cert expiry, expansion),
  Medium (recurring, seasonal, medical leave, cross-train), Low (future, exploratory)
- Color-coded scenario banners on ALL contracts, not just urgent
- Each scenario carries context (note) + recommended action

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-04-17 16:41:00 -05:00
co-authored by Claude Opus 4.6
parent e87155306b
commit be7436b6f0
2 changed files with 79 additions and 33 deletions
+52 -17
View File
@@ -1061,19 +1061,47 @@ const CITIES: Record<string, string[]> = {
WI: ["Milwaukee","Madison"], MI: ["Detroit","Grand Rapids"],
};
const CLIENTS = ["Midwest Logistics","Precision Mfg","Amazon DSP","CleanSpace","AutoParts Direct","Great Lakes Steel","Heartland Foods","Summit Packaging","Cardinal Health","TechFlow Assembly","River City Plastics","Prairie Wind Energy"];
const PRIORITIES = ["urgent","high","medium","medium","medium","low"];
const STARTS = ["5:00 AM","6:00 AM","6:30 AM","7:00 AM","7:30 AM","8:00 AM"];
const NOTES = [
"Warehouse expansion — need experienced workers",
"Peak season surge — client called last night",
"2nd shift, CNC preferred",
"Chemical plant — hazmat cert MANDATORY",
"ISO audit next week — need detail-oriented workers",
"Structural welding — experienced only",
"Regular fill — ongoing contract",
"Client doubled their order",
"Night shift coverage needed",
"Replacing 2 no-shows from yesterday",
// Diverse scenarios — each tells a different story about WHY this contract exists
const SCENARIOS = [
// URGENT — real emergencies that need immediate action
{ priority: "urgent", weight: 8, note: "Worker walked off the job at 3 PM yesterday — client needs replacement by morning",
situation: "walkoff", action: "Replacement needed ASAP — previous worker quit mid-shift" },
{ priority: "urgent", weight: 5, note: "Client emailed at 11 PM — their regular crew has COVID exposure, entire team quarantined",
situation: "quarantine", action: "Full crew replacement — health emergency at job site" },
{ priority: "urgent", weight: 5, note: "2 no-shows this morning — client is short-staffed on the floor right now",
situation: "noshow", action: "Immediate backfill — client waiting on the phone" },
// HIGH — important but not crisis
{ priority: "high", weight: 10, note: "New contract starting Monday — client wants to meet workers this week",
situation: "new_client", action: "New client onboarding — first impression matters" },
{ priority: "high", weight: 8, note: "Client expanding to 2nd shift — need additional crew by next week",
situation: "expansion", action: "Growth opportunity — client adding a shift" },
{ priority: "high", weight: 6, note: "Worker's OSHA certification expires Friday — need certified replacement lined up",
situation: "cert_expiry", action: "Cert compliance — current worker can't continue without renewal" },
{ priority: "high", weight: 5, note: "Client requested specific workers back from last month's project",
situation: "client_request", action: "Client relationship — they asked for specific people" },
// MEDIUM — standard day-to-day operations
{ priority: "medium", weight: 15, note: "Ongoing weekly fill — same client, same role, reliable pipeline",
situation: "recurring", action: "Recurring contract — steady work" },
{ priority: "medium", weight: 12, note: "Seasonal uptick — warehouse volume increasing ahead of holidays",
situation: "seasonal", action: "Seasonal planning — volume ramping up" },
{ priority: "medium", weight: 10, note: "Backfill for worker on approved medical leave — returns in 3 weeks",
situation: "medical_leave", action: "Temporary coverage — worker returning soon" },
{ priority: "medium", weight: 8, note: "Client testing new role — wants to try 2 workers for a week before committing",
situation: "trial", action: "Trial placement — client evaluating the role" },
{ priority: "medium", weight: 6, note: "Cross-training opportunity — client wants workers who can learn a new skill",
situation: "cross_train", action: "Development opportunity — workers can learn new skills" },
// LOW — planning ahead
{ priority: "low", weight: 10, note: "Future fill — project starts in 2 weeks, gathering candidates now",
situation: "future", action: "Pipeline building — no rush, quality over speed" },
{ priority: "low", weight: 8, note: "Client exploring staffing options — not committed yet, just want to see who's available",
situation: "exploratory", action: "Exploratory — client shopping, impress them with quality" },
{ priority: "low", weight: 5, note: "Internal transfer — moving a worker from one site to another, need replacement at original",
situation: "transfer", action: "Planned transition — smooth handoff between sites" },
];
function pick<T>(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; }
@@ -1095,10 +1123,16 @@ async function runWeekSimulation() {
const state = pick(STATES);
const city = pick(CITIES[state] || [state]);
const role = pick(ROLES);
const priority = pick(PRIORITIES) as string;
const headcount = priority === "urgent" ? 4 + Math.floor(Math.random() * 5) :
priority === "high" ? 3 + Math.floor(Math.random() * 3) :
2 + Math.floor(Math.random() * 3);
// Weighted scenario selection
const totalWeight = SCENARIOS.reduce((s, sc) => s + sc.weight, 0);
let r = Math.random() * totalWeight;
let scenario = SCENARIOS[0];
for (const sc of SCENARIOS) { r -= sc.weight; if (r <= 0) { scenario = sc; break; } }
const priority = scenario.priority;
const headcount = priority === "urgent" ? 3 + Math.floor(Math.random() * 4) :
priority === "high" ? 2 + Math.floor(Math.random() * 3) :
priority === "medium" ? 2 + Math.floor(Math.random() * 3) :
1 + Math.floor(Math.random() * 2);
const minRel = priority === "urgent" ? 0.6 : priority === "high" ? 0.75 : 0.8;
const cid = `W${d+1}-${String(c+1).padStart(3,"0")}`;
@@ -1132,7 +1166,8 @@ async function runWeekSimulation() {
contracts.push({
id: cid, client: pick(CLIENTS), role, state, city,
headcount, filled: Math.min(filled, headcount), priority,
start: pick(STARTS), notes: pick(NOTES), matches,
start: pick(STARTS), notes: scenario.note, situation: scenario.situation,
action: scenario.action, matches,
staffer, handoff_to: d < 4 ? handoffTo : null,
});
}