Home Loan Refinancing Budget Planner
Step 1: Enter Current Loan & Refinance Offer
Step 2: Refinance Cost & Savings Analysis
Complete loan details in Tab 1 and click "Prepare Refinance Analysis" to see results here.
Step 3: Budget Impact, Amortization & Download
Change in Monthly Cash Flow (P&I): $${Math.abs(cashFlowChange).toFixed(2)} ${cashFlowChange >= 0 ? 'Improved' : 'Reduced'}
Current Front-End DTI (Housing): ${currentFrontDTI.toFixed(1)}% | New Front-End DTI: ${newFrontDTI.toFixed(1)}%
Current Back-End DTI (Total): ${currentBackDTI.toFixed(1)}% | New Back-End DTI: ${newBackDTI.toFixed(1)}%
Note: DTI calculation here uses P&I for housing. For full PITI, add taxes & insurance to housing costs.`;
}
function displayAmortizationTable(schedule, containerId, title) {
const container = document.getElementById(containerId);
if (!schedule || schedule.length === 0) {
container.innerHTML = `
${title}: No data to display.
`;
return;
}
// Show first 5, ellipsis if more than 10, then last 5
let displaySchedule = schedule;
if (schedule.length > 12) {
displaySchedule = [
...schedule.slice(0, 5),
{ month: '...', beginningBalance: '...', payment: '...', interest: '...', principal: '...', lumpSum: '...', endingBalance: '...' },
...schedule.slice(-5)
];
}
let tableHTML = `
| Mth | Start Bal. | Payment | Interest | Principal | Lump Sum | End Bal. |
`;
displaySchedule.forEach(row => {
tableHTML += `
| ${row.month} |
${typeof row.beginningBalance === 'number' ? row.beginningBalance.toFixed(2) : row.beginningBalance} |
${typeof row.payment === 'number' ? row.payment.toFixed(2) : row.payment} |
${typeof row.interest === 'number' ? row.interest.toFixed(2) : row.interest} |
${typeof row.principal === 'number' ? row.principal.toFixed(2) : row.principal} |
${typeof row.lumpSum === 'number' ? row.lumpSum.toFixed(2) : row.lumpSum} |
${typeof row.endingBalance === 'number' ? row.endingBalance.toFixed(2) : row.endingBalance} |
`;
});
tableHTML += `
Showing summarized schedule. Full schedule in PDF.`;
container.innerHTML = `
${title}
${tableHTML}`;
}
function downloadRefiPdf() {
if (!refiReportData.comparison || refiReportData.comparison.monthlyPaymentDifference === undefined) {
alert("Please prepare and view the refinance analysis first."); return;
}
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('PDF generation library (jsPDF) is not loaded.'); return;
}
const jsPDFConstructor = window.jspdf.jsPDF;
const doc = new jsPDFConstructor();
if (typeof doc.autoTable !== 'function') {
alert('jsPDF AutoTable plugin not loaded.'); return;
}
const { inputs, currentLoanCalcs, newLoanCalcs, comparison, budgetImpact, amortization } = refiReportData;
const primaryColor = '#007bff', textColor = '#212529', tableHeaderColor = '#e9ecef';
let yPos = 22;
const pageHeight = doc.internal.pageSize.height; const margin = 20;
function checkYPdf(increment = 10) { if (yPos + increment > pageHeight - margin) { doc.addPage(); yPos = margin; } }
doc.setFontSize(18); doc.setTextColor(primaryColor);
doc.text("Home Loan Refinance Analysis", 14, yPos); yPos += 8;
doc.setFontSize(10); doc.setTextColor(textColor);
doc.text(`Report Date: ${new Date().toLocaleDateString()} | Analysis As Of: ${inputs.refiAsOfMonth}/${inputs.refiAsOfYear}`, 14, yPos); yPos += 10;
// Current Loan Summary
checkYPdf(30);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Current Loan Details", 14, yPos); yPos += 6;
let currentLoanBody = [
['Original Amount:', `$${inputs.currentOrigLoanAmount.toFixed(2)}`],
['Original Rate:', `${inputs.currentOrigInterestRate.toFixed(3)}%`],
['Original Term:', `${inputs.currentOrigLoanTerm} years`],
['Start Date:', `${inputs.currentLoanStartMonth}/${inputs.currentLoanStartYear}`],
[{content:'Status as of ' + inputs.refiAsOfMonth+'/'+inputs.refiAsOfYear, styles:{fontStyle:'bold', halign:'left', cellWidth:'wrap'}}],
[' Remaining Balance:', `$${currentLoanCalcs.remainingBalance.toFixed(2)}`],
[' Monthly P&I:', `$${currentLoanCalcs.monthlyPI.toFixed(2)}`],
[' Remaining Term:', `${Math.floor(currentLoanCalcs.remainingTermMonths/12)}y ${currentLoanCalcs.remainingTermMonths%12}m`]
];
doc.autoTable({startY: yPos, body: currentLoanBody, theme:'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}});
yPos = doc.lastAutoTable.finalY + 7;
// Refinance Offer Summary
checkYPdf(30);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Refinance Offer Details", 14, yPos); yPos += 6;
let refiOfferBody = [
['New Loan Amount:', `$${inputs.effectiveNewLoanAmount.toFixed(2)}`],
['New Rate:', `${inputs.refiNewInterestRate.toFixed(3)}%`],
['New Term:', `${inputs.refiNewLoanTerm} years`],
['Est. Closing Costs:', `$${inputs.refiClosingCosts.toFixed(2)}`],
['New Monthly P&I:', `$${newLoanCalcs.monthlyPI.toFixed(2)}`]
];
doc.autoTable({startY: yPos, body: refiOfferBody, theme:'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}});
yPos = doc.lastAutoTable.finalY + 10;
// Comparative Analysis
checkYPdf(40);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Refinance Comparison Summary", 14, yPos); yPos +=6;
const comparisonBody = [
['Monthly P&I Payment Difference:', `$${comparison.monthlyPaymentDifference.toFixed(2)} ${comparison.monthlyPaymentDifference >= 0 ? '(Savings)' : '(Increased Cost)'}`],
['Total Future Interest (Current Loan):', `$${comparison.totalRemainingInterestCurrent.toFixed(2)}`],
['Total Future Interest (New Loan):', `$${comparison.totalInterestNew.toFixed(2)}`],
['Interest Savings (New vs Current):', `$${(comparison.totalRemainingInterestCurrent - comparison.totalInterestNew).toFixed(2)}`],
['Net Lifetime Financial Impact:', `$${Math.abs(comparison.lifetimeSavings).toFixed(2)} ${comparison.lifetimeSavings >= 0 ? 'Total Savings' : 'Total Cost'}`],
['Break-Even Point for Closing Costs:', `${isFinite(comparison.breakEvenMonths) ? comparison.breakEvenMonths + ' months' : 'N/A (No monthly savings)'}`]
];
doc.autoTable({startY: yPos, body: comparisonBody, theme:'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}});
yPos = doc.lastAutoTable.finalY + 7;
// Budget Impact
if(budgetImpact && budgetImpact.gmi > 0){
checkYPdf(30);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Budget Impact Analysis", 14, yPos); yPos +=6;
const budgetBody = [
['Gross Monthly Income:', `$${budgetImpact.gmi.toFixed(2)}`],
['Other Monthly Debts:', `$${budgetImpact.otherDebts.toFixed(2)}`],
['Change in Monthly Cash Flow (P&I):', `$${Math.abs(budgetImpact.cashFlowChange).toFixed(2)} ${budgetImpact.cashFlowChange >=0 ? 'Improved' : 'Reduced'}`],
['Current Front-End DTI:', `${budgetImpact.currentFrontDTI.toFixed(1)}% -> New: ${budgetImpact.newFrontDTI.toFixed(1)}%`],
['Current Back-End DTI:', `${budgetImpact.currentBackDTI.toFixed(1)}% -> New: ${budgetImpact.newBackDTI.toFixed(1)}%`]
];
doc.autoTable({startY: yPos, body: budgetBody, theme:'plain', styles:{fontSize:9, cellPadding:1.2}, columnStyles:{0:{fontStyle:'bold'}}});
yPos = doc.lastAutoTable.finalY + 10;
}
// Amortization Schedules
function addAmortToPdf(title, scheduleData) {
if (!scheduleData || scheduleData.length === 0) return;
checkYPdf(20);
doc.setFontSize(11); doc.setTextColor(primaryColor); doc.text(title, 14, yPos); yPos += 6;
const head = [['Month', 'Start Bal.', 'Payment', 'Interest', 'Principal', 'Lump Sum', 'End Bal.']];
const body = scheduleData.map(r => [
r.month, r.beginningBalance.toFixed(2), r.payment.toFixed(2), r.interest.toFixed(2),
r.principal.toFixed(2), r.lumpSum.toFixed(2), r.endingBalance.toFixed(2)
]);
doc.autoTable({
startY: yPos, head: head, body: body, theme: 'grid',
headStyles: {fillColor: tableHeaderColor, textColor:textColor, fontStyle:'bold', fontSize:8},
styles:{fontSize:7, cellPadding:1, halign:'right'},
columnStyles:{0:{halign:'center'}},
pageBreak: 'auto' // Let autotable handle page breaks for long tables
});
yPos = doc.lastAutoTable.finalY + 10;
}
addAmortToPdf("Amortization: Current Loan (Remaining Term)", amortization.current);
addAmortToPdf("Amortization: New Refinanced Loan", amortization.new);
doc.save("Home_Loan_Refinance_Analysis.pdf");
}
updateRefiNavButtons(); // Initial call