UNPKG

mcp-tenant-credit-scorer

Version:

MCP server for tenant credit scoring based on S&P corporate methodology

506 lines (444 loc) 16.2 kB
export class ReportGenerator { constructor() { this.reportTemplate = null; } async generateFullReport(data) { const { companyName, scores, validation, industryClass, financials, companyInfo, confidenceLevel } = data; const report = { executiveSummary: this.generateExecutiveSummary(data), detailedAnalysis: this.generateDetailedAnalysis(data), financialTables: this.generateFinancialTables(financials), scorecardSummary: this.generateScorecardSummary(scores), validationResults: validation, recommendations: this.generateRecommendations(scores, validation) }; return report; } async generateSummary(data) { const { companyName, scores, validation } = data; return { companyName, finalScore: scores.finalFaropointScore, bondEquivalent: scores.bondEquivalent, defaultProbability: scores.defaultProbability, keyStrengths: this.identifyStrengths(scores), keyRisks: this.identifyRisks(scores), validationStatus: validation.isValid ? 'Valid' : 'Issues Detected', consistencyScore: validation.consistencyScore }; } generateExecutiveSummary(data) { const { companyName, scores, industryClass, financials, companyInfo } = data; return { header: { date: new Date().toISOString().split('T')[0], company: companyName, description: companyInfo.description || 'Company description not available' }, companyMetrics: { website: companyInfo.website || 'Not provided', headquarters: companyInfo.headquarters || 'Not provided', revenue: this.formatCurrency(financials.revenue), employees: companyInfo.employees || 'Not provided', geographicScope: companyInfo.geographicScope || 'Not determined', yearsInBusiness: companyInfo.yearsInBusiness || 'Not provided', spIndustry: industryClass.primaryIndustry, naics: companyInfo.naics || 'Not provided' }, creditAssessment: { faropointRange: this.formatScoreRange(scores.finalFaropointScore), baseScore: scores.basefaropointScore.toFixed(1), revenueAdjustment: this.formatPercentage(scores.revenueAdjustment), confidenceRange: this.getConfidenceRange(data.confidenceLevel), bondEquivalent: scores.bondEquivalent }, keyStrengths: this.identifyStrengths(scores), keyRisks: this.identifyRisks(scores), mitigants: this.identifyMitigants(scores) }; } generateDetailedAnalysis(data) { const { scores, industryClass, financials, companyInfo } = data; return { companyProfile: { description: this.generateCompanyDescription(companyInfo, industryClass), industryAnalysis: this.generateIndustryAnalysis(industryClass) }, financialRiskAssessment: { documentationQuality: this.assessDocumentationQuality(data), keyMetrics: this.extractKeyFinancialMetrics(financials), ratios: this.calculateFinancialRatios(financials), assessment: this.generateFinancialAssessment(scores.componentScores.financialRisk) }, businessRiskAssessment: { competitivePosition: this.generateCompetitiveAnalysis(scores.componentScores.competitivePosition), combinedBusinessRisk: this.calculateBusinessRisk(scores.componentScores) }, modifierAssessment: { liquidity: this.generateLiquidityAssessment(scores.componentScores.liquidity), management: this.generateManagementAssessment(scores.componentScores.management) } }; } generateFinancialTables(financials) { const years = this.getAvailableYears(financials); return { incomeStatement: this.formatIncomeStatement(financials, years), balanceSheet: this.formatBalanceSheet(financials, years), keyRatios: this.formatKeyRatios(financials, years), trends: this.calculateTrends(financials, years) }; } generateScorecardSummary(scores) { const components = [ { dimension: 'Industry Risk', score: scores.componentScores.industryRisk.score, rating: scores.componentScores.industryRisk.riskLevel, weight: '20%', weightedScore: (scores.componentScores.industryRisk.score * 0.20).toFixed(2) }, { dimension: 'Competitive Position', score: scores.componentScores.competitivePosition.score, rating: this.getRatingDescription(scores.componentScores.competitivePosition.score), weight: '20%', weightedScore: (scores.componentScores.competitivePosition.score * 0.20).toFixed(2) }, { dimension: 'Financial Risk', score: scores.componentScores.financialRisk.score, rating: this.getFinancialRiskDescription(scores.componentScores.financialRisk.score), weight: '40%', weightedScore: (scores.componentScores.financialRisk.score * 0.40).toFixed(2) }, { dimension: 'Liquidity', score: scores.componentScores.liquidity.score, rating: this.getLiquidityDescription(scores.componentScores.liquidity.score), weight: '10%', weightedScore: (scores.componentScores.liquidity.score * 0.10).toFixed(2) }, { dimension: 'Management & Governance', score: scores.componentScores.management.score, rating: this.getManagementDescription(scores.componentScores.management.score), weight: '10%', weightedScore: (scores.componentScores.management.score * 0.10).toFixed(2) } ]; return { components, overallScore: scores.weightedScore.toFixed(2), revenueAdjustment: { revenue: this.formatCurrency(scores.revenue), segment: this.getRevenueSegment(scores.revenue), adjustmentFactor: this.formatPercentage(scores.revenueAdjustment), impactOnScore: (scores.finalFaropointScore - scores.basefaropointScore).toFixed(1) } }; } generateRecommendations(scores, validation) { const recommendations = []; // Add validation-based recommendations if (validation.recommendations) { recommendations.push(...validation.recommendations); } // Add score-based recommendations if (scores.componentScores.financialRisk.score <= 2) { recommendations.push({ area: 'Financial Structure', priority: 'High', action: 'Improve leverage metrics', details: 'Consider debt reduction or EBITDA improvement strategies' }); } if (scores.componentScores.liquidity.score <= 2) { recommendations.push({ area: 'Liquidity Management', priority: 'High', action: 'Enhance liquidity position', details: 'Establish committed credit facilities or improve working capital' }); } if (scores.componentScores.competitivePosition.score <= 2) { recommendations.push({ area: 'Market Position', priority: 'Medium', action: 'Strengthen competitive advantages', details: 'Focus on differentiation and customer diversification' }); } return recommendations; } // Helper methods formatCurrency(value) { if (!value) return 'Not provided'; if (value >= 1000000000) return `$${(value / 1000000000).toFixed(1)}B`; if (value >= 1000000) return `$${(value / 1000000).toFixed(1)}M`; if (value >= 1000) return `$${(value / 1000).toFixed(1)}K`; return `$${value.toFixed(0)}`; } formatPercentage(value) { if (!value && value !== 0) return 'N/A'; const percentage = value * 100; return `${percentage > 0 ? '+' : ''}${percentage.toFixed(1)}%`; } formatScoreRange(score) { const confidence = 0.25; // Default confidence range const lower = Math.max(2.0, score - confidence); const upper = Math.min(10.0, score + confidence); return `${lower.toFixed(1)} to ${upper.toFixed(1)}`; } getConfidenceRange(level) { const ranges = { 'audited': '±0.25', 'company-prepared': '±0.50', 'tax-returns': '±0.75', 'limited': '±1.00' }; return ranges[level] || '±0.50'; } identifyStrengths(scores) { const strengths = []; if (scores.componentScores.industryRisk.score >= 4) { strengths.push({ title: 'Low Industry Risk', description: `Operating in ${scores.componentScores.industryRisk.riskLevel} industry with minimal cyclicality` }); } if (scores.componentScores.financialRisk.score >= 4) { strengths.push({ title: 'Strong Financial Profile', description: `Coverage ratio of ${scores.componentScores.financialRisk.ebitdaToInterest.toFixed(1)}x indicates robust debt service capability` }); } if (scores.componentScores.liquidity.score >= 4) { strengths.push({ title: 'Adequate Liquidity', description: `Current ratio of ${scores.componentScores.liquidity.currentRatio.toFixed(2)} provides sufficient short-term flexibility` }); } return strengths; } identifyRisks(scores) { const risks = []; if (scores.componentScores.industryRisk.score <= 2) { risks.push({ title: 'High Industry Cyclicality', description: `${scores.componentScores.industryRisk.riskLevel} exposes company to economic downturns` }); } if (scores.componentScores.financialRisk.score <= 2) { risks.push({ title: 'Elevated Financial Risk', description: 'High leverage limits financial flexibility' }); } if (scores.componentScores.competitivePosition.score <= 2) { risks.push({ title: 'Weak Market Position', description: 'Limited competitive advantages in commodity-like market' }); } if (scores.componentScores.liquidity.score <= 2) { risks.push({ title: 'Liquidity Constraints', description: 'Limited near-term financial resources' }); } return risks; } identifyMitigants(scores) { const mitigants = []; // Find mitigating factors for identified risks scores.componentScores.industryRisk.score <= 3 && scores.componentScores.competitivePosition.score >= 4 && mitigants.push('Strong competitive position partially offsets industry risks'); scores.componentScores.financialRisk.score <= 3 && scores.componentScores.liquidity.score >= 4 && mitigants.push('Good liquidity provides cushion despite leverage'); scores.componentScores.management.score >= 4 && mitigants.push('Experienced management team with proven track record'); return mitigants; } getRatingDescription(score) { const ratings = { 5: 'Excellent', 4: 'Strong', 3: 'Adequate', 2: 'Fair', 1: 'Weak' }; return ratings[score] || 'Not rated'; } getFinancialRiskDescription(score) { const descriptions = { 5: 'Minimal', 4: 'Modest', 3: 'Intermediate', 2: 'Significant', 1: 'Aggressive' }; return descriptions[score] || 'Not rated'; } getLiquidityDescription(score) { const descriptions = { 5: 'Strong', 4: 'Adequate', 3: 'Moderate', 2: 'Weak', 1: 'Very Weak' }; return descriptions[score] || 'Not rated'; } getManagementDescription(score) { const descriptions = { 5: 'Strong', 4: 'Adequate', 3: 'Neutral', 2: 'Concerning', 1: 'Negative' }; return descriptions[score] || 'Not rated'; } getRevenueSegment(revenue) { if (!revenue) return 'Unknown'; if (revenue < 5000000) return '<$5M'; if (revenue < 10000000) return '$5-10M'; if (revenue < 25000000) return '$10-25M'; if (revenue < 50000000) return '$25-50M'; if (revenue < 100000000) return '$50-100M'; if (revenue < 1000000000) return '$100M-1B'; if (revenue < 10000000000) return '$1-10B'; return '>$10B'; } // Additional helper methods would go here... generateCompanyDescription(companyInfo, industryClass) { return `${companyInfo.name || 'The company'} operates in the ${industryClass.primaryIndustry} industry. ${companyInfo.description || ''}`; } generateIndustryAnalysis(industryClass) { return { industry: industryClass.primaryIndustry, riskScore: industryClass.industryRisk.score, characteristics: industryClass.typicalCharacteristics, cyclicality: industryClass.industryRisk.cyclicality, ebitdaDecline: industryClass.industryRisk.ebitdaDeclineRange }; } assessDocumentationQuality(data) { if (data.confidenceLevel === 'audited') return 'Audited'; if (data.confidenceLevel === 'company-prepared') return 'Company-Prepared'; if (data.confidenceLevel === 'tax-returns') return 'Tax Returns Only'; return 'Limited/Informal'; } extractKeyFinancialMetrics(financials) { return { revenue: financials.revenue, ebitda: financials.ebitda, ebitdaMargin: financials.ebitdaMargin, totalDebt: financials.totalDebt, netDebt: financials.netDebt, interestExpense: financials.interestExpense }; } calculateFinancialRatios(financials) { return { currentRatio: financials.currentRatio, ebitdaToInterest: financials.ebitdaToInterest, netDebtToEbitda: financials.netDebtToEbitda, debtToEquity: financials.debtToEquity }; } generateFinancialAssessment(financialRisk) { return { score: financialRisk.score, volatilityTable: financialRisk.volatilityTable, rationale: financialRisk.rationale }; } generateCompetitiveAnalysis(competitivePosition) { return { score: competitivePosition.score, cpgp: competitivePosition.cpgp, subfactors: competitivePosition.subfactors, rationale: competitivePosition.rationale }; } calculateBusinessRisk(componentScores) { const industryRisk = componentScores.industryRisk.score; const competitivePosition = componentScores.competitivePosition.score; // Use business risk matrix const matrix = { 5: { 5: 5, 4: 5, 3: 4, 2: 3, 1: 2 }, 4: { 5: 5, 4: 4, 3: 3, 2: 2, 1: 1 }, 3: { 5: 4, 4: 4, 3: 3, 2: 2, 1: 1 }, 2: { 5: 3, 4: 3, 3: 2, 2: 2, 1: 1 }, 1: { 5: 2, 4: 2, 3: 2, 2: 1, 1: 1 } }; const businessRisk = matrix[industryRisk]?.[competitivePosition] || 3; return { score: businessRisk, description: this.getRatingDescription(businessRisk) }; } generateLiquidityAssessment(liquidity) { return { score: liquidity.score, currentRatio: liquidity.currentRatio, designation: this.getLiquidityDescription(liquidity.score), rationale: liquidity.rationale }; } generateManagementAssessment(management) { return { score: management.score, rating: this.getManagementDescription(management.score), factors: management.factors, rationale: management.rationale }; } getAvailableYears(financials) { // Simplified - would normally extract from multi-year data return ['Year 1', 'Year 2', 'Year 3']; } formatIncomeStatement(financials, years) { // Simplified - would normally format multi-year data return { revenue: [financials.revenue], ebitda: [financials.ebitda], netIncome: [financials.netIncome] }; } formatBalanceSheet(financials, years) { // Simplified - would normally format multi-year data return { totalAssets: [financials.totalAssets], currentAssets: [financials.currentAssets], totalDebt: [financials.totalDebt], currentLiabilities: [financials.currentLiabilities] }; } formatKeyRatios(financials, years) { // Simplified - would normally calculate for multiple years return { currentRatio: [financials.currentRatio], ebitdaMargin: [financials.ebitdaMargin], debtToEbitda: [financials.netDebtToEbitda] }; } calculateTrends(financials, years) { // Simplified - would normally calculate year-over-year trends return { revenueGrowth: 'Not available', marginTrend: 'Stable', leverageTrend: 'Improving' }; } }