diff --git a/mcp-server/search.html b/mcp-server/search.html index bb6e1fb..5a58d16 100644 --- a/mcp-server/search.html +++ b/mcp-server/search.html @@ -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.filled0.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); } }