Every page refresh feeds the learning loop — contracts logged as playbook entries
Each simulation fill now logs: role, headcount, city, state, workers matched, client, start time, and scenario type. One page refresh = ~20 playbook entries. 4 refreshes = 28 entries with patterns already forming. Fixed activity counters: shows Contract Fills, Searches, and Patterns. Activity feed now shows the actual fill data with worker names and scenarios. This is the PRD's learning loop in action — the system records every successful match so future queries can learn from past decisions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bba5b826a3
commit
b16e485be1
@ -1047,23 +1047,25 @@ tr:hover{background:#111827}
|
||||
// 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([
|
||||
const [playbooksR, searchCountR, fillCountR, totalR] = 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 }] })),
|
||||
api("POST", "/query/sql", { sql: "SELECT COUNT(*) cnt FROM successful_playbooks WHERE operation LIKE 'fill:%'" }).catch(() => ({ rows: [{ cnt: 0 }] })),
|
||||
api("POST", "/query/sql", { sql: "SELECT COUNT(*) cnt FROM successful_playbooks" }).catch(() => ({ rows: [{ cnt: 0 }] })),
|
||||
]);
|
||||
// Extract learned patterns — which workers get picked for which queries
|
||||
// Extract learned patterns — which roles+cities get filled most
|
||||
const patterns: Record<string, number> = {};
|
||||
for (const p of (playbooksR.rows || [])) {
|
||||
if (p.operation?.startsWith("search:")) {
|
||||
const key = p.operation.replace("search: ", "").trim();
|
||||
if (p.operation?.startsWith("fill:") || p.operation?.startsWith("search:")) {
|
||||
const key = p.operation.replace(/^(fill|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,
|
||||
fill_count: fillCountR.rows?.[0]?.cnt || 0,
|
||||
total_operations: totalR.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,
|
||||
});
|
||||
@ -1452,12 +1454,28 @@ async function runWeekSimulation() {
|
||||
playbook_entries: playbookEntries,
|
||||
};
|
||||
|
||||
// Log the week to playbooks
|
||||
// Log every filled contract as a playbook entry — this is the training data
|
||||
try {
|
||||
const form = new FormData();
|
||||
const csv = `timestamp,operation,approach,result,context\n"${new Date().toISOString()}","week_simulation: ${summary.total_contracts} contracts over 5 days","hybrid SQL+vector with multi-model routing","${summary.total_filled}/${summary.total_needed} filled (${summary.fill_pct}%)","${summary.emergencies} emergencies, ${summary.handoffs} handoffs"`;
|
||||
form.append("file", new Blob([csv], { type: "text/csv" }), "playbook.csv");
|
||||
await fetch(`${BASE}/ingest/file?name=successful_playbooks`, { method: "POST", body: form });
|
||||
const ts = new Date().toISOString();
|
||||
const rows: string[] = [];
|
||||
for (const day of results) {
|
||||
for (const c of day.contracts) {
|
||||
if (c.matches && c.matches.length > 0) {
|
||||
const workerNames = c.matches.slice(0, 3).map((m: any) => m.name || m.doc_id).join(", ");
|
||||
const op = `fill: ${c.role} x${c.headcount} in ${c.city}, ${c.state}`;
|
||||
const approach = `${c.situation} (${c.priority}) → hybrid search`;
|
||||
const result = `${c.filled}/${c.headcount} filled → ${workerNames}`;
|
||||
const context = `client=${c.client} start=${c.start} scenario=${c.situation}`;
|
||||
rows.push(`"${ts}","${op.replace(/"/g,'""')}","${approach}","${result.replace(/"/g,'""')}","${context.replace(/"/g,'""')}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rows.length) {
|
||||
const csv = `timestamp,operation,approach,result,context\n${rows.join("\n")}`;
|
||||
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 });
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return { days: results, summary };
|
||||
|
||||
@ -614,7 +614,7 @@ 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);
|
||||
var total=d.total_operations||0;
|
||||
if(total===0&&(!d.playbooks||!d.playbooks.length))return; // nothing to show yet
|
||||
|
||||
var card=document.createElement('div');card.className='insight info';
|
||||
@ -626,9 +626,9 @@ function loadLearning(){
|
||||
|
||||
// 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');
|
||||
addLearnStat(stats,d.fill_count||0,'Contract Fills','#3fb950');
|
||||
addLearnStat(stats,d.search_count||0,'Searches','#58a6ff');
|
||||
addLearnStat(stats,(d.learned_patterns||[]).length,'Patterns','#bc8cff');
|
||||
card.appendChild(stats);
|
||||
|
||||
// Learned patterns
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user