Skip to main content
Glama

Excel Finance MCP

lease-management.ts20.1 kB
import { CellValue } from '../excel/excel-manager.js'; export interface Lease { leaseId: string; propertyId: string; unitId: string; tenantId: string; tenantName: string; leaseStartDate: string; leaseEndDate: string; currentRent: number; securityDeposit: number; leaseType: 'residential' | 'commercial' | 'retail' | 'office' | 'industrial'; rentEscalations: RentEscalation[]; renewalOptions: RenewalOption[]; status: 'active' | 'expired' | 'terminated' | 'pending' | 'holdover'; paymentTerms: string; specialClauses: string[]; } export interface RentEscalation { effectiveDate: string; escalationType: 'fixed_amount' | 'percentage' | 'cpi_adjustment' | 'market_rate'; escalationValue: number; baseAmount?: number; } export interface RenewalOption { optionPeriod: number; // months renewalRate: number; noticeDaysRequired: number; marketRateAdjustment?: boolean; } export interface LeaseAnalysis { leaseId: string; currentMetrics: { monthsRemaining: number; daysToExpiration: number; renewalProbability: number; marketRentComparison: number; escalationsDue: RentEscalation[]; }; financialProjection: { totalRemainingRent: number; futureEscalations: number[]; renewalValue: number; vacancyRisk: number; leaseValue: number; // NPV of remaining lease payments }; riskFactors: { tenantCreditRisk: 'low' | 'medium' | 'high'; paymentHistory: 'excellent' | 'good' | 'concerning' | 'poor'; marketRentRisk: 'below_market' | 'at_market' | 'above_market'; renewalLikelihood: 'high' | 'medium' | 'low'; }; } export interface ExpirationAnalysis { propertyId: string; timeframe: string; expiringLeases: { totalLeases: number; totalSquareFeet: number; totalRent: number; averageRent: number; tenantMix: { [tenantType: string]: number }; }; renewalProjections: { expectedRenewals: number; projectedVacancy: number; rentRollRisk: number; revenueAtRisk: number; }; marketAnalysis: { currentMarketRent: number; rentGrowthOpportunity: number; competitivePosition: string; leaseUpTime: number; // months to re-lease vacant units }; } export class LeaseLifecycleManager { private calculateLeaseMetrics(lease: Lease, analysisDate: Date = new Date()): LeaseAnalysis['currentMetrics'] { // const startDate = new Date(lease.leaseStartDate); // Available for future use const endDate = new Date(lease.leaseEndDate); const daysToExpiration = Math.ceil((endDate.getTime() - analysisDate.getTime()) / (1000 * 60 * 60 * 24)); const monthsRemaining = Math.ceil(daysToExpiration / 30.44); // Average days per month // Calculate renewal probability based on lease terms and market conditions let renewalProbability = 0.65; // Base probability // Adjust based on rent vs market if (lease.renewalOptions.length > 0) renewalProbability += 0.15; if (monthsRemaining > 12) renewalProbability += 0.10; if (monthsRemaining < 3) renewalProbability -= 0.20; renewalProbability = Math.max(0.1, Math.min(0.95, renewalProbability)); // Find upcoming escalations const escalationsDue = lease.rentEscalations.filter(escalation => { const escalationDate = new Date(escalation.effectiveDate); const daysDiff = (escalationDate.getTime() - analysisDate.getTime()) / (1000 * 60 * 60 * 24); return daysDiff > 0 && daysDiff <= 90; // Next 90 days }); return { monthsRemaining, daysToExpiration, renewalProbability, marketRentComparison: 0, // Would integrate with market data escalationsDue }; } private calculateFinancialProjection(lease: Lease, metrics: LeaseAnalysis['currentMetrics']): LeaseAnalysis['financialProjection'] { const monthsRemaining = metrics.monthsRemaining; let currentRent = lease.currentRent; const futureEscalations: number[] = []; let totalRemainingRent = 0; // Calculate month-by-month rent with escalations for (let month = 1; month <= monthsRemaining; month++) { const monthDate = new Date(); monthDate.setMonth(monthDate.getMonth() + month); // Check for escalations this month const escalation = lease.rentEscalations.find(esc => { const escDate = new Date(esc.effectiveDate); return escDate.getFullYear() === monthDate.getFullYear() && escDate.getMonth() === monthDate.getMonth(); }); if (escalation) { switch (escalation.escalationType) { case 'fixed_amount': currentRent += escalation.escalationValue; break; case 'percentage': currentRent *= (1 + escalation.escalationValue / 100); break; case 'cpi_adjustment': // Simplified CPI adjustment currentRent *= (1 + Math.max(0.02, escalation.escalationValue / 100)); break; } futureEscalations.push(currentRent - lease.currentRent); } totalRemainingRent += currentRent; } // Calculate renewal value if applicable let renewalValue = 0; if (lease.renewalOptions.length > 0 && metrics.renewalProbability > 0.5) { const renewalOption = lease.renewalOptions[0]; renewalValue = renewalOption.renewalRate * renewalOption.optionPeriod * metrics.renewalProbability; } // Calculate NPV of remaining lease payments (simplified) const discountRate = 0.08; // 8% discount rate const leaseValue = totalRemainingRent / Math.pow(1 + discountRate, monthsRemaining / 12); return { totalRemainingRent, futureEscalations, renewalValue, vacancyRisk: (1 - metrics.renewalProbability) * totalRemainingRent, leaseValue }; } private assessRiskFactors(lease: Lease, metrics: LeaseAnalysis['currentMetrics']): LeaseAnalysis['riskFactors'] { // Simplified risk assessment - in production would integrate with credit data let tenantCreditRisk: 'low' | 'medium' | 'high' = 'medium'; let paymentHistory: 'excellent' | 'good' | 'concerning' | 'poor' = 'good'; let marketRentRisk: 'below_market' | 'at_market' | 'above_market' = 'at_market'; let renewalLikelihood: 'high' | 'medium' | 'low'; // Assess renewal likelihood if (metrics.renewalProbability >= 0.75) renewalLikelihood = 'high'; else if (metrics.renewalProbability >= 0.45) renewalLikelihood = 'medium'; else renewalLikelihood = 'low'; // Commercial leases typically have better credit assessment if (lease.leaseType === 'commercial' || lease.leaseType === 'office') { tenantCreditRisk = 'low'; paymentHistory = 'excellent'; } return { tenantCreditRisk, paymentHistory, marketRentRisk, renewalLikelihood }; } analyzeLeasePortfolio(leases: Lease[], analysisDate: Date = new Date()): LeaseAnalysis[] { return leases.map(lease => { const currentMetrics = this.calculateLeaseMetrics(lease, analysisDate); const financialProjection = this.calculateFinancialProjection(lease, currentMetrics); const riskFactors = this.assessRiskFactors(lease, currentMetrics); return { leaseId: lease.leaseId, currentMetrics, financialProjection, riskFactors }; }); } generateExpirationAnalysis( leases: Lease[], timeframe: 'next_30_days' | 'next_90_days' | 'next_6_months' | 'next_12_months' | 'next_24_months', analysisDate: Date = new Date() ): ExpirationAnalysis { // Calculate timeframe end date const endDate = new Date(analysisDate); switch (timeframe) { case 'next_30_days': endDate.setDate(endDate.getDate() + 30); break; case 'next_90_days': endDate.setDate(endDate.getDate() + 90); break; case 'next_6_months': endDate.setMonth(endDate.getMonth() + 6); break; case 'next_12_months': endDate.setFullYear(endDate.getFullYear() + 1); break; case 'next_24_months': endDate.setFullYear(endDate.getFullYear() + 2); break; } // Filter leases expiring in timeframe const expiringLeases = leases.filter(lease => { const leaseEndDate = new Date(lease.leaseEndDate); return leaseEndDate >= analysisDate && leaseEndDate <= endDate; }); // Calculate metrics const totalLeases = expiringLeases.length; const totalRent = expiringLeases.reduce((sum, lease) => sum + lease.currentRent, 0); const totalSquareFeet = expiringLeases.length * 1000; // Simplified - would get actual sq ft const averageRent = totalLeases > 0 ? totalRent / totalLeases : 0; // Tenant mix analysis const tenantMix: { [tenantType: string]: number } = {}; expiringLeases.forEach(lease => { tenantMix[lease.leaseType] = (tenantMix[lease.leaseType] || 0) + 1; }); // Renewal projections const leaseAnalyses = this.analyzeLeasePortfolio(expiringLeases, analysisDate); const averageRenewalProbability = leaseAnalyses.reduce((sum, analysis) => sum + analysis.currentMetrics.renewalProbability, 0) / leaseAnalyses.length; const expectedRenewals = Math.round(totalLeases * averageRenewalProbability); const projectedVacancy = totalLeases - expectedRenewals; const rentRollRisk = totalRent * (1 - averageRenewalProbability); const revenueAtRisk = rentRollRisk * 12; // Annualized return { propertyId: 'PORTFOLIO', // Would be specific property or portfolio-wide timeframe, expiringLeases: { totalLeases, totalSquareFeet, totalRent, averageRent, tenantMix }, renewalProjections: { expectedRenewals, projectedVacancy, rentRollRisk, revenueAtRisk }, marketAnalysis: { currentMarketRent: averageRent * 1.05, // Simplified market rent rentGrowthOpportunity: averageRent * 0.05, competitivePosition: 'Competitive', leaseUpTime: 4 // Months } }; } generateLeaseExpirationWorksheet( analyses: ExpirationAnalysis[], detailedLeases?: LeaseAnalysis[] ): Array<Array<CellValue | string | number>> { const worksheet: Array<Array<CellValue | string | number>> = []; // Header worksheet.push(['LEASE EXPIRATION ANALYSIS REPORT', '', '', '', '', '', '', '', '', '']); worksheet.push([`Analysis Date: ${new Date().toLocaleDateString()}`, '', '', '', '', '', '', '', '', '']); worksheet.push(['Property Management Intelligence Platform', '', '', '', '', '', '', '', '', '']); worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Executive Summary worksheet.push(['📊 EXECUTIVE SUMMARY', '', '', '', '', '', '', '', '', '']); const totalExpiringLeases = analyses.reduce((sum, analysis) => sum + analysis.expiringLeases.totalLeases, 0); const totalRentAtRisk = analyses.reduce((sum, analysis) => sum + analysis.expiringLeases.totalRent, 0); const totalRevenueAtRisk = analyses.reduce((sum, analysis) => sum + analysis.renewalProjections.revenueAtRisk, 0); worksheet.push(['Total Expiring Leases (Next 24 Months)', totalExpiringLeases, 'leases', 'Lease renewal management required']); worksheet.push(['Monthly Rent at Risk', (totalRentAtRisk / 1000).toFixed(0) + 'K', 'monthly', 'Potential revenue loss if not renewed']); worksheet.push(['Annual Revenue at Risk', (totalRevenueAtRisk / 1000000).toFixed(1) + 'M', 'annually', 'Total revenue impact from non-renewals']); worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Expiration Timeline Analysis worksheet.push(['🕐 LEASE EXPIRATION TIMELINE', '', '', '', '', '', '', '', '', '']); worksheet.push(['Timeframe', 'Expiring Leases', 'Monthly Rent', 'Sq Ft', 'Renewal Rate', 'Revenue Risk', 'Action Required', '', '', '']); for (const analysis of analyses) { const renewalRate = analysis.renewalProjections.expectedRenewals / analysis.expiringLeases.totalLeases; const actionRequired = renewalRate < 0.7 ? 'HIGH PRIORITY' : renewalRate < 0.85 ? 'MODERATE' : 'MONITOR'; worksheet.push([ analysis.timeframe.replace('_', ' ').toUpperCase(), analysis.expiringLeases.totalLeases, (analysis.expiringLeases.totalRent / 1000).toFixed(0) + 'K', (analysis.expiringLeases.totalSquareFeet / 1000).toFixed(0) + 'K', (renewalRate * 100).toFixed(1) + '%', (analysis.renewalProjections.revenueAtRisk / 1000000).toFixed(2) + 'M', actionRequired, '', '', '' ]); } worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Tenant Mix Analysis worksheet.push(['🏢 TENANT MIX ANALYSIS', '', '', '', '', '', '', '', '', '']); worksheet.push(['Property Type', 'Expiring Leases', 'Percentage', 'Renewal Risk', 'Market Conditions', '', '', '', '', '']); const allTenantTypes = new Set<string>(); analyses.forEach(analysis => { Object.keys(analysis.expiringLeases.tenantMix).forEach(type => allTenantTypes.add(type)); }); for (const tenantType of allTenantTypes) { const typeLeases = analyses.reduce((sum, analysis) => sum + (analysis.expiringLeases.tenantMix[tenantType] || 0), 0); const percentage = (typeLeases / totalExpiringLeases * 100).toFixed(1); let renewalRisk = 'MODERATE'; let marketConditions = 'Stable'; if (tenantType === 'retail') { renewalRisk = 'HIGH'; marketConditions = 'Challenging'; } else if (tenantType === 'office') { renewalRisk = 'MODERATE'; marketConditions = 'Mixed'; } else if (tenantType === 'industrial') { renewalRisk = 'LOW'; marketConditions = 'Strong'; } worksheet.push([ tenantType.toUpperCase(), typeLeases, percentage + '%', renewalRisk, marketConditions, '', '', '', '', '' ]); } worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Detailed Lease Analysis (if provided) if (detailedLeases && detailedLeases.length > 0) { worksheet.push(['📋 DETAILED LEASE ANALYSIS', '', '', '', '', '', '', '', '', '']); worksheet.push(['Lease ID', 'Tenant', 'Expiration', 'Monthly Rent', 'Renewal Prob', 'Risk Level', 'Action', 'Value at Risk', '', '']); // Sort by renewal probability (lowest first) const sortedLeases = detailedLeases.sort((a, b) => a.currentMetrics.renewalProbability - b.currentMetrics.renewalProbability); for (const lease of sortedLeases.slice(0, 20)) { // Top 20 highest risk const riskLevel = lease.riskFactors.renewalLikelihood === 'low' ? 'HIGH RISK' : lease.riskFactors.renewalLikelihood === 'medium' ? 'MODERATE' : 'LOW RISK'; const action = lease.currentMetrics.daysToExpiration < 90 ? 'URGENT - RENEW NOW' : lease.currentMetrics.daysToExpiration < 180 ? 'BEGIN RENEWAL PROCESS' : 'MONITOR AND PLAN'; worksheet.push([ lease.leaseId, 'Tenant Name', // Would be actual tenant name new Date(Date.now() + lease.currentMetrics.daysToExpiration * 24 * 60 * 60 * 1000).toLocaleDateString(), (lease.financialProjection.totalRemainingRent / lease.currentMetrics.monthsRemaining / 1000).toFixed(0) + 'K', (lease.currentMetrics.renewalProbability * 100).toFixed(0) + '%', riskLevel, action, (lease.financialProjection.vacancyRisk / 1000).toFixed(0) + 'K', '', '' ]); } } worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Renewal Strategy Recommendations worksheet.push(['💡 RENEWAL STRATEGY RECOMMENDATIONS', '', '', '', '', '', '', '', '', '']); worksheet.push(['Priority', 'Strategy', 'Target Leases', 'Expected Impact', 'Timeline', 'Resources Required', '', '', '', '']); worksheet.push([ 'HIGH', 'Immediate Renewal Outreach', 'Expiring < 90 days', 'Prevent immediate vacancy', 'Next 30 days', 'Leasing team focus', '', '', '', '' ]); worksheet.push([ 'MEDIUM', 'Market Rate Analysis & Negotiation', 'Below-market rents', '5-15% rent increase', '60-90 days', 'Market research, negotiation', '', '', '', '' ]); worksheet.push([ 'LOW', 'Tenant Satisfaction Program', 'High renewal probability', 'Maintain occupancy', 'Ongoing', 'Property management', '', '', '', '' ]); worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Financial Impact Projections worksheet.push(['💰 FINANCIAL IMPACT PROJECTIONS', '', '', '', '', '', '', '', '', '']); worksheet.push(['Scenario', 'Renewal Rate', 'Revenue Impact', 'Leasing Costs', 'Net Impact', 'Probability', '', '', '', '']); const scenarios = [ { name: 'Pessimistic', renewalRate: 0.60, probability: 0.15 }, { name: 'Base Case', renewalRate: 0.75, probability: 0.60 }, { name: 'Optimistic', renewalRate: 0.90, probability: 0.25 } ]; for (const scenario of scenarios) { const revenueImpact = totalRentAtRisk * 12 * (1 - scenario.renewalRate); const leasingCosts = totalExpiringLeases * (1 - scenario.renewalRate) * 5000; // $5K per re-lease const netImpact = revenueImpact + leasingCosts; worksheet.push([ scenario.name, (scenario.renewalRate * 100).toFixed(0) + '%', '($' + (revenueImpact / 1000000).toFixed(1) + 'M)', '($' + (leasingCosts / 1000).toFixed(0) + 'K)', '($' + (netImpact / 1000000).toFixed(1) + 'M)', (scenario.probability * 100).toFixed(0) + '%', '', '', '', '' ]); } worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Action Plan worksheet.push(['✅ 90-DAY ACTION PLAN', '', '', '', '', '', '', '', '', '']); worksheet.push(['Week', 'Action Item', 'Responsible', 'Target', 'Success Metric', '', '', '', '', '']); const actionPlan = [ ['1-2', 'Complete lease expiration audit', 'Property Manager', 'All expiring leases identified', '100% lease data accuracy'], ['3-4', 'Market rent analysis and pricing strategy', 'Leasing Team', 'Market-competitive pricing', 'Pricing within 5% of market'], ['5-6', 'Begin renewal outreach (90+ day pipeline)', 'Leasing Team', '80% contact rate', 'Tenant meetings scheduled'], ['7-8', 'Urgent renewals (60-90 day expirations)', 'Property Manager', '70% renewal rate', 'Signed lease agreements'], ['9-10', 'Marketing for non-renewals', 'Marketing Team', 'Active marketing campaigns', '50% prospect pipeline'], ['11-12', 'Final renewal push and new lease signings', 'All Teams', '75% overall renewal rate', 'Minimized vacancy'] ]; for (const [week, action, responsible, target, metric] of actionPlan) { worksheet.push([week, action, responsible, target, metric, '', '', '', '', '']); } worksheet.push(['', '', '', '', '', '', '', '', '', '']); // Professional Notes worksheet.push(['📝 ANALYSIS METHODOLOGY & ASSUMPTIONS', '', '', '', '', '', '', '', '', '']); worksheet.push(['- Renewal probabilities based on market conditions and lease terms', '', '', '', '', '', '', '', '', '']); worksheet.push(['- Revenue at risk assumes non-renewed leases remain vacant for 4 months', '', '', '', '', '', '', '', '', '']); worksheet.push(['- Market rent estimates based on comparable property analysis', '', '', '', '', '', '', '', '', '']); worksheet.push(['- Financial projections include estimated re-leasing costs and downtime', '', '', '', '', '', '', '', '', '']); worksheet.push(['- Analysis should be updated monthly as market conditions change', '', '', '', '', '', '', '', '', '']); return worksheet; } }

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/jeremycharlesgillespie/excel-mcp'

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