mcp-tenant-credit-scorer
Version:
MCP server for tenant credit scoring based on S&P corporate methodology
506 lines (444 loc) • 16.2 kB
JavaScript
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'
};
}
}