src/report-generator.lua
1#!/usr/bin/env lua
2
3-- Report Generator for Similarity Validation Results
4-- Creates comprehensive reports in multiple formats (HTML, JSON, Markdown)
5
6package.path = package.path .. ';./?.lua;./libs/?.lua'
7
8local utils = require("libs.utils")
9local json = require("libs.json")
10
11local DIR = "/mnt/mtwo/programming/ai-stuff/neocities-modernization"
12
13local ReportGenerator = {}
14ReportGenerator.__index = ReportGenerator
15
16-- {{{ function ReportGenerator:new
17function ReportGenerator:new(config)
18 config = config or {}
19 local obj = {
20 config = config,
21 format = config.format or "html", -- html, json, markdown
22 include_details = config.include_details ~= false, -- default true
23 include_recommendations = config.include_recommendations ~= false, -- default true
24 max_discrepancy_samples = config.max_discrepancy_samples or 20
25 }
26
27 setmetatable(obj, ReportGenerator)
28 return obj
29end
30-- }}}
31
32-- {{{ function ReportGenerator:generate_validation_report
33function ReportGenerator:generate_validation_report(validation_result, output_file)
34 if self.format == "html" then
35 return self:generate_html_report(validation_result, output_file)
36 elseif self.format == "markdown" then
37 return self:generate_markdown_report(validation_result, output_file)
38 elseif self.format == "json" then
39 return self:generate_json_report(validation_result, output_file)
40 else
41 error("Unsupported report format: " .. self.format)
42 end
43end
44-- }}}
45
46-- {{{ function ReportGenerator:substitute_template_vars
47function ReportGenerator:substitute_template_vars(template, result)
48 local stats = result.statistics or {}
49 local perf = result.performance or {}
50
51 local accuracy_percent = math.floor((stats.accuracy_rate or 0) * 100 * 10) / 10
52 local comparisons_per_sec = math.floor((perf.comparisons_per_second or 0) * 10) / 10
53
54 local substitutions = {
55 ["{ALGORITHM}"] = result.algorithm or "unknown",
56 ["{TIMESTAMP}"] = result.timestamp or os.date("%Y-%m-%d %H:%M:%S"),
57 ["{DURATION}"] = tostring(result.duration_seconds or 0),
58 ["{TOTAL_COMPARISONS}"] = tostring(stats.total_comparisons or 0),
59 ["{ACCURACY_PERCENT}"] = string.format("%.1f", accuracy_percent),
60 ["{INACCURATE_SCORES}"] = tostring(stats.inaccurate_scores or 0),
61 ["{COMPARISONS_PER_SEC}"] = string.format("%.1f", comparisons_per_sec),
62 ["{PERFORMANCE_DETAILS}"] = self:generate_performance_details(result)
63 }
64
65 for placeholder, value in pairs(substitutions) do
66 -- Escape the value to handle % characters safely
67 local safe_value = tostring(value):gsub("%%", "%%%%")
68 template = template:gsub(placeholder:gsub("([%[%]%(%)%.%+%-%*%?%^%$])", "%%%1"), safe_value)
69 end
70
71 return template
72end
73-- }}}
74
75-- {{{ function ReportGenerator:generate_performance_details
76function ReportGenerator:generate_performance_details(result)
77 local details = {}
78 local stats = result.statistics or {}
79 local perf = result.performance or {}
80 local errors = result.errors or {}
81 local discrepancies = result.discrepancies or {}
82
83 table.insert(details, string.format("Total Comparisons: %d", stats.total_comparisons or 0))
84 table.insert(details, string.format("Accurate Scores: %d", stats.accurate_scores or 0))
85 table.insert(details, string.format("Inaccurate Scores: %d", stats.inaccurate_scores or 0))
86 table.insert(details, string.format("Missing Embeddings: %d", stats.missing_embeddings or 0))
87 table.insert(details, string.format("Calculation Errors: %d", errors.count or 0))
88 table.insert(details, string.format(""))
89 table.insert(details, string.format("Processing Time: %d seconds", result.duration_seconds or 0))
90 table.insert(details, string.format("Comparisons/Second: %.1f", perf.comparisons_per_second or 0))
91 table.insert(details, string.format("Avg Time per Compare: %.2f ms", perf.avg_comparison_time_ms or 0))
92 table.insert(details, string.format(""))
93 table.insert(details, string.format("Tolerance Used: %.6f", stats.tolerance or 0))
94
95 if discrepancies.max_difference then
96 table.insert(details, string.format("Max Discrepancy: %.6f", discrepancies.max_difference))
97 end
98
99 if discrepancies.avg_difference then
100 table.insert(details, string.format("Avg Discrepancy: %.6f", discrepancies.avg_difference))
101 end
102
103 return table.concat(details, "\n")
104end
105-- }}}
106
107-- {{{ function ReportGenerator:generate_discrepancies_section
108function ReportGenerator:generate_discrepancies_section(result)
109 local discrepancies = result.discrepancies or {}
110 if not discrepancies.samples or #discrepancies.samples == 0 then
111 return ""
112 end
113
114 local section = [[
115 <section class="discrepancies-section">
116 <h2>⚠️ Worst Discrepancies</h2>
117 <p>Showing the top discrepancies found during validation:</p>
118 <table class="discrepancy-table">
119 <thead>
120 <tr>
121 <th>Poem A</th>
122 <th>Poem B</th>
123 <th>Stored Score</th>
124 <th>Calculated Score</th>
125 <th>Difference</th>
126 <th>Relative Error</th>
127 </tr>
128 </thead>
129 <tbody>
130]]
131
132 local samples = discrepancies.samples
133 for i = 1, math.min(self.max_discrepancy_samples, #samples) do
134 local disc = samples[i]
135 local error_class = "error-low"
136 if disc.difference > 0.1 then
137 error_class = "error-high"
138 elseif disc.difference > 0.05 then
139 error_class = "error-medium"
140 end
141
142 local relative_error_percent = math.floor((disc.relative_error or 0) * 100 * 10) / 10
143
144 section = section .. string.format([[
145 <tr>
146 <td>%d</td>
147 <td>%d</td>
148 <td>%.6f</td>
149 <td>%.6f</td>
150 <td class="%s">%.6f</td>
151 <td class="%s">%.1f%%</td>
152 </tr>]],
153 disc.poem_a, disc.poem_b, disc.stored_score, disc.calculated_score,
154 error_class, disc.difference, error_class, relative_error_percent)
155 end
156
157 section = section .. [[
158 </tbody>
159 </table>
160 </section>
161]]
162
163 return section
164end
165-- }}}
166
167-- {{{ function ReportGenerator:generate_recommendations_section
168function ReportGenerator:generate_recommendations_section(result)
169 if not result.recommendations or #result.recommendations == 0 then
170 return ""
171 end
172
173 local section = [[
174 <section class="recommendations-section">
175 <h2>💡 Recommendations</h2>
176]]
177
178 for _, recommendation in ipairs(result.recommendations) do
179 section = section .. string.format([[
180 <div class="recommendation-box">
181 <p>%s</p>
182 </div>]], recommendation)
183 end
184
185 section = section .. [[
186 </section>
187]]
188
189 return section
190end
191-- }}}
192
193-- {{{ function ReportGenerator:generate_html_report
194function ReportGenerator:generate_html_report(result, output_file)
195 local html_template = [[<!DOCTYPE html>
196<html lang="en">
197<head>
198 <meta charset="UTF-8">
199 <meta name="viewport" content="width=device-width, initial-scale=1.0">
200 <title>Similarity Validation Report - {ALGORITHM}</title>
201 <style>
202 body {
203 font-family: Georgia, serif;
204 line-height: 1.6;
205 max-width: 1200px;
206 margin: 0 auto;
207 padding: 1rem;
208 color: #333;
209 background-color: #fafafa;
210 }
211
212 .report-header {
213 background: linear-gradient(135deg, #f0f8ff, #e6f3ff);
214 padding: 2rem;
215 border-radius: 8px;
216 margin-bottom: 2rem;
217 border: 1px solid #b0d4f1;
218 }
219
220 .report-header h1 {
221 margin: 0 0 1rem 0;
222 color: #1a365d;
223 }
224
225 .stats-grid {
226 display: grid;
227 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
228 gap: 1rem;
229 margin: 2rem 0;
230 }
231
232 .stat-card {
233 background: white;
234 border: 1px solid #e2e8f0;
235 border-radius: 6px;
236 padding: 1.5rem;
237 text-align: center;
238 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
239 }
240
241 .stat-value {
242 font-size: 2rem;
243 font-weight: bold;
244 color: #2c5282;
245 margin-bottom: 0.5rem;
246 }
247
248 .stat-label {
249 color: #4a5568;
250 font-size: 0.9rem;
251 }
252
253 .accuracy-meter {
254 background: #e2e8f0;
255 border-radius: 10px;
256 height: 20px;
257 margin: 1rem 0;
258 overflow: hidden;
259 }
260
261 .accuracy-fill {
262 height: 100%;
263 background: linear-gradient(90deg, #48bb78, #38a169);
264 transition: width 0.3s ease;
265 }
266
267 .discrepancy-table {
268 width: 100%;
269 border-collapse: collapse;
270 margin: 1rem 0;
271 background: white;
272 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
273 }
274
275 .discrepancy-table th,
276 .discrepancy-table td {
277 border: 1px solid #e2e8f0;
278 padding: 0.75rem;
279 text-align: left;
280 }
281
282 .discrepancy-table th {
283 background: #f7fafc;
284 font-weight: bold;
285 color: #2d3748;
286 }
287
288 .discrepancy-table tr:nth-child(even) {
289 background-color: #f8f9fa;
290 }
291
292 .error-high { color: #e53e3e; font-weight: bold; }
293 .error-medium { color: #dd6b20; font-weight: bold; }
294 .error-low { color: #38a169; }
295
296 .recommendation-box {
297 background: #fef5e7;
298 border-left: 4px solid #ed8936;
299 padding: 1rem;
300 margin: 1rem 0;
301 border-radius: 4px;
302 }
303
304 .performance-chart {
305 background: #f7fafc;
306 padding: 1rem;
307 border-radius: 6px;
308 font-family: monospace;
309 margin: 1rem 0;
310 white-space: pre-line;
311 border: 1px solid #e2e8f0;
312 }
313
314 section {
315 background: white;
316 margin: 2rem 0;
317 padding: 1.5rem;
318 border-radius: 8px;
319 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
320 }
321
322 section h2 {
323 margin-top: 0;
324 color: #2d3748;
325 border-bottom: 2px solid #e2e8f0;
326 padding-bottom: 0.5rem;
327 }
328
329 .overview-section {
330 border-top: 4px solid #4299e1;
331 }
332
333 .performance-section {
334 border-top: 4px solid #38a169;
335 }
336
337 .discrepancies-section {
338 border-top: 4px solid #ed8936;
339 }
340
341 .recommendations-section {
342 border-top: 4px solid #9f7aea;
343 }
344 </style>
345</head>
346<body bgcolor="#000000" text="#FFFFFF" link="#6699FF" vlink="#9966FF">
347 <div class="report-header">
348 <h1>🔍 Similarity Validation Report</h1>
349 <p><strong>Algorithm:</strong> {ALGORITHM}</p>
350 <p><strong>Generated:</strong> {TIMESTAMP}</p>
351 <p><strong>Duration:</strong> {DURATION} seconds</p>
352 </div>
353
354 <section class="overview-section">
355 <h2>📊 Validation Overview</h2>
356 <div class="stats-grid">
357 <div class="stat-card">
358 <div class="stat-value">{TOTAL_COMPARISONS}</div>
359 <div class="stat-label">Total Comparisons</div>
360 </div>
361 <div class="stat-card">
362 <div class="stat-value">{ACCURACY_PERCENT}%</div>
363 <div class="stat-label">Accuracy Rate</div>
364 </div>
365 <div class="stat-card">
366 <div class="stat-value">{INACCURATE_SCORES}</div>
367 <div class="stat-label">Discrepancies Found</div>
368 </div>
369 <div class="stat-card">
370 <div class="stat-value">{COMPARISONS_PER_SEC}</div>
371 <div class="stat-label">Comparisons/Second</div>
372 </div>
373 </div>
374
375 <div class="accuracy-meter">
376 <div class="accuracy-fill" style="width: {ACCURACY_PERCENT}%"></div>
377 </div>
378 </section>
379
380 <section class="performance-section">
381 <h2>⚡ Performance Metrics</h2>
382 <div class="performance-chart">{PERFORMANCE_DETAILS}</div>
383 </section>
384
385 {DISCREPANCIES_SECTION}
386
387 {RECOMMENDATIONS_SECTION}
388
389 <footer style="margin-top: 3rem; padding: 2rem; background: #f7fafc; border-radius: 6px; text-align: center;">
390 <p style="margin: 0; color: #4a5568;">
391 Report generated by Similarity Validation System • {TIMESTAMP}
392 </p>
393 </footer>
394</body>
395</html>]]
396
397 -- Substitute template variables
398 html_template = self:substitute_template_vars(html_template, result)
399
400 -- Generate discrepancies section if needed
401 local discrepancies = result.discrepancies or {}
402 if self.include_details and discrepancies.count and discrepancies.count > 0 then
403 local disc_section = self:generate_discrepancies_section(result)
404 -- Escape % characters in the replacement string
405 disc_section = disc_section:gsub("%%", "%%%%")
406 html_template = html_template:gsub("{DISCREPANCIES_SECTION}", disc_section)
407 else
408 html_template = html_template:gsub("{DISCREPANCIES_SECTION}", "")
409 end
410
411 -- Generate recommendations section
412 local recommendations = result.recommendations or {}
413 if self.include_recommendations and #recommendations > 0 then
414 local rec_section = self:generate_recommendations_section(result)
415 -- Escape % characters in the replacement string
416 rec_section = rec_section:gsub("%%", "%%%%")
417 html_template = html_template:gsub("{RECOMMENDATIONS_SECTION}", rec_section)
418 else
419 html_template = html_template:gsub("{RECOMMENDATIONS_SECTION}", "")
420 end
421
422 -- Write report
423 local success = utils.write_file(output_file, html_template)
424
425 if success then
426 print(string.format("HTML validation report generated: %s", output_file))
427 return output_file
428 else
429 error("Failed to write HTML report: " .. output_file)
430 end
431end
432-- }}}
433
434-- {{{ function ReportGenerator:generate_markdown_report
435function ReportGenerator:generate_markdown_report(result, output_file)
436 local markdown_content = {}
437
438 -- Header
439 table.insert(markdown_content, string.format("# 🔍 Similarity Validation Report"))
440 table.insert(markdown_content, "")
441 table.insert(markdown_content, string.format("**Algorithm:** %s", result.algorithm or "unknown"))
442 table.insert(markdown_content, string.format("**Generated:** %s", result.timestamp or os.date("%Y-%m-%d %H:%M:%S")))
443 table.insert(markdown_content, string.format("**Duration:** %d seconds", result.duration_seconds or 0))
444 table.insert(markdown_content, "")
445
446 -- Overview
447 table.insert(markdown_content, "## 📊 Validation Overview")
448 table.insert(markdown_content, "")
449 local accuracy_percent = math.floor((result.statistics.accuracy_rate or 0) * 100 * 10) / 10
450 table.insert(markdown_content, string.format("- **Total Comparisons:** %d", result.statistics.total_comparisons or 0))
451 table.insert(markdown_content, string.format("- **Accuracy Rate:** %.1f%%", accuracy_percent))
452 table.insert(markdown_content, string.format("- **Discrepancies Found:** %d", result.statistics.inaccurate_scores or 0))
453 table.insert(markdown_content, string.format("- **Comparisons/Second:** %.1f", result.performance.comparisons_per_second or 0))
454 table.insert(markdown_content, "")
455
456 -- Performance
457 table.insert(markdown_content, "## ⚡ Performance Metrics")
458 table.insert(markdown_content, "")
459 table.insert(markdown_content, "```")
460 table.insert(markdown_content, self:generate_performance_details(result))
461 table.insert(markdown_content, "```")
462 table.insert(markdown_content, "")
463
464 -- Discrepancies
465 local discrepancies = result.discrepancies or {}
466 if self.include_details and discrepancies.count and discrepancies.count > 0 and discrepancies.samples then
467 table.insert(markdown_content, "## ⚠️ Worst Discrepancies")
468 table.insert(markdown_content, "")
469 table.insert(markdown_content, "| Poem A | Poem B | Stored Score | Calculated Score | Difference | Relative Error |")
470 table.insert(markdown_content, "|--------|--------|--------------|------------------|------------|----------------|")
471
472 local samples = discrepancies.samples
473 for i = 1, math.min(self.max_discrepancy_samples, #samples) do
474 local disc = samples[i]
475 local relative_error_percent = math.floor((disc.relative_error or 0) * 100 * 10) / 10
476 table.insert(markdown_content, string.format("| %d | %d | %.6f | %.6f | %.6f | %.1f%% |",
477 disc.poem_a, disc.poem_b, disc.stored_score, disc.calculated_score,
478 disc.difference, relative_error_percent))
479 end
480 table.insert(markdown_content, "")
481 end
482
483 -- Recommendations
484 local recommendations = result.recommendations or {}
485 if self.include_recommendations and #recommendations > 0 then
486 table.insert(markdown_content, "## 💡 Recommendations")
487 table.insert(markdown_content, "")
488 for i, recommendation in ipairs(recommendations) do
489 table.insert(markdown_content, string.format("%d. %s", i, recommendation))
490 end
491 table.insert(markdown_content, "")
492 end
493
494 -- Footer
495 table.insert(markdown_content, "---")
496 table.insert(markdown_content, "")
497 table.insert(markdown_content, string.format("*Report generated by Similarity Validation System • %s*",
498 result.timestamp or os.date("%Y-%m-%d %H:%M:%S")))
499
500 local content = table.concat(markdown_content, "\n")
501 local success = utils.write_file(output_file, content)
502
503 if success then
504 print(string.format("Markdown validation report generated: %s", output_file))
505 return output_file
506 else
507 error("Failed to write Markdown report: " .. output_file)
508 end
509end
510-- }}}
511
512-- {{{ function ReportGenerator:generate_json_report
513function ReportGenerator:generate_json_report(result, output_file)
514 local success = utils.write_json_file(output_file, result)
515
516 if success then
517 print(string.format("JSON validation report generated: %s", output_file))
518 return output_file
519 else
520 error("Failed to write JSON report: " .. output_file)
521 end
522end
523-- }}}
524
525-- {{{ function ReportGenerator:generate_comparative_report
526function ReportGenerator:generate_comparative_report(validation_results, output_file)
527 -- validation_results is array of results from different algorithms
528
529 local comparison_data = {
530 timestamp = os.date("%Y-%m-%d %H:%M:%S"),
531 algorithms_compared = {},
532 performance_comparison = {},
533 accuracy_comparison = {},
534 recommendations = {}
535 }
536
537 -- Analyze each algorithm result
538 for _, result in ipairs(validation_results) do
539 local algorithm_summary = {
540 algorithm = result.algorithm,
541 accuracy_rate = result.statistics.accuracy_rate,
542 total_comparisons = result.statistics.total_comparisons,
543 comparisons_per_second = result.performance.comparisons_per_second,
544 max_discrepancy = result.discrepancies.max_difference or 0,
545 error_count = result.errors.count
546 }
547
548 table.insert(comparison_data.algorithms_compared, algorithm_summary)
549 end
550
551 -- Sort by accuracy rate
552 table.sort(comparison_data.algorithms_compared, function(a, b)
553 return a.accuracy_rate > b.accuracy_rate
554 end)
555
556 -- Generate performance comparison
557 local best_accuracy = comparison_data.algorithms_compared[1]
558 local fastest_algorithm = nil
559 local max_speed = 0
560
561 for _, algo in ipairs(comparison_data.algorithms_compared) do
562 if algo.comparisons_per_second > max_speed then
563 max_speed = algo.comparisons_per_second
564 fastest_algorithm = algo
565 end
566 end
567
568 comparison_data.performance_comparison = {
569 most_accurate = best_accuracy,
570 fastest = fastest_algorithm,
571 speed_vs_accuracy_tradeoff = self:analyze_speed_accuracy_tradeoff(comparison_data.algorithms_compared)
572 }
573
574 -- Generate comparative recommendations
575 comparison_data.recommendations = self:generate_comparative_recommendations(comparison_data)
576
577 if self.format == "html" then
578 return self:generate_comparative_html_report(comparison_data, output_file)
579 elseif self.format == "json" then
580 return self:generate_json_report(comparison_data, output_file)
581 else
582 return self:generate_comparative_markdown_report(comparison_data, output_file)
583 end
584end
585-- }}}
586
587-- {{{ function ReportGenerator:analyze_speed_accuracy_tradeoff
588function ReportGenerator:analyze_speed_accuracy_tradeoff(algorithms)
589 local analysis = {
590 high_accuracy_high_speed = {},
591 high_accuracy_low_speed = {},
592 low_accuracy_high_speed = {},
593 low_accuracy_low_speed = {}
594 }
595
596 -- Calculate median values for thresholds
597 local accuracies = {}
598 local speeds = {}
599
600 for _, algo in ipairs(algorithms) do
601 table.insert(accuracies, algo.accuracy_rate)
602 table.insert(speeds, algo.comparisons_per_second)
603 end
604
605 table.sort(accuracies)
606 table.sort(speeds)
607
608 local median_accuracy = accuracies[math.ceil(#accuracies / 2)]
609 local median_speed = speeds[math.ceil(#speeds / 2)]
610
611 -- Categorize algorithms
612 for _, algo in ipairs(algorithms) do
613 local high_acc = algo.accuracy_rate >= median_accuracy
614 local high_speed = algo.comparisons_per_second >= median_speed
615
616 if high_acc and high_speed then
617 table.insert(analysis.high_accuracy_high_speed, algo)
618 elseif high_acc and not high_speed then
619 table.insert(analysis.high_accuracy_low_speed, algo)
620 elseif not high_acc and high_speed then
621 table.insert(analysis.low_accuracy_high_speed, algo)
622 else
623 table.insert(analysis.low_accuracy_low_speed, algo)
624 end
625 end
626
627 return analysis
628end
629-- }}}
630
631-- {{{ function ReportGenerator:generate_comparative_recommendations
632function ReportGenerator:generate_comparative_recommendations(comparison_data)
633 local recommendations = {}
634
635 local algorithms = comparison_data.algorithms_compared
636 if #algorithms == 0 then
637 return recommendations
638 end
639
640 local best_accuracy = algorithms[1]
641 local fastest = comparison_data.performance_comparison.fastest
642
643 table.insert(recommendations, string.format("Most accurate algorithm: %s (%.1f%% accuracy)",
644 best_accuracy.algorithm, best_accuracy.accuracy_rate * 100))
645
646 if fastest then
647 table.insert(recommendations, string.format("Fastest algorithm: %s (%.1f comparisons/second)",
648 fastest.algorithm, fastest.comparisons_per_second))
649 end
650
651 -- Check for any algorithms with perfect accuracy
652 for _, algo in ipairs(algorithms) do
653 if algo.accuracy_rate >= 0.999 then
654 table.insert(recommendations, string.format("%s shows excellent accuracy (%.1f%%) - recommended for production use",
655 algo.algorithm, algo.accuracy_rate * 100))
656 elseif algo.accuracy_rate < 0.9 then
657 table.insert(recommendations, string.format("%s has low accuracy (%.1f%%) - investigate calculation differences",
658 algo.algorithm, algo.accuracy_rate * 100))
659 end
660 end
661
662 -- Performance recommendations
663 local tradeoff = comparison_data.performance_comparison.speed_vs_accuracy_tradeoff
664 if #tradeoff.high_accuracy_high_speed > 0 then
665 table.insert(recommendations, "Consider algorithms in the high-accuracy, high-speed category for optimal performance")
666 end
667
668 return recommendations
669end
670-- }}}
671
672-- {{{ function ReportGenerator:generate_comparative_markdown_report
673function ReportGenerator:generate_comparative_markdown_report(comparison_data, output_file)
674 local markdown_content = {}
675
676 -- Header
677 table.insert(markdown_content, "# 📊 Comparative Algorithm Validation Report")
678 table.insert(markdown_content, "")
679 table.insert(markdown_content, string.format("**Generated:** %s", comparison_data.timestamp))
680 table.insert(markdown_content, string.format("**Algorithms Compared:** %d", #comparison_data.algorithms_compared))
681 table.insert(markdown_content, "")
682
683 -- Algorithm comparison table
684 table.insert(markdown_content, "## 🏆 Algorithm Rankings")
685 table.insert(markdown_content, "")
686 table.insert(markdown_content, "| Rank | Algorithm | Accuracy Rate | Comparisons/Sec | Max Discrepancy | Errors |")
687 table.insert(markdown_content, "|------|-----------|---------------|-----------------|-----------------|--------|")
688
689 for i, algo in ipairs(comparison_data.algorithms_compared) do
690 table.insert(markdown_content, string.format("| %d | %s | %.1f%% | %.1f | %.6f | %d |",
691 i, algo.algorithm, algo.accuracy_rate * 100, algo.comparisons_per_second,
692 algo.max_discrepancy, algo.error_count))
693 end
694 table.insert(markdown_content, "")
695
696 -- Recommendations
697 if #comparison_data.recommendations > 0 then
698 table.insert(markdown_content, "## 💡 Recommendations")
699 table.insert(markdown_content, "")
700 for i, recommendation in ipairs(comparison_data.recommendations) do
701 table.insert(markdown_content, string.format("%d. %s", i, recommendation))
702 end
703 table.insert(markdown_content, "")
704 end
705
706 local content = table.concat(markdown_content, "\n")
707 local success = utils.write_file(output_file, content)
708
709 if success then
710 print(string.format("Comparative Markdown report generated: %s", output_file))
711 return output_file
712 else
713 error("Failed to write comparative Markdown report: " .. output_file)
714 end
715end
716-- }}}
717
718-- {{{ function create_report_generator
719local function create_report_generator(config)
720 return ReportGenerator:new(config)
721end
722-- }}}
723
724-- {{{ function generate_single_report
725local function generate_single_report(validation_result, output_file, format)
726 local generator = ReportGenerator:new({format = format or "html"})
727 return generator:generate_validation_report(validation_result, output_file)
728end
729-- }}}
730
731-- {{{ function generate_comparative_report
732local function generate_comparative_report(validation_results, output_file, format)
733 local generator = ReportGenerator:new({format = format or "html"})
734 return generator:generate_comparative_report(validation_results, output_file)
735end
736-- }}}
737
738return {
739 ReportGenerator = ReportGenerator,
740 create_report_generator = create_report_generator,
741 generate_single_report = generate_single_report,
742 generate_comparative_report = generate_comparative_report
743}