API: Externe Integrationen sollten ihre eigenen Energievertragstypen deklarieren können

Hallo zusammen,

Im Anschluss an die Diskussion über Wochenendtarife und die Antwort von @pierre-gilles auf Gladys#2999 folgt hier der entsprechende Funktionswunsch.

Zuerst das Prinzip. Pierre-Gilles schlug vor, den Kern generisch zu halten und es externen Integrationen zu ermöglichen, neue Vertragstypen zu deklarieren, anstatt im Kern landesspezifische Fälle zu stapeln. Ich bin voll und ganz damit einverstanden, und beim Betrachten des Codes finde ich, dass es noch relevanter ist, als ich dachte: Die Architektur geht bereits in diese Richtung, es fehlt vor allem die Öffnung.

Zu beachten ist, dass @mutmut bereits einen Wunsch zur Verwaltung der Tarife nach Tag, Saison und Ferien gestellt hat. Dieser ersetzt den seinen nicht: Sein Wunsch beschreibt den Nutzerbedarf, dieser beschreibt den technischen Mechanismus, der es ermöglichen würde, darauf zu reagieren, ohne den Kern zu überladen. Beide sind komplementär.

Was bereits existiert und in die richtige Richtung geht

server/services/energy-monitoring/contracts/contracts.calculateCost.js ist bereits ein nach Vertragstyp indexiertes Wörterbuch, bei dem jeder Eintrag eine Berechnungsfunktion mit einer einheitlichen Signatur ist:

(energyPricesAtConsumptionDate, consumptionDate, consumptionValue, systemTimezone, context) => cost

Das ist genau ein Erweiterungspunkt. Besser noch: Der Parameter context wird bereits verwendet, um externe Daten zu injizieren — Tempo erhält edfTempoHistoricalMap, d. h. einen Kalender mit Farbtagen, der anderswo abgerufen wird. Das Beispiel eines Vertragstyps, der von einer externen Datenquelle abhängt, ist also bereits etabliert.

Auf der Speicherseite sind hour_slots eine einfache freie Zeichenkette und subscribed_power ebenfalls. Eine externe Integration könnte dort codieren, was sie möchte, ohne das Schema zu berühren.

Was blockiert

Vier Hindernisse, vom schwierigsten zum einfachsten:

  1. Das SQL-Modell. In server/models/energy_price.js ist die Spalte contract ein DataTypes.ENUM(...ENERGY_CONTRACT_TYPES_LIST), und day_type ein ENUM(...ENERGY_PRICE_DAY_TYPES_LIST). Das ist das echte Hindernis: Das Hinzufügen eines Typs erfordert eine Datenbankmigration, was eine externe Integration nicht tun kann.
  2. Die harte Liste ENERGY_CONTRACT_TYPES in server/utils/constants.js, die diesen ENUM und die Servervalidierung füttert.
  3. Das Berechnungswörterbuch ist nicht registrierbar: Die drei Handler sind hart im Modul geschrieben.
  4. Das Frontend, mit KNOWN_CONTRACT_TYPES hart in ImportPrices.jsx und die Beschriftungen in contractTypes der i18n-Dateien.

Was ich vorschlage

a) contract und day_type von ENUM zu STRING wechseln, indem die Validierung in die Anwendungsschicht verschoben wird (gegen die Liste der registrierten Typen, Kern + Integrationen). Eine Namenskonvention pro Integration würde Kollisionen vermeiden, z. B. <service>:<type>. Das ist die strukturelle Änderung: Ohne sie ist keine externe Erweiterung möglich.

b) Eine Registrierungs-API auf dem Service anbieten, im Sinne von:

gladys.energyMonitoring.registerContractType({
  id: 'edf-zen-week-end',
  calculateCost: async (prices, date, value, timezone, context) => { ... },
});

Die Signatur ist bereits die der bestehenden Handler, sodass die drei aktuellen Typen zu diesem Mechanismus migriert werden könnten, ohne ihre Logik zu ändern — eine gute Möglichkeit, die API im Vorbeigehen zu validieren.

c) Das Import-Bildschirm durch Daten steuerbar machen: Die Liste der Typen und die Beschriftung kommen von den registrierten Typen, mit einem sauberen Fallback, wenn keine Übersetzung existiert. Der aktuelle Zeitfenster-Selektor bliebe das Standardverhalten.

