LM-201 est la règle qui manquait sous LM-200. Une charge existe quand il y a un canal de commande DISTINCT pour un service. Elle repose la question du domaine, que 0.1.3 posait mal : une PAC air/eau fait chauffage, rafraîchissement et ECS — le domaine ne décrit donc pas la MACHINE, il décrit CE QU'ON ARBITRE. Conséquences : PAC + ECS sans ballon séparé = UNE charge (heating), pas de charge ecs — le SG-Ready commande la machine entière et la répartition appartient à sa régulation ; ballon séparé = charge distincte, il a son propre canal ; PAC qui fait du froid = toujours une seule charge, ce qui change est la saison, pas la configuration. La règle explique l'exclusivité de validateSet() (LM-302-b / ECS-110-b) : deux charges sur un canal unique se contrediraient, et le budget compterait la même puissance deux fois. Le refus existait avant la règle ; la règle dit enfin pourquoi il est juste. LM-202 : thermostats et zones ne sont pas des charges. Un thermostat ouvre une vanne, il ne porte pas la consommation. Les déclarer produirait des entrées à 0 W dans le waterfall pendant que la PAC est comptée ailleurs. Ils décrivent où va la chaleur, pas combien on en achète — leur écran, leur namespace, jamais LoadConfig. Enforcement LM-200 tranché : avertissement au journal, PAS de refus dans isValid(). Mais l'avertissement n'a pas encore de prédicat — voir le rapport de session. Keyé sur domain seul il crierait sur des installations correctes (heating + relay-router = chauffage résistif ; hvac + relay-router = VMC). Son discriminant est typeAppareil (§8-1), valeur thermodynamique. Spécifié ici, implémenté avec lui. §8-5 : heating vs hvac reste OUVERT et ne se saura qu'en dessinant la PAC communicante, où les consignes ECS et chauffage sont enfin séparées. D'ici là o:domain garde son rôle actuel, sans force normative — ne construire aucune règle dessus. INTERFACE_etmvariableload.md (canonique) : §4-bis et §4-ter ajoutés, côté app. C'est l'app qui guide au moment du choix, et c'est le seul endroit fiable — elle sait quel appareil l'installateur déclare, le moteur ne le sait pas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
26 KiB
SPEC — Modèle de charges (domaine × mécanisme)
Version : 0.1.4
Dépôt : etm-powersync-energy-plugin-etm
Statut : intention de conception figée. §3 (schéma persisté) est en cours
d'implémentation depuis le 2026-08-09 : relay-router, etmvariableload et
sg-ready sont trois charges utiles discriminées par adapter. Le reste du
document reste une intention.
0.1.4 — LM-201 et LM-202 ajoutés (§2). LM-201 est la règle qui manquait : une charge existe quand il y a un canal de commande distinct pour un service. Un canal, une charge. Elle repose la question du domaine, que 0.1.3 posait mal — le domaine ne décrit pas la machine, il décrit ce qu'on arbitre. Elle explique du même coup la contrainte d'exclusivité de
validateSet(), qui cesse d'être une limite technique subie. LM-202 : les thermostats et les zones ne sont pas des charges.
0.1.3 — LM-200 ajouté (§2) : toutes les cases de la matrice ne se valent pas. Certaines sont non prévues, d'autres sont interdites par la physique — le cas de la PAC, qui ne se pilote qu'en
SgReadyou en communicant. Précise LM-101, qui était trop permissif lu seul. Aucune décision de conception renversée ; une contrainte qui existait dans le matériel et pas dans le document.
0.1.2 — LM-104 ajouté (§1) : « supposer conservateur pour annoncer, tenter systématiquement pour agir ». Principe dégagé des tests d'ECS-411 et ECS-414, inscrit ici parce qu'il vaut pour tout mécanisme et non pour l'ECS seul. §3 : le mécanisme
sg-readya sa charge utile et cesse d'être une intention.
0.1.1 — passe de vérification contre le code. Quatre précisions : préséance des verrous (LM-303), portée de l'union discriminée (LM-302), citation de règle (LM-102),
EvAdapterinscrit au renommage (LM-601). Aucune décision de conception modifiée.
Ce document ne déclenche aucun travail. L'ordre de
specs/spec_ecs.md§2 reste seul en vigueur, et l'étape 1 reste bloquée par ses arbitrages §13-1 et §13-2. Une seule chose ici est urgente : la forme du schéma persisté (§3), parce que c'est la seule partie coûteuse à rattraper une fois que le banc et la beta auront écrit des données. Le reste s'implémente au fil des besoins.
§1 — Deux couches
LM-100 — Le modèle de charges se découpe en deux couches indépendantes :
- Couche domaine —
Ecs,Hvac,Ev,SmartHome. Détient le modèle de la charge (état thermique, échéance de départ, cycle), en déduit la demande etavailable. Ne parle à aucun matériel. - Couche mécanisme —
Relay,Variable,SgReady,ModbusSetpoint. Traduit une enveloppe en watts vers le matériel et tient les verrous. Écrite une seule fois, partagée par tous les domaines.
LM-101 — Une charge configurée choisit un domaine et un mécanisme. Le domaine ne détermine pas le mécanisme et réciproquement — mais il en exclut certains, cf. LM-200. L'indépendance des deux couches porte sur le couplage de conception, pas sur la liberté de combiner : elle dit qu'aucune des deux n'a besoin de connaître l'autre pour être écrite, jamais que toute paire est réalisable.
LM-102 — Aucune des deux couches ne répartit de budget. La couche domaine se
retire de l'allocation via available ; elle ne la négocie pas. Règles absolues
1 et 2 d'AGENTS.md inchangées.
LM-104 — Supposer conservateur pour annoncer, tenter systématiquement pour agir.
Un adaptateur qui ne peut pas LIRE l'état d'un organe DOIT le supposer dans l'état
le plus consommateur (contact fermé, palier engagé) quand il annonce — dans
telemetry(), toLoadContext() ou la déduction de son état courant au démarrage.
Le même adaptateur DOIT néanmoins émettre l'écriture vers cet organe quand il
agit, sans jamais la court-circuiter au motif que l'état supposé coïncide déjà avec
la cible.
Les deux moitiés sont indissociables, et c'est l'omission de la seconde qui est piégeuse : supposer « fermé » puis en déduire « donc rien à écrire » transforme une hypothèse de prudence en masquage de panne — l'organe reste dans son état réel, inconnu, et aucun verdict d'échec ne se déclenche. La prudence porte sur ce qu'on DIT de l'installation, jamais sur ce qu'on lui ENVOIE.
Portée : tout mécanisme, présent et futur. Relay (déduction de palier au
démarrage), SgReady (relevé du motif de contacts réellement fermés), et par
construction ModbusSetpoint dès qu'un registre devient illisible. Origine :
deux défauts symétriques trouvés par les tests d'ECS-411 puis d'ECS-414 —
d'abord l'hypothèse inverse (« injoignable donc ouvert »), puis son corollaire
(« supposé fermé donc jamais écrit »).
Justification. Un nommage par domaine seul (EcsAdapter, HvacAdapter)
obligerait à écrire l'encodage SG-Ready deux fois (ECS et HVAC), la combinatoire
de relais deux fois (ECS et EV en prise commandée), et le transport Modbus deux
fois (HVAC et EV). Un nommage par mécanisme seul perdrait les comportements
propres au domaine, qui sont réels : stockage différable pour l'ECS, confort
immédiat pour le HVAC, échéance de départ pour l'EV, cycle non modulable pour le
SmartHome.
§2 — Matrice
| Domaine | Mécanismes attendus |
|---|---|
Ecs |
Relay (résistance à paliers) · SgReady (ballon thermodynamique) · Variable (triac) |
Hvac |
SgReady (PAC) · ModbusSetpoint (PAC, clim, VMC) |
Ev |
Relay (prise commandée : GreenUp, Witty) · ModbusSetpoint (borne) |
SmartHome |
Relay — voir §7, sémantique différente |
LM-200 — Une case vide se lit de deux façons, et il faut les distinguer. La plupart sont non prévues : rien ne les interdit, elles n'ont simplement pas d'usage identifié. D'autres sont impossibles, parce que le mécanisme ne sait pas commander cet organe-là. Les confondre revient à traiter une contrainte physique comme une lacune de feuille de route.
Le cas dur, et le seul identifié à ce jour : la PAC. Une pompe à chaleur se pilote de deux façons, et de deux seulement :
| Voie | Mécanisme | Ce qui circule |
|---|---|---|
| SG-Ready | SgReady |
deux contacts secs vers l'entrée de signalisation — 4 états normés |
| Communicante | mécanisme communicant, via un plugin nymea | mode, consignes, limite de puissance — le modèle propre de la machine |
Ni Relay, ni Variable. Ce ne sont pas des cases à remplir plus tard :
Relaycouperait l'ALIMENTATION du compresseur. UnRelayRouterétage de la puissance réelle ; appliqué à une PAC, il lui retire son autorité sur son propre cycle — dégivrage, retour d'huile, temporisation de redémarrage — et transforme chaque décision d'arbitrage en court-cycling. Le verrouminOn/minOfflimite la casse, il ne rend pas le geste légitime.Variablesuppose un cadran en watts. Un triac module une résistance ; un compresseur n'est pas gradable. Une PAC communicante peut certes accepter une limite de puissance, mais elle l'interprète selon son propre modèle et ne s'engage sur aucune valeur instantanée — ce n'est pas la sémantique d'enveloppe queVariablepromet à l'optimiseur, et cette promesse alimente le budget.
⚠️ Le piège de lecture : « ma PAC est pilotée par deux relais, donc Relay ».
Non. Les contacts SG-Ready sont des relais, et au banc .75 ce sont bien deux
things power (K1/K2). Mais ils signalent, ils ne portent pas la charge.
C'est exactement ce qui sépare les deux mécanismes : Relay commute de la
puissance, SgReady commute du sens. Voir des relais dans une configuration ne
dit donc rien du mécanisme qui convient.
Ce que ça implique pour le mécanisme communicant, qui n'existe pas encore. Il
est appelé ModbusSetpoint dans ce document : le nom est trop étroit. Ce qui
le définit n'est pas Modbus — c'est qu'un thing nymea porte le modèle de la
machine et que le moteur lui parle dans ce modèle-là. Le transport (Modbus
RTU/TCP, IP propriétaire, passerelle cloud) est une affaire de plugin
d'intégration, invisible d'ici. À renommer quand il sera implémenté ; à ne pas
confondre avec Variable d'ici là, malgré la ressemblance de surface d'une
« consigne » à écrire.
Rien n'empêche aujourd'hui d'exprimer une case interdite, et c'est assumé.
domain est une métadonnée d'intention, optionnelle, que l'arbitre ne lit
pas : une charge domain: "hvac" + adapter: "relay-router" est acceptée par
isValid(). Décision prise (2026-08-25) : pas de refus. Un refus ferait
cesser o:domain d'être une métadonnée — donc changerait le contrat annoncé à
l'app — et resterait contournable par omission, le domaine étant facultatif.
Un contrôle qu'on esquive en effaçant un champ protège mal. La sanction retenue
est un avertissement au journal à la construction de l'adaptateur.
Cet avertissement n'a pas encore de prédicat, et ne peut donc pas être écrit aujourd'hui. LM-201 en dit la raison : le domaine décrit ce qu'on arbitre, pas la machine — il ne peut pas révéler qu'il y a un compresseur derrière. Keyé sur le domaine seul, l'avertissement crierait sur des installations correctes :
| Configuration | Verdict réel |
|---|---|
heating + relay-router |
légitime — chauffage électrique résistif sur relais |
hvac + relay-router |
légitime — VMC, ventilateur sur contact |
hvac + etmvariableload |
légitime — ventilation à vitesse variable |
heating/hvac + relais, mais PAC derrière |
ce qu'on veut signaler — indistinguable des trois lignes ci-dessus |
Le discriminant qui le porterait est typeAppareil (§3, LM-300), dont
l'énumération reste ouverte (§8-1) — c'est sa valeur thermodynamique qui dirait
« compresseur ». L'avertissement est donc spécifié ici et implémenté avec
typeAppareil, pas avant. Un avertissement qui crie au loup sur des
installations correctes abîme la seule surface de diagnostic dont dispose le banc.
LM-201 — UN CANAL DE COMMANDE, UNE CHARGE. Une charge existe quand il y a un canal de commande distinct pour un service. C'est la règle qui fonde toutes les autres, et elle repose la question du domaine : « quel domaine pour une PAC ? » est mal posée. Une PAC air/eau fait chauffage, rafraîchissement et ECS — le domaine ne décrit donc pas la machine, il décrit ce qu'on arbitre.
Conséquences, toutes vérifiables sur une installation :
| Installation | Charges | Pourquoi |
|---|---|---|
| PAC chauffage + ECS, sans ballon séparé | UNE charge, domaine heating |
un seul canal SG-Ready commande la machine entière |
| PAC + ballon séparé | DEUX charges (heating, ecs) |
le ballon a son propre canal |
| PAC qui fait aussi du froid | UNE charge | même machine, même canal, même rang |
- Pas de charge ECS sur une PAC sans ballon séparé. Le SG-Ready commande la machine entière ; la répartition entre chauffage et ECS appartient à la régulation de la PAC. Il n'y a rien à arbitrer indépendamment — en déclarer deux reviendrait à promettre un arbitrage qu'aucun canal ne peut exécuter.
- Le froid ne crée pas de charge. Ce qui change entre l'été et l'hiver est la saison, pas la configuration. Une configuration qui devrait être réécrite deux fois par an serait le signe qu'on a modélisé la machine et non le canal.
- La contrainte d'exclusivité de
validateSet()n'est pas une limite technique subie — c'est le modèle. Deux charges ne peuvent pas revendiquer les mêmes contacts (LM-302-b, ECS-110-b) parce que deux charges sur un canal unique se contrediraient : elles recevraient deux consignes pour un seul organe, et le budget compterait la même puissance deux fois (règle absolue 1). Le refus existait avant la règle ; la règle dit enfin pourquoi il est juste.
LM-202 — Un thermostat de zone n'est PAS une charge. Un thermostat ouvre une vanne ; il ne porte pas la consommation, qui reste à la machine. Les déclarer comme charges produirait des entrées à 0 W dans le waterfall pendant que la PAC est comptée ailleurs — des charges qui n'ont rien à recevoir, sur un canal qui ne consomme rien.
Zones et thermostats sont de la configuration de confort : ils décrivent où
va la chaleur, pas combien on en achète. Ils vivent dans leur écran et leur
namespace — pas dans LoadConfig. C'est la même frontière que LM-102 trace
entre la couche domaine et l'allocation : décrire un besoin n'est pas négocier un
budget.
§3 — Schéma de configuration (à figer)
LM-300 — LoadConfig est une union discriminée par le mécanisme :
LoadConfig {
id, nom, enabled
domaine : Ecs | Hvac | Ev | SmartHome
typeAppareil : affine le domaine (résistif | thermodynamique | …)
groupe, prioritéGroupe, prioritéMembre
mécanisme : Relay | Variable | SgReady | ModbusSetpoint ← discriminant
chargeUtile : LoadConfigRelay | LoadConfigVariable | …
verrous : minOnS, minOffS
facettes[] : voir §4
}
État d'implémentation (2026-08-09). Trois charges utiles existent, discriminées
par le champ adapter du schéma persisté — relay-router, etmvariableload,
sg-ready. Le domaine, le groupe et les facettes ne sont pas encore portés : ce
qui est figé, c'est la forme, celle qui coûte cher à rattraper.
adapter = "sg-ready" → sgReady {
states[] { state : 1..4, relays[] : thingId, estimatedPowerW }
minStateHoldS
}
estimatedPowerW porte volontairement ce nom : c'est une estimation, jamais un
engagement — une PAC ne consomme pas la même chose à −5 °C et à +12 °C. Le champ
sert à ordonner et à budgéter, la mesure reste la source de vérité (ECS-500).
L'état 2 est obligatoire : c'est le plancher de repli du mode dégradé L2 et de
la désactivation (ECS-413/414). Une configuration qui ne peut pas l'exprimer est
refusée, pas construite.
LM-301 — Ajouter un mécanisme DOIT se limiter à un type de charge utile et
une branche de fabrique. Rien d'autre ne bouge. Vérifié à l'usage : l'ajout de
sg-ready a touché exactement une charge utile, une branche de isValid(), une
branche de fabrique dans rebuildLoadAdapters(), et un champ optionnel au schéma
RPC. Aucun code d'arbitrage n'a bougé.
LM-301-b — La limite de LM-301, trouvée au lot B-bis (2026-08-25). « Une charge utile, une
branche de fabrique » vaut pour construire un mécanisme, pas pour l'alimenter en actions.
Quand sg-ready a rejoint la table commune des charges arbitrées, un code d'arbitrage a bel et
bien dû bouger : le repli du mode dégradé L2 fabriquait un Setpoint(0) pour toute la table,
en présumant du kind. Une PAC de configuration recevait donc une action que SgReadyAdapter
rejette d'entrée — et rien d'autre : elle restait en état 4 (forcé) sous compteur muet, quand
SAFETY.md §L2 exige l'état 2. Le défaut ne se voyait pas dans la télémétrie, où le motif
DEGRADED_L2 s'affichait correctement ; seule l'écriture manquait.
Règle qui en sort : tout code qui fabrique une action pour une charge quelconque DOIT
lire descriptor().supportedKinds et se refuser explicitement s'il n'y trouve rien de connu —
jamais présumer du kind à partir de la table où la charge se trouve. C'est le pendant, côté
actions, de ce que LM-302-b dit côté validation : ce qui est vrai « pour toutes les charges »
n'appartient à aucune charge utile.
LM-302-b — Ce qu'une charge utile ne peut pas valider. L'exclusivité d'un Thing est
une propriété de l'ensemble des charges, pas d'une charge : aucune charge utile ne
peut la vérifier, quelle que soit sa rigueur. Elle vit donc au niveau du store
(validateSet()), et tout mécanisme futur DOIT déclarer les Things qu'il revendique via
LoadConfig::claimedThingIds() — c'est le seul point à étendre, et l'oublier rend le
mécanisme invisible à la vérification. Voir ECS-110-b.
LM-302 — Charges utiles auto-validantes, et portée de l'« inexprimable ». Chaque charge utile valide la sienne. Les états invalides doivent être inexprimables dans les types C++ et le schéma persisté ; à la frontière JSON-RPC, la validation est à l'exécution, par contrainte nymea.
Corollaire vérifié à l'usage (2026-08-09). GetLoadConfig sérialise via le
méta-objet, donc toutes les propriétés déclarées apparaissent dans la sortie — y
compris la charge utile vide d'un autre mécanisme. Un schéma qui exige une clé
interne dès que la charge utile est présente rend donc impossible l'aller-retour
lire-puis-réécrire, c'est-à-dire le geste ordinaire de l'application, et le rejet
survient avant le handler.
Règle. Toutes les clés internes d'une charge utile optionnelle DOIVENT être
déclarées optionnelles (o:) au schéma RPC. L'exigence réelle vit dans isValid(),
jamais dans le schéma. Les clés internes d'une charge utile optionnelle contraignent
les charges des autres mécanismes — c'est le côté par lequel le mur se touche.
Obligation permanente. Tout mécanisme ajouté DOIT être accompagné d'un test
d'aller-retour GetLoadConfig → SetLoadConfig inchangé, vérifié échouant sans
le correctif. Sans cette obligation, le prochain mécanisme rejouera la scène.
LM-303 — Verrous. typeAppareil fournit des défauts (résistif ≈ 60 s,
thermodynamique ≈ 300 s), l'installateur peut les modifier, et c'est la valeur
stockée qui fait foi. Jamais de temporisation codée en dur.
Préséance. Les verrous déclarés au niveau de la charge s'appliquent par défaut. Une charge utile PEUT en porter de plus fins — par relais, par registre — et ceux-là priment sur le défaut de charge, pour le seul actionneur qu'ils désignent.
Cette règle laisse spec_ecs.md §13-3 (ECS-307, grain des temporisations par
relais) entièrement ouvert : ajouter plus tard un champ optionnel dans une charge
utile est rétro-compatible, quelle que soit la réponse. Le schéma persisté n'a
donc pas à être « rendu tolérant » — c'est la préséance qui devait être écrite,
et elle l'est ici.
§4 — Facettes : une machine, plusieurs fonctions
LM-400 — Une machine physique est une seule charge : un rang, un jeu de verrous, une ligne de budget. Une PAC assurant chauffage et ECS n'est jamais déclarée deux fois.
Justification. Deux déclarations produiraient une double allocation du surplus pour une seule consommation, deux rangs pour un seul comportement, et deux jeux de verrous sur un seul compresseur — la protection anti court-cycling tomberait précisément là où elle coûte le plus cher.
LM-401 — Une charge PEUT porter plusieurs facettes au-dessus d'un actionnement unique. Une PAC mixte porte une facette ECS (consigne, température ballon) et une facette chauffage, avec un seul mécanisme.
LM-402 — Les facettes servent à la lecture, au suivi et au calcul
d'available. Elles ne portent pas de rang. Une charge à deux facettes ne
peut pas être classée « après les chauffe-eau mais avant la clim » : si cette
granularité est nécessaire, il faut deux machines, pas deux entrées.
LM-403 — Le mode est une sortie, pas une entrée. SG-Ready applique un niveau d'encouragement à la machine entière ; le choix de la fonction appartient au régulateur interne. Le HEMS observe le mode courant, il ne le sélectionne pas. Aucune conception ne DOIT supposer un aiguillage.
Ce que le mode apporte : available devient honnête. Machine en mode chauffage
→ la facette ECS n'absorbera rien quoi qu'on encourage → le waterfall passe au
suivant au lieu de le deviner.
LM-404 — Biais par consigne : option avancée, hors périmètre pour l'instant.
Monter hotWaterSetpointTemperature place le ballon sous consigne et la machine
bascule d'elle-même. C'est indirect, lent, et surtout persistant : une
consigne écrite le reste après un plantage ou un redémarrage, et elle écrase un
réglage d'installateur. Toute implémentation future DOIT comporter des bornes
dures, une politique de restauration et un chien de garde. À ne rouvrir que si le
terrain le justifie.
LM-405 — Décomposition côté ems, pas côté plugin. Le découpage en facettes DOIT se faire dans l'ems au-dessus des états plats du thing, et non en forkant les plugins constructeurs. Chaque constructeur expose une structure différente ; l'abstraction doit vivre là où elle est indépendante du constructeur, sous peine de la refaire N fois et de gérer deux formes du même concept.
LM-406 — Si l'installation n'a pas de chauffe-eau distinct, il n'y a pas de charge ECS : la facette ECS s'accroche à la charge PAC.
§5 — Groupes et double rang
LM-500 — Un groupe est une clé de tri, jamais un détenteur de budget. Le
waterfall trie sur le couple (prioritéGroupe, prioritéMembre) et reste
inchangé. Un groupe qui reçoit une allocation puis la redistribue est un second
décideur (règle absolue 1).
LM-501 — Classer un ECS, une PAC et une clim les uns par rapport aux autres
fonctionne déjà avec la liste plate triée par priority. Les groupes ne sont
nécessaires qu'à l'échelle : plusieurs chauffe-eau, plusieurs bornes, plusieurs
zones, quand on veut déplacer une famille entière sans renuméroter.
LM-502 — Plafond de groupe : optionnel. Justifié quand les membres partagent une limite électrique réelle (bornes sur un même câble ou un même abonnement). Sans objet pour un groupe thermique, dont les membres sont sur des circuits distincts. C'est le seul point où la boucle du waterfall change vraiment : elle doit suivre l'engagement cumulé du groupe.
LM-503 — Deux notions à ne pas confondre avec un rang :
- Exclusion mutuelle (clim et PAC en opposition dans une même zone) : une contrainte entre appareils.
- Équité interne (trois chauffe-eau à rang égal, dont le troisième resterait froid tout l'été) : une politique de rotation dans le tri.
LM-504 — « HVAC » couvre chauffage, ventilation et climatisation ; y ranger l'eau chaude sanitaire est un abus. Libellé recommandé côté installateur : « Thermique » ou « Chauffage & eau chaude ».
§6 — Nommage
LM-600 — Les classes d'actionnement portent le nom du mécanisme, jamais du domaine.
LM-601 — Un renommage de cohérence est souhaitable. Trois classes sont concernées, avec des échéances distinctes :
| Classe | Défaut | Débloqué après |
|---|---|---|
RelayRouter |
Ne suit pas la convention *Adapter de ses voisins |
étape 3 de spec_ecs.md |
EtmVariableLoadAdapter |
Préfixe Etm redondant dans un dépôt ETM |
étape 3 de spec_ecs.md |
EvAdapter |
Nommé d'après un domaine (§2 range Ev en domaine) |
3g |
EvAdapter n'est pas une classe de domaine égarée dans la couche mécanisme :
c'est un adaptateur de mécanisme mal nommé. Il parle à l'interface
evcharger de nymea — un mécanisme au même titre que SgReady. Le correctif est
un renom, pas un redécoupage.
Aucun de ces renommages ne se fait avant son échéance. Pour RelayRouter,
renommer juste avant la restructuration de m_relayMapping double le bruit dans
l'historique ; pour EvAdapter, le câblage lui-même change en 3g (l'adaptateur
n'est aujourd'hui pas dispatché), et renommer une classe dont le branchement va
bouger produit le même bruit pour rien.
BatteryAdapter n'entre pas dans ce tableau : LM-700 le traite hors moule.
§7 — Cas hors moule
LM-700 — Batterie. Une batterie n'est pas une charge avec un rang : elle est aussi une source, capable de financer les autres charges (grid-funding, 3f). Elle intervient à un autre moment de la cascade et ne se modélise pas comme un consommateur classé. Onduleur hybride et AC-coupling sont bien deux mécanismes.
LM-701 — SmartHome. Un cycle de lave-vaisselle ne se descend pas à 0 W en
cours de route : c'est un engagement pris au démarrage, pas un verrou minOn.
La sémantique d'action n'est pas une enveloppe en watts mais un décalage
temporel — démarrer maintenant ou plus tard. À traiter comme un type d'action
distinct, pas comme une variante.
§8 — Points ouverts
typeAppareil— quelle énumération exacte, et jusqu'où elle affine le domaine. A désormais un consommateur nommé : c'est lui, et nondomain, qui porterait l'avertissement de LM-200 (valeur thermodynamique = il y a un compresseur). Tant qu'il n'existe pas, l'avertissement n'a pas de prédicat.- Représentation persistée des facettes (§4) : imbriquées dans la charge, ou table séparée référençant la charge.
- Sémantique d'action du SmartHome (LM-701) : à spécifier avant toute implémentation, elle ne se déduit pas du reste.
- Rotation d'équité (LM-503) : politique à définir si le besoin se confirme.
heatingvshvac— ce que ces deux codes désignent. Question ouverte, et volontairement non tranchée. Elle ne se saura vraiment qu'en dessinant la PAC communicante (Alpha Innotec, Aquarea), où les consignes ECS et chauffage sont enfin séparées — donc où le canal de commande cesse d'être unique, et où LM-201 rend un verdict différent. D'ici là,o:domaingarde son rôle actuel : grouper l'ordre de service, sans force normative. Ne construire aucune règle dessus — l'avertissement de LM-200 attendtypeAppareilprécisément pour cette raison. (La question « le domaine doit-il devenir un discriminant de validation ? » est, elle, tranchée : non — avertissement, pas refus. Voir LM-200.)
§9 — Ce que ce document ne change pas
- L'ordre de
specs/spec_ecs.md§2 et ses arbitrages bloquants. - Les règles absolues 1 à 10 d'
AGENTS.md. - Le découpage en vigueur : le scheduler alloue,
RelayRoutertraduit. - Rien dans le code aujourd'hui.