@energiok/node-red-contrib-pricecontrol-thermal 1.2.0 → 1.2.2

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.
@@ -1,114 +0,0 @@
1
- /**
2
- * Test: Hourly alignment when minModeDuration >= 60
3
- *
4
- * When minModeDuration is 60 minutes or more, mode changes should
5
- * align to hour boundaries (16:00, 17:00) instead of quarter-hours
6
- * (16:15, 17:30) because electricity billing is hourly.
7
- */
8
-
9
- const { createSchedule } = require('./src/strategy-smart-thermal-functions.js');
10
-
11
- // 15-minute interval prices for a day
12
- const prices = [
13
- { timestamp: "2026-01-17T14:00:00Z", price: 100 },
14
- { timestamp: "2026-01-17T14:15:00Z", price: 102 },
15
- { timestamp: "2026-01-17T14:30:00Z", price: 105 },
16
- { timestamp: "2026-01-17T14:45:00Z", price: 108 },
17
- { timestamp: "2026-01-17T15:00:00Z", price: 115 }, // 16:00 Oslo - getting expensive
18
- { timestamp: "2026-01-17T15:15:00Z", price: 120 },
19
- { timestamp: "2026-01-17T15:30:00Z", price: 125 },
20
- { timestamp: "2026-01-17T15:45:00Z", price: 128 },
21
- { timestamp: "2026-01-17T16:00:00Z", price: 130 }, // 17:00 Oslo - peak
22
- { timestamp: "2026-01-17T16:15:00Z", price: 128 },
23
- { timestamp: "2026-01-17T16:30:00Z", price: 125 },
24
- { timestamp: "2026-01-17T16:45:00Z", price: 122 },
25
- { timestamp: "2026-01-17T17:00:00Z", price: 118 }, // 18:00 Oslo
26
- { timestamp: "2026-01-17T17:15:00Z", price: 115 },
27
- { timestamp: "2026-01-17T17:30:00Z", price: 110 },
28
- { timestamp: "2026-01-17T17:45:00Z", price: 105 },
29
- { timestamp: "2026-01-17T18:00:00Z", price: 100 }, // 19:00 Oslo
30
- { timestamp: "2026-01-17T18:15:00Z", price: 95 },
31
- { timestamp: "2026-01-17T18:30:00Z", price: 90 },
32
- { timestamp: "2026-01-17T18:45:00Z", price: 88 },
33
- { timestamp: "2026-01-17T19:00:00Z", price: 85 }, // 20:00 Oslo - cheap
34
- { timestamp: "2026-01-17T19:15:00Z", price: 82 },
35
- { timestamp: "2026-01-17T19:30:00Z", price: 80 },
36
- { timestamp: "2026-01-17T19:45:00Z", price: 78 },
37
- ];
38
-
39
- const baseConfig = {
40
- numPriceGroups: 3,
41
- minSavingsPercent: 5,
42
- outputTimezone: "Europe/Oslo",
43
- heatLossCoefficient: 0.05,
44
- };
45
-
46
- console.log("=== HOURLY ALIGNMENT TEST ===\n");
47
-
48
- // Test with 30-minute min duration (should NOT align to hours)
49
- console.log("--- With minModeDuration: 30 (NO hourly alignment) ---\n");
50
- const schedule30 = createSchedule(prices, 5, { ...baseConfig, minModeDuration: 30 });
51
-
52
- console.log("Oslo Time | Mode | Price");
53
- console.log("-".repeat(35));
54
- schedule30.forEach(entry => {
55
- const oslo = entry.time.substring(11, 16);
56
- console.log(`${oslo} | ${entry.mode.padEnd(6)} | ${entry.price.toFixed(0)} EUR`);
57
- });
58
-
59
- // Find mode transitions
60
- const transitions30 = [];
61
- for (let i = 1; i < schedule30.length; i++) {
62
- if (schedule30[i].mode !== schedule30[i-1].mode) {
63
- transitions30.push(schedule30[i].time.substring(11, 16));
64
- }
65
- }
66
- console.log(`\nMode transitions at: ${transitions30.join(", ")}`);
67
-
68
- // Test with 60-minute min duration (SHOULD align to hours)
69
- console.log("\n--- With minModeDuration: 60 (WITH hourly alignment) ---\n");
70
- const schedule60 = createSchedule(prices, 5, { ...baseConfig, minModeDuration: 60 });
71
-
72
- console.log("Oslo Time | Mode | Price");
73
- console.log("-".repeat(35));
74
- schedule60.forEach(entry => {
75
- const oslo = entry.time.substring(11, 16);
76
- console.log(`${oslo} | ${entry.mode.padEnd(6)} | ${entry.price.toFixed(0)} EUR`);
77
- });
78
-
79
- // Find mode transitions
80
- const transitions60 = [];
81
- for (let i = 1; i < schedule60.length; i++) {
82
- if (schedule60[i].mode !== schedule60[i-1].mode) {
83
- transitions60.push(schedule60[i].time.substring(11, 16));
84
- }
85
- }
86
- console.log(`\nMode transitions at: ${transitions60.join(", ")}`);
87
-
88
- // Verify transitions are on hour boundaries
89
- const allOnHour = transitions60.every(t => t.endsWith(":00"));
90
- console.log(`\nAll transitions on hour boundaries: ${allOnHour ? "YES ✓" : "NO ✗"}`);
91
-
92
- // Test with 120-minute min duration
93
- console.log("\n--- With minModeDuration: 120 (WITH hourly alignment) ---\n");
94
- const schedule120 = createSchedule(prices, 5, { ...baseConfig, minModeDuration: 120 });
95
-
96
- console.log("Oslo Time | Mode | Price");
97
- console.log("-".repeat(35));
98
- schedule120.forEach(entry => {
99
- const oslo = entry.time.substring(11, 16);
100
- console.log(`${oslo} | ${entry.mode.padEnd(6)} | ${entry.price.toFixed(0)} EUR`);
101
- });
102
-
103
- const transitions120 = [];
104
- for (let i = 1; i < schedule120.length; i++) {
105
- if (schedule120[i].mode !== schedule120[i-1].mode) {
106
- transitions120.push(schedule120[i].time.substring(11, 16));
107
- }
108
- }
109
- console.log(`\nMode transitions at: ${transitions120.join(", ")}`);
110
-
111
- console.log("\n=== CONCLUSION ===");
112
- console.log("✓ minModeDuration < 60: transitions can be at any quarter-hour");
113
- console.log("✓ minModeDuration >= 60: transitions aligned to hour boundaries");
114
- console.log("✓ This matches electricity billing periods (hourly)");
@@ -1,77 +0,0 @@
1
- const { createSchedule } = require('./src/strategy-smart-thermal-functions.js');
2
-
3
- const prices = [{"price":83.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:00:00Z"},{"price":82.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:15:00Z"},{"price":81.91,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:30:00Z"},{"price":80.89,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:45:00Z"},{"price":81.62,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:00:00Z"},{"price":80.6,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:15:00Z"},{"price":79.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:30:00Z"},{"price":77.88,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:45:00Z"},{"price":78.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:00:00Z"},{"price":77.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:15:00Z"},{"price":75.03,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:30:00Z"},{"price":74.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:45:00Z"},{"price":72.9,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:00:00Z"},{"price":72.47,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:15:00Z"},{"price":72.89,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:30:00Z"},{"price":73.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:45:00Z"},{"price":74.65,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:00:00Z"},{"price":75.09,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:15:00Z"},{"price":77.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:30:00Z"},{"price":78.48,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:45:00Z"},{"price":80.92,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:00:00Z"},{"price":82.07,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:15:00Z"},{"price":83.29,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:30:00Z"},{"price":84.17,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:45:00Z"},{"price":83.98,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:00:00Z"},{"price":85.58,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:15:00Z"},{"price":86.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:30:00Z"},{"price":87.82,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:45:00Z"},{"price":87.25,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:00:00Z"},{"price":87.91,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:15:00Z"},{"price":88.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:30:00Z"},{"price":88.69,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:45:00Z"},{"price":88.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:00:00Z"},{"price":88.55,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:15:00Z"},{"price":88.88,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:30:00Z"},{"price":88.97,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:45:00Z"},{"price":88.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:00:00Z"},{"price":88.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:15:00Z"},{"price":88.86,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:30:00Z"},{"price":89.01,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:45:00Z"},{"price":88.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:00:00Z"},{"price":88.24,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:15:00Z"},{"price":88.21,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:30:00Z"},{"price":87.97,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:45:00Z"},{"price":89.66,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:00:00Z"},{"price":89.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:15:00Z"},{"price":89.48,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:30:00Z"},{"price":89.49,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:45:00Z"},{"price":88.75,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:00:00Z"},{"price":89.62,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:15:00Z"},{"price":90.03,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:30:00Z"},{"price":90.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:45:00Z"},{"price":90.12,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:00:00Z"},{"price":90.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:15:00Z"},{"price":92.33,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:30:00Z"},{"price":94.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:45:00Z"},{"price":91.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:00:00Z"},{"price":93.01,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:15:00Z"},{"price":93.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:30:00Z"},{"price":93.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:45:00Z"},{"price":92.2,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:00:00Z"},{"price":93.38,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:15:00Z"},{"price":93.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:30:00Z"},{"price":93.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:45:00Z"},{"price":94,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:00:00Z"},{"price":95.16,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:15:00Z"},{"price":96.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:30:00Z"},{"price":101.04,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:45:00Z"},{"price":96.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:00:00Z"},{"price":95.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:15:00Z"},{"price":95.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:30:00Z"},{"price":95.29,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:45:00Z"},{"price":95.16,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:00:00Z"},{"price":95,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:15:00Z"},{"price":95.31,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:30:00Z"},{"price":95.13,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:45:00Z"},{"price":95.2,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:00:00Z"},{"price":94.93,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:15:00Z"},{"price":94.79,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:30:00Z"},{"price":94.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:45:00Z"},{"price":94.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:00:00Z"},{"price":94.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:15:00Z"},{"price":94.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:30:00Z"},{"price":94.21,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:45:00Z"},{"price":94,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:00:00Z"},{"price":93.94,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:15:00Z"},{"price":93.7,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:30:00Z"},{"price":93.49,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:45:00Z"},{"price":93.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:00:00Z"},{"price":93.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:15:00Z"},{"price":93.02,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:30:00Z"},{"price":92.7,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:45:00Z"},{"price":93.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:00:00Z"},{"price":92.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:15:00Z"},{"price":92.08,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:30:00Z"},{"price":91.1,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:45:00Z"}];
4
-
5
- const outdoorTemp = 5.0; // Example outdoor temperature
6
-
7
- const config = {
8
- heatLossCoefficient: 0.05,
9
- minModeDuration: 60,
10
- minSavingsPercent: 5,
11
- };
12
-
13
- const schedule = createSchedule(prices, outdoorTemp, config);
14
-
15
- // Calculate average price
16
- const avgPrice = prices.reduce((sum, p) => sum + p.price, 0) / prices.length;
17
- console.log(`Average price: ${avgPrice.toFixed(2)} EUR/MWh\n`);
18
-
19
- if (schedule.length > 0 && schedule[0].tuning) {
20
- const tuning = schedule[0].tuning;
21
- console.log('=== TUNING INFO ===');
22
- console.log(`Outdoor temp: ${tuning.outdoorTemp}C`);
23
- console.log(`Min savings percent: ${tuning.minSavingsPercent}%`);
24
- console.log(`Max safe coast time: ${tuning.maxCoastMinutes.toFixed(0)} minutes (${(tuning.maxCoastMinutes / 60).toFixed(1)} hours)`);
25
- console.log();
26
- }
27
-
28
- // Group by hour and mode
29
- const hourly = {};
30
- schedule.forEach(entry => {
31
- const date = new Date(entry.time);
32
- const hour = date.getUTCHours();
33
- const hourKey = `${hour.toString().padStart(2, '0')}:00`;
34
-
35
- if (!hourly[hourKey]) {
36
- hourly[hourKey] = { normal: 0, boost: 0, coast: 0, prices: [] };
37
- }
38
-
39
- hourly[hourKey][entry.mode]++;
40
- hourly[hourKey].prices.push(entry.price.toFixed(2));
41
- });
42
-
43
- // Display results
44
- console.log('Hour-by-hour breakdown:\n');
45
- console.log('Time | Mode Distribution | Avg Price | Status');
46
- console.log('------|----------------------------|-----------|--------');
47
-
48
- Object.keys(hourly).sort().forEach(hour => {
49
- const data = hourly[hour];
50
- const total = data.normal + data.boost + data.coast;
51
- const avgHourPrice = data.prices.reduce((sum, p) => sum + parseFloat(p), 0) / data.prices.length;
52
-
53
- let dominantMode = 'normal';
54
- if (data.boost > data.normal && data.boost > data.coast) dominantMode = 'boost';
55
- if (data.coast > data.normal && data.coast > data.boost) dominantMode = 'coast';
56
-
57
- const modeStr = `N:${data.normal} B:${data.boost} C:${data.coast}`;
58
- const status = dominantMode === 'boost' ? '🟢 ON ' : dominantMode === 'coast' ? '🔴 OFF' : '🔵 NORM';
59
-
60
- console.log(`${hour} | ${modeStr.padEnd(26)} | ${avgHourPrice.toFixed(2).padStart(9)} | ${status}`);
61
- });
62
-
63
- // Summary
64
- console.log('\n=== SUMMARY ===\n');
65
- const totalIntervals = schedule.length;
66
- const normalCount = schedule.filter(s => s.mode === 'normal').length;
67
- const boostCount = schedule.filter(s => s.mode === 'boost').length;
68
- const coastCount = schedule.filter(s => s.mode === 'coast').length;
69
-
70
- console.log(`Total intervals: ${totalIntervals}`);
71
- console.log(`Normal: ${normalCount} (${(normalCount/totalIntervals*100).toFixed(1)}%)`);
72
- console.log(`Boost: ${boostCount} (${(boostCount/totalIntervals*100).toFixed(1)}%)`);
73
- console.log(`Coast: ${coastCount} (${(coastCount/totalIntervals*100).toFixed(1)}%)`);
74
-
75
- console.log('\n🟢 Boost = Pre-heat (heat pump ON at high temp)');
76
- console.log('🔵 Normal = Regular operation (heat pump maintains setpoint)');
77
- console.log('🔴 Coast = Turn off (heat pump OFF, temperature coasts down)');
package/test-runway.js DELETED
@@ -1,118 +0,0 @@
1
- /**
2
- * Test: Runway/lookahead extension for coast periods
3
- *
4
- * The Saturday evening scenario:
5
- * - Coast ends at 18:00 (123.38 EUR)
6
- * - 18:15-20:00 still expensive (121-125 EUR)
7
- * - 20:15 onwards gets cheaper (106, 104, 99...)
8
- *
9
- * Expected: Coast should extend to ~20:00-20:15 to "land" on cheaper prices
10
- */
11
-
12
- const { createSchedule } = require('./src/strategy-smart-thermal-functions.js');
13
-
14
- // Saturday prices from the user's actual data
15
- const saturdayPrices = [
16
- { timestamp: "2026-01-17T14:00:00Z", price: 100.32 },
17
- { timestamp: "2026-01-17T14:15:00Z", price: 105.32 },
18
- { timestamp: "2026-01-17T14:30:00Z", price: 110.81 },
19
- { timestamp: "2026-01-17T14:45:00Z", price: 113.24 },
20
- { timestamp: "2026-01-17T15:00:00Z", price: 111.59 },
21
- { timestamp: "2026-01-17T15:15:00Z", price: 120.96 },
22
- { timestamp: "2026-01-17T15:30:00Z", price: 122.54 }, // Coast starts
23
- { timestamp: "2026-01-17T15:45:00Z", price: 123.82 },
24
- { timestamp: "2026-01-17T16:00:00Z", price: 128.80 },
25
- { timestamp: "2026-01-17T16:15:00Z", price: 126.62 },
26
- { timestamp: "2026-01-17T16:30:00Z", price: 127.73 },
27
- { timestamp: "2026-01-17T16:45:00Z", price: 131.06 }, // Peak
28
- { timestamp: "2026-01-17T17:00:00Z", price: 123.38 }, // Coast ends here in old algorithm
29
- { timestamp: "2026-01-17T17:15:00Z", price: 121.69 }, // Still expensive!
30
- { timestamp: "2026-01-17T17:30:00Z", price: 125.39 },
31
- { timestamp: "2026-01-17T17:45:00Z", price: 124.59 },
32
- { timestamp: "2026-01-17T18:00:00Z", price: 122.52 },
33
- { timestamp: "2026-01-17T18:15:00Z", price: 121.64 },
34
- { timestamp: "2026-01-17T18:30:00Z", price: 120.84 },
35
- { timestamp: "2026-01-17T18:45:00Z", price: 113.53 },
36
- { timestamp: "2026-01-17T19:00:00Z", price: 119.74 },
37
- { timestamp: "2026-01-17T19:15:00Z", price: 106.23 }, // Getting cheaper
38
- { timestamp: "2026-01-17T19:30:00Z", price: 104.52 },
39
- { timestamp: "2026-01-17T19:45:00Z", price: 99.31 }, // Much cheaper - good landing zone
40
- { timestamp: "2026-01-17T20:00:00Z", price: 104.05 },
41
- { timestamp: "2026-01-17T20:15:00Z", price: 100.21 },
42
- { timestamp: "2026-01-17T20:30:00Z", price: 98.94 },
43
- { timestamp: "2026-01-17T20:45:00Z", price: 96.23 },
44
- ];
45
-
46
- const config = {
47
- heatLossCoefficient: 0.05,
48
- minModeDuration: 60,
49
- minSavingsPercent: 8,
50
- minSavingsAbsolute: 0,
51
- numPriceGroups: 5,
52
- outputTimezone: "Europe/Oslo",
53
- extremeWeatherThreshold: -15,
54
- };
55
-
56
- const outdoorTemp = 5;
57
-
58
- console.log("=== RUNWAY/LOOKAHEAD TEST ===\n");
59
-
60
- const schedule = createSchedule(saturdayPrices, outdoorTemp, config);
61
-
62
- console.log("Tuning info:");
63
- const tuning = schedule[0]?.tuning;
64
- if (tuning) {
65
- console.log(` Cheap threshold: ${tuning.clustering.cheapThreshold.toFixed(2)} EUR`);
66
- console.log(` Expensive threshold: ${tuning.clustering.expensiveThreshold.toFixed(2)} EUR`);
67
- console.log(` Landing threshold (midpoint): ${((tuning.clustering.cheapThreshold + tuning.clustering.expensiveThreshold) / 2).toFixed(2)} EUR`);
68
- console.log(` Max coast intervals: ${tuning.maxCoastIntervals}`);
69
- }
70
-
71
- console.log("\nSchedule (Oslo time):");
72
- console.log("-".repeat(50));
73
- schedule.forEach(entry => {
74
- const oslo = entry.time.substring(11, 16);
75
- const mode = entry.mode.padEnd(6);
76
- const price = entry.price.toFixed(2);
77
- const marker = entry.mode === "coast" ? " <-- COAST" : "";
78
- console.log(` ${oslo} ${mode} ${price} EUR${marker}`);
79
- });
80
-
81
- // Count coast intervals
82
- const coastCount = schedule.filter(s => s.mode === "coast").length;
83
- const coastMinutes = coastCount * 15;
84
-
85
- console.log("\n" + "=".repeat(50));
86
- console.log(`Total coast: ${coastCount} intervals (${coastMinutes} minutes)`);
87
-
88
- // Find coast end time
89
- const coastIntervals = schedule.filter(s => s.mode === "coast");
90
- if (coastIntervals.length > 0) {
91
- const lastCoast = coastIntervals[coastIntervals.length - 1];
92
- console.log(`Coast ends at: ${lastCoast.time.substring(11, 16)} Oslo`);
93
-
94
- // What's next after coast?
95
- const lastCoastIndex = schedule.indexOf(lastCoast);
96
- if (lastCoastIndex < schedule.length - 1) {
97
- const nextAfterCoast = schedule[lastCoastIndex + 1];
98
- console.log(`Next mode: ${nextAfterCoast.mode} at ${nextAfterCoast.time.substring(11, 16)} (${nextAfterCoast.price.toFixed(2)} EUR)`);
99
- }
100
- }
101
-
102
- console.log("\n=== ANALYSIS ===");
103
- // Check if coast extends past 18:00
104
- const extendedPast1800 = coastIntervals.some(c => {
105
- const hour = parseInt(c.time.substring(11, 13));
106
- return hour >= 19;
107
- });
108
- console.log(`Coast extends past 19:00 Oslo: ${extendedPast1800 ? "YES ✓" : "NO"}`);
109
-
110
- // Check if we land on cheaper prices
111
- const lastCoastEntry = coastIntervals[coastIntervals.length - 1];
112
- const lastCoastIdx = schedule.indexOf(lastCoastEntry);
113
- if (lastCoastIdx < schedule.length - 1) {
114
- const landingPrice = schedule[lastCoastIdx + 1].price;
115
- const peakPrice = Math.max(...coastIntervals.map(c => c.price));
116
- const savings = peakPrice - landingPrice;
117
- console.log(`Landing price: ${landingPrice.toFixed(2)} EUR (saving ${savings.toFixed(2)} EUR vs peak)`);
118
- }
@@ -1,132 +0,0 @@
1
- /**
2
- * Debug: Why doesn't the Saturday peak get coasted?
3
- */
4
-
5
- const { DateTime } = require("luxon");
6
- const { createSchedule, classifyPricesWithCkmeans } = require('./src/strategy-smart-thermal-functions.js');
7
-
8
- const prices = [{"price":97.41,"currency":"EUR","area":"NO1","timestamp":"2026-01-14T23:00:00Z"},{"price":95.18,"currency":"EUR","area":"NO1","timestamp":"2026-01-14T23:15:00Z"},{"price":92.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-14T23:30:00Z"},{"price":90.1,"currency":"EUR","area":"NO1","timestamp":"2026-01-14T23:45:00Z"},{"price":94.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T00:00:00Z"},{"price":92.32,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T00:15:00Z"},{"price":90,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T00:30:00Z"},{"price":88.44,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T00:45:00Z"},{"price":90.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T01:00:00Z"},{"price":86.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T01:15:00Z"},{"price":84.8,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T01:30:00Z"},{"price":84.1,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T01:45:00Z"},{"price":83.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T02:00:00Z"},{"price":83.11,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T02:15:00Z"},{"price":81.99,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T02:30:00Z"},{"price":80.98,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T02:45:00Z"},{"price":80.33,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T03:00:00Z"},{"price":80.33,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T03:15:00Z"},{"price":80.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T03:30:00Z"},{"price":81.73,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T03:45:00Z"},{"price":81.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T04:00:00Z"},{"price":82.47,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T04:15:00Z"},{"price":84.17,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T04:30:00Z"},{"price":86.6,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T04:45:00Z"},{"price":88.49,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T05:00:00Z"},{"price":92.15,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T05:15:00Z"},{"price":93.8,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T05:30:00Z"},{"price":95.54,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T05:45:00Z"},{"price":95.2,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T06:00:00Z"},{"price":98.28,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T06:15:00Z"},{"price":101.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T06:30:00Z"},{"price":103.04,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T06:45:00Z"},{"price":98.12,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T07:00:00Z"},{"price":99.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T07:15:00Z"},{"price":99.67,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T07:30:00Z"},{"price":99.31,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T07:45:00Z"},{"price":107.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T08:00:00Z"},{"price":104.01,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T08:15:00Z"},{"price":100.59,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T08:30:00Z"},{"price":98.28,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T08:45:00Z"},{"price":108.27,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T09:00:00Z"},{"price":104.8,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T09:15:00Z"},{"price":100.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T09:30:00Z"},{"price":98.62,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T09:45:00Z"},{"price":98.98,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T10:00:00Z"},{"price":96.95,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T10:15:00Z"},{"price":95.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T10:30:00Z"},{"price":95.11,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T10:45:00Z"},{"price":99.43,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T11:00:00Z"},{"price":96.57,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T11:15:00Z"},{"price":96.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T11:30:00Z"},{"price":99.4,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T11:45:00Z"},{"price":101.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T12:00:00Z"},{"price":102.56,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T12:15:00Z"},{"price":104.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T12:30:00Z"},{"price":101.99,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T12:45:00Z"},{"price":98.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T13:00:00Z"},{"price":102.35,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T13:15:00Z"},{"price":101.57,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T13:30:00Z"},{"price":110.66,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T13:45:00Z"},{"price":99.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T14:00:00Z"},{"price":117.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T14:15:00Z"},{"price":124.09,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T14:30:00Z"},{"price":138.69,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T14:45:00Z"},{"price":121.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T15:00:00Z"},{"price":131.5,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T15:15:00Z"},{"price":135.11,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T15:30:00Z"},{"price":141.15,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T15:45:00Z"},{"price":147.79,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T16:00:00Z"},{"price":151.48,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T16:15:00Z"},{"price":140.68,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T16:30:00Z"},{"price":128.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T16:45:00Z"},{"price":130.32,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T17:00:00Z"},{"price":113.7,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T17:15:00Z"},{"price":112.49,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T17:30:00Z"},{"price":102.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T17:45:00Z"},{"price":107.13,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T18:00:00Z"},{"price":101.56,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T18:15:00Z"},{"price":100.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T18:30:00Z"},{"price":100,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T18:45:00Z"},{"price":98.71,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T19:00:00Z"},{"price":97.8,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T19:15:00Z"},{"price":95.31,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T19:30:00Z"},{"price":91.64,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T19:45:00Z"},{"price":91.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T20:00:00Z"},{"price":88.64,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T20:15:00Z"},{"price":88.24,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T20:30:00Z"},{"price":88.59,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T20:45:00Z"},{"price":91.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T21:00:00Z"},{"price":86.91,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T21:15:00Z"},{"price":86.86,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T21:30:00Z"},{"price":84.67,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T21:45:00Z"},{"price":85.21,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T22:00:00Z"},{"price":83.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T22:15:00Z"},{"price":81.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T22:30:00Z"},{"price":79.86,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T22:45:00Z"},{"price":83.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:00:00Z"},{"price":82.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:15:00Z"},{"price":81.91,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:30:00Z"},{"price":80.89,"currency":"EUR","area":"NO1","timestamp":"2026-01-15T23:45:00Z"},{"price":81.62,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:00:00Z"},{"price":80.6,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:15:00Z"},{"price":79.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:30:00Z"},{"price":77.88,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T00:45:00Z"},{"price":78.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:00:00Z"},{"price":77.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:15:00Z"},{"price":75.03,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:30:00Z"},{"price":74.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T01:45:00Z"},{"price":72.9,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:00:00Z"},{"price":72.47,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:15:00Z"},{"price":72.89,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:30:00Z"},{"price":73.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T02:45:00Z"},{"price":74.65,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:00:00Z"},{"price":75.09,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:15:00Z"},{"price":77.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:30:00Z"},{"price":78.48,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T03:45:00Z"},{"price":80.92,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:00:00Z"},{"price":82.07,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:15:00Z"},{"price":83.29,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:30:00Z"},{"price":84.17,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T04:45:00Z"},{"price":83.98,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:00:00Z"},{"price":85.58,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:15:00Z"},{"price":86.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:30:00Z"},{"price":87.82,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T05:45:00Z"},{"price":87.25,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:00:00Z"},{"price":87.91,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:15:00Z"},{"price":88.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:30:00Z"},{"price":88.69,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T06:45:00Z"},{"price":88.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:00:00Z"},{"price":88.55,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:15:00Z"},{"price":88.88,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:30:00Z"},{"price":88.97,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T07:45:00Z"},{"price":88.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:00:00Z"},{"price":88.83,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:15:00Z"},{"price":88.86,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:30:00Z"},{"price":89.01,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T08:45:00Z"},{"price":88.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:00:00Z"},{"price":88.24,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:15:00Z"},{"price":88.21,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:30:00Z"},{"price":87.97,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T09:45:00Z"},{"price":89.66,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:00:00Z"},{"price":89.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:15:00Z"},{"price":89.48,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:30:00Z"},{"price":89.49,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T10:45:00Z"},{"price":88.75,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:00:00Z"},{"price":89.62,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:15:00Z"},{"price":90.03,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:30:00Z"},{"price":90.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T11:45:00Z"},{"price":90.12,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:00:00Z"},{"price":90.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:15:00Z"},{"price":92.33,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:30:00Z"},{"price":94.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T12:45:00Z"},{"price":91.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:00:00Z"},{"price":93.01,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:15:00Z"},{"price":93.14,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:30:00Z"},{"price":93.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T13:45:00Z"},{"price":92.2,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:00:00Z"},{"price":93.38,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:15:00Z"},{"price":93.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:30:00Z"},{"price":93.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T14:45:00Z"},{"price":94,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:00:00Z"},{"price":95.16,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:15:00Z"},{"price":96.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:30:00Z"},{"price":101.04,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T15:45:00Z"},{"price":96.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:00:00Z"},{"price":95.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:15:00Z"},{"price":95.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:30:00Z"},{"price":95.29,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T16:45:00Z"},{"price":95.16,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:00:00Z"},{"price":95,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:15:00Z"},{"price":95.31,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:30:00Z"},{"price":95.13,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T17:45:00Z"},{"price":95.2,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:00:00Z"},{"price":94.93,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:15:00Z"},{"price":94.79,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:30:00Z"},{"price":94.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T18:45:00Z"},{"price":94.72,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:00:00Z"},{"price":94.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:15:00Z"},{"price":94.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:30:00Z"},{"price":94.21,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T19:45:00Z"},{"price":94,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:00:00Z"},{"price":93.94,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:15:00Z"},{"price":93.7,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:30:00Z"},{"price":93.49,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T20:45:00Z"},{"price":93.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:00:00Z"},{"price":93.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:15:00Z"},{"price":93.02,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:30:00Z"},{"price":92.7,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T21:45:00Z"},{"price":93.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:00:00Z"},{"price":92.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:15:00Z"},{"price":92.08,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:30:00Z"},{"price":91.1,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T22:45:00Z"},{"price":94.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T23:00:00Z"},{"price":93.55,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T23:15:00Z"},{"price":93.45,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T23:30:00Z"},{"price":91.08,"currency":"EUR","area":"NO1","timestamp":"2026-01-16T23:45:00Z"},{"price":91.02,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T00:00:00Z"},{"price":90.87,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T00:15:00Z"},{"price":90.71,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T00:30:00Z"},{"price":90.6,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T00:45:00Z"},{"price":90.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T01:00:00Z"},{"price":90.28,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T01:15:00Z"},{"price":90.22,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T01:30:00Z"},{"price":90.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T01:45:00Z"},{"price":91.28,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T02:00:00Z"},{"price":91.79,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T02:15:00Z"},{"price":91.4,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T02:30:00Z"},{"price":91.58,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T02:45:00Z"},{"price":91.51,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T03:00:00Z"},{"price":91.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T03:15:00Z"},{"price":91.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T03:30:00Z"},{"price":91.46,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T03:45:00Z"},{"price":91.79,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T04:00:00Z"},{"price":90.98,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T04:15:00Z"},{"price":92.16,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T04:30:00Z"},{"price":91.75,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T04:45:00Z"},{"price":91.78,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T05:00:00Z"},{"price":90.08,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T05:15:00Z"},{"price":91.78,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T05:30:00Z"},{"price":91.56,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T05:45:00Z"},{"price":91.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T06:00:00Z"},{"price":92.38,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T06:15:00Z"},{"price":94.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T06:30:00Z"},{"price":96.68,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T06:45:00Z"},{"price":93.5,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T07:00:00Z"},{"price":97.4,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T07:15:00Z"},{"price":97.65,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T07:30:00Z"},{"price":98.39,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T07:45:00Z"},{"price":99.67,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T08:00:00Z"},{"price":101.82,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T08:15:00Z"},{"price":99.42,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T08:30:00Z"},{"price":97.94,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T08:45:00Z"},{"price":105.19,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T09:00:00Z"},{"price":101.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T09:15:00Z"},{"price":98.71,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T09:30:00Z"},{"price":97.29,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T09:45:00Z"},{"price":98.66,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T10:00:00Z"},{"price":98.42,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T10:15:00Z"},{"price":98.17,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T10:30:00Z"},{"price":97.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T10:45:00Z"},{"price":98.92,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T11:00:00Z"},{"price":98.03,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T11:15:00Z"},{"price":98.5,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T11:30:00Z"},{"price":98.3,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T11:45:00Z"},{"price":98.26,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T12:00:00Z"},{"price":98.82,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T12:15:00Z"},{"price":98.55,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T12:30:00Z"},{"price":97.99,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T12:45:00Z"},{"price":97.43,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T13:00:00Z"},{"price":97.77,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T13:15:00Z"},{"price":100.74,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T13:30:00Z"},{"price":105.94,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T13:45:00Z"},{"price":100.32,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T14:00:00Z"},{"price":105.32,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T14:15:00Z"},{"price":110.81,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T14:30:00Z"},{"price":113.24,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T14:45:00Z"},{"price":111.59,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T15:00:00Z"},{"price":120.96,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T15:15:00Z"},{"price":122.54,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T15:30:00Z"},{"price":123.82,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T15:45:00Z"},{"price":128.8,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T16:00:00Z"},{"price":126.62,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T16:15:00Z"},{"price":127.73,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T16:30:00Z"},{"price":131.06,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T16:45:00Z"},{"price":123.38,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T17:00:00Z"},{"price":121.69,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T17:15:00Z"},{"price":125.39,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T17:30:00Z"},{"price":124.59,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T17:45:00Z"},{"price":122.52,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T18:00:00Z"},{"price":121.64,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T18:15:00Z"},{"price":120.84,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T18:30:00Z"},{"price":113.53,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T18:45:00Z"},{"price":119.74,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T19:00:00Z"},{"price":106.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T19:15:00Z"},{"price":104.52,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T19:30:00Z"},{"price":99.31,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T19:45:00Z"},{"price":104.05,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T20:00:00Z"},{"price":100.21,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T20:15:00Z"},{"price":98.94,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T20:30:00Z"},{"price":96.23,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T20:45:00Z"},{"price":98.37,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T21:00:00Z"},{"price":97.66,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T21:15:00Z"},{"price":98.35,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T21:30:00Z"},{"price":99.81,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T21:45:00Z"},{"price":99.04,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T22:00:00Z"},{"price":98.13,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T22:15:00Z"},{"price":97.85,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T22:30:00Z"},{"price":96,"currency":"EUR","area":"NO1","timestamp":"2026-01-17T22:45:00Z"}];
9
-
10
- const outdoorTemp = 5;
11
-
12
- // User's config
13
- const userConfig = {
14
- heatLossCoefficient: 0.06,
15
- minModeDuration: 60,
16
- minSavingsPercent: 10,
17
- minSavingsAbsolute: 3,
18
- numPriceGroups: 5, // <-- 5 groups means only top 20% is "coast"
19
- outputTimezone: "Europe/Oslo",
20
- };
21
-
22
- console.log("=== DEBUGGING SATURDAY PEAK ===\n");
23
-
24
- // First, let's see what the clustering produces
25
- const priceValues = prices.map(p => p.price);
26
- const effectiveVariationThreshold = userConfig.minSavingsPercent * 2; // 20%
27
-
28
- console.log("User config:");
29
- console.log(` numPriceGroups: ${userConfig.numPriceGroups}`);
30
- console.log(` minSavingsPercent: ${userConfig.minSavingsPercent}%`);
31
- console.log(` effectiveVariationThreshold: ${effectiveVariationThreshold}%`);
32
- console.log();
33
-
34
- const clustering = classifyPricesWithCkmeans(priceValues, effectiveVariationThreshold, userConfig.numPriceGroups);
35
-
36
- console.log("Clustering results:");
37
- console.log(` Number of clusters: ${clustering.numGroups}`);
38
- console.log(` Cluster breaks (thresholds):`);
39
- clustering.breaks.forEach((b, i) => {
40
- console.log(` Break ${i + 1}: ${b.toFixed(2)} EUR/MWh`);
41
- });
42
- console.log(` Cluster means:`);
43
- clustering.clusterMeans.forEach((m, i) => {
44
- const label = i === 0 ? "BOOST" : i === clustering.numGroups - 1 ? "COAST" : "normal";
45
- console.log(` Cluster ${i}: ${m.toFixed(2)} EUR/MWh → ${label}`);
46
- });
47
-
48
- // The last break is the coast threshold
49
- const coastThreshold = clustering.breaks[clustering.breaks.length - 1];
50
- console.log(`\n *** COAST THRESHOLD: > ${coastThreshold.toFixed(2)} EUR/MWh ***`);
51
-
52
- // Now let's look at Saturday (Jan 17) prices around 17:45
53
- console.log("\n=== SATURDAY (Jan 17) EVENING PRICES ===\n");
54
-
55
- const saturdayPrices = prices.filter(p => p.timestamp.startsWith("2026-01-17T"));
56
- const eveningPrices = saturdayPrices.filter(p => {
57
- const hour = parseInt(p.timestamp.substring(11, 13));
58
- return hour >= 14 && hour <= 20;
59
- });
60
-
61
- console.log("Time (UTC) | Time (Oslo) | Price | Above coast threshold?");
62
- console.log("-----------|-------------|----------|------------------------");
63
- eveningPrices.forEach(p => {
64
- const utcTime = DateTime.fromISO(p.timestamp);
65
- const osloTime = utcTime.setZone("Europe/Oslo");
66
- const aboveThreshold = p.price > coastThreshold;
67
- const marker = aboveThreshold ? "✓ COAST" : "";
68
- console.log(`${p.timestamp.substring(11, 16)} UTC | ${osloTime.toFormat("HH:mm")} Oslo | ${p.price.toFixed(2).padStart(8)} | ${marker}`);
69
- });
70
-
71
- // The peak at 17:45 Oslo = 16:45 UTC
72
- const peak1745 = prices.find(p => p.timestamp === "2026-01-17T16:45:00Z");
73
- console.log(`\n*** The 17:45 Oslo peak (16:45 UTC): ${peak1745?.price} EUR/MWh`);
74
- console.log(`*** Coast threshold: ${coastThreshold.toFixed(2)} EUR/MWh`);
75
- console.log(`*** Is ${peak1745?.price} > ${coastThreshold.toFixed(2)}? ${peak1745?.price > coastThreshold ? "YES → should coast" : "NO → won't coast"}`);
76
-
77
- // Now run the full algorithm
78
- console.log("\n=== FULL SCHEDULE FOR SATURDAY ===\n");
79
-
80
- const schedule = createSchedule(prices, outdoorTemp, userConfig);
81
- const saturdaySchedule = schedule.filter(s => s.time.startsWith("2026-01-17"));
82
-
83
- console.log("Time (Oslo) | Mode | Price");
84
- console.log("------------|--------|--------");
85
- saturdaySchedule.filter(s => {
86
- const hour = parseInt(s.time.substring(11, 13));
87
- return hour >= 14 && hour <= 20;
88
- }).forEach(entry => {
89
- const marker = entry.mode === "coast" ? " ← COAST" : "";
90
- console.log(`${entry.time.substring(11, 16)} | ${entry.mode.padEnd(6)} | ${entry.price.toFixed(2)}${marker}`);
91
- });
92
-
93
- // Summary
94
- console.log("\n=== DIAGNOSIS ===\n");
95
- console.log(`With 5 price groups, only prices above ${coastThreshold.toFixed(2)} EUR/MWh are classified as "coast".`);
96
- console.log(`The Saturday 17:45 peak is ${peak1745?.price} EUR/MWh.`);
97
- if (peak1745?.price > coastThreshold) {
98
- console.log(`\nThis IS above the threshold, so it should coast.`);
99
- console.log(`If it's not coasting, check thermal budget or min duration constraints.`);
100
- } else {
101
- console.log(`\nThis is BELOW the threshold.`);
102
- console.log(`\nSolution: Use fewer price groups (3 instead of 5) to be more aggressive,`);
103
- console.log(`or lower minSavingsPercent to include more prices in the coast band.`);
104
- }
105
-
106
- // Now compare with 3 groups
107
- console.log("\n=== COMPARISON: 3 GROUPS vs 5 GROUPS ===\n");
108
-
109
- const config3 = { ...userConfig, numPriceGroups: 3 };
110
- const schedule3 = createSchedule(prices, outdoorTemp, config3);
111
- const satSchedule3 = schedule3.filter(s => s.time.startsWith("2026-01-17"));
112
- const satCoast3 = satSchedule3.filter(s => s.mode === "coast");
113
-
114
- console.log("With 3 GROUPS:");
115
- console.log(` Coast threshold: ${schedule3[0]?.tuning?.clustering?.expensiveThreshold?.toFixed(2)} EUR/MWh`);
116
- console.log(` Saturday coast intervals: ${satCoast3.length}`);
117
- if (satCoast3.length > 0) {
118
- console.log(" Coast times (Oslo):");
119
- satCoast3.forEach(x => console.log(` ${x.time.substring(11,16)} - ${x.price.toFixed(2)} EUR`));
120
- }
121
-
122
- const satCoast5 = saturdaySchedule.filter(s => s.mode === "coast");
123
- console.log("\nWith 5 GROUPS (your config):");
124
- console.log(` Coast threshold: ${coastThreshold.toFixed(2)} EUR/MWh`);
125
- console.log(` Saturday coast intervals: ${satCoast5.length}`);
126
- if (satCoast5.length > 0) {
127
- console.log(" Coast times (Oslo):");
128
- satCoast5.forEach(x => console.log(` ${x.time.substring(11,16)} - ${x.price.toFixed(2)} EUR`));
129
- }
130
-
131
- console.log("\n=== RECOMMENDATION ===");
132
- console.log("Use numPriceGroups: 3 for more aggressive coasting on days with smaller peaks.");