Personal Savings Stress Test Tool

Personal Savings Stress Test Tool

Your Financial Snapshot

Cash, checking, savings, easily accessible funds.
Your regular take-home pay that would be lost.

Essential Monthly Expenses

List only non-discretionary expenses needed to get by (housing, basic food, utilities, minimum debt payments).

No essential expenses added yet.

Total Essential Monthly Expenses: $0.00

Define Stress Events

Income Loss Scenario


Unexpected Large Expense Scenario

Stress Test Analysis

Analysis will appear here once all inputs are provided.

Summary Report

A summary of your stress test will appear here.

Savings Remaining After Expense: $${savingsAfterLargeExpense.toFixed(2)}

`; if (savingsAfterLargeExpense < 0) { analysisHTML += `

This expense would deplete your liquid savings and leave a deficit.

`; } else if (totalEssentialMonthlyExpenses <= 0) { analysisHTML += `

Please define your essential monthly expenses to see how long remaining savings would last.

`; } else { const monthsRemainingSavingsLast = savingsAfterLargeExpense > 0 && totalEssentialMonthlyExpenses > 0 ? savingsAfterLargeExpense / totalEssentialMonthlyExpenses : 0; let coverageText = ""; if (savingsAfterLargeExpense <= 0) { coverageText = "0 months (savings depleted or negative)."; } else if (totalEssentialMonthlyExpenses <=0) { coverageText = "Indefinitely (no essential expenses listed)."; } else { coverageText = `${monthsRemainingSavingsLast.toFixed(1)} months (assuming no other income).`; } analysisHTML += `

These remaining savings would cover essential monthly expenses ($${totalEssentialMonthlyExpenses.toFixed(2)}) for approximately: ${coverageText}

`; } analysisHTML += `
`; resultsContainer.innerHTML = analysisHTML; return { liquidSavings, totalEssentialMonthlyExpenses, incomeLossDuration, temporaryIncome, unexpectedExpenseAmount }; // For PDF } // --- Report & PDF (Tab 4) --- function psst_generateReportPreview() { const previewDiv = document.getElementById('psstReportPreview'); const snapshotData = { liquidSavings: psst_getFloatValue('psstLiquidSavings'), monthlyIncome: psst_getFloatValue('psstMonthlyIncome'), totalEssentialMonthlyExpenses: psst_essentialExpenses.reduce((sum, item) => sum + item.amount, 0) }; const eventData = { incomeLossDuration: psst_getIntValue('psstIncomeLossDuration', 6), temporaryIncome: psst_getFloatValue('psstTemporaryIncome', 0), unexpectedExpenseAmount: psst_getFloatValue('psstUnexpectedExpenseAmount', 0) }; // Simulate calculation again for preview data consistency const monthlyShortfall = Math.max(0, snapshotData.totalEssentialMonthlyExpenses - eventData.temporaryIncome); let monthsSavingsLastJobLoss = (monthlyShortfall > 0) ? snapshotData.liquidSavings / monthlyShortfall : Infinity; const savingsAfterLargeExpense = snapshotData.liquidSavings - eventData.unexpectedExpenseAmount; let monthsRemainingSavingsLast = (savingsAfterLargeExpense > 0 && snapshotData.totalEssentialMonthlyExpenses > 0) ? savingsAfterLargeExpense / snapshotData.totalEssentialMonthlyExpenses : 0; if (savingsAfterLargeExpense <=0) monthsRemainingSavingsLast = 0; if (snapshotData.totalEssentialMonthlyExpenses <=0 && savingsAfterLargeExpense > 0) monthsRemainingSavingsLast = Infinity; let html = `

Savings Stress Test Summary

Liquid Savings: $${snapshotData.liquidSavings.toFixed(2)}

Essential Monthly Expenses: $${snapshotData.totalEssentialMonthlyExpenses.toFixed(2)}


Income Loss Scenario (for ${eventData.incomeLossDuration} months)

Months savings cover shortfall: ${monthsSavingsLastJobLoss === Infinity ? 'Expenses Covered' : monthsSavingsLastJobLoss.toFixed(1)} months


Unexpected Expense Scenario ($${eventData.unexpectedExpenseAmount.toFixed(2)})

Savings after expense: $${savingsAfterLargeExpense.toFixed(2)}

Remaining savings cover essentials for: ${monthsRemainingSavingsLast === Infinity ? 'Indefinitely' : monthsRemainingSavingsLast.toFixed(1)} months (w/o income)

Full details and itemized expenses will be in the PDF.

