diff --git a/scripts/kb_staffer_report.py b/scripts/kb_staffer_report.py new file mode 100755 index 0000000..5d8bee2 --- /dev/null +++ b/scripts/kb_staffer_report.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Phase 23 staffer leaderboard + cross-staffer pattern finder. + +Reads data/_kb/staffers.jsonl + outcomes.jsonl + signatures.jsonl and +emits: + - Leaderboard sorted by competence_score + - Per-staffer breakdown (fill rate, turns, citations, rescue rate) + - Cross-staffer common workers: names endorsed by multiple top + staffers on similar signatures — candidates for "auto-discovered + high-value worker" labels. + +Run after scripts/run_staffer_demo.sh completes. +""" +import json +import os +from collections import defaultdict, Counter +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +KB = ROOT / "data" / "_kb" +PLAYBOOKS = ROOT / "tests" / "multi-agent" / "playbooks" + + +def load_jsonl(p): + if not p.exists(): + return [] + out = [] + for line in p.read_text().splitlines(): + if line.strip(): + try: + out.append(json.loads(line)) + except json.JSONDecodeError: + pass + return out + + +def main(): + staffers = load_jsonl(KB / "staffers.jsonl") + outcomes = load_jsonl(KB / "outcomes.jsonl") + + if not staffers: + print("(no staffer stats yet — run scripts/run_staffer_demo.sh first)") + return + + staffers.sort(key=lambda s: -s["competence_score"]) + + print("=== STAFFER LEADERBOARD ===") + print(f"{'rank':4s} {'id':7s} {'name':16s} {'role':8s} {'mo':>4s} {'runs':>4s} {'fill':>6s} {'turns':>6s} {'cites':>6s} {'rescue':>7s} {'score':>6s}") + print("-" * 100) + for i, s in enumerate(staffers): + rank_mark = "★" if i == 0 else " " + print(f"{rank_mark}{i+1:3d} {s['id']:7s} {s['name']:16s} {s['role']:8s} {s['tenure_months']:>4d} {s['total_runs']:>4d} {s['fill_rate']*100:>5.1f}% {s['avg_turns_per_event']:>6.1f} {s['avg_citations_per_run']:>6.2f} {s['rescue_rate']*100:>6.1f}% {s['competence_score']:>6.3f}") + print() + + # Cross-staffer pattern — workers endorsed on the same sig_hash + # across multiple staffers. Auto-discovered "reliable performers". + print("=== CROSS-STAFFER WORKER OVERLAP ===") + print("(workers endorsed on same sig_hash by ≥2 staffers)") + worker_touches = defaultdict(lambda: {"staffers": set(), "sigs": set(), "endorsements": 0}) + for o in outcomes: + run_dir = PLAYBOOKS / o["run_id"] + results_file = run_dir / "results.json" + if not results_file.exists(): + continue + try: + results = json.loads(results_file.read_text()) + except Exception: + continue + staffer_id = o.get("staffer", {}).get("id") + if not staffer_id: + continue + for r in results: + if not r.get("ok"): + continue + for f in r.get("fills", []): + name = f.get("name") + if not name or name.startswith("Candidate "): + continue + key = (name, r["event"]["role"], r["event"]["city"], r["event"]["state"]) + worker_touches[key]["staffers"].add(staffer_id) + worker_touches[key]["sigs"].add(o["sig_hash"]) + worker_touches[key]["endorsements"] += 1 + shared = sorted( + [(k, v) for k, v in worker_touches.items() if len(v["staffers"]) >= 2], + key=lambda x: -x[1]["endorsements"], + ) + if shared: + for (name, role, city, state), v in shared[:15]: + print(f" {name:25s} {role:22s} {city:15s} {state}: {v['endorsements']} endorsements across {len(v['staffers'])} staffers") + else: + print(" (none yet — needs ≥2 staffers on overlapping scenarios)") + print() + + # Competence differential — how much does top vs bottom differ? + if len(staffers) >= 2: + top = staffers[0] + bot = staffers[-1] + print(f"=== TOP vs BOTTOM DIFFERENTIAL ===") + print(f"{top['name']} (top) vs {bot['name']} (bottom):") + print(f" fill rate: {top['fill_rate']*100:.1f}% vs {bot['fill_rate']*100:.1f}% (Δ {(top['fill_rate']-bot['fill_rate'])*100:+.1f}pt)") + print(f" avg turns: {top['avg_turns_per_event']:.1f} vs {bot['avg_turns_per_event']:.1f} (Δ {top['avg_turns_per_event']-bot['avg_turns_per_event']:+.1f})") + print(f" avg citations: {top['avg_citations_per_run']:.2f} vs {bot['avg_citations_per_run']:.2f} (Δ {top['avg_citations_per_run']-bot['avg_citations_per_run']:+.2f})") + print(f" rescue rate: {top['rescue_rate']*100:.1f}% vs {bot['rescue_rate']*100:.1f}%") + print(f" competence: {top['competence_score']:.3f} vs {bot['competence_score']:.3f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_staffer_demo.sh b/scripts/run_staffer_demo.sh new file mode 100755 index 0000000..46e436c --- /dev/null +++ b/scripts/run_staffer_demo.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Run the 12 staffer-demo scenarios (4 personas × 3 contracts) with +# cloud T3 enabled. After each run, KB indexes the outcome and +# recomputes that staffer's competence_score. By the end, the top +# staffer's playbooks will be surfacing first in neighbor retrieval. + +set -e +cd "$(dirname "$0")/.." + +export OLLAMA_CLOUD_KEY="$(python3 -c "import json; print(json.load(open('/root/llm_team_config.json'))['providers']['ollama_cloud']['api_key'])" 2>/dev/null || echo '')" + +MANIFEST="tests/multi-agent/scenarios/staffer_demo/manifest.json" +if [ ! -f "$MANIFEST" ]; then + echo "✗ no manifest — run: bun tests/multi-agent/gen_staffer_demo.ts" + exit 1 +fi + +START_TS=$(date -Iseconds) +LOG_DIR="/tmp/lakehouse_staffer_demo_$(date +%s)" +mkdir -p "$LOG_DIR" +echo "▶ Staffer demo start: $START_TS, logs → $LOG_DIR" + +python3 -c " +import json +m = json.load(open('$MANIFEST')) +for s in m['scenarios']: + print(s['file'], '|', s['staffer'], '|', s['contract']) +" | while IFS='|' read -r SCEN STAFFER CONTRACT; do + SCEN=$(echo "$SCEN" | xargs) + STAFFER=$(echo "$STAFFER" | xargs) + CONTRACT=$(echo "$CONTRACT" | xargs) + SPEC="tests/multi-agent/scenarios/staffer_demo/$SCEN" + BASE=$(basename "$SPEC" .json) + LOG="$LOG_DIR/${BASE}.log" + echo " ▶ $STAFFER × $CONTRACT" + LH_OVERVIEW_CLOUD=1 bun tests/multi-agent/scenario.ts "$SPEC" > "$LOG" 2>&1 || true + OK=$(grep -oP '\d+/\d+ events succeeded' "$LOG" | tail -1 || echo "no-result") + RESCUES=$(grep -c "cloud rescue requested" "$LOG" || true) + RESCUE_OK=$(grep -c "retry outcome: ✓" "$LOG" || true) + SIG=$(grep -oP 'KB indexed: sig=\K[a-f0-9]+' "$LOG" | tail -1 || echo "-") + echo " → $OK; rescues=$RESCUES (${RESCUE_OK} succeeded); sig=$SIG" +done + +echo "▶ Staffer demo done: $(date -Iseconds)" +echo "▶ Staffer competence leaderboard:" +python3 scripts/kb_staffer_report.py 2>/dev/null || echo "(run scripts/kb_staffer_report.py after batch completes)" diff --git a/tests/multi-agent/gen_scenarios.ts b/tests/multi-agent/gen_scenarios.ts index 032c96e..21e63bf 100644 --- a/tests/multi-agent/gen_scenarios.ts +++ b/tests/multi-agent/gen_scenarios.ts @@ -147,7 +147,30 @@ function genSpec(rng: () => number, id: number): any { if (events.length > 0) e.replaces_event = events[0].at; events.push(e); } - return { client, date, events }; + + // Contract terms — most real staffing contracts have these. 70% of + // generated specs carry them so KB + T3 learn to reason about budget + // and radius trade-offs, not just geography. Distributions are + // deliberately varied: 2 week to 45 day deadlines, $22-$38/hr caps, + // 25-150mi local radii. + const contract = rng() > 0.3 ? (() => { + const deadlineDays = 10 + Math.floor(rng() * 35); + const deadlineDate = new Date(today.getTime() + (id + deadlineDays) * 86400000) + .toISOString().split("T")[0]; + const budgetPerHour = 22 + Math.floor(rng() * 17); + const bonusRadius = 25 + Math.floor(rng() * 125); + const bonusPerHour = 2 + Math.floor(rng() * 5); + const fill: "paramount" | "preferred" = rng() > 0.4 ? "paramount" : "preferred"; + return { + deadline: deadlineDate, + budget_per_hour_max: budgetPerHour, + local_bonus_per_hour: bonusPerHour, + local_bonus_radius_mi: bonusRadius, + fill_requirement: fill, + }; + })() : undefined; + + return { client, date, events, ...(contract ? { contract } : {}) }; } async function main() { diff --git a/tests/multi-agent/gen_staffer_demo.ts b/tests/multi-agent/gen_staffer_demo.ts new file mode 100644 index 0000000..847cd6b --- /dev/null +++ b/tests/multi-agent/gen_staffer_demo.ts @@ -0,0 +1,120 @@ +// Phase 23 demo — 4 staffer personas × 3 contract scenarios each. +// Same contracts run against different staffers to measure competence +// differential. After the batch, findNeighbors should rank top-staffer +// playbooks above junior-staffer playbooks for similar scenarios. +// +// Output: 12 spec files under tests/multi-agent/scenarios/staffer_demo/ + +import { mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +const STAFFERS = [ + { id: "S-001", name: "Maria Chen", tenure_months: 48, role: "senior" as const }, + { id: "S-002", name: "James Park", tenure_months: 14, role: "mid" as const }, + { id: "S-003", name: "Sam Torres", tenure_months: 4, role: "junior" as const }, + { id: "S-004", name: "Alex Rivera", tenure_months: 1, role: "trainee" as const }, +]; + +// Three contract shapes — one downtown assembly, one warehouse ramp, +// one emergency recovery. Different cities to vary the sig_hash. +const CONTRACTS = [ + { + tag: "nashville_downtown", + client: "Riverline Logistics — Nashville Downtown Build-Out", + city: "Nashville", state: "TN", + contract: { + deadline: "2026-05-19", + budget_total_usd: 180000, + budget_per_hour_max: 32, + local_bonus_per_hour: 4, + local_bonus_radius_mi: 75, + fill_requirement: "paramount" as const, + }, + events: [ + { kind: "baseline_fill", at: "07:00", role: "Welder", count: 4 }, + { kind: "expansion", at: "08:30", role: "Packaging Operator", count: 6 }, + { kind: "baseline_fill", at: "09:00", role: "Shipping Clerk", count: 2 }, + { kind: "emergency", at: "13:00", role: "Welder", count: 2, deadline: "15:00" }, + { kind: "misplacement", at: "15:30", role: "Packaging Operator", count: 1, replaces_event: "08:30" }, + ], + }, + { + tag: "joliet_warehouse", + client: "Midway Distribution — Joliet DC Ramp", + city: "Joliet", state: "IL", + contract: { + deadline: "2026-05-12", + budget_total_usd: 120000, + budget_per_hour_max: 28, + local_bonus_per_hour: 3, + local_bonus_radius_mi: 50, + fill_requirement: "preferred" as const, + }, + events: [ + { kind: "baseline_fill", at: "07:00", role: "Warehouse Associate", count: 5 }, + { kind: "recurring", at: "10:00", role: "Forklift Operator", count: 3 }, + { kind: "expansion", at: "12:30", role: "Picker", count: 4 }, + { kind: "misplacement", at: "15:00", role: "Forklift Operator", count: 1, replaces_event: "10:00" }, + ], + }, + { + tag: "indianapolis_assembly", + client: "Pioneer Assembly — Indianapolis Plant Expansion", + city: "Indianapolis", state: "IN", + contract: { + deadline: "2026-05-26", + budget_total_usd: 220000, + budget_per_hour_max: 30, + local_bonus_per_hour: 5, + local_bonus_radius_mi: 60, + fill_requirement: "paramount" as const, + }, + events: [ + { kind: "baseline_fill", at: "07:30", role: "Assembler", count: 6 }, + { kind: "recurring", at: "09:30", role: "Quality Tech", count: 2 }, + { kind: "expansion", at: "11:00", role: "Machine Operator", count: 5 }, + { kind: "emergency", at: "14:00", role: "Machine Operator", count: 3, deadline: "16:00" }, + { kind: "misplacement", at: "16:00", role: "Assembler", count: 1, replaces_event: "07:30" }, + ], + }, +]; + +async function main() { + const outDir = "tests/multi-agent/scenarios/staffer_demo"; + await mkdir(outDir, { recursive: true }); + const manifest: Array<{ file: string; staffer: string; contract: string; client: string }> = []; + let day = 0; + for (const staffer of STAFFERS) { + for (const ct of CONTRACTS) { + day += 1; + const date = new Date(Date.now() + day * 86400000).toISOString().split("T")[0]; + const spec = { + client: ct.client, + date, + contract: ct.contract, + staffer, + events: ct.events.map(e => ({ + ...e, + city: ct.city, + state: ct.state, + shift_start: `${e.at} ${e.at.startsWith("0") ? "AM" : "PM"}`, + scenario_note: `Staffed by ${staffer.name} (${staffer.role}, ${staffer.tenure_months}mo). Contract deadline ${ct.contract.deadline}, fill=${ct.contract.fill_requirement}.`, + })), + }; + const fname = `${staffer.id}_${ct.tag}.json`; + await writeFile(join(outDir, fname), JSON.stringify(spec, null, 2)); + manifest.push({ file: fname, staffer: staffer.name, contract: ct.tag, client: ct.client }); + } + } + await writeFile( + join(outDir, "manifest.json"), + JSON.stringify({ count: manifest.length, scenarios: manifest }, null, 2), + ); + console.log(`✓ ${manifest.length} staffer-demo specs → ${outDir}/`); + for (const m of manifest) console.log(` ${m.file} — ${m.staffer} × ${m.contract}`); +} + +main().catch(e => { + console.error("gen_staffer_demo failed:", (e as Error).message); + process.exit(1); +}); diff --git a/tests/multi-agent/kb.ts b/tests/multi-agent/kb.ts index 219ade3..a0d7ce9 100644 --- a/tests/multi-agent/kb.ts +++ b/tests/multi-agent/kb.ts @@ -34,6 +34,7 @@ const OUTCOMES_FILE = "outcomes.jsonl"; const CONFIG_SNAPSHOTS_FILE = "config_snapshots.jsonl"; const ERROR_CORRECTIONS_FILE = "error_corrections.jsonl"; const RECOMMENDATIONS_FILE = "pathway_recommendations.jsonl"; +const STAFFERS_FILE = "staffers.jsonl"; // What a playbook signature looks like — stable hash of the scenario // shape that ignores timestamps and specific worker IDs. Two runs with @@ -60,11 +61,19 @@ export interface RunOutcome { overview: string; overview_cloud: boolean; }; + staffer?: { // Phase 23 — who ran this scenario + id: string; + name: string; + tenure_months: number; + role: string; + }; ok_events: number; total_events: number; total_turns: number; total_gap_signals: number; total_citations: number; + rescue_attempts: number; // Phase 22 item B + rescue_successes: number; per_event: Array<{ at: string; kind: string; @@ -78,6 +87,27 @@ export interface RunOutcome { created_at: string; } +// Per-staffer aggregate. Recomputed from outcomes.jsonl after every +// run. Competence_score weights neighbor retrieval so Senior staffers' +// playbooks rank higher for similar scenarios than Junior staffers'. +export interface StafferStats { + id: string; + name: string; + tenure_months: number; + role: string; + total_runs: number; + total_events_attempted: number; + total_events_ok: number; + fill_rate: number; // ok / attempted + avg_turns_per_event: number; + avg_citations_per_run: number; + rescue_attempts: number; + rescue_successes: number; + rescue_rate: number; + competence_score: number; // 0.0 - 1.0, see formula below + last_updated: string; +} + // The AI-synthesized recommendation written back for future runs. export interface PathwayRecommendation { sig_hash: string; @@ -142,7 +172,7 @@ async function readJsonl(file: string): Promise { // embedding, appends outcome, updates or inserts signature entry. export async function indexRun( scenarioDir: string, - spec: { client: string; date: string; events: Array }, + spec: { client: string; date: string; events: Array; staffer?: { id: string; name: string; tenure_months: number; role: string } }, models: { executor: string; reviewer: string; overview: string; overview_cloud: boolean }, elapsed_secs: number, ): Promise<{ sig_hash: string; outcome: RunOutcome }> { @@ -154,16 +184,21 @@ export async function indexRun( // Read results.json produced by scenario.ts to build the outcome record. const resultsRaw = await readFile(join(scenarioDir, "results.json"), "utf8"); const results = JSON.parse(resultsRaw) as any[]; + const rescue_attempts = results.filter(r => r.retry_remediation).length; + const rescue_successes = results.filter(r => r.retry_remediation && r.ok).length; const outcome: RunOutcome = { sig_hash, run_id: scenarioDir.split("/").pop() ?? scenarioDir, date: spec.date, models, + staffer: spec.staffer, ok_events: results.filter(r => r.ok).length, total_events: results.length, total_turns: results.reduce((s, r) => s + (r.turns ?? 0), 0), total_gap_signals: results.reduce((s, r) => s + (r.gap_signals?.length ?? 0), 0), total_citations: results.reduce((s, r) => s + (r.playbook_citations?.length ?? 0), 0), + rescue_attempts, + rescue_successes, per_event: results.map(r => ({ at: r.event.at, kind: r.event.kind, @@ -178,6 +213,13 @@ export async function indexRun( }; await appendFile(join(KB_DIR, OUTCOMES_FILE), JSON.stringify(outcome) + "\n"); + // Phase 23 — recompute this staffer's aggregate + competence_score. + // Cheap until we have thousands of runs; swap to streaming aggregate + // when that becomes a concern. + if (spec.staffer) { + await recomputeStafferStats(spec.staffer.id); + } + // Embed + upsert signature. Skip if embedding fails — the outcome is // still persisted, just without neighbor-search hookup. try { @@ -221,6 +263,72 @@ export async function indexRun( return { sig_hash, outcome }; } +// Phase 23 — per-staffer aggregate, recomputed from scratch on each +// run. Writes the full table to STAFFERS_FILE (not append-only; we +// always want the current-state snapshot). Competence score is bounded +// 0..1 and combines four dimensions: +// fill_rate — how often the staffer completes their events +// turn_efficiency — lower turn counts are better +// citation_density — signals use of playbook_memory feedback +// rescue_rate — cloud rescue successes per attempt (doesn't +// penalize staffers who never needed one) +// Weights: fill 0.45, turn_eff 0.20, citation 0.20, rescue 0.15. +// Rationale: completing the job matters most; everything else is style. +export async function recomputeStafferStats(staffer_id: string): Promise { + const outcomes = await readJsonl(OUTCOMES_FILE); + const mine = outcomes.filter(o => o.staffer?.id === staffer_id); + if (mine.length === 0) return null; + const latest = mine[mine.length - 1].staffer!; + const total_events_attempted = mine.reduce((s, o) => s + o.total_events, 0); + const total_events_ok = mine.reduce((s, o) => s + o.ok_events, 0); + const total_turns = mine.reduce((s, o) => s + o.total_turns, 0); + const total_citations = mine.reduce((s, o) => s + o.total_citations, 0); + const rescue_attempts = mine.reduce((s, o) => s + (o.rescue_attempts ?? 0), 0); + const rescue_successes = mine.reduce((s, o) => s + (o.rescue_successes ?? 0), 0); + const fill_rate = total_events_attempted > 0 ? total_events_ok / total_events_attempted : 0; + const avg_turns_per_event = total_events_attempted > 0 ? total_turns / total_events_attempted : 0; + const avg_citations_per_run = mine.length > 0 ? total_citations / mine.length : 0; + const rescue_rate = rescue_attempts > 0 ? rescue_successes / rescue_attempts : 1.0; // never-rescued defaults to perfect + // Normalize each axis to 0..1. Caps chosen from observed distributions. + const turn_eff = avg_turns_per_event === 0 ? 0 : Math.max(0, 1 - Math.min(avg_turns_per_event / 10, 1)); + const cite_norm = Math.min(avg_citations_per_run / 3, 1); + const competence_score = 0.45 * fill_rate + + 0.20 * turn_eff + + 0.20 * cite_norm + + 0.15 * rescue_rate; + const stats: StafferStats = { + id: staffer_id, + name: latest.name, + tenure_months: latest.tenure_months, + role: latest.role, + total_runs: mine.length, + total_events_attempted, + total_events_ok, + fill_rate, + avg_turns_per_event, + avg_citations_per_run, + rescue_attempts, + rescue_successes, + rescue_rate, + competence_score, + last_updated: new Date().toISOString(), + }; + // Rewrite whole file — cheap at O(staffers) scale. + const existing = await readJsonl(STAFFERS_FILE); + const others = existing.filter(s => s.id !== staffer_id); + others.push(stats); + await writeFile( + join(KB_DIR, STAFFERS_FILE), + others.map(s => JSON.stringify(s)).join("\n") + "\n", + ); + return stats; +} + +// Public accessor — used by findNeighbors + the staffer report script. +export async function loadStafferStats(): Promise { + return readJsonl(STAFFERS_FILE); +} + // Cosine similarity for neighbor lookup. Float32-in-memory is fine for // O(thousands) signatures; swap to vectord HNSW once the corpus grows. function cosine(a: number[], b: number[]): number { @@ -237,9 +345,18 @@ function cosine(a: number[], b: number[]): number { // Find the k nearest-neighbor signatures for a target spec, returning // neighbor sigs + their outcome history. This is what the recommender // feeds to the overview model. +// +// Phase 23 — weighted by staffer competence. The ranking score is +// cosine_similarity * max_competence_among_this_sig's_outcomes. Top +// staffers' playbooks surface first even if their scenario was +// slightly less similar than a junior's. Sigs with no staffer history +// fall back to raw similarity (competence defaults to 0.5). export async function findNeighbors(spec: any, k = 5): Promise> { await ensureKb(); @@ -256,16 +373,45 @@ export async function findNeighbors(spec: any, k = 5): Promise(SIGNATURES_FILE); const outcomes = await readJsonl(OUTCOMES_FILE); + const staffers = await readJsonl(STAFFERS_FILE); + const competenceById = new Map(staffers.map(s => [s.id, s.competence_score])); + + // Per-sig best staffer — the highest-competence coordinator who has + // ever run this signature. Used to weight the sig's ranking. + function bestStafferFor(sig_hash: string): { id: string | null; competence: number } { + const mine = outcomes.filter(o => o.sig_hash === sig_hash && o.staffer?.id); + if (mine.length === 0) return { id: null, competence: 0.5 }; + let bestId: string | null = null; + let bestComp = 0; + for (const o of mine) { + const c = competenceById.get(o.staffer!.id) ?? 0.5; + if (c > bestComp) { bestComp = c; bestId = o.staffer!.id; } + } + return { id: bestId, competence: bestComp }; + } const ranked = sigs .filter(s => s.sig_hash !== targetHash) // don't recommend against self - .map(s => ({ sig: s, similarity: cosine(targetVec, s.embedding) })) - .sort((a, b) => b.similarity - a.similarity) + .map(s => { + const similarity = cosine(targetVec, s.embedding); + const best = bestStafferFor(s.sig_hash); + // Weighted score: similarity multiplied by the best staffer's + // competence (floored at 0.3 so a sig with a low-competence + // staffer still shows up if similarity is very high). + const weight = Math.max(best.competence, 0.3); + return { + sig: s, + similarity, + weighted_score: similarity * weight, + best_staffer_competence: best.competence, + best_staffer_id: best.id, + }; + }) + .sort((a, b) => b.weighted_score - a.weighted_score) .slice(0, k); return ranked.map(r => ({ - sig: r.sig, - similarity: r.similarity, + ...r, outcomes: outcomes.filter(o => o.sig_hash === r.sig.sig_hash), })); } @@ -342,6 +488,8 @@ export async function recommendFor( // Build the prompt. Include target spec digest, neighbor digests with // their outcome stats, and recent error corrections. Ask for // structured output so we can parse it. + const staffers = await readJsonl(STAFFERS_FILE); + const stafferById = new Map(staffers.map(s => [s.id, s])); const neighborBlock = neighbors.map(n => { const best = n.outcomes.reduce((a, b) => a && a.ok_events / Math.max(1, a.total_events) >= b.ok_events / Math.max(1, b.total_events) ? a : b, @@ -349,7 +497,11 @@ export async function recommendFor( const avgOk = n.outcomes.length > 0 ? (n.outcomes.reduce((s, o) => s + o.ok_events, 0) / n.outcomes.length).toFixed(1) : "?"; - return `- sig ${n.sig.sig_hash} sim=${n.similarity.toFixed(3)} (${n.sig.events_digest.slice(0, 100)}): ${n.outcomes.length} runs, avg ${avgOk}/${best?.total_events ?? "?"} ok; best models: exec=${best?.models.executor ?? "?"} review=${best?.models.reviewer ?? "?"}`; + const topStaffer = n.best_staffer_id ? stafferById.get(n.best_staffer_id) : null; + const stafferTag = topStaffer + ? ` [top staffer: ${topStaffer.name} (${topStaffer.role}, competence=${topStaffer.competence_score.toFixed(2)})]` + : ""; + return `- sig ${n.sig.sig_hash} sim=${n.similarity.toFixed(3)} weighted=${n.weighted_score.toFixed(3)}${stafferTag}: ${n.outcomes.length} runs, avg ${avgOk}/${best?.total_events ?? "?"} ok; best models: exec=${best?.models.executor ?? "?"} review=${best?.models.reviewer ?? "?"}`; }).join("\n"); const correctionBlock = corrections.slice(-3).map(c => diff --git a/tests/multi-agent/scenario.ts b/tests/multi-agent/scenario.ts index 44220d8..6ca3cba 100644 --- a/tests/multi-agent/scenario.ts +++ b/tests/multi-agent/scenario.ts @@ -122,10 +122,40 @@ interface FillEvent { replaces_event?: string; // misplacement back-ref for reporting } +// Contract terms describe the business constraints around a scenario: +// how much can the client pay, by when, and how is a local candidate +// bonused vs an out-of-area one. T3 checkpoint + cloud rescue pass +// these into the overseer's prompt so pivots respect budget (not just +// geography). Optional — legacy scenarios without a contract degrade +// to the original "nearest-city" pivot logic. +interface ContractTerms { + deadline: string; // ISO date — "2026-05-05" + budget_total_usd?: number; // optional total cap across the contract + budget_per_hour_max?: number; // per-worker hourly cap + local_bonus_per_hour?: number; // extra $/hr for in-radius candidates + local_bonus_radius_mi?: number; // radius from the contract city + fill_requirement?: "paramount" | "preferred"; // "paramount" = must fill; + // "preferred" = best-effort +} + +// Phase 23 — staffer identity. When a scenario is assigned to a +// specific coordinator, we record their trajectory so KB can +// weight future retrieval by competence. Junior staffers inherit +// Senior staffers' playbooks; top staffers become the training +// signal the system auto-discovers. +interface Staffer { + id: string; // "S-001" + name: string; // "Maria Chen" + tenure_months: number; + role: "senior" | "mid" | "junior" | "trainee"; +} + interface ScenarioSpec { client: string; date: string; events: FillEvent[]; + contract?: ContractTerms; + staffer?: Staffer; } interface EventResult { @@ -167,6 +197,7 @@ interface EventResult { interface CloudRemediation { retry: boolean; new_city?: string; + new_state?: string; new_role?: string; new_count?: number; rationale: string; @@ -957,10 +988,25 @@ function extractDiagnostics(log: LogEntry[] | undefined): { return { sql_filters, hybrid_row_counts, sql_errors, drift_reasons }; } +// Render contract terms into a prompt-friendly block. Empty string when +// no contract is attached — keeps legacy scenarios unaffected. +function contractBlock(contract: ContractTerms | undefined): string { + if (!contract) return ""; + const parts: string[] = []; + parts.push(`deadline=${contract.deadline}`); + if (contract.fill_requirement) parts.push(`fill=${contract.fill_requirement}`); + if (contract.budget_per_hour_max) parts.push(`budget/hr max=$${contract.budget_per_hour_max}`); + if (contract.budget_total_usd) parts.push(`total budget=$${contract.budget_total_usd}`); + if (contract.local_bonus_per_hour) parts.push(`local bonus=+$${contract.local_bonus_per_hour}/hr`); + if (contract.local_bonus_radius_mi) parts.push(`local radius=${contract.local_bonus_radius_mi}mi`); + return `\nCONTRACT TERMS: ${parts.join(", ")}`; +} + async function runOverviewCheckpoint( event: FillEvent, result: EventResult, prior: EventResult[], + contract?: ContractTerms, ): Promise { if (T3_DISABLED) return null; const start = Date.now(); @@ -985,13 +1031,13 @@ async function runOverviewCheckpoint( + `Gap signals: ${result.gap_signals.join("; ") || "none"}.`; const prompt = `You are the overview reviewer for a staffing coordinator agent system. A mid-day checkpoint has been triggered. - +${contractBlock(contract)} Recent events (most recent last): ${priorSummary || "(no prior events)"} ${thisOne}${diagBlock} -Your job: emit ONE risk flag (≤8 words) and ONE actionable hint (≤40 words) for the NEXT event. Be concrete: name the role, city, worker class, OR a geographic pivot (e.g. "pivot Gary IN → Chicago IL, 40min drive"). Do not restate what happened. If the failure was zero-supply, your hint MUST propose a specific alternative city or role. Think step by step, then output strictly as: +Your job: emit ONE risk flag (≤8 words) and ONE actionable hint (≤40 words) for the NEXT event. Be concrete: name the role, city, worker class, OR a geographic pivot (e.g. "pivot to {city}, {state} ({distance}mi) — still in bonus radius"). Do not restate what happened. If the failure was zero-supply, your hint MUST propose a specific alternative city or role. If contract has local_bonus_radius_mi, prefer alternates within that radius before proposing farther pivots. Think step by step, then output strictly as: RISK: HINT: `; @@ -1036,6 +1082,7 @@ HINT: `; async function requestCloudRemediation( event: FillEvent, result: EventResult, + contract?: ContractTerms, ): Promise<{ remediation: CloudRemediation; duration_secs: number } | null> { if (T3_DISABLED) return null; const start = Date.now(); @@ -1054,6 +1101,7 @@ FAILED EVENT: outcome: ${result.error ?? "unknown failure"} turns used: ${result.turns} pool surfaced: ${result.pool_size ?? "n/a"} +${contractBlock(contract)} RAW DIAGNOSTICS (what the agent actually saw): ${diagBlock} @@ -1062,6 +1110,7 @@ Respond with a JSON object (NOTHING else, no prose before or after, no markdown) { "retry": true | false, "new_city": "string (same as original if no pivot)", + "new_state": "XX (two-letter abbreviation)", "new_role": "string (same as original if no pivot)", "new_count": , "rationale": "2-3 sentences explaining the fix or why retry is futile" @@ -1072,7 +1121,9 @@ RULES: - If the city has genuine zero supply, pivot to the NEAREST alternate city with comparable labor pool (name a specific one). - If the role is uniquely scarce, either broaden to a synonym role OR reduce count to something achievable. - If no pivot seems viable, set retry=false — wasting a retry is worse than declaring impossible. -- Keep new_count realistic. Don't propose 5× in a city that clearly only has 2 workers.`; +- Keep new_count realistic. Don't propose 5× in a city that clearly only has 2 workers. +- new_city is the CITY name only (e.g. "Clarksville"). Put the two-letter state in new_state. Don't concat them. +${contract ? `- CONTRACT AWARENESS: fill_requirement=${contract.fill_requirement ?? "preferred"}. ${contract.fill_requirement === "paramount" ? "MUST fill — splitting across more cities is acceptable if one city can't cover headcount." : "Best-effort — declining is OK if budget can't absorb pivot premium."} ${contract.local_bonus_radius_mi ? `Local bonus radius is ${contract.local_bonus_radius_mi}mi from ${event.city}, ${event.state}; prefer pivots WITHIN that radius even if they have slightly less supply, since those candidates earn the local bonus premium.` : ""} ${contract.budget_per_hour_max ? `Per-hour budget max is $${contract.budget_per_hour_max}.` : ""}` : ""}`; let raw = ""; try { @@ -1108,7 +1159,7 @@ async function runCrossDayLesson(ctx: ScenarioContext, checkpoints: OverviewChec const prompt = `You are the end-of-day lesson writer for a staffing coordinator agent system. The day is done. Distill it. -Client: ${ctx.spec.client} Date: ${ctx.spec.date} +Client: ${ctx.spec.client} Date: ${ctx.spec.date}${contractBlock(ctx.spec.contract)} Events that ran: ${eventDigest} @@ -1326,6 +1377,13 @@ async function main() { const checkpoints: OverviewCheckpoint[] = []; console.log(`▶ scenario: ${spec.client}, ${spec.date}, ${spec.events.length} events`); + if (spec.staffer) { + console.log(`▶ staffer: ${spec.staffer.id} ${spec.staffer.name} (${spec.staffer.role}, ${spec.staffer.tenure_months}mo)`); + } + if (spec.contract) { + const c = spec.contract; + console.log(`▶ contract: deadline=${c.deadline} fill=${c.fill_requirement ?? "preferred"}${c.budget_per_hour_max ? ` budget=$${c.budget_per_hour_max}/hr` : ""}${c.local_bonus_radius_mi ? ` local_radius=${c.local_bonus_radius_mi}mi+$${c.local_bonus_per_hour ?? 0}` : ""}`); + } console.log(`▶ models: exec=${EXECUTOR_MODEL} review=${REVIEWER_MODEL} overview=${T3_DISABLED ? "disabled" : OVERVIEW_MODEL + (OVERVIEW_CLOUD ? " (cloud)" : "")}`); console.log(`▶ out: ${out_dir}\n`); @@ -1363,26 +1421,21 @@ async function main() { // scenarios. if (!result.ok && RETRY_ON_FAIL && !T3_DISABLED) { console.log(` ▶ cloud rescue requested for ${event.at} ${event.kind}…`); - const rescue = await requestCloudRemediation(event, result); + const rescue = await requestCloudRemediation(event, result, spec.contract); if (rescue && rescue.remediation.retry) { const r = rescue.remediation; - // Sanitize cloud's fields — model sometimes emits "Hammond, IN" - // as new_city and "IN" as new_state, producing "Hammond, IN, IN" - // downstream. Split on comma and take the first token for city. - const sanitizeCity = (c: string | undefined) => (c ?? "").split(",")[0].trim(); - const sanitizeState = (c: string | undefined, stateFromCity: string) => { - const explicit = (c ?? "").trim(); - // If explicit state is empty or matches original, try to - // extract from the city string if it had a trailing ", XX". - return explicit || stateFromCity || event.state; - }; - const cityRaw = r.new_city ?? event.city; - const cityClean = sanitizeCity(cityRaw); - const stateFromCity = (cityRaw.match(/,\s*([A-Z]{2})/) ?? [])[1] ?? ""; + // Sanitize cloud's fields — model sometimes still packs state + // into city ("Hammond, IN") even when prompted to split; guard + // by stripping comma+ from new_city and harvesting the trailing + // state if new_state is missing. + const cityRaw = (r.new_city ?? event.city).trim(); + const cityParts = cityRaw.split(",").map(s => s.trim()); + const cityClean = cityParts[0] || event.city; + const stateFromCity = cityParts[1]?.match(/^([A-Z]{2})/i)?.[1]?.toUpperCase() ?? ""; const newEvent: FillEvent = { ...event, - city: cityClean || event.city, - state: sanitizeState(undefined, stateFromCity) || event.state, + city: cityClean, + state: (r.new_state?.trim() || stateFromCity || event.state).toUpperCase(), role: r.new_role ?? event.role, count: r.new_count ?? event.count, scenario_note: `[cloud-rescue ${rescue.duration_secs.toFixed(1)}s] ${r.rationale}`, @@ -1455,7 +1508,7 @@ async function main() { const nthHit = T3_CHECKPOINT_EVERY > 0 && ((i + 1) % T3_CHECKPOINT_EVERY === 0); const shouldCheckpoint = !T3_DISABLED && (event.kind === "misplacement" || nthHit || isLast); if (shouldCheckpoint) { - const cp = await runOverviewCheckpoint(event, result, ctx.results.slice(0, -1)); + const cp = await runOverviewCheckpoint(event, result, ctx.results.slice(0, -1), spec.contract); if (cp) { checkpoints.push(cp); await appendFile(join(out_dir, "checkpoints.jsonl"), JSON.stringify(cp) + "\n"); @@ -1532,12 +1585,17 @@ async function main() { // run ends → KB updates → next run reads rec at startup. try { const elapsed = (Date.now() - runStart) / 1000; - const { sig_hash } = await indexRun(out_dir, spec, { - executor: EXECUTOR_MODEL, - reviewer: REVIEWER_MODEL, - overview: OVERVIEW_MODEL, - overview_cloud: OVERVIEW_CLOUD, - }, elapsed); + const { sig_hash } = await indexRun( + out_dir, + { client: spec.client, date: spec.date, events: spec.events, staffer: spec.staffer }, + { + executor: EXECUTOR_MODEL, + reviewer: REVIEWER_MODEL, + overview: OVERVIEW_MODEL, + overview_cloud: OVERVIEW_CLOUD, + }, + elapsed, + ); console.log(`▶ KB indexed: sig=${sig_hash} (${elapsed.toFixed(1)}s)`); const newRec = await recommendFor(spec, { overview_model: OVERVIEW_MODEL, diff --git a/tests/multi-agent/scenarios/manifest.json b/tests/multi-agent/scenarios/manifest.json new file mode 100644 index 0000000..4575cdc --- /dev/null +++ b/tests/multi-agent/scenarios/manifest.json @@ -0,0 +1,126 @@ +{ + "count": 20, + "seed": 1337, + "scenarios": [ + { + "file": "scen_000_Great_Lakes_Mfg_Cincinnati.json", + "client": "Great Lakes Mfg", + "city": "Cincinnati", + "events": 4 + }, + { + "file": "scen_001_Parallel_Machining_Joliet.json", + "client": "Parallel Machining", + "city": "Joliet", + "events": 2 + }, + { + "file": "scen_002_Summit_Industrial_Cincinnati.json", + "client": "Summit Industrial", + "city": "Cincinnati", + "events": 3 + }, + { + "file": "scen_003_Pioneer_Assembly_Chicago.json", + "client": "Pioneer Assembly", + "city": "Chicago", + "events": 1 + }, + { + "file": "scen_004_Midway_Distribution_Columbus.json", + "client": "Midway Distribution", + "city": "Columbus", + "events": 2 + }, + { + "file": "scen_005_Apex_Warehouse_Cleveland.json", + "client": "Apex Warehouse", + "city": "Cleveland", + "events": 3 + }, + { + "file": "scen_006_Pioneer_Assembly_Flint.json", + "client": "Pioneer Assembly", + "city": "Flint", + "events": 5 + }, + { + "file": "scen_007_Riverfront_Steel_Toledo.json", + "client": "Riverfront Steel", + "city": "Toledo", + "events": 3 + }, + { + "file": "scen_008_Northland_Logistics_Indianapolis.json", + "client": "Northland Logistics", + "city": "Indianapolis", + "events": 4 + }, + { + "file": "scen_009_Parallel_Machining_Flint.json", + "client": "Parallel Machining", + "city": "Flint", + "events": 3 + }, + { + "file": "scen_010_Northland_Logistics_Chicago.json", + "client": "Northland Logistics", + "city": "Chicago", + "events": 2 + }, + { + "file": "scen_011_Heritage_Foods_Flint.json", + "client": "Heritage Foods", + "city": "Flint", + "events": 3 + }, + { + "file": "scen_012_Parallel_Machining_Kansas_City.json", + "client": "Parallel Machining", + "city": "Kansas City", + "events": 3 + }, + { + "file": "scen_013_Horizon_Supply_Flint.json", + "client": "Horizon Supply", + "city": "Flint", + "events": 3 + }, + { + "file": "scen_014_Midway_Distribution_Indianapolis.json", + "client": "Midway Distribution", + "city": "Indianapolis", + "events": 4 + }, + { + "file": "scen_015_Cornerstone_Fabrication_Kansas_City.json", + "client": "Cornerstone Fabrication", + "city": "Kansas City", + "events": 4 + }, + { + "file": "scen_016_Riverfront_Steel_Columbus.json", + "client": "Riverfront Steel", + "city": "Columbus", + "events": 4 + }, + { + "file": "scen_017_Summit_Industrial_Detroit.json", + "client": "Summit Industrial", + "city": "Detroit", + "events": 2 + }, + { + "file": "scen_018_Heritage_Foods_Cincinnati.json", + "client": "Heritage Foods", + "city": "Cincinnati", + "events": 4 + }, + { + "file": "scen_019_Midway_Distribution_Chicago.json", + "client": "Midway Distribution", + "city": "Chicago", + "events": 3 + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/nashville_contract.json b/tests/multi-agent/scenarios/nashville_contract.json new file mode 100644 index 0000000..dac43b0 --- /dev/null +++ b/tests/multi-agent/scenarios/nashville_contract.json @@ -0,0 +1,66 @@ +{ + "client": "Riverline Logistics — Nashville Downtown Build-Out", + "date": "2026-05-05", + "contract": { + "deadline": "2026-05-19", + "budget_total_usd": 180000, + "budget_per_hour_max": 32, + "local_bonus_per_hour": 4, + "local_bonus_radius_mi": 75, + "fill_requirement": "paramount" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Welder", + "count": 4, + "city": "Nashville", + "state": "TN", + "shift_start": "07:00 AM", + "scenario_note": "Skilled welding for downtown structural assembly. Prefer local (in 75mi). Contract pays up to $32/hr + $4/hr local bonus. Fill is paramount — the contract is worth $180K total, broken = penalty." + }, + { + "kind": "expansion", + "at": "08:30", + "role": "Packaging Operator", + "count": 6, + "city": "Nashville", + "state": "TN", + "shift_start": "08:30 AM", + "scenario_note": "Warehouse packaging on-site for the same project. 6 workers preferred Nashville-local; out-of-area acceptable if local pool is exhausted. Respect budget." + }, + { + "kind": "baseline_fill", + "at": "09:00", + "role": "Shipping Clerk", + "count": 2, + "city": "Nashville", + "state": "TN", + "shift_start": "09:00 AM", + "scenario_note": "Administrative/shipping coordination role. Two clerks needed; tightening the load on packaging team." + }, + { + "kind": "emergency", + "at": "13:00", + "role": "Welder", + "count": 2, + "city": "Nashville", + "state": "TN", + "shift_start": "13:00 PM", + "deadline": "15:00", + "scenario_note": "A subcontractor just dropped out — need 2 more Welders by 3pm. Use cloud rescue path if Nashville supply is exhausted; pivot to within-radius first (if any in 75mi), then budget-permitting out-of-area." + }, + { + "kind": "misplacement", + "at": "15:30", + "role": "Packaging Operator", + "count": 1, + "city": "Nashville", + "state": "TN", + "shift_start": "15:30 PM", + "replaces_event": "08:30", + "scenario_note": "One packager no-showed. Single refill. Any Nashville-area candidate, budget already covered." + } + ] +} diff --git a/tests/multi-agent/scenarios/scen_000_Great_Lakes_Mfg_Cincinnati.json b/tests/multi-agent/scenarios/scen_000_Great_Lakes_Mfg_Cincinnati.json new file mode 100644 index 0000000..a610dd0 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_000_Great_Lakes_Mfg_Cincinnati.json @@ -0,0 +1,43 @@ +{ + "client": "Great Lakes Mfg", + "date": "2026-04-21", + "events": [ + { + "kind": "baseline_fill", + "at": "13:00", + "role": "Material Handler", + "count": 2, + "city": "Cincinnati", + "state": "OH", + "shift_start": "13:00 AM" + }, + { + "kind": "recurring", + "at": "14:30", + "role": "Assembler", + "count": 2, + "city": "Cincinnati", + "state": "OH", + "shift_start": "14:30 AM" + }, + { + "kind": "emergency", + "at": "10:30", + "role": "Material Handler", + "count": 3, + "city": "Cincinnati", + "state": "OH", + "shift_start": "10:30 AM" + }, + { + "kind": "misplacement", + "at": "13:30", + "role": "Shipping Clerk", + "count": 1, + "city": "Cincinnati", + "state": "OH", + "shift_start": "13:30 AM", + "replaces_event": "13:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_001_Parallel_Machining_Joliet.json b/tests/multi-agent/scenarios/scen_001_Parallel_Machining_Joliet.json new file mode 100644 index 0000000..11b566e --- /dev/null +++ b/tests/multi-agent/scenarios/scen_001_Parallel_Machining_Joliet.json @@ -0,0 +1,24 @@ +{ + "client": "Parallel Machining", + "date": "2026-04-22", + "events": [ + { + "kind": "baseline_fill", + "at": "10:00", + "role": "Machine Operator", + "count": 2, + "city": "Joliet", + "state": "IL", + "shift_start": "10:00 AM" + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Packer", + "count": 2, + "city": "Joliet", + "state": "IL", + "shift_start": "09:30 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_002_Summit_Industrial_Cincinnati.json b/tests/multi-agent/scenarios/scen_002_Summit_Industrial_Cincinnati.json new file mode 100644 index 0000000..72bc998 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_002_Summit_Industrial_Cincinnati.json @@ -0,0 +1,33 @@ +{ + "client": "Summit Industrial", + "date": "2026-04-23", + "events": [ + { + "kind": "baseline_fill", + "at": "13:30", + "role": "Shipping Clerk", + "count": 1, + "city": "Cincinnati", + "state": "OH", + "shift_start": "13:30 AM" + }, + { + "kind": "recurring", + "at": "08:00", + "role": "Loader", + "count": 1, + "city": "Cincinnati", + "state": "OH", + "shift_start": "08:00 AM" + }, + { + "kind": "emergency", + "at": "13:00", + "role": "Forklift Operator", + "count": 2, + "city": "Cincinnati", + "state": "OH", + "shift_start": "13:00 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_003_Pioneer_Assembly_Chicago.json b/tests/multi-agent/scenarios/scen_003_Pioneer_Assembly_Chicago.json new file mode 100644 index 0000000..a362be4 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_003_Pioneer_Assembly_Chicago.json @@ -0,0 +1,15 @@ +{ + "client": "Pioneer Assembly", + "date": "2026-04-24", + "events": [ + { + "kind": "baseline_fill", + "at": "14:00", + "role": "Receiving Clerk", + "count": 3, + "city": "Chicago", + "state": "IL", + "shift_start": "14:00 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_004_Midway_Distribution_Columbus.json b/tests/multi-agent/scenarios/scen_004_Midway_Distribution_Columbus.json new file mode 100644 index 0000000..3b4a050 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_004_Midway_Distribution_Columbus.json @@ -0,0 +1,24 @@ +{ + "client": "Midway Distribution", + "date": "2026-04-25", + "events": [ + { + "kind": "baseline_fill", + "at": "09:30", + "role": "Forklift Operator", + "count": 1, + "city": "Columbus", + "state": "OH", + "shift_start": "09:30 AM" + }, + { + "kind": "expansion", + "at": "13:00", + "role": "Assembler", + "count": 4, + "city": "Columbus", + "state": "OH", + "shift_start": "13:00 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_005_Apex_Warehouse_Cleveland.json b/tests/multi-agent/scenarios/scen_005_Apex_Warehouse_Cleveland.json new file mode 100644 index 0000000..c0c0969 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_005_Apex_Warehouse_Cleveland.json @@ -0,0 +1,33 @@ +{ + "client": "Apex Warehouse", + "date": "2026-04-26", + "events": [ + { + "kind": "baseline_fill", + "at": "10:30", + "role": "Receiving Clerk", + "count": 1, + "city": "Cleveland", + "state": "OH", + "shift_start": "10:30 AM" + }, + { + "kind": "recurring", + "at": "15:30", + "role": "Quality Tech", + "count": 2, + "city": "Cleveland", + "state": "OH", + "shift_start": "15:30 AM" + }, + { + "kind": "expansion", + "at": "14:30", + "role": "Machine Operator", + "count": 5, + "city": "Cleveland", + "state": "OH", + "shift_start": "14:30 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_006_Pioneer_Assembly_Flint.json b/tests/multi-agent/scenarios/scen_006_Pioneer_Assembly_Flint.json new file mode 100644 index 0000000..031b91a --- /dev/null +++ b/tests/multi-agent/scenarios/scen_006_Pioneer_Assembly_Flint.json @@ -0,0 +1,52 @@ +{ + "client": "Pioneer Assembly", + "date": "2026-04-27", + "events": [ + { + "kind": "baseline_fill", + "at": "13:30", + "role": "CNC Operator", + "count": 2, + "city": "Flint", + "state": "MI", + "shift_start": "13:30 AM" + }, + { + "kind": "recurring", + "at": "11:30", + "role": "Loader", + "count": 1, + "city": "Flint", + "state": "MI", + "shift_start": "11:30 AM" + }, + { + "kind": "expansion", + "at": "14:00", + "role": "Welder", + "count": 3, + "city": "Flint", + "state": "MI", + "shift_start": "14:00 AM" + }, + { + "kind": "emergency", + "at": "12:30", + "role": "Machine Operator", + "count": 2, + "city": "Flint", + "state": "MI", + "shift_start": "12:30 AM" + }, + { + "kind": "misplacement", + "at": "17:00", + "role": "Shipping Clerk", + "count": 1, + "city": "Flint", + "state": "MI", + "shift_start": "17:00 AM", + "replaces_event": "13:30" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_007_Riverfront_Steel_Toledo.json b/tests/multi-agent/scenarios/scen_007_Riverfront_Steel_Toledo.json new file mode 100644 index 0000000..76d7c35 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_007_Riverfront_Steel_Toledo.json @@ -0,0 +1,34 @@ +{ + "client": "Riverfront Steel", + "date": "2026-04-28", + "events": [ + { + "kind": "baseline_fill", + "at": "11:00", + "role": "Assembler", + "count": 3, + "city": "Toledo", + "state": "OH", + "shift_start": "11:00 AM" + }, + { + "kind": "recurring", + "at": "14:00", + "role": "Material Handler", + "count": 1, + "city": "Toledo", + "state": "OH", + "shift_start": "14:00 AM" + }, + { + "kind": "misplacement", + "at": "08:30", + "role": "Packer", + "count": 1, + "city": "Toledo", + "state": "OH", + "shift_start": "08:30 AM", + "replaces_event": "11:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_008_Northland_Logistics_Indianapolis.json b/tests/multi-agent/scenarios/scen_008_Northland_Logistics_Indianapolis.json new file mode 100644 index 0000000..1609379 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_008_Northland_Logistics_Indianapolis.json @@ -0,0 +1,43 @@ +{ + "client": "Northland Logistics", + "date": "2026-04-29", + "events": [ + { + "kind": "baseline_fill", + "at": "14:00", + "role": "Quality Tech", + "count": 1, + "city": "Indianapolis", + "state": "IN", + "shift_start": "14:00 AM" + }, + { + "kind": "recurring", + "at": "12:30", + "role": "Shipping Clerk", + "count": 1, + "city": "Indianapolis", + "state": "IN", + "shift_start": "12:30 AM" + }, + { + "kind": "emergency", + "at": "10:00", + "role": "Welder", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "10:00 AM" + }, + { + "kind": "misplacement", + "at": "14:30", + "role": "Material Handler", + "count": 1, + "city": "Indianapolis", + "state": "IN", + "shift_start": "14:30 AM", + "replaces_event": "14:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_009_Parallel_Machining_Flint.json b/tests/multi-agent/scenarios/scen_009_Parallel_Machining_Flint.json new file mode 100644 index 0000000..fcd794b --- /dev/null +++ b/tests/multi-agent/scenarios/scen_009_Parallel_Machining_Flint.json @@ -0,0 +1,33 @@ +{ + "client": "Parallel Machining", + "date": "2026-04-30", + "events": [ + { + "kind": "baseline_fill", + "at": "16:00", + "role": "Loader", + "count": 3, + "city": "Flint", + "state": "MI", + "shift_start": "16:00 AM" + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Warehouse Associate", + "count": 2, + "city": "Flint", + "state": "MI", + "shift_start": "09:30 AM" + }, + { + "kind": "emergency", + "at": "12:30", + "role": "Packer", + "count": 3, + "city": "Flint", + "state": "MI", + "shift_start": "12:30 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_010_Northland_Logistics_Chicago.json b/tests/multi-agent/scenarios/scen_010_Northland_Logistics_Chicago.json new file mode 100644 index 0000000..610e449 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_010_Northland_Logistics_Chicago.json @@ -0,0 +1,24 @@ +{ + "client": "Northland Logistics", + "date": "2026-05-01", + "events": [ + { + "kind": "baseline_fill", + "at": "15:00", + "role": "Shipping Clerk", + "count": 1, + "city": "Chicago", + "state": "IL", + "shift_start": "15:00 AM" + }, + { + "kind": "expansion", + "at": "10:00", + "role": "Warehouse Associate", + "count": 2, + "city": "Chicago", + "state": "IL", + "shift_start": "10:00 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_011_Heritage_Foods_Flint.json b/tests/multi-agent/scenarios/scen_011_Heritage_Foods_Flint.json new file mode 100644 index 0000000..96b9007 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_011_Heritage_Foods_Flint.json @@ -0,0 +1,33 @@ +{ + "client": "Heritage Foods", + "date": "2026-05-02", + "events": [ + { + "kind": "baseline_fill", + "at": "10:00", + "role": "Receiving Clerk", + "count": 3, + "city": "Flint", + "state": "MI", + "shift_start": "10:00 AM" + }, + { + "kind": "expansion", + "at": "14:30", + "role": "Shipping Clerk", + "count": 4, + "city": "Flint", + "state": "MI", + "shift_start": "14:30 AM" + }, + { + "kind": "emergency", + "at": "08:30", + "role": "Assembler", + "count": 3, + "city": "Flint", + "state": "MI", + "shift_start": "08:30 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_012_Parallel_Machining_Kansas_City.json b/tests/multi-agent/scenarios/scen_012_Parallel_Machining_Kansas_City.json new file mode 100644 index 0000000..4f13d3c --- /dev/null +++ b/tests/multi-agent/scenarios/scen_012_Parallel_Machining_Kansas_City.json @@ -0,0 +1,33 @@ +{ + "client": "Parallel Machining", + "date": "2026-05-03", + "events": [ + { + "kind": "baseline_fill", + "at": "08:00", + "role": "Warehouse Associate", + "count": 2, + "city": "Kansas City", + "state": "MO", + "shift_start": "08:00 AM" + }, + { + "kind": "recurring", + "at": "11:30", + "role": "Material Handler", + "count": 2, + "city": "Kansas City", + "state": "MO", + "shift_start": "11:30 AM" + }, + { + "kind": "expansion", + "at": "09:00", + "role": "Warehouse Associate", + "count": 5, + "city": "Kansas City", + "state": "MO", + "shift_start": "09:00 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_013_Horizon_Supply_Flint.json b/tests/multi-agent/scenarios/scen_013_Horizon_Supply_Flint.json new file mode 100644 index 0000000..1fd09fd --- /dev/null +++ b/tests/multi-agent/scenarios/scen_013_Horizon_Supply_Flint.json @@ -0,0 +1,33 @@ +{ + "client": "Horizon Supply", + "date": "2026-05-04", + "events": [ + { + "kind": "baseline_fill", + "at": "11:00", + "role": "Forklift Operator", + "count": 1, + "city": "Flint", + "state": "MI", + "shift_start": "11:00 AM" + }, + { + "kind": "recurring", + "at": "10:30", + "role": "CNC Operator", + "count": 1, + "city": "Flint", + "state": "MI", + "shift_start": "10:30 AM" + }, + { + "kind": "emergency", + "at": "16:30", + "role": "Receiving Clerk", + "count": 3, + "city": "Flint", + "state": "MI", + "shift_start": "16:30 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_014_Midway_Distribution_Indianapolis.json b/tests/multi-agent/scenarios/scen_014_Midway_Distribution_Indianapolis.json new file mode 100644 index 0000000..2569548 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_014_Midway_Distribution_Indianapolis.json @@ -0,0 +1,42 @@ +{ + "client": "Midway Distribution", + "date": "2026-05-05", + "events": [ + { + "kind": "baseline_fill", + "at": "16:00", + "role": "Assembler", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "16:00 AM" + }, + { + "kind": "recurring", + "at": "17:30", + "role": "Loader", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "17:30 AM" + }, + { + "kind": "expansion", + "at": "10:30", + "role": "Packer", + "count": 5, + "city": "Indianapolis", + "state": "IN", + "shift_start": "10:30 AM" + }, + { + "kind": "emergency", + "at": "12:00", + "role": "Loader", + "count": 4, + "city": "Indianapolis", + "state": "IN", + "shift_start": "12:00 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_015_Cornerstone_Fabrication_Kansas_City.json b/tests/multi-agent/scenarios/scen_015_Cornerstone_Fabrication_Kansas_City.json new file mode 100644 index 0000000..eced3f3 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_015_Cornerstone_Fabrication_Kansas_City.json @@ -0,0 +1,43 @@ +{ + "client": "Cornerstone Fabrication", + "date": "2026-05-06", + "events": [ + { + "kind": "baseline_fill", + "at": "14:00", + "role": "Warehouse Associate", + "count": 2, + "city": "Kansas City", + "state": "MO", + "shift_start": "14:00 AM" + }, + { + "kind": "recurring", + "at": "17:30", + "role": "Loader", + "count": 1, + "city": "Kansas City", + "state": "MO", + "shift_start": "17:30 AM" + }, + { + "kind": "emergency", + "at": "12:00", + "role": "Packer", + "count": 3, + "city": "Kansas City", + "state": "MO", + "shift_start": "12:00 AM" + }, + { + "kind": "misplacement", + "at": "16:30", + "role": "Assembler", + "count": 1, + "city": "Kansas City", + "state": "MO", + "shift_start": "16:30 AM", + "replaces_event": "14:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_016_Riverfront_Steel_Columbus.json b/tests/multi-agent/scenarios/scen_016_Riverfront_Steel_Columbus.json new file mode 100644 index 0000000..05c8321 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_016_Riverfront_Steel_Columbus.json @@ -0,0 +1,43 @@ +{ + "client": "Riverfront Steel", + "date": "2026-05-07", + "events": [ + { + "kind": "baseline_fill", + "at": "12:00", + "role": "Quality Tech", + "count": 3, + "city": "Columbus", + "state": "OH", + "shift_start": "12:00 AM" + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Machine Operator", + "count": 1, + "city": "Columbus", + "state": "OH", + "shift_start": "09:30 AM" + }, + { + "kind": "emergency", + "at": "17:30", + "role": "Assembler", + "count": 3, + "city": "Columbus", + "state": "OH", + "shift_start": "17:30 AM" + }, + { + "kind": "misplacement", + "at": "15:30", + "role": "Material Handler", + "count": 1, + "city": "Columbus", + "state": "OH", + "shift_start": "15:30 AM", + "replaces_event": "12:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_017_Summit_Industrial_Detroit.json b/tests/multi-agent/scenarios/scen_017_Summit_Industrial_Detroit.json new file mode 100644 index 0000000..f7eb935 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_017_Summit_Industrial_Detroit.json @@ -0,0 +1,24 @@ +{ + "client": "Summit Industrial", + "date": "2026-05-08", + "events": [ + { + "kind": "baseline_fill", + "at": "13:00", + "role": "Warehouse Associate", + "count": 1, + "city": "Detroit", + "state": "MI", + "shift_start": "13:00 AM" + }, + { + "kind": "recurring", + "at": "10:30", + "role": "Material Handler", + "count": 1, + "city": "Detroit", + "state": "MI", + "shift_start": "10:30 AM" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_018_Heritage_Foods_Cincinnati.json b/tests/multi-agent/scenarios/scen_018_Heritage_Foods_Cincinnati.json new file mode 100644 index 0000000..31077d5 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_018_Heritage_Foods_Cincinnati.json @@ -0,0 +1,43 @@ +{ + "client": "Heritage Foods", + "date": "2026-05-09", + "events": [ + { + "kind": "baseline_fill", + "at": "12:00", + "role": "Assembler", + "count": 1, + "city": "Cincinnati", + "state": "OH", + "shift_start": "12:00 AM" + }, + { + "kind": "recurring", + "at": "17:30", + "role": "Welder", + "count": 1, + "city": "Cincinnati", + "state": "OH", + "shift_start": "17:30 AM" + }, + { + "kind": "emergency", + "at": "13:30", + "role": "Material Handler", + "count": 2, + "city": "Cincinnati", + "state": "OH", + "shift_start": "13:30 AM" + }, + { + "kind": "misplacement", + "at": "12:00", + "role": "Receiving Clerk", + "count": 1, + "city": "Cincinnati", + "state": "OH", + "shift_start": "12:00 AM", + "replaces_event": "12:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/scen_019_Midway_Distribution_Chicago.json b/tests/multi-agent/scenarios/scen_019_Midway_Distribution_Chicago.json new file mode 100644 index 0000000..5c7dad2 --- /dev/null +++ b/tests/multi-agent/scenarios/scen_019_Midway_Distribution_Chicago.json @@ -0,0 +1,34 @@ +{ + "client": "Midway Distribution", + "date": "2026-05-10", + "events": [ + { + "kind": "baseline_fill", + "at": "11:00", + "role": "Machine Operator", + "count": 2, + "city": "Chicago", + "state": "IL", + "shift_start": "11:00 AM" + }, + { + "kind": "expansion", + "at": "17:00", + "role": "Shipping Clerk", + "count": 2, + "city": "Chicago", + "state": "IL", + "shift_start": "17:00 AM" + }, + { + "kind": "misplacement", + "at": "09:30", + "role": "Packer", + "count": 1, + "city": "Chicago", + "state": "IL", + "shift_start": "09:30 AM", + "replaces_event": "11:00" + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-001_indianapolis_assembly.json b/tests/multi-agent/scenarios/staffer_demo/S-001_indianapolis_assembly.json new file mode 100644 index 0000000..b41a028 --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-001_indianapolis_assembly.json @@ -0,0 +1,72 @@ +{ + "client": "Pioneer Assembly — Indianapolis Plant Expansion", + "date": "2026-04-24", + "contract": { + "deadline": "2026-05-26", + "budget_total_usd": 220000, + "budget_per_hour_max": 30, + "local_bonus_per_hour": 5, + "local_bonus_radius_mi": 60, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-001", + "name": "Maria Chen", + "tenure_months": 48, + "role": "senior" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:30", + "role": "Assembler", + "count": 6, + "city": "Indianapolis", + "state": "IN", + "shift_start": "07:30 AM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Quality Tech", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "09:30 AM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "expansion", + "at": "11:00", + "role": "Machine Operator", + "count": 5, + "city": "Indianapolis", + "state": "IN", + "shift_start": "11:00 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "emergency", + "at": "14:00", + "role": "Machine Operator", + "count": 3, + "deadline": "16:00", + "city": "Indianapolis", + "state": "IN", + "shift_start": "14:00 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "misplacement", + "at": "16:00", + "role": "Assembler", + "count": 1, + "replaces_event": "07:30", + "city": "Indianapolis", + "state": "IN", + "shift_start": "16:00 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-26, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-001_joliet_warehouse.json b/tests/multi-agent/scenarios/staffer_demo/S-001_joliet_warehouse.json new file mode 100644 index 0000000..30df52d --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-001_joliet_warehouse.json @@ -0,0 +1,61 @@ +{ + "client": "Midway Distribution — Joliet DC Ramp", + "date": "2026-04-23", + "contract": { + "deadline": "2026-05-12", + "budget_total_usd": 120000, + "budget_per_hour_max": 28, + "local_bonus_per_hour": 3, + "local_bonus_radius_mi": 50, + "fill_requirement": "preferred" + }, + "staffer": { + "id": "S-001", + "name": "Maria Chen", + "tenure_months": 48, + "role": "senior" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Warehouse Associate", + "count": 5, + "city": "Joliet", + "state": "IL", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "recurring", + "at": "10:00", + "role": "Forklift Operator", + "count": 3, + "city": "Joliet", + "state": "IL", + "shift_start": "10:00 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "expansion", + "at": "12:30", + "role": "Picker", + "count": 4, + "city": "Joliet", + "state": "IL", + "shift_start": "12:30 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "misplacement", + "at": "15:00", + "role": "Forklift Operator", + "count": 1, + "replaces_event": "10:00", + "city": "Joliet", + "state": "IL", + "shift_start": "15:00 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-12, fill=preferred." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-001_nashville_downtown.json b/tests/multi-agent/scenarios/staffer_demo/S-001_nashville_downtown.json new file mode 100644 index 0000000..529816a --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-001_nashville_downtown.json @@ -0,0 +1,72 @@ +{ + "client": "Riverline Logistics — Nashville Downtown Build-Out", + "date": "2026-04-22", + "contract": { + "deadline": "2026-05-19", + "budget_total_usd": 180000, + "budget_per_hour_max": 32, + "local_bonus_per_hour": 4, + "local_bonus_radius_mi": 75, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-001", + "name": "Maria Chen", + "tenure_months": 48, + "role": "senior" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Welder", + "count": 4, + "city": "Nashville", + "state": "TN", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "expansion", + "at": "08:30", + "role": "Packaging Operator", + "count": 6, + "city": "Nashville", + "state": "TN", + "shift_start": "08:30 AM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "baseline_fill", + "at": "09:00", + "role": "Shipping Clerk", + "count": 2, + "city": "Nashville", + "state": "TN", + "shift_start": "09:00 AM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "emergency", + "at": "13:00", + "role": "Welder", + "count": 2, + "deadline": "15:00", + "city": "Nashville", + "state": "TN", + "shift_start": "13:00 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "misplacement", + "at": "15:30", + "role": "Packaging Operator", + "count": 1, + "replaces_event": "08:30", + "city": "Nashville", + "state": "TN", + "shift_start": "15:30 PM", + "scenario_note": "Staffed by Maria Chen (senior, 48mo). Contract deadline 2026-05-19, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-002_indianapolis_assembly.json b/tests/multi-agent/scenarios/staffer_demo/S-002_indianapolis_assembly.json new file mode 100644 index 0000000..f8aeddb --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-002_indianapolis_assembly.json @@ -0,0 +1,72 @@ +{ + "client": "Pioneer Assembly — Indianapolis Plant Expansion", + "date": "2026-04-27", + "contract": { + "deadline": "2026-05-26", + "budget_total_usd": 220000, + "budget_per_hour_max": 30, + "local_bonus_per_hour": 5, + "local_bonus_radius_mi": 60, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-002", + "name": "James Park", + "tenure_months": 14, + "role": "mid" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:30", + "role": "Assembler", + "count": 6, + "city": "Indianapolis", + "state": "IN", + "shift_start": "07:30 AM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Quality Tech", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "09:30 AM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "expansion", + "at": "11:00", + "role": "Machine Operator", + "count": 5, + "city": "Indianapolis", + "state": "IN", + "shift_start": "11:00 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "emergency", + "at": "14:00", + "role": "Machine Operator", + "count": 3, + "deadline": "16:00", + "city": "Indianapolis", + "state": "IN", + "shift_start": "14:00 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "misplacement", + "at": "16:00", + "role": "Assembler", + "count": 1, + "replaces_event": "07:30", + "city": "Indianapolis", + "state": "IN", + "shift_start": "16:00 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-26, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-002_joliet_warehouse.json b/tests/multi-agent/scenarios/staffer_demo/S-002_joliet_warehouse.json new file mode 100644 index 0000000..dcba63a --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-002_joliet_warehouse.json @@ -0,0 +1,61 @@ +{ + "client": "Midway Distribution — Joliet DC Ramp", + "date": "2026-04-26", + "contract": { + "deadline": "2026-05-12", + "budget_total_usd": 120000, + "budget_per_hour_max": 28, + "local_bonus_per_hour": 3, + "local_bonus_radius_mi": 50, + "fill_requirement": "preferred" + }, + "staffer": { + "id": "S-002", + "name": "James Park", + "tenure_months": 14, + "role": "mid" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Warehouse Associate", + "count": 5, + "city": "Joliet", + "state": "IL", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "recurring", + "at": "10:00", + "role": "Forklift Operator", + "count": 3, + "city": "Joliet", + "state": "IL", + "shift_start": "10:00 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "expansion", + "at": "12:30", + "role": "Picker", + "count": 4, + "city": "Joliet", + "state": "IL", + "shift_start": "12:30 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "misplacement", + "at": "15:00", + "role": "Forklift Operator", + "count": 1, + "replaces_event": "10:00", + "city": "Joliet", + "state": "IL", + "shift_start": "15:00 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-12, fill=preferred." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-002_nashville_downtown.json b/tests/multi-agent/scenarios/staffer_demo/S-002_nashville_downtown.json new file mode 100644 index 0000000..e560270 --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-002_nashville_downtown.json @@ -0,0 +1,72 @@ +{ + "client": "Riverline Logistics — Nashville Downtown Build-Out", + "date": "2026-04-25", + "contract": { + "deadline": "2026-05-19", + "budget_total_usd": 180000, + "budget_per_hour_max": 32, + "local_bonus_per_hour": 4, + "local_bonus_radius_mi": 75, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-002", + "name": "James Park", + "tenure_months": 14, + "role": "mid" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Welder", + "count": 4, + "city": "Nashville", + "state": "TN", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "expansion", + "at": "08:30", + "role": "Packaging Operator", + "count": 6, + "city": "Nashville", + "state": "TN", + "shift_start": "08:30 AM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "baseline_fill", + "at": "09:00", + "role": "Shipping Clerk", + "count": 2, + "city": "Nashville", + "state": "TN", + "shift_start": "09:00 AM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "emergency", + "at": "13:00", + "role": "Welder", + "count": 2, + "deadline": "15:00", + "city": "Nashville", + "state": "TN", + "shift_start": "13:00 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "misplacement", + "at": "15:30", + "role": "Packaging Operator", + "count": 1, + "replaces_event": "08:30", + "city": "Nashville", + "state": "TN", + "shift_start": "15:30 PM", + "scenario_note": "Staffed by James Park (mid, 14mo). Contract deadline 2026-05-19, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-003_indianapolis_assembly.json b/tests/multi-agent/scenarios/staffer_demo/S-003_indianapolis_assembly.json new file mode 100644 index 0000000..f49da0f --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-003_indianapolis_assembly.json @@ -0,0 +1,72 @@ +{ + "client": "Pioneer Assembly — Indianapolis Plant Expansion", + "date": "2026-04-30", + "contract": { + "deadline": "2026-05-26", + "budget_total_usd": 220000, + "budget_per_hour_max": 30, + "local_bonus_per_hour": 5, + "local_bonus_radius_mi": 60, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-003", + "name": "Sam Torres", + "tenure_months": 4, + "role": "junior" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:30", + "role": "Assembler", + "count": 6, + "city": "Indianapolis", + "state": "IN", + "shift_start": "07:30 AM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Quality Tech", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "09:30 AM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "expansion", + "at": "11:00", + "role": "Machine Operator", + "count": 5, + "city": "Indianapolis", + "state": "IN", + "shift_start": "11:00 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "emergency", + "at": "14:00", + "role": "Machine Operator", + "count": 3, + "deadline": "16:00", + "city": "Indianapolis", + "state": "IN", + "shift_start": "14:00 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "misplacement", + "at": "16:00", + "role": "Assembler", + "count": 1, + "replaces_event": "07:30", + "city": "Indianapolis", + "state": "IN", + "shift_start": "16:00 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-26, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-003_joliet_warehouse.json b/tests/multi-agent/scenarios/staffer_demo/S-003_joliet_warehouse.json new file mode 100644 index 0000000..b276ac9 --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-003_joliet_warehouse.json @@ -0,0 +1,61 @@ +{ + "client": "Midway Distribution — Joliet DC Ramp", + "date": "2026-04-29", + "contract": { + "deadline": "2026-05-12", + "budget_total_usd": 120000, + "budget_per_hour_max": 28, + "local_bonus_per_hour": 3, + "local_bonus_radius_mi": 50, + "fill_requirement": "preferred" + }, + "staffer": { + "id": "S-003", + "name": "Sam Torres", + "tenure_months": 4, + "role": "junior" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Warehouse Associate", + "count": 5, + "city": "Joliet", + "state": "IL", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "recurring", + "at": "10:00", + "role": "Forklift Operator", + "count": 3, + "city": "Joliet", + "state": "IL", + "shift_start": "10:00 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "expansion", + "at": "12:30", + "role": "Picker", + "count": 4, + "city": "Joliet", + "state": "IL", + "shift_start": "12:30 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "misplacement", + "at": "15:00", + "role": "Forklift Operator", + "count": 1, + "replaces_event": "10:00", + "city": "Joliet", + "state": "IL", + "shift_start": "15:00 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-12, fill=preferred." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-003_nashville_downtown.json b/tests/multi-agent/scenarios/staffer_demo/S-003_nashville_downtown.json new file mode 100644 index 0000000..9969ca0 --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-003_nashville_downtown.json @@ -0,0 +1,72 @@ +{ + "client": "Riverline Logistics — Nashville Downtown Build-Out", + "date": "2026-04-28", + "contract": { + "deadline": "2026-05-19", + "budget_total_usd": 180000, + "budget_per_hour_max": 32, + "local_bonus_per_hour": 4, + "local_bonus_radius_mi": 75, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-003", + "name": "Sam Torres", + "tenure_months": 4, + "role": "junior" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Welder", + "count": 4, + "city": "Nashville", + "state": "TN", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "expansion", + "at": "08:30", + "role": "Packaging Operator", + "count": 6, + "city": "Nashville", + "state": "TN", + "shift_start": "08:30 AM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "baseline_fill", + "at": "09:00", + "role": "Shipping Clerk", + "count": 2, + "city": "Nashville", + "state": "TN", + "shift_start": "09:00 AM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "emergency", + "at": "13:00", + "role": "Welder", + "count": 2, + "deadline": "15:00", + "city": "Nashville", + "state": "TN", + "shift_start": "13:00 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "misplacement", + "at": "15:30", + "role": "Packaging Operator", + "count": 1, + "replaces_event": "08:30", + "city": "Nashville", + "state": "TN", + "shift_start": "15:30 PM", + "scenario_note": "Staffed by Sam Torres (junior, 4mo). Contract deadline 2026-05-19, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-004_indianapolis_assembly.json b/tests/multi-agent/scenarios/staffer_demo/S-004_indianapolis_assembly.json new file mode 100644 index 0000000..0b5790b --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-004_indianapolis_assembly.json @@ -0,0 +1,72 @@ +{ + "client": "Pioneer Assembly — Indianapolis Plant Expansion", + "date": "2026-05-03", + "contract": { + "deadline": "2026-05-26", + "budget_total_usd": 220000, + "budget_per_hour_max": 30, + "local_bonus_per_hour": 5, + "local_bonus_radius_mi": 60, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-004", + "name": "Alex Rivera", + "tenure_months": 1, + "role": "trainee" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:30", + "role": "Assembler", + "count": 6, + "city": "Indianapolis", + "state": "IN", + "shift_start": "07:30 AM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "recurring", + "at": "09:30", + "role": "Quality Tech", + "count": 2, + "city": "Indianapolis", + "state": "IN", + "shift_start": "09:30 AM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "expansion", + "at": "11:00", + "role": "Machine Operator", + "count": 5, + "city": "Indianapolis", + "state": "IN", + "shift_start": "11:00 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "emergency", + "at": "14:00", + "role": "Machine Operator", + "count": 3, + "deadline": "16:00", + "city": "Indianapolis", + "state": "IN", + "shift_start": "14:00 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-26, fill=paramount." + }, + { + "kind": "misplacement", + "at": "16:00", + "role": "Assembler", + "count": 1, + "replaces_event": "07:30", + "city": "Indianapolis", + "state": "IN", + "shift_start": "16:00 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-26, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-004_joliet_warehouse.json b/tests/multi-agent/scenarios/staffer_demo/S-004_joliet_warehouse.json new file mode 100644 index 0000000..fcf570e --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-004_joliet_warehouse.json @@ -0,0 +1,61 @@ +{ + "client": "Midway Distribution — Joliet DC Ramp", + "date": "2026-05-02", + "contract": { + "deadline": "2026-05-12", + "budget_total_usd": 120000, + "budget_per_hour_max": 28, + "local_bonus_per_hour": 3, + "local_bonus_radius_mi": 50, + "fill_requirement": "preferred" + }, + "staffer": { + "id": "S-004", + "name": "Alex Rivera", + "tenure_months": 1, + "role": "trainee" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Warehouse Associate", + "count": 5, + "city": "Joliet", + "state": "IL", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "recurring", + "at": "10:00", + "role": "Forklift Operator", + "count": 3, + "city": "Joliet", + "state": "IL", + "shift_start": "10:00 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "expansion", + "at": "12:30", + "role": "Picker", + "count": 4, + "city": "Joliet", + "state": "IL", + "shift_start": "12:30 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-12, fill=preferred." + }, + { + "kind": "misplacement", + "at": "15:00", + "role": "Forklift Operator", + "count": 1, + "replaces_event": "10:00", + "city": "Joliet", + "state": "IL", + "shift_start": "15:00 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-12, fill=preferred." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/S-004_nashville_downtown.json b/tests/multi-agent/scenarios/staffer_demo/S-004_nashville_downtown.json new file mode 100644 index 0000000..8b32392 --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/S-004_nashville_downtown.json @@ -0,0 +1,72 @@ +{ + "client": "Riverline Logistics — Nashville Downtown Build-Out", + "date": "2026-05-01", + "contract": { + "deadline": "2026-05-19", + "budget_total_usd": 180000, + "budget_per_hour_max": 32, + "local_bonus_per_hour": 4, + "local_bonus_radius_mi": 75, + "fill_requirement": "paramount" + }, + "staffer": { + "id": "S-004", + "name": "Alex Rivera", + "tenure_months": 1, + "role": "trainee" + }, + "events": [ + { + "kind": "baseline_fill", + "at": "07:00", + "role": "Welder", + "count": 4, + "city": "Nashville", + "state": "TN", + "shift_start": "07:00 AM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "expansion", + "at": "08:30", + "role": "Packaging Operator", + "count": 6, + "city": "Nashville", + "state": "TN", + "shift_start": "08:30 AM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "baseline_fill", + "at": "09:00", + "role": "Shipping Clerk", + "count": 2, + "city": "Nashville", + "state": "TN", + "shift_start": "09:00 AM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "emergency", + "at": "13:00", + "role": "Welder", + "count": 2, + "deadline": "15:00", + "city": "Nashville", + "state": "TN", + "shift_start": "13:00 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-19, fill=paramount." + }, + { + "kind": "misplacement", + "at": "15:30", + "role": "Packaging Operator", + "count": 1, + "replaces_event": "08:30", + "city": "Nashville", + "state": "TN", + "shift_start": "15:30 PM", + "scenario_note": "Staffed by Alex Rivera (trainee, 1mo). Contract deadline 2026-05-19, fill=paramount." + } + ] +} \ No newline at end of file diff --git a/tests/multi-agent/scenarios/staffer_demo/manifest.json b/tests/multi-agent/scenarios/staffer_demo/manifest.json new file mode 100644 index 0000000..ebefa6f --- /dev/null +++ b/tests/multi-agent/scenarios/staffer_demo/manifest.json @@ -0,0 +1,77 @@ +{ + "count": 12, + "scenarios": [ + { + "file": "S-001_nashville_downtown.json", + "staffer": "Maria Chen", + "contract": "nashville_downtown", + "client": "Riverline Logistics — Nashville Downtown Build-Out" + }, + { + "file": "S-001_joliet_warehouse.json", + "staffer": "Maria Chen", + "contract": "joliet_warehouse", + "client": "Midway Distribution — Joliet DC Ramp" + }, + { + "file": "S-001_indianapolis_assembly.json", + "staffer": "Maria Chen", + "contract": "indianapolis_assembly", + "client": "Pioneer Assembly — Indianapolis Plant Expansion" + }, + { + "file": "S-002_nashville_downtown.json", + "staffer": "James Park", + "contract": "nashville_downtown", + "client": "Riverline Logistics — Nashville Downtown Build-Out" + }, + { + "file": "S-002_joliet_warehouse.json", + "staffer": "James Park", + "contract": "joliet_warehouse", + "client": "Midway Distribution — Joliet DC Ramp" + }, + { + "file": "S-002_indianapolis_assembly.json", + "staffer": "James Park", + "contract": "indianapolis_assembly", + "client": "Pioneer Assembly — Indianapolis Plant Expansion" + }, + { + "file": "S-003_nashville_downtown.json", + "staffer": "Sam Torres", + "contract": "nashville_downtown", + "client": "Riverline Logistics — Nashville Downtown Build-Out" + }, + { + "file": "S-003_joliet_warehouse.json", + "staffer": "Sam Torres", + "contract": "joliet_warehouse", + "client": "Midway Distribution — Joliet DC Ramp" + }, + { + "file": "S-003_indianapolis_assembly.json", + "staffer": "Sam Torres", + "contract": "indianapolis_assembly", + "client": "Pioneer Assembly — Indianapolis Plant Expansion" + }, + { + "file": "S-004_nashville_downtown.json", + "staffer": "Alex Rivera", + "contract": "nashville_downtown", + "client": "Riverline Logistics — Nashville Downtown Build-Out" + }, + { + "file": "S-004_joliet_warehouse.json", + "staffer": "Alex Rivera", + "contract": "joliet_warehouse", + "client": "Midway Distribution — Joliet DC Ramp" + }, + { + "file": "S-004_indianapolis_assembly.json", + "staffer": "Alex Rivera", + "contract": "indianapolis_assembly", + "client": "Pioneer Assembly — Indianapolis Plant Expansion" + } + ] +} \ No newline at end of file