Spaces:
Paused
Paused
| /** | |
| * 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<string, Record<CommunicationStyle, string>> = { | |
| 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>): 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(); | |