Skip to main content
Glama
sigaihealth

RealVest Real Estate MCP Server

dscr.js22.1 kB
export class DSCRCalculator { constructor() { this.name = 'DSCR Calculator'; this.description = 'Calculate Debt Service Coverage Ratio for investment property loans'; } getSchema() { return { type: 'object', properties: { property_income: { type: 'object', description: 'Property income details', properties: { monthly_rent: { type: 'number', description: 'Total monthly rental income', minimum: 0 }, other_monthly_income: { type: 'number', description: 'Other monthly income (parking, laundry, etc.)', minimum: 0, default: 0 }, vacancy_rate: { type: 'number', description: 'Expected vacancy rate (%)', minimum: 0, maximum: 50, default: 5 } }, required: ['monthly_rent'] }, property_expenses: { type: 'object', description: 'Monthly operating expenses', properties: { property_tax: { type: 'number', description: 'Monthly property tax', minimum: 0, default: 0 }, insurance: { type: 'number', description: 'Monthly insurance', minimum: 0, default: 0 }, hoa: { type: 'number', description: 'Monthly HOA fees', minimum: 0, default: 0 }, property_management: { type: 'number', description: 'Monthly property management', minimum: 0, default: 0 }, maintenance_reserve: { type: 'number', description: 'Monthly maintenance reserve', minimum: 0, default: 0 }, utilities: { type: 'number', description: 'Monthly utilities (if owner pays)', minimum: 0, default: 0 }, other: { type: 'number', description: 'Other monthly expenses', minimum: 0, default: 0 } } }, loan_details: { type: 'object', description: 'Loan information', properties: { loan_amount: { type: 'number', description: 'Total loan amount', minimum: 0 }, interest_rate: { type: 'number', description: 'Annual interest rate (%)', minimum: 0, maximum: 20 }, loan_term_years: { type: 'number', description: 'Loan term in years', minimum: 1, maximum: 40, default: 30 }, loan_type: { type: 'string', description: 'Type of loan', enum: ['conventional', 'dscr', 'portfolio', 'commercial'], default: 'dscr' } }, required: ['loan_amount', 'interest_rate'] }, property_details: { type: 'object', description: 'Property information', properties: { purchase_price: { type: 'number', description: 'Property purchase price', minimum: 0 }, property_type: { type: 'string', description: 'Type of property', enum: ['single_family', 'multi_family', 'condo', 'commercial'], default: 'single_family' }, units: { type: 'number', description: 'Number of rental units', minimum: 1, default: 1 } } } }, required: ['property_income', 'loan_details'] }; } calculate(params, skipStressTests = false) { const { property_income, property_expenses = {}, loan_details, property_details = {} } = params; // Calculate effective gross income const gross_monthly_income = property_income.monthly_rent + (property_income.other_monthly_income || 0); const vacancy_loss = gross_monthly_income * ((property_income.vacancy_rate || 5) / 100); const effective_gross_income = gross_monthly_income - vacancy_loss; const annual_effective_income = effective_gross_income * 12; // Calculate total operating expenses const monthly_expenses = this.calculateMonthlyExpenses(property_expenses); const annual_expenses = monthly_expenses * 12; // Calculate Net Operating Income (NOI) const monthly_noi = effective_gross_income - monthly_expenses; const annual_noi = monthly_noi * 12; // Calculate debt service const monthly_payment = this.calculateMonthlyPayment( loan_details.loan_amount, loan_details.interest_rate / 100 / 12, (loan_details.loan_term_years || 30) * 12 ); const annual_debt_service = monthly_payment * 12; // Calculate DSCR const dscr = annual_debt_service > 0 ? annual_noi / annual_debt_service : 0; // Calculate other important metrics const monthly_cash_flow = monthly_noi - monthly_payment; const annual_cash_flow = monthly_cash_flow * 12; // LTV if purchase price provided let ltv_ratio = null; let down_payment = null; if (property_details.purchase_price) { ltv_ratio = (loan_details.loan_amount / property_details.purchase_price) * 100; down_payment = property_details.purchase_price - loan_details.loan_amount; } // Loan qualification analysis const qualification = this.analyzeLoanQualification(dscr, loan_details.loan_type); // Maximum loan analysis const max_loan_analysis = this.calculateMaximumLoan( annual_noi, loan_details.interest_rate, loan_details.loan_term_years || 30, loan_details.loan_type ); // Stress test scenarios (skip if called from within stress test) const stress_tests = skipStressTests ? null : this.performStressTests( property_income, property_expenses, loan_details, dscr ); // Break-even analysis const break_even = this.calculateBreakEvenAnalysis( gross_monthly_income, monthly_expenses, monthly_payment ); // Property performance metrics const performance_metrics = this.calculatePerformanceMetrics( property_details.purchase_price, annual_noi, annual_cash_flow, down_payment ); return { income_analysis: { gross_monthly_income: parseFloat(gross_monthly_income.toFixed(2)), vacancy_loss: parseFloat(vacancy_loss.toFixed(2)), effective_monthly_income: parseFloat(effective_gross_income.toFixed(2)), annual_effective_income: parseFloat(annual_effective_income.toFixed(2)) }, expense_analysis: { monthly_expenses: parseFloat(monthly_expenses.toFixed(2)), annual_expenses: parseFloat(annual_expenses.toFixed(2)), expense_breakdown: this.formatExpenseBreakdown(property_expenses, monthly_expenses), expense_ratio: parseFloat(((monthly_expenses / effective_gross_income) * 100).toFixed(2)) }, noi_analysis: { monthly_noi: parseFloat(monthly_noi.toFixed(2)), annual_noi: parseFloat(annual_noi.toFixed(2)), noi_margin: parseFloat(((annual_noi / annual_effective_income) * 100).toFixed(2)) }, debt_service: { monthly_payment: parseFloat(monthly_payment.toFixed(2)), annual_debt_service: parseFloat(annual_debt_service.toFixed(2)), principal_and_interest: this.breakdownPayment( loan_details.loan_amount, loan_details.interest_rate / 100 / 12, monthly_payment ) }, dscr_analysis: { dscr_ratio: parseFloat(dscr.toFixed(3)), dscr_interpretation: this.interpretDSCR(dscr), monthly_cash_flow: parseFloat(monthly_cash_flow.toFixed(2)), annual_cash_flow: parseFloat(annual_cash_flow.toFixed(2)), cash_flow_margin: monthly_noi > 0 ? parseFloat(((monthly_cash_flow / monthly_noi) * 100).toFixed(2)) : 0 }, loan_metrics: { loan_amount: loan_details.loan_amount, ltv_ratio: ltv_ratio ? parseFloat(ltv_ratio.toFixed(2)) : null, loan_constant: parseFloat(((annual_debt_service / loan_details.loan_amount) * 100).toFixed(3)), debt_yield: parseFloat(((annual_noi / loan_details.loan_amount) * 100).toFixed(2)) }, qualification_analysis: qualification, maximum_loan_analysis: max_loan_analysis, stress_test_results: stress_tests, break_even_analysis: break_even, performance_metrics: performance_metrics, recommendations: this.generateRecommendations( dscr, monthly_cash_flow, qualification, stress_tests ) }; } calculateMonthlyExpenses(expenses) { return Object.values(expenses).reduce((sum, expense) => sum + (expense || 0), 0); } calculateMonthlyPayment(principal, monthlyRate, numPayments) { if (monthlyRate === 0) { return principal / numPayments; } return principal * (monthlyRate * Math.pow(1 + monthlyRate, numPayments)) / (Math.pow(1 + monthlyRate, numPayments) - 1); } formatExpenseBreakdown(expenses, totalMonthly) { const breakdown = []; const expenseNames = { property_tax: 'Property Tax', insurance: 'Insurance', hoa: 'HOA Fees', property_management: 'Property Management', maintenance_reserve: 'Maintenance Reserve', utilities: 'Utilities', other: 'Other Expenses' }; Object.entries(expenses).forEach(([key, value]) => { if (value > 0) { breakdown.push({ category: expenseNames[key] || key, monthly_amount: value, annual_amount: value * 12, percentage: totalMonthly > 0 ? parseFloat(((value / totalMonthly) * 100).toFixed(1)) : 0 }); } }); return breakdown.sort((a, b) => b.monthly_amount - a.monthly_amount); } breakdownPayment(loanAmount, monthlyRate, monthlyPayment) { const firstMonthInterest = loanAmount * monthlyRate; const firstMonthPrincipal = monthlyPayment - firstMonthInterest; return { first_month_principal: parseFloat(firstMonthPrincipal.toFixed(2)), first_month_interest: parseFloat(firstMonthInterest.toFixed(2)), principal_percentage: parseFloat(((firstMonthPrincipal / monthlyPayment) * 100).toFixed(1)), interest_percentage: parseFloat(((firstMonthInterest / monthlyPayment) * 100).toFixed(1)) }; } interpretDSCR(dscr) { if (dscr >= 1.5) { return "Excellent - Strong coverage with significant cushion"; } else if (dscr >= 1.25) { return "Good - Comfortable coverage for most lenders"; } else if (dscr >= 1.2) { return "Acceptable - Meets minimum for many DSCR loans"; } else if (dscr >= 1.0) { return "Marginal - Property breaks even on debt service"; } else { return "Poor - Insufficient income to cover debt service"; } } analyzeLoanQualification(dscr, loanType) { const requirements = { conventional: { min_dscr: 1.25, typical_dscr: 1.35 }, dscr: { min_dscr: 1.0, typical_dscr: 1.2 }, portfolio: { min_dscr: 1.2, typical_dscr: 1.25 }, commercial: { min_dscr: 1.25, typical_dscr: 1.35 } }; const req = requirements[loanType] || requirements.dscr; const qualifies = dscr >= req.min_dscr; const strong_candidate = dscr >= req.typical_dscr; let qualification_status; let explanation; if (dscr >= req.typical_dscr) { qualification_status = "Strong Candidate"; explanation = `DSCR of ${dscr.toFixed(3)} exceeds typical requirement of ${req.typical_dscr}`; } else if (dscr >= req.min_dscr) { qualification_status = "Likely Qualifies"; explanation = `DSCR of ${dscr.toFixed(3)} meets minimum requirement of ${req.min_dscr}`; } else if (dscr >= req.min_dscr * 0.95) { qualification_status = "Borderline"; explanation = `DSCR of ${dscr.toFixed(3)} is slightly below minimum of ${req.min_dscr}`; } else { qualification_status = "Unlikely to Qualify"; explanation = `DSCR of ${dscr.toFixed(3)} is well below minimum of ${req.min_dscr}`; } return { loan_type: loanType, qualifies: qualifies, qualification_status: qualification_status, explanation: explanation, minimum_dscr_required: req.min_dscr, typical_dscr_required: req.typical_dscr, dscr_surplus: parseFloat((dscr - req.min_dscr).toFixed(3)), improvement_needed: !qualifies ? parseFloat((req.min_dscr - dscr).toFixed(3)) : 0 }; } calculateMaximumLoan(annualNOI, interestRate, termYears, loanType) { const requirements = { conventional: 1.25, dscr: 1.0, portfolio: 1.2, commercial: 1.25 }; const minDSCR = requirements[loanType] || 1.2; const maxAnnualDebtService = annualNOI / minDSCR; const monthlyPayment = maxAnnualDebtService / 12; // Calculate maximum loan amount const monthlyRate = interestRate / 100 / 12; const numPayments = termYears * 12; let maxLoanAmount; if (monthlyRate === 0) { maxLoanAmount = monthlyPayment * numPayments; } else { maxLoanAmount = monthlyPayment * (Math.pow(1 + monthlyRate, numPayments) - 1) / (monthlyRate * Math.pow(1 + monthlyRate, numPayments)); } return { maximum_loan_amount: parseFloat(maxLoanAmount.toFixed(2)), required_dscr: minDSCR, max_monthly_payment: parseFloat(monthlyPayment.toFixed(2)), max_annual_debt_service: parseFloat(maxAnnualDebtService.toFixed(2)), assumptions: { interest_rate: interestRate, term_years: termYears, loan_type: loanType } }; } performStressTests(income, expenses, loanDetails, baseDSCR) { const scenarios = []; // Vacancy stress tests const vacancyRates = [5, 10, 15, 20, 25]; vacancyRates.forEach(rate => { const stressedIncome = { ...income, vacancy_rate: rate }; const result = this.calculate({ property_income: stressedIncome, property_expenses: expenses, loan_details: loanDetails }, true); // Skip nested stress tests scenarios.push({ scenario_type: 'Vacancy Rate', scenario: `${rate}% Vacancy`, dscr: result.dscr_analysis.dscr_ratio, monthly_cash_flow: result.dscr_analysis.monthly_cash_flow, passes_minimum: result.dscr_analysis.dscr_ratio >= 1.0, impact_on_dscr: parseFloat((result.dscr_analysis.dscr_ratio - baseDSCR).toFixed(3)) }); }); // Expense increase stress tests const expenseMultipliers = [1.0, 1.1, 1.2, 1.3, 1.4]; expenseMultipliers.forEach(multiplier => { const stressedExpenses = {}; Object.entries(expenses).forEach(([key, value]) => { stressedExpenses[key] = value * multiplier; }); const result = this.calculate({ property_income: income, property_expenses: stressedExpenses, loan_details: loanDetails }, true); // Skip nested stress tests scenarios.push({ scenario_type: 'Expense Increase', scenario: `${((multiplier - 1) * 100).toFixed(0)}% Expense Increase`, dscr: result.dscr_analysis.dscr_ratio, monthly_cash_flow: result.dscr_analysis.monthly_cash_flow, passes_minimum: result.dscr_analysis.dscr_ratio >= 1.0, impact_on_dscr: parseFloat((result.dscr_analysis.dscr_ratio - baseDSCR).toFixed(3)) }); }); // Combined stress test (10% vacancy + 10% expense increase) const combinedStressedIncome = { ...income, vacancy_rate: 10 }; const combinedStressedExpenses = {}; Object.entries(expenses).forEach(([key, value]) => { combinedStressedExpenses[key] = value * 1.1; }); const combinedResult = this.calculate({ property_income: combinedStressedIncome, property_expenses: combinedStressedExpenses, loan_details: loanDetails }, true); // Skip nested stress tests scenarios.push({ scenario_type: 'Combined Stress', scenario: '10% Vacancy + 10% Expense Increase', dscr: combinedResult.dscr_analysis.dscr_ratio, monthly_cash_flow: combinedResult.dscr_analysis.monthly_cash_flow, passes_minimum: combinedResult.dscr_analysis.dscr_ratio >= 1.0, impact_on_dscr: parseFloat((combinedResult.dscr_analysis.dscr_ratio - baseDSCR).toFixed(3)) }); return { scenarios: scenarios, worst_case: scenarios.reduce((worst, current) => current.dscr < worst.dscr ? current : worst ), resilience_score: this.calculateResilienceScore(scenarios) }; } calculateResilienceScore(scenarios) { const passingScenarios = scenarios.filter(s => s.passes_minimum).length; const totalScenarios = scenarios.length; const score = (passingScenarios / totalScenarios) * 100; let rating; if (score >= 90) { rating = "Highly Resilient"; } else if (score >= 70) { rating = "Moderately Resilient"; } else if (score >= 50) { rating = "Average Resilience"; } else { rating = "Low Resilience"; } return { score: parseFloat(score.toFixed(1)), rating: rating, passing_scenarios: passingScenarios, total_scenarios: totalScenarios }; } calculateBreakEvenAnalysis(grossIncome, expenses, debtService) { const totalCosts = expenses + debtService; const breakEvenOccupancy = (totalCosts / grossIncome) * 100; const currentMargin = grossIncome - totalCosts; const marginPercent = (currentMargin / grossIncome) * 100; // Calculate how much rent can drop const maxRentDecrease = currentMargin; const maxRentDecreasePercent = (maxRentDecrease / grossIncome) * 100; // Calculate how much expenses can increase const maxExpenseIncrease = currentMargin; const maxExpenseIncreasePercent = expenses > 0 ? (maxExpenseIncrease / expenses) * 100 : 0; return { break_even_occupancy: parseFloat(breakEvenOccupancy.toFixed(1)), current_margin: parseFloat(currentMargin.toFixed(2)), margin_percentage: parseFloat(marginPercent.toFixed(1)), max_rent_decrease: { amount: parseFloat(maxRentDecrease.toFixed(2)), percentage: parseFloat(maxRentDecreasePercent.toFixed(1)) }, max_expense_increase: { amount: parseFloat(maxExpenseIncrease.toFixed(2)), percentage: parseFloat(maxExpenseIncreasePercent.toFixed(1)) } }; } calculatePerformanceMetrics(purchasePrice, annualNOI, annualCashFlow, downPayment) { if (!purchasePrice) { return null; } const capRate = (annualNOI / purchasePrice) * 100; const cashOnCash = downPayment > 0 ? (annualCashFlow / downPayment) * 100 : 0; const grossRentMultiplier = purchasePrice / (annualNOI + annualCashFlow); return { cap_rate: parseFloat(capRate.toFixed(2)), cash_on_cash_return: parseFloat(cashOnCash.toFixed(2)), gross_rent_multiplier: parseFloat(grossRentMultiplier.toFixed(2)), annual_roi: parseFloat(cashOnCash.toFixed(2)) }; } generateRecommendations(dscr, monthlyCashFlow, qualification, stressTests) { const recommendations = []; // DSCR-based recommendations if (dscr < 1.0) { recommendations.push({ type: 'Critical', category: 'DSCR', message: 'Property has negative cash flow', action: 'Increase rents, reduce expenses, or negotiate better loan terms' }); } else if (dscr < 1.2) { recommendations.push({ type: 'Warning', category: 'DSCR', message: `DSCR of ${dscr.toFixed(3)} provides minimal cushion`, action: 'Build reserves and avoid additional leverage' }); } else if (dscr > 1.5) { recommendations.push({ type: 'Positive', category: 'DSCR', message: `Strong DSCR of ${dscr.toFixed(3)}`, action: 'Consider leveraging equity for additional investments' }); } // Qualification recommendations if (!qualification.qualifies) { recommendations.push({ type: 'Action Required', category: 'Loan Qualification', message: `Need DSCR improvement of ${qualification.improvement_needed.toFixed(3)}`, action: 'Increase rents or reduce loan amount to qualify' }); } // Stress test recommendations if (stressTests && stressTests.worst_case) { const worstCase = stressTests.worst_case; if (worstCase.dscr < 1.0) { recommendations.push({ type: 'Risk Alert', category: 'Stress Testing', message: `Property fails under ${worstCase.scenario} scenario`, action: 'Maintain larger reserves or improve property resilience' }); } } // Cash flow recommendations if (monthlyCashFlow < 200) { recommendations.push({ type: 'Caution', category: 'Cash Flow', message: `Thin monthly cash flow of $${monthlyCashFlow.toFixed(0)}`, action: 'Any maintenance issue could create negative cash flow' }); } // Resilience recommendations if (stressTests && stressTests.resilience_score && stressTests.resilience_score.score < 70) { recommendations.push({ type: 'Warning', category: 'Resilience', message: `Low resilience score of ${stressTests.resilience_score.score}%`, action: 'Property vulnerable to market changes - maintain higher reserves' }); } return recommendations; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sigaihealth/realvestmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server