iobroker.utility-monitor 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,6 +108,36 @@ wasser.main.consumption.daily
108
108
  - **Klarheit**: Keine Special-Case Logik mehr im Code
109
109
  - **Multi-Meter**: Bessere Unterstützung für mehrere Zähler pro Typ
110
110
  - **CSV Import**: Einfaches Nachpflegen von historischen Daten via Drag-and-Drop im Admin-Interface
111
+ - **Strukturierte Statistiken (v1.6.0)**: Klare Trennung von Verbrauch, Kosten und Zeitstempeln
112
+
113
+ ---
114
+
115
+ ## ⚠️ Breaking Changes in Version 1.6.0
116
+
117
+ **WICHTIG:** Version 1.6.0 strukturiert das Statistik-Objekt um!
118
+
119
+ ### Was hat sich geändert?
120
+
121
+ **Vorher (bis 1.5.1):**
122
+
123
+ ```
124
+ gas.main.statistics.lastDay
125
+ gas.main.statistics.lastMonth
126
+ gas.main.statistics.lastDayStart
127
+ ```
128
+
129
+ **Jetzt (ab 1.6.0):**
130
+
131
+ ```
132
+ gas.main.statistics.consumption.lastDay ← Verbrauchswerte
133
+ gas.main.statistics.cost.lastDay ← Kostenwerte (NEU!)
134
+ gas.main.statistics.timestamps.lastDayStart ← Zeitstempel der Resets
135
+ ```
136
+
137
+ ### 🔧 Migration erforderlich
138
+
139
+ 1. **Skripte/VIS anpassen**: Falls du direkt auf Statistik-Datenpunkte zugreifst, musst du die Pfade anpassen.
140
+ 2. **Kostenstatistiken**: Du profitierst nun von historischen Kostenübersichten (Tag/Woche/Monat).
111
141
 
112
142
  ---
113
143
 
@@ -212,13 +242,34 @@ Balance: +62,64 € → Nachzahlung
212
242
 
213
243
  ### 📈 **statistics** (Statistiken)
214
244
 
245
+ Seit Version 1.6.1 sind Statistiken in drei Unterkanäle unterteilt:
246
+
247
+ #### 📊 **consumption** (Verbrauchshistorie)
248
+
249
+ | Datenpunkt | Beschreibung |
250
+ | ---------------- | ------------------------------------ |
251
+ | `lastDay` | Verbrauch **gestern** (Vortag) |
252
+ | `lastWeek` | Verbrauch **letzte Woche** |
253
+ | `lastMonth` | Verbrauch **letzter Monat** |
254
+ | `lastYear` | Verbrauch **letztes Jahr** (Vorjahr) |
255
+ | `averageDaily` | Durchschnittlicher Tagesverbrauch |
256
+ | `averageMonthly` | Durchschnittlicher Monatsverbrauch |
257
+
258
+ #### 💰 **cost** (Kostenhistorie - NEU in 1.6.0)
259
+
260
+ | Datenpunkt | Beschreibung |
261
+ | ---------------- | --------------------------------- |
262
+ | `lastDay` | Kosten **gestern** (Vortag) |
263
+ | `lastWeek` | Kosten **letzte Woche** |
264
+ | `lastMonth` | Kosten **letzter Monat** |
265
+ | `lastYear` | Kosten **letztes Jahr** (Vorjahr) |
266
+ | `averageDaily` | Durchschnittliche Tageskosten |
267
+ | `averageMonthly` | Durchschnittliche Monatskosten |
268
+
269
+ #### 📅 **timestamps** (Reset-Zeitstempel)
270
+
215
271
  | Datenpunkt | Beschreibung |
216
272
  | ---------------- | ---------------------------------------- |
217
- | `averageDaily` | Durchschnittlicher Tagesverbrauch |
218
- | `averageMonthly` | Durchschnittlicher Monatsverbrauch |
219
- | `lastDay` | Verbrauch **gestern** (Vortag) |
220
- | `lastWeek` | Verbrauch **letzte Woche** |
221
- | `lastMonth` | Verbrauch **letzter Monat** |
222
273
  | `lastDayStart` | Letzter Tages-Reset (23:59 Uhr) |
223
274
  | `lastWeekStart` | Letzter Wochen-Reset (Sonntag 23:59) |
224
275
  | `lastMonthStart` | Letzter Monats-Reset (letzter Tag 23:59) |
@@ -308,6 +359,28 @@ Der Adapter setzt Zähler automatisch zurück:
308
359
 
309
360
  ## Changelog
