Verbräuche aus Zählerständen berechnen

Zielstellung

  • Berechnen von Verbräuchen pro 15min aus Zählständen von bspw. Energie-/Durchfluss/Wasserzähler
  • Umgang mit Rücksetzen von Zählern bei bspw. Zählertausch
  • Speisen einer neuen Tabelle basierend auf den berechneten Verläufen

Einstellungen Trigger

  • Art der Ausführung: Nach Zeitplan
  • Datenquellen: Datenquelle mit Zählständen
  • Zeitpläne: Stündlich, Ausführung um 00/15/30/45 Minuten nach der Stunde
    • Zusätzlich: 20 Minuten & 1 Zeile
    • Daten seit letzter Ausführung
  • Ausführung verzögern: 5min (Kompensation von Datenverzögerungen)
  • Zeitzone: Lokale Zeitzone, in der die Auswertung stattfinden soll

Berechnung

Konfigurieren Sie eine Berechnungskomponente und fügen Sie die gewünschten Zählstände als Input ein. Den Output benennen Sie gleich wie den Input. Dann verknüpfen Sie diese mit einer Tabelle.
Zur Berechnung nutzen Sie diesen Code:

const WINDOW_MS = 15 * 60 * 1000;

/**
 * Average consumption per 15-minute interval, based on the two most recent
 * non-null readings that fall within (end - 15min, end].
 *
 * Returns null if there's not enough data, or if the values decrease
 * (meter reset/rollover); returns 0 if the readings are unchanged.
 */
function calculateAverageConsumption(values, timestamps, end) {
  if (values.length < 2) return null;

  // Only rows from (end - 15min) onward are "in window" — plus one more row
  // before that boundary, kept as the baseline reading to diff against.
  const windowStart = new Date(end).getTime() - WINDOW_MS;
  let boundaryIndex = timestamps.length - 1;
  while (boundaryIndex >= 0 && new Date(timestamps[boundaryIndex]).getTime() >= windowStart) {
    boundaryIndex--;
  }
  const minIndex = Math.max(boundaryIndex, 0);

  // Find the last non-null value within the considered range.
  let lastIndex = values.length - 1;
  while (lastIndex >= minIndex && values[lastIndex] === null) {
    lastIndex--;
  }
  if (lastIndex < minIndex) return null;

  // Find the previous non-null value before it, within the considered range.
  let prevIndex = lastIndex - 1;
  while (prevIndex >= minIndex && values[prevIndex] === null) {
    prevIndex--;
  }
  if (prevIndex < minIndex) return null;

  const lastValue = values[lastIndex];
  const prevValue = values[prevIndex];

  // A decrease indicates a meter reset/rollover — not a valid consumption reading.
  if (lastValue < prevValue) return null;
  if (lastValue === prevValue) return 0;

  const timeDiffMs = new Date(timestamps[lastIndex]).getTime() - new Date(timestamps[prevIndex]).getTime();
  if (isNaN(timeDiffMs) || timeDiffMs <= 0) return null;

  // Normalize the delta to a rate per 15-minute interval.
  const intervals = timeDiffMs / WINDOW_MS;
  if (intervals <= 0) return null;

  return (lastValue - prevValue) / intervals;
}

function main(inputs) {
  const result = {};
  for (const key in inputs) {
    if (key === "timestamp" || key === "_end") continue;
    result[key] = calculateAverageConsumption(inputs[key], inputs.timestamp, inputs._end);
  }
  return result;
}