iris-meteo 0.0.1

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 (3) hide show
  1. package/README.md +80 -0
  2. package/index.js +585 -0
  3. package/package.json +22 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Iris Weather CLI
2
+
3
+ A command-line tool for correlating any CSV data with historical weather data from Open-Meteo.
4
+
5
+ ## Features
6
+
7
+ - **CSV-Weather Correlation**: Analyze relationships between any CSV data and weather conditions
8
+ - **Historical Data**: Use Open-Meteo archive endpoint for past dates
9
+ - **Flexible Filtering**: Filter both CSV and weather data
10
+ - **Comprehensive Statistics**: Pearson correlation, R-squared, and detailed analysis
11
+ - **Multiple Weather Parameters**: Temperature, precipitation, wind speed, etc.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install iris-meteo -g
17
+ ```
18
+
19
+ Refer to the open-meteo docs for how to install and run a local instance: [https://github.com/open-meteo/open-meteo/blob/main/docs/getting-started.md](https://github.com/open-meteo/open-meteo/blob/main/docs/getting-started.md)
20
+
21
+ ## Basic Usage
22
+
23
+ ### Correlate data with temperature
24
+ ```bash
25
+ iris --csv data.csv --latitude 52.52 --longitude 13.41
26
+ ```
27
+
28
+
29
+
30
+ ### Filter data before correlation
31
+ ```bash
32
+ iris --csv data.csv --latitude 52.52 --longitude 13.41 \
33
+ --filter "amount > 1000" --weather-filter "temperature_2m > 15"
34
+ ```
35
+
36
+ ### Custom field names and parameters
37
+ ```bash
38
+ iris --csv energy_data.csv --csv-date "timestamp" --csv-value "kwh_used" \
39
+ --weather-param precipitation --latitude 51.5074 --longitude -0.1278
40
+ ```
41
+
42
+ ## Options
43
+
44
+ ```
45
+ Usage: iris [options]
46
+
47
+ Options:
48
+ -V, --version output the version number
49
+ --csv <file> Load weather data from CSV file
50
+ --filter <expression> Filter CSV data using expressions like "temperature_2m:15" or "temperature_2m > 20"
51
+ --api-url <url> Open-Meteo API URL (for local instances) (default: "http://localhost:8080/v1/forecast")
52
+ --latitude <lat> Latitude for API requests
53
+ --longitude <lon> Longitude for API requests
54
+ --parameter <param> Weather parameter to analyze (e.g., temperature_2m, precipitation) (default: "temperature_2m")
55
+ --output <file> Output file for results
56
+ --verbose Show detailed output (default: false)
57
+ --o <filter> Filter API response data using expressions like "temperature_2m > 20" or "precipitation <= 5"
58
+ --correlate Analyze correlation between CSV and API data (default: false)
59
+ -h, --help display help for command
60
+ ```
61
+
62
+
63
+ ### Supported Date Formats
64
+ The tool supports multiple date formats for international compatibility:
65
+
66
+ - **ISO Format**: `YYYY-MM-DD` (e.g., `2023-01-15`)
67
+ - **European Format**: `DD.MM.YYYY` (e.g., `15.01.2023`)
68
+ - **US Format**: `MM/DD/YYYY` (e.g., `01/15/2023`)
69
+ - **ISO with Time**: `YYYY-MM-DDTHH:MM` (e.g., `2023-01-15T14:30`)
70
+
71
+ The tool will automatically detect and normalize all supported date formats to match with weather data.
72
+
73
+ ## Requirements
74
+
75
+ - Node.js 12+
76
+ - Local Open-Meteo instance (for API features)
77
+ - Internet connection (only if using public Open-Meteo API)
78
+
79
+ ## License
80
+ ISC
package/index.js ADDED
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs');
5
+ const csv = require('csv-parser');
6
+ const axios = require('axios');
7
+ const path = require('path');
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('iris')
13
+ .description('CLI tool for correlating any CSV data with weather data from open-meteo.com')
14
+ .version('1.0.0');
15
+
16
+ program
17
+ .option('--csv <file>', 'Load data from CSV file (must contain date column)')
18
+ .option('--csv-date <field>', 'CSV date field name', 'date')
19
+ .option('--csv-value <field>', 'CSV value field to correlate with weather', 'sales_amount')
20
+ .option('--weather-param <param>', 'Daily weather parameter to correlate with (e.g., temperature_2m_max, precipitation_sum, windspeed_10m_max)', 'temperature_2m_max')
21
+ .option('--api-url <url>', 'Open-Meteo API URL (for local instances)', 'http://localhost:8080/v1/forecast')
22
+ .option('--latitude <lat>', 'Latitude for weather data')
23
+ .option('--longitude <lon>', 'Longitude for weather data')
24
+ .option('--start-date <date>', 'Start date for analysis (YYYY-MM-DD)')
25
+ .option('--end-date <date>', 'End date for analysis (YYYY-MM-DD)')
26
+ .option('--filter <expression>', 'Filter CSV data using expressions like "sales_amount > 1000"')
27
+ .option('--weather-filter <expression>', 'Filter weather data using expressions like "temperature_2m > 15"')
28
+ .option('--output <file>', 'Output file for correlation results')
29
+ .option('--verbose', 'Show detailed correlation analysis', false);
30
+
31
+ program.parse(process.argv);
32
+
33
+ const options = program.opts();
34
+
35
+ // Main function to handle the CLI logic
36
+ async function main() {
37
+ try {
38
+ // Validate required options
39
+ if (!options.csv) {
40
+ console.error('Error: --csv option is required');
41
+ program.help();
42
+ process.exit(1);
43
+ }
44
+
45
+ if (!options.latitude || !options.longitude) {
46
+ console.error('Error: Both --latitude and --longitude are required');
47
+ program.help();
48
+ process.exit(1);
49
+ }
50
+
51
+ console.log('Iris Weather Correlation Analysis');
52
+ console.log('=================================');
53
+
54
+ // Load CSV data
55
+ const csvData = await loadAndProcessCsvData(options);
56
+ console.log(`Loaded ${csvData.length} records from CSV file`);
57
+
58
+ // Apply CSV filtering if specified
59
+ let filteredCsvData = csvData;
60
+ if (options.filter) {
61
+ filteredCsvData = filterCsvData(csvData, options.filter);
62
+ console.log(`🔍 Filtered CSV to ${filteredCsvData.length} records`);
63
+ }
64
+
65
+ // Determine date range for weather data
66
+ const dateRange = determineDateRange(filteredCsvData, options);
67
+ console.log(`Analyzing date range: ${dateRange.start} to ${dateRange.end}`);
68
+
69
+ // Fetch weather data
70
+ const weatherData = await fetchWeatherData(options, dateRange);
71
+ console.log(`Fetched ${weatherData.length} weather records from Open-Meteo API`);
72
+
73
+ // Apply weather filtering if specified
74
+ let filteredWeatherData = weatherData;
75
+ if (options.weatherFilter) {
76
+ filteredWeatherData = filterWeatherData(weatherData, options.weatherFilter);
77
+ console.log(`🌡️ Filtered weather data to ${filteredWeatherData.length} records`);
78
+ }
79
+
80
+ // Match CSV data with weather data by date
81
+ const matchedData = matchDataByDate(filteredCsvData, filteredWeatherData, options);
82
+ console.log(`Found ${matchedData.length} matching date pairs`);
83
+
84
+ if (matchedData.length < 2) {
85
+ console.warn('⚠️ Insufficient data for meaningful correlation analysis');
86
+ console.warn(' Try adjusting your date range or filters');
87
+ return;
88
+ }
89
+
90
+ // Perform correlation analysis
91
+ const correlationResults = analyzeCorrelation(matchedData, options);
92
+ displayCorrelationResults(correlationResults, options);
93
+
94
+ // Save results if requested
95
+ if (options.output) {
96
+ saveResultsToFile(correlationResults, options.output);
97
+ console.log(`💾 Results saved to ${options.output}`);
98
+ }
99
+
100
+ } catch (error) {
101
+ console.error('❌ Error:', error.message);
102
+ if (options.verbose) {
103
+ console.error(error.stack);
104
+ }
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ // Load and process CSV data
110
+ function loadAndProcessCsvData(options) {
111
+ return new Promise((resolve, reject) => {
112
+ if (!fs.existsSync(options.csv)) {
113
+ reject(new Error(`CSV file not found: ${options.csv}`));
114
+ return;
115
+ }
116
+
117
+ const results = [];
118
+ fs.createReadStream(options.csv)
119
+ .pipe(csv())
120
+ .on('data', (data) => results.push(data))
121
+ .on('end', () => {
122
+ if (results.length === 0) {
123
+ reject(new Error('CSV file is empty or contains no valid data'));
124
+ }
125
+
126
+ // Validate that required fields exist
127
+ if (!results[0].hasOwnProperty(options.csvDate)) {
128
+ reject(new Error(`CSV file does not contain a '${options.csvDate}' field`));
129
+ }
130
+
131
+ if (!results[0].hasOwnProperty(options.csvValue)) {
132
+ reject(new Error(`CSV file does not contain a '${options.csvValue}' field`));
133
+ }
134
+
135
+ resolve(results);
136
+ })
137
+ .on('error', (error) => reject(new Error(`Error reading CSV file: ${error.message}`)));
138
+ });
139
+ }
140
+
141
+ // Determine date range for analysis
142
+ function determineDateRange(csvData, options) {
143
+ // Parse and normalize dates from CSV data
144
+ const dateObjects = [];
145
+ const validDates = [];
146
+
147
+ csvData.forEach(item => {
148
+ let dateStr = item[options.csvDate];
149
+
150
+ // Remove time portion if present
151
+ if (dateStr.includes('T')) {
152
+ dateStr = dateStr.split('T')[0];
153
+ }
154
+
155
+ // Try to parse the date in various formats
156
+ const parsedDate = parseFlexibleDate(dateStr);
157
+ if (parsedDate) {
158
+ dateObjects.push(parsedDate);
159
+ validDates.push(dateStr);
160
+ }
161
+ });
162
+
163
+ if (dateObjects.length === 0) {
164
+ throw new Error('No valid dates found in CSV data. Supported formats: YYYY-MM-DD, DD.MM.YYYY, MM/DD/YYYY');
165
+ }
166
+
167
+ // Sort dates to find min and max
168
+ dateObjects.sort((a, b) => a - b);
169
+
170
+ // Format dates back to YYYY-MM-DD for API
171
+ const formatDate = (date) => {
172
+ return date.toISOString().split('T')[0];
173
+ };
174
+
175
+ const startDate = options.startDate || formatDate(dateObjects[0]);
176
+ const endDate = options.endDate || formatDate(dateObjects[dateObjects.length - 1]);
177
+
178
+ return { start: startDate, end: endDate };
179
+ }
180
+
181
+ // Parse dates in multiple formats
182
+ function parseFlexibleDate(dateStr) {
183
+ // Try ISO format first (YYYY-MM-DD)
184
+ if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) {
185
+ const date = new Date(dateStr);
186
+ if (!isNaN(date.getTime())) return date;
187
+ }
188
+
189
+ // Try European format (DD.MM.YYYY)
190
+ if (dateStr.match(/^\d{2}\.\d{2}\.\d{4}$/)) {
191
+ const parts = dateStr.split('.');
192
+ const date = new Date(`${parts[2]}-${parts[1]}-${parts[0]}`);
193
+ if (!isNaN(date.getTime())) return date;
194
+ }
195
+
196
+ // Try US format (MM/DD/YYYY)
197
+ if (dateStr.match(/^\d{2}\/\d{2}\/\d{4}$/)) {
198
+ const parts = dateStr.split('/');
199
+ const date = new Date(`${parts[2]}-${parts[0]}-${parts[1]}`);
200
+ if (!isNaN(date.getTime())) return date;
201
+ }
202
+
203
+ // Try ISO with time (YYYY-MM-DDTHH:MM)
204
+ if (dateStr.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/)) {
205
+ const date = new Date(dateStr);
206
+ if (!isNaN(date.getTime())) return date;
207
+ }
208
+
209
+ return null;
210
+ }
211
+
212
+ // Fetch weather data from Open-Meteo API
213
+ async function fetchWeatherData(options, dateRange) {
214
+ try {
215
+ let apiUrl = options.apiUrl;
216
+
217
+ // Map old hourly parameter names to their daily equivalents
218
+ const parameterMapping = {
219
+ 'temperature_2m': 'temperature_2m_max',
220
+ 'temperature': 'temperature_2m_max',
221
+ 'temp': 'temperature_2m_max',
222
+ 'precipitation': 'precipitation_sum',
223
+ 'rain': 'rain_sum',
224
+ 'windspeed': 'windspeed_10m_max',
225
+ 'wind': 'windspeed_10m_max'
226
+ };
227
+
228
+ // Apply parameter mapping if needed
229
+ let weatherParam = options.weatherParam;
230
+ if (parameterMapping[weatherParam]) {
231
+ console.log(`🔄 Mapping parameter '${weatherParam}' to daily equivalent '${parameterMapping[weatherParam]}'`);
232
+ weatherParam = parameterMapping[weatherParam];
233
+ }
234
+
235
+ const params = {
236
+ latitude: options.latitude,
237
+ longitude: options.longitude,
238
+ daily: weatherParam,
239
+ start_date: dateRange.start,
240
+ end_date: dateRange.end,
241
+ timezone: 'auto',
242
+ models: 'ecmwf_ifs025'
243
+ };
244
+
245
+ console.log(`Connecting to Open-Meteo API at: ${apiUrl}`);
246
+
247
+ const response = await axios.get(apiUrl, { params });
248
+
249
+ if (!response.data || !response.data.daily) {
250
+ throw new Error('Invalid API response format - expected daily weather data');
251
+ }
252
+
253
+ const dailyData = response.data.daily;
254
+ const timeData = dailyData.time;
255
+ const parameterData = dailyData[weatherParam];
256
+
257
+ if (!timeData || !parameterData || timeData.length !== parameterData.length) {
258
+ throw new Error('Inconsistent data in API response');
259
+ }
260
+
261
+ // Convert to date-value pairs for daily data
262
+ const results = [];
263
+ for (let i = 0; i < timeData.length; i++) {
264
+ results.push({
265
+ date: timeData[i],
266
+ [options.weatherParam]: parameterData[i] // Use original parameter name for consistency
267
+ });
268
+ }
269
+
270
+ return results;
271
+ } catch (error) {
272
+ throw new Error(`Failed to fetch weather data: ${error.message}`);
273
+ }
274
+ }
275
+
276
+
277
+
278
+ // Filter CSV data
279
+ function filterCsvData(data, filterExpression) {
280
+ const [field, operator, value] = parseFilterExpression(filterExpression);
281
+
282
+ return data.filter(item => {
283
+ const itemValue = parseFloat(item[field]);
284
+ const filterValue = parseFloat(value);
285
+
286
+ if (isNaN(itemValue) || isNaN(filterValue)) {
287
+ return false;
288
+ }
289
+
290
+ switch (operator) {
291
+ case '=':
292
+ case ':':
293
+ return itemValue === filterValue;
294
+ case '>':
295
+ return itemValue > filterValue;
296
+ case '<':
297
+ return itemValue < filterValue;
298
+ case '>=':
299
+ return itemValue >= filterValue;
300
+ case '<=':
301
+ return itemValue <= filterValue;
302
+ case '!=':
303
+ return itemValue !== filterValue;
304
+ default:
305
+ return false;
306
+ }
307
+ });
308
+ }
309
+
310
+ // Filter weather data
311
+ function filterWeatherData(data, filterExpression) {
312
+ const [field, operator, value] = parseFilterExpression(filterExpression);
313
+
314
+ return data.filter(item => {
315
+ const itemValue = parseFloat(item[field]);
316
+ const filterValue = parseFloat(value);
317
+
318
+ if (isNaN(itemValue) || isNaN(filterValue)) {
319
+ return false;
320
+ }
321
+
322
+ switch (operator) {
323
+ case '=':
324
+ case ':':
325
+ return itemValue === filterValue;
326
+ case '>':
327
+ return itemValue > filterValue;
328
+ case '<':
329
+ return itemValue < filterValue;
330
+ case '>=':
331
+ return itemValue >= filterValue;
332
+ case '<=':
333
+ return itemValue <= filterValue;
334
+ case '!=':
335
+ return itemValue !== filterValue;
336
+ default:
337
+ return false;
338
+ }
339
+ });
340
+ }
341
+
342
+ // Helper function to parse numbers in German format (commas as thousand separators, periods as decimals)
343
+ function parseGermanNumber(value) {
344
+ if (typeof value !== 'string') {
345
+ return parseFloat(value);
346
+ }
347
+
348
+ // Remove thousand separators (commas) and replace decimal points with dots
349
+ const cleanedValue = value.replace(/\./g, '').replace(/,/g, '.');
350
+ return parseFloat(cleanedValue);
351
+ }
352
+
353
+ // Match CSV data with weather data by date
354
+ function matchDataByDate(csvData, weatherData, options) {
355
+ const matchedPairs = [];
356
+
357
+ // Create a map of weather data by date for faster lookup
358
+ const weatherMap = {};
359
+ weatherData.forEach(weatherItem => {
360
+ const date = weatherItem.date;
361
+ if (!weatherMap[date]) {
362
+ weatherMap[date] = [];
363
+ }
364
+ weatherMap[date].push(weatherItem);
365
+ });
366
+
367
+ // Match each CSV record with corresponding weather data
368
+ csvData.forEach(csvItem => {
369
+ // Extract and normalize date
370
+ let dateStr = csvItem[options.csvDate];
371
+
372
+ // Parse the CSV date to get the normalized format (YYYY-MM-DD)
373
+ const parsedDate = parseFlexibleDate(dateStr);
374
+ if (!parsedDate) {
375
+ console.warn(`⚠️ Skipping record with unparseable date: ${dateStr}`);
376
+ return;
377
+ }
378
+
379
+ // Format as YYYY-MM-DD for matching
380
+ const normalizedDate = parsedDate.toISOString().split('T')[0];
381
+
382
+ // Find matching weather data
383
+ if (weatherMap[normalizedDate] && weatherMap[normalizedDate].length > 0) {
384
+ // Use the first weather record for the day (could be enhanced to use average, etc.)
385
+ const weatherItem = weatherMap[normalizedDate][0];
386
+
387
+ const csvValue = parseGermanNumber(csvItem[options.csvValue]);
388
+ const weatherValue = parseFloat(weatherItem[options.weatherParam]);
389
+
390
+ // Debug logging removed
391
+
392
+ matchedPairs.push({
393
+ date: normalizedDate,
394
+ originalDate: dateStr,
395
+ csvValue: csvValue,
396
+ weatherValue: weatherValue,
397
+ csvData: csvItem,
398
+ weatherData: weatherItem
399
+ });
400
+ } else {
401
+ console.warn(`⚠️ No weather data found for date: ${normalizedDate} (original: ${dateStr})`);
402
+ }
403
+ });
404
+
405
+ return matchedPairs;
406
+ }
407
+
408
+ // Parse filter expression
409
+ function parseFilterExpression(expression) {
410
+ // Handle equality operators (= or :)
411
+ const equalityMatch = expression.match(/^(\w+)\s*(=|:)\s*(\S+)$/);
412
+ if (equalityMatch) {
413
+ return [equalityMatch[1], '=', equalityMatch[3]];
414
+ }
415
+
416
+ // Handle comparison operators
417
+ const comparisonMatch = expression.match(/^(\w+)\s*(>=|<=|>|<|!=)\s*(\S+)$/);
418
+ if (comparisonMatch) {
419
+ return [comparisonMatch[1], comparisonMatch[2], comparisonMatch[3]];
420
+ }
421
+
422
+ throw new Error(`Invalid filter expression: ${expression}. Use format like "field:value" or "field > value"`);
423
+ }
424
+
425
+ // Analyze correlation between CSV values and weather data
426
+ function analyzeCorrelation(matchedData, options) {
427
+ // Extract values
428
+ const csvValues = [];
429
+ const weatherValues = [];
430
+
431
+ matchedData.forEach(pair => {
432
+ if (!isNaN(pair.csvValue) && !isNaN(pair.weatherValue)) {
433
+ csvValues.push(pair.csvValue);
434
+ weatherValues.push(pair.weatherValue);
435
+ }
436
+ });
437
+
438
+ // Debug logging removed
439
+
440
+ if (csvValues.length < 2) {
441
+ return {
442
+ csvField: options.csvValue,
443
+ weatherParam: options.weatherParam,
444
+ dataPoints: csvValues.length,
445
+ correlation: null,
446
+ rSquared: null,
447
+ message: 'Insufficient matching data points for correlation analysis'
448
+ };
449
+ }
450
+
451
+ // Calculate Pearson correlation coefficient
452
+ const correlation = calculatePearsonCorrelation(csvValues, weatherValues);
453
+ const rSquared = Math.pow(correlation, 2);
454
+
455
+ // Calculate additional statistics
456
+ const csvStats = calculateBasicStats(csvValues);
457
+ const weatherStats = calculateBasicStats(weatherValues);
458
+
459
+ return {
460
+ csvField: options.csvValue,
461
+ weatherParam: options.weatherParam,
462
+ dataPoints: csvValues.length,
463
+ correlation: correlation,
464
+ rSquared: rSquared,
465
+ interpretation: interpretCorrelation(correlation),
466
+ csvStats: csvStats,
467
+ weatherStats: weatherStats,
468
+ matchedData: matchedData
469
+ };
470
+ }
471
+
472
+ // Calculate basic statistics
473
+ function calculateBasicStats(values) {
474
+ if (values.length === 0) {
475
+ return {
476
+ min: null,
477
+ max: null,
478
+ average: null,
479
+ median: null
480
+ };
481
+ }
482
+
483
+ const sortedValues = [...values].sort((a, b) => a - b);
484
+
485
+ return {
486
+ min: Math.min(...values),
487
+ max: Math.max(...values),
488
+ average: values.reduce((sum, val) => sum + val, 0) / values.length,
489
+ median: calculateMedian(sortedValues)
490
+ };
491
+ }
492
+
493
+ // Calculate median value
494
+ function calculateMedian(sortedValues) {
495
+ const middle = Math.floor(sortedValues.length / 2);
496
+
497
+ if (sortedValues.length % 2 === 0) {
498
+ return (sortedValues[middle - 1] + sortedValues[middle]) / 2;
499
+ } else {
500
+ return sortedValues[middle];
501
+ }
502
+ }
503
+
504
+ // Calculate Pearson correlation coefficient
505
+ function calculatePearsonCorrelation(x, y) {
506
+ const n = x.length;
507
+
508
+ // Calculate means
509
+ const meanX = x.reduce((sum, val) => sum + val, 0) / n;
510
+ const meanY = y.reduce((sum, val) => sum + val, 0) / n;
511
+
512
+ // Calculate covariance and standard deviations
513
+ let covariance = 0;
514
+ let stdDevX = 0;
515
+ let stdDevY = 0;
516
+
517
+ for (let i = 0; i < n; i++) {
518
+ const diffX = x[i] - meanX;
519
+ const diffY = y[i] - meanY;
520
+ covariance += diffX * diffY;
521
+ stdDevX += Math.pow(diffX, 2);
522
+ stdDevY += Math.pow(diffY, 2);
523
+ }
524
+
525
+ // Handle division by zero
526
+ if (stdDevX === 0 || stdDevY === 0) {
527
+ return 0;
528
+ }
529
+
530
+ return covariance / (Math.sqrt(stdDevX) * Math.sqrt(stdDevY));
531
+ }
532
+
533
+ // Interpret correlation coefficient
534
+ function interpretCorrelation(r) {
535
+ const absR = Math.abs(r);
536
+
537
+ if (absR >= 0.9) return 'Very strong correlation';
538
+ if (absR >= 0.7) return 'Strong correlation';
539
+ if (absR >= 0.5) return 'Moderate correlation';
540
+ if (absR >= 0.3) return 'Weak correlation';
541
+ return 'No or negligible correlation';
542
+ }
543
+
544
+ // Display correlation results
545
+ function displayCorrelationResults(results, options) {
546
+ console.log('\nCorrelation Analysis Results');
547
+ console.log('============================');
548
+ console.log(`CSV Field: ${results.csvField}`);
549
+ console.log(`Weather Parameter: ${results.weatherParam}`);
550
+ console.log(`Data Points Analyzed: ${results.dataPoints}`);
551
+
552
+ if (results.correlation !== null) {
553
+ console.log(`\nStatistical Results:`);
554
+ console.log(` Pearson Correlation (r): ${results.correlation.toFixed(4)}`);
555
+ console.log(` R-squared: ${results.rSquared.toFixed(4)}`);
556
+ console.log(` Interpretation: ${results.interpretation}`);
557
+
558
+
559
+ } else {
560
+ console.log(`${results.message}`);
561
+ }
562
+ }
563
+
564
+ // Save results to file
565
+ function saveResultsToFile(results, filePath) {
566
+ const output = {
567
+ timestamp: new Date().toISOString(),
568
+ analysisType: 'CSV-Weather Correlation',
569
+ correlationResults: {
570
+ csvField: results.csvField,
571
+ weatherParam: results.weatherParam,
572
+ dataPoints: results.dataPoints,
573
+ correlation: results.correlation,
574
+ rSquared: results.rSquared,
575
+ interpretation: results.interpretation,
576
+ csvStats: results.csvStats,
577
+ weatherStats: results.weatherStats
578
+ }
579
+ };
580
+
581
+ fs.writeFileSync(filePath, JSON.stringify(output, null, 2), 'utf8');
582
+ }
583
+
584
+ // Start the application
585
+ main();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "iris-meteo",
3
+ "version": "0.0.1",
4
+ "description": "CLI tool for analyzing weather data with open-meteo.com",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "iris": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "start": "node index.js"
12
+ },
13
+ "keywords": ["weather", "cli", "open-meteo", "analysis"],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "type": "commonjs",
17
+ "dependencies": {
18
+ "axios": "^1.13.5",
19
+ "commander": "^14.0.3",
20
+ "csv-parser": "^3.2.0"
21
+ }
22
+ }