sebit-mcp-public
Version:
> 한국어 설명은 아래 링크에서 확인할 수 있습니다. > 👉 [README.ko.md](./README.ko.md)
287 lines (286 loc) • 15.2 kB
JavaScript
;
// =============================
// FILE: /src/report-generator.ts
// 세션 분석 보고서 생성 및 PDF 저장 기능
// =============================
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReportGenerator = void 0;
const jspdf_1 = require("jspdf");
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const dayjs_1 = __importDefault(require("dayjs"));
const logger_1 = require("./logger");
// 모델별 설명과 사용 용도
const modelDescriptions = {
dda: {
description: "Dynamic Depreciation Analysis - 동적 감가상각 분석",
purpose: "자산의 시간에 따른 가치 감소를 동적으로 계산하여 정확한 감가상각 비용 산출",
insights: "감가상각 방식의 최적화를 통해 세무 효율성을 높이고, 정확한 자산 가치 평가가 가능합니다."
},
lam: {
description: "Lease Asset Model - 리스 자산 모델",
purpose: "리스 계약에 따른 자산과 부채의 초기 인식 및 후속 측정을 위한 회계 처리",
insights: "IFRS 16 기준에 따른 리스 회계 처리로 재무상태표의 투명성을 제고할 수 있습니다."
},
rvm: {
description: "Resource Valuation Model - 자원 가치 평가 모델",
purpose: "기업이 보유한 다양한 자원의 공정가치 평가 및 가치 변동 분석",
insights: "자원의 정확한 가치 평가를 통해 투자 의사결정과 자원 배분 최적화가 가능합니다."
},
ceem: {
description: "Consumable Expense Model - 소모성 비용 모델",
purpose: "소모성 자산의 사용 패턴 분석 및 비용 배분의 최적화",
insights: "소모성 자산의 효율적 관리로 운영 비용을 절감하고 예산 계획의 정확성을 높일 수 있습니다."
},
bdm: {
description: "Bond Effective Interest Model - 채권 유효이자율 모델",
purpose: "채권의 유효이자율법에 따른 이자비용 계산 및 장부가액 조정",
insights: "정확한 이자비용 인식으로 재무성과의 신뢰성을 높이고, 채권 관리 전략 수립이 가능합니다."
},
belm: {
description: "Bank Expected Loss Model - 은행 기대손실 모델",
purpose: "IFRS 9에 따른 금융자산의 기대신용손실 측정 및 충당금 설정",
insights: "선제적 손실 인식을 통해 리스크 관리를 강화하고, 건전성 지표를 개선할 수 있습니다."
},
cprm: {
description: "Convertible Bond Risk Model - 전환사채 위험 모델",
purpose: "전환사채의 전환 옵션 가치 평가 및 리스크 헤지 전략 수립",
insights: "전환사채의 복합금융상품 특성을 반영한 정확한 가치평가로 투자 리스크를 최소화할 수 있습니다."
},
ocim: {
description: "OCI Compounded Increase Model - 기타포괄손익 복리증가 모델",
purpose: "기타포괄손익 항목의 복리 효과를 고려한 누적 변동액 계산",
insights: "기타포괄손익의 장기적 영향을 정확히 파악하여 자본 관리 전략을 수립할 수 있습니다."
},
farex: {
description: "Foreign Exchange Adjustment Model - 외환 조정 모델",
purpose: "외화표시 자산·부채의 환율 변동에 따른 환산차이 계산 및 헤지 효과 분석",
insights: "환율 리스크 관리를 통해 외환 변동성으로 인한 손실을 최소화하고 안정적인 수익성을 확보할 수 있습니다."
},
tctbeam: {
description: "Trigonometric Breakeven Analysis Model - 삼각함수 손익분기점 모델",
purpose: "주기적 변동을 고려한 손익분기점 분석 및 최적 운영점 도출",
insights: "계절성이나 주기적 변동이 있는 사업의 경우, 보다 정확한 손익분기점 분석으로 운영 효율성을 극대화할 수 있습니다."
},
cpmrv: {
description: "Crypto Real Value Model - 암호화폐 실질가치 모델",
purpose: "암호화폐 자산의 실질 가치 평가 및 변동성 리스크 분석",
insights: "암호화폐의 높은 변동성을 고려한 실질 가치 평가로 디지털 자산 투자 전략을 수립할 수 있습니다."
},
dcbpra: {
description: "Beta-Adjusted Return Analysis Model - 베타 조정 수익률 분석 모델",
purpose: "시장 베타를 조정한 위험조정수익률 계산 및 투자성과 평가",
insights: "시장 리스크를 고려한 정확한 성과 평가로 포트폴리오 최적화 및 투자 전략 개선이 가능합니다."
},
journal: {
description: "Journalizing Model - 분개 및 분개장 생성",
purpose: "회계 거래의 분개 처리 및 분개장 자동 생성",
insights: "회계 실무를 위한 도구로서 분석 보고서 대상에서 제외됩니다."
}
};
class ReportGenerator {
static DEFAULT_SAVE_PATH = path_1.default.join(process.env.USERPROFILE || 'C:\\Users\\USER', 'Documents', 'SEBIT-MCP-Reports');
constructor() {
// 기본 저장 경로 생성
this.ensureDirectoryExists(ReportGenerator.DEFAULT_SAVE_PATH);
}
async ensureDirectoryExists(dirPath) {
try {
await fs_extra_1.default.ensureDir(dirPath);
logger_1.Logger.debug(`Directory ensured: ${dirPath}`);
}
catch (error) {
logger_1.Logger.error(`Failed to create directory: ${dirPath}`, error);
}
}
// 세션 분석 수행 (journal 모델 제외)
analyzeSession(sessionData) {
// journal 모델을 제외한 실행 기록만 분석 대상으로 사용
const analysisExecutions = sessionData.executions.filter(exec => exec.modelName !== 'journal');
const allExecutions = sessionData.executions; // 전체 통계용
const totalExecutions = allExecutions.length;
const successfulExecutions = allExecutions.filter(e => e.success).length;
const successRate = totalExecutions > 0 ? (successfulExecutions / totalExecutions) * 100 : 0;
// 모델 사용 빈도 계산 (journal 제외)
const modelCounts = new Map();
analysisExecutions.forEach(exec => {
const count = modelCounts.get(exec.modelName) || 0;
modelCounts.set(exec.modelName, count + 1);
});
const mostUsedModels = Array.from(modelCounts.entries())
.map(([model, count]) => ({ model, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
// 평균 실행 시간 (전체 포함)
const avgExecutionTime = totalExecutions > 0
? allExecutions.reduce((sum, exec) => sum + exec.executionTime, 0) / totalExecutions
: 0;
// 시간 범위 (전체 포함)
const timestamps = allExecutions.map(e => e.timestamp).sort();
const timeRange = {
start: timestamps.length > 0 ? timestamps[0] : sessionData.startTime,
end: timestamps.length > 0 ? timestamps[timestamps.length - 1] : sessionData.startTime
};
// 오류 요약 (전체 포함, journal은 제외하지 않음)
const errorSummary = allExecutions
.filter(e => !e.success && e.error)
.map(e => `${e.modelName}: ${e.error}`)
.slice(0, 5); // 최대 5개만
return {
totalExecutions,
successRate,
mostUsedModels,
avgExecutionTime,
timeRange,
errorSummary
};
}
// PDF 보고서 생성
async generateSessionReport(sessionData, savePath, customAnalysis) {
const analysis = this.analyzeSession(sessionData);
const doc = new jspdf_1.jsPDF();
// 한글 폰트 설정 (기본 폰트 사용)
let yPosition = 20;
const lineHeight = 7;
const pageWidth = doc.internal.pageSize.getWidth();
// 헤더
doc.setFontSize(18);
doc.text('SEBIT MCP 세션 분석 보고서', pageWidth / 2, yPosition, { align: 'center' });
yPosition += lineHeight * 2;
doc.setFontSize(12);
doc.text(`생성일시: ${(0, dayjs_1.default)().format('YYYY-MM-DD HH:mm:ss')}`, 20, yPosition);
yPosition += lineHeight;
doc.text(`세션 ID: ${sessionData.sessionId}`, 20, yPosition);
yPosition += lineHeight * 2;
// 세션 요약
doc.setFontSize(14);
doc.text('=== 세션 요약 ===', 20, yPosition);
yPosition += lineHeight * 1.5;
doc.setFontSize(10);
const journalCount = sessionData.executions.filter(e => e.modelName === 'journal').length;
const analysisCount = analysis.totalExecutions - journalCount;
doc.text(`총 실행 모델 수: ${analysis.totalExecutions}`, 20, yPosition);
yPosition += lineHeight;
doc.text(` - 분석 모델: ${analysisCount}개, 분개 처리: ${journalCount}개`, 25, yPosition);
yPosition += lineHeight;
doc.text(`성공률: ${analysis.successRate.toFixed(1)}%`, 20, yPosition);
yPosition += lineHeight;
doc.text(`평균 실행 시간: ${analysis.avgExecutionTime.toFixed(2)}ms`, 20, yPosition);
yPosition += lineHeight;
doc.text(`세션 기간: ${(0, dayjs_1.default)(analysis.timeRange.start).format('HH:mm:ss')} ~ ${(0, dayjs_1.default)(analysis.timeRange.end).format('HH:mm:ss')}`, 20, yPosition);
yPosition += lineHeight * 2;
// 가장 많이 사용된 모델들
if (analysis.mostUsedModels.length > 0) {
doc.setFontSize(14);
doc.text('=== 주요 사용 모델 ===', 20, yPosition);
yPosition += lineHeight * 1.5;
doc.setFontSize(10);
analysis.mostUsedModels.forEach(({ model, count }) => {
const desc = modelDescriptions[model];
doc.text(`${count}회 - ${desc.description}`, 20, yPosition);
yPosition += lineHeight;
});
yPosition += lineHeight;
}
// 모델별 상세 분석 및 인사이트
if (analysis.mostUsedModels.length > 0) {
doc.setFontSize(14);
doc.text('=== 상세 분석 및 인사이트 ===', 20, yPosition);
yPosition += lineHeight * 1.5;
for (const { model, count } of analysis.mostUsedModels.slice(0, 3)) { // 상위 3개만
// journal 모델은 상세 분석에서 제외
if (model === 'journal')
continue;
const desc = modelDescriptions[model];
// 페이지 넘김 체크
if (yPosition > 250) {
doc.addPage();
yPosition = 20;
}
doc.setFontSize(12);
doc.text(`${desc.description} (${count}회 실행)`, 20, yPosition);
yPosition += lineHeight * 1.2;
doc.setFontSize(9);
// 텍스트 줄 바꿈 처리
const purposeLines = doc.splitTextToSize(`용도: ${desc.purpose}`, pageWidth - 40);
doc.text(purposeLines, 20, yPosition);
yPosition += lineHeight * purposeLines.length + 3;
const insightLines = doc.splitTextToSize(`인사이트: ${desc.insights}`, pageWidth - 40);
doc.text(insightLines, 20, yPosition);
yPosition += lineHeight * insightLines.length + lineHeight;
}
}
// 오류 요약 (있는 경우)
if (analysis.errorSummary.length > 0) {
if (yPosition > 250) {
doc.addPage();
yPosition = 20;
}
doc.setFontSize(14);
doc.text('=== 오류 분석 ===', 20, yPosition);
yPosition += lineHeight * 1.5;
doc.setFontSize(9);
analysis.errorSummary.forEach(error => {
const errorLines = doc.splitTextToSize(`• ${error}`, pageWidth - 40);
doc.text(errorLines, 20, yPosition);
yPosition += lineHeight * errorLines.length;
});
yPosition += lineHeight;
}
// 종합 의견 및 솔루션
if (yPosition > 230) {
doc.addPage();
yPosition = 20;
}
doc.setFontSize(14);
doc.text('=== 종합 의견 및 개선 방안 ===', 20, yPosition);
yPosition += lineHeight * 1.5;
doc.setFontSize(9);
// 기본 분석 의견
let opinion = '';
if (analysis.successRate >= 90) {
opinion = '우수한 성능: 모델 실행이 매우 안정적으로 수행되었습니다. 현재 설정을 유지하면서 추가적인 최적화를 고려해볼 수 있습니다.';
}
else if (analysis.successRate >= 70) {
opinion = '양호한 성능: 대부분의 모델이 정상적으로 실행되었으나, 일부 오류 케이스에 대한 검토가 필요합니다.';
}
else {
opinion = '주의 필요: 실행 실패율이 높아 입력 데이터 검증 및 모델 안정성 개선이 필요합니다.';
}
// 사용자 정의 분석이 있는 경우 추가
if (customAnalysis) {
opinion += `\n\n추가 분석: ${customAnalysis}`;
}
const opinionLines = doc.splitTextToSize(opinion, pageWidth - 40);
doc.text(opinionLines, 20, yPosition);
yPosition += lineHeight * opinionLines.length + lineHeight;
// 권장 사항
const recommendations = [
'정기적인 모델 성능 모니터링 및 최적화',
'입력 데이터 품질 검증 프로세스 강화',
'오류 발생 패턴 분석을 통한 예방적 조치 수립',
'자주 사용되는 모델의 성능 튜닝 고려'
];
doc.setFontSize(10);
doc.text('권장 사항:', 20, yPosition);
yPosition += lineHeight * 1.2;
doc.setFontSize(9);
recommendations.forEach((rec, index) => {
doc.text(`${index + 1}. ${rec}`, 25, yPosition);
yPosition += lineHeight;
});
// 파일 저장
const finalSavePath = savePath || ReportGenerator.DEFAULT_SAVE_PATH;
await this.ensureDirectoryExists(finalSavePath);
const fileName = `SEBIT-MCP-Report_${(0, dayjs_1.default)().format('YYYY-MM-DD_HH-mm-ss')}.pdf`;
const fullPath = path_1.default.join(finalSavePath, fileName);
const pdfBuffer = doc.output('arraybuffer');
await fs_extra_1.default.writeFile(fullPath, Buffer.from(pdfBuffer));
logger_1.Logger.info(`Session report generated successfully: ${fullPath}`);
return fullPath;
}
}
exports.ReportGenerator = ReportGenerator;