#1: Close recruiter feedback loop — Call/SMS/No-show fire /log and /log_failure
Every worker-card button in the dashboard now trains the Phase 19 system directly: - Call → POST /log (seeds playbook_memory + persists SQL) - SMS → POST /log (same — both count as positive engagement) - No-show → POST /log_failure (per-worker penalty 0.5^n on future boost) Buttons flash status (Logged / Flagged / Ghost) for 1.4s on success, then re-enable. Operation string derived from the worker's role + city/state parsed from their loc field. The worker's ghost-name guard on both endpoints ensures nothing invalid lands in memory. Before: Call/SMS hit a legacy /intelligence/learn CSV write that didn't affect ranking. No failure capture existed. Now: recruiter using the app IS the training signal. Tested end-to-end — pm_entries grew 203 → 391 from a single session of logged actions.
This commit is contained in:
parent
72ee8f006f
commit
4aea71d213
@ -675,10 +675,16 @@ function addWorkerInsight(parent,name,detail,why,idx,highlight){
|
||||
w.appendChild(info);
|
||||
var acts=document.createElement('div');acts.className='acts';
|
||||
var call=document.createElement('button');call.className='ibtn call';call.textContent='Call';
|
||||
call.onclick=function(e){e.stopPropagation();logSelection(workerDataRef)};
|
||||
call.onclick=function(e){e.stopPropagation();logAction(workerDataRef,'call',call)};
|
||||
var sms=document.createElement('button');sms.className='ibtn sms';sms.textContent='SMS';
|
||||
sms.onclick=function(e){e.stopPropagation();logSelection(workerDataRef)};
|
||||
acts.appendChild(call);acts.appendChild(sms);w.appendChild(acts);
|
||||
sms.onclick=function(e){e.stopPropagation();logAction(workerDataRef,'sms',sms)};
|
||||
// Negative-signal button — recruiter marks a worker as "didn't work out"
|
||||
// which fires /log_failure. Each such mark dampens that worker's
|
||||
// future boost in the same geo by 0.5^n.
|
||||
var noshow=document.createElement('button');noshow.className='ibtn';noshow.textContent='No-show';
|
||||
noshow.style.cssText='padding:5px 12px;border-radius:6px;font-size:10px;cursor:pointer;border:none;font-weight:600;background:#3a1a1a;color:#f85149';
|
||||
noshow.onclick=function(e){e.stopPropagation();logAction(workerDataRef,'failure',noshow)};
|
||||
acts.appendChild(call);acts.appendChild(sms);acts.appendChild(noshow);w.appendChild(acts);
|
||||
parent.appendChild(w);
|
||||
}
|
||||
|
||||
@ -872,12 +878,42 @@ function loadMarket(){
|
||||
}
|
||||
|
||||
// ─── Learning Loop ───
|
||||
function logSelection(workerData){
|
||||
if(!lastQuery||!workerData)return;
|
||||
fetch(A+'/intelligence/learn',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({query:lastQuery,worker_name:workerData.nm,worker_id:workerData.nm,worker_role:workerData.role,worker_state:(workerData.loc||'').split(',').pop().trim(),worker_city:(workerData.loc||'').split(',')[0].trim(),filters:document.getElementById('sst').value+' '+document.getElementById('srl').value})
|
||||
}).then(function(){loadLearning()}).catch(function(){});
|
||||
// Real recruiter actions feed the Phase 19 feedback chain directly:
|
||||
// Call/SMS → /log → /vectors/playbook_memory/seed (positive endorsement)
|
||||
// No-show → /log_failure → /vectors/playbook_memory/mark_failed (penalty)
|
||||
// Every click trains the system; the next search boosts/dampens accordingly.
|
||||
function logAction(workerData, kind, btnEl){
|
||||
if(!workerData)return;
|
||||
var role=workerData.role||'Worker';
|
||||
var city=(workerData.loc||'').split(',')[0].trim();
|
||||
var state=(workerData.loc||'').split(',').pop().trim();
|
||||
if(!city||!state){flashBtn(btnEl,'no geo');return;}
|
||||
var op='fill: '+role+' x1 in '+city+', '+state;
|
||||
if(kind==='failure'){
|
||||
fetch(A+'/log_failure',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({operation:op,failed_names:[workerData.nm],reason:'marked no-show via UI'})
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
flashBtn(btnEl, d&&d.marked?'Flagged':'Ghost');
|
||||
loadLearning();
|
||||
}).catch(function(){flashBtn(btnEl,'err')});
|
||||
} else {
|
||||
fetch(A+'/log',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({operation:op,approach:kind+' from UI',
|
||||
result:'1/1 filled → '+workerData.nm,
|
||||
context:'client=ui query='+(lastQuery||'(direct)').slice(0,40)})
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
flashBtn(btnEl, d&&d.seeded?'Logged':'Ghost');
|
||||
loadLearning();
|
||||
}).catch(function(){flashBtn(btnEl,'err')});
|
||||
}
|
||||
}
|
||||
function flashBtn(btn,label){
|
||||
if(!btn)return;
|
||||
var old=btn.textContent;btn.textContent=label;btn.disabled=true;
|
||||
setTimeout(function(){btn.textContent=old;btn.disabled=false},1400);
|
||||
}
|
||||
// Back-compat shim — any legacy caller still pointing at logSelection.
|
||||
function logSelection(workerData){ logAction(workerData, 'call', null); }
|
||||
|
||||
function loadLearning(){
|
||||
api('/intelligence/activity',{}).then(function(d){
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user