#3: Worker profile modal shows past playbook history

Click any worker card → modal now includes a 'Past Playbooks' section
that queries successful_playbooks_live for any row where this worker's
name appears in the result field. Shows up to 8 most recent with
operation, timestamp, approach, and context.

When empty: 'No prior playbooks for NAME yet. First placement builds
the first entry.' — makes the institutional-memory claim visible to
the recruiter: the system is tracking everyone, not just the ones
that sealed this session.

Also added Call / SMS / No-show buttons to the modal action row
(matching the card-level buttons from #1). Every worker-card path
now trains the system.

Closes the user-visible side of Phase 19 — patterns surface during
search (Pass A), boosts fire in ranking (Phase 19 core), and now
the worker's own profile shows the full history that informs those
boosts. Institutional memory legibility, per J's ask.
This commit is contained in:
root 2026-04-20 16:21:27 -05:00
parent cdd12a1438
commit 1f56630d5d

View File

@ -619,11 +619,51 @@ function showProfile(workerData){
body.appendChild(srcBox);
}
// 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
// see at a glance: "this person has been used for X role Y times."
addSection(body,'Past Playbooks','Where this worker has been endorsed before');
var histBox=document.createElement('div');histBox.id='hist-'+(workerData.nm||'anon').replace(/\s/g,'-');
histBox.style.cssText='background:#0d1117;border-radius:8px;padding:14px;margin-bottom:20px;font-size:12px;color:#8b949e;line-height:1.6';
histBox.textContent='Loading history...';body.appendChild(histBox);
var city=(workerData.loc||'').split(',')[0].trim();
var state=(workerData.loc||'').split(',').pop().trim();
var nameLit=(workerData.nm||'').replace(/'/g,"''");
var sqlQ="SELECT operation, approach, context, timestamp FROM successful_playbooks_live "
+"WHERE result LIKE '%"+nameLit+"%' ORDER BY timestamp DESC LIMIT 8";
api('/sql',{sql:sqlQ}).then(function(r){
histBox.textContent='';
var rows=(r&&r.rows)||[];
if(rows.length===0){
histBox.textContent='No prior playbooks for '+(workerData.nm||'this worker')+' yet. First placement builds the first entry.';
histBox.style.color='#484f58';return;
}
var hdr2=document.createElement('div');hdr2.style.cssText='color:#3fb950;font-weight:600;margin-bottom:8px;font-size:11px';
hdr2.textContent=rows.length+' past endorsement'+(rows.length!==1?'s':'');
histBox.appendChild(hdr2);
rows.forEach(function(pb){
var row=document.createElement('div');row.style.cssText='padding:6px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;border-left:2px solid #2ea043';
var op=document.createElement('div');op.style.cssText='color:#e6edf3;font-weight:500;font-size:12px';
op.textContent=pb.operation||'(unknown op)';row.appendChild(op);
var meta=document.createElement('div');meta.style.cssText='color:#8b949e;font-size:10px;margin-top:2px';
var ts=(pb.timestamp||'').substring(0,10);
meta.textContent=ts+' · '+(pb.approach||'').slice(0,40)+(pb.context?' · '+pb.context.slice(0,30):'');
row.appendChild(meta);histBox.appendChild(row);
});
}).catch(function(){
histBox.textContent='(history unavailable)';histBox.style.color='#484f58';
});
// Actions
var acts=document.createElement('div');acts.style.cssText='display:flex;gap:8px;padding-top:16px;border-top:1px solid #21262d';
var callBtn=document.createElement('button');callBtn.style.cssText='flex:1;padding:12px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#1f3d68;color:#58a6ff';callBtn.textContent='Call';
callBtn.onclick=function(){logAction(workerData,'call',callBtn)};
var smsBtn=document.createElement('button');smsBtn.style.cssText='flex:1;padding:12px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#0d261a;color:#3fb950';smsBtn.textContent='Send SMS';
acts.appendChild(callBtn);acts.appendChild(smsBtn);body.appendChild(acts);
smsBtn.onclick=function(){logAction(workerData,'sms',smsBtn)};
var noshowBtn=document.createElement('button');noshowBtn.style.cssText='flex:1;padding:12px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#3a1a1a;color:#f85149';noshowBtn.textContent='No-show';
noshowBtn.onclick=function(){logAction(workerData,'failure',noshowBtn)};
acts.appendChild(callBtn);acts.appendChild(smsBtn);acts.appendChild(noshowBtn);body.appendChild(acts);
modal.appendChild(body);overlay.appendChild(modal);document.body.appendChild(overlay);
}