{ "openrpc": "1.3.2", "info": { "title": "etm-powersync — protocole moteur ↔ optimiseur", "version": "1.1", "license": { "name": "GPL-3.0-or-later", "url": "https://www.gnu.org/licenses/gpl-3.0.html" }, "description": "Contrat MACHINE du protocole entre le moteur d'énergie etm-powersync-energy-plugin-etm (client) et un optimiseur externe (serveur) — docs/OPTIMIZER_PROTOCOL.md en est la prose. Ce document est la source de la forme des messages ; il est servi par le moteur qui tourne (NymeaEnergy.GetOptimizerProtocol) et les deux côtés sont testés contre lui.\n\nCe qu'OpenRPC ne dit pas, et où cela se trouve :\n- TRANSPORT : unix:// (défaut, socket local 0660) ou tcp:// (LAN), JSON-RPC 2.0, un message par ligne (NDJSON), UTF-8 — x-etm-transport ;\n- SENS : le moteur est le CLIENT, il se connecte et envoie Handshake puis GetPlan ; la box n'ouvre aucun port. UpdateLearned va dans l'autre sens (x-etm-direction) ;\n- HEARTBEAT IMPLICITE et machine de repli §7 — x-etm-fallback-machine ;\n- VALIDATION SÉMANTIQUE du plan : le JSON Schema dit la forme ; ce que le validateur du moteur refuse en plus (charge inconnue, créneau courant incomplet…) est listé, règle par règle, dans x-etm-semantic-rules avec son fallbackCode ;\n- listes fermées : régénérées DEPUIS LE CODE (x-etm-generated), jamais recopiées à la main.\n\nForme côté moteur (requêtes) : STRICTE — additionalProperties: false ; un champ ajouté au sérialiseur sans le schéma fait échouer les tests. Forme côté optimiseur (réponses) : ouverte — un champ inconnu est ignoré (§9) ; le schéma dit ce qu'un optimiseur CONFORME envoie, x-etm-tolerances ce que le moteur accepte en plus." }, "servers": [ { "name": "unix", "url": "unix:///run/powersync/optimizer.sock", "summary": "Défaut : optimiseur local, socket possédé par root:powersync, mode 0660." }, { "name": "tcp", "url": "tcp://{host}:{port}", "summary": "Optimiseur sur le LAN. Token recommandé ; il circule en clair sans TLS (E16).", "variables": { "host": { "default": "192.168.1.10" }, "port": { "default": "7400" } } } ], "methods": [ { "name": "Handshake", "summary": "Ouverture de session, à chaque (re)connexion (§3).", "description": "Envoyé par le moteur (client) dès la connexion. Délai : 5 s depuis le DÉBUT de la tentative ; au-delà, repli et nouvelle tentative au backoff. Le message porte le secret : il n'est jamais journalisé.", "paramStructure": "by-name", "params": [ { "name": "protocolVersion", "required": true, "schema": { "$ref": "#/components/schemas/ProtocolVersion" }, "description": "Version du moteur ; seule la majeure est comparée." }, { "name": "engine", "required": true, "schema": { "type": "string", "pattern": "^etm-powersync-energy-plugin-etm/" }, "description": "Moteur et sa version." }, { "name": "token", "required": false, "schema": { "$ref": "#/components/schemas/HandshakeToken" } } ], "result": { "name": "handshake", "schema": { "$ref": "#/components/schemas/HandshakeResult" } }, "errors": [ { "code": -32001, "message": "token refusé", "data": "Recommandé (optimiseur de référence). Toute réponse error est traitée pareil : handshake-refused, connexion fermée." } ], "x-etm-direction": "engine→optimizer", "x-etm-timeout-s": 5 }, { "name": "GetPlan", "summary": "Demande d'un plan pour le contexte courant (§4-§6).", "description": "À chaque cycle (~1/min) et sur événement (trigger). Une requête en vol au plus : une plus récente remplace l'attente de la précédente (sa réponse est ignorée par id, ce n'est pas un échec). Délai : 10 s par requête. La réponse à la requête N sert au cycle N+1 (modèle cache). Le JSON Schema dit la FORME ; x-etm-semantic-rules dit ce que le validateur refuse en plus.", "paramStructure": "by-name", "params": [ { "name": "timestamp", "required": true, "schema": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "description": "Instant du cycle — le « maintenant » du validateur." }, { "name": "trigger", "required": true, "schema": { "$ref": "#/components/schemas/Trigger" } }, { "name": "site", "required": true, "schema": { "$ref": "#/components/schemas/Site" } }, { "name": "meter", "required": true, "schema": { "$ref": "#/components/schemas/Meter" } }, { "name": "pv", "required": true, "schema": { "$ref": "#/components/schemas/Pv" } }, { "name": "battery", "required": true, "schema": { "$ref": "#/components/schemas/Battery" } }, { "name": "loads", "required": true, "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Load" } }, "description": "Charges arbitrées, ⊆ GetLoadConfig." }, { "name": "forecast", "required": true, "schema": { "anyOf": [ { "$ref": "#/components/schemas/Forecast" }, { "type": "null" } ] }, "description": "null sans instantané de prévision." }, { "name": "tariff", "required": true, "schema": { "anyOf": [ { "$ref": "#/components/schemas/Tariff" }, { "type": "null" } ] } } ], "result": { "name": "plan", "schema": { "$ref": "#/components/schemas/GetPlanResult" } }, "errors": [ { "code": -32603, "message": "erreur interne", "data": "Toute réponse error compte comme un échec de la requête (rpc-error, §7) ; le code n'est pas lu." } ], "x-etm-direction": "engine→optimizer", "x-etm-timeout-s": 10 }, { "name": "UpdateLearned", "summary": "Estimations apprises de l'optimiseur (§8) — notification, sans id.", "description": "Envoyée par l'OPTIMISEUR (sens inverse des deux autres), sans id, sans réponse. État 1.1 du moteur : reçue, journalisée une fois, IGNORÉE — rien n'est persisté, le contenu n'est pas lu. Forme non normée en 1.1.", "paramStructure": "by-name", "params": [ { "name": "loads", "required": false, "schema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "description": "Non normé en 1.1 : intention §8 (learned + confidence par charge)." } ], "x-etm-direction": "optimizer→engine", "x-etm-notification": true } ], "components": { "schemas": { "ProtocolVersion": { "description": "Version du protocole parlée par le moteur (GEN : OptimizerProtocol::ProtocolVersion).", "const": "1.1" }, "Trigger": { "description": "Cause de la requête (§5.1). GEN : SocketScheduler::knownTriggers() — du moins au plus significatif.", "type": "string", "enum": [ "cycle", "forecast", "config", "load-change", "intent" ] }, "Domain": { "description": "Intention déclarée par l'installateur, jamais déduite du mécanisme, non lue par l'arbitre (§5.3). GEN : LoadConfig::knownDomains().", "type": "string", "enum": [ "ecs", "ev", "heating", "hvac", "battery" ] }, "ActionKind": { "description": "Type d'action (§6). GEN : OptimizerPlan::kinds(). `constraint` est RÉSERVÉ (annexe F) : reconnu par le validateur, toujours refusé (règle kind-mismatch) faute de mécanisme porteur.", "type": "string", "enum": [ "setpoint", "state", "constraint" ] }, "ReasonCode": { "description": "Codes de motif qu'un optimiseur peut émettre (§6, liste blanche). GEN : DecisionCode::optimizerEmittableCodes(). Clés de paramètres admises par code : x-etm-reason-params.", "type": "string", "enum": [ "SURPLUS_SETPOINT", "SURPLUS_INSUFFICIENT", "BELOW_MIN_POWER", "EV_DEADLINE", "SG_FORCED", "SG_RECOMMENDED", "SG_NORMAL", "ECO_FLOOR_GRID", "PRICE_SLOT", "OPTIMIZER_DECISION" ] }, "PriceInclude": { "description": "Composante incluse dans le prix (§5.2). GEN : TariffConfig::includes().", "type": "string", "enum": [ "energy", "grid", "taxes" ] }, "FallbackCode": { "description": "Côté app (NymeaEnergy.GetPlan.fallbackCode, INTERFACE.md) : pourquoi l'optimiseur n'a pas commandé le cycle. Hors fil. GEN : SocketScheduler::knownFallbackCodes().", "type": "string", "enum": [ "no-plan", "abstain", "empty-plan", "no-current-slot", "expired", "malformed-plan", "bad-time", "slots-unordered", "malformed-action", "unknown-load", "unknown-kind", "kind-mismatch", "kind-not-announced", "missing-reason", "bad-value", "duplicate-load", "incomplete-slot", "no-shadow-slot" ] }, "LastErrorCode": { "description": "Côté app (NymeaEnergy.GetOptimizerStatus.lastErrorCode, INTERFACE.md) : dernier code d'erreur vu (connexion, requête, plan). Hors fil. GEN : SocketScheduler::knownLastErrorCodes().", "type": "string", "enum": [ "invalid-endpoint", "token-too-short", "connect-failed", "closed-during-handshake", "connection-lost", "handshake-timeout", "handshake-refused", "invalid-handshake", "major-mismatch", "line-too-long", "invalid-json", "missing-id", "unknown-id", "timeout", "rpc-error", "invalid-response", "disconnected", "malformed-plan", "bad-time", "slots-unordered", "malformed-action", "unknown-load", "unknown-kind", "kind-mismatch", "kind-not-announced", "missing-reason", "bad-value", "duplicate-load", "incomplete-slot" ] }, "HandshakeToken": { "description": "Secret partagé (§3, E-T2-8) : 16 caractères au moins, 4 096 octets au plus (borne en octets non exprimable ici : contrôlée par le moteur), imprimable, sans blanc ni caractère de contrôle, UTF-8 admis. Envoyé seulement s'il est configuré ; jamais journalisé.", "type": "string", "minLength": 16, "pattern": "^[^\\s\\x00-\\x1f\\x7f]+$" }, "Capabilities": { "description": "Capacités annoncées (§3, 1.1, optionnel). Affichées en mode installateur, réduites aux clés et valeurs connues (capacitesPubliees) ; un champ inconnu est ignoré (§9).", "type": "object", "properties": { "maxHorizonH": { "description": "Horizon maximal planifié, en heures.", "type": "number", "minimum": 0 }, "resolutionS": { "description": "Pas des créneaux, en secondes.", "type": "number", "minimum": 0 }, "kinds": { "description": "PRÉSENT, il restreint les kinds acceptés (règle kind-not-announced).", "type": "array", "items": { "$ref": "#/components/schemas/ActionKind" } }, "usesForecast": { "type": "array", "items": { "type": "string", "enum": [ "pv", "baseload", "price", "temperature" ] } }, "reasonCodes": { "description": "L'optimiseur émet des reasonCode du catalogue.", "type": "boolean" } }, "required": [], "additionalProperties": true }, "HandshakeResult": { "type": "object", "properties": { "protocolVersion": { "description": "Version semver de l'optimiseur ; même MAJEURE que le moteur exigée (règle major-mismatch).", "type": "string", "minLength": 1, "pattern": "^[0-9]+(\\.[0-9]+)*$" }, "name": { "description": "Nom affiché (mode installateur), non contrôlé par le moteur (E-T7-3).", "type": "string" }, "planning": { "description": "Vrai si l'optimiseur produit des plans multi-créneaux. Informatif.", "type": "boolean" }, "capabilities": { "$ref": "#/components/schemas/Capabilities" } }, "required": [ "protocolVersion" ], "additionalProperties": true }, "Site": { "type": "object", "properties": { "contractedPowerW": { "description": "Puissance souscrite (W) ; null si inconnue (E1), jamais 0.", "type": [ "number", "null" ] }, "gridCapW": { "description": "RÉSERVÉ (signaux réseau type §14a EnWG) : toujours null en 1.1.", "type": "null" }, "phaseLimitA": { "description": "Limite par phase en A ; null si inconnue.", "type": [ "array", "null" ], "items": { "type": "number" } } }, "required": [ "contractedPowerW", "gridCapW", "phaseLimitA" ], "additionalProperties": false }, "Meter": { "type": "object", "properties": { "source": { "description": "ThingId du compteur racine désigné ; null si inconnu.", "type": [ "string", "null" ] }, "importW": { "description": "Puissance importée (W).", "type": "number", "minimum": 0 }, "exportW": { "description": "Puissance exportée (W), mesure BRUTE (AGENTS.md règle 8) — jamais le net après pilotage.", "type": "number", "minimum": 0 }, "perPhaseA": { "description": "Courant par phase (A).", "type": "array", "items": { "type": "number" } } }, "required": [ "source", "importW", "exportW", "perPhaseA" ], "additionalProperties": false }, "Pv": { "type": "object", "properties": { "currentW": { "description": "Production instantanée (W), positive ; null sans producteur (jamais 0).", "type": [ "number", "null" ], "minimum": 0 } }, "required": [ "currentW" ], "additionalProperties": false }, "BatteryAbsent": { "description": "Aucun stockage déclaré : rien d'autre n'est émis.", "type": "object", "properties": { "present": { "const": false } }, "required": [ "present" ], "additionalProperties": false }, "BatteryPresent": { "type": "object", "properties": { "present": { "const": true }, "socPercent": { "type": "number" }, "powerW": { "description": "Positif = charge, négatif = décharge.", "type": "number" }, "capacityWh": { "description": "null si inconnue.", "type": [ "number", "null" ] }, "maxChargeW": { "description": "null si inconnue.", "type": [ "number", "null" ] }, "maxDischargeW": { "description": "null si inconnue.", "type": [ "number", "null" ] }, "reserveSocPercent": { "description": "Réserve de secours (%) ; null si inconnue.", "type": [ "integer", "null" ] } }, "required": [ "present", "socPercent", "powerW", "capacityWh", "maxChargeW", "maxDischargeW", "reserveSocPercent" ], "additionalProperties": false }, "Battery": { "oneOf": [ { "$ref": "#/components/schemas/BatteryAbsent" }, { "$ref": "#/components/schemas/BatteryPresent" } ] }, "TariffEntry": { "type": "object", "properties": { "label": { "type": "string" }, "priceCtkWh": { "type": "number" } }, "required": [ "label", "priceCtkWh" ], "additionalProperties": false }, "TariffNextEntry": { "type": "object", "properties": { "label": { "type": "string" }, "priceCtkWh": { "type": "number" }, "from": { "type": [ "string", "null" ], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." } }, "required": [ "label", "priceCtkWh", "from" ], "additionalProperties": false }, "Tariff": { "description": "Tarif 1.0, conservé pour compatibilité ; un optimiseur 1.1 lit forecast.price. Aucun écrivain aujourd'hui (E3) : null en pratique.", "type": "object", "properties": { "provider": { "type": [ "string", "null" ] }, "current": { "$ref": "#/components/schemas/TariffEntry" }, "next": { "type": "array", "items": { "$ref": "#/components/schemas/TariffNextEntry" } } }, "required": [ "provider", "current", "next" ], "additionalProperties": false }, "Correction": { "type": "string", "enum": [ "temperature", "ac-limit" ] }, "PvField": { "type": "object", "properties": { "id": { "description": "ThingId du plan.", "type": "string" }, "corrections": { "type": "array", "items": { "$ref": "#/components/schemas/Correction" } } }, "required": [ "id", "corrections" ], "additionalProperties": false }, "PvForecast": { "type": "object", "properties": { "p50W": { "description": "Puissance AC moyenne sur [t, t+pas), sommée sur les plans puis plafonnée à acLimitW ; null = case inconnue. Un plafond, pas une espérance (§5.2).", "type": "array", "items": { "type": [ "number", "null" ] } }, "conversion": { "description": "Seule valeur émise par le moteur GPL ; un consommateur ignore une valeur inconnue.", "type": "string", "enum": [ "physics" ] }, "corrections": { "description": "[] = conversion brute.", "type": "array", "items": { "$ref": "#/components/schemas/Correction" } }, "weatherModel": { "description": "Modèle qui a RÉPONDU ; null si inconnu.", "type": [ "string", "null" ] }, "issuedAt": { "type": [ "string", "null" ], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "contentChangedAt": { "type": [ "string", "null" ], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "acLimitW": { "description": "Plafond AC du site (W), s'il est configuré.", "type": "integer" }, "perField": { "type": "array", "items": { "$ref": "#/components/schemas/PvField" } } }, "required": [ "p50W", "conversion", "corrections", "weatherModel", "issuedAt", "contentChangedAt" ], "additionalProperties": false }, "BaseloadLoadQuality": { "type": "object", "properties": { "loadId": { "type": "string" }, "source": { "type": "string", "enum": [ "meter", "device", "none" ] }, "minutesBySource": { "type": "object", "propertyNames": { "enum": [ "meter", "device", "none" ] }, "additionalProperties": { "type": "integer" } }, "constantMeasurement": { "type": "boolean" } }, "required": [ "loadId", "source", "minutesBySource" ], "additionalProperties": false }, "BaseloadQuality": { "type": "object", "properties": { "windowDays": { "type": "integer" }, "minDays": { "type": "integer" }, "resolutionS": { "type": "integer" }, "daysCovered": { "type": "object", "properties": { "workday": { "type": "integer" }, "weekend": { "type": "integer" } }, "required": [ "workday", "weekend" ], "additionalProperties": false }, "minutes": { "type": "integer" }, "commandedMinutes": { "type": "integer" }, "negativeMinutes": { "type": "integer" }, "incompleteMinutes": { "type": "integer" }, "computedAt": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "loads": { "type": "array", "items": { "$ref": "#/components/schemas/BaseloadLoadQuality" } } }, "required": [ "windowDays", "minDays", "resolutionS", "daysCovered", "minutes", "commandedMinutes", "negativeMinutes", "incompleteMinutes", "loads" ], "additionalProperties": false }, "Baseload": { "type": "object", "properties": { "model": { "description": "Le moteur GPL émet profile ou none (learned n'est pas émis).", "type": "string", "enum": [ "profile", "none" ] }, "p50W": { "type": "array", "items": { "type": [ "number", "null" ] } }, "quality": { "$ref": "#/components/schemas/BaseloadQuality" } }, "required": [ "model", "p50W" ], "additionalProperties": false }, "Price": { "type": "object", "properties": { "currency": { "const": "EUR" }, "importCtkWh": { "type": "array", "items": { "type": [ "number", "null" ] } }, "exportCtkWh": { "type": "array", "items": { "type": [ "number", "null" ] } }, "includes": { "type": "array", "items": { "$ref": "#/components/schemas/PriceInclude" } }, "source": { "description": "Tarif SAISI : une zone (fixed) ou des zones horaires (tou). tempo et day-ahead ne sont pas émis en 1.1.", "type": "string", "enum": [ "fixed", "tou" ] }, "knownUntil": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." } }, "required": [ "currency", "importCtkWh", "exportCtkWh", "includes", "source", "knownUntil" ], "additionalProperties": false }, "Forecast": { "description": "Prévisions sur UNE grille (§5.2) ; tableaux alignés index par index.", "type": "object", "properties": { "start": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "resolutionS": { "description": "Pas de la grille (s) — fait foi, jamais supposé.", "type": "integer", "minimum": 1 }, "steps": { "description": "Nombre de cases ; toutes les séries ont cette longueur.", "type": "integer", "minimum": 0 }, "pv": { "description": "null sans courbe (site non désigné, charge utile refusée, canal muet).", "anyOf": [ { "$ref": "#/components/schemas/PvForecast" }, { "type": "null" } ] }, "baseload": { "$ref": "#/components/schemas/Baseload" }, "weather": { "description": "Aucune source de température en 1.1 : toujours null.", "type": "null" }, "price": { "description": "null sans tarif saisi.", "anyOf": [ { "$ref": "#/components/schemas/Price" }, { "type": "null" } ] } }, "required": [ "start", "resolutionS", "steps", "pv", "baseload", "weather", "price" ], "additionalProperties": false }, "Measurement": { "oneOf": [ { "description": "Rien ne mesure : état positif, pas une erreur.", "type": "object", "properties": { "source": { "const": "none" } }, "required": [ "source" ], "additionalProperties": false }, { "type": "object", "properties": { "source": { "type": "string", "enum": [ "meter", "device" ] }, "powerW": { "type": "number" } }, "required": [ "source", "powerW" ], "additionalProperties": false } ], "description": "Mesure par charge (§5.3, LM-1105/1106) : décider sur source, jamais sur adapter." }, "EnergyTarget": { "type": "object", "properties": { "by": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "minWh": { "description": "Énergie minimale à avoir délivrée à l'échéance.", "type": "number", "exclusiveMinimum": 0 }, "source": { "description": "learned n'est pas émis en 1.1.", "type": "string", "enum": [ "installer", "user-intent" ] } }, "required": [ "by", "minWh", "source" ], "additionalProperties": false }, "LoadNeeds": { "description": "Besoins (§5.3). Champs 1.0 émis tels quels quand ils sont déclarés ; un optimiseur 1.1 préfère energyTargets.", "type": "object", "properties": { "dailyDeadline": { "description": "Heure murale locale HH:MM (1.0).", "type": "string", "pattern": "^[0-9]{2}:[0-9]{2}$" }, "minEnergyWhPerDay": { "type": "integer", "exclusiveMinimum": 0 }, "targetSocPercent": { "type": "integer", "exclusiveMinimum": 0 }, "deadline": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "energyTargets": { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/EnergyTarget" } } }, "required": [], "additionalProperties": false }, "LoadLearned": { "description": "Absent tant qu'aucun profil n'a été reçu (E12).", "type": "object", "properties": { "dailyEnergyWh": { "type": "number" }, "confidence": { "type": "number", "minimum": 0, "maximum": 1 } }, "required": [ "dailyEnergyWh", "confidence" ], "additionalProperties": false }, "LoadWatts": { "description": "Charge pilotée en watts : combinaison de relais (relay-router) ou consigne continue (etmvariableload).", "type": "object", "properties": { "id": { "description": "Identifiant de la charge — clé des actions.", "type": "string", "minLength": 1 }, "label": { "description": "Nom lisible.", "type": "string" }, "priority": { "description": "Rang effectif (1 = premier servi).", "type": "integer" }, "domain": { "$ref": "#/components/schemas/Domain" }, "learned": { "$ref": "#/components/schemas/LoadLearned" }, "needs": { "$ref": "#/components/schemas/LoadNeeds" }, "adapter": { "type": "string", "enum": [ "relay-router", "etmvariableload" ] }, "declared": { "type": "object", "properties": { "minPowerW": { "description": "0 = aucun plancher.", "type": "number" }, "maxPowerW": { "type": "number" } }, "required": [ "minPowerW", "maxPowerW" ], "additionalProperties": false }, "telemetry": { "type": "object", "properties": { "currentPowerW": { "description": "Puissance courante (W) telle que l'adaptateur la publie.", "type": "number" }, "available": { "description": "Gouverne l'ALLOCATION, pas la COMPTABILITÉ (§5.3).", "type": "boolean" }, "measurement": { "$ref": "#/components/schemas/Measurement" }, "lockMinPowerW": { "description": "Plancher imposé MAINTENANT par un verrou.", "type": "number" }, "lockMaxPowerW": { "description": "Plafond imposé maintenant ; négatif = aucun plafond.", "type": "number" } }, "required": [ "currentPowerW", "available", "lockMinPowerW", "lockMaxPowerW" ], "additionalProperties": false }, "limits": { "type": "object", "properties": { "minOnS": { "type": "integer" }, "minOffS": { "type": "integer" } }, "required": [ "minOnS", "minOffS" ], "additionalProperties": false } }, "required": [ "id", "adapter", "label", "priority", "declared", "telemetry", "limits" ], "additionalProperties": false }, "LoadEvCharger": { "description": "Borne. Une borne CONFIGURÉE dont l'appareil manque n'a ni declared, ni limits, ni pluggedIn/charging : ses zéros diraient « inconnu ».", "type": "object", "properties": { "id": { "description": "Identifiant de la charge — clé des actions.", "type": "string", "minLength": 1 }, "label": { "description": "Nom lisible.", "type": "string" }, "priority": { "description": "Rang effectif (1 = premier servi).", "type": "integer" }, "domain": { "$ref": "#/components/schemas/Domain" }, "learned": { "$ref": "#/components/schemas/LoadLearned" }, "needs": { "$ref": "#/components/schemas/LoadNeeds" }, "adapter": { "const": "evcharger" }, "declared": { "type": "object", "properties": { "minA": { "type": "number" }, "maxA": { "type": "number" }, "phases": { "description": "Phases VISÉES ce cycle, pas celles de la plaque (E26).", "type": "integer" } }, "required": [ "minA", "maxA", "phases" ], "additionalProperties": false }, "telemetry": { "type": "object", "properties": { "currentPowerW": { "description": "Puissance courante (W) telle que l'adaptateur la publie.", "type": "number" }, "available": { "description": "Gouverne l'ALLOCATION, pas la COMPTABILITÉ (§5.3).", "type": "boolean" }, "measurement": { "$ref": "#/components/schemas/Measurement" }, "pluggedIn": { "type": "boolean" }, "charging": { "type": "boolean" }, "sessionWh": { "description": "Absent si la borne ne compte pas sa session (LM-1009 C1).", "type": "number" } }, "required": [ "currentPowerW", "available" ], "additionalProperties": false }, "limits": { "type": "object", "properties": { "chargingEnabledLockS": { "type": "integer" }, "currentChangeLockS": { "type": "integer" } }, "required": [ "chargingEnabledLockS", "currentChangeLockS" ], "additionalProperties": false } }, "required": [ "id", "adapter", "label", "priority", "telemetry" ], "additionalProperties": false }, "LoadSgReady": { "description": "PAC SG-Ready : 4 états normés, kind state.", "type": "object", "properties": { "id": { "description": "Identifiant de la charge — clé des actions.", "type": "string", "minLength": 1 }, "label": { "description": "Nom lisible.", "type": "string" }, "priority": { "description": "Rang effectif (1 = premier servi).", "type": "integer" }, "domain": { "$ref": "#/components/schemas/Domain" }, "learned": { "$ref": "#/components/schemas/LoadLearned" }, "needs": { "$ref": "#/components/schemas/LoadNeeds" }, "adapter": { "const": "sg-ready" }, "declared": { "type": "object", "properties": { "states": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 4 } }, "estimatedPowerW": { "type": "object", "propertyNames": { "pattern": "^[1-4]$" }, "additionalProperties": { "type": "number" } } }, "required": [ "states", "estimatedPowerW" ], "additionalProperties": false }, "telemetry": { "type": "object", "properties": { "currentPowerW": { "description": "Puissance courante (W) telle que l'adaptateur la publie.", "type": "number" }, "available": { "description": "Gouverne l'ALLOCATION, pas la COMPTABILITÉ (§5.3).", "type": "boolean" }, "measurement": { "$ref": "#/components/schemas/Measurement" }, "state": { "description": "État commandé (1-4) ; 0 = non applicable.", "type": "integer", "minimum": 0, "maximum": 4 }, "minState": { "description": "Fenêtre d'états autorisée MAINTENANT par minStateHold.", "type": "integer" }, "maxState": { "type": "integer" } }, "required": [ "currentPowerW", "available", "state", "minState", "maxState" ], "additionalProperties": false }, "limits": { "type": "object", "properties": { "minStateHoldS": { "type": "integer" } }, "required": [ "minStateHoldS" ], "additionalProperties": false } }, "required": [ "id", "adapter", "label", "priority", "declared", "telemetry", "limits" ], "additionalProperties": false }, "Load": { "oneOf": [ { "$ref": "#/components/schemas/LoadWatts" }, { "$ref": "#/components/schemas/LoadEvCharger" }, { "$ref": "#/components/schemas/LoadSgReady" } ], "description": "Une charge arbitrée (§5.3) — la forme dépend du mécanisme (adapter)." }, "ReasonParams": { "description": "Paramètres du motif : valeurs nombre, booléen ou code [A-Za-z]+ ; clés admises par code : x-etm-reason-params. Toute autre clé est SUPPRIMÉE avant l'app (filtrée, pas refusée).", "type": [ "object", "null" ], "additionalProperties": { "type": [ "number", "boolean", "string" ], "pattern": "^[A-Za-z]+$" } }, "SetpointAction": { "type": "object", "properties": { "loadId": { "description": "Charge visée : connue du contexte (règle unknown-load), une fois par créneau (duplicate-load).", "type": "string", "minLength": 1 }, "reason": { "description": "Texte français, OBLIGATOIRE et non vide (règle missing-reason). Traces et journal seulement, jamais vers l'app.", "type": "string", "minLength": 1, "pattern": "\\S" }, "reasonCode": { "$ref": "#/components/schemas/ReasonCode" }, "reasonParams": { "$ref": "#/components/schemas/ReasonParams" }, "force": { "description": "INTERDIT sur le fil (§6) : ignoré, signalé une fois." }, "kind": { "const": "setpoint" }, "powerW": { "description": "Consigne en watts (relay-router, etmvariableload ; ou borne).", "type": "number", "minimum": 0 }, "currentA": { "description": "Borne : courant (0 = suspendre).", "type": "number", "minimum": 0 }, "phases": { "description": "Borne : phases visées ; absent = inchangé.", "enum": [ 1, 3 ] } }, "required": [ "loadId", "kind", "reason" ], "additionalProperties": true, "anyOf": [ { "required": [ "powerW" ] }, { "required": [ "currentA" ] } ] }, "StateAction": { "type": "object", "properties": { "loadId": { "description": "Charge visée : connue du contexte (règle unknown-load), une fois par créneau (duplicate-load).", "type": "string", "minLength": 1 }, "reason": { "description": "Texte français, OBLIGATOIRE et non vide (règle missing-reason). Traces et journal seulement, jamais vers l'app.", "type": "string", "minLength": 1, "pattern": "\\S" }, "reasonCode": { "$ref": "#/components/schemas/ReasonCode" }, "reasonParams": { "$ref": "#/components/schemas/ReasonParams" }, "force": { "description": "INTERDIT sur le fil (§6) : ignoré, signalé une fois." }, "kind": { "const": "state" }, "state": { "description": "1 = verrouillage, 2 = normal, 3 = recommandation, 4 = forcé.", "type": "integer", "minimum": 1, "maximum": 4 } }, "required": [ "loadId", "kind", "reason", "state" ], "additionalProperties": true }, "ConstraintAction": { "description": "RÉSERVÉ (annexe F) : forme décrite, toujours refusée (kind-mismatch).", "type": "object", "properties": { "loadId": { "description": "Charge visée : connue du contexte (règle unknown-load), une fois par créneau (duplicate-load).", "type": "string", "minLength": 1 }, "reason": { "description": "Texte français, OBLIGATOIRE et non vide (règle missing-reason). Traces et journal seulement, jamais vers l'app.", "type": "string", "minLength": 1, "pattern": "\\S" }, "reasonCode": { "$ref": "#/components/schemas/ReasonCode" }, "reasonParams": { "$ref": "#/components/schemas/ReasonParams" }, "force": { "description": "INTERDIT sur le fil (§6) : ignoré, signalé une fois." }, "kind": { "const": "constraint" }, "charge": { "enum": [ "allow", "forbid" ] }, "discharge": { "enum": [ "allow", "forbid" ] } }, "required": [ "loadId", "kind", "reason" ], "additionalProperties": true }, "Action": { "oneOf": [ { "$ref": "#/components/schemas/SetpointAction" }, { "$ref": "#/components/schemas/StateAction" }, { "$ref": "#/components/schemas/ConstraintAction" } ] }, "Expected": { "description": "Trajectoire prévue : affichage et mesure d'écart, JAMAIS pour commander.", "type": "object", "properties": { "gridImportW": { "type": "number" }, "gridExportW": { "type": "number" }, "batterySocPercent": { "description": "RÉSERVÉ (annexe F) : ignoré." } }, "required": [], "additionalProperties": true }, "Slot": { "type": "object", "properties": { "from": { "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.", "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$" }, "to": { "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.", "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$" }, "actions": { "description": "Absent ou null = aucune action.", "type": [ "array", "null" ], "items": { "$ref": "#/components/schemas/Action" } }, "expected": { "$ref": "#/components/schemas/Expected" } }, "required": [ "from", "to" ], "additionalProperties": true }, "Diagnostics": { "description": "Informatif : mode installateur et traces.", "type": "object", "properties": { "solver": { "type": "string" }, "status": { "type": "string", "enum": [ "optimal", "feasible", "best-so-far", "infeasible-relaxed" ] }, "gapPercent": { "type": "number" }, "computeMs": { "type": "number", "minimum": 0 }, "inputsUsed": { "type": "object", "properties": { "pv": { "type": "string", "enum": [ "physics", "hybrid", "none" ] } }, "required": [], "additionalProperties": true } }, "required": [], "additionalProperties": true }, "Plan": { "description": "Plan par créneaux (§6).", "type": "object", "properties": { "abstain": { "const": false }, "planId": { "type": "string" }, "strategy": { "description": "Chaîne libre, informative.", "type": "string" }, "computedAt": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$", "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée." }, "validUntil": { "description": "Instant ISO 8601 AVEC son décalage (Z ou ±hh:mm) — une heure murale nue est refusée.", "type": [ "string", "null" ], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2}(\\.[0-9]+)?)?(Z|[+-][0-9]{2}:[0-9]{2})$" }, "slots": { "description": "[] = abstention (empty-plan). Seul le créneau couvrant maintenant est exécuté.", "type": "array", "items": { "$ref": "#/components/schemas/Slot" } }, "diagnostics": { "$ref": "#/components/schemas/Diagnostics" } }, "required": [ "slots" ], "additionalProperties": true }, "Abstention": { "description": "« Pas de plan maintenant » (§7) : repli pour ce cycle, pas un échec.", "type": "object", "properties": { "abstain": { "const": true }, "reason": { "type": "string" } }, "required": [ "abstain" ], "additionalProperties": true }, "GetPlanResult": { "oneOf": [ { "$ref": "#/components/schemas/Abstention" }, { "$ref": "#/components/schemas/Plan" } ] } } }, "x-etm-transport": { "description": "Ce qu'OpenRPC ne dit pas du fil (§2-§4).", "schemes": [ "unix", "tcp" ], "defaultEndpoint": "unix:///run/powersync/optimizer.sock", "jsonrpc": "2.0", "framing": "ndjson", "encoding": "utf-8", "roles": { "client": "engine", "server": "optimizer" }, "maxLineBytes": 1048576, "handshakeTimeoutS": 5, "requestTimeoutS": 10, "backoffS": { "initial": 1, "max": 60, "factor": 2 }, "requestsInFlight": 1, "ids": "entiers croissants sur toute la vie du client, à travers les reconnexions", "lineErrors": "ligne non JSON, sans id, id jamais émis, ligne > maxLineBytes : comptée, connexion CONSERVÉE", "tokenMasking": "toute occurrence du token dans un message reçu est remplacée par « [token masqué] » dès l'analyse" }, "x-etm-fallback-machine": { "description": "Machine de repli §7 (OptimizerStatus) : heartbeat implicite — une réponse valide à GetPlan = optimiseur vivant. Les échecs se comptent PAR REQUÊTE.", "failuresBeforeDead": 2, "rows": [ { "situation": "réponse plan valide", "optimizerAlive": "true", "activeStrategy": "optimizer" }, { "situation": "abstain, plan vide, aucun créneau courant", "optimizerAlive": "inchangé", "activeStrategy": "rules (ce cycle)" }, { "situation": "plan expiré (validUntil dépassé)", "optimizerAlive": "inchangé", "activeStrategy": "rules jusqu'au prochain plan valide" }, { "situation": "timeout / erreur / plan invalide sur deux requêtes consécutives", "optimizerAlive": "false", "activeStrategy": "rules" }, { "situation": "socket absent / fermé / handshake refusé", "optimizerAlive": "false", "activeStrategy": "rules" }, { "situation": "aucun endpoint configuré", "optimizerAlive": "false", "activeStrategy": "rules" }, { "situation": "mode dégradé L2 (compteur muet > 90 s)", "optimizerAlive": "inchangé", "activeStrategy": "none" } ] }, "x-etm-semantic-rules": { "description": "Ce que le moteur refuse EN PLUS de la forme, dans l'ordre de ses contrôles (OptimizerPlan::valider, OptimizerClient::handleHandshakeReply). name = code du verdict. issue : abstention (repli ce cycle, pas un échec), expired, invalid (plan refusé en entier, compté comme échec), connection-refused (handshake). needs : ce qu'il faut connaître en plus de la réponse (la requête, le handshake).", "rules": [ { "name": "abstain", "method": "GetPlan", "issue": "abstention", "fallbackCode": "abstain", "countsAsFailure": false, "needs": [], "schemaAlsoCatches": false, "description": "result.abstain vaut true." }, { "name": "malformed-plan", "method": "GetPlan", "issue": "invalid", "fallbackCode": "malformed-plan", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": true, "description": "slots absent ou non liste ; un créneau qui n'est pas un objet ; actions non liste.", "lastErrorCode": "malformed-plan" }, { "name": "empty-plan", "method": "GetPlan", "issue": "abstention", "fallbackCode": "empty-plan", "countsAsFailure": false, "needs": [], "schemaAlsoCatches": false, "description": "slots est une liste vide." }, { "name": "bad-time", "method": "GetPlan", "issue": "invalid", "fallbackCode": "bad-time", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": true, "description": "from/to illisibles ou sans décalage, ou from ≥ to ; validUntil illisible ou sans décalage.", "lastErrorCode": "bad-time" }, { "name": "slots-unordered", "method": "GetPlan", "issue": "invalid", "fallbackCode": "slots-unordered", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": false, "description": "un créneau commence avant la fin du précédent.", "lastErrorCode": "slots-unordered" }, { "name": "malformed-action", "method": "GetPlan", "issue": "invalid", "fallbackCode": "malformed-action", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": true, "description": "action qui n'est pas un objet, ou sans loadId (chaîne non vide).", "lastErrorCode": "malformed-action" }, { "name": "unknown-load", "method": "GetPlan", "issue": "invalid", "fallbackCode": "unknown-load", "countsAsFailure": true, "needs": [ "request" ], "schemaAlsoCatches": false, "description": "loadId absent des loads[] de la requête.", "lastErrorCode": "unknown-load" }, { "name": "unknown-kind", "method": "GetPlan", "issue": "invalid", "fallbackCode": "unknown-kind", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": true, "description": "kind hors ActionKind.", "lastErrorCode": "unknown-kind" }, { "name": "kind-mismatch", "method": "GetPlan", "issue": "invalid", "fallbackCode": "kind-mismatch", "countsAsFailure": true, "needs": [ "request" ], "schemaAlsoCatches": false, "description": "kind différent de celui du mécanisme de la charge (setpoint : evcharger, relay-router, etmvariableload ; state : sg-ready ; constraint : aucun).", "lastErrorCode": "kind-mismatch" }, { "name": "kind-not-announced", "method": "GetPlan", "issue": "invalid", "fallbackCode": "kind-not-announced", "countsAsFailure": true, "needs": [ "handshake" ], "schemaAlsoCatches": false, "description": "capabilities.kinds annoncé au handshake et kind absent de la liste.", "lastErrorCode": "kind-not-announced" }, { "name": "missing-reason", "method": "GetPlan", "issue": "invalid", "fallbackCode": "missing-reason", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": true, "description": "reason absent, non textuel, ou vide une fois les caractères de contrôle retirés.", "lastErrorCode": "missing-reason" }, { "name": "bad-value", "method": "GetPlan", "issue": "invalid", "fallbackCode": "bad-value", "countsAsFailure": true, "needs": [ "request" ], "schemaAlsoCatches": true, "description": "state absent ou hors 1-4 ; borne : currentA/powerW absents ou négatifs, phases hors {1, 3} ; charge en watts : powerW absent ou négatif.", "lastErrorCode": "bad-value" }, { "name": "duplicate-load", "method": "GetPlan", "issue": "invalid", "fallbackCode": "duplicate-load", "countsAsFailure": true, "needs": [], "schemaAlsoCatches": false, "description": "deux actions pour la même charge dans un créneau.", "lastErrorCode": "duplicate-load" }, { "name": "expired", "method": "GetPlan", "issue": "expired", "fallbackCode": "expired", "countsAsFailure": false, "needs": [ "request" ], "schemaAlsoCatches": false, "description": "validUntil ≤ timestamp de la requête." }, { "name": "no-current-slot", "method": "GetPlan", "issue": "abstention", "fallbackCode": "no-current-slot", "countsAsFailure": false, "needs": [ "request" ], "schemaAlsoCatches": false, "description": "aucun créneau ne couvre le timestamp de la requête." }, { "name": "incomplete-slot", "method": "GetPlan", "issue": "invalid", "fallbackCode": "incomplete-slot", "countsAsFailure": true, "needs": [ "request" ], "schemaAlsoCatches": false, "description": "une charge available de la requête n'a pas d'action dans le créneau courant (décision Q1).", "lastErrorCode": "incomplete-slot" }, { "name": "handshake-refused", "method": "Handshake", "issue": "connection-refused", "fallbackCode": "no-plan", "lastErrorCode": "handshake-refused", "countsAsFailure": false, "needs": [], "schemaAlsoCatches": false, "description": "réponse error (token absent ou différent : -32001 recommandé) → connexion fermée, nouvelle tentative au backoff." }, { "name": "invalid-handshake", "method": "Handshake", "issue": "connection-refused", "fallbackCode": "no-plan", "lastErrorCode": "invalid-handshake", "countsAsFailure": false, "needs": [], "schemaAlsoCatches": true, "description": "result non objet, protocolVersion absent ou vide, capabilities présent et non objet." }, { "name": "major-mismatch", "method": "Handshake", "issue": "connection-refused", "fallbackCode": "no-plan", "lastErrorCode": "major-mismatch", "countsAsFailure": false, "needs": [], "schemaAlsoCatches": false, "description": "MAJEURE de protocolVersion (ce qui précède le premier point) différente de celle du moteur." } ] }, "x-etm-tolerances": { "description": "Ce que le moteur ACCEPTE au-delà du schéma — un optimiseur conforme ne s'en sert pas.", "items": [ "reasonCode absent ou hors ReasonCode : action acceptée, motif transmis sous OPTIMIZER_DECISION sans paramètres.", "reasonParams : clé non admise pour le code (x-etm-reason-params) ou valeur refusée : clé supprimée, action acceptée.", "planId, strategy : lus comme chaînes quel que soit leur type ; computedAt illisible : ignoré.", "diagnostics, expected : non objets ignorés ; valeurs inconnues filtrées avant l'app.", "abstain : toute valeur « vraie » au sens de QVariant::toBool (true, chaîne non vide autre que « false »/« 0 », nombre non nul).", "champs inconnus (params et result) : ignorés (§9) ; force et champs réservés (annexe F) : ignorés et signalés une fois.", "state : 3.0 accepté comme 3 (entier au sens JSON Schema)." ] }, "x-etm-reason-params": { "SURPLUS_SETPOINT": [ "budgetW", "setpointW", "stepped" ], "SURPLUS_INSUFFICIENT": [ "budgetW" ], "BELOW_MIN_POWER": [ "budgetW", "minPowerW", "state" ], "EV_DEADLINE": [], "SG_FORCED": [ "budgetW", "estimatedW" ], "SG_RECOMMENDED": [ "budgetW", "estimatedW" ], "SG_NORMAL": [ "budgetW" ], "ECO_FLOOR_GRID": [ "gridW", "atMaximum", "remainingWh", "targetWh", "remainingS", "regime", "minAlwaysW", "boundBy" ], "PRICE_SLOT": [ "priceCtkWh", "setpointW" ], "OPTIMIZER_DECISION": [] }, "x-etm-generated": { "description": "Listes fermées RÉGÉNÉRÉES depuis le code (tools/etm-powersync-gen-protocol-doc --enums-from) ; la suite etm-powersync-protocolschema échoue si le code et le schéma divergent.", "items": [ { "pointer": "/info/version", "from": "OptimizerProtocol::ProtocolVersion" }, { "pointer": "/components/schemas/ProtocolVersion/const", "from": "OptimizerProtocol::ProtocolVersion" }, { "pointer": "/components/schemas/Trigger/enum", "from": "SocketScheduler::knownTriggers()" }, { "pointer": "/components/schemas/Domain/enum", "from": "LoadConfig::knownDomains()" }, { "pointer": "/components/schemas/ActionKind/enum", "from": "OptimizerPlan::kinds()" }, { "pointer": "/components/schemas/ReasonCode/enum", "from": "DecisionCode::optimizerEmittableCodes()" }, { "pointer": "/x-etm-reason-params", "from": "DecisionCode::optimizerParamKeys()" }, { "pointer": "/components/schemas/PriceInclude/enum", "from": "TariffConfig::includes()" }, { "pointer": "/components/schemas/FallbackCode/enum", "from": "SocketScheduler::knownFallbackCodes()" }, { "pointer": "/components/schemas/LastErrorCode/enum", "from": "SocketScheduler::knownLastErrorCodes()" } ] } }