@energiok/node-red-contrib-pricecontrol-thermal 1.2.1 → 1.2.3

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,213 +0,0 @@
1
- /**
2
- * Step-by-step trace of what happens with the price data
3
- */
4
-
5
- const { DateTime } = require("luxon");
6
- const { createSchedule, ckmeans, 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; // Example outdoor temperature
11
- const config = {
12
- heatLossCoefficient: 0.05,
13
- minModeDuration: 60,
14
- minSavingsPercent: 5,
15
- numPriceGroups: 3,
16
- outputTimezone: "Europe/Oslo",
17
- };
18
-
19
- console.log("═══════════════════════════════════════════════════════════════════");
20
- console.log("STEP 1: INPUT DATA ANALYSIS");
21
- console.log("═══════════════════════════════════════════════════════════════════\n");
22
-
23
- const priceValues = prices.map(p => p.price);
24
- const n = priceValues.length;
25
- const avgPrice = priceValues.reduce((a, b) => a + b, 0) / n;
26
- const minPrice = Math.min(...priceValues);
27
- const maxPrice = Math.max(...priceValues);
28
- const priceRange = maxPrice - minPrice;
29
- const variationPercent = (priceRange / avgPrice) * 100;
30
-
31
- console.log(`Total intervals: ${n} (${n * 15} minutes = ${(n * 15 / 60).toFixed(1)} hours)`);
32
- console.log(`Price range: ${minPrice.toFixed(2)} - ${maxPrice.toFixed(2)} EUR/MWh`);
33
- console.log(`Average price: ${avgPrice.toFixed(2)} EUR/MWh`);
34
- console.log(`Price variation: ${variationPercent.toFixed(1)}% (range/avg)`);
35
- console.log(`First timestamp: ${prices[0].timestamp} (UTC)`);
36
- console.log(`Last timestamp: ${prices[n-1].timestamp} (UTC)`);
37
-
38
- // Convert to Oslo time to show the actual local times
39
- const firstOslo = DateTime.fromISO(prices[0].timestamp).setZone("Europe/Oslo");
40
- const lastOslo = DateTime.fromISO(prices[n-1].timestamp).setZone("Europe/Oslo");
41
- console.log(`\nIn Oslo time:`);
42
- console.log(` First: ${firstOslo.toFormat("yyyy-MM-dd HH:mm")} (${firstOslo.toFormat("cccc")})`);
43
- console.log(` Last: ${lastOslo.toFormat("yyyy-MM-dd HH:mm")} (${lastOslo.toFormat("cccc")})`);
44
-
45
- console.log("\n═══════════════════════════════════════════════════════════════════");
46
- console.log("STEP 2: CONFIG VALIDATION");
47
- console.log("═══════════════════════════════════════════════════════════════════\n");
48
-
49
- console.log(`Heat loss coefficient: ${config.heatLossCoefficient}`);
50
- console.log(`Min mode duration: ${config.minModeDuration} minutes`);
51
- console.log(`Min savings percent: ${config.minSavingsPercent}%`);
52
- console.log(`Num price groups: ${config.numPriceGroups}`);
53
- console.log(`Output timezone: ${config.outputTimezone}`);
54
- console.log(`Outdoor temperature: ${outdoorTemp}°C`);
55
-
56
- console.log("\n═══════════════════════════════════════════════════════════════════");
57
- console.log("STEP 3: THERMAL CONSTRAINTS CALCULATION");
58
- console.log("═══════════════════════════════════════════════════════════════════\n");
59
-
60
- const comfortNormal = 22;
61
- const comfortMin = 20;
62
- const comfortMax = 24;
63
- const tempDelta = comfortNormal - outdoorTemp;
64
- const tempBuffer = comfortNormal - comfortMin;
65
- const heatLossRate = tempDelta * config.heatLossCoefficient;
66
- const maxCoastHours = tempBuffer / heatLossRate;
67
- const maxCoastMinutes = maxCoastHours * 60;
68
- const intervalMinutes = 15;
69
- const maxCoastIntervals = Math.floor(maxCoastMinutes / intervalMinutes);
70
-
71
- console.log(`Indoor temp (normal): ${comfortNormal}°C`);
72
- console.log(`Outdoor temp: ${outdoorTemp}°C`);
73
- console.log(`Temperature delta: ${tempDelta}°C`);
74
- console.log(`Temperature buffer (22 - 20): ${tempBuffer}°C`);
75
- console.log(`Heat loss rate: ${tempDelta} × ${config.heatLossCoefficient} = ${heatLossRate.toFixed(2)}°C/hour`);
76
- console.log(`Max coast time: ${tempBuffer} / ${heatLossRate.toFixed(2)} = ${maxCoastHours.toFixed(1)} hours (${maxCoastMinutes.toFixed(0)} minutes)`);
77
- console.log(`Max coast intervals (at 15min each): ${maxCoastIntervals} intervals`);
78
-
79
- console.log("\n═══════════════════════════════════════════════════════════════════");
80
- console.log("STEP 4: CKMEANS CLUSTERING");
81
- console.log("═══════════════════════════════════════════════════════════════════\n");
82
-
83
- // Calculate effective variation threshold
84
- const effectiveVariationThreshold = config.minSavingsPercent * 2;
85
- console.log(`Minimum variation threshold: ${effectiveVariationThreshold}%`);
86
- console.log(`Actual variation: ${variationPercent.toFixed(1)}%`);
87
- console.log(`Clustering will activate: ${variationPercent >= effectiveVariationThreshold ? "YES" : "NO"}\n`);
88
-
89
- const clustering = classifyPricesWithCkmeans(priceValues, effectiveVariationThreshold, config.numPriceGroups);
90
-
91
- console.log(`Number of clusters: ${clustering.numGroups}`);
92
- console.log(`Cluster breaks (thresholds):`);
93
- clustering.breaks.forEach((b, i) => {
94
- console.log(` Break ${i + 1}: ${b.toFixed(2)} EUR/MWh`);
95
- });
96
- console.log(`\nCluster means:`);
97
- clustering.clusterMeans.forEach((m, i) => {
98
- const label = i === 0 ? "BOOST (cheapest)" : i === clustering.numGroups - 1 ? "COAST (expensive)" : "NORMAL";
99
- console.log(` Cluster ${i}: ${m.toFixed(2)} EUR/MWh → ${label}`);
100
- });
101
-
102
- // Count assignments
103
- const boostCount = clustering.assignments.filter(a => a === 0).length;
104
- const coastCount = clustering.assignments.filter(a => a === clustering.numGroups - 1).length;
105
- const normalCount = n - boostCount - coastCount;
106
-
107
- console.log(`\nRaw clustering results (before thermal constraints):`);
108
- console.log(` Boost intervals: ${boostCount} (${(boostCount/n*100).toFixed(1)}%)`);
109
- console.log(` Normal intervals: ${normalCount} (${(normalCount/n*100).toFixed(1)}%)`);
110
- console.log(` Coast intervals: ${coastCount} (${(coastCount/n*100).toFixed(1)}%)`);
111
-
112
- console.log("\n═══════════════════════════════════════════════════════════════════");
113
- console.log("STEP 5: APPLY THERMAL CONSTRAINTS");
114
- console.log("═══════════════════════════════════════════════════════════════════\n");
115
-
116
- console.log(`Max coast intervals allowed: ${maxCoastIntervals}`);
117
- console.log(`Raw coast intervals requested: ${coastCount}`);
118
-
119
- if (coastCount > maxCoastIntervals) {
120
- console.log(`\n⚠️ Thermal budget exceeded! Need to reduce coast from ${coastCount} to ${maxCoastIntervals}`);
121
- console.log(` Will prioritize the ${maxCoastIntervals} most expensive intervals for coasting.`);
122
- } else {
123
- console.log(`✓ Thermal budget OK. All ${coastCount} coast intervals can be used.`);
124
- }
125
-
126
- console.log("\n═══════════════════════════════════════════════════════════════════");
127
- console.log("STEP 6: ENFORCE MINIMUM DURATIONS");
128
- console.log("═══════════════════════════════════════════════════════════════════\n");
129
-
130
- const minIntervals = Math.ceil(config.minModeDuration / intervalMinutes);
131
- console.log(`Min mode duration: ${config.minModeDuration} minutes`);
132
- console.log(`Interval length: ${intervalMinutes} minutes`);
133
- console.log(`Min intervals required: ${minIntervals} intervals`);
134
- console.log(`\nShort runs (< ${minIntervals} intervals) will be converted to "normal" mode.`);
135
-
136
- console.log("\n═══════════════════════════════════════════════════════════════════");
137
- console.log("STEP 7: CREATE SCHEDULE WITH TIMEZONE CONVERSION");
138
- console.log("═══════════════════════════════════════════════════════════════════\n");
139
-
140
- const schedule = createSchedule(prices, outdoorTemp, config);
141
-
142
- // Show first few and last few entries
143
- console.log("Sample schedule entries (first 8 and last 8):\n");
144
- console.log("Oslo Time | UTC Time | Mode | Price");
145
- console.log("--------------------|---------------------|--------|--------");
146
-
147
- const showEntries = [...schedule.slice(0, 8), "...", ...schedule.slice(-8)];
148
- showEntries.forEach(entry => {
149
- if (entry === "...") {
150
- console.log("... (middle entries omitted) ...");
151
- return;
152
- }
153
- const osloTime = entry.time.substring(0, 19);
154
- const utcTime = entry.timeUtc.substring(0, 19);
155
- console.log(`${osloTime} | ${utcTime}Z | ${entry.mode.padEnd(6)} | ${entry.price.toFixed(2)}`);
156
- });
157
-
158
- console.log("\n═══════════════════════════════════════════════════════════════════");
159
- console.log("STEP 8: FINAL RESULTS");
160
- console.log("═══════════════════════════════════════════════════════════════════\n");
161
-
162
- const finalBoost = schedule.filter(s => s.mode === "boost").length;
163
- const finalNormal = schedule.filter(s => s.mode === "normal").length;
164
- const finalCoast = schedule.filter(s => s.mode === "coast").length;
165
-
166
- console.log("Final mode distribution:");
167
- console.log(` Boost: ${finalBoost} intervals (${(finalBoost/n*100).toFixed(1)}%) - ${(finalBoost * 15 / 60).toFixed(1)} hours`);
168
- console.log(` Normal: ${finalNormal} intervals (${(finalNormal/n*100).toFixed(1)}%) - ${(finalNormal * 15 / 60).toFixed(1)} hours`);
169
- console.log(` Coast: ${finalCoast} intervals (${(finalCoast/n*100).toFixed(1)}%) - ${(finalCoast * 15 / 60).toFixed(1)} hours`);
170
-
171
- // Show tuning info
172
- if (schedule[0].tuning) {
173
- const t = schedule[0].tuning;
174
- console.log("\nTuning info from schedule:");
175
- console.log(` Cheap threshold: < ${t.clustering.cheapThreshold.toFixed(2)} EUR/MWh → BOOST`);
176
- console.log(` Expensive threshold: > ${t.clustering.expensiveThreshold.toFixed(2)} EUR/MWh → COAST`);
177
- console.log(` Raw boost count: ${t.clustering.rawBoostCount}`);
178
- console.log(` Raw coast count: ${t.clustering.rawCoastCount}`);
179
- console.log(` Final boost count: ${t.finalModeCount.boost}`);
180
- console.log(` Final coast count: ${t.finalModeCount.coast}`);
181
- }
182
-
183
- console.log("\n═══════════════════════════════════════════════════════════════════");
184
- console.log("VISUAL TIMELINE (Oslo time)");
185
- console.log("═══════════════════════════════════════════════════════════════════\n");
186
-
187
- // Group by hour
188
- const hourlyModes = {};
189
- schedule.forEach(entry => {
190
- const hour = DateTime.fromISO(entry.time).toFormat("MM-dd HH");
191
- if (!hourlyModes[hour]) {
192
- hourlyModes[hour] = { boost: 0, normal: 0, coast: 0 };
193
- }
194
- hourlyModes[hour][entry.mode]++;
195
- });
196
-
197
- console.log("Date Hour | B N C | Visual");
198
- console.log("-----------|-------|----------------------------------------");
199
-
200
- Object.keys(hourlyModes).sort().forEach(hour => {
201
- const m = hourlyModes[hour];
202
- const total = m.boost + m.normal + m.coast;
203
-
204
- // Create visual bar
205
- let bar = "";
206
- bar += "🟢".repeat(m.boost);
207
- bar += "⚪".repeat(m.normal);
208
- bar += "🔴".repeat(m.coast);
209
-
210
- console.log(`${hour}:00 | ${m.boost} ${m.normal} ${m.coast} | ${bar}`);
211
- });
212
-
213
- console.log("\nLegend: 🟢 = Boost (ON) ⚪ = Normal 🔴 = Coast (OFF)");
@@ -1,62 +0,0 @@
1
- /**
2
- * Test: Temperature scaling of minSavingsPercent
3
- *
4
- * Below 10°C, the minSavingsPercent increases to make the algorithm
5
- * more conservative (require bigger price gaps before coasting).
6
- */
7
-
8
- const { createSchedule } = require('./src/strategy-smart-thermal-functions.js');
9
-
10
- const prices = [
11
- { timestamp: '2026-01-17T00:00:00Z', price: 90 },
12
- { timestamp: '2026-01-17T01:00:00Z', price: 90 },
13
- { timestamp: '2026-01-17T02:00:00Z', price: 90 },
14
- { timestamp: '2026-01-17T03:00:00Z', price: 90 },
15
- { timestamp: '2026-01-17T04:00:00Z', price: 90 },
16
- { timestamp: '2026-01-17T05:00:00Z', price: 90 },
17
- { timestamp: '2026-01-17T06:00:00Z', price: 95 },
18
- { timestamp: '2026-01-17T07:00:00Z', price: 100 },
19
- { timestamp: '2026-01-17T08:00:00Z', price: 110 },
20
- { timestamp: '2026-01-17T09:00:00Z', price: 120 },
21
- { timestamp: '2026-01-17T10:00:00Z', price: 130 },
22
- { timestamp: '2026-01-17T11:00:00Z', price: 125 },
23
- { timestamp: '2026-01-17T12:00:00Z', price: 115 },
24
- { timestamp: '2026-01-17T13:00:00Z', price: 105 },
25
- { timestamp: '2026-01-17T14:00:00Z', price: 100 },
26
- { timestamp: '2026-01-17T15:00:00Z', price: 95 },
27
- { timestamp: '2026-01-17T16:00:00Z', price: 100 },
28
- { timestamp: '2026-01-17T17:00:00Z', price: 120 },
29
- { timestamp: '2026-01-17T18:00:00Z', price: 130 },
30
- { timestamp: '2026-01-17T19:00:00Z', price: 125 },
31
- { timestamp: '2026-01-17T20:00:00Z', price: 110 },
32
- { timestamp: '2026-01-17T21:00:00Z', price: 100 },
33
- { timestamp: '2026-01-17T22:00:00Z', price: 95 },
34
- { timestamp: '2026-01-17T23:00:00Z', price: 90 },
35
- ];
36
-
37
- const config = { minSavingsPercent: 8, numPriceGroups: 3 };
38
-
39
- console.log('=== TEMPERATURE SCALING OF minSavingsPercent ===\n');
40
- console.log('Base minSavingsPercent: 8%\n');
41
- console.log('Temp | Base % | Effective % | Coast Hours');
42
- console.log('-'.repeat(50));
43
-
44
- for (const temp of [15, 10, 5, 0, -5, -10, -15, -20]) {
45
- const schedule = createSchedule(prices, temp, config);
46
- const tuning = schedule[0]?.tuning;
47
- const basePct = tuning?.minSavingsPercent;
48
- const effectivePct = tuning?.effectiveMinSavingsPercent?.toFixed(1);
49
- const coastCount = schedule.filter(s => s.mode === 'coast').length;
50
- const marker = temp < 10 ? ' (scaled)' : '';
51
- console.log(
52
- `${temp.toString().padStart(3)}°C | ${basePct.toString().padStart(4)}% | ` +
53
- `${effectivePct.padStart(8)}% | ${coastCount} hrs${marker}`
54
- );
55
- }
56
-
57
- console.log('\n=== EXPLANATION ===');
58
- console.log('At 10°C and above: uses configured value (8%)');
59
- console.log('Below 10°C: scales linearly up to 1.5× at extreme cold (-15°C)');
60
- console.log('At -15°C: 8% × 1.5 = 12%');
61
- console.log('\nThis makes the algorithm more conservative when cold,');
62
- console.log('requiring larger price differences to justify coasting.');
@@ -1,174 +0,0 @@
1
- /**
2
- * Test: Thermal budget varies with outdoor temperature
3
- *
4
- * Colder weather = less coast time allowed, more boost needed
5
- * Warmer weather = more coast time allowed
6
- */
7
-
8
- const { createSchedule } = require('./src/strategy-smart-thermal-functions.js');
9
-
10
- // Use 15-minute interval prices (matching your actual Tibber data)
11
- const prices = [
12
- { timestamp: "2026-01-17T00:00:00Z", price: 91.02 },
13
- { timestamp: "2026-01-17T00:15:00Z", price: 90.87 },
14
- { timestamp: "2026-01-17T00:30:00Z", price: 90.71 },
15
- { timestamp: "2026-01-17T00:45:00Z", price: 90.60 },
16
- { timestamp: "2026-01-17T01:00:00Z", price: 90.37 },
17
- { timestamp: "2026-01-17T01:15:00Z", price: 90.28 },
18
- { timestamp: "2026-01-17T01:30:00Z", price: 90.22 },
19
- { timestamp: "2026-01-17T01:45:00Z", price: 90.30 },
20
- { timestamp: "2026-01-17T02:00:00Z", price: 91.28 },
21
- { timestamp: "2026-01-17T02:15:00Z", price: 91.79 },
22
- { timestamp: "2026-01-17T02:30:00Z", price: 91.40 },
23
- { timestamp: "2026-01-17T02:45:00Z", price: 91.58 },
24
- { timestamp: "2026-01-17T03:00:00Z", price: 91.51 },
25
- { timestamp: "2026-01-17T03:15:00Z", price: 91.19 },
26
- { timestamp: "2026-01-17T03:30:00Z", price: 91.30 },
27
- { timestamp: "2026-01-17T03:45:00Z", price: 91.46 },
28
- { timestamp: "2026-01-17T04:00:00Z", price: 91.79 },
29
- { timestamp: "2026-01-17T04:15:00Z", price: 90.98 },
30
- { timestamp: "2026-01-17T04:30:00Z", price: 92.16 },
31
- { timestamp: "2026-01-17T04:45:00Z", price: 91.75 },
32
- { timestamp: "2026-01-17T05:00:00Z", price: 91.78 },
33
- { timestamp: "2026-01-17T05:15:00Z", price: 90.08 },
34
- { timestamp: "2026-01-17T05:30:00Z", price: 91.78 },
35
- { timestamp: "2026-01-17T05:45:00Z", price: 91.56 },
36
- { timestamp: "2026-01-17T06:00:00Z", price: 91.30 },
37
- { timestamp: "2026-01-17T06:15:00Z", price: 92.38 },
38
- { timestamp: "2026-01-17T06:30:00Z", price: 94.26 },
39
- { timestamp: "2026-01-17T06:45:00Z", price: 96.68 },
40
- { timestamp: "2026-01-17T07:00:00Z", price: 93.50 },
41
- { timestamp: "2026-01-17T07:15:00Z", price: 97.40 },
42
- { timestamp: "2026-01-17T07:30:00Z", price: 97.65 },
43
- { timestamp: "2026-01-17T07:45:00Z", price: 98.39 },
44
- { timestamp: "2026-01-17T08:00:00Z", price: 99.67 },
45
- { timestamp: "2026-01-17T08:15:00Z", price: 101.82 },
46
- { timestamp: "2026-01-17T08:30:00Z", price: 99.42 },
47
- { timestamp: "2026-01-17T08:45:00Z", price: 97.94 },
48
- { timestamp: "2026-01-17T09:00:00Z", price: 105.19 },
49
- { timestamp: "2026-01-17T09:15:00Z", price: 101.37 },
50
- { timestamp: "2026-01-17T09:30:00Z", price: 98.71 },
51
- { timestamp: "2026-01-17T09:45:00Z", price: 97.29 },
52
- { timestamp: "2026-01-17T10:00:00Z", price: 98.66 },
53
- { timestamp: "2026-01-17T10:15:00Z", price: 98.42 },
54
- { timestamp: "2026-01-17T10:30:00Z", price: 98.17 },
55
- { timestamp: "2026-01-17T10:45:00Z", price: 97.37 },
56
- { timestamp: "2026-01-17T11:00:00Z", price: 98.92 },
57
- { timestamp: "2026-01-17T11:15:00Z", price: 98.03 },
58
- { timestamp: "2026-01-17T11:30:00Z", price: 98.50 },
59
- { timestamp: "2026-01-17T11:45:00Z", price: 98.30 },
60
- { timestamp: "2026-01-17T12:00:00Z", price: 98.26 },
61
- { timestamp: "2026-01-17T12:15:00Z", price: 98.82 },
62
- { timestamp: "2026-01-17T12:30:00Z", price: 98.55 },
63
- { timestamp: "2026-01-17T12:45:00Z", price: 97.99 },
64
- { timestamp: "2026-01-17T13:00:00Z", price: 97.43 },
65
- { timestamp: "2026-01-17T13:15:00Z", price: 97.77 },
66
- { timestamp: "2026-01-17T13:30:00Z", price: 100.74 },
67
- { timestamp: "2026-01-17T13:45:00Z", price: 105.94 },
68
- { timestamp: "2026-01-17T14:00:00Z", price: 100.32 },
69
- { timestamp: "2026-01-17T14:15:00Z", price: 105.32 },
70
- { timestamp: "2026-01-17T14:30:00Z", price: 110.81 },
71
- { timestamp: "2026-01-17T14:45:00Z", price: 113.24 },
72
- { timestamp: "2026-01-17T15:00:00Z", price: 111.59 },
73
- { timestamp: "2026-01-17T15:15:00Z", price: 120.96 },
74
- { timestamp: "2026-01-17T15:30:00Z", price: 122.54 },
75
- { timestamp: "2026-01-17T15:45:00Z", price: 123.82 },
76
- { timestamp: "2026-01-17T16:00:00Z", price: 128.80 }, // Peak
77
- { timestamp: "2026-01-17T16:15:00Z", price: 126.62 },
78
- { timestamp: "2026-01-17T16:30:00Z", price: 127.73 },
79
- { timestamp: "2026-01-17T16:45:00Z", price: 131.06 },
80
- { timestamp: "2026-01-17T17:00:00Z", price: 123.38 },
81
- { timestamp: "2026-01-17T17:15:00Z", price: 121.69 },
82
- { timestamp: "2026-01-17T17:30:00Z", price: 125.39 },
83
- { timestamp: "2026-01-17T17:45:00Z", price: 124.59 },
84
- { timestamp: "2026-01-17T18:00:00Z", price: 122.52 },
85
- { timestamp: "2026-01-17T18:15:00Z", price: 121.64 },
86
- { timestamp: "2026-01-17T18:30:00Z", price: 120.84 },
87
- { timestamp: "2026-01-17T18:45:00Z", price: 113.53 },
88
- { timestamp: "2026-01-17T19:00:00Z", price: 119.74 },
89
- { timestamp: "2026-01-17T19:15:00Z", price: 106.23 },
90
- { timestamp: "2026-01-17T19:30:00Z", price: 104.52 },
91
- { timestamp: "2026-01-17T19:45:00Z", price: 99.31 },
92
- { timestamp: "2026-01-17T20:00:00Z", price: 104.05 },
93
- { timestamp: "2026-01-17T20:15:00Z", price: 100.21 },
94
- { timestamp: "2026-01-17T20:30:00Z", price: 98.94 },
95
- { timestamp: "2026-01-17T20:45:00Z", price: 96.23 },
96
- { timestamp: "2026-01-17T21:00:00Z", price: 98.37 },
97
- { timestamp: "2026-01-17T21:15:00Z", price: 97.66 },
98
- { timestamp: "2026-01-17T21:30:00Z", price: 98.35 },
99
- { timestamp: "2026-01-17T21:45:00Z", price: 99.81 },
100
- { timestamp: "2026-01-17T22:00:00Z", price: 99.04 },
101
- { timestamp: "2026-01-17T22:15:00Z", price: 98.13 },
102
- { timestamp: "2026-01-17T22:30:00Z", price: 97.85 },
103
- { timestamp: "2026-01-17T22:45:00Z", price: 96.00 },
104
- ];
105
-
106
- const config = {
107
- heatLossCoefficient: 0.05,
108
- minModeDuration: 60,
109
- minSavingsPercent: 5,
110
- numPriceGroups: 3,
111
- outputTimezone: "Europe/Oslo",
112
- extremeWeatherThreshold: -15,
113
- };
114
-
115
- console.log("=== THERMAL BUDGET VS OUTDOOR TEMPERATURE ===\n");
116
- console.log("Testing how coast/boost budgets change with temperature\n");
117
-
118
- const temperatures = [10, 5, 0, -5, -10, -15, -20];
119
-
120
- console.log("Temp(°C) | Max Coast | Max Boost | Coast Intervals | Boost Intervals");
121
- console.log("-".repeat(70));
122
-
123
- for (const temp of temperatures) {
124
- const schedule = createSchedule(prices, temp, config);
125
- const tuning = schedule[0]?.tuning;
126
-
127
- const coastCount = schedule.filter(s => s.mode === "coast").length;
128
- const boostCount = schedule.filter(s => s.mode === "boost").length;
129
-
130
- const maxCoast = tuning?.maxCoastIntervals ?? "N/A";
131
- const maxBoostLookahead = tuning?.maxBoostLookahead ?? "N/A";
132
- const isExtreme = tuning?.isExtremeWeather ? " (EXTREME)" : "";
133
-
134
- console.log(
135
- `${temp.toString().padStart(4)}°C | ` +
136
- `${maxCoast.toString().padStart(5)} int | ` +
137
- `${maxBoostLookahead.toString().padStart(5)} int | ` +
138
- `${coastCount.toString().padStart(7)} actual | ` +
139
- `${boostCount.toString().padStart(7)} actual${isExtreme}`
140
- );
141
- }
142
-
143
- console.log("\n=== DETAILED COMPARISON: 5°C vs -10°C ===\n");
144
-
145
- // 5°C - mild weather
146
- const schedule5 = createSchedule(prices, 5, config);
147
- const tuning5 = schedule5[0]?.tuning;
148
- console.log("At 5°C (mild):");
149
- console.log(` Max coast time: ${tuning5.maxCoastMinutes?.toFixed(0)} min (${tuning5.maxCoastIntervals} intervals)`);
150
- console.log(` Max boost retention: ${tuning5.maxBoostRetention?.toFixed(0)} min`);
151
- console.log(` Actual coast: ${schedule5.filter(s => s.mode === "coast").length} intervals`);
152
- console.log(` Actual boost: ${schedule5.filter(s => s.mode === "boost").length} intervals`);
153
-
154
- // -10°C - cold weather
155
- const scheduleM10 = createSchedule(prices, -10, config);
156
- const tuningM10 = scheduleM10[0]?.tuning;
157
- console.log("\nAt -10°C (cold):");
158
- console.log(` Max coast time: ${tuningM10.maxCoastMinutes?.toFixed(0)} min (${tuningM10.maxCoastIntervals} intervals)`);
159
- console.log(` Max boost retention: ${tuningM10.maxBoostRetention?.toFixed(0)} min`);
160
- console.log(` Actual coast: ${scheduleM10.filter(s => s.mode === "coast").length} intervals`);
161
- console.log(` Actual boost: ${scheduleM10.filter(s => s.mode === "boost").length} intervals`);
162
-
163
- // -20°C - extreme cold (below threshold)
164
- const scheduleM20 = createSchedule(prices, -20, config);
165
- const tuningM20 = scheduleM20[0]?.tuning;
166
- console.log("\nAt -20°C (extreme - below -15°C threshold):");
167
- console.log(` isExtremeWeather: ${tuningM20.isExtremeWeather}`);
168
- console.log(` Actual coast: ${scheduleM20.filter(s => s.mode === "coast").length} intervals (disabled!)`);
169
- console.log(` Actual boost: ${scheduleM20.filter(s => s.mode === "boost").length} intervals`);
170
-
171
- console.log("\n=== CONCLUSION ===");
172
- console.log("✓ Colder weather = shorter max coast time (house cools faster)");
173
- console.log("✓ Colder weather = longer boost retention (heat loss faster)");
174
- console.log("✓ Below extreme threshold (-15°C) = coasting disabled entirely");
package/test-timezone.js DELETED
@@ -1,83 +0,0 @@
1
- /**
2
- * Test timezone handling for Smart Thermal Strategy
3
- *
4
- * This script verifies that the algorithm works correctly regardless of
5
- * whether the host machine runs in UTC or local time.
6
- */
7
-
8
- const { DateTime } = require("luxon");
9
- const { createSchedule, getCurrentMode } = require('./src/strategy-smart-thermal-functions.js');
10
-
11
- console.log("=== TIMEZONE HANDLING TEST ===\n");
12
-
13
- // Simulate price data in UTC (as it comes from most APIs)
14
- const prices = [
15
- { timestamp: "2026-01-16T00:00:00Z", price: 72 }, // 01:00 Oslo time (UTC+1)
16
- { timestamp: "2026-01-16T01:00:00Z", price: 70 }, // 02:00 Oslo time - CHEAP
17
- { timestamp: "2026-01-16T02:00:00Z", price: 68 }, // 03:00 Oslo time - CHEAP
18
- { timestamp: "2026-01-16T03:00:00Z", price: 75 }, // 04:00 Oslo time
19
- { timestamp: "2026-01-16T04:00:00Z", price: 82 }, // 05:00 Oslo time
20
- { timestamp: "2026-01-16T05:00:00Z", price: 88 }, // 06:00 Oslo time
21
- { timestamp: "2026-01-16T06:00:00Z", price: 95 }, // 07:00 Oslo time - EXPENSIVE
22
- { timestamp: "2026-01-16T07:00:00Z", price: 98 }, // 08:00 Oslo time - EXPENSIVE
23
- { timestamp: "2026-01-16T08:00:00Z", price: 92 }, // 09:00 Oslo time
24
- { timestamp: "2026-01-16T09:00:00Z", price: 85 }, // 10:00 Oslo time
25
- { timestamp: "2026-01-16T10:00:00Z", price: 80 }, // 11:00 Oslo time
26
- { timestamp: "2026-01-16T11:00:00Z", price: 78 }, // 12:00 Oslo time
27
- ];
28
-
29
- const config = {
30
- heatLossCoefficient: 0.05,
31
- minModeDuration: 60,
32
- outputTimezone: "Europe/Oslo",
33
- minSavingsPercent: 5,
34
- };
35
-
36
- const schedule = createSchedule(prices, 5, config);
37
-
38
- console.log("Schedule entries (time = Oslo, timeUtc = UTC):\n");
39
- console.log("Oslo Time | UTC Time | Mode | Price");
40
- console.log("--------------------|---------------------|--------|------");
41
- schedule.forEach(entry => {
42
- const osloTime = entry.time.substring(0, 19);
43
- const utcTime = entry.timeUtc.substring(0, 19);
44
- console.log(`${osloTime} | ${utcTime}Z | ${entry.mode.padEnd(6)} | ${entry.price}`);
45
- });
46
-
47
- console.log("\n=== getCurrentMode() TIMEZONE TEST ===\n");
48
-
49
- // Test 1: Pass UTC time (simulating machine running in UTC)
50
- const utcNow = DateTime.fromISO("2026-01-16T07:30:00Z"); // 08:30 Oslo
51
- const modeFromUtc = getCurrentMode(schedule, utcNow);
52
- console.log(`Test 1: UTC time "2026-01-16T07:30:00Z" (= 08:30 Oslo)`);
53
- console.log(` Mode: ${modeFromUtc}`);
54
- console.log(` Expected: Should match 08:00 Oslo entry\n`);
55
-
56
- // Test 2: Pass Oslo time with offset (full ISO)
57
- const osloNow = DateTime.fromISO("2026-01-16T08:30:00+01:00");
58
- const modeFromOslo = getCurrentMode(schedule, osloNow);
59
- console.log(`Test 2: Oslo time "2026-01-16T08:30:00+01:00"`);
60
- console.log(` Mode: ${modeFromOslo}`);
61
- console.log(` Expected: Same as Test 1\n`);
62
-
63
- // Test 3: Pass Oslo time without offset (string)
64
- // This is the tricky case - should be interpreted as Oslo time
65
- const osloNoOffset = "2026-01-16T08:30:00";
66
- const modeFromOsloString = getCurrentMode(schedule, osloNoOffset);
67
- console.log(`Test 3: Oslo time string "2026-01-16T08:30:00" (no offset)`);
68
- console.log(` Mode: ${modeFromOsloString}`);
69
- console.log(` Note: String without offset - getCurrentMode converts via fromISO`);
70
-
71
- // Test 4: Verify UTC vs Oslo time consistency
72
- console.log("\n=== VERIFICATION ===\n");
73
- const testTime = "2026-01-16T02:30:00Z"; // 03:30 Oslo - should be in cheap period
74
- const mode = getCurrentMode(schedule, DateTime.fromISO(testTime));
75
- console.log(`At ${testTime} (03:30 Oslo), mode = ${mode}`);
76
- console.log(`Expected: boost (cheap period at 02:00-03:00 UTC = 03:00-04:00 Oslo)`);
77
-
78
- // Summary
79
- console.log("\n=== SUMMARY ===\n");
80
- console.log("✓ Schedule stores both 'time' (Oslo) and 'timeUtc' (UTC)");
81
- console.log("✓ getCurrentMode() compares using UTC internally");
82
- console.log("✓ Works correctly whether machine runs in UTC or local time");
83
- console.log("✓ Display times (status, logs) use configured timezone (Oslo)");