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,380 @@
1
+ import type {EnergyDataPoint} from '@/types';
2
+ import {DataValidationService} from "@/services/data/DataValidationService";
3
+ import {EventBus} from "@/core/events/EventBus";
4
+ import {Logger} from "@/utils/Logger";
5
+
6
+ /**
7
+ * Data Service
8
+ *
9
+ * Core data access and management service providing validated, sorted energy data
10
+ * with comprehensive query capabilities. Handles input validation, data sorting,
11
+ * year-based navigation, and statistical analysis.
12
+ *
13
+ * Provides validated, indexed access to energy flow data with comprehensive
14
+ * query capabilities and statistical analysis. Maintains data integrity
15
+ * through immutable structures and validation at construction time.
16
+ *
17
+ * Key Responsibilities:
18
+ * - Input validation and data structure verification
19
+ * - Chronological data sorting and indexing
20
+ * - Year-based data retrieval and navigation
21
+ * - Fuel and sector totals calculation
22
+ * - Milestone data management
23
+ * - Dataset statistics and metadata
24
+ *
25
+ * Data Structure:
26
+ * Manages EnergyDataPoint arrays with comprehensive energy flow data
27
+ * including all major fuel types (solar, nuclear, hydro, wind, etc.)
28
+ * and consumption sectors (electricity, residential, industrial, etc.)
29
+ *
30
+ * Performance Features:
31
+ * - Immutable data structures for safety
32
+ * - Pre-computed indices for fast lookups
33
+ * - Efficient year-based navigation
34
+ * - Cached statistics calculations
35
+ *
36
+ */
37
+ export class DataService {
38
+ // Core immutable data structures
39
+ public readonly data: readonly EnergyDataPoint[];
40
+ public readonly years: readonly number[];
41
+ public readonly yearsLength: number;
42
+ public readonly firstYear: number;
43
+ public readonly lastYear: number;
44
+
45
+ constructor(
46
+ energyData: EnergyDataPoint[],
47
+ private validationService: DataValidationService,
48
+ private eventBus: EventBus,
49
+ private logger: Logger,
50
+ ) {
51
+ const startTime = performance.now(); // Performance monitoring for data service initialization
52
+
53
+ // Validate input data structure and content - Data Integrity Assurance
54
+ // Critical first step: ensures all downstream calculations receive valid data
55
+ // Validation service performs comprehensive structure and content verification
56
+ this.validationService.validateData(energyData);
57
+
58
+ // Sort data chronologically and create immutable reference - Data Structure Optimization
59
+ // Chronological sorting enables efficient year-based navigation and animation
60
+ // Immutability prevents accidental data corruption throughout application lifecycle
61
+ const sortedData = [...energyData].sort((a, b) => a.year - b.year);
62
+ this.data = Object.freeze(sortedData);
63
+
64
+ // Extract and freeze year indices for efficient navigation - Performance Optimization
65
+ // Pre-computed year array enables O(1) year lookups and efficient navigation
66
+ // Frozen arrays prevent mutation while maintaining reference stability
67
+ this.years = Object.freeze(this.data.map(d => d.year));
68
+ this.yearsLength = this.years.length;
69
+ this.firstYear = this.years[0]; // Timeline start boundary
70
+ this.lastYear = this.years[this.yearsLength - 1]; // Timeline end boundary
71
+
72
+ const endTime = performance.now();
73
+
74
+ // Emit data loaded event for service coordination - Event-Driven Architecture
75
+ // Notifies calculation and visualization services that validated data is available
76
+ // Includes metadata for capacity planning and performance monitoring
77
+ this.eventBus.emit({
78
+ type: 'data.loaded',
79
+ timestamp: Date.now(),
80
+ source: 'DataService',
81
+ data: {
82
+ dataPoints: [...this.data], // Complete dataset for processing
83
+ yearCount: this.yearsLength, // Timeline scope information
84
+ yearRange: [this.firstYear, this.lastYear] // Temporal boundaries
85
+ }
86
+ });
87
+
88
+ // Debug logging (controlled by library configuration when available) - Performance Monitoring
89
+ // Tracks initialization performance and data characteristics for optimization
90
+ this.logger.log(`DataService: ${this.data.length} data points loaded and sorted in ${(endTime - startTime).toFixed(2)}ms`);
91
+ this.logger.log(`DataService: Year range ${this.firstYear}-${this.lastYear}`);
92
+ }
93
+
94
+ // ==================== DATA ACCESS METHODS ====================
95
+ // Core data retrieval and navigation methods
96
+
97
+ /**
98
+ * Get data for specific year
99
+ * @param year - Target year to retrieve
100
+ * @returns Energy data for the specified year, or undefined if not found
101
+ */
102
+ public getYearData(year: number): EnergyDataPoint | undefined {
103
+ return this.data.find(d => d.year === year);
104
+ }
105
+
106
+ /**
107
+ * Get index of year in chronological sequence
108
+ * @param year - Year to locate
109
+ * @returns Zero-based index of year, or -1 if not found
110
+ */
111
+ public getYearIndex(year: number): number {
112
+ return this.years.indexOf(year);
113
+ }
114
+
115
+ /**
116
+ * Check if year exists in dataset
117
+ * @param year - Year to check
118
+ * @returns True if year has data available
119
+ */
120
+ public hasYear(year: number): boolean {
121
+ return this.years.includes(year);
122
+ }
123
+
124
+ /**
125
+ * Get data by chronological index
126
+ * @param index - Zero-based index in sorted data array
127
+ * @returns Energy data at specified index, or undefined if out of bounds
128
+ */
129
+ public getYearDataByIndex(index: number): EnergyDataPoint | undefined {
130
+ if (index < 0 || index >= this.data.length) {
131
+ return undefined;
132
+ }
133
+ return this.data[index];
134
+ }
135
+
136
+ /**
137
+ * Check if year is valid and within dataset range
138
+ * @param year - Year to validate
139
+ * @returns True if year exists and is within valid range
140
+ */
141
+ public isValidYear(year: number): boolean {
142
+ return year >= this.firstYear && year <= this.lastYear && this.hasYear(year);
143
+ }
144
+
145
+ // ==================== NAVIGATION METHODS ====================
146
+ // Year-based navigation and range operations
147
+
148
+ /**
149
+ * Get year range as tuple
150
+ * @returns Tuple containing [firstYear, lastYear]
151
+ */
152
+ public getYearRange(): [number, number] {
153
+ return [this.firstYear, this.lastYear];
154
+ }
155
+
156
+ /**
157
+ * Get total number of data points
158
+ * @returns Count of available years in dataset
159
+ */
160
+ public getDataCount(): number {
161
+ return this.data.length;
162
+ }
163
+
164
+ /**
165
+ * Get chronologically next year
166
+ * @param currentYear - Starting year
167
+ * @returns Next available year, or null if at end of dataset
168
+ */
169
+ public getNextYear(currentYear: number): number | null {
170
+ const currentIndex = this.getYearIndex(currentYear);
171
+ if (currentIndex === -1 || currentIndex === this.yearsLength - 1) {
172
+ return null;
173
+ }
174
+ return this.years[currentIndex + 1];
175
+ }
176
+
177
+ /**
178
+ * Get chronologically previous year
179
+ * @param currentYear - Starting year
180
+ * @returns Previous available year, or null if at beginning of dataset
181
+ */
182
+ public getPreviousYear(currentYear: number): number | null {
183
+ const currentIndex = this.getYearIndex(currentYear);
184
+ if (currentIndex <= 0) {
185
+ return null;
186
+ }
187
+ return this.years[currentIndex - 1];
188
+ }
189
+
190
+ // ==================== CALCULATION METHODS ====================
191
+ // Fuel and sector totals calculation methods
192
+
193
+ /**
194
+ * Get energy total for specific year, fuel, and sector combination
195
+ * @param year - Target year
196
+ * @param fuel - Fuel type (e.g., 'coal', 'gas', 'solar')
197
+ * @param sector - Consumption sector (e.g., 'elec', 'trans', 'indus')
198
+ * @returns Energy value for the specified combination
199
+ */
200
+ public getTotalForYear(year: number, fuel: string, sector: string): number {
201
+ const yearData = this.getYearData(year);
202
+ if (!yearData) {
203
+ return 0;
204
+ }
205
+
206
+ const fuelData = (yearData as any)[fuel];
207
+ if (!fuelData) {
208
+ return 0;
209
+ }
210
+
211
+ return fuelData[sector] || 0;
212
+ }
213
+
214
+ /**
215
+ * Get total energy consumption for specific fuel across all sectors
216
+ *
217
+ * **Energy Industry Analysis Method:**
218
+ * - Aggregates fuel consumption across all end-use sectors
219
+ * - Provides fuel-specific energy totals for analysis and visualization
220
+ * - Follows EIA energy accounting standards for sector summation
221
+ *
222
+ * @param year - Target year
223
+ * @param fuel - Fuel type to sum (e.g., 'coal', 'gas', 'solar')
224
+ * @returns Total energy consumption for fuel across all sectors
225
+ */
226
+ public getYearTotalForFuel(year: number, fuel: string): number {
227
+ const yearData = this.getYearData(year);
228
+ if (!yearData) {
229
+ return 0; // Graceful degradation for missing year data
230
+ }
231
+
232
+ const fuelData = (yearData as any)[fuel];
233
+ if (!fuelData) {
234
+ return 0; // Graceful degradation for missing fuel data
235
+ }
236
+
237
+ // Sum across all major consumption sectors - Energy Industry Standard
238
+ // Covers complete energy end-use landscape: electricity generation, residential,
239
+ // agriculture, industrial, and transportation sectors
240
+ return (fuelData.elec || 0) + (fuelData.res || 0) + (fuelData.ag || 0) +
241
+ (fuelData.indus || 0) + (fuelData.trans || 0);
242
+ }
243
+
244
+ /**
245
+ * Get total energy consumption for specific sector across all fuels
246
+ *
247
+ * **Sectoral Energy Analysis Method:**
248
+ * - Aggregates energy consumption by end-use sector across all fuel types
249
+ * - Handles electricity as both source and consumption (special case logic)
250
+ * - Provides sectoral energy totals for economic and policy analysis
251
+ *
252
+ * @param year - Target year
253
+ * @param sector - Sector to sum (e.g., 'trans' for transportation)
254
+ * @returns Total energy consumption for sector across all fuel types
255
+ */
256
+ public getYearTotalForSector(year: number, sector: string): number {
257
+ const yearData = this.getYearData(year);
258
+ if (!yearData) {
259
+ return 0; // Graceful degradation for missing year data
260
+ }
261
+
262
+ let total = 0;
263
+ // Standard fuel types for comprehensive calculation - Energy Source Categories
264
+ // Covers all primary energy sources in US energy system per EIA classification
265
+ const fuels = ['solar', 'nuclear', 'hydro', 'wind', 'geo', 'gas', 'coal', 'bio', 'petro'];
266
+
267
+ for (const fuel of fuels) {
268
+ const fuelData = (yearData as any)[fuel];
269
+ if (fuelData && fuelData[sector]) {
270
+ total += fuelData[sector]; // Direct fuel consumption in target sector
271
+ }
272
+ }
273
+
274
+ // Add electricity consumption for non-electricity sectors - Special Case Handling
275
+ // Electricity is both a fuel source (for generation) and consumption medium
276
+ // For non-electricity sectors, add electricity consumption to capture total energy use
277
+ if (sector !== 'elec' && yearData.elec[sector as keyof typeof yearData.elec]) {
278
+ total += yearData.elec[sector as keyof typeof yearData.elec];
279
+ }
280
+
281
+ return total;
282
+ }
283
+
284
+ // ==================== MILESTONE METHODS ====================
285
+ // Historical milestone data retrieval methods
286
+
287
+ /**
288
+ * Get historical milestone description for specific year
289
+ * @param year - Target year
290
+ * @returns Milestone description string, or undefined if no milestone
291
+ */
292
+ public getMilestoneForYear(year: number): string | undefined {
293
+ const yearData = this.getYearData(year);
294
+ return yearData?.milestone;
295
+ }
296
+
297
+ /**
298
+ * Get array of all years containing historical milestones
299
+ * @returns Array of years with milestone data available
300
+ */
301
+ public getYearsWithMilestones(): number[] {
302
+ return this.data
303
+ .filter(d => d.milestone)
304
+ .map(d => d.year);
305
+ }
306
+
307
+ // ==================== ANALYSIS METHODS ====================
308
+ // Dataset analysis and metadata extraction methods
309
+
310
+ /**
311
+ * Get list of all fuel types available in dataset
312
+ * @returns Array of fuel type names (e.g., ['solar', 'coal', 'gas'])
313
+ */
314
+ public getAvailableFuels(): string[] {
315
+ if (this.data.length === 0) {
316
+ return [];
317
+ }
318
+
319
+ // Extract all fuel types from first data point (structure should be consistent)
320
+ const firstPoint = this.data[0];
321
+ return Object.keys(firstPoint).filter(key =>
322
+ key !== 'year' &&
323
+ key !== 'milestone' &&
324
+ typeof (firstPoint as any)[key] === 'object'
325
+ );
326
+ }
327
+
328
+ /**
329
+ * Get list of all consumption sectors available in dataset
330
+ * @returns Array of sector names (e.g., ['elec', 'trans', 'indus'])
331
+ */
332
+ public getAvailableSectors(): string[] {
333
+ if (this.data.length === 0) {
334
+ return [];
335
+ }
336
+
337
+ // Extract sectors from first fuel object (structure should be consistent)
338
+ const firstPoint = this.data[0];
339
+ const firstFuel = (firstPoint as any)['elec']; // Use elec as reference
340
+ if (!firstFuel) {
341
+ return [];
342
+ }
343
+
344
+ return Object.keys(firstFuel);
345
+ }
346
+
347
+ /**
348
+ * Get comprehensive statistics about the dataset
349
+ * Provides metadata useful for analysis, debugging, and performance monitoring
350
+ * @returns Object containing dataset statistics and metadata
351
+ */
352
+ public getDataStatistics(): {
353
+ totalDataPoints: number;
354
+ yearRange: [number, number];
355
+ averageYearGap: number;
356
+ milestonesCount: number;
357
+ fuelsCount: number;
358
+ sectorsCount: number;
359
+ } {
360
+ const milestonesCount = this.getYearsWithMilestones().length;
361
+ const fuelsCount = this.getAvailableFuels().length;
362
+ const sectorsCount = this.getAvailableSectors().length;
363
+
364
+ // Calculate average gap between consecutive years
365
+ let totalGap = 0;
366
+ for (let i = 1; i < this.yearsLength; i++) {
367
+ totalGap += this.years[i] - this.years[i - 1];
368
+ }
369
+ const averageYearGap = this.yearsLength > 1 ? totalGap / (this.yearsLength - 1) : 0;
370
+
371
+ return {
372
+ totalDataPoints: this.data.length,
373
+ yearRange: [this.firstYear, this.lastYear],
374
+ averageYearGap,
375
+ milestonesCount,
376
+ fuelsCount,
377
+ sectorsCount
378
+ };
379
+ }
380
+ }
@@ -0,0 +1,155 @@
1
+ import type {EnergyDataPoint} from '@/types';
2
+ import {DataValidationError} from '@/types';
3
+ import {EventBus} from '@/core/events/EventBus';
4
+ import {Logger} from "@/utils/Logger";
5
+ import {ConfigurationService} from "@/services/ConfigurationService";
6
+
7
+ /**
8
+ * Data Validation Service
9
+ *
10
+ * Comprehensive validation service ensuring data integrity and structure
11
+ * compliance for energy flow datasets. Performs multi-level validation
12
+ * including structure, content, and value range verification.
13
+ *
14
+ * Validation Levels:
15
+ * 1. Structure Validation - Array format and basic structure checks
16
+ * 2. Content Validation - Required sectors and breakdown verification
17
+ * 3. Type Validation - Ensures proper data types for all values
18
+ * 4. Range Validation - Warns about unusual values or potential errors
19
+ * 5. Duplicate Detection - Prevents duplicate years in dataset
20
+ *
21
+ * Error Handling:
22
+ * Throws DataValidationError with detailed context for failed validations,
23
+ * including field names, indices, and descriptive error messages.
24
+ * Emits validation events for service coordination and monitoring.
25
+ *
26
+ * Required Data Structure:
27
+ * Each data point must contain all major fuel types (solar, nuclear, hydro,
28
+ * wind, geothermal, gas, coal, biomass, petroleum) with consumption
29
+ * breakdowns across sectors (electricity, residential, agriculture,
30
+ * industrial, transportation).
31
+ *
32
+ * Provides comprehensive validation for energy flow datasets with
33
+ * multi-level checks ensuring data integrity, proper structure,
34
+ * and content completeness. Maintains strict validation rules
35
+ * to prevent downstream calculation errors.
36
+ */
37
+ export class DataValidationService {
38
+ constructor(private configurationService: ConfigurationService, private eventBus: EventBus, private logger: Logger) {
39
+ }
40
+
41
+ /**
42
+ * Validate complete data array with comprehensive checks
43
+ * Performs structure, content, and type validation for entire dataset
44
+ * @param data - Array of energy data points to validate
45
+ * @throws DataValidationError - If any validation check fails
46
+ */
47
+ public validateData(data: EnergyDataPoint[]): void {
48
+ const startTime = performance.now(); // Performance monitoring for validation efficiency
49
+
50
+ try {
51
+ // Basic array structure validation - Critical first line of defense
52
+ // Prevents downstream calculation errors from malformed input
53
+ if (!Array.isArray(data)) {
54
+ throw new DataValidationError('Data must be an array', 'data');
55
+ }
56
+
57
+ if (data.length === 0) {
58
+ throw new DataValidationError('Data array cannot be empty', 'data');
59
+ }
60
+
61
+ // Validate all required fuel sectors are present - Energy Industry Standard Compliance
62
+ // These sectors represent the complete US energy landscape per EIA standards
63
+ // Missing sectors would cause calculation failures in downstream services
64
+ const requiredSectors = ['elec', 'waste', 'solar', 'nuclear', 'hydro', 'wind', 'geo', 'gas', 'coal', 'bio', 'petro'];
65
+
66
+ // Validate consumption breakdown for each sector - Mathematical Integrity Check
67
+ // Each fuel sector must have consumption across all major sectors:
68
+ // elec (electricity), res (residential), ag (agriculture), indus (industrial), trans (transportation)
69
+ // This structure is required for the triple nested loop calculations in SummaryService
70
+ const requiredBreakdown = ['elec', 'res', 'ag', 'indus', 'trans'];
71
+
72
+ let hasHeatData = false;
73
+
74
+ // Validate each data point in the array - Comprehensive structural integrity check
75
+ // O(n) iteration ensures every data point meets strict requirements
76
+ for (let i = 0; i < data.length; i++) {
77
+ const point = data[i];
78
+
79
+ // Year validation - Critical for timeline functionality
80
+ // Year must be numeric for chronological sorting and animation
81
+ if (!point.year) {
82
+ throw new DataValidationError(`Invalid year at index ${i}`, 'year');
83
+ }
84
+
85
+ for (const sector of requiredSectors) {
86
+ if (!(sector in point)) {
87
+ throw new DataValidationError(`Missing sector '${sector}' in data point for year ${point.year}`, sector);
88
+ }
89
+
90
+ const sectorData = (point as any)[sector];
91
+ if (!sectorData || typeof sectorData !== 'object') {
92
+ throw new DataValidationError(`Invalid sector data for '${sector}' in year ${point.year}`, sector);
93
+ }
94
+
95
+ let currentRequiredBreakdown = [...requiredBreakdown];
96
+ if ("heat" in point) {
97
+ hasHeatData = true;
98
+ currentRequiredBreakdown.push("heat");
99
+ }
100
+
101
+ for (const breakdown of currentRequiredBreakdown) {
102
+ if (!(breakdown in sectorData) || typeof sectorData[breakdown] !== 'number') {
103
+ throw new DataValidationError(`Invalid breakdown '${breakdown}' for sector '${sector}' in year ${point.year}`, `${sector}.${breakdown}`);
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ if (hasHeatData) {
110
+ this.configurationService.hasHeatData = hasHeatData;
111
+ }
112
+ // this.configurationService.updateFuelAndBoxNames()
113
+
114
+ const endTime = performance.now();
115
+
116
+ // Emit validation success event for service coordination - Event-Driven Architecture
117
+ // This notifies other services that data is validated and safe to use
118
+ // Provides validation metadata for performance monitoring and debugging
119
+ this.eventBus.emit({
120
+ type: 'data.validated',
121
+ timestamp: Date.now(),
122
+ source: 'DataValidationService',
123
+ data: {
124
+ dataPointCount: data.length, // Dataset size for capacity planning
125
+ yearRange: data.length > 0 ? [data[0].year, data[data.length - 1].year] : [0, 0], // Temporal scope
126
+ validationTime: endTime - startTime // Performance metrics for optimization
127
+ }
128
+ });
129
+
130
+ // Debug logging when enabled - Performance Monitoring
131
+ // Tracks validation performance to identify potential bottlenecks
132
+ this.logger.log(`DataValidationService: ${data.length} data points validated in ${(endTime - startTime).toFixed(2)}ms`);
133
+
134
+ } catch (error) {
135
+ // Emit validation error event for error handling - Error Recovery Architecture
136
+ // Enables centralized error handling and user notification systems
137
+ // Marks validation errors as non-recoverable (require data fix)
138
+ this.eventBus.emit({
139
+ type: 'system.error',
140
+ timestamp: Date.now(),
141
+ source: 'DataValidationService',
142
+ data: {
143
+ error: error instanceof Error ? error : new Error(String(error)),
144
+ context: 'data_validation', // Error categorization for debugging
145
+ recoverable: false // Validation errors require data correction
146
+ }
147
+ });
148
+
149
+ // Re-throw error to maintain validation contract - Fail-Fast Pattern
150
+ // Prevents invalid data from propagating to calculation services
151
+ // Maintains data integrity throughout the system
152
+ throw error;
153
+ }
154
+ }
155
+ }