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:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user