Contextual insights: workers and bench strength driven by today's actual contracts
- loadDay() now runs simulation first, extracts unfilled roles/states, then builds SQL queries filtered to what's actually needed today - "Workers Available for Today's Open Contracts" replaces generic top-5 list - Each worker shows which gap they fill: "Could fill 4 open Loader spots" - Bench Strength section scoped to states with active contracts + open slot counts - Every refresh produces different workers because contracts change each time Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
be7436b6f0
commit
e9b5498f43
@ -74,23 +74,54 @@ function api(path,body){
|
||||
}
|
||||
|
||||
function loadDay(){
|
||||
Promise.all([
|
||||
api('/simulation/run',{}),
|
||||
api('/sql',{sql:"SELECT role, COUNT(*) total, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable FROM workers_500k GROUP BY role ORDER BY total DESC LIMIT 5"}),
|
||||
api('/sql',{sql:"SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, certifications FROM workers_500k WHERE CAST(reliability AS DOUBLE)>0.95 ORDER BY CAST(reliability AS DOUBLE) DESC LIMIT 5"}),
|
||||
api('/sql',{sql:"SELECT state, COUNT(*) cnt, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) good FROM workers_500k GROUP BY state ORDER BY cnt DESC LIMIT 5"})
|
||||
]).then(function(results){
|
||||
var sim=results[0], roles=results[1], topWorkers=results[2], coverage=results[3];
|
||||
// Step 1: run simulation first
|
||||
api('/simulation/run',{}).then(function(sim){
|
||||
var today=sim.days?sim.days[0]:null;
|
||||
var sum=sim.summary||{};
|
||||
document.getElementById('status').textContent=sum.total_filled+'/'+sum.total_needed+' positions filled across '+sum.total_contracts+' contracts';
|
||||
renderMain(today,sum,roles,topWorkers,coverage);
|
||||
|
||||
// Step 2: extract what's ACTUALLY needed from today's contracts
|
||||
var contracts=today?today.contracts:[];
|
||||
var needRoles={}, needStates={}, urgentRoles=[];
|
||||
contracts.forEach(function(c){
|
||||
if(c.filled<c.headcount){
|
||||
needRoles[c.role]=(needRoles[c.role]||0)+(c.headcount-c.filled);
|
||||
needStates[c.state]=(needStates[c.state]||0)+(c.headcount-c.filled);
|
||||
if(c.priority==='urgent'||c.priority==='high') urgentRoles.push(c.role);
|
||||
}
|
||||
});
|
||||
|
||||
// Build contextual queries based on today's gaps
|
||||
var roleList=Object.keys(needRoles);
|
||||
var stateList=Object.keys(needStates);
|
||||
var roleFilter=roleList.length?roleList.map(function(r){return"'"+r.replace(/'/g,"''")+"'"}).join(','):"'Forklift Operator'";
|
||||
var stateFilter=stateList.length?stateList.map(function(s){return"'"+s.replace(/'/g,"''")+"'"}).join(','):"'IL'";
|
||||
|
||||
// Contextual: workers who match TODAY's unfilled roles+states, randomized within tier
|
||||
var topSql="SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, certifications "+
|
||||
"FROM workers_500k WHERE role IN ("+roleFilter+") AND state IN ("+stateFilter+") "+
|
||||
"AND CAST(reliability AS DOUBLE)>0.85 ORDER BY CAST(reliability AS DOUBLE) DESC LIMIT 8";
|
||||
|
||||
// Coverage for states that matter today
|
||||
var covSql="SELECT state, COUNT(*) cnt, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) good "+
|
||||
"FROM workers_500k WHERE state IN ("+stateFilter+") GROUP BY state ORDER BY cnt DESC";
|
||||
|
||||
// Roles breakdown for today's needed roles
|
||||
var roleSql="SELECT role, COUNT(*) total, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable "+
|
||||
"FROM workers_500k WHERE role IN ("+roleFilter+") GROUP BY role ORDER BY total DESC";
|
||||
|
||||
return Promise.all([roleSql, topSql, covSql].map(function(sql){
|
||||
return api('/sql',{sql:sql});
|
||||
})).then(function(results){
|
||||
var roles=results[0], topWorkers=results[1], coverage=results[2];
|
||||
renderMain(today,sum,roles,topWorkers,coverage,needRoles,needStates);
|
||||
});
|
||||
}).catch(function(e){
|
||||
document.getElementById('main').textContent='Error loading: '+e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function renderMain(today,sum,roles,topWorkers,coverage){
|
||||
function renderMain(today,sum,roles,topWorkers,coverage,needRoles,needStates){
|
||||
var el=document.getElementById('main');
|
||||
el.textContent='';
|
||||
|
||||
@ -159,37 +190,48 @@ function renderMain(today,sum,roles,topWorkers,coverage){
|
||||
}
|
||||
}
|
||||
|
||||
// INSIGHT 2: Top available workers they should know about
|
||||
// INSIGHT 2: Top available workers — contextual to today's unfilled contracts
|
||||
if(topWorkers&&topWorkers.rows&&topWorkers.rows.length){
|
||||
var ins3=makeInsight('info','Your Strongest Available Workers',
|
||||
'These workers have 95%+ reliability — they rarely no-show and clients request them back',
|
||||
'Based on placement history and performance tracking. Consider these first for high-priority contracts.');
|
||||
// Build a contextual headline from today's gaps
|
||||
var gapRoles=needRoles?Object.keys(needRoles):[];
|
||||
var gapStates=needStates?Object.keys(needStates):[];
|
||||
var headline='Workers Available for Today\'s Open Contracts';
|
||||
var sub='Matched to the roles and locations you need filled right now';
|
||||
if(gapRoles.length<=3&&gapRoles.length>0){
|
||||
headline='Top '+gapRoles.join(', ')+' Workers Available';
|
||||
sub='These workers match your unfilled contracts in '+gapStates.join(', ');
|
||||
}
|
||||
var ins3=makeInsight('info',headline,sub,
|
||||
'Filtered to roles and states with open positions today. Reliability 85%+.');
|
||||
topWorkers.rows.forEach(function(w,i){
|
||||
// Show which contract gap this worker could fill
|
||||
var gapNote='';
|
||||
if(needRoles&&needRoles[w.role]){gapNote='→ Could fill '+needRoles[w.role]+' open '+w.role+' spot'+(needRoles[w.role]>1?'s':'')}
|
||||
var wd={nm:w.name,role:w.role,loc:w.city+', '+w.state,skills:[],
|
||||
certs:(w.certifications||'').split(',').filter(function(c){return c.trim()&&c.trim()!=='none'}),
|
||||
rel:w.rel,avail:0,arch:'',hasM:true};
|
||||
addWorkerInsight(ins3,w.name,w.role+' · '+w.city+', '+w.state,
|
||||
'Reliability: '+w.rel*100+'% · Certs: '+(w.certifications||'none'),i,null,wd);
|
||||
'Reliability: '+Math.round(w.rel*100)+'%'+(w.certifications&&w.certifications!=='none'?' · Certs: '+w.certifications:'')+(gapNote?' · '+gapNote:''),i,null,wd);
|
||||
});
|
||||
el.appendChild(ins3);
|
||||
}
|
||||
|
||||
// INSIGHT 3: Coverage warning
|
||||
if(coverage&&coverage.rows){
|
||||
var thin=coverage.rows.filter(function(r){return r.good/r.cnt<0.45});
|
||||
if(thin.length){
|
||||
var ins4=makeInsight('warning','Bench Strength Alert',
|
||||
'Some states have fewer reliable workers than usual',
|
||||
'The system monitors your worker pool and flags when coverage drops. Consider recruiting in these areas.');
|
||||
thin.forEach(function(r){
|
||||
var pct=Math.round(r.good/r.cnt*100);
|
||||
var d=document.createElement('div');d.style.cssText='display:flex;justify-content:space-between;padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:4px;font-size:13px';
|
||||
var l=document.createElement('span');l.textContent=r.state+' — '+r.cnt.toLocaleString()+' workers';l.style.color='#f0f6fc';
|
||||
var v=document.createElement('span');v.textContent=pct+'% reliable';v.style.color=pct<40?'#f85149':'#d29922';
|
||||
d.appendChild(l);d.appendChild(v);ins4.appendChild(d);
|
||||
});
|
||||
el.appendChild(ins4);
|
||||
}
|
||||
// INSIGHT 3: Coverage for states with active contracts today
|
||||
if(coverage&&coverage.rows&&coverage.rows.length){
|
||||
var stateLabel=gapStates.length?' in '+gapStates.join(', '):'';
|
||||
var ins4=makeInsight('warning','Bench Strength'+stateLabel,
|
||||
'Worker pool depth for states with open contracts today',
|
||||
'Shows how many reliable workers (80%+ reliability) you have in states where you need to fill positions right now.');
|
||||
coverage.rows.forEach(function(r){
|
||||
var pct=Math.round(r.good/r.cnt*100);
|
||||
var openSlots=needStates&&needStates[r.state]?needStates[r.state]:0;
|
||||
var d=document.createElement('div');d.style.cssText='display:flex;justify-content:space-between;padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:4px;font-size:13px';
|
||||
var l=document.createElement('span');l.style.color='#f0f6fc';
|
||||
l.textContent=r.state+' — '+r.cnt.toLocaleString()+' workers'+(openSlots?' · '+openSlots+' open slot'+(openSlots>1?'s':''):'');
|
||||
var v=document.createElement('span');v.textContent=pct+'% reliable';v.style.color=pct<40?'#f85149':'#d29922';
|
||||
d.appendChild(l);d.appendChild(v);ins4.appendChild(d);
|
||||
});
|
||||
el.appendChild(ins4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user