d) Optional, für später: Erlauben, dass die Integration den Typ des Preis-Editors angibt, den sie benötigt, oder sogar ihren eigenen Bildschirm bereitstellt.

Mit diesem Ansatz könnte eine Integration „Energie Frankreich“ Zen Week-End, Tempo, die Angebote von Engie / OHM / Enercoop und die saisonalen Fälle von @mutmut unterstützen, ohne dass der Kern den Kalender der französischen Feiertage kennen muss.

Eine offene Frage

Heute leben die Tariftabellen im Gemeinschaftsdepot energy-contracts, und das ist wertvoll: Wenn jemand die EDF-Preise aktualisiert, profitiert jeder davon. Wenn die landesspezifischen Typen in eine externe Integration wandern, wo gehen die Preisdaten hin? Bleiben sie in energy-contracts mit der Integration, die nur die Berechnungslogik hinzufügt, oder ziehen sie ebenfalls um?

Ich habe eine Präferenz für die erste Option — ein einziges Depot für Tabellen beibehalten, gemeinsam genutzt, und nur den Geschäftslogik-Code herausnehmen — aber das ist wirklich eine Frage, keine festgelegte Position.

Danke!

Kurze Rückmeldung zu dieser Anfrage, die unbeantwortet blieb: Statt zu warten, habe ich etwas mit Fable 5.1 versucht: PR erstellen!!! Versucht habe ich, so nah wie möglich an dem zu bleiben, was bereits im Kern existiert.

PR: https://github.com/GladysAssistant/Gladys/pull/3099

Hier sind einige Präzisierungen der KI:

Beim erneuten Lesen des Codes habe ich meine Meinung im Vergleich zu meiner ersten Nachricht geändert. Die Berechnungsfunktion durch die Integration zu tragen, wie ich es vorgeschlagen hatte, hätte den Kern gezwungen, die Integration für jedes 30-Minuten-Sample abzufragen: Zehntausende von WebSocket-Befehlen, die für eine Neuberechnung von Anfang an quittiert werden, und ein nicht geprüfter Drittanbieter-Code, der über die zu berechnenden Beträge entscheidet. Das ist nicht vernünftig.

Der Vorschlag in der PR ist bescheidener und übernimmt genau das Schema des Typs weather (Spec B.18):

  • Ein neuer Typ der externen Integration energy-calendar. Der Kern fragt ihn einmal pro Berechnung und in einem Datumsbereich nach dem Tagestyp jedes Tages (weekday, weekend, holiday… der Wortschatz ist frei, die Integration veröffentlicht ihn). Die Antwort ist normalisiert und begrenzt, bevor sie in den Kern eintritt, wie beim Wetter. Kein Gerätedisplay, kein Gerät: eine dedizierte API.
  • Ein generischer Vertragstyp day-type im Kern. Seine Preise tragen einen freien day_type und optionale hour_slots. Die Berechnung ist die von Tempo, mit dem Tagestyp des Kalenders anstelle der Farbe: Man filtert die Preise nach Tagestyp und dann nach Zeitschlitz; ein Preis ohne Zeitschlitz gilt für den ganzen Tag (das ist die Zeile „Wochenende“ eines EDF Zen Week-End-Vertrags). Der Kern kennt keinen nationalen Kalender.
  • day_type wechselt von einem ENUM der Tempo-Farben zu einer validierten Zeichenkette. Unter SQLite ist ein ENUM eine TEXT-Spalte, also keine Migration, und Tempo funktioniert weiter wie bisher.
  • Auf der Schnittstellenseite: Der Vertrag im Selektor des Preiseditors, das Suffix -day-type wird vom Importbildschirm erkannt, und der Typ in den Bildschirmen der externen Integration. Übersetzungen auf en, fr, de.
  • Die Spec docs/specs/external-integrations.md hat einen Abschnitt B.19, der alles detailliert beschreibt.

