Improve PDF formatting with financial tables and professional styling - Add comprehensive financial table with FY1/FY2/FY3/LTM periods - Include all missing sections (investment analysis, next steps, etc.) - Update PDF styling with smaller fonts (10pt), Times New Roman, professional layout - Add proper table formatting with borders and headers - Fix TypeScript compilation errors

This commit is contained in:
Jon
2025-07-29 00:34:12 -04:00
parent 4ce430b531
commit a4c8aac92d
2 changed files with 287 additions and 75 deletions

View File

@@ -499,35 +499,11 @@ export class OptimizedAgenticRAGProcessor {
if (analysisData.dealOverview.geography) {
summary += `**Geography:** ${analysisData.dealOverview.geography}\n\n`;
}
}
// Add financial summary
if (analysisData.financialSummary?.financials) {
summary += `## Financial Summary\n\n`;
const financials = analysisData.financialSummary.financials;
if (financials.fy3) {
summary += `### FY3 (Latest)\n\n`;
if (financials.fy3.revenue) summary += `- **Revenue:** ${financials.fy3.revenue}\n`;
if (financials.fy3.ebitda) summary += `- **EBITDA:** ${financials.fy3.ebitda}\n`;
if (financials.fy3.ebitdaMargin) summary += `- **EBITDA Margin:** ${financials.fy3.ebitdaMargin}\n`;
if (financials.fy3.revenueGrowth) summary += `- **Revenue Growth:** ${financials.fy3.revenueGrowth}\n\n`;
if (analysisData.dealOverview.dealSource) {
summary += `**Deal Source:** ${analysisData.dealOverview.dealSource}\n\n`;
}
if (financials.fy2) {
summary += `### FY2\n\n`;
if (financials.fy2.revenue) summary += `- **Revenue:** ${financials.fy2.revenue}\n`;
if (financials.fy2.ebitda) summary += `- **EBITDA:** ${financials.fy2.ebitda}\n`;
if (financials.fy2.ebitdaMargin) summary += `- **EBITDA Margin:** ${financials.fy2.ebitdaMargin}\n`;
if (financials.fy2.revenueGrowth) summary += `- **Revenue Growth:** ${financials.fy2.revenueGrowth}\n\n`;
}
if (financials.fy1) {
summary += `### FY1\n\n`;
if (financials.fy1.revenue) summary += `- **Revenue:** ${financials.fy1.revenue}\n`;
if (financials.fy1.ebitda) summary += `- **EBITDA:** ${financials.fy1.ebitda}\n`;
if (financials.fy1.ebitdaMargin) summary += `- **EBITDA Margin:** ${financials.fy1.ebitdaMargin}\n`;
if (financials.fy1.revenueGrowth) summary += `- **Revenue Growth:** ${financials.fy1.revenueGrowth}\n\n`;
if (analysisData.dealOverview.statedReasonForSale) {
summary += `**Reason for Sale:** ${analysisData.dealOverview.statedReasonForSale}\n\n`;
}
}
@@ -542,15 +518,153 @@ export class OptimizedAgenticRAGProcessor {
if (analysisData.businessDescription.uniqueValueProposition) {
summary += `**Unique Value Proposition:** ${analysisData.businessDescription.uniqueValueProposition}\n\n`;
}
// Add customer base overview
if (analysisData.businessDescription.customerBaseOverview) {
summary += `### Customer Base Overview\n\n`;
if (analysisData.businessDescription.customerBaseOverview.keyCustomerSegments) {
summary += `**Key Customer Segments:** ${analysisData.businessDescription.customerBaseOverview.keyCustomerSegments}\n\n`;
}
if (analysisData.businessDescription.customerBaseOverview.customerConcentrationRisk) {
summary += `**Customer Concentration Risk:** ${analysisData.businessDescription.customerBaseOverview.customerConcentrationRisk}\n\n`;
}
if (analysisData.businessDescription.customerBaseOverview.typicalContractLength) {
summary += `**Typical Contract Length:** ${analysisData.businessDescription.customerBaseOverview.typicalContractLength}\n\n`;
}
}
// Add supplier overview
if (analysisData.businessDescription.keySupplierOverview?.dependenceConcentrationRisk) {
summary += `**Supplier Dependence Risk:** ${analysisData.businessDescription.keySupplierOverview.dependenceConcentrationRisk}\n\n`;
}
}
// Add key questions and next steps
if (analysisData.keyQuestionsNextSteps?.criticalQuestions) {
summary += `## Key Questions & Next Steps\n\n`;
summary += `**Critical Questions:** ${analysisData.keyQuestionsNextSteps.criticalQuestions}\n\n`;
// Add market analysis
if (analysisData.marketIndustryAnalysis?.estimatedMarketSize) {
summary += `## Market & Industry Analysis\n\n`;
summary += `**Market Size:** ${analysisData.marketIndustryAnalysis.estimatedMarketSize}\n\n`;
if (analysisData.keyQuestionsNextSteps.preliminaryRecommendation) {
summary += `**Preliminary Recommendation:** ${analysisData.keyQuestionsNextSteps.preliminaryRecommendation}\n\n`;
if (analysisData.marketIndustryAnalysis.estimatedMarketGrowthRate) {
summary += `**Market Growth Rate:** ${analysisData.marketIndustryAnalysis.estimatedMarketGrowthRate}\n\n`;
}
if (analysisData.marketIndustryAnalysis.keyIndustryTrends) {
summary += `**Industry Trends:** ${analysisData.marketIndustryAnalysis.keyIndustryTrends}\n\n`;
}
if (analysisData.marketIndustryAnalysis.barriersToEntry) {
summary += `**Barriers to Entry:** ${analysisData.marketIndustryAnalysis.barriersToEntry}\n\n`;
}
// Add competitive landscape
if (analysisData.marketIndustryAnalysis.competitiveLandscape) {
summary += `### Competitive Landscape\n\n`;
if (analysisData.marketIndustryAnalysis.competitiveLandscape.keyCompetitors) {
summary += `**Key Competitors:** ${analysisData.marketIndustryAnalysis.competitiveLandscape.keyCompetitors}\n\n`;
}
if (analysisData.marketIndustryAnalysis.competitiveLandscape.targetMarketPosition) {
summary += `**Market Position:** ${analysisData.marketIndustryAnalysis.competitiveLandscape.targetMarketPosition}\n\n`;
}
if (analysisData.marketIndustryAnalysis.competitiveLandscape.basisOfCompetition) {
summary += `**Basis of Competition:** ${analysisData.marketIndustryAnalysis.competitiveLandscape.basisOfCompetition}\n\n`;
}
}
}
// Add financial summary
if (analysisData.financialSummary?.financials) {
summary += `## Financial Summary\n\n`;
const financials = analysisData.financialSummary.financials;
// Create financial table
summary += `<table class="financial-table">\n`;
summary += `<thead>\n<tr>\n<th>Metric</th>\n`;
const periods = [];
if (financials.fy1) periods.push('FY1');
if (financials.fy2) periods.push('FY2');
if (financials.fy3) periods.push('FY3');
if (financials.ltm) periods.push('LTM');
periods.forEach(period => {
summary += `<th>${period}</th>\n`;
});
summary += `</tr>\n</thead>\n<tbody>\n`;
// Revenue row
if (financials.fy1?.revenue || financials.fy2?.revenue || financials.fy3?.revenue || financials.ltm?.revenue) {
summary += `<tr>\n<td><strong>Revenue</strong></td>\n`;
periods.forEach(period => {
let value = '-';
if (period === 'FY1' && financials.fy1?.revenue) value = financials.fy1.revenue;
else if (period === 'FY2' && financials.fy2?.revenue) value = financials.fy2.revenue;
else if (period === 'FY3' && financials.fy3?.revenue) value = financials.fy3.revenue;
else if (period === 'LTM' && financials.ltm?.revenue) value = financials.ltm.revenue;
summary += `<td>${value}</td>\n`;
});
summary += `</tr>\n`;
}
// EBITDA row
if (financials.fy1?.ebitda || financials.fy2?.ebitda || financials.fy3?.ebitda || financials.ltm?.ebitda) {
summary += `<tr>\n<td><strong>EBITDA</strong></td>\n`;
periods.forEach(period => {
let value = '-';
if (period === 'FY1' && financials.fy1?.ebitda) value = financials.fy1.ebitda;
else if (period === 'FY2' && financials.fy2?.ebitda) value = financials.fy2.ebitda;
else if (period === 'FY3' && financials.fy3?.ebitda) value = financials.fy3.ebitda;
else if (period === 'LTM' && financials.ltm?.ebitda) value = financials.ltm.ebitda;
summary += `<td>${value}</td>\n`;
});
summary += `</tr>\n`;
}
// EBITDA Margin row
if (financials.fy1?.ebitdaMargin || financials.fy2?.ebitdaMargin || financials.fy3?.ebitdaMargin || financials.ltm?.ebitdaMargin) {
summary += `<tr>\n<td><strong>EBITDA Margin</strong></td>\n`;
periods.forEach(period => {
let value = '-';
if (period === 'FY1' && financials.fy1?.ebitdaMargin) value = financials.fy1.ebitdaMargin;
else if (period === 'FY2' && financials.fy2?.ebitdaMargin) value = financials.fy2.ebitdaMargin;
else if (period === 'FY3' && financials.fy3?.ebitdaMargin) value = financials.fy3.ebitdaMargin;
else if (period === 'LTM' && financials.ltm?.ebitdaMargin) value = financials.ltm.ebitdaMargin;
summary += `<td>${value}</td>\n`;
});
summary += `</tr>\n`;
}
// Revenue Growth row
if (financials.fy1?.revenueGrowth || financials.fy2?.revenueGrowth || financials.fy3?.revenueGrowth || financials.ltm?.revenueGrowth) {
summary += `<tr>\n<td><strong>Revenue Growth</strong></td>\n`;
periods.forEach(period => {
let value = '-';
if (period === 'FY1' && financials.fy1?.revenueGrowth) value = financials.fy1.revenueGrowth;
else if (period === 'FY2' && financials.fy2?.revenueGrowth) value = financials.fy2.revenueGrowth;
else if (period === 'FY3' && financials.fy3?.revenueGrowth) value = financials.fy3.revenueGrowth;
else if (period === 'LTM' && financials.ltm?.revenueGrowth) value = financials.ltm.revenueGrowth;
summary += `<td>${value}</td>\n`;
});
summary += `</tr>\n`;
}
summary += `</tbody>\n</table>\n\n`;
// Add financial notes
if (analysisData.financialSummary.qualityOfEarnings) {
summary += `**Quality of Earnings:** ${analysisData.financialSummary.qualityOfEarnings}\n\n`;
}
if (analysisData.financialSummary.revenueGrowthDrivers) {
summary += `**Revenue Growth Drivers:** ${analysisData.financialSummary.revenueGrowthDrivers}\n\n`;
}
if (analysisData.financialSummary.marginStabilityAnalysis) {
summary += `**Margin Stability:** ${analysisData.financialSummary.marginStabilityAnalysis}\n\n`;
}
if (analysisData.financialSummary.capitalExpenditures) {
summary += `**Capital Expenditures:** ${analysisData.financialSummary.capitalExpenditures}\n\n`;
}
if (analysisData.financialSummary.workingCapitalIntensity) {
summary += `**Working Capital Intensity:** ${analysisData.financialSummary.workingCapitalIntensity}\n\n`;
}
if (analysisData.financialSummary.freeCashFlowQuality) {
summary += `**Free Cash Flow Quality:** ${analysisData.financialSummary.freeCashFlowQuality}\n\n`;
}
}
@@ -562,15 +676,11 @@ export class OptimizedAgenticRAGProcessor {
if (analysisData.managementTeamOverview.managementQualityAssessment) {
summary += `**Quality Assessment:** ${analysisData.managementTeamOverview.managementQualityAssessment}\n\n`;
}
}
// Add market analysis
if (analysisData.marketIndustryAnalysis?.estimatedMarketSize) {
summary += `## Market & Industry Analysis\n\n`;
summary += `**Market Size:** ${analysisData.marketIndustryAnalysis.estimatedMarketSize}\n\n`;
if (analysisData.marketIndustryAnalysis.keyIndustryTrends) {
summary += `**Industry Trends:** ${analysisData.marketIndustryAnalysis.keyIndustryTrends}\n\n`;
if (analysisData.managementTeamOverview.postTransactionIntentions) {
summary += `**Post-Transaction Intentions:** ${analysisData.managementTeamOverview.postTransactionIntentions}\n\n`;
}
if (analysisData.managementTeamOverview.organizationalStructure) {
summary += `**Organizational Structure:** ${analysisData.managementTeamOverview.organizationalStructure}\n\n`;
}
}
@@ -582,6 +692,31 @@ export class OptimizedAgenticRAGProcessor {
if (analysisData.preliminaryInvestmentThesis.potentialRisks) {
summary += `**Potential Risks:** ${analysisData.preliminaryInvestmentThesis.potentialRisks}\n\n`;
}
if (analysisData.preliminaryInvestmentThesis.valueCreationLevers) {
summary += `**Value Creation Levers:** ${analysisData.preliminaryInvestmentThesis.valueCreationLevers}\n\n`;
}
if (analysisData.preliminaryInvestmentThesis.alignmentWithFundStrategy) {
summary += `**Alignment with Fund Strategy:** ${analysisData.preliminaryInvestmentThesis.alignmentWithFundStrategy}\n\n`;
}
}
// Add key questions and next steps
if (analysisData.keyQuestionsNextSteps?.criticalQuestions) {
summary += `## Key Questions & Next Steps\n\n`;
summary += `**Critical Questions:** ${analysisData.keyQuestionsNextSteps.criticalQuestions}\n\n`;
if (analysisData.keyQuestionsNextSteps.missingInformation) {
summary += `**Missing Information:** ${analysisData.keyQuestionsNextSteps.missingInformation}\n\n`;
}
if (analysisData.keyQuestionsNextSteps.preliminaryRecommendation) {
summary += `**Preliminary Recommendation:** ${analysisData.keyQuestionsNextSteps.preliminaryRecommendation}\n\n`;
}
if (analysisData.keyQuestionsNextSteps.rationaleForRecommendation) {
summary += `**Rationale for Recommendation:** ${analysisData.keyQuestionsNextSteps.rationaleForRecommendation}\n\n`;
}
if (analysisData.keyQuestionsNextSteps.proposedNextSteps) {
summary += `**Proposed Next Steps:** ${analysisData.keyQuestionsNextSteps.proposedNextSteps}\n\n`;
}
}
return summary;

View File

@@ -74,8 +74,7 @@ class PDFGenerationService {
* Convert markdown to HTML
*/
private markdownToHTML(markdown: string): string {
// Simple markdown to HTML conversion
// In a production environment, you might want to use a proper markdown parser
// Enhanced markdown to HTML conversion with table support
let html = markdown
// Headers
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
@@ -87,13 +86,19 @@ class PDFGenerationService {
.replace(/\*(.*?)\*/g, '<em>$1</em>')
// Lists
.replace(/^- (.*$)/gim, '<li>$1</li>')
// Paragraphs
// Paragraphs (but preserve tables)
.replace(/\n\n/g, '</p><p>')
.replace(/^(.+)$/gm, '<p>$1</p>');
// Wrap lists properly
html = html.replace(/<li>(.*?)<\/li>/g, '<ul><li>$1</li></ul>');
html = html.replace(/<\/ul>\s*<ul>/g, '');
// Preserve HTML tables by removing paragraph tags around them
html = html.replace(/<p><table/g, '<table');
html = html.replace(/<\/table><\/p>/g, '</table>');
html = html.replace(/<p><\/table>/g, '</table>');
html = html.replace(/<p><table/g, '<table');
return `
<!DOCTYPE html>
@@ -102,58 +107,128 @@ class PDFGenerationService {
<meta charset="UTF-8">
<title>CIM Review Summary</title>
<style>
@page {
margin: 0.75in;
size: A4;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: 'Times New Roman', serif;
font-size: 10pt;
line-height: 1.4;
color: #2c3e50;
margin: 0;
padding: 0;
}
h1 {
color: #2c3e50;
border-bottom: 3px solid #3498db;
padding-bottom: 10px;
font-size: 18pt;
font-weight: bold;
color: #1a365d;
text-align: center;
margin-bottom: 8pt;
border-bottom: 2pt solid #2c5282;
padding-bottom: 8pt;
}
h2 {
color: #34495e;
border-bottom: 2px solid #bdc3c7;
padding-bottom: 5px;
margin-top: 30px;
font-size: 14pt;
font-weight: bold;
color: #2d3748;
margin-top: 20pt;
margin-bottom: 8pt;
border-bottom: 1pt solid #cbd5e0;
padding-bottom: 4pt;
page-break-after: avoid;
}
h3 {
color: #7f8c8d;
margin-top: 25px;
font-size: 12pt;
font-weight: bold;
color: #4a5568;
margin-top: 16pt;
margin-bottom: 6pt;
page-break-after: avoid;
}
p {
margin-bottom: 15px;
margin-bottom: 8pt;
text-align: justify;
}
ul {
margin-bottom: 15px;
margin-bottom: 8pt;
margin-left: 20pt;
}
li {
margin-bottom: 5px;
margin-bottom: 3pt;
text-align: justify;
}
strong {
color: #2c3e50;
font-weight: bold;
color: #2d3748;
}
.header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #ecf0f1;
margin-bottom: 20pt;
padding-bottom: 12pt;
border-bottom: 1pt solid #e2e8f0;
}
.header h1 {
margin-bottom: 4pt;
}
.header p {
font-size: 9pt;
color: #718096;
margin: 0;
}
.footer {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #ecf0f1;
font-size: 12px;
color: #7f8c8d;
margin-top: 20pt;
padding-top: 12pt;
border-top: 1pt solid #e2e8f0;
font-size: 8pt;
color: #718096;
}
.section {
margin-bottom: 16pt;
page-break-inside: avoid;
}
.financial-table {
width: 100%;
border-collapse: collapse;
margin: 8pt 0;
font-size: 9pt;
}
.financial-table th,
.financial-table td {
border: 1pt solid #cbd5e0;
padding: 4pt;
text-align: left;
}
.financial-table th {
background-color: #f7fafc;
font-weight: bold;
color: #2d3748;
}
.page-break {
page-break-before: always;
}
.avoid-break {
page-break-inside: avoid;
}
</style>
</head>
<body>
@@ -161,9 +236,11 @@ class PDFGenerationService {
<h1>CIM Review Summary</h1>
<p>Generated on ${new Date().toLocaleDateString()}</p>
</div>
${html}
<div class="content">
${html}
</div>
<div class="footer">
<p>BPCP CIM Document Processor</p>
<p>BPCP CIM Document Processor | Confidential</p>
</div>
</body>
</html>