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
|
// Intelligence: Activity feed — what the system has learned
|
||||||
if (url.pathname === "/intelligence/activity" && req.method === "POST") {
|
if (url.pathname === "/intelligence/activity" && req.method === "POST") {
|
||||||
const start = Date.now();
|
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 * 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 '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> = {};
|
const patterns: Record<string, number> = {};
|
||||||
for (const p of (playbooksR.rows || [])) {
|
for (const p of (playbooksR.rows || [])) {
|
||||||
if (p.operation?.startsWith("search:")) {
|
if (p.operation?.startsWith("fill:") || p.operation?.startsWith("search:")) {
|
||||||
const key = p.operation.replace("search: ", "").trim();
|
const key = p.operation.replace(/^(fill|search): ?/, "").trim();
|
||||||
patterns[key] = (patterns[key] || 0) + 1;
|
patterns[key] = (patterns[key] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ok({
|
return ok({
|
||||||
playbooks: playbooksR.rows || [],
|
playbooks: playbooksR.rows || [],
|
||||||
search_count: searchCountR.rows?.[0]?.cnt || 0,
|
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),
|
learned_patterns: Object.entries(patterns).map(([q, c]) => ({ query: q, times: c })).sort((a, b) => b.times - a.times),
|
||||||
duration_ms: Date.now() - start,
|
duration_ms: Date.now() - start,
|
||||||
});
|
});
|
||||||
@ -1452,12 +1454,28 @@ async function runWeekSimulation() {
|
|||||||
playbook_entries: playbookEntries,
|
playbook_entries: playbookEntries,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log the week to playbooks
|
// Log every filled contract as a playbook entry — this is the training data
|
||||||
try {
|
try {
|
||||||
const form = new FormData();
|
const ts = new Date().toISOString();
|
||||||
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"`;
|
const rows: string[] = [];
|
||||||
form.append("file", new Blob([csv], { type: "text/csv" }), "playbook.csv");
|
for (const day of results) {
|
||||||
await fetch(`${BASE}/ingest/file?name=successful_playbooks`, { method: "POST", body: form });
|
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 {}
|
} catch {}
|
||||||
|
|
||||||
return { days: results, summary };
|
return { days: results, summary };
|
||||||
|
|||||||
@ -614,7 +614,7 @@ function loadLearning(){
|
|||||||
api('/intelligence/activity',{}).then(function(d){
|
api('/intelligence/activity',{}).then(function(d){
|
||||||
var el=document.getElementById('learning');
|
var el=document.getElementById('learning');
|
||||||
el.textContent='';
|
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
|
if(total===0&&(!d.playbooks||!d.playbooks.length))return; // nothing to show yet
|
||||||
|
|
||||||
var card=document.createElement('div');card.className='insight info';
|
var card=document.createElement('div');card.className='insight info';
|
||||||
@ -626,9 +626,9 @@ function loadLearning(){
|
|||||||
|
|
||||||
// Stats row
|
// Stats row
|
||||||
var stats=document.createElement('div');stats.style.cssText='display:flex;gap:16px;margin-bottom:12px';
|
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.fill_count||0,'Contract Fills','#3fb950');
|
||||||
addLearnStat(stats,d.sim_count||0,'Simulations Run','#3fb950');
|
addLearnStat(stats,d.search_count||0,'Searches','#58a6ff');
|
||||||
addLearnStat(stats,(d.learned_patterns||[]).length,'Patterns Detected','#bc8cff');
|
addLearnStat(stats,(d.learned_patterns||[]).length,'Patterns','#bc8cff');
|
||||||
card.appendChild(stats);
|
card.appendChild(stats);
|
||||||
|
|
||||||
// Learned patterns
|
// Learned patterns
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user