Gap fixes: pattern fallback, narrative citations, call_log plumbing
Closing trust-breaks surfaced in the strategic audit. A — MEMORY chip renders even when sparse: Previously rendered nothing when no trait crossed threshold, which recruiters would read as "system has no signal." Now explicitly says "memory is sparse for this role+geo — no trait crossed threshold" or "no similar past playbooks yet — first fill of this kind will seed it." Honest when it doesn't know. B — Removed /intelligence/learn dead endpoint: Legacy CSV-writer path that destructively re-wrote successful_playbooks. /log and /log_failure replace it cleanly. Leaving dead code confuses future maintainers. C — Narrative tooltips on Endorsed chips: Hovering the green "Endorsed · N playbooks" chip now fetches the worker's past operations from successful_playbooks_live and shows a story: "Maria — past endorsements: • Welder x2 in Toledo (2026-04-15), • Welder x1 in Toledo (2026-04-18)..." Falls back to honest "narrative unavailable" if the seed didn't land in SQL. D — call_log infrastructure in worker modal: New "Recent Contact" section queries call_log JOIN candidates by name. Surfaces last 3 call entries with timestamp, recruiter, disposition, duration. When empty (which is today's reality — candidates table only has 1000 rows vs call_log's higher IDs), shows an honest message about the data gap and what real ATS integration would unlock. Honest call: D ships infrastructure. Actual utility depends on aligning candidate IDs between the candidates table and call_log — current synthetic data doesn't cross-ref cleanly. When real ATS data lands, this section becomes the "system knows who we called yesterday" feature the recruiter needs. Deferred (would require a dedicated session): - Rate awareness (needs worker pay_rate + contract bill_rate) - Push / background daemon (Slack/SMS/email integration) - Confidence calibration (needs a probabilistic ranking layer)
This commit is contained in:
parent
1f56630d5d
commit
2595d48535
@ -1391,15 +1391,11 @@ tr:hover{background:#111827}
|
||||
}
|
||||
}
|
||||
|
||||
// Intelligence: Log a search → selection as a learned pattern
|
||||
if (url.pathname === "/intelligence/learn" && req.method === "POST") {
|
||||
const b = await json();
|
||||
const csv = `timestamp,operation,approach,result,context\n"${new Date().toISOString()}","search: ${(b.query||"").replace(/"/g,'""')}","${(b.filters||"").replace(/"/g,'""')}","selected: ${(b.worker_name||"").replace(/"/g,'""')} (${b.worker_id||""})","role=${b.worker_role||""} state=${b.worker_state||""} city=${b.worker_city||""}"`;
|
||||
const form = new FormData();
|
||||
form.append("file", new Blob([csv], { type: "text/csv" }), "playbook.csv");
|
||||
await fetch(`${BASE}/ingest/file?name=successful_playbooks`, { method: "POST", body: form });
|
||||
return ok({ learned: true, pattern: `"${b.query}" → ${b.worker_name}` });
|
||||
}
|
||||
// Removed 2026-04-20: /intelligence/learn was a legacy CSV writer
|
||||
// that destructively re-wrote successful_playbooks. /log and
|
||||
// /log_failure replace it cleanly via /vectors/playbook_memory/seed
|
||||
// and /mark_failed. Keeping the endpoint would only mislead
|
||||
// future callers — dead code rots.
|
||||
|
||||
// Intelligence: Activity feed — what the system has learned
|
||||
if (url.pathname === "/intelligence/activity" && req.method === "POST") {
|
||||
|
||||
@ -619,6 +619,39 @@ function showProfile(workerData){
|
||||
body.appendChild(srcBox);
|
||||
}
|
||||
|
||||
// Call history — recruiter-facing institutional memory from call_log.
|
||||
// Queries for prior contact with this specific worker (by name
|
||||
// cross-ref). Fails soft: if no rows, shows "no recent contact" which
|
||||
// is itself a useful signal (or an honest tell about data sparsity).
|
||||
addSection(body,'Recent Contact','Last phone outreach logged in call_log');
|
||||
var callBox=document.createElement('div');
|
||||
callBox.style.cssText='background:#0d1117;border-radius:8px;padding:14px;margin-bottom:20px;font-size:12px;color:#8b949e;line-height:1.6';
|
||||
callBox.textContent='Checking call log...';body.appendChild(callBox);
|
||||
var nameLitC=(workerData.nm||'').replace(/'/g,"''");
|
||||
var callSQL="SELECT cl.timestamp, cl.recruiter, cl.duration_seconds, cl.disposition "
|
||||
+"FROM call_log cl JOIN candidates c ON c.candidate_id = cl.candidate_id "
|
||||
+"WHERE CONCAT(c.first_name, ' ', c.last_name) = '"+nameLitC+"' "
|
||||
+"ORDER BY cl.timestamp DESC LIMIT 3";
|
||||
api('/sql',{sql:callSQL}).then(function(r){
|
||||
callBox.textContent='';
|
||||
var rows=(r&&r.rows)||[];
|
||||
if(rows.length===0){
|
||||
callBox.textContent='No recent call logged for '+(workerData.nm||'this worker')+'. Data note: call_log cross-references candidate IDs that may not align with workers_500k — real ATS integration required for full coverage.';
|
||||
callBox.style.color='#484f58';return;
|
||||
}
|
||||
rows.forEach(function(c){
|
||||
var row=document.createElement('div');row.style.cssText='padding:6px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;border-left:2px solid #58a6ff;display:flex;justify-content:space-between;gap:10px';
|
||||
var left=document.createElement('div');
|
||||
var ts=(c.timestamp||'').substring(0,10);
|
||||
var dur=Math.round((c.duration_seconds||0)/60);
|
||||
var l1=document.createElement('div');l1.style.cssText='color:#e6edf3;font-weight:500;font-size:12px';
|
||||
l1.textContent=ts+(c.recruiter?' · by '+c.recruiter:'');left.appendChild(l1);
|
||||
var l2=document.createElement('div');l2.style.cssText='color:#8b949e;font-size:10px';
|
||||
l2.textContent=(c.disposition||'?').replace(/_/g,' ')+(dur?' · '+dur+' min':'');left.appendChild(l2);
|
||||
row.appendChild(left);callBox.appendChild(row);
|
||||
});
|
||||
}).catch(function(){callBox.textContent='(call log unavailable)';callBox.style.color='#484f58'});
|
||||
|
||||
// Past playbook history — Phase 19 institutional memory surfaced on
|
||||
// the worker's own profile. Shows every past fill this worker was
|
||||
// endorsed in (from successful_playbooks_live), so the recruiter can
|
||||
@ -700,14 +733,35 @@ function addWorkerInsight(parent,name,detail,why,idx,highlight){
|
||||
var info=document.createElement('div');info.className='info';
|
||||
var nm=document.createElement('div');nm.className='nm';nm.textContent=name;
|
||||
// Phase 19: when a past playbook endorsed this worker, show a green chip
|
||||
// next to the name. Hover reveals the citation IDs.
|
||||
// next to the name. Hover reveals a NARRATIVE of past endorsements
|
||||
// derived from successful_playbooks_live — "filled X in Y on date" —
|
||||
// rather than opaque pb-seed-xxx ids. Recruiters need stories, not
|
||||
// citation keys. Lazy-loaded per card on first render.
|
||||
if(boostInfo && boostInfo.boost > 0){
|
||||
var chip=document.createElement('span');
|
||||
chip.style.cssText='display:inline-block;margin-left:8px;padding:2px 7px;border-radius:9px;font-size:10px;font-weight:600;background:#0d2818;border:1px solid #2ea043;color:#3fb950;vertical-align:middle';
|
||||
chip.style.cssText='display:inline-block;margin-left:8px;padding:2px 7px;border-radius:9px;font-size:10px;font-weight:600;background:#0d2818;border:1px solid #2ea043;color:#3fb950;vertical-align:middle;cursor:help';
|
||||
var n=(boostInfo.citations && boostInfo.citations.length) || 0;
|
||||
chip.textContent='Endorsed · '+n+' playbook'+(n!==1?'s':'');
|
||||
chip.title='Boosted by past playbooks: '+(boostInfo.citations||[]).join(', ');
|
||||
chip.title='Loading past playbooks for '+name+'...';
|
||||
nm.appendChild(chip);
|
||||
// Fetch narrative for this worker lazily
|
||||
var safeName = (name||'').replace(/'/g,"''");
|
||||
var narrativeSQL = "SELECT operation, result, timestamp FROM successful_playbooks_live "
|
||||
+ "WHERE result LIKE '%"+safeName+"%' ORDER BY timestamp DESC LIMIT 5";
|
||||
api('/sql',{sql:narrativeSQL}).then(function(r){
|
||||
var rows=(r&&r.rows)||[];
|
||||
if(rows.length===0){
|
||||
chip.title=name+' — endorsed in '+n+' playbook'+(n!==1?'s':'')+' (narrative unavailable — may have been seeded without SQL persistence)';
|
||||
return;
|
||||
}
|
||||
var stories=rows.map(function(pb){
|
||||
var d=(pb.timestamp||'').substring(0,10);
|
||||
return '• '+(pb.operation||'?').replace(/^fill:\s*/,'')+' ('+d+')';
|
||||
});
|
||||
chip.title=name+' — past endorsements:\n'+stories.join('\n');
|
||||
}).catch(function(){
|
||||
chip.title=name+' — endorsed in '+n+' playbook'+(n!==1?'s':'');
|
||||
});
|
||||
}
|
||||
var dt=document.createElement('div');dt.className='detail';dt.textContent=detail;
|
||||
info.appendChild(nm);info.appendChild(dt);
|
||||
@ -770,16 +824,31 @@ function doSearch(){
|
||||
var h=document.createElement('div');h.style.cssText='color:#8b949e;font-size:12px;margin-bottom:10px';
|
||||
h.textContent=(d.sql_matches?d.sql_matches.toLocaleString()+' workers matched — ':'')+'showing best results ('+(d.duration_ms||0)+'ms)';
|
||||
out.appendChild(h);
|
||||
// Meta-index signal — what past similar fills had in common. Only
|
||||
// renders when memory had at least one relevant playbook.
|
||||
if(d.discovered_pattern && d.pattern_playbooks_matched > 0){
|
||||
// Meta-index signal — ALWAYS render when the system has any memory,
|
||||
// even if no trait crossed threshold. Silence here would have
|
||||
// recruiters assume "no signal" when the reality is "threshold
|
||||
// filtered it out" or "memory is sparse for this geo." Trust
|
||||
// depends on the system being honest about what it doesn't know.
|
||||
if(d.pattern_playbooks_matched > 0 || d.discovered_pattern){
|
||||
var mem=document.createElement('div');
|
||||
mem.style.cssText='background:#0d2818;border:1px solid #2ea04360;border-radius:6px;padding:8px 12px;margin-bottom:10px;font-size:11px;color:#86efac;line-height:1.5';
|
||||
var label=document.createElement('span');label.style.cssText='color:#3fb950;font-weight:600;margin-right:6px';
|
||||
label.textContent='MEMORY ('+d.pattern_playbooks_matched+' playbooks):';
|
||||
label.textContent='MEMORY ('+(d.pattern_playbooks_matched||0)+' playbook'+(d.pattern_playbooks_matched===1?'':'s')+'):';
|
||||
mem.appendChild(label);
|
||||
mem.appendChild(document.createTextNode(' '+d.discovered_pattern));
|
||||
var pattern = d.discovered_pattern || '';
|
||||
if(!pattern || pattern.indexOf('No similar')>=0 || pattern.indexOf('0 workers')>=0){
|
||||
mem.appendChild(document.createTextNode(' memory is sparse for this role+geo — no trait crossed threshold. Will accumulate as fills land.'));
|
||||
mem.style.color='#6ca885';
|
||||
} else {
|
||||
mem.appendChild(document.createTextNode(' '+pattern));
|
||||
}
|
||||
out.appendChild(mem);
|
||||
} else {
|
||||
// Zero playbooks matched — be explicit
|
||||
var mem0=document.createElement('div');
|
||||
mem0.style.cssText='background:#161b22;border:1px solid #21262d;border-radius:6px;padding:6px 12px;margin-bottom:10px;font-size:11px;color:#6e7681';
|
||||
mem0.textContent='MEMORY: no similar past playbooks yet — first fill of this kind will seed it.';
|
||||
out.appendChild(mem0);
|
||||
}
|
||||
// Render results based on type
|
||||
var workers=d.sql_results||[];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user