310
361
 
362
+ ### 1.6.1 (2026-01-28)
363
+
364
+ - **NEU:** 📊 **Erweiterte Jahresstatistiken** - Einführung von `lastYear` Datenpunkten in den Statistiken:
365
+ - `statistics.consumption.lastYear`: Gesamtverbrauch des Vorjahres
366
+ - `statistics.cost.lastYear`: Gesamtkosten des Vorjahres
367
+ - Unterstützung für HT/NT und Gas-Volumen in der Vorjahresansicht
368
+ - **NEU:** 🔄 **Automatisches Sichern** - Vorjahreswerte werden beim jährlichen Reset automatisch in die Statistik archiviert
369
+ - **FIX:** 🛠️ **Syntax & Einheiten** - Korrektur von Einheiten-Inkonsistenzen (speziell Wasser/m³) und Linter-Fehlern
370
+ - **DOCS:** 🌐 **Übersetzungen** - News-Einträge in alle unterstützten Sprachen übersetzt
371
+
372
+ ### 1.6.0 (2026-01-28)
373
+
374
+ - **NEU:** 📊 **Strukturierte Statistiken** - Einführung von Unterkanälen für bessere Übersicht:
375
+ - `statistics.consumption`: Alle historischen Verbrauchswerte
376
+ - `statistics.cost`: Alle historischen Kostenwerte (Tag/Woche/Monat)
377
+ - `statistics.timestamps`: Alle Reset-Zeitstempel an einem Ort
378
+ - **NEU:** 💰 **Kostenstatistiken** - Verfolge deine Kosten nun auch für gestern, letzte Woche und letzten Monat
379
+ - **REFACTORING:** 🏗️ **Modulare State-Verwaltung**:
380
+ - `stateManager.js` wurde in spezialisierte Module aufgeteilt (`lib/state/`)
381
+ - Verbesserte Wartbarkeit und Testbarkeit
382
+ - **CLEANUP:** 🧹 **Bereinigung** - Automatische Entfernung veralteter Statistik-Datenpunkte beim ersten Start
383
+
311
384
  ### 1.5.1 (2026-01-26)
312
385
 
313
386
  - **FIX:** 🕛 **Reset-Timing** - Automatische Resets werden nun um 23:59 Uhr ausgeführt (statt 00:00 Uhr)
