energy-visualization-sankey 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +497 -0
  2. package/babel.config.cjs +28 -0
  3. package/coverage/clover.xml +6 -0
  4. package/coverage/coverage-final.json +1 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/favicon.png +0 -0
  8. package/coverage/lcov-report/index.html +101 -0
  9. package/coverage/lcov-report/prettify.css +1 -0
  10. package/coverage/lcov-report/prettify.js +2 -0
  11. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  12. package/coverage/lcov-report/sorter.js +210 -0
  13. package/coverage/lcov.info +0 -0
  14. package/demo-caching.js +68 -0
  15. package/dist/core/Sankey.d.ts +294 -0
  16. package/dist/core/Sankey.d.ts.map +1 -0
  17. package/dist/core/events/EventBus.d.ts +195 -0
  18. package/dist/core/events/EventBus.d.ts.map +1 -0
  19. package/dist/core/types/events.d.ts +42 -0
  20. package/dist/core/types/events.d.ts.map +1 -0
  21. package/dist/index.d.ts +19 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/sankey.esm.js +5212 -0
  24. package/dist/sankey.esm.js.map +1 -0
  25. package/dist/sankey.standalone.esm.js +9111 -0
  26. package/dist/sankey.standalone.esm.js.map +1 -0
  27. package/dist/sankey.standalone.min.js +2 -0
  28. package/dist/sankey.standalone.min.js.map +1 -0
  29. package/dist/sankey.standalone.umd.js +9119 -0
  30. package/dist/sankey.standalone.umd.js.map +1 -0
  31. package/dist/sankey.umd.js +5237 -0
  32. package/dist/sankey.umd.js.map +1 -0
  33. package/dist/sankey.umd.min.js +2 -0
  34. package/dist/sankey.umd.min.js.map +1 -0
  35. package/dist/services/AnimationService.d.ts +229 -0
  36. package/dist/services/AnimationService.d.ts.map +1 -0
  37. package/dist/services/ConfigurationService.d.ts +173 -0
  38. package/dist/services/ConfigurationService.d.ts.map +1 -0
  39. package/dist/services/InteractionService.d.ts +377 -0
  40. package/dist/services/InteractionService.d.ts.map +1 -0
  41. package/dist/services/RenderingService.d.ts +152 -0
  42. package/dist/services/RenderingService.d.ts.map +1 -0
  43. package/dist/services/calculation/GraphService.d.ts +111 -0
  44. package/dist/services/calculation/GraphService.d.ts.map +1 -0
  45. package/dist/services/calculation/SummaryService.d.ts +149 -0
  46. package/dist/services/calculation/SummaryService.d.ts.map +1 -0
  47. package/dist/services/data/DataService.d.ts +167 -0
  48. package/dist/services/data/DataService.d.ts.map +1 -0
  49. package/dist/services/data/DataValidationService.d.ts +48 -0
  50. package/dist/services/data/DataValidationService.d.ts.map +1 -0
  51. package/dist/types/index.d.ts +189 -0
  52. package/dist/types/index.d.ts.map +1 -0
  53. package/dist/utils/Logger.d.ts +88 -0
  54. package/dist/utils/Logger.d.ts.map +1 -0
  55. package/jest.config.cjs +20 -0
  56. package/package.json +68 -0
  57. package/rollup.config.js +131 -0
  58. package/scripts/performance-validation-real.js +411 -0
  59. package/scripts/validate-optimization.sh +147 -0
  60. package/scripts/visual-validation-real-data.js +374 -0
  61. package/src/core/Sankey.ts +1039 -0
  62. package/src/core/events/EventBus.ts +488 -0
  63. package/src/core/types/events.ts +80 -0
  64. package/src/index.ts +35 -0
  65. package/src/services/AnimationService.ts +983 -0
  66. package/src/services/ConfigurationService.ts +497 -0
  67. package/src/services/InteractionService.ts +920 -0
  68. package/src/services/RenderingService.ts +484 -0
  69. package/src/services/calculation/GraphService.ts +616 -0
  70. package/src/services/calculation/SummaryService.ts +394 -0
  71. package/src/services/data/DataService.ts +380 -0
  72. package/src/services/data/DataValidationService.ts +155 -0
  73. package/src/styles/controls.css +184 -0
  74. package/src/styles/sankey.css +211 -0
  75. package/src/types/index.ts +220 -0
  76. package/src/utils/Logger.ts +105 -0
  77. package/tests/numerical-validation.test.js +575 -0
  78. package/tests/setup.js +53 -0
  79. package/tsconfig.json +54 -0
