docs(spec): LM-201 — un canal de commande, une charge ; LM-202 — un thermostat n'en est pas une
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
This commit is contained in:
parent
7c4248ca97
commit
a75d4d8dd7
@ -155,6 +155,59 @@ Règles :
|
||||
le discriminant quand cette couche arrivera. L'énumération de ce spec (§2 :
|
||||
`Ecs`/`Hvac`/`Ev`/`SmartHome`) est une intention plus ancienne ; c'est celle ci-dessus qui
|
||||
est implémentée.
|
||||
- ⚠️ **`domain` n'a AUCUNE force normative aujourd'hui, et n'en aura pas à court terme.**
|
||||
Il groupe l'ordre de service, rien d'autre. **Ne construire aucune règle dessus** — ni
|
||||
côté app, ni côté moteur. Ce que `heating` et `hvac` désignent exactement reste ouvert et
|
||||
ne se saura qu'en dessinant la PAC communicante.
|
||||
|
||||
### 4-bis. Ce qui fait une charge — **un canal de commande, une charge**
|
||||
|
||||
> **Règle LM-201** (`specs/spec_loadmodel.md` §2). Elle décide de ce que l'écran
|
||||
> « Configurer » doit **créer**, et c'est la question que l'app pose en premier.
|
||||
|
||||
**Une charge existe quand il y a un canal de commande distinct pour un service.** Le domaine
|
||||
ne décrit pas la **machine**, il décrit **ce qu'on arbitre** — une PAC air/eau fait chauffage,
|
||||
rafraîchissement et ECS, et cela ne fait pas trois charges.
|
||||
|
||||
| Installation | Ce que l'app doit créer | Pourquoi |
|
||||
|---|---|---|
|
||||
| PAC chauffage + ECS, **sans ballon séparé** | **UNE** charge (`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 — ce qui change est la saison, pas la configuration |
|
||||
|
||||
- **Ne pas créer de charge ECS sur une PAC sans ballon séparé.** La répartition entre chauffage
|
||||
et ECS appartient à la **régulation de la PAC** ; il n'y a rien à arbitrer indépendamment.
|
||||
- **Deux charges ne peuvent pas revendiquer le même Thing.** `SetLoadConfig` **rejette en
|
||||
bloc** (LM-302-b / ECS-110-b). Ce n'est pas une limite technique : deux charges sur un canal
|
||||
unique recevraient deux consignes pour un seul organe, et le budget compterait la même
|
||||
puissance deux fois.
|
||||
|
||||
**Les thermostats et les zones ne sont PAS des charges (LM-202).** Un thermostat ouvre une
|
||||
**vanne** ; il ne porte pas la consommation, qui reste à la machine. Les déclarer produirait
|
||||
des entrées à **0 W** dans le waterfall pendant que la PAC est comptée ailleurs. Zones et
|
||||
thermostats sont de la **configuration de confort** — ils disent **où va la chaleur**, pas
|
||||
**combien on en achète**. Ils vivent dans leur écran et leur namespace, **jamais** dans
|
||||
`LoadConfig`.
|
||||
|
||||
### 4-ter. Mécanismes recevables selon l'organe (LM-200)
|
||||
|
||||
**Une PAC se pilote de deux façons, et de deux seulement** : `sg-ready` (deux contacts **secs**
|
||||
de signalisation, 4 états normés) ou **communicante** via un plugin nymea — ce dernier mécanisme
|
||||
**n'existe pas encore**.
|
||||
|
||||
**Ni `relay-router`, ni `etmvariableload`** : le premier couperait l'**alimentation** du
|
||||
compresseur et lui retirerait son autorité sur son propre cycle (dégivrage, retour d'huile,
|
||||
temporisation de redémarrage) ; le second suppose un cadran en watts qu'un compresseur n'est pas.
|
||||
|
||||
> ⚠️ **Piège d'UI : « ma PAC est pilotée par deux relais, donc `relay-router` ».** Non. Les
|
||||
> contacts SG-Ready **sont** des things `power` — au banc `.75`, K1/K2 en sont deux. Mais ils
|
||||
> **signalent**, ils ne **portent** pas la charge. Voir des relais dans une configuration ne dit
|
||||
> **rien** du mécanisme qui convient. Le picker de relais de §10 sert les deux mécanismes.
|
||||
|
||||
Le moteur **n'interdit pas** ces combinaisons — `domain` est facultatif et sans force normative,
|
||||
un refus serait contournable en effaçant le champ. **C'est donc l'app qui guide au moment du
|
||||
choix**, et c'est le seul endroit où le guidage est fiable : elle sait quel appareil
|
||||
l'installateur est en train de déclarer, le moteur ne le sait pas.
|
||||
|
||||
---
|
||||
|
||||
@ -279,3 +332,14 @@ toucher à l'écran Configurer, sinon il code contre une frontière périmée (r
|
||||
experience-plugin) ; un relais = thing `power` nu ; `LoadConfig` du cas ECS devient une **liste
|
||||
de relais** (`relays[]` + `minOnS`/`minOffS`) ; `powerLevels` **dérivés** ; cas continu
|
||||
(`EtmVariableLoadAdapter`) inchangé. **Impact app §10.**
|
||||
- **lot B-bis (la PAC entre en configuration)** : plus **aucun** adaptateur codé en dur —
|
||||
`loads[]` de `GetLoadTelemetry` est **inclus** dans `GetLoadConfig`, par construction ; la
|
||||
règle de détection « un `loadId` sans équivalent dans `GetLoadConfig` » est **retirée**
|
||||
d'`INTERFACE.md` et aucun `configurable: false` n'a été ajouté. `pac-terrain` est devenue une
|
||||
`LoadConfig` `sg-ready` ordinaire.
|
||||
- **§4-bis / §4-ter ajoutés** : **LM-201** — *un canal de commande, une charge* — qui décide de
|
||||
ce que l'écran « Configurer » doit **créer** (une PAC sans ballon séparé = **une** charge, pas
|
||||
deux) ; **LM-202** — thermostats et zones **ne sont pas** des charges ; **LM-200** — les
|
||||
mécanismes recevables selon l'organe, et le piège « pilotée par deux relais donc
|
||||
`relay-router` ». `domain` est explicitement **sans force normative** : c'est l'**app** qui
|
||||
guide au moment du choix, le moteur ne le peut pas.
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
# SPEC — Modèle de charges (domaine × mécanisme)
|
||||
|
||||
Version : 0.1.3
|
||||
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 `SgReady` ou en communicant. Précise LM-101, qui était trop
|
||||
@ -136,12 +143,75 @@ d'intégration, invisible d'ici. À renommer quand il sera implémenté ; à ne
|
||||
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.
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
@ -373,28 +443,23 @@ distinct, pas comme une variante.
|
||||
## §8 — Points ouverts
|
||||
|
||||
1. `typeAppareil` — quelle énumération exacte, et jusqu'où elle affine le
|
||||
domaine.
|
||||
domaine. **A désormais un consommateur nommé** : c'est lui, et non `domain`,
|
||||
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.
|
||||
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.**
|
||||
5. **`heating` vs `hvac` — 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:domain` **garde son rôle actuel** : grouper l'ordre de
|
||||
service, **sans force normative**. **Ne construire aucune règle dessus** — l'avertissement
|
||||
de LM-200 attend `typeAppareil` pré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.)*
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user