package/io-package.json CHANGED
@@ -1,8 +1,38 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "utility-monitor",
4
- "version": "1.5.0",
4
+ "version": "1.6.1",
5
5
  "news": {
6
+ "1.6.1": {
7
+ "en": "New statistics data points: lastDay, lastWeek, lastMonth, and lastYear for costs and consumption.",
8
+ "de": "Neue Statistik-Datenpunkte: lastDay, lastWeek, lastMonth, lastYear für Kosten und Verbrauch.",
9
+ "ru": "Новые точки статистических данных: lastDay, lastWeek, lastMonth и lastYear для затрат и потребления.",
10
+ "pt": "Novos pontos de dados estatísticos: lastDay, lastWeek, lastMonth e lastYear para custos e consumo.",
11
+ "nl": "Nieuwe statistiekgegevenspunten: lastDay, lastWeek, lastMonth en lastYear voor kosten en verbruik.",
12
+ "fr": "Nouveaux points de données statistiques : lastDay, lastWeek, lastMonth et lastYear pour les coûts et la consommation.",
13
+ "it": "Nuovi punti dati statistici: lastDay, lastWeek, lastMonth e lastYear per costi e consumi.",
14
+ "es": "Nuevos puntos de datos estadísticos: lastDay, lastWeek, lastMonth y lastYear para costes y consumo.",
15
+ "pl": "Nowe punkty danych statystycznych: lastDay, lastWeek, lastMonth i lastYear dla kosztów i zużycia.",
16
+ "uk": "Нові статистичні дані: lastDay, lastWeek, lastMonth і lastYear для витрат і споживання.",
17
+ "zh-cn": "新的统计数据点:成本和消耗的 lastDay、lastWeek、lastMonth 和 lastYear。"
18
+ },
19
+ "1.6.0": {
20
+ "en": "New: Structured statistics with consumption and cost sub-channels. New: Weekly and monthly cost tracking. Refactoring: Split stateManager.js into modular files. Cleanup: Removed redundant old statistics states.",
21
+ "de": "Neu: Strukturierte Statistiken mit Unterkanälen für Verbrauch und Kosten. Neu: Wöchentliche und monatliche Kostenverfolgung. Refactoring: stateManager.js in modulare Dateien aufgeteilt. Bereinigung: Veraltete Statistik-Datenpunkte entfernt."
22
+ },
23
+ "1.5.1": {
24
+ "en": "Fix: Reset timing and monthly calculation logic. Fix: JSDoc type definitions.",
25
+ "de": "Fix: Reset-Timing und monatliche Berechnungslogik. Fix: JSDoc Typ-Definitionen.",
26
+ "ru": "Исправление: время сброса и логика ежемесячного расчета. Исправление: определения типов JSDoc.",
27
+ "pt": "Correção: tempo de redefinição e lógica de cálculo mensal. Correção: definições de tipo JSDoc.",
28
+ "nl": "Fix: Reset timing en maandelijkse berekeningslogica. Fix: JSDoc type definities.",
29
+ "fr": "Correctif : synchronisation de la réinitialisation et logique de calcul mensuel. Correctif : définitions de type JSDoc.",
30
+ "it": "Correzione: tempi di ripristino e logica di calcolo mensile. Correzione: definizioni dei tipi JSDoc.",
31
+ "es": "Solución: sincronización de reinicio y lógica de cálculo mensual. Solución: definiciones de tipos de JSDoc.",
32
+ "pl": "Naprawa: czas resetowania i logika obliczeń miesięcznych. Naprawa: definicje typów JSDoc.",
33
+ "uk": "Виправлення: час скидання та логіка щомісячного розрахунку. Виправлення: визначення типів JSDoc.",
34
+ "zh-cn": "修复:重置时间和每月计算逻辑。修复:JSDoc 类型定义。"
35
+ },
6
36
  "1.5.0": {
7
37
  "en": "New: CSV Import feature with drag and drop support. New: Weekly tracking points. Fix: Resets now performed at 23:59. Refactoring: Modular backend architecture.",
8
38
  "de": "Neu: CSV-Import mit Drag-and-Drop. Neu: Wöchentliche Datenpunkte. Fix: Resets werden jetzt um 23:59 Uhr ausgeführt. Refactoring: Modulare Backend-Architektur.",
@@ -171,7 +171,7 @@ class BillingManager {
171
171
  }
172
172
 
173
173
  if (!startDate || isNaN(startDate.getTime())) {
174
- const lastYearStart = await this.adapter.getStateAsync(`${type}.statistics.lastYearStart`);
174
+ const lastYearStart = await this.adapter.getStateAsync(`${type}.statistics.timestamps.lastYearStart`);
175
175
  startDate = new Date(lastYearStart?.val || Date.now());
176
176
  }
177
177
 
@@ -372,7 +372,11 @@ class BillingManager {
372
372
  // even if the user closes the period early (e.g. 2 days before)
373
373
  const thisYearAnniversary = new Date(startDate);
374
374
  thisYearAnniversary.setFullYear(new Date().getFullYear());
375
- await this.adapter.setStateAsync(`${type}.statistics.lastYearStart`, thisYearAnniversary.getTime(), true);
375
+ await this.adapter.setStateAsync(
376
+ `${type}.statistics.timestamps.lastYearStart`,
377
+ thisYearAnniversary.getTime(),
378
+ true,
379
+ );
376
380
 
377
381
  this.adapter.log.info(`✅ Abrechnungszeitraum ${year} für ${type} erfolgreich abgeschlossen!`);
378
382
  this.adapter.log.info(
@@ -526,7 +530,11 @@ class BillingManager {
526
530
  // Update lastYearStart to contract anniversary
527
531
  const thisYearAnniversary = new Date(startDate);
528
532
  thisYearAnniversary.setFullYear(new Date().getFullYear());
529
- await this.adapter.setStateAsync(`${basePath}.statistics.lastYearStart`, thisYearAnniversary.getTime(), true);
533
+ await this.adapter.setStateAsync(
534
+ `${basePath}.statistics.timestamps.lastYearStart`,
535
+ thisYearAnniversary.getTime(),
536
+ true,
537
+ );
530
538
 
531
539
  // Update totals if multiple meters exist
532
540
  const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
@@ -644,7 +652,7 @@ class BillingManager {
644
652
  };
645
653
 
646
654
  // DAILY RESET: Trigger at 23:59 if today's reset hasn't happened yet
647
- const lastDayStart = await this.adapter.getStateAsync(`${basePath}.statistics.lastDayStart`);
655
+ const lastDayStart = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastDayStart`);
648
656
  if (lastDayStart?.val) {
649
657
  const lastResetTime = lastDayStart.val;
650
658
  const alreadyResetToday = isValidResetToday(lastResetTime);
@@ -665,7 +673,7 @@ class BillingManager {
665
673
  }
666
674
 
667
675
  // WEEKLY RESET: Trigger at 23:59 on Sunday if this week's reset hasn't happened yet
668
- const lastWeekStart = await this.adapter.getStateAsync(`${basePath}.statistics.lastWeekStart`);
676
+ const lastWeekStart = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`);
669
677
  if (lastWeekStart?.val) {
670
678
  const lastWeekTime = lastWeekStart.val;
671
679
  const isSunday = nowDate.getDay() === 0; // 0 = Sunday
@@ -694,7 +702,9 @@ class BillingManager {
694
702
 
695
703
  // MONTHLY RESET: Trigger at 23:59 on last day of month
696
704
  if (meters.length > 0) {
697
- const lastMonthStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastMonthStart`);
705
+ const lastMonthStartState = await this.adapter.getStateAsync(
706
+ `${basePath}.statistics.timestamps.lastMonthStart`,
707
+ );
698
708
  if (lastMonthStartState?.val) {
699
709
  const lastMonthTime = lastMonthStartState.val;
700
710
  const lastMonthDate = new Date(lastMonthTime);
@@ -725,7 +735,7 @@ class BillingManager {
725
735
  for (const meter of meters) {
726
736
  const meterBasePath = `${type}.${meter.name}`;
727
737
  const lastYearStartState = await this.adapter.getStateAsync(
728
- `${meterBasePath}.statistics.lastYearStart`,
738
+ `${meterBasePath}.statistics.timestamps.lastYearStart`,
729
739
  );
730
740
 
731
741
  if (lastYearStartState?.val) {
@@ -835,7 +845,12 @@ class BillingManager {
835
845
  const dailyValue = dailyState?.val || 0;
836
846
 
837
847
  // Save last day consumption
838
- await this.adapter.setStateAsync(`${basePath}.statistics.lastDay`, dailyValue, true);
848
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastDay`, dailyValue, true);
849
+
850
+ // Save last day costs
851
+ const dailyCostState = await this.adapter.getStateAsync(`${basePath}.costs.daily`);
852
+ const dailyCostValue = dailyCostState?.val || 0;
853
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDay`, dailyCostValue, true);
839
854
 
840
855
  await this.adapter.setStateAsync(`${basePath}.consumption.daily`, 0, true);
841
856
 
@@ -843,7 +858,11 @@ class BillingManager {
843
858
  const dailyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.dailyVolume`);
844
859
  const dailyVolumeValue = dailyVolume?.val || 0;
845
860
  // Save last day volume for gas
846
- await this.adapter.setStateAsync(`${basePath}.statistics.lastDayVolume`, dailyVolumeValue, true);
861
+ await this.adapter.setStateAsync(
862
+ `${basePath}.statistics.consumption.lastDayVolume`,
863
+ dailyVolumeValue,
864
+ true,
865
+ );
847
866
  await this.adapter.setStateAsync(`${basePath}.consumption.dailyVolume`, 0, true);
848
867
  }
849
868
 
@@ -853,23 +872,44 @@ class BillingManager {
853
872
  if (htNtEnabled) {
854
873
  const dailyHT = await this.adapter.getStateAsync(`${basePath}.consumption.dailyHT`);
855
874
  const dailyNT = await this.adapter.getStateAsync(`${basePath}.consumption.dailyNT`);
856
- await this.adapter.setStateAsync(`${basePath}.statistics.lastDayHT`, dailyHT?.val || 0, true);
857
- await this.adapter.setStateAsync(`${basePath}.statistics.lastDayNT`, dailyNT?.val || 0, true);
875
+ await this.adapter.setStateAsync(
876
+ `${basePath}.statistics.consumption.lastDayHT`,
877
+ dailyHT?.val || 0,
878
+ true,
879
+ );
880
+ await this.adapter.setStateAsync(
881
+ `${basePath}.statistics.consumption.lastDayNT`,
882
+ dailyNT?.val || 0,
883
+ true,
884
+ );
858
885
  await this.adapter.setStateAsync(`${basePath}.consumption.dailyHT`, 0, true);
859
886
  await this.adapter.setStateAsync(`${basePath}.consumption.dailyNT`, 0, true);
887
+
888
+ // Costs for HT/NT
889
+ const dailyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.dailyHT`))?.val || 0;
890
+ const dailyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.dailyNT`))?.val || 0;
891
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDayHT`, dailyCostHT, true);
892
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDayNT`, dailyCostNT, true);
893
+ await this.adapter.setStateAsync(`${basePath}.costs.dailyHT`, 0, true);
894
+ await this.adapter.setStateAsync(`${basePath}.costs.dailyNT`, 0, true);
860
895
  }
861
896
 
862
897
  await this.adapter.setStateAsync(`${basePath}.costs.daily`, 0, true);
863
898
 
864
899
  // Update lastDayStart timestamp (use normalized timestamp if provided)
865
900
  const timestamp = resetTimestamp || Date.now();
866
- await this.adapter.setStateAsync(`${basePath}.statistics.lastDayStart`, timestamp, true);
901
+ await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastDayStart`, timestamp, true);
867
902
 
868
903
  await this.adapter.setStateAsync(
869
- `${basePath}.statistics.averageDaily`,
904
+ `${basePath}.statistics.consumption.averageDaily`,
870
905
  calculator.roundToDecimals(dailyValue, 2),
871
906
  true,
872
907
  );
908
+ await this.adapter.setStateAsync(
909
+ `${basePath}.statistics.cost.averageDaily`,
910
+ calculator.roundToDecimals(dailyCostValue, 2),
911
+ true,
912
+ );
873
913
  }
874
914
 
875
915
  // Update totals if multiple meters exist
@@ -907,13 +947,22 @@ class BillingManager {
907
947
  const monthlyValue = monthlyState?.val || 0;
908
948
 
909
949
  // Save last month consumption
910
- await this.adapter.setStateAsync(`${basePath}.statistics.lastMonth`, monthlyValue, true);
950
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonth`, monthlyValue, true);
951
+
952
+ // Save last month costs
953
+ const monthlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.monthly`);
954
+ const monthlyCostValue = monthlyCostState?.val || 0;
955
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonth`, monthlyCostValue, true);
911
956
 
