Navigation : « EMS — moteur à règles » (pages de fonctionnalités, précédées de « Comment le moteur décide »), « Optimiseur » (Présentation, Protocole, Contrat, Écrire un optimiseur), « API » (ex-« Référence »). Aucun fichier existant déplacé : les URL publiées restent valides. Protocole : prose courte qui renvoie à OPTIMIZER_PROTOCOL.md du moteur pour le sens ; tableaux, listes fermées et règles nommées générés depuis le contrat. Contrat servi tel quel (copie octet pour octet). reference.md, resté un bouchon, devient l'accueil de la section API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2179 lines
66 KiB
JSON
2179 lines
66 KiB
JSON
{
|
|
"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": {
|
|
"BELOW_MIN_POWER": [
|
|
"budgetW",
|
|
"minPowerW",
|
|
"state"
|
|
],
|
|
"ECO_FLOOR_GRID": [
|
|
"gridW",
|
|
"atMaximum",
|
|
"remainingWh",
|
|
"targetWh",
|
|
"remainingS",
|
|
"regime",
|
|
"minAlwaysW",
|
|
"boundBy"
|
|
],
|
|
"EV_DEADLINE": [],
|
|
"OPTIMIZER_DECISION": [],
|
|
"PRICE_SLOT": [
|
|
"priceCtkWh",
|
|
"setpointW"
|
|
],
|
|
"SG_FORCED": [
|
|
"budgetW",
|
|
"estimatedW"
|
|
],
|
|
"SG_NORMAL": [
|
|
"budgetW"
|
|
],
|
|
"SG_RECOMMENDED": [
|
|
"budgetW",
|
|
"estimatedW"
|
|
],
|
|
"SURPLUS_INSUFFICIENT": [
|
|
"budgetW"
|
|
],
|
|
"SURPLUS_SETPOINT": [
|
|
"budgetW",
|
|
"setpointW",
|
|
"stepped"
|
|
]
|
|
},
|
|
"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()"
|
|
}
|
|
]
|
|
}
|
|
}
|