UNPKG

bowling-analysis-system

Version:

A comprehensive system for analyzing bowling techniques using video processing and metrics calculation

215 lines (185 loc) 6.85 kB
/** * Report Generator Processor * * Generates reports from metrics and analysis data * @module processors/ReportGeneratorProcessor */ const { BaseProcessor } = require('../core/BaseProcessor'); const { defaultFileService } = require('../utils/FileService'); /** * @class ReportGeneratorProcessor * @description Processor for generating reports from metrics and analysis data * @extends BaseProcessor */ class ReportGeneratorProcessor extends BaseProcessor { /** * Create a new report generator processor * @param {Object} config - Processor configuration */ constructor(config = {}) { super('reportGenerator', config); this.formatters = new Map(); // Register default formatters this.registerFormatter('html', this._formatHtml.bind(this)); this.registerFormatter('md', this._formatMarkdown.bind(this)); this.registerFormatter('json', this._formatJson.bind(this)); } /** * Register a report formatter * @param {string} format - Format name * @param {Function} formatter - Formatter function * @returns {ReportGeneratorProcessor} Processor instance for chaining */ registerFormatter(format, formatter) { this.formatters.set(format, formatter); return this; } /** * Process input by generating a report * @param {Object} input - Input metrics and analysis data * @param {Object} context - Processing context * @returns {Promise<string>} Generated report * @protected */ async _process(input, context) { const format = this.config.format || 'html'; const template = this.config.template; // Get formatter const formatter = this.formatters.get(format); if (!formatter) { throw new Error(`Unknown report format: ${format}`); } // Load custom template if specified let customTemplate = null; if (template) { try { customTemplate = await defaultFileService.readFile(template, { encoding: 'utf8' }); } catch (error) { throw new Error(`Failed to load template ${template}: ${error.message}`); } } // Apply formatter return formatter(input, { template: customTemplate, ...context }); } /** * Format report as HTML * @param {Object} input - Input data * @param {Object} options - Formatting options * @returns {string} HTML report * @private */ _formatHtml(input, options) { const { template } = options; // Use custom template if provided if (template) { // Placeholder for template processing logic return template.replace('{{content}}', JSON.stringify(input, null, 2)); } // Generate default HTML report return `<!DOCTYPE html> <html> <head> <title>Bowling Analysis Report</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; } h1 { color: #333; } .section { margin-bottom: 20px; } .metric { margin-bottom: 10px; } .recommendation { background-color: #f0f0f0; padding: 10px; margin-bottom: 10px; border-left: 4px solid #007bff; } .high { border-left-color: #dc3545; } .medium { border-left-color: #fd7e14; } .low { border-left-color: #28a745; } </style> </head> <body> <h1>Bowling Analysis Report</h1> <div class="section"> <h2>Summary</h2> <p>Generated on ${new Date().toLocaleString()}</p> <p>Analysis contains ${input.analysis?.insights?.length || 0} insights and ${input.analysis?.recommendations?.length || 0} recommendations.</p> </div> <div class="section"> <h2>Recommendations</h2> ${input.analysis?.recommendations?.map(rec => ` <div class="recommendation ${rec.priority === 1 ? 'high' : rec.priority === 2 ? 'medium' : 'low'}"> <h3>${rec.recommendation}</h3> <p><strong>Category:</strong> ${rec.category}</p> <p><strong>Priority:</strong> ${rec.priority}</p> <p><strong>Suggested Drills:</strong> ${rec.drills?.join(', ') || 'None'}</p> </div> `).join('') || '<p>No recommendations available.</p>'} </div> <div class="section"> <h2>Insights</h2> ${input.analysis?.insights?.map(insight => ` <div class="metric"> <h3>${insight.aspect}</h3> <p><strong>Finding:</strong> ${insight.finding}</p> <p><strong>Category:</strong> ${insight.category}</p> <p><strong>Impact:</strong> ${insight.impact}</p> <p><strong>Confidence:</strong> ${insight.confidence ? (insight.confidence * 100).toFixed(1) : 'N/A'}%</p> </div> `).join('') || '<p>No insights available.</p>'} </div> </body> </html>`; } /** * Format report as Markdown * @param {Object} input - Input data * @param {Object} options - Formatting options * @returns {string} Markdown report * @private */ _formatMarkdown(input, options) { const { template } = options; // Use custom template if provided if (template) { // Placeholder for template processing logic return template.replace('{{content}}', JSON.stringify(input, null, 2)); } // Generate default Markdown report return `# Bowling Analysis Report Generated on ${new Date().toLocaleString()} ## Summary Analysis contains ${input.analysis?.insights?.length || 0} insights and ${input.analysis?.recommendations?.length || 0} recommendations. ## Recommendations ${input.analysis?.recommendations?.map(rec => ` ### ${rec.recommendation} - **Category:** ${rec.category} - **Priority:** ${rec.priority} - **Suggested Drills:** ${rec.drills?.join(', ') || 'None'} `).join('') || 'No recommendations available.'} ## Insights ${input.analysis?.insights?.map(insight => ` ### ${insight.aspect} - **Finding:** ${insight.finding} - **Category:** ${insight.category} - **Impact:** ${insight.impact} - **Confidence:** ${insight.confidence ? (insight.confidence * 100).toFixed(1) : 'N/A'}% `).join('') || 'No insights available.'} `; } /** * Format report as JSON * @param {Object} input - Input data * @param {Object} options - Formatting options * @returns {string} JSON report * @private */ _formatJson(input, options) { // Create report structure const report = { generated: new Date().toISOString(), summary: { insightCount: input.analysis?.insights?.length || 0, recommendationCount: input.analysis?.recommendations?.length || 0 }, recommendations: input.analysis?.recommendations || [], insights: input.analysis?.insights || [], metrics: input.metrics || {} }; return JSON.stringify(report, null, 2); } } module.exports = ReportGeneratorProcessor;