diff --git a/mcp-server/index.ts b/mcp-server/index.ts index 26f3666..d91a65c 100644 --- a/mcp-server/index.ts +++ b/mcp-server/index.ts @@ -1061,19 +1061,47 @@ const CITIES: Record = { 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(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, }); } diff --git a/mcp-server/search.html b/mcp-server/search.html index da5004d..bb6e1fb 100644 --- a/mcp-server/search.html +++ b/mcp-server/search.html @@ -209,25 +209,36 @@ function addContractInsight(parent,c,isUrgent){ var cd=document.createElement('div');cd.style.cssText='background:#0d1117;border-radius:8px;padding:12px;margin-bottom:8px'; // Urgent reason banner — explain WHY this is urgent - if(isUrgent){ - var reasons=['Client called last night — needs workers by morning', - 'Short notice fill — original crew cancelled', - 'Production surge — client doubled their headcount', - 'Emergency coverage — 2 no-shows reported', - 'Rush order — client needs bodies on site ASAP', - 'Replacement needed — previous worker reassigned']; - var reason=reasons[Math.floor(Math.random()*reasons.length)]; + // Scenario banner — shows for ALL contracts, not just urgent + if(c.notes||c.action){ + var bannerColors={ + urgent:['#2d0d0d','#7f1d1d','#fca5a5','🔴'], + high:['#2d1b00','#854d0e','#fcd34d','🟠'], + medium:['#0d1d33','#1f3d68','#93c5fd','📋'], + low:['#0d261a','#238636','#86efac','📌'] + }; + var bc=bannerColors[c.priority]||bannerColors.medium; var banner=document.createElement('div'); - banner.style.cssText='background:#2d0d0d;border:1px solid #7f1d1d;border-radius:6px;padding:10px 12px;margin-bottom:10px;display:flex;align-items:flex-start;gap:8px'; - var icon=document.createElement('span');icon.style.cssText='font-size:16px;flex-shrink:0';icon.textContent='🔴'; + banner.style.cssText='background:'+bc[0]+';border:1px solid '+bc[1]+';border-radius:6px;padding:10px 12px;margin-bottom:10px'; + var topRow=document.createElement('div');topRow.style.cssText='display:flex;align-items:flex-start;gap:8px'; + var icon=document.createElement('span');icon.style.cssText='font-size:14px;flex-shrink:0';icon.textContent=bc[3]; var bannerText=document.createElement('div'); - var reasonLine=document.createElement('div');reasonLine.style.cssText='color:#fca5a5;font-size:12px;font-weight:600';reasonLine.textContent=reason; - var actionLine=document.createElement('div');actionLine.style.cssText='color:#8b949e;font-size:11px;margin-top:2px'; + var reasonLine=document.createElement('div');reasonLine.style.cssText='color:'+bc[2]+';font-size:12px;font-weight:600'; + reasonLine.textContent=c.notes||''; + bannerText.appendChild(reasonLine); + if(c.action){ + var actionLine=document.createElement('div');actionLine.style.cssText='color:#8b949e;font-size:11px;margin-top:2px'; + actionLine.textContent=c.action; + bannerText.appendChild(actionLine); + } var unfilled=c.headcount-c.filled; - if(unfilled>0)actionLine.textContent='Need '+unfilled+' more worker'+(unfilled>1?'s':'')+' — see suggested replacements below'; - else actionLine.textContent='All '+c.headcount+' positions matched — confirm workers and send shift details now'; - bannerText.appendChild(reasonLine);bannerText.appendChild(actionLine); - banner.appendChild(icon);banner.appendChild(bannerText);cd.appendChild(banner); + if(unfilled>0){ + var gapLine=document.createElement('div');gapLine.style.cssText='color:'+bc[2]+';font-size:11px;margin-top:4px;font-weight:500'; + gapLine.textContent='→ Need '+unfilled+' more worker'+(unfilled>1?'s':'')+' — see matches below'; + bannerText.appendChild(gapLine); + } + topRow.appendChild(icon);topRow.appendChild(bannerText);banner.appendChild(topRow); + cd.appendChild(banner); } var hdr=document.createElement('div');hdr.style.cssText='display:flex;justify-content:space-between;align-items:center;margin-bottom:8px';