`; previewDiv.innerHTML = html; } function psst_generatePdf() { const snapshot = { liquidSavings: psst_getFloatValue('psstLiquidSavings'), monthlyIncome: psst_getFloatValue('psstMonthlyIncome') }; const essentialExpensesList = [...psst_essentialExpenses]; const totalEssentialMonthlyExpenses = essentialExpensesList.reduce((sum, item) => sum + item.amount, 0); const events = { incomeLossDuration: psst_getIntValue('psstIncomeLossDuration', 6), temporaryIncome: psst_getFloatValue('psstTemporaryIncome', 0), unexpectedExpenseAmount: psst_getFloatValue('psstUnexpectedExpenseAmount', 0) }; const { jsPDF } = window.jspdf; const doc = new jsPDF(); let yPos = 20; const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(); const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-color').trim(); const dangerColor = getComputedStyle(document.documentElement).getPropertyValue('--danger-color').trim(); const warningColor = getComputedStyle(document.documentElement).getPropertyValue('--warning-color').trim(); const textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim(); const toCurrency = (num) => `$${num.toFixed(2)}`; doc.setFontSize(18); doc.setTextColor(primaryColor); doc.text("Personal Savings Stress Test Report", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 15; // Financial Snapshot doc.setFontSize(14); doc.setTextColor(primaryColor); doc.text("Financial Snapshot", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(textColor); doc.text(`Total Liquid Savings: ${toCurrency(snapshot.liquidSavings)}`, 14, yPos); yPos += 6; doc.text(`Net Monthly Income (at risk): ${toCurrency(snapshot.monthlyIncome)}`, 14, yPos); yPos += 6; doc.text(`Total Essential Monthly Expenses: ${toCurrency(totalEssentialMonthlyExpenses)}`, 14, yPos); yPos += 8; if (essentialExpensesList.length > 0) { const expensesTableBody = essentialExpensesList.map(item => [item.name, toCurrency(item.amount)]); doc.autoTable({ startY: yPos, head: [['Essential Expense Item', 'Monthly Cost ($)']], body: expensesTableBody, theme: 'grid', headStyles: { fillColor: primaryColor, textColor: '#ffffff' }, styles: { fontSize: 9, cellPadding: 2 }, didDrawPage: (d) => { yPos = d.cursor.y; } }); yPos = doc.lastAutoTable.finalY + 10; } // Stress Event Parameters if (yPos > 250) { doc.addPage(); yPos = 20; } doc.setFontSize(14); doc.setTextColor(primaryColor); doc.text("Stress Event Parameters", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(textColor); doc.text(`Assumed Duration of Income Loss: ${events.incomeLossDuration} months`, 14, yPos); yPos += 6; doc.text(`Expected Monthly Temporary Income: ${toCurrency(events.temporaryIncome)}`, 14, yPos); yPos += 6; doc.text(`Amount of Unexpected Large Expense: ${toCurrency(events.unexpectedExpenseAmount)}`, 14, yPos += 8); // Income Loss Scenario Results if (yPos > 240) { doc.addPage(); yPos = 20; } doc.setFontSize(14); doc.setTextColor(primaryColor); doc.text("Income Loss Scenario Analysis", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(textColor); const monthlyShortfall = Math.max(0, totalEssentialMonthlyExpenses - events.temporaryIncome); doc.text(`Monthly Shortfall to Cover: ${toCurrency(monthlyShortfall)}`, 14, yPos); yPos += 6; let monthsSavingsLastJobLoss = (monthlyShortfall > 0 && snapshot.liquidSavings > 0) ? snapshot.liquidSavings / monthlyShortfall : (snapshot.liquidSavings > 0 && monthlyShortfall <= 0 ? Infinity : 0); let jobLossResilienceText = monthsSavingsLastJobLoss === Infinity ? 'Indefinitely (expenses covered by temp. income)' : `${monthsSavingsLastJobLoss.toFixed(1)} months`; doc.text(`Savings would last approx.: ${jobLossResilienceText}`, 14, yPos); yPos += 6; if (monthsSavingsLastJobLoss !== Infinity) { if (monthsSavingsLastJobLoss >= events.incomeLossDuration) { doc.setTextColor(accentColor); doc.text(`This covers the assumed ${events.incomeLossDuration} month income loss.`, 14, yPos); } else { doc.setTextColor(dangerColor); doc.text(`This is LESS than the assumed ${events.incomeLossDuration} month income loss.`, 14, yPos); } } else { doc.setTextColor(accentColor); doc.text(`Temporary income covers essential expenses.`, 14, yPos); } doc.setTextColor(textColor); yPos += 8; // Unexpected Expense Scenario Results if (yPos > 250) { doc.addPage(); yPos = 20; } doc.setFontSize(14); doc.setTextColor(primaryColor); doc.text("Unexpected Large Expense Scenario Analysis", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(textColor); const savingsAfterLargeExpense = snapshot.liquidSavings - events.unexpectedExpenseAmount; doc.text(`Savings Remaining After Expense: ${toCurrency(savingsAfterLargeExpense)}`, 14, yPos); yPos += 6; let monthsRemSavingsLastEssentials = 0; let expenseResilienceText = ""; if (savingsAfterLargeExpense <= 0) { expenseResilienceText = "Savings depleted by expense."; doc.setTextColor(dangerColor); } else if (totalEssentialMonthlyExpenses <= 0) { expenseResilienceText = "Indefinitely (no essential expenses listed)."; doc.setTextColor(accentColor); } else { monthsRemSavingsLastEssentials = savingsAfterLargeExpense / totalEssentialMonthlyExpenses; expenseResilienceText = `${monthsRemSavingsLastEssentials.toFixed(1)} months (assuming no income).`; if (monthsRemSavingsLastEssentials >= 3) doc.setTextColor(accentColor); else if (monthsRemSavingsLastEssentials > 0) doc.setTextColor(warningColor); else doc.setTextColor(dangerColor); } doc.text(`Remaining savings cover essentials for approx.: ${expenseResilienceText}`, 14, yPos); doc.setTextColor(textColor); doc.save('personal_savings_stress_test.pdf'); } // Initialize document.addEventListener('DOMContentLoaded', () => { psst_showTab(0); psst_renderEssentialExpensesList(); psst_updateTotalEssentialExpensesDisplay(); // Demo Data psst_essentialExpenses.push({id:'ess_exp_001', name:'Rent/Mortgage', amount:1200}); psst_essentialExpenses.push({id:'ess_exp_002', name:'Utilities (Avg)', amount:200}); psst_essentialExpenses.push({id:'ess_exp_003', name:'Groceries (Basic)', amount:400}); psst_essentialExpenses.push({id:'ess_exp_004', name:'Essential Transport', amount:100}); psst_renderEssentialExpensesList(); psst_updateTotalEssentialExpensesDisplay(); });
Scroll to Top