Learning loop + smart search on datalake page
Learning Loop: - /intelligence/learn endpoint logs search→selection as playbook entry - /intelligence/activity returns learning stats, patterns, and recent activity - Call/SMS buttons trigger logSelection() — records what query led to what pick - "System Learning" card on main page shows searches logged, patterns detected, and recent activity feed with timestamps - Every search-selection pair becomes institutional knowledge stored in the lakehouse Smart Search on Main Page: - doSearch() now routes through /intelligence/chat (smart NL parser) - Extracts role, city, state, availability, reliability from natural language - Shows understanding tags so staffer sees what the system parsed - Returns workers with ZIP codes, availability %, reliability %, archetype - "reliable forklift operator available in Nashville" → 10 Nashville forklift operators with ZIP codes, all 86-98% reliable, all available — 372ms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
df71ac7156
commit
bba5b826a3
@ -1034,6 +1034,41 @@ tr:hover{background:#111827}
|
||||
return new Response(Bun.file(import.meta.dir + "/console.html"));
|
||||
}
|
||||
|
||||
// Intelligence: Log a search → selection as a learned pattern
|
||||
if (url.pathname === "/intelligence/learn" && req.method === "POST") {
|
||||
const b = await json();
|
||||
const csv = `timestamp,operation,approach,result,context\n"${new Date().toISOString()}","search: ${(b.query||"").replace(/"/g,'""')}","${(b.filters||"").replace(/"/g,'""')}","selected: ${(b.worker_name||"").replace(/"/g,'""')} (${b.worker_id||""})","role=${b.worker_role||""} state=${b.worker_state||""} city=${b.worker_city||""}"`;
|
||||
const form = new FormData();
|
||||
form.append("file", new Blob([csv], { type: "text/csv" }), "playbook.csv");
|
||||
await fetch(`${BASE}/ingest/file?name=successful_playbooks`, { method: "POST", body: form });
|
||||
return ok({ learned: true, pattern: `"${b.query}" → ${b.worker_name}` });
|
||||
}
|
||||
|
||||
// Intelligence: Activity feed — what the system has learned
|
||||
if (url.pathname === "/intelligence/activity" && req.method === "POST") {
|
||||
const start = Date.now();
|
||||
const [playbooksR, searchCountR, simCountR] = await Promise.all([
|
||||
api("POST", "/query/sql", { sql: "SELECT * FROM successful_playbooks ORDER BY timestamp DESC LIMIT 20" }).catch(() => ({ rows: [] })),
|
||||
api("POST", "/query/sql", { sql: "SELECT COUNT(*) cnt FROM successful_playbooks WHERE operation LIKE 'search:%'" }).catch(() => ({ rows: [{ cnt: 0 }] })),
|
||||
api("POST", "/query/sql", { sql: "SELECT COUNT(*) cnt FROM successful_playbooks WHERE operation LIKE 'week_simulation%'" }).catch(() => ({ rows: [{ cnt: 0 }] })),
|
||||
]);
|
||||
// Extract learned patterns — which workers get picked for which queries
|
||||
const patterns: Record<string, number> = {};
|
||||
for (const p of (playbooksR.rows || [])) {
|
||||
if (p.operation?.startsWith("search:")) {
|
||||
const key = p.operation.replace("search: ", "").trim();
|
||||
patterns[key] = (patterns[key] || 0) + 1;
|
||||
}
|
||||
}
|
||||
return ok({
|
||||
playbooks: playbooksR.rows || [],
|
||||
search_count: searchCountR.rows?.[0]?.cnt || 0,
|
||||
sim_count: simCountR.rows?.[0]?.cnt || 0,
|
||||
learned_patterns: Object.entries(patterns).map(([q, c]) => ({ query: q, times: c })).sort((a, b) => b.times - a.times),
|
||||
duration_ms: Date.now() - start,
|
||||
});
|
||||
}
|
||||
|
||||
// Intelligence Brief — parallel analytics across 500K profiles
|
||||
if (url.pathname === "/intelligence/brief" && req.method === "POST") {
|
||||
const start = Date.now();
|
||||
|
||||
@ -56,18 +56,21 @@ body{font-family:-apple-system,system-ui,sans-serif;background:#0b0f19;color:#c9
|
||||
<div class="content">
|
||||
<div id="main"><div class="ld">Analyzing your contracts and workers...</div></div>
|
||||
|
||||
<div id="learning"></div>
|
||||
|
||||
<details class="sa"><summary>Search workers...</summary><div class="inner">
|
||||
<input type="text" id="sq" placeholder="Describe who you need — the AI understands plain English" onkeydown="if(event.key==='Enter')doSearch()">
|
||||
<div class="srow"><select id="sst"><option value="">Any State</option></select>
|
||||
<select id="srl"><option value="">Any Role</option></select></div>
|
||||
<button class="sbtn" onclick="doSearch()">Find Workers</button><div id="sresults"></div></div></details>
|
||||
<div class="ft"><a href="proof">How this works</a></div>
|
||||
<div class="ft"><a href="console">Intelligence Console</a> · <a href="proof">How this works</a></div>
|
||||
</div>
|
||||
<script>
|
||||
var P=location.pathname.indexOf('/lakehouse')>=0?'/lakehouse':'';
|
||||
var A=location.origin+P;
|
||||
var AC=['#1a2744','#1a3a2a','#2a1a3a','#3a2a1a','#1a3a3a','#2a2a1a'];
|
||||
window.addEventListener('load',loadDay);
|
||||
var lastQuery='';
|
||||
window.addEventListener('load',function(){loadDay();loadLearning()});
|
||||
|
||||
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()})
|
||||
@ -523,7 +526,9 @@ 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)};
|
||||
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);
|
||||
parent.appendChild(w);
|
||||
}
|
||||
@ -547,22 +552,125 @@ function pw(text){
|
||||
|
||||
function doSearch(){
|
||||
var q=document.getElementById('sq').value.trim();if(!q)return;
|
||||
lastQuery=q;
|
||||
var st=document.getElementById('sst').value,rl=document.getElementById('srl').value;
|
||||
// Append dropdown filters to the query so the smart parser picks them up
|
||||
var fullQ=q;
|
||||
if(st&&q.indexOf(st)<0)fullQ+=' in '+st;
|
||||
if(rl&&q.toLowerCase().indexOf(rl.toLowerCase())<0)fullQ+=' '+rl;
|
||||
var out=document.getElementById('sresults');out.textContent='Finding the best matches...';
|
||||
var f="CAST(reliability AS DOUBLE)>=0.5";
|
||||
if(st)f+=" AND state='"+st+"'";if(rl)f+=" AND role='"+rl+"'";
|
||||
fetch(A+'/search',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({question:q,index_name:'workers_500k_v1',sql_filter:f,dataset:'workers_500k',id_column:'worker_id',top_k:8,generate:false})
|
||||
fetch(A+'/intelligence/chat',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({message:fullQ})
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
out.textContent='';var src=d.sources||[];if(!src.length){out.textContent='No matches found. Try different terms or broaden filters.';return}
|
||||
out.textContent='';
|
||||
// Show what the system understood
|
||||
if(d.understood&&d.understood.length){
|
||||
var tags=document.createElement('div');tags.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px';
|
||||
d.understood.forEach(function(u){
|
||||
var tag=document.createElement('span');tag.style.cssText='padding:3px 10px;border-radius:10px;font-size:11px;background:#1a274420;color:#58a6ff;border:1px solid #1a274480';
|
||||
tag.textContent=u;tags.appendChild(tag);
|
||||
});
|
||||
out.appendChild(tags);
|
||||
}
|
||||
var h=document.createElement('div');h.style.cssText='color:#8b949e;font-size:12px;margin-bottom:10px';
|
||||
h.textContent='Found '+(d.sql_matches||0).toLocaleString()+' workers matching your filters — showing the '+src.length+' best matches ('+(d.duration_ms||0)+'ms)';
|
||||
h.textContent=(d.sql_matches?d.sql_matches.toLocaleString()+' workers matched — ':'')+'showing best results ('+(d.duration_ms||0)+'ms)';
|
||||
out.appendChild(h);
|
||||
src.forEach(function(s,i){
|
||||
var w=pw(s.chunk_text);if(!w.nm)w.nm=s.doc_id;
|
||||
addWorkerInsight(out,w.nm,[w.role,w.loc].filter(Boolean).join(' · '),
|
||||
(w.hasM?'Reliability: '+Math.round(w.rel*100)+'% · ':'')+(w.certs.length?'Certs: '+w.certs.join(', '):'AI match: '+Math.round(s.score*100)+'%'),i,null,w);
|
||||
});
|
||||
// Render results based on type
|
||||
var workers=d.sql_results||[];
|
||||
if(workers.length){
|
||||
workers.forEach(function(w,i){
|
||||
var wd={nm:w.name,role:w.role||'',loc:(w.city||'')+', '+(w.state||''),skills:(w.skills||'').split(',').filter(function(s){return s.trim()}),
|
||||
certs:(w.certifications||'').split(',').filter(function(c){return c.trim()&&c.trim()!=='none'}),
|
||||
rel:w.rel||0,avail:w.avail||0,arch:w.archetype||'',hasM:true};
|
||||
var detail=[w.role,w.city+', '+w.state];
|
||||
if(w.zip)detail.push('ZIP: '+w.zip);
|
||||
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;
|
||||
addWorkerInsight(out,w.name,detail.join(' · '),why,i,null,wd);
|
||||
});
|
||||
} else {
|
||||
// Fall back to vector results
|
||||
var vr=d.results||d.vector_results||[];
|
||||
if(!vr.length){out.appendChild(document.createTextNode('No matches found. Try different terms.'));return}
|
||||
vr.forEach(function(s,i){
|
||||
var w=pw(s.text||s.chunk_text||'');if(!w.nm)w.nm=s.doc_id;
|
||||
addWorkerInsight(out,w.nm,[w.role,w.loc].filter(Boolean).join(' · '),
|
||||
(w.hasM?'Reliability: '+Math.round(w.rel*100)+'% · ':'')+(w.certs.length?'Certs: '+w.certs.join(', '):'AI match: '+Math.round((s.score||0)*100)+'%'),i,null,w);
|
||||
});
|
||||
}
|
||||
}).catch(function(e){out.textContent='Error: '+e.message});
|
||||
}
|
||||
|
||||
// ─── 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(){});
|
||||
}
|
||||
|
||||
function loadLearning(){
|
||||
api('/intelligence/activity',{}).then(function(d){
|
||||
var el=document.getElementById('learning');
|
||||
el.textContent='';
|
||||
var total=(d.search_count||0)+(d.sim_count||0);
|
||||
if(total===0&&(!d.playbooks||!d.playbooks.length))return; // nothing to show yet
|
||||
|
||||
var card=document.createElement('div');card.className='insight info';
|
||||
var lb=document.createElement('div');lb.className='label';lb.textContent='SYSTEM LEARNING';
|
||||
var hl=document.createElement('div');hl.className='headline';hl.textContent='The System Gets Smarter With Every Use';
|
||||
var sub=document.createElement('div');sub.className='sub';
|
||||
sub.textContent='Every search, every placement, every simulation teaches the system what works. '+total+' operations logged so far.';
|
||||
card.appendChild(lb);card.appendChild(hl);card.appendChild(sub);
|
||||
|
||||
// Stats row
|
||||
var stats=document.createElement('div');stats.style.cssText='display:flex;gap:16px;margin-bottom:12px';
|
||||
addLearnStat(stats,d.search_count||0,'Searches Logged','#58a6ff');
|
||||
addLearnStat(stats,d.sim_count||0,'Simulations Run','#3fb950');
|
||||
addLearnStat(stats,(d.learned_patterns||[]).length,'Patterns Detected','#bc8cff');
|
||||
card.appendChild(stats);
|
||||
|
||||
// Learned patterns
|
||||
if(d.learned_patterns&&d.learned_patterns.length){
|
||||
var ph=document.createElement('div');ph.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin-bottom:6px';
|
||||
ph.textContent='Learned Search Patterns';card.appendChild(ph);
|
||||
d.learned_patterns.slice(0,5).forEach(function(p){
|
||||
var row=document.createElement('div');row.style.cssText='display:flex;justify-content:space-between;padding:5px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:12px';
|
||||
var q=document.createElement('span');q.style.color='#c9d1d9';q.textContent='"'+p.query+'"';
|
||||
var c=document.createElement('span');c.style.cssText='color:#58a6ff;font-weight:600';c.textContent=p.times+'x';
|
||||
row.appendChild(q);row.appendChild(c);card.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Recent activity feed
|
||||
if(d.playbooks&&d.playbooks.length){
|
||||
var ah=document.createElement('div');ah.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin:10px 0 6px';
|
||||
ah.textContent='Recent Activity';card.appendChild(ah);
|
||||
d.playbooks.slice(0,5).forEach(function(p){
|
||||
var row=document.createElement('div');row.style.cssText='padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:11px';
|
||||
var op=document.createElement('div');op.style.color='#f0f6fc';op.textContent=p.operation||'';
|
||||
var det=document.createElement('div');det.style.cssText='color:#484f58;margin-top:2px';
|
||||
det.textContent=(p.result||'')+(p.context?' · '+p.context:'');
|
||||
var ts=document.createElement('div');ts.style.cssText='color:#2d333b;font-size:10px;margin-top:2px';
|
||||
ts.textContent=p.timestamp?new Date(p.timestamp).toLocaleString():'';
|
||||
row.appendChild(op);row.appendChild(det);row.appendChild(ts);card.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Explainer
|
||||
var ex=document.createElement('div');ex.style.cssText='font-size:11px;color:#484f58;margin-top:10px;font-style:italic;padding:8px;background:#0d1117;border-radius:6px';
|
||||
ex.textContent='Every time you search and select a worker, the system records what worked. Over time, it learns which workers are best for which situations — turning your decisions into institutional knowledge that never leaves when a staffer does.';
|
||||
card.appendChild(ex);
|
||||
|
||||
el.appendChild(card);
|
||||
}).catch(function(){});
|
||||
}
|
||||
|
||||
function addLearnStat(parent,n,label,color){
|
||||
var d=document.createElement('div');d.style.cssText='text-align:center;flex:1';
|
||||
var num=document.createElement('div');num.style.cssText='font-size:24px;font-weight:800;color:'+color;num.textContent=n;
|
||||
var lb=document.createElement('div');lb.style.cssText='font-size:10px;color:#484f58';lb.textContent=label;
|
||||
d.appendChild(num);d.appendChild(lb);parent.appendChild(d);
|
||||
}
|
||||
</script></body></html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user