Kraft102's picture
Update backend source
34367da verified
/**
* 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();