@@ -0,0 +1,147 @@
1
+ #!/bin/bash
2
+
3
+ # Master Validation Script for US Energy Sankey v5
4
+ # Comprehensive validation with real data (1800-2021)
5
+ # Ensures pixel-perfect accuracy during optimization
6
+
7
+ echo "๐Ÿš€ US Energy Sankey v5 - Comprehensive Validation"
8
+ echo "================================================="
9
+ echo "Testing with real US energy data: 1800-2021 (222 years)"
10
+ echo ""
11
+
12
+ # Colors for output
13
+ RED='\033[0;31m'
14
+ GREEN='\033[0;32m'
15
+ YELLOW='\033[1;33m'
16
+ BLUE='\033[0;34m'
17
+ NC='\033[0m' # No Color
18
+
19
+ # Track validation results
20
+ NUMERICAL_RESULT=0
21
+ VISUAL_RESULT=0
22
+ PERFORMANCE_RESULT=0
23
+ BUILD_RESULT=0
24
+
25
+ # Function to print status
26
+ print_status() {
27
+ if [ $1 -eq 0 ]; then
28
+ echo -e "${GREEN}โœ… $2 PASSED${NC}"
29
+ else
30
+ echo -e "${RED}โŒ $2 FAILED${NC}"
31
+ fi
32
+ }
33
+
34
+ # Function to cleanup background processes
35
+ cleanup() {
36
+ echo -e "\n๐Ÿงน Cleaning up..."
37
+ if [ ! -z "$SERVER_PID" ]; then
38
+ kill $SERVER_PID 2>/dev/null
39
+ wait $SERVER_PID 2>/dev/null
40
+ echo " Server stopped"
41
+ fi
42
+ }
43
+
44
+ # Trap to ensure cleanup on exit
45
+ trap cleanup EXIT INT TERM
46
+
47
+ # Step 1: Build the project
48
+ echo -e "${BLUE}๐Ÿ“ฆ Step 1: Building project...${NC}"
49
+ npm run build
50
+ BUILD_RESULT=$?
51
+ print_status $BUILD_RESULT "Build"
52
+
53
+ if [ $BUILD_RESULT -ne 0 ]; then
54
+ echo -e "${RED}Build failed. Cannot proceed with validation.${NC}"
55
+ exit 1
56
+ fi
57
+
58
+ # Verify required build files exist
59
+ echo "๐Ÿ” Verifying build artifacts..."
60
+ if [ ! -f "dist/us-energy-sankey-v5.standalone.esm.js" ]; then
61
+ echo -e "${RED}โŒ Required standalone ESM build not found${NC}"
62
+ exit 1
63
+ fi
64
+ if [ ! -f "examples/data/data.json" ]; then
65
+ echo -e "${RED}โŒ Required data file not found${NC}"
66
+ exit 1
67
+ fi
68
+ echo -e "${GREEN}โœ… All required files present${NC}"
69
+
70
+ # Step 2: Run numerical validation
71
+ echo -e "\n${BLUE}๐Ÿ“Š Step 2: Running numerical validation...${NC}"
72
+ echo "Testing mathematical consistency with real data"
73
+ npm run test:numerical
74
+ NUMERICAL_RESULT=$?
75
+ print_status $NUMERICAL_RESULT "Numerical Validation"
76
+
77
+ # Step 3: Start server for visual/performance tests
78
+ echo -e "\n${BLUE}๐ŸŒ Step 3: Starting local server...${NC}"
79
+ npm run serve &
80
+ SERVER_PID=$!
81
+ echo "Server PID: $SERVER_PID"
82
+
83
+ # Wait for server to start
84
+ echo "Waiting for server to start..."
85
+ sleep 3
86
+
87
+ # Check if server is running
88
+ if ! curl -s http://localhost:8080 > /dev/null; then
89
+ echo -e "${RED}โŒ Server failed to start${NC}"
90
+ exit 1
91
+ fi
92
+ echo -e "${GREEN}โœ… Server running on http://localhost:8080${NC}"
93
+
94
+ # Step 4: Run visual validation
95
+ echo -e "\n${BLUE}๐Ÿ‘๏ธ Step 4: Running visual validation...${NC}"
96
+ echo "Testing pixel-perfect accuracy across energy eras"
97
+ npm run test:visual
98
+ VISUAL_RESULT=$?
99
+ print_status $VISUAL_RESULT "Visual Validation"
100
+
101
+ # Step 5: Run performance validation
102
+ echo -e "\n${BLUE}โšก Step 5: Running performance validation...${NC}"
103
+ echo "Testing performance with 222 years of real data"
104
+ npm run test:performance
105
+ PERFORMANCE_RESULT=$?
106
+ print_status $PERFORMANCE_RESULT "Performance Validation"
107
+
108
+ # Generate final report
109
+ echo -e "\n๐Ÿ“‹ VALIDATION SUMMARY"
110
+ echo "===================="
111
+ print_status $BUILD_RESULT "Build"
112
+ print_status $NUMERICAL_RESULT "Numerical Validation (Real Data Calculations)"
113
+ print_status $VISUAL_RESULT "Visual Validation (Pixel-Perfect Screenshots)"
114
+ print_status $PERFORMANCE_RESULT "Performance Validation (Speed & Memory)"
115
+
116
+ # Calculate overall result
117
+ OVERALL_RESULT=$((BUILD_RESULT + NUMERICAL_RESULT + VISUAL_RESULT + PERFORMANCE_RESULT))
118
+
119
+ if [ $OVERALL_RESULT -eq 0 ]; then
120
+ echo -e "\n${GREEN}๐ŸŽ‰ ALL VALIDATIONS PASSED!${NC}"
121
+ echo -e "${GREEN}โœ… Your optimization maintains identical results${NC}"
122
+ echo -e "${GREEN}โœ… Ready for production deployment${NC}"
123
+ else
124
+ echo -e "\n${RED}โŒ VALIDATION FAILURES DETECTED${NC}"
125
+ echo -e "${RED}โš ๏ธ Do not proceed with optimization until all tests pass${NC}"
126
+
127
+ # Provide specific guidance
128
+ if [ $NUMERICAL_RESULT -ne 0 ]; then
129
+ echo -e "${YELLOW}๐Ÿ’ก Numerical failures: Check mathematical calculations and constants${NC}"
130
+ fi
131
+ if [ $VISUAL_RESULT -ne 0 ]; then
132
+ echo -e "${YELLOW}๐Ÿ’ก Visual failures: Check validation/diffs/ for pixel differences${NC}"
133
+ fi
134
+ if [ $PERFORMANCE_RESULT -ne 0 ]; then
135
+ echo -e "${YELLOW}๐Ÿ’ก Performance failures: Check if performance thresholds were exceeded${NC}"
136
+ fi
137
+ fi
138
+
139
+ # Show validation artifacts
140
+ echo -e "\n๐Ÿ“ VALIDATION ARTIFACTS"
141
+ echo "======================"
142
+ echo "๐Ÿ“Š Numerical test results: Jest output above"
143
+ echo "๐Ÿ“ธ Visual baselines: validation/baselines/"
144
+ echo "๐Ÿ” Visual differences: validation/diffs/"
145
+ echo "โšก Performance data: validation/performance/"
146
+
147
+ exit $OVERALL_RESULT
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Visual Validation Script for US Energy Sankey v5
3
+ * Using REAL data from examples/data/data.json (1800-2021)
4
+ * Pixel-perfect comparison across key historical periods
5
+ */
6
+
7
+ import {chromium} from 'playwright';
8
+ import pixelmatch from 'pixelmatch';
9
+ import {PNG} from 'pngjs';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import {fileURLToPath} from 'url';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ class RealDataVisualValidator {
18
+ constructor() {
19
+ this.browser = null;
20
+ this.baselineDir = path.join(__dirname, '../validation/baselines');
21
+ this.outputDir = path.join(__dirname, '../validation/output');
22
+ this.diffDir = path.join(__dirname, '../validation/diffs');
23
+
24
+ // Test scenarios based on ACTUAL US energy history
25
+ this.testScenarios = [
26
+ {
27
+ name: 'colonial-1800.png',
28
+ year: 1800,
29
+ description: 'Colonial America - Wood burning era',
30
+ significance: 'Baseline minimal energy usage'
31
+ },
32
+ {
33
+ name: 'industrial-1900.png',
34
+ year: 1900,
35
+ description: 'Industrial Revolution - Coal emergence',
36
+ significance: 'Industrial transformation period'
37
+ },
38
+ {
39
+ name: 'postwar-1950.png',
40
+ year: 1950,
41
+ description: 'Post-war boom - Coal dominant + steam locomotives retired',
42
+ significance: 'Transportation electrification milestone'
43
+ },
44
+ {
45
+ name: 'nuclear-1980.png',
46
+ year: 1980,
47
+ description: 'Nuclear age - Atomic energy adoption',
48
+ significance: 'Nuclear power peak period'
49
+ },
50
+ {
51
+ name: 'modern-2000.png',
52
+ year: 2000,
53
+ description: 'Modern era - Diversified energy mix',
54
+ significance: 'Pre-renewable diversity'
55
+ },
56
+ {
57
+ name: 'renewable-2020.png',
58
+ year: 2020,
59
+ description: 'Renewable era - Solar and wind growth',
60
+ significance: 'Clean energy transition'
61
+ },
62
+ {
63
+ name: 'current-2021.png',
64
+ year: 2021,
65
+ description: 'Latest available data',
66
+ significance: 'Most recent energy snapshot'
67
+ },
68
+ {
69
+ name: 'waste-visible-2000.png',
70
+ year: 2000,
71
+ wasteVisible: true,
72
+ description: 'Waste heat visible - Modern era',
73
+ significance: 'Full energy flow visualization'
74
+ },
75
+ {
76
+ name: 'waste-hidden-2000.png',
77
+ year: 2000,
78
+ wasteVisible: false,
79
+ description: 'Waste heat hidden - Modern era',
80
+ significance: 'Clean flow visualization'
81
+ }
82
+ ];
83
+
84
+ this.ensureDirectories();
85
+ }
86
+
87
+ ensureDirectories() {
88
+ [this.baselineDir, this.outputDir, this.diffDir].forEach(dir => {
89
+ if (!fs.existsSync(dir)) {
90
+ fs.mkdirSync(dir, {recursive: true});
91
+ console.log(`๐Ÿ“ Created directory: ${dir}`);
92
+ }
93
+ });
94
+ }
95
+
96
+ async setup() {
97
+ console.log('๐Ÿš€ Setting up browser for visual validation...');
98
+ this.browser = await chromium.launch({
99
+ headless: true,
100
+ args: [
101
+ '--no-sandbox',
102
+ '--disable-dev-shm-usage',
103
+ '--disable-web-security',
104
+ '--allow-running-insecure-content'
105
+ ]
106
+ });
107
+ }
108
+
109
+ async captureScenario(scenario) {
110
+ const page = await this.browser.newPage();
111
+
112
+ try {
113
+ // Set consistent viewport for all tests
114
+ await page.setViewportSize({width: 1400, height: 900});
115
+
116
+ // Navigate to the test page
117
+ console.log(`๐Ÿ“ธ Capturing: ${scenario.description}`);
118
+ await page.goto('http://localhost:8080/examples/visual-test.html', {
119
+ waitUntil: 'networkidle',
120
+ timeout: 30000
121
+ });
122
+
123
+ // Wait for sankey initialization with REAL data validation
124
+ await page.waitForFunction(() => {
125
+ return window.sankey &&
126
+ window.sankey.isInitialized() &&
127
+ window.sankey.getYears().length === 222 && // Verify all 222 years loaded
128
+ window.sankey.getYears().includes(1800) &&
129
+ window.sankey.getYears().includes(2021);
130
+ }, {timeout: 20000});
131
+
132
+ // Set specific year from real dataset
133
+ if (scenario.year) {
134
+ await page.evaluate((year) => {
135
+ console.log(`Setting year to ${year}`);
136
+ window.sankey.setYear(year);
137
+ }, scenario.year);
138
+
139
+ // Wait for year transition to complete
140
+ await page.waitForTimeout(1000);
141
+
142
+ // Verify year was set correctly
143
+ const actualYear = await page.evaluate(() => {
144
+ return window.sankey.getCurrentYear();
145
+ });
146
+
147
+ if (actualYear !== scenario.year) {
148
+ console.warn(`โš ๏ธ Year mismatch: expected ${scenario.year}, got ${actualYear}`);
149
+ }
150
+ }
151
+
152
+ // Handle waste heat visibility
153
+ if (scenario.wasteVisible !== undefined) {
154
+ await page.evaluate((visible) => {
155
+ const currentState = window.sankey.isWasteHeatVisible();
156
+ console.log(`Waste heat: current=${currentState}, target=${visible}`);
157
+ if (currentState !== visible) {
158
+ window.sankey.toggleWasteHeat();
159
+ }
160
+ }, scenario.wasteVisible);
161
+
162
+ // Wait for waste heat toggle to complete
163
+ await page.waitForTimeout(500);
164
+ }
165
+
166
+ // Wait for any final rendering
167
+ await page.waitForTimeout(1500);
168
+
169
+ // Additional wait for stability
170
+ await page.waitForLoadState('networkidle');
171
+
172
+ // Capture screenshot
173
+ const screenshot = await page.screenshot({
174
+ path: path.join(this.outputDir, scenario.name),
175
+ fullPage: false,
176
+ clip: {x: 0, y: 0, width: 1400, height: 1000} // Consistent clipping
177
+ });
178
+
179
+ console.log(`โœ… Captured: ${scenario.name} (${scenario.year || 'default'})`);
180
+ return screenshot;
181
+
182
+ } catch (error) {
183
+ console.error(`โŒ Failed to capture ${scenario.name}:`, error.message);
184
+ throw error;
185
+ } finally {
186
+ await page.close();
187
+ }
188
+ }
189
+
190
+ async compareWithBaseline(filename) {
191
+ const baselinePath = path.join(this.baselineDir, filename);
192
+ const outputPath = path.join(this.outputDir, filename);
193
+ const diffPath = path.join(this.diffDir, filename);
194
+
195
+ if (!fs.existsSync(baselinePath)) {
196
+ console.log(`๐Ÿ“ Creating new baseline: ${filename}`);
197
+ fs.copyFileSync(outputPath, baselinePath);
198
+ return {match: true, isNewBaseline: true};
199
+ }
200
+
201
+ try {
202
+ const baseline = PNG.sync.read(fs.readFileSync(baselinePath));
203
+ const current = PNG.sync.read(fs.readFileSync(outputPath));
204
+
205
+ const {width, height} = baseline;
206
+ const diff = new PNG({width, height});
207
+
208
+ const pixelDiffCount = pixelmatch(
209
+ baseline.data,
210
+ current.data,
211
+ diff.data,
212
+ width,
213
+ height,
214
+ {
215
+ threshold: 0.005, // Very strict threshold for energy visualization
216
+ includeAA: false,
217
+ alpha: 0.1,
218
+ diffColor: [255, 0, 0] // Red diff highlights
219
+ }
220
+ );
221
+
222
+ const totalPixels = width * height;
223
+ const diffPercentage = (pixelDiffCount / totalPixels) * 100;
224
+
225
+ if (pixelDiffCount > 0) {
226
+ fs.writeFileSync(diffPath, PNG.sync.write(diff));
227
+ } else {
228
+ // Remove diff file if no differences
229
+ if (fs.existsSync(diffPath)) {
230
+ fs.unlinkSync(diffPath);
231
+ }
232
+ }
233
+
234
+ return {
235
+ match: pixelDiffCount === 0,
236
+ pixelDiffCount,
237
+ diffPercentage,
238
+ totalPixels,
239
+ diffPath: pixelDiffCount > 0 ? diffPath : null
240
+ };
241
+
242
+ } catch (error) {
243
+ console.error(`โŒ Error comparing ${filename}:`, error.message);
244
+ return {
245
+ match: false,
246
+ error: error.message
247
+ };
248
+ }
249
+ }
250
+
251
+ async validateAllScenarios() {
252
+ console.log('๐ŸŽฏ Starting visual validation with REAL US energy data (1800-2021)...\n');
253
+ console.log(`Testing ${this.testScenarios.length} scenarios across 222 years of energy history\n`);
254
+
255
+ const results = [];
256
+
257
+ for (let i = 0; i < this.testScenarios.length; i++) {
258
+ const scenario = this.testScenarios[i];
259
+ console.log(`[${i + 1}/${this.testScenarios.length}] ${scenario.description}`);
260
+ console.log(` Significance: ${scenario.significance}`);
261
+
262
+ try {
263
+ await this.captureScenario(scenario);
264
+ const comparison = await this.compareWithBaseline(scenario.name);
265
+
266
+ results.push({
267
+ scenario: scenario.name,
268
+ description: scenario.description,
269
+ year: scenario.year,
270
+ significance: scenario.significance,
271
+ ...comparison
272
+ });
273
+
274
+ if (comparison.error) {
275
+ console.error(`โŒ ${scenario.name}: ${comparison.error}`);
276
+ } else if (!comparison.match && !comparison.isNewBaseline) {
277
+ console.error(`โŒ ${scenario.name}: ${comparison.pixelDiffCount} pixels different (${comparison.diffPercentage.toFixed(3)}%)`);
278
+ console.error(` Diff saved to: ${comparison.diffPath}`);
279
+ } else if (comparison.isNewBaseline) {
280
+ console.log(`๐Ÿ“ ${scenario.name}: New baseline created`);
281
+ } else {
282
+ console.log(`โœ… ${scenario.name}: Perfect pixel match`);
283
+ }
284
+
285
+ } catch (error) {
286
+ console.error(`๐Ÿ’ฅ ${scenario.name}: Capture failed - ${error.message}`);
287
+ results.push({
288
+ scenario: scenario.name,
289
+ description: scenario.description,
290
+ year: scenario.year,
291
+ match: false,
292
+ error: error.message
293
+ });
294
+ }
295
+
296
+ console.log(''); // Empty line for readability
297
+ }
298
+
299
+ return results;
300
+ }
301
+
302
+ async teardown() {
303
+ if (this.browser) {
304
+ await this.browser.close();
305
+ console.log('๐Ÿงน Browser closed');
306
+ }
307
+ }
308
+
309
+ generateReport(results) {
310
+ console.log(`\n๐Ÿ“Š VISUAL VALIDATION REPORT`);
311
+ console.log(`${'='.repeat(50)}`);
312
+
313
+ const total = results.length;
314
+ const newBaselines = results.filter(r => r.isNewBaseline).length;
315
+ const passed = results.filter(r => r.match && !r.isNewBaseline).length;
316
+ const failed = results.filter(r => !r.match && !r.isNewBaseline).length;
317
+ const errors = results.filter(r => r.error).length;
318
+
319
+ console.log(`Total scenarios: ${total}`);
320
+ console.log(`New baselines: ${newBaselines}`);
321
+ console.log(`Passed: ${passed}`);
322
+ console.log(`Failed: ${failed}`);
323
+ console.log(`Errors: ${errors}`);
324
+
325
+ if (failed > 0) {
326
+ console.log(`\nโŒ FAILED SCENARIOS:`);
327
+ results.filter(r => !r.match && !r.isNewBaseline && !r.error).forEach(r => {
328
+ console.log(` ${r.scenario} (${r.year}): ${r.diffPercentage.toFixed(3)}% difference`);
329
+ console.log(` ${r.description}`);
330
+ console.log(` Diff: ${r.diffPath}`);
331
+ });
332
+ }
333
+
334
+ if (errors > 0) {
335
+ console.log(`\n๐Ÿ’ฅ ERROR SCENARIOS:`);
336
+ results.filter(r => r.error).forEach(r => {
337
+ console.log(` ${r.scenario}: ${r.error}`);
338
+ });
339
+ }
340
+
341
+ return {total, newBaselines, passed, failed, errors};
342
+ }
343
+ }
344
+
345
+ // Main execution
346
+ async function main() {
347
+ const validator = new RealDataVisualValidator();
348
+
349
+ try {
350
+ await validator.setup();
351
+ const results = await validator.validateAllScenarios();
352
+ const summary = validator.generateReport(results);
353
+
354
+ if (summary.failed > 0 || summary.errors > 0) {
355
+ console.error(`\nโŒ Visual validation failed: ${summary.failed} failures, ${summary.errors} errors`);
356
+ process.exit(1);
357
+ } else if (summary.newBaselines > 0) {
358
+ console.log(`\n๐Ÿ“ ${summary.newBaselines} new baselines created. Re-run to validate against them.`);
359
+ } else {
360
+ console.log(`\nโœ… All ${summary.passed} visual validations passed! Pixel-perfect accuracy maintained.`);
361
+ }
362
+
363
+ } catch (error) {
364
+ console.error('โŒ Visual validation crashed:', error);
365
+ process.exit(1);
366
+ } finally {
367
+ await validator.teardown();
368
+ }
369
+ }
370
+
371
+ // Run if called directly
372
+ if (import.meta.url === `file://${process.argv[1]}`) {
373
+ main();
374
+ }