912
957
  // For gas: also save volume
913
958
  if (type === 'gas') {
914
959
  const monthlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.monthlyVolume`);
915
960
  const monthlyVolumeValue = monthlyVolume?.val || 0;
916
- await this.adapter.setStateAsync(`${basePath}.statistics.lastMonthVolume`, monthlyVolumeValue, true);
961
+ await this.adapter.setStateAsync(
962
+ `${basePath}.statistics.consumption.lastMonthVolume`,
963
+ monthlyVolumeValue,
964
+ true,
965
+ );
917
966
  await this.adapter.setStateAsync(`${basePath}.consumption.monthlyVolume`, 0, true);
918
967
  }
919
968
 
@@ -924,21 +973,38 @@ class BillingManager {
924
973
  const configType = getConfigType(type);
925
974
  const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
926
975
  if (htNtEnabled) {
976
+ const monthlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.monthlyHT`))?.val || 0;
977
+ const monthlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.monthlyNT`))?.val || 0;
978
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonthHT`, monthlyHT, true);
979
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonthNT`, monthlyNT, true);
927
980
  await this.adapter.setStateAsync(`${basePath}.consumption.monthlyHT`, 0, true);
928
981
  await this.adapter.setStateAsync(`${basePath}.consumption.monthlyNT`, 0, true);
982
+
983
+ // Costs for HT/NT
984
+ const monthlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.monthlyHT`))?.val || 0;
985
+ const monthlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.monthlyNT`))?.val || 0;
986
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonthHT`, monthlyCostHT, true);
987
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonthNT`, monthlyCostNT, true);
988
+ await this.adapter.setStateAsync(`${basePath}.costs.monthlyHT`, 0, true);
989
+ await this.adapter.setStateAsync(`${basePath}.costs.monthlyNT`, 0, true);
929
990
  }
930
991
 
931
992
  await this.adapter.setStateAsync(`${basePath}.costs.monthly`, 0, true);
932
993
 
933
994
  // Update lastMonthStart timestamp (use normalized timestamp if provided)
934
995
  const timestamp = resetTimestamp || Date.now();
935
- await this.adapter.setStateAsync(`${basePath}.statistics.lastMonthStart`, timestamp, true);
996
+ await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastMonthStart`, timestamp, true);
936
997
 
