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:
root 2026-04-17 20:05:51 -05:00
parent bba5b826a3
commit b16e485be1
2 changed files with 33 additions and 15 deletions

View File

@ -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 };

View File

@ -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