242 lines
8.8 KiB
TypeScript
242 lines
8.8 KiB
TypeScript
import { logger } from '../utils/logger';
|
|
|
|
export interface CIMReviewData {
|
|
dealOverview: {
|
|
targetCompanyName: string;
|
|
industrySector: string;
|
|
geography: string;
|
|
dealSource: string;
|
|
transactionType: string;
|
|
dateCIMReceived: string;
|
|
dateReviewed: string;
|
|
reviewers: string;
|
|
cimPageCount: string;
|
|
statedReasonForSale: string;
|
|
employeeCount: string;
|
|
};
|
|
businessDescription: {
|
|
coreOperationsSummary: string;
|
|
keyProductsServices: string;
|
|
uniqueValueProposition: string;
|
|
customerBaseOverview: {
|
|
keyCustomerSegments: string;
|
|
customerConcentrationRisk: string;
|
|
typicalContractLength: string;
|
|
};
|
|
keySupplierOverview: {
|
|
dependenceConcentrationRisk: string;
|
|
};
|
|
};
|
|
marketIndustryAnalysis: {
|
|
estimatedMarketSize: string;
|
|
estimatedMarketGrowthRate: string;
|
|
keyIndustryTrends: string;
|
|
competitiveLandscape: {
|
|
keyCompetitors: string;
|
|
targetMarketPosition: string;
|
|
basisOfCompetition: string;
|
|
};
|
|
barriersToEntry: string;
|
|
};
|
|
financialSummary: {
|
|
financials: {
|
|
fy3: { revenue: string; revenueGrowth: string; grossProfit: string; grossMargin: string; ebitda: string; ebitdaMargin: string };
|
|
fy2: { revenue: string; revenueGrowth: string; grossProfit: string; grossMargin: string; ebitda: string; ebitdaMargin: string };
|
|
fy1: { revenue: string; revenueGrowth: string; grossProfit: string; grossMargin: string; ebitda: string; ebitdaMargin: string };
|
|
ltm: { revenue: string; revenueGrowth: string; grossProfit: string; grossMargin: string; ebitda: string; ebitdaMargin: string };
|
|
};
|
|
qualityOfEarnings: string;
|
|
revenueGrowthDrivers: string;
|
|
marginStabilityAnalysis: string;
|
|
capitalExpenditures: string;
|
|
workingCapitalIntensity: string;
|
|
freeCashFlowQuality: string;
|
|
};
|
|
managementTeamOverview: {
|
|
keyLeaders: string;
|
|
managementQualityAssessment: string;
|
|
postTransactionIntentions: string;
|
|
organizationalStructure: string;
|
|
};
|
|
preliminaryInvestmentThesis: {
|
|
keyAttractions: string;
|
|
potentialRisks: string;
|
|
valueCreationLevers: string;
|
|
alignmentWithFundStrategy: string;
|
|
};
|
|
keyQuestionsNextSteps: {
|
|
criticalQuestions: string;
|
|
missingInformation: string;
|
|
preliminaryRecommendation: string;
|
|
rationaleForRecommendation: string;
|
|
proposedNextSteps: string;
|
|
};
|
|
}
|
|
|
|
class CSVExportService {
|
|
/**
|
|
* Convert CIM Review data to CSV format
|
|
*/
|
|
static generateCIMReviewCSV(reviewData: CIMReviewData, companyName: string = 'Unknown'): string {
|
|
try {
|
|
const csvRows: string[] = [];
|
|
|
|
// Add header
|
|
csvRows.push('BPCP CIM Review Summary');
|
|
csvRows.push(`Company: ${companyName}`);
|
|
csvRows.push(`Generated: ${new Date().toISOString()}`);
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Deal Overview Section
|
|
csvRows.push('DEAL OVERVIEW');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.dealOverview) {
|
|
Object.entries(reviewData.dealOverview).forEach(([key, value]) => {
|
|
csvRows.push(`${this.formatFieldName(key)},${this.escapeCSVValue(value)}`);
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Business Description Section
|
|
csvRows.push('BUSINESS DESCRIPTION');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.businessDescription) {
|
|
Object.entries(reviewData.businessDescription).forEach(([key, value]) => {
|
|
if (typeof value === 'object') {
|
|
// Handle nested objects like customerBaseOverview
|
|
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
csvRows.push(`${this.formatFieldName(key)} - ${this.formatFieldName(nestedKey)},${this.escapeCSVValue(nestedValue)}`);
|
|
});
|
|
} else {
|
|
csvRows.push(`${this.formatFieldName(key)},${this.escapeCSVValue(value)}`);
|
|
}
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Market & Industry Analysis Section
|
|
csvRows.push('MARKET & INDUSTRY ANALYSIS');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.marketIndustryAnalysis) {
|
|
Object.entries(reviewData.marketIndustryAnalysis).forEach(([key, value]) => {
|
|
if (typeof value === 'object') {
|
|
// Handle nested objects like competitiveLandscape
|
|
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
csvRows.push(`${this.formatFieldName(key)} - ${this.formatFieldName(nestedKey)},${this.escapeCSVValue(nestedValue)}`);
|
|
});
|
|
} else {
|
|
csvRows.push(`${this.formatFieldName(key)},${this.escapeCSVValue(value)}`);
|
|
}
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Financial Summary Section
|
|
csvRows.push('FINANCIAL SUMMARY');
|
|
csvRows.push('Period,Revenue,Revenue Growth,Gross Profit,Gross Margin,EBITDA,EBITDA Margin');
|
|
if (reviewData.financialSummary?.financials) {
|
|
Object.entries(reviewData.financialSummary.financials).forEach(([period, financials]) => {
|
|
csvRows.push(`${period.toUpperCase()},${this.escapeCSVValue(financials.revenue)},${this.escapeCSVValue(financials.revenueGrowth)},${this.escapeCSVValue(financials.grossProfit)},${this.escapeCSVValue(financials.grossMargin)},${this.escapeCSVValue(financials.ebitda)},${this.escapeCSVValue(financials.ebitdaMargin)}`);
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Additional Financial Metrics
|
|
csvRows.push('ADDITIONAL FINANCIAL METRICS');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.financialSummary) {
|
|
const additionalMetrics = [
|
|
'qualityOfEarnings',
|
|
'revenueGrowthDrivers',
|
|
'marginStabilityAnalysis',
|
|
'capitalExpenditures',
|
|
'workingCapitalIntensity',
|
|
'freeCashFlowQuality'
|
|
];
|
|
|
|
additionalMetrics.forEach(metric => {
|
|
if (reviewData.financialSummary[metric]) {
|
|
csvRows.push(`${this.formatFieldName(metric)},${this.escapeCSVValue(reviewData.financialSummary[metric])}`);
|
|
}
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Management Team Overview Section
|
|
csvRows.push('MANAGEMENT TEAM OVERVIEW');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.managementTeamOverview) {
|
|
Object.entries(reviewData.managementTeamOverview).forEach(([key, value]) => {
|
|
csvRows.push(`${this.formatFieldName(key)},${this.escapeCSVValue(value)}`);
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Preliminary Investment Thesis Section
|
|
csvRows.push('PRELIMINARY INVESTMENT THESIS');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.preliminaryInvestmentThesis) {
|
|
Object.entries(reviewData.preliminaryInvestmentThesis).forEach(([key, value]) => {
|
|
csvRows.push(`${this.formatFieldName(key)},${this.escapeCSVValue(value)}`);
|
|
});
|
|
}
|
|
csvRows.push(''); // Empty row
|
|
|
|
// Key Questions & Next Steps Section
|
|
csvRows.push('KEY QUESTIONS & NEXT STEPS');
|
|
csvRows.push('Field,Value');
|
|
if (reviewData.keyQuestionsNextSteps) {
|
|
Object.entries(reviewData.keyQuestionsNextSteps).forEach(([key, value]) => {
|
|
csvRows.push(`${this.formatFieldName(key)},${this.escapeCSVValue(value)}`);
|
|
});
|
|
}
|
|
|
|
return csvRows.join('\n');
|
|
} catch (error) {
|
|
logger.error('Failed to generate CSV from CIM Review data', { error });
|
|
throw new Error('Failed to generate CSV export');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format field names for better readability
|
|
*/
|
|
private static formatFieldName(fieldName: string): string {
|
|
return fieldName
|
|
.replace(/([A-Z])/g, ' $1') // Add space before capital letters
|
|
.replace(/^./, str => str.toUpperCase()) // Capitalize first letter
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* Escape CSV values to handle commas, quotes, and newlines
|
|
*/
|
|
private static escapeCSVValue(value: string): string {
|
|
if (!value) return '';
|
|
|
|
// Replace newlines with spaces and trim
|
|
const cleanValue = value.replace(/\n/g, ' ').replace(/\r/g, ' ').trim();
|
|
|
|
// If value contains comma, quote, or newline, wrap in quotes and escape internal quotes
|
|
if (cleanValue.includes(',') || cleanValue.includes('"') || cleanValue.includes('\n')) {
|
|
return `"${cleanValue.replace(/"/g, '""')}"`;
|
|
}
|
|
|
|
return cleanValue;
|
|
}
|
|
|
|
/**
|
|
* Generate standardized filename for CSV export
|
|
*/
|
|
static generateCSVFilename(companyName: string): string {
|
|
const date = new Date().toISOString().split('T')[0].replace(/-/g, ''); // YYYYMMDD
|
|
const sanitizedCompanyName = companyName
|
|
.replace(/[^a-zA-Z0-9\s]/g, '') // Remove special characters
|
|
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
.toUpperCase();
|
|
|
|
return `${date}_${sanitizedCompanyName}_CIM_Data.csv`;
|
|
}
|
|
}
|
|
|
|
export default CSVExportService;
|