@himorishige/noren-devtools
Version:
Development and testing tools for Noren PII detection library
164 lines (163 loc) ⢠6.24 kB
JavaScript
/**
* Common report generation utilities for noren-devtools
* Unified formatting and output generation to eliminate duplication
*/
import { formatDuration, formatNumber, formatPercentage } from './stats-common.js';
// ===== Formatters =====
export function formatPerformanceSection(name, metrics, confidence) {
const lines = [
`ā” Avg Duration: ${formatDuration(metrics.duration)}`,
`š Throughput: ${formatNumber(metrics.throughput)}K chars/sec`,
`š¾ Memory Efficiency: ${formatNumber(metrics.memoryUsage, 3)} MB/KB`,
];
if (metrics.errorRate !== undefined) {
lines.push(`ā Error Rate: ${formatPercentage(metrics.errorRate)}`);
}
if (confidence) {
lines.push(`š Confidence: [${formatDuration(confidence.lower)}, ${formatDuration(confidence.upper)}]`);
}
return {
title: name,
content: lines.map((line) => ` ${line}`).join('\n'),
metadata: { type: 'performance', metrics },
};
}
export function formatAccuracySection(name, metrics) {
const lines = [
`šÆ F1 Score: ${formatNumber(metrics.f1Score, 3)}`,
`ā
Precision: ${formatNumber(metrics.precision, 3)}, Recall: ${formatNumber(metrics.recall, 3)}`,
];
if (metrics.truePositives || metrics.falsePositives || metrics.falseNegatives) {
lines.push(`š TP: ${metrics.truePositives}, FP: ${metrics.falsePositives}, FN: ${metrics.falseNegatives}`);
}
return {
title: name,
content: lines.map((line) => ` ${line}`).join('\n'),
metadata: { type: 'accuracy', metrics },
};
}
export function formatSummaryStatsSection(name, stats) {
const lines = [
`š Count: ${stats.count}`,
`š Mean: ${formatNumber(stats.mean)} (±${formatNumber(stats.standardDeviation)})`,
`š Range: ${formatNumber(stats.min)} - ${formatNumber(stats.max)}`,
`š Percentiles: P25=${formatNumber(stats.p25)}, P75=${formatNumber(stats.p75)}, P95=${formatNumber(stats.p95)}`,
];
return {
title: name,
content: lines.map((line) => ` ${line}`).join('\n'),
metadata: { type: 'summary', stats },
};
}
// ===== Report Builders =====
export class ReportBuilder {
sections = [];
title(text) {
this.sections.push({
title: 'header',
content: this.formatHeader(text),
});
return this;
}
section(section) {
this.sections.push(section);
return this;
}
performance(name, metrics, confidence) {
return this.section(formatPerformanceSection(name, metrics, confidence));
}
accuracy(name, metrics) {
return this.section(formatAccuracySection(name, metrics));
}
summaryStats(name, stats) {
return this.section(formatSummaryStatsSection(name, stats));
}
comparison(winner, improvement, significance) {
const lines = [
`š Winner: ${winner}`,
`š Improvement: ${formatPercentage(improvement / 100)}`,
`š Significance: ${formatPercentage(significance / 100)}`,
];
this.sections.push({
title: 'comparison',
content: lines.map((line) => ` ${line}`).join('\n'),
metadata: { winner, improvement, significance },
});
return this;
}
summary(testName, sampleCount, duration, completed) {
const lines = [
`Test Name: ${testName}`,
`Total Samples: ${sampleCount}`,
`Duration: ${formatDuration(duration)}`,
`Completed: ${completed}`,
];
this.sections.push({
title: 'summary',
content: lines.map((line) => ` ${line}`).join('\n'),
metadata: { testName, sampleCount, duration, completed },
});
return this;
}
text(content) {
this.sections.push({
title: 'text',
content,
});
return this;
}
build() {
return this.sections.map((section) => section.content).join('\n');
}
formatHeader(text) {
const separator = '='.repeat(60);
return `\n${separator}\nšÆ ${text}\n${separator}`;
}
}
// ===== Predefined Templates =====
export function createBenchmarkReport(name, results) {
const builder = new ReportBuilder().title(`Benchmark Results: ${name}`);
results.forEach((result) => {
builder.performance(result.name, result.metrics, result.confidence);
});
return builder.build();
}
export function createABTestReport(testName, variantA, variantB, winner, improvement, significance, sampleCount, duration) {
const builder = new ReportBuilder()
.title(`A/B Test Results: ${testName}`)
.text('\nš Variant Performance:\n');
builder.performance(variantA.name, variantA.performance);
if (variantA.accuracy) {
builder.accuracy(variantA.name, variantA.accuracy);
}
builder.text(''); // Add spacing
builder.performance(variantB.name, variantB.performance);
if (variantB.accuracy) {
builder.accuracy(variantB.name, variantB.accuracy);
}
builder
.text('')
.comparison(winner, improvement, significance)
.text('\nš Test Summary:')
.summary(testName, sampleCount, duration, new Date().toISOString());
return builder.build();
}
export function createEvaluationReport(name, accuracy, performanceStats) {
const builder = new ReportBuilder()
.title(`Evaluation Results: ${name}`)
.accuracy('Detection Accuracy', accuracy);
if (performanceStats) {
builder.summaryStats('Performance Statistics', performanceStats);
}
return builder.build();
}
// ===== Console Output Utilities =====
export function printReport(report, options = {}) {
const timestamp = options.timestamp ? `[${new Date().toISOString()}] ` : '';
const prefix = options.prefix ? `${options.prefix} ` : '';
console.log(`${timestamp}${prefix}${report}`);
}
export function printProgress(current, total, operation = 'Processing') {
const percentage = ((current / total) * 100).toFixed(1);
console.log(` Progress: ${percentage}% (${current}/${total}) - ${operation}`);
}