Sonde en LECTURE SEULE — l'obligation est celle du moteur, rien n'a été écrit. Le critère positif est atteint : `deliveredWh` +25 Wh/min (1 500 W, ce que le compteur ECS annonce), `remainingWh` qui descend d'autant, `gridW` 466 → 463. Décroissance, pas absence de mur. Deux invariants tiennent, et l'écran les vérifiera à chaque trame : - `deliveredWh + remainingWh == targetWh` — et c'est sous `measured` qu'elle mord, là où elle n'est plus le corollaire trivial d'`unmeasurable` ; - `gridW == remainingWh / remainingS` — 466 == 466 puis 463 == 463, donc la loi d'étalement est vérifiable de l'extérieur, sans lire le code. Fuseau : `remainingS` le confirme de notre côté (18 719 s à 06:48 heure box mènent à 12:00 heure box), et `System.GetTime` publie `timeZone` — l'app peut donc être honnête sans attendre l'arbitrage. Règle écrite dans CLAUDE.md et dans la maquette : dire l'horloge au lieu de la choisir, fuseau de la box affiché à côté du champ et divergence signalée. Ce que le lot moteur doit trancher reste entier : d'où vient le fuseau, et ce qu'il fait quand il diverge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
826 lines
55 KiB
HTML
826 lines
55 KiB
HTML
<!doctype html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>ETM PowerSync — §10 : deux passes, une commande</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#081f2b; --panel:#0d2b3b; --card:#12384a; --card2:#0f3242;
|
||
--line:rgba(255,255,255,.08); --line2:rgba(255,255,255,.14);
|
||
--blue:#31a3dd; --yellow:#fec113; --green:#35c98a; --red:#e07a7a; --violet:#9d8ce0;
|
||
--txt:#e8f1f6; --muted:#90a8b6; --muted2:#6f8b9a; --r:16px;
|
||
}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:
|
||
radial-gradient(900px 500px at 15% -10%, #0e3145 0%, transparent 60%),
|
||
radial-gradient(900px 600px at 110% 10%, #0b2535 0%, transparent 55%),
|
||
var(--bg);
|
||
font-family:'Inter',system-ui,sans-serif;color:var(--txt);min-height:100vh;padding:34px 20px 60px}
|
||
.head{max-width:1300px;margin:0 auto 26px}
|
||
.head h1{font-family:'Poppins';font-weight:700;font-size:20px;margin:0 0 6px}
|
||
.head p{margin:0 0 8px;color:var(--muted);font-size:13.5px;line-height:1.55;max-width:940px}
|
||
.head code{color:#9fd4ee} .head b{color:var(--yellow)}
|
||
.head .rep{border-left:2px solid var(--blue);padding-left:12px;margin:14px 0 0;max-width:940px}
|
||
.head .rep p{margin:0 0 5px;font-size:13px}
|
||
.head .rep b{color:var(--blue)}
|
||
.stage{display:flex;gap:34px;justify-content:center;flex-wrap:wrap;align-items:flex-start}
|
||
.col{display:flex;flex-direction:column;align-items:center;gap:12px;max-width:392px;width:100%}
|
||
.cap{color:var(--muted2);font-size:12px;font-family:'Poppins';font-weight:500;letter-spacing:.4px;text-transform:uppercase}
|
||
.cap b{color:var(--blue)}
|
||
|
||
.phone{width:min(392px,100%);background:var(--panel);border:1px solid var(--line2);border-radius:30px;
|
||
overflow:hidden;box-shadow:0 30px 80px -30px rgba(0,0,0,.7);display:flex;flex-direction:column;height:900px}
|
||
.appbar{display:flex;align-items:center;gap:12px;padding:18px 16px 14px;border-bottom:1px solid var(--line);
|
||
background:linear-gradient(#102f40,#0d2b3b)}
|
||
.appbar .ic{width:26px;height:26px;display:grid;place-items:center;color:var(--muted);font-size:18px}
|
||
.appbar h2{font-family:'Poppins';font-weight:600;font-size:15px;margin:0;flex:1;text-align:center}
|
||
.scroll{overflow:auto;padding:16px 15px 26px}
|
||
.scroll::-webkit-scrollbar{width:0}
|
||
|
||
.sect{font-family:'Poppins';font-weight:600;font-size:12px;color:var(--muted);letter-spacing:.3px;
|
||
margin:20px 0 9px;display:flex;align-items:baseline;gap:8px}
|
||
.sect:first-child{margin-top:2px}
|
||
.sect small{font-weight:500;color:var(--muted2);letter-spacing:0;font-size:11px}
|
||
|
||
/* ─ bandeau arbitrage : DEUX comptabilités ─ */
|
||
.arb{background:var(--card2);border:1px solid var(--line);border-radius:13px;padding:12px 13px}
|
||
.arb .top{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||
.arb .dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 4px rgba(53,201,138,.15)}
|
||
.arb .top b{font-family:'Poppins';font-size:12.5px;font-weight:600}
|
||
.arb .top i{margin-left:auto;font-style:normal;color:var(--muted2);font-size:11px}
|
||
.arb .lab{font-size:10px;color:var(--muted2);font-family:'Poppins';font-weight:600;letter-spacing:.3px;
|
||
text-transform:uppercase;margin:9px 0 5px;display:flex;align-items:center;gap:6px}
|
||
.arb .lab u{text-decoration:none;color:var(--muted2)}
|
||
.arb .lab .p1{color:var(--yellow)} .arb .lab .p2{color:var(--green)}
|
||
.arb .grid{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}
|
||
.arb .grid.g4{grid-template-columns:repeat(4,1fr)}
|
||
.arb .cell{background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:7px 4px;text-align:center}
|
||
.arb .cell u{display:block;text-decoration:none;font-size:9.5px;color:var(--muted2);margin-bottom:2px}
|
||
.arb .cell b{font-family:'Poppins';font-weight:600;font-size:11.5px}
|
||
.arb .cell.zero b{color:var(--muted2)}
|
||
.arb .cell.bind{border-color:rgba(224,122,122,.4);background:rgba(224,122,122,.07)}
|
||
.arb .cell.bind b{color:var(--red);font-size:10px}
|
||
|
||
/* ─ la phrase qui DIT que l'ordre affiché n'est pas l'ordre de service ─ */
|
||
.twopass{margin-top:9px;border:1px solid rgba(157,140,224,.34);background:rgba(157,140,224,.08);
|
||
border-radius:11px;padding:10px 11px;font-size:11.5px;line-height:1.5;color:var(--muted)}
|
||
.twopass b{color:var(--violet);font-family:'Poppins';font-size:11.5px;display:block;margin-bottom:3px}
|
||
.twopass a{color:var(--violet);text-decoration:none;font-weight:600;display:block;margin-top:6px}
|
||
|
||
/* ─ carte de charge ─ */
|
||
.load{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:12px;margin-bottom:9px}
|
||
.load.spot{border-color:rgba(157,140,224,.42);box-shadow:0 0 0 3px rgba(157,140,224,.07)}
|
||
.load .r1{display:flex;align-items:center;gap:10px}
|
||
.load .grip{color:var(--muted2);font-size:15px;cursor:grab}
|
||
.rank{width:23px;height:23px;border-radius:7px;background:rgba(49,163,221,.16);color:var(--blue);
|
||
font-family:'Poppins';font-weight:700;font-size:11.5px;display:grid;place-items:center;flex:none}
|
||
.load .nm{flex:1;min-width:0}
|
||
.load .nm b{font-family:'Poppins';font-weight:600;font-size:13.5px;display:block}
|
||
.load .nm span{font-size:11px;color:var(--muted2)}
|
||
.sw{width:38px;height:22px;border-radius:20px;background:rgba(53,201,138,.85);position:relative;cursor:pointer;flex:none}
|
||
.sw::after{content:"";position:absolute;top:2px;left:18px;width:18px;height:18px;border-radius:50%;background:#fff}
|
||
.sw.off{background:rgba(255,255,255,.16)} .sw.off::after{left:2px}
|
||
|
||
/* ─ LA COMMANDE : le parent. Les niveaux sont sa décomposition. ─ */
|
||
.cmd{margin-top:10px;padding:9px 10px;border:1px solid var(--line2);border-radius:11px;background:var(--card2)}
|
||
.cmd .l{display:flex;align-items:baseline;gap:8px}
|
||
.cmd .l b{font-family:'Poppins';font-weight:700;font-size:14px}
|
||
.cmd .l i{font-style:normal;font-size:10.5px;color:var(--muted2);margin-left:auto}
|
||
.cmd .l small{font-size:11px;color:var(--muted)}
|
||
.cmd.idle .l b{color:var(--muted2)}
|
||
.cmd .round{font-size:10.5px;color:var(--yellow);margin-top:4px}
|
||
|
||
.lvls{margin-top:8px;border-left:2px solid var(--line2);padding-left:10px;margin-left:4px}
|
||
.lvl{display:flex;gap:8px;padding:6px 0;font-size:11.5px;line-height:1.45}
|
||
.lvl + .lvl{border-top:1px dashed var(--line)}
|
||
.lvl .tag{flex:none;width:60px;font-family:'Poppins';font-weight:700;font-size:9.5px;letter-spacing:.4px;
|
||
padding:3px 0;text-align:center;border-radius:6px;height:19px}
|
||
.lvl .tag.eco{background:rgba(254,193,19,.14);color:var(--yellow)}
|
||
.lvl .tag.cft{background:rgba(53,201,138,.13);color:var(--green)}
|
||
.lvl .bd{flex:1;min-width:0}
|
||
.lvl .bd b{font-family:'Poppins';font-weight:600;font-size:11.5px;display:block}
|
||
.lvl .bd span{color:var(--muted2);display:block;margin-top:1px}
|
||
.lvl .bd em{font-style:normal;color:var(--muted)}
|
||
.lvl.ok .bd b{color:var(--green)}
|
||
.lvl.grid .bd b{color:var(--yellow)}
|
||
.lvl.wait .bd b{color:var(--muted)}
|
||
.lvl.no .bd b{color:var(--red)}
|
||
.lvl.mute .bd b{color:var(--muted2)}
|
||
.pill{display:inline-block;font-size:9.5px;font-family:'Poppins';font-weight:700;padding:1px 6px;border-radius:20px;
|
||
border:1px solid var(--line2);color:var(--muted2);margin-left:6px;vertical-align:1px}
|
||
.pill.meas{border-color:rgba(53,201,138,.4);color:var(--green)}
|
||
.pill.est{border-color:rgba(254,193,19,.4);color:var(--yellow)}
|
||
.pill.nm{border-color:rgba(224,122,122,.45);color:var(--red)}
|
||
|
||
.bar{height:5px;border-radius:4px;background:rgba(255,255,255,.08);margin-top:5px;overflow:hidden;display:flex}
|
||
.bar i{display:block;height:100%;background:var(--yellow)}
|
||
.bar.dash{background:repeating-linear-gradient(90deg,rgba(255,255,255,.14) 0 5px,transparent 5px 10px)}
|
||
|
||
.cfg{margin-top:10px;width:100%;background:transparent;border:1px solid rgba(49,163,221,.42);color:var(--blue);
|
||
font-family:'Poppins';font-weight:600;font-size:12px;padding:9px;border-radius:10px;cursor:pointer;
|
||
display:flex;align-items:center;justify-content:center;gap:7px}
|
||
|
||
/* ─ écran 2 : ordre de service ─ */
|
||
.hint{border:1px solid rgba(49,163,221,.28);background:rgba(49,163,221,.07);border-radius:12px;
|
||
padding:11px 12px;font-size:11.5px;color:var(--muted);line-height:1.5}
|
||
.hint b{color:var(--blue);font-family:'Poppins';font-size:12px;display:block;margin-bottom:3px}
|
||
.pass{margin-top:18px}
|
||
.pass .ph{display:flex;align-items:center;gap:9px;margin-bottom:9px}
|
||
.pass .ph .n{width:22px;height:22px;border-radius:7px;font-family:'Poppins';font-weight:700;font-size:11px;
|
||
display:grid;place-items:center;flex:none}
|
||
.pass.p1 .ph .n{background:rgba(254,193,19,.16);color:var(--yellow)}
|
||
.pass.p2 .ph .n{background:rgba(53,201,138,.16);color:var(--green)}
|
||
.pass .ph b{font-family:'Poppins';font-weight:600;font-size:12.5px}
|
||
.pass .ph i{margin-left:auto;font-style:normal;font-size:10.5px;color:var(--muted2)}
|
||
.pass.p1 .ph b{color:var(--yellow)} .pass.p2 .ph b{color:var(--green)}
|
||
.srv{display:flex;align-items:flex-start;gap:9px;padding:9px 10px;border-radius:11px;background:var(--card);
|
||
border:1px solid var(--line);margin-bottom:6px}
|
||
.srv .rk{width:21px;height:21px;border-radius:6px;background:rgba(49,163,221,.16);color:var(--blue);
|
||
font-family:'Poppins';font-weight:700;font-size:11px;display:grid;place-items:center;flex:none;margin-top:1px}
|
||
.srv .bd{flex:1;min-width:0;font-size:11.5px;line-height:1.45}
|
||
.srv .bd b{font-family:'Poppins';font-weight:600;font-size:12.5px;display:block}
|
||
.srv .bd span{color:var(--muted2);display:block;font-size:11px;margin-top:1px}
|
||
.srv .w{flex:none;text-align:right;font-family:'Poppins';font-weight:700;font-size:12.5px}
|
||
.srv .w u{display:block;text-decoration:none;font-size:9.5px;color:var(--muted2);font-weight:600;margin-top:2px}
|
||
.srv.g .w{color:var(--yellow)} .srv.s .w{color:var(--green)}
|
||
.srv.nil{opacity:.72} .srv.nil .w{color:var(--muted2)}
|
||
.srv.cut{border-color:rgba(224,122,122,.35);background:rgba(224,122,122,.06)} .srv.cut .w{color:var(--red)}
|
||
.rule{display:flex;align-items:center;gap:8px;margin:9px 0;color:var(--muted2);font-size:10.5px}
|
||
.rule:before,.rule:after{content:"";flex:1;height:1px;background:var(--line2)}
|
||
.exh{border:1px dashed rgba(224,122,122,.4);border-radius:10px;padding:8px 10px;font-size:11px;color:var(--muted);
|
||
line-height:1.45;margin-bottom:6px}
|
||
.exh b{color:var(--red);font-family:'Poppins';font-size:11px}
|
||
|
||
/* ─ écran 3 : catalogue ─ */
|
||
.cat{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:10px 11px;margin-bottom:8px}
|
||
.cat .h{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||
.cat .h .tag{flex:none;font-family:'Poppins';font-weight:700;font-size:9.5px;letter-spacing:.4px;
|
||
padding:3px 7px;border-radius:6px}
|
||
.cat .h .tag.eco{background:rgba(254,193,19,.14);color:var(--yellow)}
|
||
.cat .h .tag.cft{background:rgba(53,201,138,.13);color:var(--green)}
|
||
.cat .h b{font-family:'Poppins';font-weight:600;font-size:12px;flex:1}
|
||
.cat .h code{font-size:10px;color:#9fd4ee}
|
||
.cat .txt{font-size:11.5px;color:var(--txt);line-height:1.45}
|
||
.cat .why{font-size:10.8px;color:var(--muted2);line-height:1.5;margin-top:4px}
|
||
.never{margin-top:16px;border:1px solid rgba(224,122,122,.32);background:rgba(224,122,122,.06);
|
||
border-radius:12px;padding:11px 12px}
|
||
.never b{color:var(--red);font-family:'Poppins';font-size:11.5px;display:block;margin-bottom:5px}
|
||
.never ul{margin:0;padding-left:16px;font-size:11px;color:var(--muted);line-height:1.6}
|
||
.never li b{display:inline;color:var(--red);font-size:11px}
|
||
|
||
.note{width:min(392px,100%);font-size:11.5px;color:var(--muted2);line-height:1.55;text-align:left}
|
||
.note b{color:var(--yellow)} .note code{color:#9fd4ee}
|
||
.note ul{margin:7px 0 0;padding-left:16px} .note li{margin-bottom:5px}
|
||
|
||
/* ─ le brief moteur ─ */
|
||
.brief{max-width:1300px;margin:44px auto 0;border-top:1px solid var(--line2);padding-top:28px}
|
||
.brief h2{font-family:'Poppins';font-weight:700;font-size:17px;margin:0 0 4px}
|
||
.brief h3{font-family:'Poppins';font-weight:600;font-size:13.5px;margin:22px 0 6px;color:var(--blue)}
|
||
.brief p{color:var(--muted);font-size:13px;line-height:1.6;max-width:940px;margin:0 0 9px}
|
||
.brief b{color:var(--yellow)} .brief code{color:#9fd4ee;font-size:12px}
|
||
.brief pre{background:#06171f;border:1px solid var(--line);border-radius:12px;padding:14px 16px;overflow:auto;
|
||
font-size:11.8px;line-height:1.55;color:#cfe6f2;max-width:940px}
|
||
.brief pre .c{color:#5d8698}
|
||
.brief pre .k{color:#9fd4ee}
|
||
.brief pre .n{color:var(--yellow)}
|
||
.brief pre .s{color:#9ee0b8}
|
||
.req{border:1px solid var(--line);background:var(--card2);border-radius:12px;padding:12px 14px;margin-bottom:9px;
|
||
max-width:940px}
|
||
.req b{font-family:'Poppins';font-weight:600;font-size:12.5px;color:var(--txt);display:block;margin-bottom:3px}
|
||
.req b u{text-decoration:none;color:var(--blue);margin-right:7px}
|
||
.req p{margin:0;font-size:12px;color:var(--muted);line-height:1.55}
|
||
.req.hard{border-color:rgba(224,122,122,.35);background:rgba(224,122,122,.05)}
|
||
.req.hard b u{color:var(--red)}
|
||
.brief .no{border-left:2px solid var(--line2);padding-left:12px;max-width:940px}
|
||
.brief .no li{color:var(--muted);font-size:12.5px;line-height:1.6;margin-bottom:4px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="head">
|
||
<h1>§10 — Éco et confort : deux passes, deux lectures, <b>une seule commande</b></h1>
|
||
<p>Réponse de l'app aux trois questions du brief plugin <code>docs/BRIEF_depuis_plugin.md</code> §9
|
||
(= <code>DESIGN_10.md</code> 8.5, LM-1002 / LM-1003 / LM-1006 couplage 2 / LM-1009). Rien n'est câblé côté app :
|
||
cette maquette est le <b>brief</b>, et la charge utile demandée est en bas de page.</p>
|
||
<p><b>Elle vaut comme contrat, pas comme feuille de route.</b> Le §10 n'est pas implémenté, le transport du
|
||
§14a n'existe pas, <code>draw</code> non plus : aucun de ces trois écrans ne se construit avant que
|
||
<code>levels[]</code> soit en ligne. Ce qu'elle fixe, c'est la <em>forme</em> de ce qui sera publié — pour
|
||
que le moteur n'ait pas à inventer une charge utile que l'écran devra ensuite arranger.</p>
|
||
|
||
<div class="rep">
|
||
<p><b>1 · La liste reste ordonnée par RANG</b> — parce que c'est la surface d'édition du rang, et parce que
|
||
l'ordre de service <em>n'est pas une propriété de la configuration</em> : il change à chaque cycle selon les
|
||
planchers déjà atteints. Une liste qui se réordonne toute seule sous le doigt est inutilisable.</p>
|
||
<p><b>2 · Une seule entrée par charge dans la liste, deux lignes DANS la carte</b> — la commande est le parent,
|
||
les deux niveaux sont sa décomposition. On ne dédouble que là où l'objet affiché est la <em>séquence</em>,
|
||
jamais là où l'objet est la <em>charge</em>.</p>
|
||
<p><b>3 · L'ordre de service devient un second écran, en lecture seule</b> — c'est lui qui « le dit ». Il montre
|
||
le rang 4 éco avant le rang 1 confort, avec les rangs visibles, donc il montre en même temps que
|
||
<em>votre ordre est respecté dans chaque passe</em>. C'est l'écran qui désamorce « l'ordre n'est pas respecté ».</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stage">
|
||
|
||
<!-- ══════════ ÉCRAN 1 — la liste, au rang ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Rôles & appareils</h2><div class="ic">⟳</div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="sect">Arbitrage en direct <small>· cycle de 06:41, il y a 22 s</small></div>
|
||
<div class="arb">
|
||
<div class="top"><span class="dot"></span><b>Matin sans soleil</b><i>2 comptabilités</i></div>
|
||
|
||
<div class="lab"><span class="p2">■</span><u>Passe 2 — surplus disponible</u></div>
|
||
<div class="grid">
|
||
<div class="cell zero"><u>Surplus</u><b>0</b></div>
|
||
<div class="cell zero" style="border-style:dashed"><u>Réservé VE *</u><b>0</b></div>
|
||
<div class="cell zero"><u>Recrédité</u><b>0</b></div>
|
||
<div class="cell zero"><u>Alloué</u><b>0</b></div>
|
||
<div class="cell zero"><u>Reliquat</u><b>0</b></div>
|
||
</div>
|
||
|
||
<div class="lab"><span class="p1">■</span><u>Passe 1 — autorisation de soutirage</u></div>
|
||
<div class="grid g4">
|
||
<div class="cell"><u>Autorisé</u><b>4 000</b></div>
|
||
<div class="cell"><u>Engagé</u><b>4 000</b></div>
|
||
<div class="cell zero"><u>Restant</u><b>0</b></div>
|
||
<div class="cell bind"><u>Borné par</u><b>GRD</b></div>
|
||
</div>
|
||
|
||
<div class="twopass">
|
||
<b>◆ Deux passes ce cycle</b>
|
||
Tous les <em>planchers éco</em> sont servis d'abord, par rang croissant ; le <em>confort</em> ensuite,
|
||
dans le même ordre. Un plancher de rang 4 passe donc avant un confort de rang 1 —
|
||
<em>c'est voulu, et votre ordre est respecté à l'intérieur de chaque passe.</em>
|
||
<a>Voir l'ordre de service de ce cycle ›</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sect">Charges pilotables <small>· ordonnées par rang — glissez pour classer</small></div>
|
||
|
||
<!-- LE CAS FRÉQUENT : éco servi, confort non -->
|
||
<div class="load spot">
|
||
<div class="r1">
|
||
<span class="grip">⠿</span><span class="rank">1</span>
|
||
<div class="nm"><b>Chauffe-eau</b><span>Routeur de relais · 3 relais · ECS</span></div>
|
||
<span class="sw"></span>
|
||
</div>
|
||
<div class="cmd">
|
||
<div class="l"><b>Décidé 1 400 W</b><i>une seule décision</i></div>
|
||
<div class="round">↳ appliqué au mécanisme : <b>palier 1 000 W</b> (relais 1+2) — l'adaptateur
|
||
n'a pas ce point exact</div>
|
||
</div>
|
||
<div class="lvls">
|
||
<div class="lvl grid">
|
||
<span class="tag eco">ÉCO</span>
|
||
<div class="bd">
|
||
<b>Plancher servi — 1 400 W achetés au réseau</b>
|
||
<span>2,1 kWh livrés sur les 3,0 dus avant 07:00 <em>(heure box)</em>
|
||
<span class="pill meas">MESURÉ</span></span>
|
||
<div class="bar"><i style="width:70%"></i></div>
|
||
</div>
|
||
</div>
|
||
<div class="lvl wait">
|
||
<span class="tag cft">CONFORT</span>
|
||
<div class="bd">
|
||
<b>En attente</b>
|
||
<span>Aucun surplus ce cycle — le bonus n'est pas refusé, il n'a rien à prendre.</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button class="cfg">⚙ Configurer</button>
|
||
</div>
|
||
|
||
<div class="load">
|
||
<div class="r1">
|
||
<span class="grip">⠿</span><span class="rank">2</span>
|
||
<div class="nm"><b>PAC banc</b><span>SG-Ready · chauffage</span></div>
|
||
<span class="sw"></span>
|
||
</div>
|
||
<div class="cmd idle">
|
||
<div class="l"><b>Décidé 0 W</b><small>état SG-Ready 2 appliqué</small><i>une seule décision</i></div>
|
||
</div>
|
||
<div class="lvls">
|
||
<div class="lvl ok">
|
||
<span class="tag eco">ÉCO</span>
|
||
<div class="bd">
|
||
<b>Plancher atteint</b>
|
||
<span>3,0 kWh livrés sur 3,0 <span class="pill meas">MESURÉ</span> — plus rien à servir en passe 1.</span>
|
||
<div class="bar"><i style="width:100%;background:var(--green)"></i></div>
|
||
</div>
|
||
</div>
|
||
<div class="lvl wait">
|
||
<span class="tag cft">CONFORT</span>
|
||
<div class="bd"><b>En attente</b><span>Aucun surplus ce cycle.</span></div>
|
||
</div>
|
||
</div>
|
||
<button class="cfg">⚙ Configurer</button>
|
||
</div>
|
||
|
||
<div class="load">
|
||
<div class="r1">
|
||
<span class="grip">⠿</span><span class="rank">3</span>
|
||
<div class="nm"><b>Borne sans compteur de session</b><span>Borne de recharge · VE</span></div>
|
||
<span class="sw"></span>
|
||
</div>
|
||
<div class="cmd">
|
||
<div class="l"><b>Décidé 2 600 W</b><i>une seule décision</i></div>
|
||
<div class="round">↳ appliqué au mécanisme : <b>11 A · 1 phase ≈ 2 530 W</b> — le courant est
|
||
entier, la décision ne l'est pas</div>
|
||
</div>
|
||
<div class="lvls">
|
||
<div class="lvl grid">
|
||
<span class="tag eco">ÉCO</span>
|
||
<div class="bd">
|
||
<b>Plancher servi — 2 600 W achetés au réseau</b>
|
||
<span>Avancement <em>non mesurable</em> : cette borne ne publie pas d'énergie de session.
|
||
<span class="pill nm">NON MESURABLE</span></span>
|
||
<div class="bar dash"></div>
|
||
<span><b>Reste 4,0 kWh à livrer en 5 h 13</b> — et ce taux ne peut que <b>monter</b> : rien
|
||
n'est soustrait de ce qui reste, donc l'achat s'accélère jusqu'à l'échéance.</span>
|
||
<span>L'obligation est servie ; elle ne pourra jamais être annoncée <em>tenue</em>.</span>
|
||
</div>
|
||
</div>
|
||
<div class="lvl wait">
|
||
<span class="tag cft">CONFORT</span>
|
||
<div class="bd"><b>En attente</b><span>Aucun surplus ce cycle.</span></div>
|
||
</div>
|
||
</div>
|
||
<button class="cfg">⚙ Configurer</button>
|
||
</div>
|
||
|
||
<div class="load">
|
||
<div class="r1">
|
||
<span class="grip">⠿</span><span class="rank">4</span>
|
||
<div class="nm"><b>Plancher chauffant</b><span>Charge modulable · chauffage</span></div>
|
||
<span class="sw"></span>
|
||
</div>
|
||
<div class="cmd idle">
|
||
<div class="l"><b>Décidé 0 W</b><i>une seule décision</i></div>
|
||
</div>
|
||
<div class="lvls">
|
||
<div class="lvl no">
|
||
<span class="tag eco">ÉCO</span>
|
||
<div class="bd">
|
||
<b>Plancher NON servi — plafond de soutirage atteint</b>
|
||
<span>Plafond imposé par le <em>gestionnaire de réseau</em> (4 000 W), épuisé au rang 3.
|
||
Ce n'est pas la protection de surcharge de l'installation.</span>
|
||
<span>0,4 kWh livrés sur 2,0 <span class="pill meas">MESURÉ</span> — obligation reportée.</span>
|
||
<div class="bar"><i style="width:20%;background:var(--red)"></i></div>
|
||
</div>
|
||
</div>
|
||
<div class="lvl wait">
|
||
<span class="tag cft">CONFORT</span>
|
||
<div class="bd"><b>En attente</b><span>Aucun surplus ce cycle.</span></div>
|
||
</div>
|
||
</div>
|
||
<button class="cfg">⚙ Configurer</button>
|
||
</div>
|
||
|
||
<div class="load">
|
||
<div class="r1">
|
||
<span class="grip">⠿</span><span class="rank">5</span>
|
||
<div class="nm"><b>Sèche-serviettes</b><span>Routeur de relais · 1 relais</span></div>
|
||
<span class="sw"></span>
|
||
</div>
|
||
<div class="cmd idle">
|
||
<div class="l"><b>Décidé 0 W</b><i>une seule décision</i></div>
|
||
</div>
|
||
<div class="lvls">
|
||
<div class="lvl mute">
|
||
<span class="tag cft">CONFORT</span>
|
||
<div class="bd">
|
||
<b>Pas demandeur</b>
|
||
<span>22,4 °C mesurés ≥ 22 °C de consigne — le bonus n'est plus utile.</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="lvl mute" style="padding-left:14px;border-top:1px dashed var(--line)">
|
||
<div class="bd"><span>Aucune obligation éco déclarée : cette charge ne participe qu'à la passe 2 —
|
||
c'est exactement le comportement d'aujourd'hui.</span></div>
|
||
</div>
|
||
<button class="cfg">⚙ Configurer</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="cap">Écran 1 — <b>liste au rang</b>, une entrée par charge</div>
|
||
<div class="note">
|
||
<b>Ce qui ne change pas :</b> le glisser-déposer produit toujours une suite d'entiers <code>priority</code> à plat,
|
||
et il n'existe qu'un rang. La maquette est un <b>rejet appliqué</b> de <code>prioritéEco</code>/<code>prioritéConfort</code>.
|
||
<ul>
|
||
<li>Les deux lignes de niveau vivent <b>sous</b> la ligne de commande, décalées et rattachées par un filet :
|
||
la commande est le fait, les niveaux en sont la décomposition. C'est ce qui rend LM-1003 lisible sans
|
||
l'écrire.</li>
|
||
<li><b>L'écart affiché est décidé ↔ appliqué</b>, jamais <code>Σ targetW</code> ↔ <code>allocatedW</code> :
|
||
ces deux-là sont <b>égaux par construction</b>, tous deux du décidé. Un écran bâti sur la prémisse
|
||
inverse comparerait deux valeurs toujours égales — il ne montrerait jamais rien, <b>et sans
|
||
erreur</b>. L'appliqué se lit dans la charge utile de mécanisme (<code>stageW</code>,
|
||
<code>setpointW</code>, <code>currentA</code>), qui existe déjà aujourd'hui.</li>
|
||
<li><b>Et il ne se répartit pas non plus.</b> L'écart appartient à la charge, pas à un niveau :
|
||
l'adaptateur arrondit une consigne unique, il ne sait pas de quel besoin elle venait.</li>
|
||
<li><b>L'heure d'une échéance est celle de la BOX</b>, pas celle du client :
|
||
<code>dailyDeadline</code> est un <code>"HH:MM"</code> sans fuseau, interprété dans celui de
|
||
la box. Mesuré le 2026-09-04 — <code>.75</code> est en <code>Europe/London</code>, donc
|
||
« 12:00 » vaut 13:00 sur une horloge française. Le fuseau se lit par
|
||
<code>System.GetTime</code> : l'écran l'affiche à côté du champ et signale la divergence,
|
||
plutôt que de choisir une horloge à la place de l'installateur.</li>
|
||
<li><b>Sous <code>unmeasurable</code>, le plancher ne peut pas se refermer</b> — mesuré le
|
||
2026-09-01 sur quatre cycles : <code>remainingWh</code> figé à 4 000, <code>remainingS</code> qui
|
||
descend, <code>gridW</code> qui monte 755 → 767. Ce n'est pas un cas limite, c'est la
|
||
<b>trajectoire garantie</b> du régime : l'écran doit montrer <em>reste</em> et <em>temps</em>, pas
|
||
seulement les watts de l'instant, sans quoi « 767 W achetés » ne laisse pas deviner le mur.</li>
|
||
<li><b>* « Commandé, pas encore mesuré » — et ce n'est pas une destination.</b> Le champ
|
||
<code>budget.evReservedW</code> est un <b>terme de correction</b> : la latence entre l'ordre et sa
|
||
lecture au compteur. Il est affiché parce que la box le publie, <b>jamais sommé</b>. Son ancien
|
||
libellé, « Réservé au véhicule », affirmait deux faussetés — que c'est réservé, et que ça regarde le
|
||
véhicule plutôt que la latence de sa mesure ; réécrit le 2026-08-30 sur la définition livrée par le
|
||
moteur. Les watts achetés, eux, n'ont de registre qu'avec <code>draw.committedW</code>.</li>
|
||
<li>Le bandeau ◆ n'apparaît <b>que si au moins une charge porte une obligation éco</b>. Sans obligation,
|
||
il n'y a qu'une passe utile et la phrase serait du bruit (LM-1007).</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ ÉCRAN 2 — l'ordre de service ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>Ordre de service</h2><div class="ic">⟳</div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="hint">
|
||
<b>Ce que le moteur a fait, dans l'ordre</b>
|
||
Cycle de 06:41 · lecture seule. Une charge apparaît <em>deux fois</em> — une par passe : ici l'objet
|
||
affiché est la <b>séquence</b>, pas la charge. Votre rang décide de l'ordre <em>à l'intérieur</em> de
|
||
chaque passe, jamais entre les deux.
|
||
</div>
|
||
|
||
<div class="pass p1">
|
||
<div class="ph"><span class="n">1</span><b>PLANCHERS ÉCO</b><i>4 000 W autorisés au réseau</i></div>
|
||
|
||
<div class="srv g">
|
||
<span class="rk">1</span>
|
||
<div class="bd"><b>Chauffe-eau</b><span>2,1 / 3,0 kWh · au réseau</span></div>
|
||
<div class="w">1 400 W<u>reste 2 600</u></div>
|
||
</div>
|
||
|
||
<div class="srv nil">
|
||
<span class="rk">2</span>
|
||
<div class="bd"><b>PAC banc</b><span>Plancher déjà atteint — rien à servir</span></div>
|
||
<div class="w">0 W<u>reste 2 600</u></div>
|
||
</div>
|
||
|
||
<div class="srv g">
|
||
<span class="rk">3</span>
|
||
<div class="bd"><b>Borne sans compteur de session</b><span>Avancement non mesurable · au réseau</span></div>
|
||
<div class="w">2 600 W<u>reste 0</u></div>
|
||
</div>
|
||
|
||
<div class="exh"><b>⚠ Autorisation de soutirage épuisée ici</b> — plafond du gestionnaire de réseau
|
||
(4 000 W). Les charges suivantes ne verront pas leur plancher ce cycle.</div>
|
||
|
||
<div class="srv cut">
|
||
<span class="rk">4</span>
|
||
<div class="bd"><b>Plancher chauffant</b><span>0,4 / 2,0 kWh · obligation reportée</span></div>
|
||
<div class="w">refusé<u>plafond GRD</u></div>
|
||
</div>
|
||
|
||
<div class="srv nil">
|
||
<span class="rk">5</span>
|
||
<div class="bd"><b>Sèche-serviettes</b><span>Aucune obligation déclarée</span></div>
|
||
<div class="w">—<u>hors passe 1</u></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rule">puis, sur le reliquat, et seulement là</div>
|
||
|
||
<div class="pass p2">
|
||
<div class="ph"><span class="n">2</span><b>CONFORT</b><i>0 W de surplus</i></div>
|
||
|
||
<div class="exh" style="border-color:rgba(255,255,255,.18)">
|
||
<b style="color:var(--muted)">Aucun surplus ce cycle</b> — la passe 2 a été parcourue en entier et n'a
|
||
rien pu servir. Ce n'est pas un refus par charge : c'est une passe sans budget.
|
||
</div>
|
||
|
||
<div class="srv nil"><span class="rk">1</span>
|
||
<div class="bd"><b>Chauffe-eau</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||
<div class="srv nil"><span class="rk">2</span>
|
||
<div class="bd"><b>PAC banc</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||
<div class="srv nil"><span class="rk">3</span>
|
||
<div class="bd"><b>Borne sans compteur de session</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||
<div class="srv nil"><span class="rk">4</span>
|
||
<div class="bd"><b>Plancher chauffant</b><span>Demandeur · en attente</span></div><div class="w">0 W<u>reste 0</u></div></div>
|
||
<div class="srv nil"><span class="rk">5</span>
|
||
<div class="bd"><b>Sèche-serviettes</b><span>Pas demandeur — 22,4 °C ≥ 22 °C</span></div>
|
||
<div class="w">—<u>sauté</u></div></div>
|
||
</div>
|
||
|
||
<div class="hint" style="margin-top:20px;border-color:rgba(157,140,224,.3);background:rgba(157,140,224,.07)">
|
||
<b style="color:var(--violet)">Pourquoi le rang 4 passe avant le rang 1</b>
|
||
Le rang classe les <em>charges</em>. La hiérarchie éco/confort classe les <em>besoins</em>, et elle
|
||
surplombe le rang : un plancher est une obligation, un confort est un bonus. Aucune charge ne double
|
||
une autre <em>à niveau égal</em> — et c'est exactement ce que cet écran donne à vérifier, ligne à ligne.
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="cap">Écran 2 — <b>séquence</b>, une charge deux fois</div>
|
||
<div class="note">
|
||
<b>Écran dérivé, sans champ nouveau.</b> La séquence se reconstruit entièrement côté app par un tri
|
||
lexicographique sur <code>(niveau, priority)</code> : <b>ne publiez pas d'index d'ordre</b>, il serait une
|
||
seconde source de vérité pour une donnée déjà déductible.
|
||
<ul>
|
||
<li>Les compteurs « reste N » de chaque ligne sont un <b>cumul local</b> — ils exigent seulement que
|
||
chaque niveau publie ce qu'il a pris <em>et</em> son financement.</li>
|
||
<li>Le point de rupture (« autorisation épuisée ici ») est <b>lu</b>, jamais déduit d'un solde à zéro :
|
||
il vient du motif <code>DRAW_CAP</code> de la première charge refusée, avec sa <b>source</b>.</li>
|
||
<li>Cet écran s'ouvre depuis le bandeau ◆ et depuis chaque carte ; il n'a aucun réglage, donc aucun
|
||
risque de devenir un second endroit où l'on classe.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ ÉCRAN 3 — catalogue d'états ══════════ -->
|
||
<div class="col">
|
||
<div class="phone">
|
||
<div class="appbar"><div class="ic">‹</div><h2>États d'un niveau</h2><div class="ic">?</div></div>
|
||
<div class="scroll">
|
||
|
||
<div class="hint">
|
||
<b>Le vocabulaire, et il est fermé</b>
|
||
Chaque niveau d'une charge est <em>toujours</em> dans un de ces états, et chacun est un état
|
||
<b>positif</b> — jamais « à moitié servie », jamais un pourcentage d'avancement.
|
||
</div>
|
||
|
||
<div class="sect">Niveau ÉCO <small>· présent seulement si la charge porte une obligation</small></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Plancher servi</b><code>ECO_FLOOR_*</code></div>
|
||
<div class="txt">« Plancher servi — 1 400 W pris sur le surplus »<br>
|
||
« Plancher servi — 1 400 W achetés au réseau »</div>
|
||
<div class="why">Deux phrases, parce que ce sont deux faits différents pour le client : l'un est gratuit,
|
||
l'autre est une facture. Le financement se lit sur le <b>niveau</b>, pas sur la charge.</div></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Plancher atteint</b><code>ECO_FLOOR_MET</code></div>
|
||
<div class="txt">« Plancher atteint — 3,0 / 3,0 kWh livrés »</div>
|
||
<div class="why">Le seul état qui <b>ferme</b> l'obligation. Interdit sous le régime « non mesurable » :
|
||
on ne peut pas annoncer tenu ce qu'on ne mesure pas (LM-1009 C1).</div></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Non servi — plafond</b><code>DRAW_CAP</code></div>
|
||
<div class="txt">« Plafond de soutirage atteint — <em>gestionnaire de réseau</em> »<br>
|
||
« … — <em>protection du branchement</em> » · « … — <em>limite que vous vous imposez</em> »</div>
|
||
<div class="why">Trois sources, trois phrases, trois gestes : subir et se renseigner, délester ou revoir
|
||
l'abonnement, ou changer un réglage. Un motif qui les confond envoie diagnostiquer le mauvais
|
||
problème.</div>
|
||
<div class="why" style="border-top:1px dashed rgba(255,255,255,.1);padding-top:5px;margin-top:6px">
|
||
<b style="color:var(--yellow)">Sa frontière</b> : <code>DRAW_CAP</code> ne sort que si
|
||
l'autorisation résiduelle est <b>nulle</b>. S'il en reste mais sous le plus petit palier, le motif
|
||
est <code>BELOW_MIN_POWER</code> — il y en a, pas au bon format. Deux gestes : revoir le plafond,
|
||
ou lire la fiche technique.</div></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag eco">ÉCO</span><b>Avancement non mesurable</b><code>regime</code></div>
|
||
<div class="txt">« Servi · avancement non mesurable » — barre <em>hachurée</em>, aucun chiffre livré.</div>
|
||
<div class="why">Un régime, pas un motif : les trois régimes coexistent dans le même cycle sur des
|
||
charges différentes (LM-1009 C2). <b>Zéro serait un mensonge</b> ; l'absence est la vérité.
|
||
<br><b style="color:var(--green)">Corrigé le 2026-08-30</b> : l'exemple ne nomme plus la V2C
|
||
Trydan — elle <b>publie</b> `sessionEnergy` depuis le plugin `+etm13`, vérifié sur charge réelle
|
||
(15,80 → 15,84 kWh en mouvement). Le régime « non mesurable » reste vrai pour les bornes qui n'ont
|
||
pas cet état.</div></div>
|
||
|
||
<div class="sect">Niveau CONFORT <small>· toujours présent</small></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag cft">CONFORT</span><b>Servi</b><code>SURPLUS_SETPOINT</code></div>
|
||
<div class="txt">« Bonus servi — 2 100 W de surplus »</div>
|
||
<div class="why">L'état d'aujourd'hui, inchangé. Une charge sans obligation éco n'a jamais que cette
|
||
ligne : le modèle à deux niveaux dégrade proprement vers l'actuel.</div></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag cft">CONFORT</span><b>En attente</b><code>SURPLUS_INSUFFICIENT</code></div>
|
||
<div class="txt">« En attente — aucun surplus ce cycle »<br>« En attente — reliquat épuisé au rang 2 »</div>
|
||
<div class="why">La charge <em>veut</em> son bonus et ne l'a pas. Un état de file d'attente, pas un
|
||
échec — et surtout pas « 0 W » tout seul, qui ne dit pas s'il y avait une demande.</div></div>
|
||
|
||
<div class="cat"><div class="h"><span class="tag cft">CONFORT</span><b>Pas demandeur</b><code>COMFORT_SKIPPED</code></div>
|
||
<div class="txt">« Pas demandeur — 22,4 °C ≥ 22 °C de consigne »</div>
|
||
<div class="why">Rien ne manque, rien n'attend : le comparateur de LM-1008 est satisfait. <b>À ne jamais
|
||
confondre avec « en attente »</b> — l'un se règle en changeant la consigne, l'autre en attendant le
|
||
soleil.</div></div>
|
||
|
||
<div class="cat" style="border-color:rgba(49,163,221,.3);background:rgba(49,163,221,.05)">
|
||
<div class="h"><span class="tag" style="background:rgba(49,163,221,.16);color:var(--blue)">×3</span>
|
||
<b>Trois nombres, et jamais le même sens</b></div>
|
||
<div class="txt">
|
||
<b>Décidé</b> <code>allocatedW</code> — ce que l'arbitre a planifié, et ce que les compteurs de
|
||
budget comptent.<br>
|
||
<b>Appliqué</b> <code>mechanism.*</code> — ce que l'adaptateur a su produire.<br>
|
||
<b>Mesuré</b> <code>measurement.powerW</code> — ce que la charge tire, quand c'est mesurable.
|
||
</div>
|
||
<div class="why">Chaque écart dit autre chose : <b>décidé ↔ appliqué</b> = l'arrondi du mécanisme,
|
||
normal et permanent ; <b>appliqué ↔ mesuré</b> = la charge n'obéit pas (et ce que ça prouve dépend
|
||
de <code>measurement.source</code>) ; <b>décidé ↔ mesuré</b> = la somme des deux, à ne jamais lire
|
||
seule — elle mélange une cause bénigne et une cause qui ne l'est pas.</div>
|
||
</div>
|
||
|
||
<div class="never">
|
||
<b>✖ Ce que l'écran n'affichera jamais</b>
|
||
<ul>
|
||
<li>Un <b>pourcentage</b> d'avancement d'obligation — l'énergie livrée, ou rien (LM-1009 D1).</li>
|
||
<li>Une charge <b>« servie à moitié »</b> : deux niveaux, deux états nommés.</li>
|
||
<li><b>Deux consignes</b> pour une charge : les deux lignes décomposent une commande unique.</li>
|
||
<li>Un <b>zéro</b> à la place d'une mesure absente.</li>
|
||
<li>Un <b>second classement</b> à régler.</li>
|
||
<li>Un écart entre <b><code>Σ targetW</code> et <code>allocatedW</code></b> : ils sont égaux par
|
||
construction. Cherché là, l'arrondi est invisible et l'écran se tait sans se tromper.</li>
|
||
<li>Une <b>exception de réconciliation</b> : avec <code>counts</code>, il n'y en a plus une seule.
|
||
Un écart qui subsiste est un défaut, et il se dit.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="cap">Écran 3 — <b>catalogue</b> des états de niveau</div>
|
||
<div class="note">
|
||
<b>Pourquoi un catalogue plutôt qu'une échelle :</b> « éco servi / confort non » est le cas le plus fréquent
|
||
en régime normal. S'il s'affiche comme une <em>fraction</em> de quelque chose, il se lira comme une
|
||
dégradation permanente. Nommé, il se lit comme le fonctionnement nominal — <b>le plancher est tenu, le bonus
|
||
attend le soleil</b>.
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ══════════════════ BRIEF MOTEUR ══════════════════ -->
|
||
<div class="brief">
|
||
<h2>Ce que ces écrans demandent à <code>GetLoadTelemetry</code></h2>
|
||
<p>Aucun de ces trois écrans n'est constructible sur la charge utile d'aujourd'hui : un <code>allocatedW</code> et
|
||
un <code>decision</code> par charge, un seul de chaque. Voici la forme minimale qui les rend possibles — elle
|
||
ajoute <b>un tableau</b> et ne retire rien.</p>
|
||
|
||
<pre>{
|
||
<span class="k">"loadId"</span>: "…", <span class="k">"available"</span>: true, <span class="k">"measurement"</span>: { … }, <span class="c">// inchangés</span>
|
||
|
||
<span class="k">"allocatedW"</span>: <span class="n">1400</span>, <span class="c">// INCHANGÉ — le DÉCIDÉ du cycle. Σ targetW == allocatedW, exactement.</span>
|
||
<span class="c">// L'appliqué vit dans "mechanism" ; le mesuré dans "measurement".</span>
|
||
|
||
<span class="k">"levels"</span>: [ <span class="c">// AJOUT — un élément par passe RÉELLEMENT parcourue</span>
|
||
{ <span class="k">"level"</span>: <span class="s">"eco"</span>, <span class="c">// RELEVÉ sur `.75` le 2026-09-01, +etm43</span>
|
||
<span class="k">"targetW"</span>: <span class="n">766.813994355397</span>, <span class="c">// le décidé, NON arrondi</span>
|
||
<span class="k">"funding"</span>: <span class="s">"grid"</span>,
|
||
<span class="k">"counts"</span>: { <span class="k">"draw.committedW"</span>: <span class="n">766.813994355397</span> },
|
||
<span class="k">"progress"</span>: { <span class="k">"regime"</span>: <span class="s">"unmeasurable"</span>, <span class="k">"targetWh"</span>: <span class="n">4000</span> },
|
||
<span class="c">// deliveredWh ABSENT sous unmeasurable — jamais 0</span>
|
||
<span class="k">"decision"</span>: { <span class="k">"code"</span>: <span class="s">"ECO_FLOOR_GRID"</span>, <span class="k">"params"</span>: {
|
||
<span class="k">"gridW"</span>: <span class="n">767</span>, <span class="k">"remainingWh"</span>: <span class="n">4000</span>, <span class="k">"targetWh"</span>: <span class="n">4000</span>,
|
||
<span class="k">"remainingS"</span>: <span class="n">18779</span>, <span class="k">"regime"</span>: <span class="s">"unmeasurable"</span>, <span class="k">"atMaximum"</span>: false } } },
|
||
{ <span class="k">"level"</span>: <span class="s">"comfort"</span>,
|
||
<span class="k">"targetW"</span>: <span class="n">0</span>,
|
||
<span class="k">"decision"</span>: { <span class="k">"code"</span>: <span class="s">"SURPLUS_INSUFFICIENT"</span>, <span class="k">"params"</span>: { <span class="k">"requiredW"</span>: <span class="n">900</span>, <span class="k">"availableW"</span>: <span class="n">0</span> } } }
|
||
],
|
||
|
||
<span class="k">"decision"</span>: { <span class="k">"level"</span>: <span class="s">"eco"</span>, <span class="k">"code"</span>: <span class="s">"ECO_FLOOR_GRID"</span>, <span class="k">"params"</span>: { … } } <span class="c">// CONSERVÉ + le niveau reflété</span>
|
||
}
|
||
|
||
<span class="c">// et, une fois par cycle, à côté de "budget" :</span>
|
||
<span class="k">"draw"</span>: { <span class="k">"authorisedW"</span>: <span class="n">4000</span>, <span class="k">"committedW"</span>: <span class="n">4000</span>, <span class="k">"remainingW"</span>: <span class="n">0</span>,
|
||
<span class="k">"binding"</span>: <span class="s">"gridOperator"</span>, <span class="k">"perPhaseBound"</span>: false }
|
||
|
||
<span class="c">// ⚠ committedW part SEUL, et AVANT counts (2026-08-30) : c'est le registre des watts</span>
|
||
<span class="c">// achetés, et il n'en existe aucun aujourd'hui. draw: {"committedW": 1400} tout court</span>
|
||
<span class="c">// est la bonne première livraison — les quatre autres champs ABSENTS, jamais à 0 :</span>
|
||
<span class="c">// un authorisedW forgé afficherait un plafond de soutirage qui n'existe pas.</span></pre>
|
||
|
||
<h3>Huit exigences, et chacune tient un endroit précis de la maquette</h3>
|
||
|
||
<div class="req"><b><u>R1</u>`levels[]` ne contient que les passes réellement parcourues</b>
|
||
<p>Une charge sans obligation éco porte <b>une seule entrée</b>, <code>comfort</code> — pas une entrée
|
||
<code>eco</code> à zéro. L'absence est alors l'information (« ne participe qu'à la passe 2 », LM-1007), et un
|
||
zéro forgé la détruirait. <code>levels[]</code> entièrement absent = box antérieure : l'app retombe sur
|
||
<code>allocatedW</code>/<code>decision</code> et n'affiche ni bandeau ◆ ni écran 2.</p></div>
|
||
|
||
<div class="req hard"><b><u>R2</u>`funding` descend au niveau — <span style="color:var(--green)">✔ EN COURS côté moteur</span></b>
|
||
<p>Aujourd'hui il est <em>par charge</em>. Au §10, la même charge peut être financée <b>au réseau pour son éco
|
||
et au surplus pour son confort dans le même cycle</b> : un <code>funding</code> par charge devient
|
||
indécidable. Or c'est lui qui porte notre identité de réconciliation (« somme des allocations financées au
|
||
surplus == <code>budget.allocatedW</code> »). S'il reste à la charge, cette identité cesse d'être vérifiable
|
||
sans que rien ne le signale.</p>
|
||
<p><b>Si vous hiérarchisez, prenez celle-ci en premier</b> : c'est la seule des huit qui soit un
|
||
<b>changement de forme d'un champ existant</b> et non un ajout. Chaque écran qui s'appuiera d'ici là sur
|
||
un <code>funding</code> par charge en augmentera le coût de déplacement — les sept autres attendent sans
|
||
se renchérir.</p>
|
||
<p><span style="color:var(--green)"><b>Parti le 2026-08-29</b></span> — l'argument de coût a emporté la
|
||
priorisation. C'est la première des huit à être écrite.</p></div>
|
||
|
||
<div class="req"><b><u>R3</u>Chaque watt tombe dans un compteur nommé — <span style="color:var(--green)">✔ résolu par `counts`</span></b>
|
||
<p><b>La forme du moteur est prise telle quelle</b> (2026-08-30) : à côté de <code>funding</code>, un
|
||
<code>counts</code> qui nomme les registres, avec <code>Σ counts == targetW</code> <b>toujours</b>, publié
|
||
même à une seule destination. L'identité devient calculable <b>sans exception</b> et <b>par niveau</b> :
|
||
l'exception <code>EV_GRID_START</code>, qui partageait une allocation entre deux compteurs, disparaît —
|
||
remplacée par une règle vérifiable qui vaudra telle quelle quand <code>draw.committedW</code> arrivera.</p>
|
||
<p><b>Les deux champs ne se dérivent pas l'un de l'autre</b>, et c'est ce qui empêchera qu'on en supprime un :
|
||
<code>funding</code> dit <b>l'origine</b> (« ces watts sont achetés » — voisin du motif), <code>counts</code>
|
||
dit <b>le registre</b> (« ils sont comptés ici » — une comptabilité).</p>
|
||
<p><b>Sur un niveau mixte, un code unique : <code>"grid"</code></b> — et la règle demandée n'est pas « une
|
||
approximation » mais <b>« grid l'emporte dès qu'un watt est acheté »</b>. Déterministe, indépendante des
|
||
proportions (un libellé qui bascule sur 900/480 serait pire que faux), et alignée sur ce qu'un humain doit
|
||
savoir : l'achat est le fait actionnable. Elle vaudra sans nouvelle décision pour le plancher éco du §10.
|
||
<b>Ni omission</b> (notre <code>isSurplusFunded</code> range alors le niveau au soleil, en silence)
|
||
<b>ni <code>"mixed"</code> publié</b> : une troisième valeur tomberait dans la branche par défaut de tout
|
||
client — « surplus » — donc reproduirait la panne de l'omission, et elle serait <b>dérivable de
|
||
<code>counts</code></b>, donc supprimée un jour à juste titre.</p>
|
||
<p><b>Et il a un préalable, trouvé en le câblant (2026-08-30) : <code>draw.committedW</code>.</b>
|
||
<code>budget.evReservedW</code> n'est pas un registre d'allocation mais un <b>terme de correction</b> du
|
||
budget — une borne servie au réseau qui tire déjà ce qu'on lui a commandé y contribue <b>0</b> pour un
|
||
<code>targetW</code> de 7 000 W. <code>Σ counts == targetW</code> y serait vraie au premier cycle après un
|
||
changement et fausse ensuite : une identité <b>dépendante du temps</b>, qui passe tous les tests et
|
||
certifie un total faux. <b>Les watts achetés n'ont aucun registre aujourd'hui</b> — d'où une seule identité
|
||
vérifiable, du côté surplus, et rien de l'autre côté tant que <code>committedW</code> n'est pas publié.</p>
|
||
<p><b>Ce que l'app promet en échange</b> : les clés de <code>counts</code> sont traitées comme des
|
||
<b>identifiants opaques</b>, jamais comme des chemins. Une clé inconnue d'une version ancienne n'est pas
|
||
perdue : l'identité <code>Σ counts == targetW</code> reste vérifiable sans connaître le sens des clés, et
|
||
l'écran dit « destination inconnue de cette version », <b>jamais zéro</b>. Corollaire pour le moteur : ne
|
||
jamais réutiliser une clé existante pour un sens nouveau — c'est le seul cas qu'aucun client ne peut
|
||
détecter.</p></div>
|
||
|
||
<div class="req"><b><u>R4</u>`Σ targetW == allocatedW` — une identité, pas un écart <span style="color:var(--green)">✔ tranché</span></b>
|
||
<p><b>Corrigé le 2026-08-29 : notre prémisse était fausse.</b> <code>allocatedW</code> publie le
|
||
<b>décidé</b>, pas le commandé — <code>buildTelemetry()</code> publie le plan, <code>applyActionsToAdapters()</code>
|
||
reçoit le slot en <code>const</code>, et un test attend <code>allocatedW == 3000</code> sur une borne dont
|
||
le courant commandé donne 2 990 W. Les niveaux et <code>allocatedW</code> sont donc <b>la même monnaie</b>,
|
||
et leur somme est une <b>identité exacte</b> — c'est ce qui rend <code>levels[]</code> vérifiable contre un
|
||
champ qui existe déjà. L'app traitera toute violation comme une incohérence de la box, pas comme un
|
||
arrondi.</p>
|
||
<p><b>Ce qui reste demandé</b> : ne pas attribuer l'appliqué à un niveau. L'écart vit entre
|
||
<code>allocatedW</code> et la charge utile de mécanisme (<code>stageW</code>, <code>setpointW</code>,
|
||
<code>currentA</code>) ; l'adaptateur arrondit une consigne unique et ne sait pas de quel besoin elle
|
||
venait. Toute répartition serait une fiction — le principe n'a pas bougé, seulement l'endroit où l'écart
|
||
se trouve.</p>
|
||
<p><b>Et les compteurs de budget comptent le décidé</b>, parce que c'est la valeur qui détermine ce que
|
||
reçoit la charge suivante. Conséquence d'écran, assumée : le budget d'un cycle peut annoncer 1 400 W
|
||
pendant que l'installation en tire 1 000 — d'où le bloc « trois nombres » de l'écran 3, qui empêche de
|
||
lire cet écart comme une erreur de comptage.</p></div>
|
||
|
||
<div class="req"><b><u>R5</u>Le régime est porté par le NIVEAU — <span style="color:var(--green)">✔ en ligne</span></b>
|
||
<p><b>Vérifié à la sonde le 2026-09-01</b> : <code>progress</code> et <code>decision</code> descendent au
|
||
niveau, et <code>deliveredWh</code> est bien <b>absent</b> sous <code>unmeasurable</code>.</p>
|
||
<p><b>Et le motif publie <code>remainingWh</code>, pas <code>deliveredWh</code> — accepté.</b> Ce n'est pas
|
||
« une source par fait » qui tranche (on aurait pu retirer <code>deliveredWh</code> de <code>progress</code>)
|
||
mais ceci : <b><code>remainingWh</code> n'est pas dérivable sous <code>unmeasurable</code></b>, et c'est
|
||
exactement le régime où l'on achète. Un champ qui n'existe que là où on peut le calculer ferait taire
|
||
l'écran dans le cas qui coûte de l'argent. Sous <code>unmeasurable</code>,
|
||
<code>remainingWh == targetWh</code> : « 4 000 sur 4 000 » se lit <b>« rien de mesurable »</b>, jamais
|
||
« rien livré ».</p>
|
||
<p><code>progress.regime ∈ measured | estimated | unmeasurable</code>, et <code>deliveredWh</code>
|
||
<b>absent</b> sous <code>unmeasurable</code> — jamais 0. C'est la même règle que
|
||
<code>measurement.source = "none"</code>, qui nous a déjà servis : un régime publié sépare « pas mesurable »
|
||
de « rien reçu », et c'est lui qui autorise la barre hachurée plutôt qu'une barre vide.</p></div>
|
||
|
||
<div class="req hard"><b><u>R6</u>`ECO_FLOOR_MET` interdit sous le régime `unmeasurable`</b>
|
||
<p>Une obligation non mesurable peut être <b>servie</b>, jamais annoncée <b>tenue</b> (LM-1009 C1). C'est une
|
||
garde testable côté moteur — et côté app, la combinaison sera traitée comme une <b>incohérence de la box</b>,
|
||
affichée comme telle, pas rendue. Le cas est réel dès aujourd'hui : la V2C Trydan ne publie pas
|
||
<code>sessionEnergy</code>.</p></div>
|
||
|
||
<div class="req"><b><u>R7</u>`decision` legacy conservé, avec le niveau qu'il reflète</b>
|
||
<p>Ajoutez <code>decision.level</code>. Sans lui, chaque client réinvente sa propre règle de fusion (« le
|
||
motif du dernier niveau servi ? du premier refusé ? ») et deux écrans affichent deux motifs différents pour le
|
||
même cycle. Une clé de plus, et la règle cesse d'être devinée.</p></div>
|
||
|
||
<div class="req"><b><u>R8</u>`DRAW_CAP` publie sa source, et l'app en fait trois phrases</b>
|
||
<p>Déjà prévu au <code>DESIGN_10.md</code> §6, on le confirme comme <b>condition d'écran</b> : c'est la source
|
||
qui décide du texte, et les trois textes appellent trois gestes différents. Le point de rupture de l'écran 2
|
||
est lu sur ce motif, jamais déduit d'un solde tombé à zéro.</p></div>
|
||
|
||
<h3>Ce dont nous n'avons PAS besoin — et qu'il ne faut pas fabriquer</h3>
|
||
<ul class="no">
|
||
<li>Un <b>index d'ordre de service</b> (<code>servedIndex</code>, <code>order</code>…) : la séquence de
|
||
l'écran 2 se dérive entièrement d'un tri lexicographique sur <code>(niveau, priority)</code>. Un index
|
||
publié serait une seconde source de vérité pour une donnée déductible — et diverger un jour.</li>
|
||
<li>Un <b>second rang</b>, sous quelque nom que ce soit. La maquette est construite pour rendre le rang unique
|
||
lisible <em>malgré</em> les deux passes ; elle n'a de sens que si LM-1002 tient.</li>
|
||
<li>Une <b>phrase</b> composée par la box : les motifs restent des codes à catalogue fermé avec paramètres
|
||
numériques, le rendu reste dans <code>services/telemetry_text.dart</code>.</li>
|
||
<li>Un <b>pourcentage</b> d'avancement, même « à titre indicatif ». D1 est appliqué sans exception.</li>
|
||
</ul>
|
||
|
||
<h3>La précédence des motifs — <b>tranchée</b> (Patrick, 2026-08-29)</h3>
|
||
<p>Autorisation résiduelle <b>non nulle mais inférieure au plus petit palier</b> du mécanisme :
|
||
<code>BELOW_MIN_POWER</code>. Les deux motifs sont vrais, mais pas au même niveau —
|
||
<code>DRAW_CAP</code> dit <em>« il n'y a plus d'autorisation »</em>, or ici il en reste, simplement
|
||
<b>pas au bon format</b>. C'est exactement la distinction posée en <code>+etm19</code> entre
|
||
<code>SURPLUS_INSUFFICIENT</code> (« il n'y a rien ») et <code>BELOW_MIN_POWER</code> (« il y en a,
|
||
mais pas au bon format »), transposée telle quelle au soutirage. Le geste utile est de lire la fiche
|
||
technique du mécanisme, pas de revoir le plafond.</p>
|
||
<p><code>DRAW_CAP</code> reste réservé à <b>l'autorisation résiduelle nulle</b>. Le point de rupture de
|
||
l'écran 2 (« autorisation épuisée ici ») est donc bien un <code>DRAW_CAP</code> ; une charge refusée
|
||
<em>après</em> lui pour cause de palier porterait <code>BELOW_MIN_POWER</code> et ne déplacerait pas le
|
||
point de rupture. La règle est écrite par le moteur, pas établie de chaque côté.</p>
|
||
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|