Was folgen würde, außerhalb dieses Repositories: ein Handler onEnergyCalendarGetDayTypes im SDK, die Hinzufügung des Typs im Manifestschema von integration-store, eine Integration „Kalender Frankreich“ (Wochenenden und Feiertage, lokal berechnet), und das EDF Zen Week-End-Raster in energy-contracts. Die Tarifraster würden also zentralisiert bleiben, die Integration würde nur den Kalender liefern.

Server-Tests mit 100 % Abdeckung auf den hinzugefügten Zeilen, Lint, Übersetzungen und Frontend-Build ebenfalls. Ich bin natürlich offen für jedes Feedback zur Aufteilung oder Benennung, bevor ich weitergehe.

Hallo zusammen!

Heute gibt es im Energietracking von Gladys drei Arten von Tarifen: Basis, Heures Creuses und Tempo. Sobald ein Tarif außerhalb dieser Kategorien liegt (Wochenendangebot, Feiertage, Saisontarif, Spotpreis…), kann er nicht verwendet werden.

Ich arbeite gerade daran, das grundlegend zu ändern :raising_hands:

Was kommt:

  • Ein echter « Vertrag » pro Zähler, erstellt in wenigen Klicks: Auswahl des Zählers, Auswahl des Modells, Einstellung der Parameter (Ihre Heures Creuses, Ihr Abonnement…), und eine Vorschau der Kosten für Ihre letzten 7 Tage vor dem Speichern.
  • Viel mehr mögliche Tarife: Heures Creuses, Wochenenden, Feiertage, Schulferien, Jahreszeiten, Verbrauchsstufen, tägliche Preisänderungen… und nicht nur in Frankreich.
  • Tarifkalender: Feiertage, Tempo-Farben, Schulferien… Gladys verwendet diese, um den richtigen Preis zum richtigen Zeitpunkt anzuwenden.
  • Ein Widget « aktueller Preis » auf dem Dashboard, das den aktuellen Preis und die nächste Änderung anzeigt.
  • Szenarien basierend auf dem Preis: Zum Beispiel, « wenn die Heures Creuses beginnen, den Boiler einschalten ».
  • Von der Community veröffentlichbare Verträge, ohne auf ein Gladys-Update zu warten: Entweder einfach zum Community-Katalog hinzugefügt oder als externe Integration für komplexere Fälle (Kalender, die aktualisiert werden müssen, spezifische Preisberechnung…).

Für die Screenshots unten habe ich eine kleine Testintegration erstellt, « France Week-end & Vacances », mit drei Verträgen: Heures Creuses + Wochenende + Feiertage, ein reduziertes Angebot während der Schulferien und ein Treueangebot, das von der Integration selbst berechnet wird.

Ihre aktuellen Verträge werden automatisch migriert, Sie müssen nichts neu machen.

Es wird noch überarbeitet, bevor es in einer kommenden Version erscheint :slight_smile:

Der PR: https://github.com/GladysAssistant/Gladys/pull/3130

Das Docker-Image, falls Sie testen möchten:

ghcr.io/gladysassistant/gladys-preview:claude-amazing-einstein-6f4ub7

Ich liebe es :heart_eyes: !!! Ich glaube nicht, dass ich Zeit haben werde, dein Bild in naher Zukunft zu testen, aber das beantwortet mein Problem (und viele andere nebenbei :stuck_out_tongue: )

perfekt, ich kann eine virtuelle Vorrichtung mit 6 Funktionen, einen Node-Red-Fluss und eine Aktualisierungsszene löschen :partying_face:

Ich wäre an Testern interessiert, die an Produktionskopien arbeiten, um sicherzustellen, dass die Berechnungen vor und nach den Änderungen dieselben bleiben!

Wird dies dynamische Preisaktualisierungen ermöglichen? Zum Beispiel ist es in Finnland nicht ungewöhnlich, einen Stromvertrag zu haben, bei dem der Preis dem Spotstrompreis folgt und sich daher alle 15 Minuten ändert und die Preise jeden Tag unterschiedlich sind. Ich kann die Preise für morgen problemlos über öffentliche APIs abrufen, aber ich habe noch keine gute Möglichkeit gefunden, dies in Gladys zu integrieren, und habe stattdessen die gesamte benutzerdefinierte Logik direkt in Node-RED implementiert, um Schalter zu aktivieren, um die günstigsten Tageszeiten zu nutzen.