Rate/margin awareness: implied pay rate per worker, bill rate per contract

Closes one of the Path 1 trust-break gaps. The scenario we kept flagging:
recruiter calls the system's top pick, worker quotes $35/hr, contract
pays $28/hr. First broken call kills the demo. This fixes it.

Heuristic (no schema change, derived at query time):
- Per worker: implied_pay_rate = role_base + (reliability × 4) + archetype_bump
  role_base: Electrician $28, Welder $26, Machine Op $24, Maint $26,
    Forklift Op $20, Loader $17, Warehouse Assoc $17, Quality Tech $23,
    Production Worker $18 ...
  archetype bump: specialist +4, leader +3, reliable +1, else 0
- Per contract: implied_bill_rate = role_base × 1.4
  (40% markup — industry norm: pay + overhead + insurance + margin)
- Worker is 'over_bill_rate' when implied_pay_rate > contract's bill_rate
  on a candidate-by-candidate basis

Backend (mcp-server/index.ts):
- ROLE_BASE_PAY_RATE + BILL_MARKUP constants
- impliedPayRate(worker), impliedBillRate(role) functions
- parseWorkerChunk() extracts role/reliability/archetype from vector text
- enrichWithRates() attaches implied_pay_rate on every /vectors/hybrid
  source response. Called from /search and /intelligence/permit_contracts.
- /search accepts optional max_pay_rate number — if set, filters out
  workers above that rate and reports pay_rate_filtered_out count.
- /intelligence/permit_contracts returns implied_bill_rate per contract
  AND over_bill_rate boolean per candidate.

Frontend (search.html):
- Live Contracts cards show 'bill rate: $X/hr' under the headcount line
- Each candidate shows 'pay $X/hr' in the sub-line; red 'Over bill rate'
  chip next to name when their pay exceeds the contract's bill rate
  (hover reveals the exact numbers and why it's flagged)
- Main 'Search all workers' results now include 'pay $X/hr' in the
  why-text (computeImpliedPayRate mirrored client-side to match Bun)

End-to-end verified live:
- Masonry Work permit, bill_rate $25.20/hr
  Kathleen M. Gutierrez pay $25.56/hr → 🔴 OVER
  Melissa C. Rivera pay $20.88/hr → 🟢 OK
- /search with max_pay_rate:32 filtered out 1 Toledo Welder above $32
- Main search shows 'pay $28.64/hr' in each result row

When real ATS data replaces synthetic workers_500k, same UI — the
client's real pay_rate column substitutes for the heuristic.
This commit is contained in:
root
2026-04-20 18:56:51 -05:00
parent a117ae8b38
commit af3856b103
2 changed files with 129 additions and 3 deletions
+37 -1
View File
@@ -222,6 +222,22 @@ function api(path,body){
return fetch(A+path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(function(r){return r.json()})
}
// Mirror of the Bun-side pay-rate formula so client-side renderers
// (main search, modal) can show a rate when they only have worker
// fields, not a pre-enriched hybrid source. Keep in sync with
// impliedPayRate() in mcp-server/index.ts.
var ROLE_BASE_PAY={Electrician:28,Welder:26,'Machine Operator':24,'Maintenance Tech':26,
'Forklift Operator':20,Loader:17,'Warehouse Associate':17,'Material Handler':18,
'Production Worker':18,'Quality Tech':23,'Line Lead':22,Assembler:18,'Shipping Clerk':19};
function computeImpliedPayRate(role,rel,archetype){
var base=ROLE_BASE_PAY[role||'']||19;
var r=typeof rel==='string'?parseFloat(rel):(rel||0.5);
var relBump=(isFinite(r)?r:0.5)*4;
var a=(archetype||'').toLowerCase();
var archBump=a==='specialist'?4:a==='leader'?3:a==='reliable'?1:0;
return Math.round((base+relBump+archBump)*100)/100;
}
function loadLiveContracts(){
// Pair live Chicago permits with our 500K worker bench and the
// meta-index discovered patterns for each role+geo. This is the
@@ -258,6 +274,13 @@ function loadLiveContracts(){
var sub=document.createElement('div');sub.style.cssText='color:#545d68;font-size:10px;text-align:right';
sub.textContent='pool: '+(prop.pool_size||'?').toLocaleString()+' available';
right.appendChild(sub);
// Rate awareness: show implied bill rate per contract
if(c.implied_bill_rate){
var rate=document.createElement('div');
rate.style.cssText='color:#d29922;font-size:10px;text-align:right;margin-top:3px';
rate.textContent='bill rate: $'+c.implied_bill_rate.toFixed(2)+'/hr';
right.appendChild(rate);
}
hdr.appendChild(left);hdr.appendChild(right);card.appendChild(hdr);
// Description
if(p.description){
@@ -286,8 +309,17 @@ function loadLiveContracts(){
chip.textContent='Endorsed · '+(cand.playbook_citations||[]).length+' playbook'+((cand.playbook_citations||[]).length===1?'':'s');
nm.appendChild(chip);
}
// Rate warning chip when worker's pay exceeds the contract's bill rate
if(cand.over_bill_rate){
var warn=document.createElement('span');warn.style.cssText='margin-left:6px;padding:2px 7px;border-radius:9px;font-size:9px;font-weight:600;background:#3a1a1a;border:1px solid #f85149;color:#fca5a5;vertical-align:middle';
warn.textContent='Over bill rate';
warn.title='Worker\'s implied pay rate ($'+(cand.implied_pay_rate||0).toFixed(2)+'/hr) exceeds contract bill rate ($'+(c.implied_bill_rate||0).toFixed(2)+'/hr) — margin at risk';
nm.appendChild(warn);
}
var sub2=document.createElement('div');sub2.style.cssText='color:#545d68;font-size:10px';
sub2.textContent=cand.doc_id+' · score '+(cand.score||0).toFixed(3);
var subText=cand.doc_id+' · score '+(cand.score||0).toFixed(3);
if(cand.implied_pay_rate) subText+=' · pay $'+cand.implied_pay_rate.toFixed(2)+'/hr';
sub2.textContent=subText;
info.appendChild(nm);info.appendChild(sub2);
row.appendChild(av);row.appendChild(info);
card.appendChild(row);
@@ -933,6 +965,10 @@ function doSearch(){
var why='Reliability: '+Math.round((w.rel||0)*100)+'%';
if(w.avail)why+=' · Available: '+Math.round(w.avail*100)+'%';
if(w.archetype)why+=' · '+w.archetype;
// Derive and show implied pay rate client-side so the main search
// surface matches the live-contracts cards. Same formula as Bun.
var rate=computeImpliedPayRate(w.role,w.rel,w.archetype);
if(rate) why+=' · pay $'+rate.toFixed(2)+'/hr';
addWorkerInsight(out,w.name,detail.join(' · '),why,i,null,wd);
});
} else {