937
998
  await this.adapter.setStateAsync(
938
- `${basePath}.statistics.averageMonthly`,
999
+ `${basePath}.statistics.consumption.averageMonthly`,
939
1000
  calculator.roundToDecimals(monthlyValue, 2),
940
1001
  true,
941
1002
  );
1003
+ await this.adapter.setStateAsync(
1004
+ `${basePath}.statistics.cost.averageMonthly`,
1005
+ calculator.roundToDecimals(monthlyCostValue, 2),
1006
+ true,
1007
+ );
942
1008
  }
943
1009
 
944
1010
  // Update totals if multiple meters exist
@@ -970,18 +1036,51 @@ class BillingManager {
970
1036
 
971
1037
  this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
972
1038
 
1039
+ const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
1040
+ const yearlyValue = yearlyState?.val || 0;
1041
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYear`, yearlyValue, true);
1042
+
1043
+ const yearlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.yearly`);
1044
+ const yearlyCostValue = yearlyCostState?.val || 0;
1045
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYear`, yearlyCostValue, true);
1046
+
973
1047
  await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
974
1048
 
975
1049
  if (type === 'gas') {
1050
+ const yearlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`);
1051
+ const yearlyVolumeValue = yearlyVolume?.val || 0;
1052
+ await this.adapter.setStateAsync(
1053
+ `${basePath}.statistics.consumption.lastYearVolume`,
1054
+ yearlyVolumeValue,
1055
+ true,
1056
+ );
976
1057
  await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, 0, true);
