aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
1,031 lines (877 loc) • 33.2 kB
JavaScript
import { TokenCounter } from './token-counter.js';
import fs from 'fs';
import path from 'path';
/**
* 압축률 측정 및 성능 분석 도구
*/
export class CompressionMetrics {
constructor() {
this.tokenCounter = new TokenCounter();
this.measurements = new Map();
this.benchmarks = new Map();
this.initializeMetrics();
}
/**
* 메트릭 시스템을 초기화합니다
*/
initializeMetrics() {
// 메트릭 타입 정의
this.metricTypes = {
compression: {
name: 'Compression Metrics',
description: '압축률 관련 메트릭',
measurements: [
'compression_ratio',
'token_reduction',
'size_reduction',
'information_preservation'
]
},
performance: {
name: 'Performance Metrics',
description: '성능 관련 메트릭',
measurements: [
'compression_time',
'decompression_time',
'memory_usage',
'throughput'
]
},
quality: {
name: 'Quality Metrics',
description: '품질 관련 메트릭',
measurements: [
'readability_score',
'structure_preservation',
'semantic_similarity',
'information_loss'
]
},
efficiency: {
name: 'Efficiency Metrics',
description: '효율성 관련 메트릭',
measurements: [
'tokens_per_second',
'compression_efficiency',
'resource_utilization',
'cost_effectiveness'
]
}
};
// 벤치마크 기준점
this.benchmarkThresholds = {
compression_ratio: {
excellent: 70,
good: 50,
acceptable: 30,
poor: 10
},
compression_time: {
excellent: 1000, // 1초
good: 3000, // 3초
acceptable: 5000, // 5초
poor: 10000 // 10초
},
information_preservation: {
excellent: 90,
good: 80,
acceptable: 70,
poor: 50
},
readability_score: {
excellent: 90,
good: 80,
acceptable: 70,
poor: 50
}
};
// 측정 히스토리
this.measurementHistory = [];
}
/**
* 압축 결과를 측정합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @param {Object} metadata - 압축 메타데이터
* @returns {Object} 측정 결과
*/
measureCompression(original, compressed, metadata = {}) {
const measurementId = this.generateMeasurementId();
const timestamp = Date.now();
const measurement = {
id: measurementId,
timestamp,
originalContent: original,
compressedContent: compressed,
metadata,
metrics: {},
analysis: {},
benchmark: {}
};
// 1. 압축 메트릭 계산
measurement.metrics.compression = this.calculateCompressionMetrics(original, compressed);
// 2. 성능 메트릭 계산
measurement.metrics.performance = this.calculatePerformanceMetrics(metadata);
// 3. 품질 메트릭 계산
measurement.metrics.quality = this.calculateQualityMetrics(original, compressed, metadata);
// 4. 효율성 메트릭 계산
measurement.metrics.efficiency = this.calculateEfficiencyMetrics(original, compressed, metadata);
// 5. 벤치마크 분석
measurement.benchmark = this.analyzeBenchmark(measurement.metrics);
// 6. 종합 분석
measurement.analysis = this.generateAnalysis(measurement.metrics, measurement.benchmark);
// 측정 결과 저장
this.measurements.set(measurementId, measurement);
this.measurementHistory.push(measurement);
return measurement;
}
/**
* 압축 메트릭을 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @returns {Object} 압축 메트릭
*/
calculateCompressionMetrics(original, compressed) {
const originalTokens = this.tokenCounter.countTokens(original);
const compressedTokens = this.tokenCounter.countTokens(compressed);
const originalSize = Buffer.byteLength(original, 'utf8');
const compressedSize = Buffer.byteLength(compressed, 'utf8');
const tokenReduction = originalTokens - compressedTokens;
const sizeReduction = originalSize - compressedSize;
const compressionRatio = originalTokens > 0 ? (tokenReduction / originalTokens) * 100 : 0;
const sizeCompressionRatio = originalSize > 0 ? (sizeReduction / originalSize) * 100 : 0;
// 정보 보존률 계산
const informationPreservation = this.calculateInformationPreservation(original, compressed);
return {
original_tokens: originalTokens,
compressed_tokens: compressedTokens,
token_reduction: tokenReduction,
token_reduction_percentage: compressionRatio,
original_size: originalSize,
compressed_size: compressedSize,
size_reduction: sizeReduction,
size_reduction_percentage: sizeCompressionRatio,
information_preservation: informationPreservation,
compression_efficiency: this.calculateCompressionEfficiency(compressionRatio, informationPreservation)
};
}
/**
* 성능 메트릭을 계산합니다
* @param {Object} metadata - 메타데이터
* @returns {Object} 성능 메트릭
*/
calculatePerformanceMetrics(metadata) {
const performance = {
compression_time: metadata.processingTime || 0,
decompression_time: metadata.decompressionTime || 0,
memory_usage: metadata.memoryUsage || 0,
throughput: 0,
tokens_per_second: 0
};
// 처리량 계산
if (performance.compression_time > 0 && metadata.originalTokens) {
performance.tokens_per_second = (metadata.originalTokens / performance.compression_time) * 1000;
performance.throughput = (metadata.originalTokens / performance.compression_time) * 1000;
}
// 메모리 효율성 계산
if (performance.memory_usage > 0 && metadata.originalTokens) {
performance.memory_efficiency = metadata.originalTokens / performance.memory_usage;
}
// 파이프라인 성능 분석
if (metadata.pipelineStages) {
performance.pipeline_analysis = this.analyzePipelinePerformance(metadata.pipelineStages);
}
return performance;
}
/**
* 품질 메트릭을 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @param {Object} metadata - 메타데이터
* @returns {Object} 품질 메트릭
*/
calculateQualityMetrics(original, compressed, metadata) {
const quality = {
readability_score: this.calculateReadabilityScore(compressed),
structure_preservation: this.calculateStructurePreservation(original, compressed),
semantic_similarity: this.calculateSemanticSimilarity(original, compressed),
information_loss: this.calculateInformationLoss(original, compressed),
coherence_score: this.calculateCoherenceScore(compressed),
completeness_score: this.calculateCompletenessScore(original, compressed)
};
// 전체 품질 점수 계산
quality.overall_quality = this.calculateOverallQuality(quality);
return quality;
}
/**
* 효율성 메트릭을 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @param {Object} metadata - 메타데이터
* @returns {Object} 효율성 메트릭
*/
calculateEfficiencyMetrics(original, compressed, metadata) {
const originalTokens = this.tokenCounter.countTokens(original);
const compressedTokens = this.tokenCounter.countTokens(compressed);
const processingTime = metadata.processingTime || 1;
const efficiency = {
compression_efficiency: this.calculateCompressionEfficiency(
((originalTokens - compressedTokens) / originalTokens) * 100,
this.calculateInformationPreservation(original, compressed)
),
resource_utilization: this.calculateResourceUtilization(metadata),
cost_effectiveness: this.calculateCostEffectiveness(originalTokens, compressedTokens, processingTime),
energy_efficiency: this.calculateEnergyEfficiency(originalTokens, processingTime)
};
return efficiency;
}
/**
* 정보 보존률을 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @returns {number} 정보 보존률 (0-100)
*/
calculateInformationPreservation(original, compressed) {
// 키워드 보존률 계산
const originalKeywords = this.extractKeywords(original);
const compressedKeywords = this.extractKeywords(compressed);
const preservedKeywords = originalKeywords.filter(keyword =>
compressedKeywords.includes(keyword)
);
const keywordPreservation = originalKeywords.length > 0
? (preservedKeywords.length / originalKeywords.length) * 100
: 100;
// 구조 보존률 계산
const structurePreservation = this.calculateStructurePreservation(original, compressed);
// 의미 보존률 계산
const semanticPreservation = this.calculateSemanticSimilarity(original, compressed);
// 가중 평균 계산
return (keywordPreservation * 0.4 + structurePreservation * 0.3 + semanticPreservation * 0.3);
}
/**
* 키워드를 추출합니다
* @param {string} text - 텍스트
* @returns {Array<string>} 키워드 배열
*/
extractKeywords(text) {
const keywords = [];
const words = text.toLowerCase().split(/\s+/);
// 중요한 키워드 패턴
const keywordPatterns = [
/\b(goal|objective|task|requirement|critical|important|key|main|primary|essential)\b/g,
/\b(목표|목적|태스크|요구사항|중요|핵심|주요|필수|기본|우선)\b/g
];
for (const pattern of keywordPatterns) {
const matches = text.match(pattern);
if (matches) {
keywords.push(...matches);
}
}
// 고유 키워드만 반환
return [...new Set(keywords)];
}
/**
* 구조 보존률을 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @returns {number} 구조 보존률 (0-100)
*/
calculateStructurePreservation(original, compressed) {
const originalHeaders = (original.match(/^#{1,6}\s+.+$/gm) || []).length;
const compressedHeaders = (compressed.match(/^#{1,6}\s+.+$/gm) || []).length;
const originalLists = (original.match(/^\s*[-*+]\s+/gm) || []).length;
const compressedLists = (compressed.match(/^\s*[-*+]\s+/gm) || []).length;
const originalCodeBlocks = (original.match(/```[\s\S]*?```/g) || []).length;
const compressedCodeBlocks = (compressed.match(/```[\s\S]*?```/g) || []).length;
const headerPreservation = originalHeaders > 0 ? (compressedHeaders / originalHeaders) * 100 : 100;
const listPreservation = originalLists > 0 ? (compressedLists / originalLists) * 100 : 100;
const codePreservation = originalCodeBlocks > 0 ? (compressedCodeBlocks / originalCodeBlocks) * 100 : 100;
return (headerPreservation + listPreservation + codePreservation) / 3;
}
/**
* 의미 유사도를 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @returns {number} 의미 유사도 (0-100)
*/
calculateSemanticSimilarity(original, compressed) {
// 단순한 어휘 기반 유사도 계산
const originalWords = new Set(original.toLowerCase().split(/\s+/));
const compressedWords = new Set(compressed.toLowerCase().split(/\s+/));
const intersection = new Set([...originalWords].filter(word => compressedWords.has(word)));
const union = new Set([...originalWords, ...compressedWords]);
return union.size > 0 ? (intersection.size / union.size) * 100 : 0;
}
/**
* 정보 손실률을 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @returns {number} 정보 손실률 (0-100)
*/
calculateInformationLoss(original, compressed) {
const preservation = this.calculateInformationPreservation(original, compressed);
return 100 - preservation;
}
/**
* 가독성 점수를 계산합니다
* @param {string} text - 텍스트
* @returns {number} 가독성 점수 (0-100)
*/
calculateReadabilityScore(text) {
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
const words = text.split(/\s+/).filter(w => w.trim().length > 0);
if (sentences.length === 0 || words.length === 0) return 0;
const avgWordsPerSentence = words.length / sentences.length;
const avgCharsPerWord = words.reduce((sum, word) => sum + word.length, 0) / words.length;
// 단순한 가독성 점수 계산
let score = 100;
// 문장 길이 페널티
if (avgWordsPerSentence > 20) score -= 20;
else if (avgWordsPerSentence > 15) score -= 10;
// 단어 길이 페널티
if (avgCharsPerWord > 7) score -= 20;
else if (avgCharsPerWord > 5) score -= 10;
return Math.max(0, Math.min(100, score));
}
/**
* 일관성 점수를 계산합니다
* @param {string} text - 텍스트
* @returns {number} 일관성 점수 (0-100)
*/
calculateCoherenceScore(text) {
// 단순한 일관성 점수 계산
const lines = text.split('\n');
let coherenceScore = 100;
// 빈 줄 패턴 확인
const emptyLinePattern = /^\s*$/;
const consecutiveEmptyLines = lines.reduce((count, line, index) => {
if (emptyLinePattern.test(line) && index > 0 && emptyLinePattern.test(lines[index - 1])) {
return count + 1;
}
return count;
}, 0);
coherenceScore -= consecutiveEmptyLines * 2;
// 헤더 계층 구조 확인
const headers = lines.filter(line => line.match(/^#{1,6}\s+/));
const headerLevels = headers.map(header => header.match(/^(#{1,6})/)[1].length);
let previousLevel = 0;
for (const level of headerLevels) {
if (level - previousLevel > 1) {
coherenceScore -= 5; // 헤더 레벨 건너뛰기 페널티
}
previousLevel = level;
}
return Math.max(0, Math.min(100, coherenceScore));
}
/**
* 완성도 점수를 계산합니다
* @param {string} original - 원본 콘텐츠
* @param {string} compressed - 압축된 콘텐츠
* @returns {number} 완성도 점수 (0-100)
*/
calculateCompletenessScore(original, compressed) {
// 중요한 섹션이 유지되었는지 확인
const importantSections = [
'goal', 'objective', 'requirement', 'task', 'subtask', 'acceptance criteria',
'목표', '목적', '요구사항', '태스크', '하위', '승인 기준'
];
let foundSections = 0;
const lowerCompressed = compressed.toLowerCase();
for (const section of importantSections) {
if (lowerCompressed.includes(section.toLowerCase())) {
foundSections++;
}
}
const completenessRatio = foundSections / importantSections.length;
return completenessRatio * 100;
}
/**
* 압축 효율성을 계산합니다
* @param {number} compressionRatio - 압축률
* @param {number} informationPreservation - 정보 보존률
* @returns {number} 압축 효율성 (0-100)
*/
calculateCompressionEfficiency(compressionRatio, informationPreservation) {
// 압축률과 정보 보존률의 균형을 고려한 효율성 계산
const alpha = 0.6; // 압축률 가중치
const beta = 0.4; // 정보 보존률 가중치
return (alpha * compressionRatio) + (beta * informationPreservation);
}
/**
* 자원 사용률을 계산합니다
* @param {Object} metadata - 메타데이터
* @returns {number} 자원 사용률 (0-100)
*/
calculateResourceUtilization(metadata) {
// 메모리 사용량, 처리 시간 등을 고려한 자원 사용률
const memoryScore = metadata.memoryUsage ? Math.min(100, 100 - (metadata.memoryUsage / 1024 / 1024)) : 100;
const timeScore = metadata.processingTime ? Math.min(100, 100 - (metadata.processingTime / 1000)) : 100;
return (memoryScore + timeScore) / 2;
}
/**
* 비용 효율성을 계산합니다
* @param {number} originalTokens - 원본 토큰 수
* @param {number} compressedTokens - 압축된 토큰 수
* @param {number} processingTime - 처리 시간
* @returns {number} 비용 효율성 (0-100)
*/
calculateCostEffectiveness(originalTokens, compressedTokens, processingTime) {
const tokenSaved = originalTokens - compressedTokens;
const processingTimeInSeconds = processingTime / 1000;
if (processingTimeInSeconds === 0) return 0;
// 초당 절약된 토큰 수
const tokensPerSecond = tokenSaved / processingTimeInSeconds;
// 정규화된 비용 효율성 (최대 100)
return Math.min(100, tokensPerSecond);
}
/**
* 에너지 효율성을 계산합니다
* @param {number} originalTokens - 원본 토큰 수
* @param {number} processingTime - 처리 시간
* @returns {number} 에너지 효율성 (0-100)
*/
calculateEnergyEfficiency(originalTokens, processingTime) {
const processingTimeInSeconds = processingTime / 1000;
if (processingTimeInSeconds === 0) return 0;
// 초당 처리된 토큰 수
const tokensPerSecond = originalTokens / processingTimeInSeconds;
// 정규화된 에너지 효율성 (최대 100)
return Math.min(100, tokensPerSecond / 10);
}
/**
* 전체 품질 점수를 계산합니다
* @param {Object} quality - 품질 메트릭
* @returns {number} 전체 품질 점수 (0-100)
*/
calculateOverallQuality(quality) {
const weights = {
readability_score: 0.2,
structure_preservation: 0.25,
semantic_similarity: 0.25,
coherence_score: 0.15,
completeness_score: 0.15
};
let totalScore = 0;
let totalWeight = 0;
for (const [metric, weight] of Object.entries(weights)) {
if (quality[metric] !== undefined) {
totalScore += quality[metric] * weight;
totalWeight += weight;
}
}
return totalWeight > 0 ? totalScore / totalWeight : 0;
}
/**
* 파이프라인 성능을 분석합니다
* @param {Array<Object>} stages - 파이프라인 스테이지
* @returns {Object} 파이프라인 성능 분석
*/
analyzePipelinePerformance(stages) {
const analysis = {
total_stages: stages.length,
total_duration: stages.reduce((sum, stage) => sum + stage.duration, 0),
average_duration: 0,
slowest_stage: null,
fastest_stage: null,
stage_breakdown: []
};
if (stages.length > 0) {
analysis.average_duration = analysis.total_duration / stages.length;
// 가장 느린/빠른 스테이지 찾기
let slowest = stages[0];
let fastest = stages[0];
for (const stage of stages) {
if (stage.duration > slowest.duration) slowest = stage;
if (stage.duration < fastest.duration) fastest = stage;
}
analysis.slowest_stage = { name: slowest.name, duration: slowest.duration };
analysis.fastest_stage = { name: fastest.name, duration: fastest.duration };
// 스테이지별 세부 분석
analysis.stage_breakdown = stages.map(stage => ({
name: stage.name,
duration: stage.duration,
percentage: (stage.duration / analysis.total_duration) * 100,
success: stage.success
}));
}
return analysis;
}
/**
* 벤치마크 분석을 수행합니다
* @param {Object} metrics - 메트릭 데이터
* @returns {Object} 벤치마크 분석
*/
analyzeBenchmark(metrics) {
const benchmark = {
overall_grade: 'C',
overall_score: 0,
category_grades: {},
recommendations: []
};
let totalScore = 0;
let categoryCount = 0;
// 각 카테고리별 벤치마크 분석
for (const [category, categoryMetrics] of Object.entries(metrics)) {
const categoryGrade = this.gradeCategoryMetrics(categoryMetrics);
benchmark.category_grades[category] = categoryGrade;
totalScore += categoryGrade.score;
categoryCount++;
}
// 전체 점수 계산
benchmark.overall_score = categoryCount > 0 ? totalScore / categoryCount : 0;
benchmark.overall_grade = this.scoreToGrade(benchmark.overall_score);
// 권장사항 생성
benchmark.recommendations = this.generateRecommendations(metrics, benchmark);
return benchmark;
}
/**
* 카테고리 메트릭을 평가합니다
* @param {Object} categoryMetrics - 카테고리 메트릭
* @returns {Object} 카테고리 평가
*/
gradeCategoryMetrics(categoryMetrics) {
const grade = {
score: 0,
grade: 'F',
details: {}
};
let totalScore = 0;
let metricCount = 0;
for (const [metric, value] of Object.entries(categoryMetrics)) {
if (typeof value === 'number' && this.benchmarkThresholds[metric]) {
const metricGrade = this.gradeMetric(metric, value);
grade.details[metric] = metricGrade;
totalScore += metricGrade.score;
metricCount++;
}
}
if (metricCount > 0) {
grade.score = totalScore / metricCount;
grade.grade = this.scoreToGrade(grade.score);
}
return grade;
}
/**
* 개별 메트릭을 평가합니다
* @param {string} metric - 메트릭 이름
* @param {number} value - 메트릭 값
* @returns {Object} 메트릭 평가
*/
gradeMetric(metric, value) {
const thresholds = this.benchmarkThresholds[metric];
if (!thresholds) return { score: 50, grade: 'C' };
let score = 0;
if (value >= thresholds.excellent) {
score = 90;
} else if (value >= thresholds.good) {
score = 80;
} else if (value >= thresholds.acceptable) {
score = 70;
} else if (value >= thresholds.poor) {
score = 60;
} else {
score = 40;
}
return {
score,
grade: this.scoreToGrade(score),
value,
threshold: thresholds
};
}
/**
* 점수를 등급으로 변환합니다
* @param {number} score - 점수 (0-100)
* @returns {string} 등급 (A-F)
*/
scoreToGrade(score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
if (score >= 60) return 'D';
return 'F';
}
/**
* 분석 결과를 생성합니다
* @param {Object} metrics - 메트릭 데이터
* @param {Object} benchmark - 벤치마크 분석
* @returns {Object} 분석 결과
*/
generateAnalysis(metrics, benchmark) {
return {
summary: this.generateSummary(metrics),
strengths: this.identifyStrengths(metrics, benchmark),
weaknesses: this.identifyWeaknesses(metrics, benchmark),
recommendations: this.generateRecommendations(metrics, benchmark),
trend_analysis: this.analyzeTrends(),
comparative_analysis: this.generateComparativeAnalysis(metrics)
};
}
/**
* 요약을 생성합니다
* @param {Object} metrics - 메트릭 데이터
* @returns {string} 요약
*/
generateSummary(metrics) {
const compressionRatio = metrics.compression?.token_reduction_percentage || 0;
const informationPreservation = metrics.compression?.information_preservation || 0;
const processingTime = metrics.performance?.compression_time || 0;
return `압축률 ${compressionRatio.toFixed(1)}%로 ${informationPreservation.toFixed(1)}%의 정보를 보존하며 ${processingTime}ms 내에 처리되었습니다.`;
}
/**
* 강점을 식별합니다
* @param {Object} metrics - 메트릭 데이터
* @param {Object} benchmark - 벤치마크 분석
* @returns {Array<string>} 강점 목록
*/
identifyStrengths(metrics, benchmark) {
const strengths = [];
for (const [category, grade] of Object.entries(benchmark.category_grades)) {
if (grade.score >= 80) {
strengths.push(`${category} 카테고리에서 우수한 성능을 보였습니다 (${grade.grade}등급)`);
}
}
return strengths;
}
/**
* 약점을 식별합니다
* @param {Object} metrics - 메트릭 데이터
* @param {Object} benchmark - 벤치마크 분석
* @returns {Array<string>} 약점 목록
*/
identifyWeaknesses(metrics, benchmark) {
const weaknesses = [];
for (const [category, grade] of Object.entries(benchmark.category_grades)) {
if (grade.score < 60) {
weaknesses.push(`${category} 카테고리에서 개선이 필요합니다 (${grade.grade}등급)`);
}
}
return weaknesses;
}
/**
* 권장사항을 생성합니다
* @param {Object} metrics - 메트릭 데이터
* @param {Object} benchmark - 벤치마크 분석
* @returns {Array<string>} 권장사항 목록
*/
generateRecommendations(metrics, benchmark) {
const recommendations = [];
// 압축률 기반 권장사항
const compressionRatio = metrics.compression?.token_reduction_percentage || 0;
if (compressionRatio < 30) {
recommendations.push('압축률을 높이기 위해 더 적극적인 압축 전략을 고려하세요.');
} else if (compressionRatio > 80) {
recommendations.push('압축률이 너무 높습니다. 정보 손실을 방지하기 위해 덜 적극적인 압축을 고려하세요.');
}
// 성능 기반 권장사항
const processingTime = metrics.performance?.compression_time || 0;
if (processingTime > 5000) {
recommendations.push('처리 시간이 오래 걸립니다. 성능 최적화를 고려하세요.');
}
// 품질 기반 권장사항
const informationPreservation = metrics.compression?.information_preservation || 0;
if (informationPreservation < 70) {
recommendations.push('정보 보존률이 낮습니다. 중요한 정보를 더 잘 보존하는 방법을 고려하세요.');
}
return recommendations;
}
/**
* 트렌드를 분석합니다
* @returns {Object} 트렌드 분석
*/
analyzeTrends() {
if (this.measurementHistory.length < 2) {
return { message: '트렌드 분석을 위한 충분한 데이터가 없습니다.' };
}
const recent = this.measurementHistory.slice(-5);
const trends = {};
// 압축률 트렌드
const compressionRatios = recent.map(m => m.metrics.compression?.token_reduction_percentage || 0);
trends.compression_ratio = this.calculateTrend(compressionRatios);
// 처리 시간 트렌드
const processingTimes = recent.map(m => m.metrics.performance?.compression_time || 0);
trends.processing_time = this.calculateTrend(processingTimes);
// 품질 트렌드
const qualityScores = recent.map(m => m.metrics.quality?.overall_quality || 0);
trends.quality = this.calculateTrend(qualityScores);
return trends;
}
/**
* 트렌드를 계산합니다
* @param {Array<number>} values - 값 배열
* @returns {Object} 트렌드 정보
*/
calculateTrend(values) {
if (values.length < 2) return { trend: 'stable', change: 0 };
const first = values[0];
const last = values[values.length - 1];
const change = ((last - first) / first) * 100;
let trend = 'stable';
if (change > 5) trend = 'increasing';
else if (change < -5) trend = 'decreasing';
return { trend, change: change.toFixed(1) };
}
/**
* 비교 분석을 생성합니다
* @param {Object} metrics - 메트릭 데이터
* @returns {Object} 비교 분석
*/
generateComparativeAnalysis(metrics) {
const analysis = {
vs_previous: null,
vs_average: null,
vs_best: null
};
if (this.measurementHistory.length > 1) {
const current = metrics;
const previous = this.measurementHistory[this.measurementHistory.length - 2].metrics;
analysis.vs_previous = {
compression_ratio_change: this.calculateChange(
previous.compression?.token_reduction_percentage || 0,
current.compression?.token_reduction_percentage || 0
),
quality_change: this.calculateChange(
previous.quality?.overall_quality || 0,
current.quality?.overall_quality || 0
),
performance_change: this.calculateChange(
previous.performance?.compression_time || 0,
current.performance?.compression_time || 0
)
};
}
return analysis;
}
/**
* 변화량을 계산합니다
* @param {number} previous - 이전 값
* @param {number} current - 현재 값
* @returns {Object} 변화량 정보
*/
calculateChange(previous, current) {
const change = current - previous;
const changePercentage = previous > 0 ? (change / previous) * 100 : 0;
return {
absolute: change,
percentage: changePercentage.toFixed(1),
direction: change > 0 ? 'increase' : change < 0 ? 'decrease' : 'stable'
};
}
/**
* 측정 ID를 생성합니다
* @returns {string} 측정 ID
*/
generateMeasurementId() {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 9);
return `metric_${timestamp}_${random}`;
}
/**
* 측정 결과를 내보냅니다
* @param {string} measurementId - 측정 ID
* @param {string} format - 내보내기 형식 ('json', 'csv', 'report')
* @returns {string} 내보내기 결과
*/
exportMeasurement(measurementId, format = 'json') {
const measurement = this.measurements.get(measurementId);
if (!measurement) {
throw new Error(`측정 결과를 찾을 수 없습니다: ${measurementId}`);
}
switch (format) {
case 'json':
return JSON.stringify(measurement, null, 2);
case 'csv':
return this.convertToCSV(measurement);
case 'report':
return this.generateReport(measurement);
default:
throw new Error(`지원되지 않는 형식: ${format}`);
}
}
/**
* CSV 형식으로 변환합니다
* @param {Object} measurement - 측정 결과
* @returns {string} CSV 문자열
*/
convertToCSV(measurement) {
// 플랫한 구조로 변환
const flatData = this.flattenObject(measurement.metrics);
const headers = Object.keys(flatData);
const values = Object.values(flatData);
return [headers.join(','), values.join(',')].join('\n');
}
/**
* 객체를 평면화합니다
* @param {Object} obj - 객체
* @param {string} prefix - 접두사
* @returns {Object} 평면화된 객체
*/
flattenObject(obj, prefix = '') {
const flattened = {};
for (const [key, value] of Object.entries(obj)) {
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
Object.assign(flattened, this.flattenObject(value, newKey));
} else {
flattened[newKey] = value;
}
}
return flattened;
}
/**
* 보고서를 생성합니다
* @param {Object} measurement - 측정 결과
* @returns {string} 보고서
*/
generateReport(measurement) {
const report = [];
report.push('# 압축 성능 보고서');
report.push(`측정 ID: ${measurement.id}`);
report.push(`측정 시간: ${new Date(measurement.timestamp).toLocaleString()}`);
report.push('');
// 요약
report.push('## 요약');
report.push(measurement.analysis.summary);
report.push('');
// 벤치마크 결과
report.push('## 벤치마크 결과');
report.push(`전체 등급: ${measurement.benchmark.overall_grade} (${measurement.benchmark.overall_score.toFixed(1)}점)`);
report.push('');
// 강점과 약점
if (measurement.analysis.strengths.length > 0) {
report.push('## 강점');
measurement.analysis.strengths.forEach(strength => {
report.push(`- ${strength}`);
});
report.push('');
}
if (measurement.analysis.weaknesses.length > 0) {
report.push('## 약점');
measurement.analysis.weaknesses.forEach(weakness => {
report.push(`- ${weakness}`);
});
report.push('');
}
// 권장사항
if (measurement.analysis.recommendations.length > 0) {
report.push('## 권장사항');
measurement.analysis.recommendations.forEach(rec => {
report.push(`- ${rec}`);
});
report.push('');
}
return report.join('\n');
}
/**
* 리소스를 정리합니다
*/
cleanup() {
if (this.tokenCounter) {
this.tokenCounter.cleanup();
}
this.measurements.clear();
this.benchmarks.clear();
this.measurementHistory.length = 0;
}
}
export default CompressionMetrics;