demo: G — per-staffer hot-swap index (synthetic coordinator personas)

Same corpus, different relevance gradient per staffer. Three personas
defined in mcp-server/index.ts STAFFERS roster (Maria/IL, Devon/IN,
Aisha/WI), each with a primary state + secondary cities. Server-side:
/intelligence/chat smart_search accepts a staffer_id body field; when
set, defaults state to the staffer's territory and labels the playbook
context as theirs. The playbook patterns query also defaults its geo
to the staffer's primary city/state, so the recurring-skills/cert
breakdowns reflect what they actually fill, not the global IL prior.

Front-end: a staffer selector dropdown beside the existing state/role
filters. Picking a staffer auto-pins state to their territory, shows
a greeting line, relabels the MEMORY panel as MARIA'S/DEVON'S/AISHA'S
MEMORY, and sends staffer_id to chat for scoping.

Dropdown is populated from /staffers (NOT /api/staffers — the generic
/api/* passthrough sends everything under /api/ to the Rust gateway,
which doesn't own the roster). loadStaffers runs at window-load
independently of loadDay's Promise.all so the dropdown populates even
if simulation/SQL inits error out.

Verified end-to-end via playwright. Same q="forklift operators":
  no staffer  → 509 workers across MI/OH/IA, MEMORY label
  as Devon    → 89 IN-only (Fort Wayne, Terre Haute), DEVON'S MEMORY
  as Aisha    → 16 WI-only (Milwaukee, Madison, Green Bay), AISHA'S MEMORY
As Maria with q="8 production workers near 60607":
  tags: headcount: 8 · zip 60607 → Chicago, IL · role: production · city: Chicago
  20 workers, MARIA'S MEMORY label, top results in Chicago zips

Closes the demo-side build of A-G from the persona plan:
  A. zip → city/state, B. headcount, C. bare-name, D. temporal,
  E. late-worker triage, F. contractor anchor, G. per-staffer index.
This commit is contained in:
root
2026-04-28 06:01:04 -05:00
parent 677065de76
commit 5f0beffe80
2 changed files with 139 additions and 8 deletions
+89 -2
View File
@@ -22,6 +22,58 @@ import { buildPermitBrief } from "./entity.js";
const BASE = process.env.LAKEHOUSE_URL || "http://localhost:3100";
const PORT = parseInt(process.env.MCP_PORT || "3700");
// ─── Staffer roster — used by the per-staffer hot-swap index (G). ────
//
// J's vision: each staffer has their own molded view of the corpus.
// When Maria searches, the system surfaces *Maria's* prior fills and
// her territory's playbooks first. When Aisha searches, the same
// corpus gets re-shaped to her geo and recent activity. This is what
// generic CRM fast-search can't do: a relevance gradient that
// compounds with each staffer's own signal.
//
// First implementation is geography-based — each staffer has a primary
// state and a list of cities they recruit for. Playbook queries get
// scoped to that territory when staffer_id is provided. As the system
// accumulates per-staffer signal (call_log assignments, email threads,
// SMS history), the scope expands beyond geography.
//
// Adding a staffer: append to this list. The /api/staffers endpoint
// exposes the public-safe fields to the UI dropdown.
const STAFFERS: Array<{
id: string;
name: string;
display: string;
territory: { state: string; cities: string[] };
greeting: string;
}> = [
{
id: "maria",
name: "Maria",
display: "Maria · Chicago coordinator",
territory: { state: "IL", cities: ["Chicago", "Joliet", "Rockford", "Peoria", "Springfield", "Decatur"] },
greeting: "Maria's territory: Illinois warehouse + manufacturing fills",
},
{
id: "devon",
name: "Devon",
display: "Devon · Indiana coordinator",
territory: { state: "IN", cities: ["Indianapolis", "Fort Wayne", "South Bend", "Evansville", "Bloomington", "Terre Haute"] },
greeting: "Devon's territory: Indiana production + assembly fills",
},
{
id: "aisha",
name: "Aisha",
display: "Aisha · Wisconsin/Michigan coordinator",
territory: { state: "WI", cities: ["Milwaukee", "Madison", "Green Bay", "Detroit", "Grand Rapids", "Lansing"] },
greeting: "Aisha's territory: Wisconsin + Michigan logistics",
},
];
function lookupStaffer(id: string | undefined): typeof STAFFERS[number] | null {
if (!id) return null;
return STAFFERS.find((s) => s.id === id) || null;
}
const MODE = process.env.MCP_TRANSPORT || "http"; // "stdio" or "http"
// Active trace for the current request — set per-request in the HTTP handler
@@ -824,6 +876,20 @@ async function main() {
}
}
// Staffer roster — read by the UI dropdown so each coordinator
// can act under their own identity (per-staffer hot-swap index).
if (url.pathname === "/api/staffers" || url.pathname === "/staffers") {
return ok({
staffers: STAFFERS.map((s) => ({
id: s.id,
name: s.name,
display: s.display,
territory: s.territory,
greeting: s.greeting,
})),
});
}
if (url.pathname === "/system/summary") {
const [ds, indexes, workersCount, candsCount] = await Promise.all([
api("GET", "/catalog/datasets").catch(() => [] as any),
@@ -1862,6 +1928,21 @@ async function main() {
const explicitState = String(b.state || "").trim().toUpperCase();
const explicitRole = String(b.role || "").trim();
// (G) Per-staffer context. When the UI sends a staffer_id,
// playbook queries scope to that staffer's territory — their
// recent fills, their geo's recurring patterns. The corpus is
// the same for everyone; the relevance gradient is unique to
// each staffer because each pulls a different shape from it.
const staffer = lookupStaffer(String(b.staffer_id || "").trim());
// If the staffer has a territory and the user hasn't already
// pinned a state/city via dropdown or NL, default the search
// to their territory. They can override by typing a different
// city or selecting a different state.
if (staffer && !explicitState) {
filters.push(`state = '${staffer.territory.state}'`);
understood.push(`as ${staffer.name}: ${staffer.territory.state}`);
}
// (B) Headcount parser — coordinator says "8 production
// workers", "I need 12 forklift operators", "5 welders by
// Friday". Match a leading or embedded count followed by
@@ -2048,9 +2129,14 @@ async function main() {
// Derive role+geo for the pattern query so the meta-index
// surface lines up with what the user actually asked for.
// (G) When a staffer is acting, default the geo to their
// primary territory — their playbook view is shaped by
// where they actually fill, not the global Chicago/IL prior.
const roleForPatterns = understood.find(u => u.startsWith('role:'))?.split(': ')[1] || q;
const cityForPatterns = understood.find(u => u.startsWith('city:'))?.split(': ')[1] || 'Chicago';
const stateForPatterns = understood.find(u => u.startsWith('state:'))?.split(': ')[1] || 'IL';
const cityForPatterns = understood.find(u => u.startsWith('city:'))?.split(': ')[1]
|| staffer?.territory.cities[0] || 'Chicago';
const stateForPatterns = understood.find(u => u.startsWith('state:'))?.split(': ')[1]
|| staffer?.territory.state || 'IL';
const [searchR, directR, patternR] = await Promise.all([
api("POST", "/vectors/hybrid", {
@@ -2084,6 +2170,7 @@ async function main() {
return ok({
type: "smart_search",
summary: `Found ${searchR.sql_matches || 0} workers matching your criteria${understood.length ? ' (' + understood.join(', ') + ')' : ''}`,
staffer: staffer ? { id: staffer.id, name: staffer.name, display: staffer.display, territory: staffer.territory } : null,
understood,
sql_results: sqlWorkers,
vector_results: vectorWorkers,