977
1058
  }
978
1059
 
1060
+ const configType = getConfigType(type);
1061
+ const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
1062
+ if (htNtEnabled) {
1063
+ const yearlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyHT`))?.val || 0;
1064
+ const yearlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyNT`))?.val || 0;
1065
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearHT`, yearlyHT, true);
1066
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearNT`, yearlyNT, true);
1067
+ await this.adapter.setStateAsync(`${basePath}.consumption.yearlyHT`, 0, true);
1068
+ await this.adapter.setStateAsync(`${basePath}.consumption.yearlyNT`, 0, true);
1069
+
1070
+ const yearlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyHT`))?.val || 0;
1071
+ const yearlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyNT`))?.val || 0;
1072
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearHT`, yearlyCostHT, true);
1073
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearNT`, yearlyCostNT, true);
1074
+ await this.adapter.setStateAsync(`${basePath}.costs.yearlyHT`, 0, true);
1075
+ await this.adapter.setStateAsync(`${basePath}.costs.yearlyNT`, 0, true);
1076
+ }
1077
+
979
1078
  await this.adapter.setStateAsync(`${basePath}.costs.yearly`, 0, true);
980
1079
  await this.adapter.setStateAsync(`${basePath}.billing.notificationSent`, false, true);
981
1080
  await this.adapter.setStateAsync(`${basePath}.billing.notificationChangeSent`, false, true);
982
1081
 
983
1082
  // Update lastYearStart timestamp
984
- await this.adapter.setStateAsync(`${basePath}.statistics.lastYearStart`, Date.now(), true);
1083
+ await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastYearStart`, Date.now(), true);
985
1084
  }
986
1085
 
987
1086
  // Update totals if multiple meters exist
@@ -1003,19 +1102,52 @@ class BillingManager {
1003
1102
 
1004
1103
  this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
1005
1104
 
1105
+ const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
1106
+ const yearlyValue = yearlyState?.val || 0;
1107
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYear`, yearlyValue, true);
1108
+
1109
+ const yearlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.yearly`);
1110
+ const yearlyCostValue = yearlyCostState?.val || 0;
1111
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYear`, yearlyCostValue, true);
1112
+
1006
1113
  await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
1007
1114
 
1008
1115
  if (type === 'gas') {
1116
+ const yearlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`);
1117
+ const yearlyVolumeValue = yearlyVolume?.val || 0;
1118
+ await this.adapter.setStateAsync(
1119
+ `${basePath}.statistics.consumption.lastYearVolume`,
1120
+ yearlyVolumeValue,
1121
+ true,
1122
+ );
1009
1123
  await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, 0, true);
1010
1124
  }
1011
1125
 
1126
+ const configType = getConfigType(type);
1127
+ const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
1128
+ if (htNtEnabled) {
1129
+ const yearlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyHT`))?.val || 0;
1130
+ const yearlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyNT`))?.val || 0;
1131
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearHT`, yearlyHT, true);
1132
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearNT`, yearlyNT, true);
1133
+ await this.adapter.setStateAsync(`${basePath}.consumption.yearlyHT`, 0, true);
1134
+ await this.adapter.setStateAsync(`${basePath}.consumption.yearlyNT`, 0, true);
1135
+
1136
+ const yearlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyHT`))?.val || 0;
1137
+ const yearlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyNT`))?.val || 0;
1138
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearHT`, yearlyCostHT, true);
1139
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearNT`, yearlyCostNT, true);
1140
+ await this.adapter.setStateAsync(`${basePath}.costs.yearlyHT`, 0, true);
1141
+ await this.adapter.setStateAsync(`${basePath}.costs.yearlyNT`, 0, true);
1142
+ }
1143
+
1012
1144
  await this.adapter.setStateAsync(`${basePath}.costs.yearly`, 0, true);
1013
1145
  await this.adapter.setStateAsync(`${basePath}.billing.notificationSent`, false, true);
1014
1146
  await this.adapter.setStateAsync(`${basePath}.billing.notificationChangeSent`, false, true);
1015
1147
 
1016
1148
  // Update lastYearStart timestamp (use normalized timestamp if provided)
1017
1149
  const timestamp = resetTimestamp || Date.now();
1018
- await this.adapter.setStateAsync(`${basePath}.statistics.lastYearStart`, timestamp, true);
1150
+ await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastYearStart`, timestamp, true);
1019
1151
  }
