import { z } from 'zod';
import { createLogger } from '../utils/logger.js';
/**
* Predictive Analytics Service
*
* Provides advanced predictive capabilities including success probability,
* timeline predictions, risk correlation, and outcome modeling.
*/
// Prediction schemas
export const SuccessProbabilitySchema = z.object({
overall: z.number().min(0).max(1),
factors: z.array(z.object({
name: z.string(),
weight: z.number(),
score: z.number().min(0).max(1),
impact: z.enum(['positive', 'negative'])
})),
confidence_interval: z.tuple([z.number(), z.number()]),
key_drivers: z.array(z.string())
});
export const TimelinePredictionSchema = z.object({
expected_completion: z.string().datetime(),
confidence: z.number().min(0).max(1),
milestones: z.array(z.object({
name: z.string(),
expected_date: z.string().datetime(),
probability: z.number().min(0).max(1),
dependencies: z.array(z.string())
})),
critical_path: z.array(z.string()),
buffer_recommendation: z.number() // Days
});
export const RiskCorrelationSchema = z.object({
correlations: z.array(z.object({
factor1: z.string(),
factor2: z.string(),
correlation: z.number().min(-1).max(1),
significance: z.number().min(0).max(1)
})),
risk_clusters: z.array(z.object({
name: z.string(),
factors: z.array(z.string()),
combined_impact: z.enum(['low', 'medium', 'high', 'critical'])
})),
mitigation_priorities: z.array(z.string())
});
export const OutcomePredictionSchema = z.object({
scenarios: z.array(z.object({
name: z.string(),
probability: z.number().min(0).max(1),
roi: z.number(),
payback_months: z.number(),
key_assumptions: z.array(z.string())
})),
most_likely: z.string(),
variance_drivers: z.array(z.object({
factor: z.string(),
sensitivity: z.number(),
controllable: z.boolean()
}))
});
export type SuccessProbability = z.infer<typeof SuccessProbabilitySchema>;
export type TimelinePrediction = z.infer<typeof TimelinePredictionSchema>;
export type RiskCorrelation = z.infer<typeof RiskCorrelationSchema>;
export type OutcomePrediction = z.infer<typeof OutcomePredictionSchema>;
export class PredictiveAnalytics {
private logger = createLogger({ component: 'PredictiveAnalytics' });
// Historical success patterns
private readonly SUCCESS_PATTERNS = {
high_success: {
quick_payback: { threshold: 12, weight: 0.25 },
moderate_complexity: { max_use_cases: 7, weight: 0.2 },
proven_technology: { categories: ['automation', 'analytics'], weight: 0.15 },
strong_sponsorship: { investment: 500000, weight: 0.2 },
clear_metrics: { confidence: 0.85, weight: 0.2 }
}
};
/**
* Predict success probability for ROI projects
*/
async predictProjectSuccess(
projectData: any,
historicalData?: any[]
): Promise<SuccessProbability> {
this.logger.debug('Predicting project success probability');
const factors = this.evaluateSuccessFactors(projectData);
const overall = this.calculateOverallSuccess(factors);
const confidence = this.calculateConfidenceInterval(overall, projectData);
const keyDrivers = this.identifyKeyDrivers(factors);
return {
overall,
factors,
confidence_interval: confidence,
key_drivers: keyDrivers
};
}
/**
* Predict timeline with milestones and critical path
*/
async predictTimeline(
projectData: any,
useCases: any[]
): Promise<TimelinePrediction> {
this.logger.debug('Predicting project timeline');
const milestones = this.generateMilestones(projectData, useCases);
const criticalPath = this.identifyCriticalPath(milestones);
const completion = this.calculateExpectedCompletion(milestones, criticalPath);
const buffer = this.recommendBuffer(projectData, milestones);
return {
expected_completion: completion.date,
confidence: completion.confidence,
milestones,
critical_path: criticalPath,
buffer_recommendation: buffer
};
}
/**
* Analyze risk correlations and clusters
*/
async analyzeRiskCorrelations(
risks: any[],
projectContext: any
): Promise<RiskCorrelation> {
this.logger.debug('Analyzing risk correlations');
const correlations = this.calculateRiskCorrelations(risks);
const clusters = this.identifyRiskClusters(correlations, risks);
const priorities = this.prioritizeMitigations(clusters, correlations);
return {
correlations,
risk_clusters: clusters,
mitigation_priorities: priorities
};
}
/**
* Predict project outcomes with scenarios
*/
async predictOutcomes(
projectData: any,
marketConditions?: any
): Promise<OutcomePrediction> {
this.logger.debug('Predicting project outcomes');
const scenarios = this.generateScenarios(projectData, marketConditions);
const mostLikely = this.identifyMostLikelyScenario(scenarios, projectData);
const varianceDrivers = this.analyzeVarianceDrivers(scenarios, projectData);
return {
scenarios,
most_likely: mostLikely,
variance_drivers: varianceDrivers
};
}
/**
* Advanced ML-based pattern matching
*/
async matchHistoricalPatterns(
currentProject: any,
historicalProjects: any[]
): Promise<{
similar_projects: Array<{
project_id: string;
similarity_score: number;
outcome: string;
key_similarities: string[];
}>;
success_indicators: string[];
warning_signs: string[];
}> {
this.logger.debug('Matching historical patterns');
const similarities = this.calculateProjectSimilarities(currentProject, historicalProjects);
const topMatches = similarities
.sort((a, b) => b.similarity_score - a.similarity_score)
.slice(0, 5);
const successIndicators = this.extractSuccessIndicators(topMatches);
const warningSigns = this.extractWarningSigns(topMatches);
return {
similar_projects: topMatches,
success_indicators: successIndicators,
warning_signs: warningSigns
};
}
// Private helper methods
private evaluateSuccessFactors(projectData: any): SuccessProbability['factors'] {
const factors = [];
const patterns = this.SUCCESS_PATTERNS.high_success;
// Quick payback factor
if (projectData.summary?.payback_period_months <= patterns.quick_payback.threshold) {
factors.push({
name: 'Quick Payback Period',
weight: patterns.quick_payback.weight,
score: 0.9,
impact: 'positive' as const
});
} else {
factors.push({
name: 'Extended Payback Period',
weight: patterns.quick_payback.weight,
score: 0.4,
impact: 'negative' as const
});
}
// Complexity factor
const useCaseCount = projectData.use_cases?.length || 0;
if (useCaseCount <= patterns.moderate_complexity.max_use_cases) {
factors.push({
name: 'Manageable Complexity',
weight: patterns.moderate_complexity.weight,
score: 0.85,
impact: 'positive' as const
});
} else {
factors.push({
name: 'High Complexity',
weight: patterns.moderate_complexity.weight,
score: 0.3,
impact: 'negative' as const
});
}
// Technology maturity factor
const hasProvenTech = projectData.use_cases?.some((uc: any) =>
patterns.proven_technology.categories.includes(uc.category)
);
if (hasProvenTech) {
factors.push({
name: 'Proven Technology',
weight: patterns.proven_technology.weight,
score: 0.8,
impact: 'positive' as const
});
}
// Investment level factor
if (projectData.summary?.total_investment >= patterns.strong_sponsorship.investment) {
factors.push({
name: 'Strong Investment Commitment',
weight: patterns.strong_sponsorship.weight,
score: 0.75,
impact: 'positive' as const
});
}
// Confidence factor
if (projectData.metadata?.confidence_level >= patterns.clear_metrics.confidence) {
factors.push({
name: 'High Confidence Data',
weight: patterns.clear_metrics.weight,
score: 0.9,
impact: 'positive' as const
});
}
return factors;
}
private calculateOverallSuccess(factors: SuccessProbability['factors']): number {
const weightedSum = factors.reduce((sum, factor) => {
const adjustedScore = factor.impact === 'positive' ? factor.score : (1 - factor.score);
return sum + (adjustedScore * factor.weight);
}, 0);
const totalWeight = factors.reduce((sum, factor) => sum + factor.weight, 0);
return Math.min(0.95, Math.max(0.05, weightedSum / totalWeight));
}
private calculateConfidenceInterval(
mean: number,
projectData: any
): [number, number] {
// Simplified confidence interval calculation
const dataQuality = projectData.metadata?.confidence_level || 0.8;
const sampleSize = projectData.use_cases?.length || 1;
// Larger projects have more uncertainty
const uncertainty = 0.1 + (0.05 * Math.log10(sampleSize));
const adjustedUncertainty = uncertainty * (2 - dataQuality);
return [
Math.max(0, mean - adjustedUncertainty),
Math.min(1, mean + adjustedUncertainty)
];
}
private identifyKeyDrivers(factors: SuccessProbability['factors']): string[] {
return factors
.filter(f => f.weight >= 0.2)
.sort((a, b) => b.weight - a.weight)
.map(f => f.name);
}
private generateMilestones(projectData: any, useCases: any[]): TimelinePrediction['milestones'] {
const milestones = [];
const startDate = new Date();
const totalMonths = projectData.timeline_months || 12;
// Planning phase
milestones.push({
name: 'Project Kickoff & Planning',
expected_date: new Date(startDate.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString(),
probability: 0.95,
dependencies: []
});
// Implementation phases
const implementationMonths = totalMonths * 0.6;
const useCasesPerPhase = Math.ceil(useCases.length / 3);
for (let phase = 0; phase < 3; phase++) {
const phaseUseCases = useCases.slice(
phase * useCasesPerPhase,
(phase + 1) * useCasesPerPhase
);
if (phaseUseCases.length > 0) {
const phaseDate = new Date(
startDate.getTime() +
(30 + (implementationMonths / 3 * phase * 30)) * 24 * 60 * 60 * 1000
);
milestones.push({
name: `Phase ${phase + 1}: ${phaseUseCases.map(uc => uc.name).join(', ')}`,
expected_date: phaseDate.toISOString(),
probability: 0.85 - (phase * 0.05),
dependencies: phase > 0 ? [`Phase ${phase}: `] : ['Project Kickoff & Planning']
});
}
}
// Go-live milestone
milestones.push({
name: 'Full Production Deployment',
expected_date: new Date(
startDate.getTime() + (totalMonths * 30 * 24 * 60 * 60 * 1000)
).toISOString(),
probability: 0.75,
dependencies: milestones.slice(-3).map(m => m.name)
});
return milestones;
}
private identifyCriticalPath(milestones: TimelinePrediction['milestones']): string[] {
// Simplified critical path - in reality would use CPM algorithm
return milestones
.filter(m => m.dependencies.length > 0 || m.name.includes('Phase'))
.map(m => m.name);
}
private calculateExpectedCompletion(
milestones: TimelinePrediction['milestones'],
criticalPath: string[]
): { date: string; confidence: number } {
const lastMilestone = milestones[milestones.length - 1];
const criticalMilestones = milestones.filter(m => criticalPath.includes(m.name));
// Calculate confidence based on critical path probabilities
const pathConfidence = criticalMilestones.reduce((conf, m) => conf * m.probability, 1);
return {
date: lastMilestone.expected_date,
confidence: pathConfidence
};
}
private recommendBuffer(projectData: any, milestones: any[]): number {
const complexity = projectData.use_cases?.length || 1;
const avgProbability = milestones.reduce((sum, m) => sum + m.probability, 0) / milestones.length;
// Base buffer of 10% of timeline
const baseBuffer = (projectData.timeline_months || 12) * 0.1 * 30;
// Adjust for complexity and uncertainty
const complexityFactor = Math.log10(complexity + 1);
const uncertaintyFactor = 2 - avgProbability;
return Math.round(baseBuffer * complexityFactor * uncertaintyFactor);
}
private calculateRiskCorrelations(risks: any[]): RiskCorrelation['correlations'] {
const correlations = [];
// Common correlation patterns
const correlationPatterns = {
'Implementation Complexity': {
'User Adoption': 0.7,
'Timeline Slippage': 0.8,
'Budget Overrun': 0.6
},
'Technology Maturity': {
'Integration Risk': 0.75,
'Performance Issues': 0.65
},
'Resource Availability': {
'Timeline Slippage': 0.85,
'Quality Issues': 0.6
}
};
// Calculate correlations based on patterns
for (let i = 0; i < risks.length; i++) {
for (let j = i + 1; j < risks.length; j++) {
const risk1 = risks[i].factor;
const risk2 = risks[j].factor;
let correlation = 0;
let found = false;
// Check predefined patterns
for (const [pattern, corrs] of Object.entries(correlationPatterns)) {
if (risk1.includes(pattern) && corrs[risk2 as keyof typeof corrs]) {
correlation = corrs[risk2 as keyof typeof corrs];
found = true;
break;
} else if (risk2.includes(pattern) && corrs[risk1 as keyof typeof corrs]) {
correlation = corrs[risk1 as keyof typeof corrs];
found = true;
break;
}
}
if (found) {
correlations.push({
factor1: risk1,
factor2: risk2,
correlation,
significance: correlation > 0.7 ? 0.95 : 0.8
});
}
}
}
return correlations;
}
private identifyRiskClusters(
correlations: RiskCorrelation['correlations'],
risks: any[]
): RiskCorrelation['risk_clusters'] {
const clusters: RiskCorrelation['risk_clusters'] = [];
// Group highly correlated risks
const highCorrelations = correlations.filter(c => c.correlation > 0.6);
const riskGroups = new Map<string, Set<string>>();
highCorrelations.forEach(corr => {
let group1 = Array.from(riskGroups.entries()).find(([_, risks]) =>
risks.has(corr.factor1)
);
let group2 = Array.from(riskGroups.entries()).find(([_, risks]) =>
risks.has(corr.factor2)
);
if (group1 && group2 && group1[0] !== group2[0]) {
// Merge groups
group2[1].forEach(risk => group1[1].add(risk));
riskGroups.delete(group2[0]);
} else if (group1) {
group1[1].add(corr.factor2);
} else if (group2) {
group2[1].add(corr.factor1);
} else {
// Create new group
const groupName = `Risk Cluster ${riskGroups.size + 1}`;
riskGroups.set(groupName, new Set([corr.factor1, corr.factor2]));
}
});
// Convert to clusters with impact assessment
riskGroups.forEach((factors, name) => {
const clusterRisks = risks.filter(r => factors.has(r.factor));
const maxImpact = Math.max(...clusterRisks.map(r =>
r.impact === 'critical' ? 4 : r.impact === 'high' ? 3 : r.impact === 'medium' ? 2 : 1
));
clusters.push({
name,
factors: Array.from(factors),
combined_impact: maxImpact === 4 ? 'critical' :
maxImpact === 3 ? 'high' :
maxImpact === 2 ? 'medium' : 'low'
});
});
return clusters;
}
private prioritizeMitigations(
clusters: RiskCorrelation['risk_clusters'],
correlations: RiskCorrelation['correlations']
): string[] {
// Prioritize based on cluster impact and correlation strength
const priorities = clusters
.map(cluster => ({
name: cluster.factors[0], // Primary factor
score: (cluster.combined_impact === 'critical' ? 4 :
cluster.combined_impact === 'high' ? 3 :
cluster.combined_impact === 'medium' ? 2 : 1) *
cluster.factors.length
}))
.sort((a, b) => b.score - a.score)
.map(p => p.name);
return priorities;
}
private generateScenarios(
projectData: any,
marketConditions?: any
): OutcomePrediction['scenarios'] {
const baseROI = projectData.summary?.expected_roi || 100;
const basePayback = projectData.summary?.payback_period_months || 18;
const scenarios = [
{
name: 'Conservative',
probability: 0.25,
roi: baseROI * 0.7,
payback_months: basePayback * 1.4,
key_assumptions: [
'Slower adoption than expected',
'Higher implementation costs',
'Limited initial scope'
]
},
{
name: 'Expected',
probability: 0.5,
roi: baseROI,
payback_months: basePayback,
key_assumptions: [
'Normal adoption curve',
'On-budget implementation',
'Planned scope delivery'
]
},
{
name: 'Optimistic',
probability: 0.2,
roi: baseROI * 1.3,
payback_months: basePayback * 0.8,
key_assumptions: [
'Rapid user adoption',
'Efficiency gains in implementation',
'Additional use cases identified'
]
},
{
name: 'Best Case',
probability: 0.05,
roi: baseROI * 1.6,
payback_months: basePayback * 0.6,
key_assumptions: [
'Viral adoption across organization',
'Significant cost savings discovered',
'Network effects realized'
]
}
];
// Adjust for market conditions if provided
if (marketConditions?.sentiment === 'bullish') {
scenarios.forEach(s => {
s.probability = s.name === 'Optimistic' || s.name === 'Best Case'
? s.probability * 1.2
: s.probability * 0.8;
});
}
// Normalize probabilities
const totalProb = scenarios.reduce((sum, s) => sum + s.probability, 0);
scenarios.forEach(s => s.probability = s.probability / totalProb);
return scenarios;
}
private identifyMostLikelyScenario(
scenarios: OutcomePrediction['scenarios'],
projectData: any
): string {
// Adjust probabilities based on project characteristics
const adjustedScenarios = scenarios.map(scenario => {
let adjustedProb = scenario.probability;
// High confidence data favors expected/optimistic
if (projectData.metadata?.confidence_level > 0.9) {
if (scenario.name === 'Expected' || scenario.name === 'Optimistic') {
adjustedProb *= 1.2;
}
}
// Quick payback favors optimistic scenarios
if (projectData.summary?.payback_period_months < 12) {
if (scenario.name === 'Optimistic' || scenario.name === 'Best Case') {
adjustedProb *= 1.15;
}
}
return { ...scenario, adjustedProb };
});
// Find scenario with highest adjusted probability
const mostLikely = adjustedScenarios.reduce((prev, curr) =>
curr.adjustedProb > prev.adjustedProb ? curr : prev
);
return mostLikely.name;
}
private analyzeVarianceDrivers(
scenarios: OutcomePrediction['scenarios'],
projectData: any
): OutcomePrediction['variance_drivers'] {
const drivers = [];
// Calculate ROI variance
const roiValues = scenarios.map(s => s.roi);
const roiVariance = this.calculateVariance(roiValues);
// Identify main variance drivers
const potentialDrivers = [
{
factor: 'Adoption Rate',
sensitivity: 0.35,
controllable: true,
relevance: projectData.use_cases?.some((uc: any) =>
uc.category === 'customer_service' || uc.category === 'hr_recruiting'
)
},
{
factor: 'Implementation Efficiency',
sensitivity: 0.25,
controllable: true,
relevance: true
},
{
factor: 'Market Conditions',
sensitivity: 0.2,
controllable: false,
relevance: projectData.summary?.total_investment > 1000000
},
{
factor: 'Technology Performance',
sensitivity: 0.3,
controllable: true,
relevance: projectData.use_cases?.some((uc: any) =>
uc.category === 'analytics' || uc.name.includes('ML')
)
},
{
factor: 'Regulatory Changes',
sensitivity: 0.15,
controllable: false,
relevance: ['financial_services', 'healthcare'].includes(projectData.project?.industry)
}
];
// Filter and sort by relevance and sensitivity
return potentialDrivers
.filter(d => d.relevance)
.sort((a, b) => b.sensitivity - a.sensitivity)
.map(({ factor, sensitivity, controllable }) => ({
factor,
sensitivity,
controllable
}));
}
private calculateProjectSimilarities(
current: any,
historical: any[]
): Array<{
project_id: string;
similarity_score: number;
outcome: string;
key_similarities: string[];
}> {
return historical.map(hist => {
const similarities = [];
let score = 0;
// Industry match
if (current.project?.industry === hist.project?.industry) {
score += 0.3;
similarities.push(`Same industry: ${current.project.industry}`);
}
// Investment range match (within 50%)
const currentInv = current.summary?.total_investment || 0;
const histInv = hist.summary?.total_investment || 0;
if (Math.abs(currentInv - histInv) / currentInv < 0.5) {
score += 0.2;
similarities.push('Similar investment scale');
}
// Use case category overlap
const currentCategories = new Set(current.use_cases?.map((uc: any) => uc.category) || []);
const histCategories = new Set(hist.use_cases?.map((uc: any) => uc.category) || []);
const overlap = Array.from(currentCategories).filter(c => histCategories.has(c)).length;
const overlapRatio = overlap / Math.max(currentCategories.size, histCategories.size);
score += overlapRatio * 0.25;
if (overlap > 0) {
similarities.push(`${overlap} common use case categories`);
}
// Complexity similarity
const currentComplexity = current.use_cases?.length || 0;
const histComplexity = hist.use_cases?.length || 0;
if (Math.abs(currentComplexity - histComplexity) <= 2) {
score += 0.15;
similarities.push('Similar project complexity');
}
// Timeline similarity
const currentTimeline = current.timeline_months || 12;
const histTimeline = hist.timeline_months || 12;
if (Math.abs(currentTimeline - histTimeline) <= 3) {
score += 0.1;
similarities.push('Similar implementation timeline');
}
return {
project_id: hist.project_id,
similarity_score: score,
outcome: hist.outcome || 'Unknown',
key_similarities: similarities
};
});
}
private extractSuccessIndicators(matches: any[]): string[] {
const successfulProjects = matches.filter(m =>
m.outcome === 'Successful' || m.outcome === 'Exceeded Expectations'
);
const indicators = new Set<string>();
successfulProjects.forEach(project => {
project.key_similarities.forEach((sim: string) => {
indicators.add(sim);
});
});
return Array.from(indicators);
}
private extractWarningSigns(matches: any[]): string[] {
const failedProjects = matches.filter(m =>
m.outcome === 'Failed' || m.outcome === 'Underperformed'
);
const warnings = new Set<string>();
failedProjects.forEach(project => {
project.key_similarities.forEach((sim: string) => {
warnings.add(`Risk factor: ${sim}`);
});
});
return Array.from(warnings);
}
private calculateVariance(values: number[]): number {
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
const squaredDiffs = values.map(val => Math.pow(val - mean, 2));
return squaredDiffs.reduce((sum, diff) => sum + diff, 0) / values.length;
}
}
// Export singleton instance
export const predictiveAnalytics = new PredictiveAnalytics();