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:
+37
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user