const { getDb } = require('./db'); const ADVICE_ACTIONS = { lead_followup: (id) => `/clients/${id}`, initial_assessment: (id) => `/consulting?client_id=${id}`, reassessment: (id) => `/consulting?client_id=${id}`, start_engagement: (id) => `/engagements/new/${id}`, next_phase: (id) => `/clients/${id}`, architecture_phase: (id) => `/architecture?client_id=${id}`, implementation_phase: (id) => `/engagements/new/${id}`, monitoring_phase: (id) => `/engagements/new/${id}`, training: (id) => `/engagements/new/${id}`, overdue_tasks: (id) => `/clients/${id}`, no_diagram: (id) => id ? `/architecture/new/${id}` : '/architecture/new', data_quality: (id) => `/quality?client_id=${id}`, ai_readiness: () => '/ai', no_time_logged: (id) => `/time?client_id=${id}`, stale_engagement: (id) => `/clients/${id}`, leadgen_opportunity: (id) => `/?tab=leadgen`, proposal_ready: (id) => `/consulting/report/${id}`, }; function adviceAction(type, clientId, extra) { const fn = ADVICE_ACTIONS[type]; if (!fn) return clientId ? `/clients/${clientId}` : '/clients'; return fn(clientId, extra); } function generateAdvice(clientId) { const db = getDb(); const client = db.prepare('SELECT * FROM clients WHERE id = ?').get(clientId); if (!client) return []; const engagementCount = db.prepare('SELECT COUNT(*) as cnt FROM engagements WHERE client_id = ?').get(clientId); const lastAssessment = db.prepare('SELECT * FROM assessments WHERE client_id = ? ORDER BY created_at DESC').get(clientId); const activeEngagements = db.prepare("SELECT * FROM engagements WHERE client_id = ? AND status = 'in_progress'").all(clientId); const completedEngagements = db.prepare("SELECT * FROM engagements WHERE client_id = ? AND status = 'completed'").all(clientId); const diagramCount = db.prepare('SELECT COUNT(*) as cnt FROM data_diagrams WHERE client_id = ?').get(clientId); const timeLogged = db.prepare('SELECT COUNT(*) as cnt FROM time_entries WHERE client_id = ?').get(clientId); const existingAdvice = db.prepare("SELECT advice_type FROM advice_log WHERE client_id = ? AND dismissed = 0").all(clientId); const existing = new Set(existingAdvice.map(a => a.advice_type)); const advice = []; function checkAdvice(type, text, priority) { if (existing.has(type)) return; advice.push({ client_id: clientId, client_name: client.name, advice_type: type, advice_text: text, priority: priority || 'medium', action_url: adviceAction(type, clientId, lastAssessment?.id), }); } if (client.status === 'lead') { checkAdvice('lead_followup', `${client.name} is nog een lead. Plan een intakegesprek en start het Enterprise Framework assessment.`, 'high'); } if (!lastAssessment) { checkAdvice('initial_assessment', `Start een eerste assessment voor ${client.name}: infra, data, AI-maturity en roadmap.`, 'critical'); } if (lastAssessment) { const daysSince = Math.floor((Date.now() - new Date(lastAssessment.created_at).getTime()) / (1000 * 60 * 60 * 24)); if (daysSince > 90) { checkAdvice('reassessment', `Laatste assessment voor ${client.name} was ${daysSince} dagen geleden. Tijd voor her-evaluatie.`, 'medium'); } if (diagramCount.cnt === 0) { checkAdvice('no_diagram', `Assessment afgerond voor ${client.name}, maar nog geen architectuurdiagram. Maak een to-be design.`, 'high'); } } if (client.status === 'active' && engagementCount.cnt === 0) { checkAdvice('start_engagement', `${client.name} is actief maar heeft geen engagements. Start een discovery- of designtraject.`, 'high'); } if (client.status === 'active' && activeEngagements.length === 0 && completedEngagements.length > 0) { checkAdvice('next_phase', `${client.name} heeft geen lopende projecten. Bespreek volgende fase: monitoring, AI Ops of opschaling.`, 'high'); } const hasArchitecture = [...completedEngagements, ...activeEngagements].some(e => e.type === 'architecture'); const hasImplementation = [...completedEngagements, ...activeEngagements].some(e => e.type === 'implementation'); const hasMonitoring = [...completedEngagements, ...activeEngagements].some(e => e.type === 'monitoring'); const hasConsulting = [...completedEngagements, ...activeEngagements].some(e => e.type === 'consulting'); if (lastAssessment && !hasArchitecture && !hasConsulting) { checkAdvice('architecture_phase', `Assessment klaar voor ${client.name}. Start architectuur- of design engagement.`, 'high'); } if (hasArchitecture && !hasImplementation) { checkAdvice('implementation_phase', `Architectuur gereed voor ${client.name}. Start implementatiefase of POC.`, 'high'); } if (hasImplementation && !hasMonitoring) { checkAdvice('monitoring_phase', `Implementatie afgerond bij ${client.name}. Richt monitoring & observability in.`, 'medium'); } const hasTraining = [...completedEngagements, ...activeEngagements].some(e => e.type === 'training'); if (hasImplementation && !hasTraining) { checkAdvice('training', `${client.name} heeft implementatie gehad. Bied training & knowledge transfer aan.`, 'low'); } if (lastAssessment && diagramCount.cnt > 0) { checkAdvice('data_quality', `Voer een data quality scan uit voor ${client.name} ter onderbouwing van het design.`, 'medium'); } if (lastAssessment && client.status === 'active') { checkAdvice('ai_readiness', `Beoordeel AI/LLM readiness voor ${client.name} en configureer de juiste provider.`, 'low'); } if (client.status === 'active' && timeLogged.cnt === 0 && engagementCount.cnt > 0) { checkAdvice('no_time_logged', `Nog geen uren geregistreerd voor ${client.name}. Log tijd voor facturatie & projectinzicht.`, 'medium'); } for (const eng of activeEngagements) { const daysSinceUpdate = Math.floor((Date.now() - new Date(eng.updated_at || eng.created_at).getTime()) / (1000 * 60 * 60 * 24)); if (daysSinceUpdate > 14) { checkAdvice('stale_engagement', `Engagement "${eng.title}" bij ${client.name} is ${daysSinceUpdate} dagen niet bijgewerkt. Plan een statusreview.`, 'medium'); break; } } const allTasks = db.prepare(` SELECT t.* FROM tasks t JOIN engagements e ON e.id = t.engagement_id WHERE e.client_id = ? AND t.status IN ('todo','in_progress') `).all(clientId); if (allTasks.length > 0) { const overdue = allTasks.filter(t => t.due_date && new Date(t.due_date) < new Date()); if (overdue.length > 0) { checkAdvice('overdue_tasks', `${overdue.length} ta(a)k(en) voor ${client.name} zijn verlopen. Herprioriteer en communiceer.`, 'critical'); } } if (client.status === 'lead' && !client.website) { checkAdvice('leadgen_opportunity', `Voeg een website toe voor ${client.name} en scan met LeadGen AI op tech signals.`, 'low'); } return advice; } function syncAdviceToDb(clientId, adviceItems) { if (!adviceItems || adviceItems.length === 0) return; const db = getDb(); const insert = db.prepare('INSERT INTO advice_log (client_id, advice_type, advice_text, priority) VALUES (?, ?, ?, ?)'); for (const a of adviceItems) { try { insert.run(clientId, a.advice_type, a.advice_text, a.priority || 'medium'); } catch (e) {} } } function getAdviceActionUrl(type, clientId, assessmentId) { return adviceAction(type, clientId, assessmentId); } module.exports = { generateAdvice, syncAdviceToDb, getAdviceActionUrl, ADVICE_ACTIONS };