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 82d6bfb5d7
commit e3b779ea91

View File

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