NOI (Annual)
$${results.noi.toLocaleString('en-US', {maximumFractionDigits: 0})}
`;
// --- FINANCIAL BREAKDOWN DISPLAY ---
breakdownDiv.innerHTML = `
Annual Financial Summary
- Gross Rental Income$${(results.grossMonthlyRent * 12).toLocaleString()}
- Other Income$${(results.otherIncome * 12).toLocaleString()}
- Gross Operating Income (GOI)$${results.goi.toLocaleString()}
- Total Operating Expenses-$${results.totalExpenses.toLocaleString()}
- Net Operating Income (NOI)$${results.noi.toLocaleString()}
- Annual Debt Service-$${(results.monthlyPmt * 12).toLocaleString()}
- Annual Cash Flow$${results.annualCashFlow.toLocaleString()}
`;
renderChart(results);
resultsArea.style.display = 'block';
}
function calculateInvestment() {
const getValue = id => parseFloat(document.getElementById(id)?.value) || 0;
// Purchase & Financing
const purchasePrice = getValue('purchasePrice');
const closingCosts = getValue('closingCosts');
const rehabCosts = getValue('rehabCosts');
const downPaymentPercent = getValue('downPayment') / 100;
const interestRate = getValue('interestRate') / 100;
const loanTerm = getValue('loanTerm');
const totalInvestment = purchasePrice + closingCosts + rehabCosts;
const downPaymentAmount = purchasePrice * downPaymentPercent;
const loanAmount = purchasePrice - downPaymentAmount;
const totalCashNeeded = downPaymentAmount + closingCosts + rehabCosts;
const monthlyRate = interestRate / 12;
const numberOfPayments = loanTerm * 12;
const monthlyPmt = loanAmount > 0 ? loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, numberOfPayments)) / (Math.pow(1 + monthlyRate, numberOfPayments) - 1) : 0;
// Income
const grossMonthlyRent = getValue('grossMonthlyRent');
const otherIncome = getValue('otherIncome');
const goi = (grossMonthlyRent + otherIncome) * 12;
// Expenses
const vacancy = goi * (getValue('vacancy') / 100);
const repairs = goi * (getValue('repairs') / 100);
const capex = goi * (getValue('capex') / 100);
const management = goi * (getValue('management') / 100);
const propertyTaxes = getValue('propertyTaxes') * 12;
const insurance = getValue('insurance') * 12;
const utilities = getValue('utilities') * 12;
const hoa = getValue('hoa') * 12;
const totalExpenses = vacancy + repairs + capex + management + propertyTaxes + insurance + utilities + hoa;
// Key Metrics
const noi = goi - totalExpenses;
const capRate = totalInvestment > 0 ? (noi / totalInvestment) * 100 : 0;
const annualCashFlow = noi - (monthlyPmt * 12);
const cocRoi = totalCashNeeded > 0 ? (annualCashFlow / totalCashNeeded) * 100 : 0;
return {
capRate, annualCashFlow, cocRoi, noi, goi, totalExpenses, monthlyPmt, grossMonthlyRent, otherIncome
};
}
function renderChart(results) {
const ctx = document.getElementById('incomeExpenseChart');
if (!ctx) return;
if (incomeExpenseChartInstance) {
incomeExpenseChartInstance.destroy();
}
incomeExpenseChartInstance = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Net Operating Income (NOI)', 'Operating Expenses', 'Debt Service'],
datasets: [{
data: [results.noi, results.totalExpenses, results.monthlyPmt * 12],
backgroundColor: ['#10B981', '#EF4444', '#6366F1'],
hoverOffset: 4
}]
},
options: {
responsive: true,
plugins: {
title: { display: true, text: 'Annual Gross Income Allocation' }
}
}
});
}
function downloadPDF() {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-output');
const pdfTitle = document.getElementById('pdf-title');
if (!pdfContent || !pdfTitle) return;
pdfTitle.style.display = 'block';
html2canvas(pdfContent, { scale: 2 }).then(canvas => {
pdfTitle.style.display = 'none';
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p',
unit: 'mm',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const imgProps = pdf.getImageProperties(imgData);
const imgWidth = pdfWidth - 20;
const imgHeight = (imgProps.height * imgWidth) / imgProps.width;
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
pdf.save('Multi-Family-Investment-Analysis.pdf');
});
}
});