@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
338 lines • 13.6 kB
JavaScript
export class PrioritizationEngine {
async prioritizeFeatures(features, criteria) {
let results = [];
switch (criteria.method) {
case 'rice':
results = await this.riceScoring(features);
break;
case 'value-effort':
results = await this.valueEffortMatrix(features);
break;
case 'moscow':
results = await this.moscowMethod(features);
break;
case 'kano':
results = await this.kanoModel(features);
break;
case 'custom':
results = await this.customWeightedScoring(features, criteria.weights);
break;
default:
throw new Error(`Unknown prioritization method: ${criteria.method}`);
}
// Sort by score descending and assign ranks
results.sort((a, b) => b.score - a.score);
results.forEach((result, index) => {
result.rank = index + 1;
});
return results;
}
async riceScoring(features) {
return features.map(feature => {
// Calculate RICE components based on feature properties
const reach = this.estimateReach(feature);
const impact = this.estimateImpact(feature);
const confidence = this.estimateConfidence(feature);
const effort = this.estimateEffort(feature);
const score = (reach * impact * confidence) / effort;
const riceBreakdown = {
reach,
impact,
confidence,
effort,
score
};
return {
featureId: feature.id,
method: 'rice',
score,
rank: 0, // Will be set later
rationale: `RICE Score: R=${reach}, I=${impact}, C=${confidence}, E=${effort}`,
breakdown: riceBreakdown
};
});
}
async valueEffortMatrix(features) {
return features.map(feature => {
const value = feature.businessValue.score;
const effort = this.estimateEffort(feature);
// Normalize to 0-100 scale
const normalizedEffort = Math.min(100, effort * 10);
// Score calculation: high value + low effort = high score
const score = value * (100 - normalizedEffort) / 100;
const category = this.getValueEffortCategory(value, normalizedEffort);
return {
featureId: feature.id,
method: 'value-effort',
score,
rank: 0,
rationale: `Value: ${value}/100, Effort: ${normalizedEffort}/100, Category: ${category}`,
breakdown: { value, effort: normalizedEffort, category }
};
});
}
async moscowMethod(features) {
return features.map(feature => {
const category = this.getMoscowCategory(feature);
// Assign scores based on MoSCoW categories
const categoryScores = {
'must-have': 100,
'should-have': 75,
'could-have': 50,
'wont-have': 25
};
const score = categoryScores[category];
return {
featureId: feature.id,
method: 'moscow',
score,
rank: 0,
rationale: `MoSCoW Category: ${category}`,
breakdown: { category }
};
});
}
async kanoModel(features) {
return features.map(feature => {
const category = this.getKanoCategory(feature);
// Assign scores based on Kano categories
const categoryScores = {
'basic': 90, // Must-be quality
'performance': 70, // One-dimensional quality
'excitement': 100, // Attractive quality
'indifferent': 30, // Indifferent quality
'reverse': 10 // Reverse quality
};
const score = categoryScores[category];
return {
featureId: feature.id,
method: 'kano',
score,
rank: 0,
rationale: `Kano Category: ${category}`,
breakdown: { category }
};
});
}
async customWeightedScoring(features, weights) {
return features.map(feature => {
// Calculate individual scores (0-100 scale)
const businessValueScore = feature.businessValue.score;
const userImpactScore = this.calculateUserImpactScore(feature);
const strategicAlignmentScore = this.calculateStrategicAlignmentScore(feature);
const technicalFeasibilityScore = this.calculateTechnicalFeasibilityScore(feature);
const riskScore = 100 - this.calculateRiskScore(feature); // Invert so lower risk = higher score
// Apply weights
const weightedScore = (businessValueScore * weights.businessValue +
userImpactScore * weights.userImpact +
strategicAlignmentScore * weights.strategicAlignment +
technicalFeasibilityScore * weights.technicalFeasibility +
riskScore * weights.risk) /
(weights.businessValue + weights.userImpact +
weights.strategicAlignment + weights.technicalFeasibility + weights.risk);
return {
featureId: feature.id,
method: 'custom',
score: weightedScore,
rank: 0,
rationale: `Weighted Score: BV=${businessValueScore}, UI=${userImpactScore}, SA=${strategicAlignmentScore}, TF=${technicalFeasibilityScore}, R=${riskScore}`,
breakdown: {
businessValue: businessValueScore,
userImpact: userImpactScore,
strategicAlignment: strategicAlignmentScore,
technicalFeasibility: technicalFeasibilityScore,
risk: riskScore,
weights
}
};
});
}
// Helper methods for RICE scoring
estimateReach(feature) {
// Estimate based on business value and user impact
const impactLevel = feature.businessValue.score;
if (impactLevel > 80)
return 10000; // High reach
if (impactLevel > 60)
return 5000; // Medium reach
if (impactLevel > 40)
return 1000; // Low reach
return 500; // Minimal reach
}
estimateImpact(feature) {
// 3 = massive impact, 2 = high, 1 = medium, 0.5 = low, 0.25 = minimal
const score = feature.businessValue.score;
if (score > 80)
return 3;
if (score > 60)
return 2;
if (score > 40)
return 1;
if (score > 20)
return 0.5;
return 0.25;
}
estimateConfidence(feature) {
// 100% = high confidence, 80% = medium, 50% = low
// Based on feature status and complexity
if (feature.status === 'approved')
return 1.0;
if (feature.status === 'proposed')
return 0.8;
if (feature.technicalComplexity === 'very-high')
return 0.5;
if (feature.technicalComplexity === 'high')
return 0.7;
return 0.9;
}
estimateEffort(feature) {
// Estimate person-months based on complexity
const complexityEffort = {
'low': 0.5,
'medium': 2,
'high': 4,
'very-high': 8
};
return complexityEffort[feature.technicalComplexity];
}
// Helper methods for Value-Effort Matrix
getValueEffortCategory(value, effort) {
if (value > 60 && effort < 40)
return 'Quick Wins';
if (value > 60 && effort >= 40)
return 'Major Projects';
if (value <= 60 && effort < 40)
return 'Fill-ins';
return 'Time Sinks';
}
// Helper methods for MoSCoW
getMoscowCategory(feature) {
const score = feature.businessValue.score;
const complexity = feature.technicalComplexity;
// High value, manageable complexity = must have
if (score > 70 && complexity !== 'very-high')
return 'must-have';
// High value but complex, or medium value with low complexity = should have
if ((score > 70 && complexity === 'very-high') ||
(score > 50 && complexity === 'low'))
return 'should-have';
// Medium value = could have
if (score > 30)
return 'could-have';
// Low value = won't have this time
return 'wont-have';
}
// Helper methods for Kano Model
getKanoCategory(feature) {
// This is a simplified categorization - in practice, you'd survey users
const value = feature.businessValue.score;
const description = feature.description.toLowerCase();
// Basic features (must-haves)
if (description.includes('security') ||
description.includes('performance') ||
description.includes('reliability')) {
return 'basic';
}
// Excitement features (delighters)
if (description.includes('ai') ||
description.includes('innovative') ||
description.includes('unique')) {
return 'excitement';
}
// Performance features (more is better)
if (value > 60)
return 'performance';
// Indifferent features
if (value < 30)
return 'indifferent';
return 'performance'; // Default
}
// Helper methods for custom scoring
calculateUserImpactScore(feature) {
// Simplified calculation - could be enhanced with actual user data
return Math.min(100, feature.businessValue.score * 1.2);
}
calculateStrategicAlignmentScore(feature) {
// Check if feature aligns with strategic themes
// In a real implementation, this would check against company strategy
return feature.businessValue.score; // Simplified
}
calculateTechnicalFeasibilityScore(feature) {
const feasibilityScores = {
'low': 90,
'medium': 70,
'high': 50,
'very-high': 30
};
return feasibilityScores[feature.technicalComplexity];
}
calculateRiskScore(feature) {
// Higher complexity = higher risk
const riskScores = {
'low': 20,
'medium': 40,
'high': 60,
'very-high': 80
};
return riskScores[feature.technicalComplexity];
}
// Initiative prioritization
async prioritizeInitiatives(initiatives, criteria) {
// Convert initiatives to a format similar to features for prioritization
const results = initiatives.map(initiative => {
let score = 0;
// Calculate score based on value metrics and effort
const valueScore = this.calculateInitiativeValueScore(initiative.value);
const effortScore = this.calculateInitiativeEffortScore(initiative.effort);
// Simple scoring: value / effort
score = valueScore / Math.max(1, effortScore);
return {
featureId: initiative.id, // Using featureId field for compatibility
method: criteria.method,
score,
rank: 0,
rationale: `Initiative Value: ${valueScore}, Effort: ${effortScore}`,
breakdown: {
value: valueScore,
effort: effortScore,
valueMetrics: initiative.value,
effortEstimate: initiative.effort
}
};
});
// Sort and rank
results.sort((a, b) => b.score - a.score);
results.forEach((result, index) => {
result.rank = index + 1;
});
return results;
}
calculateInitiativeValueScore(value) {
const impactScores = { low: 25, medium: 50, high: 75, critical: 100 };
const userImpactScore = impactScores[value.userImpact];
// Normalize financial metrics (assuming millions)
const revenueScore = Math.min(100, (value.revenueImpact / 1000000) * 20);
const savingsScore = Math.min(100, (value.costSavings / 1000000) * 30);
// Strategic value is already 1-10, scale to 100
const strategicScore = value.strategicValue * 10;
// Customer satisfaction impact (NPS scale -100 to 100, normalize to 0-100)
const satisfactionScore = (value.customerSatisfaction + 100) / 2;
// Weighted average
return (userImpactScore * 0.3 +
revenueScore * 0.2 +
savingsScore * 0.2 +
strategicScore * 0.2 +
satisfactionScore * 0.1);
}
calculateInitiativeEffortScore(effort) {
const totalWeeks = effort.developmentWeeks + effort.designWeeks + effort.qaWeeks;
// Adjust for confidence
const confidenceMultiplier = {
low: 1.5, // Low confidence = multiply effort by 1.5
medium: 1.0,
high: 0.9 // High confidence = slight reduction
};
return totalWeeks * confidenceMultiplier[effort.confidence];
}
}
//# sourceMappingURL=prioritization-engine.js.map