Fixed simulation when salary = 0, stale college profile/grad date existed

This commit is contained in:
Josh 2025-07-07 17:31:00 +00:00
parent 0635b60792
commit 81a5592e23

View File

@ -126,6 +126,8 @@ const {
// College ---------------------------------------------------- // College ----------------------------------------------------
inCollege = false, inCollege = false,
collegeEnrollmentStatus = 'none',
programType, programType,
hoursCompleted: _hoursCompleted = 0, hoursCompleted: _hoursCompleted = 0,
creditHoursPerYear: _creditHoursPerYear, creditHoursPerYear: _creditHoursPerYear,
@ -172,6 +174,9 @@ const extraPayment = num(_extraPayment);
const studentLoanAmount = num(_studentLoanAmount); const studentLoanAmount = num(_studentLoanAmount);
const interestRate = num(_interestRate); const interestRate = num(_interestRate);
const loanTerm = num(_loanTerm); const loanTerm = num(_loanTerm);
const isProgrammeActive =
['enrolled', 'graduated'].includes(collegeEnrollmentStatus);
const hoursCompleted = num(_hoursCompleted); const hoursCompleted = num(_hoursCompleted);
const creditHoursPerYear = num(_creditHoursPerYear); const creditHoursPerYear = num(_creditHoursPerYear);
@ -360,6 +365,11 @@ function simulateDrawdown(opts){
for (let monthIndex = 0; monthIndex < maxMonths; monthIndex++) { for (let monthIndex = 0; monthIndex < maxMonths; monthIndex++) {
const currentSimDate = scenarioStartClamped.clone().add(monthIndex, 'months'); const currentSimDate = scenarioStartClamped.clone().add(monthIndex, 'months');
const hasGraduated =
isProgrammeActive &&
gradDate &&
currentSimDate.isSameOrAfter(moment(gradDate).startOf('month'));
if (!reachedRetirement && currentSimDate.isSameOrAfter(retirementStartISO)) { if (!reachedRetirement && currentSimDate.isSameOrAfter(retirementStartISO)) {
reachedRetirement = true; reachedRetirement = true;
firstRetirementBalance = currentRetirementSavings; // capture once firstRetirementBalance = currentRetirementSavings; // capture once
@ -410,10 +420,20 @@ for (let monthIndex = 0; monthIndex < maxMonths; monthIndex++) {
currentRetirementSavings -= withdrawal; currentRetirementSavings -= withdrawal;
baseMonthlyIncome += withdrawal; baseMonthlyIncome += withdrawal;
} else if (!stillInCollege) { } else if (!stillInCollege) {
baseMonthlyIncome = (expectedSalary || currentSalary) / 12; // Use expectedSalary **only** once the user has graduated
} else { const monthlyFromJob = (
baseMonthlyIncome = (currentSalary / 12) + (additionalIncome / 12); hasGraduated && expectedSalary > 0
} ? expectedSalary // kicks in the month *after* gradDate
: currentSalary
) / 12;
baseMonthlyIncome =
monthlyFromJob + (additionalIncome / 12);
} else { // stillInCollege branch
baseMonthlyIncome =
(currentSalary / 12) + (additionalIncome / 12);
}
/************************************************ /************************************************
* 7.3 MILESTONE IMPACTS safe number handling * 7.3 MILESTONE IMPACTS safe number handling
@ -512,9 +532,13 @@ baseMonthlyIncome += salaryAdjustThisMonth; // adjust gross BEFORE tax
let leftover = netMonthlyIncome - totalMonthlyExpenses; let leftover = netMonthlyIncome - totalMonthlyExpenses;
const canSaveThisMonth = leftover > 0;
// baseline contributions // baseline contributions
const monthlyRetContrib = isRetiredThisMonth ? 0 : monthlyRetirementContribution; const monthlyRetContrib =
const monthlyEmergContrib = isRetiredThisMonth ? 0 : monthlyEmergencyContribution; canSaveThisMonth && !isRetiredThisMonth ? monthlyRetirementContribution : 0;
const monthlyEmergContrib =
canSaveThisMonth && !isRetiredThisMonth ? monthlyEmergencyContribution : 0;
const baselineContributions = monthlyRetContrib + monthlyEmergContrib; const baselineContributions = monthlyRetContrib + monthlyEmergContrib;
let effectiveRetirementContribution = 0; let effectiveRetirementContribution = 0;
let effectiveEmergencyContribution = 0; let effectiveEmergencyContribution = 0;
@ -536,7 +560,7 @@ baseMonthlyIncome += salaryAdjustThisMonth; // adjust gross BEFORE tax
} }
// Surplus => leftover // Surplus => leftover
if (leftover > 0) { if (canSaveThisMonth && leftover > 0) {
const totalPct = surplusEmergencyAllocation + surplusRetirementAllocation; const totalPct = surplusEmergencyAllocation + surplusRetirementAllocation;
const emergPortion = leftover * (surplusEmergencyAllocation / totalPct); const emergPortion = leftover * (surplusEmergencyAllocation / totalPct);
const retPortion = leftover * (surplusRetirementAllocation / totalPct); const retPortion = leftover * (surplusRetirementAllocation / totalPct);
@ -546,9 +570,9 @@ baseMonthlyIncome += salaryAdjustThisMonth; // adjust gross BEFORE tax
} }
const monthlyReturnRate = getMonthlyInterestRate(); const monthlyReturnRate = getMonthlyInterestRate();
if (monthlyReturnRate !== 0) { if (monthlyReturnRate !== 0 && leftover > 0) {
currentRetirementSavings *= (1 + monthlyReturnRate); currentRetirementSavings *= (1 + monthlyReturnRate);
} }
const netSavings = netMonthlyIncome - actualExpensesPaid; const netSavings = netMonthlyIncome - actualExpensesPaid;
// (UPDATED) add inCollege, stillInCollege, loanDeferralUntilGraduation to the result // (UPDATED) add inCollege, stillInCollege, loanDeferralUntilGraduation to the result