La PAC ne se pilote qu'en SgReady (contacts SECS de signalisation, 4 états normés) ou en COMMUNICANT via un plugin nymea. Ni Relay ni Variable : le premier couperait l'alimentation du compresseur et lui retirerait son autorité sur son propre cycle (dégivrage, retour d'huile, temporisation) ; le second suppose un cadran en watts qu'un compresseur n'est pas. LM-101 disait « le domaine ne détermine pas le mécanisme et réciproquement » — vrai du couplage de CONCEPTION, faux lu comme une liberté de combiner. Précisé. Piège nommé au passage : les contacts SG-Ready SONT des relais (K1/K2 au banc sont deux things power), mais ils signalent au lieu de porter la charge. Voir des relais dans une config ne dit rien du mécanisme qui convient. Le mécanisme communicant, encore appelé ModbusSetpoint ici, porte un nom trop étroit : ce qui le définit est qu'un thing nymea porte le modèle de la machine, pas le transport. §8 point 5 : faire du domaine un discriminant de validation est une décision de contrat, pas un correctif — trois options pesées, non tranchée. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
407 lines
22 KiB
Markdown
407 lines
22 KiB
Markdown
# SPEC — Modèle de charges (domaine × mécanisme)
|
||
|
||
Version : 0.1.3
|
||
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.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 `SgReady` ou 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-ready` a 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), `EvAdapter` inscrit 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 et
|
||
`available`. **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 :
|
||
|
||
- **`Relay` couperait l'ALIMENTATION du compresseur.** Un `RelayRouter` é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 verrou `minOn/minOff` limite la
|
||
casse, il ne rend pas le geste légitime.
|
||
- **`Variable` suppose 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
|
||
que `Variable` promet à 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.** `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()`, et
|
||
l'omission du domaine contournerait de toute façon tout contrôle qui s'y
|
||
appuierait. Faire du domaine un discriminant de validation est une **décision de
|
||
contrat**, pas un correctif — elle est portée en §8.
|
||
|
||
---
|
||
|
||
## §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
|
||
|
||
1. `typeAppareil` — quelle énumération exacte, et jusqu'où elle affine le
|
||
domaine.
|
||
2. Représentation persistée des facettes (§4) : imbriquées dans la charge, ou
|
||
table séparée référençant la charge.
|
||
3. Sémantique d'action du SmartHome (LM-701) : à spécifier avant toute
|
||
implémentation, elle ne se déduit pas du reste.
|
||
4. Rotation d'équité (LM-503) : politique à définir si le besoin se confirme.
|
||
5. **Le domaine doit-il devenir un discriminant de validation (LM-200) ?** Aujourd'hui
|
||
`domain` est une métadonnée optionnelle que l'arbitre ne lit pas ; rien ne refuse
|
||
`domain: "hvac"` + `adapter: "relay-router"`. Trois façons de traiter la case interdite,
|
||
par coût croissant :
|
||
- **la documenter seulement** — l'app guide le choix, le moteur ne juge pas. C'est
|
||
l'état actuel, désormais écrit ;
|
||
- **refuser dans `isValid()`** — cohérent avec « les états invalides doivent être
|
||
inexprimables » (LM-302), mais `domain` cesse d'être une métadonnée : ce serait un
|
||
changement de contrat, à annoncer à l'app. Et le contrôle reste **contournable par
|
||
omission**, puisque le domaine est facultatif — un refus qu'on esquive en effaçant
|
||
un champ protège mal ;
|
||
- **avertir sans refuser** — une ligne de journal à la construction de l'adaptateur.
|
||
Ne bloque pas une installation atypique, laisse une trace lisible au banc.
|
||
|
||
Le troisième est le seul qui ne mente ni sur le statut de `domain`, ni sur la force
|
||
réelle du contrôle. **À trancher, pas par moi.**
|
||
|
||
---
|
||
|
||
## §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, `RelayRouter` traduit.
|
||
- Rien dans le code aujourd'hui.
|