⏱️ AI Project Effort Estimator

Convert project scope into accurate effort estimates

Project Inputs

Give your project a name for the report
Select the project phase to get feature estimate guidance
Based on your selection above
Complexity identified in business envisioning workshops
Auto-calculated based on industry guidance (Fibonacci: 1, 2, 3, 5, 8, 13, 21)
Auto-calculated: Industry standard 2-week sprints
Full-time equivalent team members
Risk buffer based on project uncertainty and requirements clarity

Effort Estimate

Total Effort
-
Calculate to see results
Timeline (with - FTE)
-
-
Industry guidance for story points based on complexity const storyPointGuidance = { low: { avg: 3, description: 'Low complexity: 2-5 story points per feature', velocityPerFTE: 8 // story points per sprint per FTE }, medium: { avg: 5, description: 'Medium complexity: 3-8 story points per feature', velocityPerFTE: 6 // story points per sprint per FTE }, high: { avteamVelocity = parseInt(document.getElementById('teamVelocity').value); const teamSize = parseInt(document.getElementById('teamSize').value); const riskBuffer = parseInt(document.getElementById('riskLevel').value) / 100; const includeDiscovery = document.getElementById('includeDiscovery').checked; const includeDeployment = document.getElementById('includeDeployment').checked; // Calculate total story points const totalStoryPoints = numFeatures * avgStoryPoints; // Industry standard: 6-8 productive hours per day, use 6.5 as average const productiveHoursPerDay = 6.5; const daysPerSprint = 10; // 2-week sprint const hoursPerSprint = productiveHoursPerDay * daysPerSprint; // Calculate hours per story point based on velocity // If team completes 'teamVelocity' points in a sprint with 'teamSize' people const hoursPerPoint = (teamSize * hoursPerSprint) / teamVelocity; // Calculate base development effort const baseHours = totalStoryPoints * hoursPerPoint function updateFeatureEstimate() { const phase = document.getElementById('journeyPhase').value; const numFeaturesInput = document.getElementById('numFeatures'); const guidance = document.getElementById('featureGuidance'); const featureRanges = { discovery: { min: 5, max: 10, avg: 7 }, mvp: { min: 10, max: 20, avg: 15 }, pilot: { min: 20, max: 40, avg: 30 }, production: { min: 40, max: 80, avg: 60 }, enterprise: { min: 80, max: 150, avg: 115 }, custom: { avg: parseInt(numFeaturesInput.value) } }; if (phase && phase !== 'custom') { const range = featureRanges[phase]; numFeaturesInput.value = range.avg; guidance.textContent = `Typical range: ${range.min}-${range.max} features for this phase`; calculateStoryPoints(); } else if (phase === 'custom') { guidance.textContent = 'Enter your custom feature count'; } } functioCalculate number of sprints const numSprints = Math.ceil(totalStoryPoints / teamVelocity); // Display results document.getElementById('totalHours').textContent = Math.round(totalHours).toLocaleString() + ' hrs'; document.getElementById('totalDays').textContent = `${Math.round(totalDays)} working days | ${numSprints} sprints (2 weeks each)`; document.getElementById('teamSizeDisplay').textContent = teamSize; if (totalMonths >= 1) { document.getElementById('timeline').textContent = totalMonths.toFixed(1) + ' months'; } else { document.getElementById('timeline').textContent = Math.round(totalWeeks) + ' weeks'; } document.getElementById('timelineWeeks').textContent = `${Math.round(totalWeeks)} weeks | ${numSprints} sprint document.getElementById('teamVelocity').value = teamVelocity; document.getElementById('velocityGuidance').textContent = `${guidance.velocityPerFTE} points/sprint/FTE × ${teamSize} FTE = ${teamVelocity} points per 2-week sprint`; } function updateRiskBuffer() { const riskLevel = document.getElementById('riskLevel').value; // Risk levels are now in the select options: 4%, 7%, 15% } // Update velocity when team size changes document.getElementById('teamSize').addEventListener('input', calculateStoryPointsocument.getElementById('riskBuffer').addEventListener('input', function() { document.getElementById('riskBufferValue').textContent = this.value + '%'; }); function calculateEffort() { // Get inputs const projectName = document.getElementById('projectName').value || 'AI Project'; const numFeatures = parseInt(document.getElementById('numFeatures').value); const complexity = document.getElementById('complexity').value; const avgStoryPoints = parseInt(document.getElementById('avgStoryPoints').value); const hoursPerPoint = parseFloat(document.getElementById('hoursPerPoint').value); const teamSize = parseInt(document.getElementById('teamSize').value); const riskBuffer = parseInt(document.getElementById('riskBuffer').value) / 100; const includeDiscovery = document.getElementById('includeDiscovery').checked; const includeDeployment = document.getElementById('includeDeployment').checked; // Complexity multipliers const complexityMultipliers = { low: 0.8, medium: 1.0, high: 1.3, veryHigh: 1.6 }; // Calculate base effort const baseHours = numFeatures * avgStoryPoints * hoursPerPoint * complexityMultipliers[complexity]; // Phase percentages (of base development hours) const phases = { development: { name: 'Development & Build', percentage: 1.0, hours: baseHours } }; if (includeDiscovery) { phases.discovery = { name: 'Discovery & Planning', percentage: 0.15, hours: baseHours * 0.15 }; } phases.testing = { name: 'Testing & QA', percentage: 0.25, hours: baseHours * 0.25 }; if (includeDeployment) { phases.deployment = { name: 'Deployment & Handover', percentage: 0.10, hours: baseHours * 0.10 }; } // Calculate subtotal let subtotal = Object.values(phases).reduce((sum, phase) => sum + phase.hours, 0); // Add risk buffer const bufferHours = subtotal * riskBuffer; const totalHours = subtotal + bufferHours; // Calculate timeline const workingHoursPerDay = 6; // Productive hours per day const totalDays = totalHours / (teamSize * workingHoursPerDay); const totalWeeks = totalDays / 5; const totalMonths = totalWeeks / 4; // Display results document.getElementById('totalHours').textContent = Math.round(totalHours).toLocaleString() + ' hrs'; document.getElementById('totalDays').textContent = `${Math.round(totalDays)} working days (${Math.round(totalHours/teamSize)} hrs per FTE)`; document.getElementById('teamSizeDisplay').textContent = teamSize; if (totalMonths >= 1) { document.getElementById('timeline').textContent = totalMonths.toFixed(1) + ' months'; } else { document.getElementById('timeline').textContent = Math.round(totalWeeks) + ' weeks'; } document.getElementById('timelineWeeks').textContent = `${Math.round(totalWeeks)} weeks | ${Math.round(totalDays)} days`; // Display phase breakdown const phaseBreakdownDiv = document.getElementById('phaseBreakdown'); phaseBreakdownDiv.style.display = 'block'; phaseBreakdownDiv.innerHTML = '

Phase Breakdown

'; const phaseOrder = ['discovery', 'development', 'testing', 'deployment']; phaseOrder.forEach(key => { if (phases[key]) { const phase = phases[key]; const percentage = (phase.hours / totalHours * 100).toFixed(1); const phaseDiv = document.createElement('div'); phaseDiv.className = 'phase-item'; phaseDiv.innerHTML = `
${phase.name}
${Math.round(phase.hours).toLocaleString()} hrs (${percentage}%)
`; phaseBreakdownDiv.appendChild(phaseDiv); } }); // Add risk buffer display const bufferDiv = document.createElement('div'); bufferDiv.className = 'phase-item'; bufferDiv.style.background = '#fff3cd'; bufferDiv.innerHTML = `
⚠️ Risk Buffer / Contingency
${Math.round(bufferHours).toLocaleString()} hrs (${(riskBuffer * 100).toFixed(0)}%)
`; phaseBreakdownDiv.appendChild(bufferDiv); // Display team composition const teamCompositionDiv = document.getElementById('teamComposition'); teamCompositionDiv.style.display = 'block'; teamCompositionDiv.innerHTML = '

Suggested Team Composition

'; const roles = calculateTeamComposition(teamSize, complexity); roles.forEach(role => { const roleDiv = document.createElement('div'); roleDiv.className = 'role-item'; roleDiv.innerHTML = ` ${role.title} ${role.count} FTE (${role.percentage}%) `; teamCompositionDiv.appendChild(roleDiv); }); // Show export buttons document.getElementById('exportButtons').style.display = 'flex'; // StoretotalStoryPoints, teamVelocity, sprintDuration: '2 weeks', teamSize, riskLevel: riskBuffer * 100 + '%' inputs: { numFeatures, complexity, avgStoryPoints, hoursPerPoint, teamSize, totalSprints: numSprints, riskBufferPercentage: riskBuffer * 100, includeDiscovery, includeDeployment }, results: { totalHours: Math.round(totalHours), totalDays: Math.round(totalDays), totalWeeks: Math.round(totalWeeks), totalMonths: parseFloat(totalMonths.toFixed(1)), hoursPerFTE: Math.round(totalHours/teamSize) }, phases: Object.entries(phases).map(([key, phase]) => ({ name: phase.name, hours: Math.round(phase.hours), percentage: (phase.hours / totalHours * 100).toFixed(1) })).concat([{ name: 'Risk Buffer', hours: Math.round(bufferHours), percentage: (riskBuffer * 100).toFixed(0) }]), teamComposition: roles }; } function calculateTeamComposition(teamSize, complexity) { // Base percentages let roles = []; if (teamSize <= 3) { // Small team roles = [ { title: 'Tech Lead / Architect', count: 1, percentage: '33%' }, { title: 'ML Engineers / Developers', count: Math.max(1, teamSize - 2), percentage: `${Math.round((teamSize-2)/teamSize*100)}%` }, { title: 'QA / DevOps', count: 1, percentage: '33%' } ]; } else if (teamSize <= 8) { // Medium team const mlEngineers = Math.ceil(teamSize * 0.5); const qaDevOps = Math.ceil(teamSize * 0.2); const architect = 1; const pm = 1; roles = [ { title: 'Product Manager', count: pm, percentage: '12%' }, { title: 'Solution Architect', count: architect, percentage: '12%' }, { title: 'ML Engineers / Developers', count: mlEngineers, percentage: '50%' }, { title: 'QA Engineers', count: Math.max(1, Math.floor(qaDevOps/2)), percentage: '13%' }, { title: 'DevOps Engineer', count: Math.max(1, Math.ceil(qaDevOps/2)), percentage: '13%' } ]; } else { // Large team roles = [ { title: 'Product Manager', count: 1, percentage: '8%' }, { title: 'Solution Architect', count: 1, percentage: '8%' }, { title: 'Tech Lead', count: 1, percentage: '8%' }, { title: 'ML Engineers / Data Scientists', count: Math.ceil(teamSize * 0.4), percentage: '40%' }, { title: 'Software Engineers', count: Math.ceil(teamSize * 0.2), percentage: '20%' }, { title: 'QA Engineers', count: Math.ceil(teamSize * 0.08), percentage: '8%' }, { title: 'DevOps Engineer', count: Math.ceil(teamSize * 0.08), percentage: '8%' } ]; } return roles; } function exportToJSON() { const dataStr = JSON.stringify(window.effortData, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = `effort-estimate-${window.effortData.projectName.replace(/\s+/g, '-')}.json`; link.click(); URL.revokeObjectURL(url); } function copyToClipboard() { const data = window.effortData; Total Story Points: ${data.inputs.totalStoryPoints} - Complexity: ${data.inputs.complexity} - Avg Story Points per Feature: ${data.inputs.avgStoryPoints} - Team Velocity: ${data.inputs.teamVelocity} points per sprint - Sprint Duration: ${data.inputs.sprintDuration} - Team Size: ${data.inputs.teamSize} FTE - Risk Level: ${data.inputs.riskLevel} RESULTS: - Total Effort: ${data.results.totalHours.toLocaleString()} hours - Timeline: ${data.results.totalMonths} months (${data.results.totalWeeks} weeks, ${data.results.totalSprints} sprint - Team Size: ${data.inputs.teamSize} FTE - Risk Buffer: ${data.inputs.riskBufferPercentage}% RESULTS: - Total Effort: ${data.results.totalHours.toLocaleString()} hours - Timeline: ${data.results.totalMonths} months (${data.results.totalWeeks} weeks) - Effort per FTE: ${data.results.hoursPerFTE.toLocaleString()} hours PHASE BREAKDOWN: ${data.phases.map(p => `- ${p.name}: ${p.hours.toLocaleString()} hrs (${p.percentage}%)`).join('\n')} TEAM COMPOSITION: ${data.teamComposition.map(r => `- ${r.title}: ${r.count} FTE (${r.percentage})`).join('\n')} `.trim(); navigator.clipboard.writeText(summary).then(() => { alert('✅ Summary copied to clipboard!'); }); }