1020
1152
 
1021
1153
  /**
@@ -1034,13 +1166,22 @@ class BillingManager {
1034
1166
  // Save last week consumption before reset
1035
1167
  const weeklyState = await this.adapter.getStateAsync(`${basePath}.consumption.weekly`);
1036
1168
  const weeklyValue = weeklyState?.val || 0;
1037
- await this.adapter.setStateAsync(`${basePath}.statistics.lastWeek`, weeklyValue, true);
1169
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeek`, weeklyValue, true);
1170
+
1171
+ // Save last week costs before reset
1172
+ const weeklyCostState = await this.adapter.getStateAsync(`${basePath}.costs.weekly`);
1173
+ const weeklyCostValue = weeklyCostState?.val || 0;
1174
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeek`, weeklyCostValue, true);
1038
1175
 
1039
1176
  // For gas: also save volume
1040
1177
  if (type === 'gas') {
1041
1178
  const weeklyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.weeklyVolume`);
1042
1179
  const weeklyVolumeValue = weeklyVolume?.val || 0;
1043
- await this.adapter.setStateAsync(`${basePath}.statistics.lastWeekVolume`, weeklyVolumeValue, true);
1180
+ await this.adapter.setStateAsync(
1181
+ `${basePath}.statistics.consumption.lastWeekVolume`,
1182
+ weeklyVolumeValue,
1183
+ true,
1184
+ );
1044
1185
  await this.adapter.setStateAsync(`${basePath}.consumption.weeklyVolume`, 0, true);
1045
1186
  }
1046
1187
 
@@ -1051,15 +1192,25 @@ class BillingManager {
1051
1192
  const configType = getConfigType(type);
1052
1193
  const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
1053
1194
  if (htNtEnabled) {
1195
+ const weeklyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.weeklyHT`))?.val || 0;
1196
+ const weeklyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.weeklyNT`))?.val || 0;
1197
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeekHT`, weeklyHT, true);
1198
+ await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeekNT`, weeklyNT, true);
1054
1199
  await this.adapter.setStateAsync(`${basePath}.consumption.weeklyHT`, 0, true);
1055
1200
  await this.adapter.setStateAsync(`${basePath}.consumption.weeklyNT`, 0, true);
1201
+
1202
+ // Costs for HT/NT
1203
+ const weeklyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.weeklyHT`))?.val || 0;
1204
+ const weeklyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.weeklyNT`))?.val || 0;
1205
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeekHT`, weeklyCostHT, true);
1206
+ await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeekNT`, weeklyCostNT, true);
1056
1207
  await this.adapter.setStateAsync(`${basePath}.costs.weeklyHT`, 0, true);
1057
1208
  await this.adapter.setStateAsync(`${basePath}.costs.weeklyNT`, 0, true);
1058
1209
  }
1059
1210
 
1060
1211
  // Update lastWeekStart timestamp (use normalized timestamp if provided)
1061
1212
  const timestamp = resetTimestamp || Date.now();
1062
- await this.adapter.setStateAsync(`${basePath}.statistics.lastWeekStart`, timestamp, true);
1213
+ await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`, timestamp, true);
1063
1214
  }
1064
1215
 
1065
1216
  if (meters.length > 1) {
@@ -227,7 +227,7 @@ class MultiMeterManager {
227
227
  const timestampRoles = ['lastDayStart', 'lastWeekStart', 'lastMonthStart', 'lastYearStart'];
228
228
 
229
229
  for (const role of timestampRoles) {
230
- const statePath = `${basePath}.statistics.${role}`;
230
+ const statePath = `${basePath}.statistics.timestamps.${role}`;
231
231
  const state = await this.adapter.getStateAsync(statePath);
232
232
 
233
233
  if (role === 'lastYearStart') {
@@ -325,8 +325,8 @@ class MultiMeterManager {
325
325
  const now = Date.now();
326
326
 
327
327
  // Get period start timestamps
328
- const lastWeekStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastWeekStart`);
329
- const lastDayStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastDayStart`);
328
+ const lastWeekStartState = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`);
329
+ const lastDayStartState = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastDayStart`);
330
330
 
331
331
  if (!lastWeekStartState?.val || !lastDayStartState?.val) {
332
332
  this.adapter.log.debug(`[${basePath}] No period timestamps found, skipping reconstruction`);
@@ -350,7 +350,7 @@ class MultiMeterManager {
350
350
  // Get current consumption values
351
351
  const weeklyState = await this.adapter.getStateAsync(`${basePath}.consumption.weekly`);
352
352
  const dailyState = await this.adapter.getStateAsync(`${basePath}.consumption.daily`);
353
- const lastDayState = await this.adapter.getStateAsync(`${basePath}.statistics.lastDay`);
353
+ const lastDayState = await this.adapter.getStateAsync(`${basePath}.statistics.consumption.lastDay`);
354
354
 
355
355
  const currentWeekly = weeklyState?.val || 0;
356
356
  const currentDaily = dailyState?.val || 0;
@@ -376,7 +376,9 @@ class MultiMeterManager {
376
376
  // Also reconstruct gas volume if applicable
377
377
  if (type === 'gas') {
378
378
  const weeklyVolumeState = await this.adapter.getStateAsync(`${basePath}.consumption.weeklyVolume`);
379
- const lastDayVolumeState = await this.adapter.getStateAsync(`${basePath}.statistics.lastDayVolume`);
379
+ const lastDayVolumeState = await this.adapter.getStateAsync(
380
+ `${basePath}.statistics.consumption.lastDayVolume`,
381
+ );
380
382
  const currentWeeklyVolume = weeklyVolumeState?.val || 0;
381
383
  const lastDayVolume = lastDayVolumeState?.val || 0;
382
384
 
@@ -396,7 +398,9 @@ class MultiMeterManager {
396
398
  }
397
399
 
398
400
  // Similar logic for monthly reconstruction
399
- const lastMonthStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastMonthStart`);
401
+ const lastMonthStartState = await this.adapter.getStateAsync(
402
+ `${basePath}.statistics.timestamps.lastMonthStart`,
403
+ );
400
404
  if (lastMonthStartState?.val) {
401
405
  const lastMonthStart = lastMonthStartState.val;
402
406
  const daysSinceMonthStart = (now - lastMonthStart) / (24 * 60 * 60 * 1000);
@@ -422,7 +426,7 @@ class MultiMeterManager {
422
426
  `${basePath}.consumption.monthlyVolume`,
423
427
  );
424
428
  const lastDayVolumeState = await this.adapter.getStateAsync(
425
- `${basePath}.statistics.lastDayVolume`,
429
+ `${basePath}.statistics.consumption.lastDayVolume`,
426
430
  );
427
431
  const currentMonthlyVolume = monthlyVolumeState?.val || 0;
428
432
  const lastDayVolume = lastDayVolumeState?.val || 0;
@@ -649,8 +653,10 @@ class MultiMeterManager {
649
653
  const value = state?.val || 0;
650
654
 
651
655
  // Get period start timestamp to calculate expected max consumption
656
+ const periodKey =
657
+ period === 'daily' ? 'Day' : period === 'weekly' ? 'Week' : period === 'monthly' ? 'Month' : 'Year';
652
658
  const periodStartState = await this.adapter.getStateAsync(
653
- `${basePath}.statistics.last${period.charAt(0).toUpperCase() + period.slice(1)}Start`,
659
+ `${basePath}.statistics.timestamps.last${periodKey}Start`,
654
660
  );
655
661
  const periodStart = periodStartState?.val || now;
656
662
  const daysSincePeriodStart = (now - periodStart) / (24 * 60 * 60 * 1000);
@@ -982,7 +988,7 @@ class MultiMeterManager {
982
988
  * @returns {Promise<number>} Months since start (at least 1)
983
989
  */
984
990
  async _calculateMonthsSinceYearStart(basePath) {
985
- const yearStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastYearStart`);
991
+ const yearStartState = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastYearStart`);
986
992
  let monthsSinceYearStart = 1;
987
993
 
988
994
  if (yearStartState && yearStartState.val) {