/** * SubconsciousService.ts - Omni-Link v11 Proactive Analysis * * Runs background analysis on stakeholder relationships. * Generates "Morning Briefing" proposals based on silence gaps. */ import { logger } from '../../utils/logger.js'; import { RedisService } from '../RedisService.js'; const log = logger.child({ module: 'SubconsciousService' }); export type CommunicationStyle = 'High_Touch' | 'Low_Touch' | 'Direct' | 'Formal'; export type StakeholderCategory = 'Internal' | 'Customer' | 'Financial' | 'External'; export type ActionType = 'Check-in' | 'Update' | 'Meeting' | 'Review' | 'Escalate'; export interface Stakeholder { id: string; name: string; role: string; category: StakeholderCategory; lastInteraction: Date; preferredFrequencyDays: number; communicationStyle: CommunicationStyle; importanceScore: number; // 1-10 enneagramType?: string; sentiment?: number; // 0-100 responsiveness?: number; // 0-100 alignment?: number; // 0-100 } export interface ActionProposal { id: string; stakeholderId: string; stakeholderName: string; stakeholderRole: string; category: StakeholderCategory; actionType: ActionType; reason: string; urgencyScore: number; // 0-100 suggestedMessage: string; generatedAt: number; dismissed: boolean; } export class SubconsciousService { private redis: RedisService; private stakeholders: Stakeholder[] = []; private proposals: ActionProposal[] = []; private analysisInterval: NodeJS.Timeout | null = null; constructor() { this.redis = RedisService.getInstance(); this.initializeDefaultStakeholders(); } /** * Initialize with default stakeholder data. * In production, this would load from database. */ private initializeDefaultStakeholders(): void { this.stakeholders = [ { id: 'stk-001', name: 'Board of Directors', role: 'Governance', category: 'Financial', lastInteraction: new Date(Date.now() - 20 * 86400000), preferredFrequencyDays: 30, communicationStyle: 'Formal', importanceScore: 10, enneagramType: 'Type 1: The Reformer', sentiment: 60, responsiveness: 40, alignment: 95 }, { id: 'stk-002', name: 'Tech Lead', role: 'Dev Team', category: 'Internal', lastInteraction: new Date(Date.now() - 3 * 86400000), preferredFrequencyDays: 2, communicationStyle: 'High_Touch', importanceScore: 8, enneagramType: 'Type 5: The Investigator', sentiment: 45, responsiveness: 85, alignment: 70 }, { id: 'stk-003', name: 'Key Client A', role: 'Enterprise Account', category: 'Customer', lastInteraction: new Date(Date.now() - 14 * 86400000), preferredFrequencyDays: 7, communicationStyle: 'Direct', importanceScore: 9, enneagramType: 'Type 3: The Achiever', sentiment: 30, responsiveness: 10, alignment: 40 }, { id: 'stk-004', name: 'Marketing Dept', role: 'Growth', category: 'Internal', lastInteraction: new Date(Date.now() - 1 * 86400000), preferredFrequencyDays: 3, communicationStyle: 'High_Touch', importanceScore: 6, enneagramType: 'Type 7: The Enthusiast', sentiment: 90, responsiveness: 95, alignment: 85 }, { id: 'stk-005', name: 'Seed Investors', role: 'Funding', category: 'Financial', lastInteraction: new Date(Date.now() - 45 * 86400000), preferredFrequencyDays: 60, communicationStyle: 'Formal', importanceScore: 10, enneagramType: 'Type 6: The Loyalist', sentiment: 55, responsiveness: 50, alignment: 90 }, { id: 'stk-006', name: 'GDPR Audit Firm', role: 'Compliance', category: 'External', lastInteraction: new Date(Date.now() - 60 * 86400000), preferredFrequencyDays: 90, communicationStyle: 'Formal', importanceScore: 7, enneagramType: 'Type 1: The Reformer', sentiment: 50, responsiveness: 30, alignment: 100 }, { id: 'stk-007', name: 'Client B (Beta)', role: 'Feedback Loop', category: 'Customer', lastInteraction: new Date(Date.now() - 8 * 86400000), preferredFrequencyDays: 5, communicationStyle: 'High_Touch', importanceScore: 7, enneagramType: 'Type 4: The Individualist', sentiment: 65, responsiveness: 70, alignment: 60 } ]; log.info(`[Subconscious] Initialized with ${this.stakeholders.length} stakeholders`); } /** * Analyzes all stakeholders for silence gaps. * Generates prioritized action proposals. */ public analyzeSilentPeriods(): ActionProposal[] { const now = new Date(); const proposals: ActionProposal[] = []; this.stakeholders.forEach(stakeholder => { const daysSinceLast = Math.floor( (now.getTime() - stakeholder.lastInteraction.getTime()) / (1000 * 3600 * 24) ); const decayRatio = daysSinceLast / stakeholder.preferredFrequencyDays; let urgency = 0; let reason = ""; let actionType: ActionType = 'Check-in'; let suggestedMessage = ""; // Critical: >1.5x preferred frequency if (decayRatio >= 1.5) { urgency = 80 + (stakeholder.importanceScore * 2); reason = `Critical silence gap (${daysSinceLast} days). Relationship at risk.`; actionType = stakeholder.importanceScore >= 9 ? 'Meeting' : 'Check-in'; suggestedMessage = this.generateSuggestedMessage(stakeholder, 'critical'); } // Warning: >=1.0x preferred frequency else if (decayRatio >= 1.0) { urgency = 50 + (stakeholder.importanceScore * 2); reason = `Maintenance cycle overdue (${daysSinceLast}/${stakeholder.preferredFrequencyDays} days).`; actionType = 'Update'; suggestedMessage = this.generateSuggestedMessage(stakeholder, 'maintenance'); } // High-touch profiles need more attention else if (stakeholder.communicationStyle === 'High_Touch' && decayRatio >= 0.7) { urgency = 40 + (stakeholder.importanceScore * 1.5); reason = `High-touch profile requires proactive engagement.`; actionType = 'Check-in'; suggestedMessage = this.generateSuggestedMessage(stakeholder, 'proactive'); } // Only create proposals above threshold if (urgency > 50) { proposals.push({ id: `prop-${stakeholder.id}-${Date.now()}`, stakeholderId: stakeholder.id, stakeholderName: stakeholder.name, stakeholderRole: stakeholder.role, category: stakeholder.category, actionType, reason, urgencyScore: Math.min(100, Math.round(urgency)), suggestedMessage, generatedAt: Date.now(), dismissed: false }); } }); // Sort by urgency this.proposals = proposals.sort((a, b) => b.urgencyScore - a.urgencyScore); // Save to Redis this.redis.saveProposals(this.proposals); log.info(`[Subconscious] Generated ${this.proposals.length} proposals`); return this.proposals; } /** * Generates contextual message suggestion. */ private generateSuggestedMessage(stakeholder: Stakeholder, urgencyLevel: 'critical' | 'maintenance' | 'proactive'): string { const templates: Record> = { critical: { 'High_Touch': `Hi ${stakeholder.name.split(' ')[0]}, I wanted to personally reach out. It's been a while and I'd love to catch up on how things are going.`, 'Low_Touch': `Quick sync request: ${stakeholder.name} - let's connect briefly this week.`, 'Direct': `${stakeholder.name} - Urgent: We need to align on current status. Available for a call?`, 'Formal': `Dear ${stakeholder.name}, I hope this message finds you well. We would appreciate the opportunity to schedule a meeting at your earliest convenience.` }, maintenance: { 'High_Touch': `Hey ${stakeholder.name.split(' ')[0]}! Just checking in - how are things going on your end?`, 'Low_Touch': `Status update: ${stakeholder.name} touchpoint scheduled.`, 'Direct': `${stakeholder.name} - Regular update: Any blockers or needs from our side?`, 'Formal': `Dear ${stakeholder.name}, As part of our regular communication cadence, we would like to provide an update and hear your feedback.` }, proactive: { 'High_Touch': `Hi ${stakeholder.name.split(' ')[0]}! Thought of you - anything exciting happening we should know about?`, 'Low_Touch': `Proactive touchpoint: ${stakeholder.name}`, 'Direct': `${stakeholder.name} - Quick pulse check. All good?`, 'Formal': `Dear ${stakeholder.name}, We wanted to proactively reach out to ensure alignment on current initiatives.` } }; return templates[urgencyLevel][stakeholder.communicationStyle] || `Reaching out to ${stakeholder.name} regarding ${urgencyLevel} engagement.`; } /** * Get current proposals. */ public getProposals(): ActionProposal[] { return this.proposals.filter(p => !p.dismissed); } /** * Get all stakeholders. */ public getStakeholders(): Stakeholder[] { return this.stakeholders; } /** * Update stakeholder parameters. */ public updateStakeholder(id: string, updates: Partial): Stakeholder | null { const index = this.stakeholders.findIndex(s => s.id === id); if (index === -1) return null; this.stakeholders[index] = { ...this.stakeholders[index], ...updates }; log.info(`[Subconscious] Updated stakeholder: ${id}`); // Re-analyze after update this.analyzeSilentPeriods(); return this.stakeholders[index]; } /** * Dismiss a proposal. */ public dismissProposal(proposalId: string): boolean { const proposal = this.proposals.find(p => p.id === proposalId); if (proposal) { proposal.dismissed = true; this.redis.saveProposals(this.proposals); log.info(`[Subconscious] Dismissed proposal: ${proposalId}`); return true; } return false; } /** * Mark stakeholder as contacted (reset last interaction). */ public markContacted(stakeholderId: string): boolean { const stakeholder = this.stakeholders.find(s => s.id === stakeholderId); if (stakeholder) { stakeholder.lastInteraction = new Date(); this.analyzeSilentPeriods(); log.info(`[Subconscious] Marked contacted: ${stakeholderId}`); return true; } return false; } /** * Start background analysis loop. */ public startAnalysisLoop(intervalMs: number = 60000): void { if (this.analysisInterval) { clearInterval(this.analysisInterval); } // Initial analysis this.analyzeSilentPeriods(); // Periodic re-analysis this.analysisInterval = setInterval(() => { this.analyzeSilentPeriods(); }, intervalMs); log.info(`[Subconscious] Analysis loop started (interval: ${intervalMs}ms)`); } /** * Stop background analysis. */ public stopAnalysisLoop(): void { if (this.analysisInterval) { clearInterval(this.analysisInterval); this.analysisInterval = null; log.info('[Subconscious] Analysis loop stopped'); } } } // Singleton export export const subconsciousService = new SubconsciousService();