Inflation-Protected Retirement Planning Model (Conceptual Simulator)

Retirement Goals & Timeline

$
$

Portfolio & Inflation Assumptions

Define your conceptual portfolio allocation and expected nominal returns for each asset class. The tool will then show a blended portfolio return.

Total Allocation: 0%

%

Contributions (Pre-Retirement) & Withdrawal Strategy (Retirement)

Accumulation Phase (Pre-Retirement)

$
%
How much your contributions increase each year (e.g., matching salary raises).

Decumulation Phase (Retirement)

$
Applies to "Fixed Amount" and "Fixed Percentage of Initial Portfolio" strategies.

Retirement Portfolio Projections & Inflation Impact

Summary of projections will appear here.

Portfolio Value Over Time (Nominal & Real)

Portfolio value chart appears here.

Annual Withdrawals Over Time (Nominal & Real)

Withdrawal chart appears here.

Year-by-Year Projection Details:

Projection table will appear here.

Value chart.

'; withdrawalChartContainer.innerHTML = '

Withdrawal chart.

'; tableContainer.innerHTML = '

Projection table.

'; return; } const r = iprpm_results; const s = r.summary; const i = r.inputsSnapshot; const totalSimYears = (i.retirementAge - i.currentAge) + i.retirementDurationYears; let longevityMsg = ""; if (s.portfolioDepletedInSimYear !== null && s.portfolioDepletedInSimYear <= totalSimYears) { if (s.portfolioDepletedInSimYear <= (i.retirementAge - i.currentAge)) { longevityMsg = `Portfolio depleted in Accumulation Year ${s.portfolioDepletedInSimYear}.`; } else { longevityMsg = `Portfolio depleted in Simulation Year ${s.portfolioDepletedInSimYear} (Retirement Year ${s.portfolioDepletedInSimYear - (i.retirementAge - i.currentAge)}).`; } } else { longevityMsg = `Portfolio lasts for the full ${i.retirementDurationYears}-year retirement period.`; if (i.desiredLegacy > 0) { longevityMsg += s.meetsLegacy ? ` It also meets the desired legacy of ${iprpm_formatCurrency(i.desiredLegacy)}.` : ` However, it does not meet desired legacy.`; } } summaryDiv.innerHTML = `

Projection Summary (Over ${totalSimYears} Years Total)

Portfolio Longevity: ${longevityMsg}

Final Nominal Portfolio Value: ${iprpm_formatCurrency(s.finalNominalValue)}
Final Real Portfolio Value (Today's $): ${iprpm_formatCurrency(s.finalRealValue)}
Total Nominal Withdrawals (Retirement): ${iprpm_formatCurrency(s.totalNominalWithdrawals)}
Total Real Withdrawals (Retirement, Today's $): ${iprpm_formatCurrency(s.totalRealWithdrawals)}
`; const nominalValues = [i.initialPortfolioValue].concat(r.projectionsTable.map(p => p.endValueNominal)); const realValues = [i.initialPortfolioValue].concat(r.projectionsTable.map(p => p.endValueReal)); iprpm_renderDualLineChart(nominalValues, realValues, 'Portfolio Value ($)', valueChartContainer, 'Nominal Value', 'Real Value', '#A78BFA', '#6EE7B7', totalSimYears); const nominalWithdrawals = [0].concat(r.projectionsTable.map(p => p.withdrawalNominal)); const realWithdrawals = [0].concat(r.projectionsTable.map(p => p.withdrawalReal)); iprpm_renderDualLineChart(nominalWithdrawals, realWithdrawals, 'Annual Withdrawal ($)', withdrawalChartContainer, 'Nominal', 'Real', '#FBBF24', '#F59E0B', totalSimYears); let tableHTML = ``; r.projectionsTable.forEach(p => { tableHTML += ``; }); tableHTML += `
Sim.YearAgePhaseStart(N)Contrib(N)Withdraw(N)Growth(N)End(N)End(Real)
${p.year}${p.age}${p.phase} ${iprpm_formatCurrency(p.startValueNominal)} ${iprpm_formatCurrency(p.contributionNominal)} ${iprpm_formatCurrency(p.withdrawalNominal)} ${iprpm_formatCurrency(p.growthNominal)} ${iprpm_formatCurrency(p.endValueNominal)} ${iprpm_formatCurrency(p.endValueReal)}
`; tableContainer.innerHTML = tableHTML; const pdfBtn = document.getElementById('iprpm_downloadPdfButton'); if(pdfBtn) pdfBtn.style.display = 'block'; } function iprpm_renderDualLineChart(data1, data2, yAxisLabel, container, label1, label2, color1, color2, xMaxOverride) { container.innerHTML = ''; const svgWidth = Math.min(800, (iprpm_getEl('iprpmContainer_main').offsetWidth || 400)/ (container.id === 'iprpm_valueChartContainer' ? 1.5 : 1) - 30); const svgHeight = 250; const m = {top: 20, right: 110, bottom: 40, left: 75}; const w = svgWidth - m.left - m.right; const h = svgHeight - m.top - m.bottom; const allYValues = data1.concat(data2); const yMin = Math.min(0, ...allYValues.filter(v=>!isNaN(v) && v !== null && isFinite(v))); const yMax = Math.max(...allYValues.filter(v=>!isNaN(v) && v !== null && isFinite(v)), 0.01); const xMax = xMaxOverride !== null ? xMaxOverride : Math.max(data1.length -1, data2.length -1); const xScale = x => (x / (xMax === 0 ? 1 : xMax)) * w; const yScale = y => h - ((y - yMin) / (yMax - yMin === 0 ? 1 : yMax - yMin)) * h; let path1Data = data1.length > 0 ? "M" + data1.map((val, i) => `${xScale(i).toFixed(2)},${yScale(val).toFixed(2)}`).join(" L") : ""; let path2Data = data2 && data2.length > 0 ? "M" + data2.map((val, i) => `${xScale(i).toFixed(2)},${yScale(val).toFixed(2)}`).join(" L") : ""; container.innerHTML = ` Years (0 - ${xMax}) ${yAxisLabel} ${iprpm_formatCurrency(yMax,0)} ${iprpm_formatCurrency(yMin,0)} ${path1Data ? `` : ''} ${path2Data ? `` : ''} ${label1} ${path2Data ? ` ${label2}` : ''} `; } // --- PDF Generation --- function iprpm_downloadPDF() { if (!iprpm_results) { alert("Please run the projection first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Core PDF library (jsPDF) is not loaded.'); console.error('jsPDF library not found.'); return; } const { jsPDF: JSPDF } = window.jspdf; const doc = new JSPDF('landscape'); if (typeof doc.autoTable !== 'function') { alert('PDF Table plugin (jsPDF-AutoTable) not loaded correctly. Tables in PDF may be missing.'); console.error('doc.autoTable is not a function.'); } let y = 15; const m = 10; const r = iprpm_results; const i = r.inputsSnapshot; const yearsToRetirement = i.retirementAge - i.currentAge; const totalSimYears = yearsToRetirement + i.retirementDurationYears; function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) { const cw = doc.internal.pageSize.getWidth() - (2 * m); if (y > 185 && size > 8) { doc.addPage(); y = m; } else if (y > 190) { doc.addPage(); y = m; } doc.setFontSize(size); doc.setFont(undefined, style); const lines = doc.splitTextToSize(text, cw - indent); doc.text(lines, m + indent, y); y += (lines.length * (size * 0.35)) + spacing; } addLine(`Inflation-Protected Retirement Plan Simulation: ${i.strategyName || 'N/A'}`, 16, 'bold', 0, 5); addLine(`Report Date: ${new Date().toLocaleDateString()}`, 9, 'italic', 0, 5); addLine("Initial Setup & Assumptions:", 11, 'bold', 0, 3); let inputData = [ ["Current Age:", `${i.currentAge} yrs`, "Retirement Age:", `${i.retirementAge} yrs`], ["Retirement Duration:", `${i.retirementDurationYears} yrs`, "Initial Portfolio:", iprpm_formatCurrency(i.initialPortfolioValue)], ["Desired Legacy:", iprpm_formatCurrency(i.desiredLegacy), "Annual Inflation (CPI):", iprpm_formatPercent(i.inflationRate*100)], ["Portfolio Nom. E(R):", iprpm_formatPercent(i.expectedNominalReturn*100), "Annual Contribution:", iprpm_formatCurrency(i.annualContribution)], ["Contrib. Growth:", iprpm_formatPercent(i.contributionGrowth*100), "Withdrawal Strategy:", i.spendingStrategyType.replace(/([A-Z])/g, ' $1').trim()], ]; if(i.spendingStrategyType === 'fixedAmountInflationAdjusted') inputData.push(["Initial Withdrawal Amt:", iprpm_formatCurrency(i.initialAnnualWithdrawalAmount), "Infl. Adjusted:", i.adjustWithdrawalsForInflation ? 'Yes':'No']); if(i.spendingStrategyType.includes('Percent')) inputData.push(["Withdrawal Rate:", iprpm_formatPercent(i.withdrawalPercentage*100), i.spendingStrategyType === 'percentInitialInflationAdjusted' ? "Infl. Adjusted:" : "", i.spendingStrategyType === 'percentInitialInflationAdjusted' ? (i.adjustWithdrawalsForInflation ? 'Yes':'No') : ""]); if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, body: inputData, theme: 'plain', styles:{fontSize:8, cellPadding:1}, columnStyles:{0:{fontStyle:'bold'}, 2:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 5; } else { inputData.forEach(row => addLine(`${row[0]} ${row[1]} | ${row[2]} ${row[3] || ''}`, 8)); y+=5; } addLine("Asset Allocation (Conceptual):", 11, 'bold', 0, 2); const assetHead = [['Asset Class', 'Allocation (%)', 'Nominal E(R) (%)']]; const assetBody = (i.assetClasses || []).map(ac => [ac.name, (ac.allocation||0).toFixed(1), (ac.nominalReturn||0).toFixed(1)]); if(typeof doc.autoTable === 'function' && assetBody.length > 0) { doc.autoTable({startY: y, head: assetHead, body: assetBody, theme: 'striped', headStyles:{fillColor:[76,175,80]}, styles:{fontSize:8}}); y = doc.lastAutoTable.finalY + 5; } else { addLine(" - No asset classes detailed or autoTable issue.", 8, 'italic'); y+=3;} if (y > 180) {doc.addPage(); y=m;} addLine("Projection Summary:", 11, 'bold', 0, 3); const s = r.summary; let summaryData = [ ["Portfolio Longevity:", s.portfolioLastsRetirement ? `Lasts for ${i.retirementDurationYears} retirement years` : `Depleted in Sim. Year ${s.portfolioDepletedInSimYear}`], ["Final Nominal Portfolio Value:", iprpm_formatCurrency(s.finalNominalValue)], ["Final Real Portfolio Value (Today's $):", iprpm_formatCurrency(s.finalRealValue)], ["Meets Desired Legacy of "+iprpm_formatCurrency(i.desiredLegacy)+":", s.meetsLegacy ? "Yes" : "No"], ["Total Nominal Withdrawals (Retirement):", iprpm_formatCurrency(s.totalNominalWithdrawals)], ["Total Real Withdrawals (Retirement, Today's $):", iprpm_formatCurrency(s.totalRealWithdrawals)], ]; if (typeof doc.autoTable === 'function') { doc.autoTable({startY: y, body: summaryData, theme: 'plain', styles:{fontSize:8, cellPadding:1}, columnStyles:{0:{fontStyle:'bold'}}}); y = doc.lastAutoTable.finalY + 6; } else { summaryData.forEach(row => addLine(`${row[0]} ${row[1]}`, 8)); y+=5; } if (y > 180 && r.projectionsTable.length > 5) {doc.addPage(); y=m;} addLine(`Year-by-Year Projections (Excerpt if long):`, 10, 'bold', 0, 3); const head = [['Sim.Yr', 'Age', 'Phase', 'Start(N)', 'Contrib(N)', 'Withdraw(N)', 'Growth(N)', 'End(N)', 'End(Real)']]; const maxPdfRows = 15; const getExcerpt = (projections) => { if (projections.length <= maxPdfRows) return projections; const half = Math.floor(maxPdfRows/2); // Ensure indices are valid const midStartIndex = Math.max(0, Math.floor(projections.length/2) - Math.floor(maxPdfRows/6)); const midEndIndex = Math.min(projections.length, midStartIndex + Math.ceil(maxPdfRows/3)); const startSlice = projections.slice(0, Math.floor(maxPdfRows/3)); const midSlice = projections.slice(midStartIndex, midEndIndex); const endSlice = projections.slice(projections.length - Math.floor(maxPdfRows/3)); // Combine and deduplicate based on year const combined = [...startSlice, ...midSlice, ...endSlice]; return combined.filter((v,i,a) => a.findIndex(t=>(t.year === v.year))===i); }; let body = getExcerpt(r.projectionsTable).map(p => [ p.year, p.age, p.phase, p.startValueNominal.toFixed(0), p.contributionNominal.toFixed(0), p.withdrawalNominal.toFixed(0), p.growthNominal.toFixed(0), p.endValueNominal.toFixed(0), p.endValueReal.toFixed(0) ]); if (typeof doc.autoTable === 'function') { doc.autoTable({ startY: y, head: head, body: body, theme: 'grid', headStyles: { fillColor: [76,175,80], textColor: 255, fontSize: 7 }, styles: { fontSize: 6.5, cellPadding: 1, halign: 'right', overflow:'linebreak' }, columnStyles: { 0: { halign: 'center', fontStyle: 'bold'}, 1: {halign:'center'}, 2:{halign:'left', cellWidth:20} } }); y = doc.lastAutoTable.finalY + 7; } else { addLine("Projection table cannot be generated (plugin issue).", 8, 'italic'); y+=5;} if (y > 275) { doc.addPage(); y = m; } addLine("Note: This is a conceptual simulator based on user-defined assumptions (deterministic returns, constant inflation). It does not account for taxes or specific investment risks and is not financial advice.", 7, 'italic'); doc.save(`${(i.strategyName || 'InflationProtectedRetirement').replace(/\s+/g, '_')}.pdf`); }
Scroll to Top