docs(prévision): §32 consommation de base et prix ; protocole §5.2 (tou, quality)
Design §32 : grille, mesure à la minute, profil, qualité, tarif mural, règle du créneau à cheval (moyenne pondérée) et ce qui reste pour e1. Protocole 1.1, correction avant publication : price.source hphc → tou (jamais émis), baseload.quality optionnel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bb34c6ed5e
commit
04fea6ffda
@ -1598,3 +1598,84 @@ S'y ajoutent les sondages en échec (pas de nouvel essai avant l'heure suivante)
|
||||
redémarrages. La vraie valeur dépend du rythme réel de mise à jour du produit satellite, qui n'est
|
||||
pas mesuré ici. Elle se lira sur le banc : la part des lignes de jour dont `gtiObserved` est NULL.
|
||||
**Rien n'est comblé côté `ems`** : la correction est de publier la série, ce sera openmeteo etm16.
|
||||
|
||||
## 32. Consommation de base et prix sur la grille — brief 3 T4 (2026-10-14)
|
||||
|
||||
*État du code. Rien de ce qui précède n'est réécrit. Aucune décision ne lit ce qui suit (§11) :
|
||||
`SurplusContext::tariff` n'est pas rempli, la base n'entre dans aucun contexte. Épinglé par
|
||||
`testNoDecisionReadsBaseloadOrPrice` (même cycle, mêmes entrées, même plan, avec et sans tarif ni
|
||||
profil). Contrat : `INTERFACE.md`, « Tarif saisi » et `GetForecastSnapshot`. Code :
|
||||
`etm/tariff/tariffconfig.{h,cpp}`, `etm/forecast/baseloadlog.{h,cpp}`.*
|
||||
|
||||
### 32.1 La grille est celle de `pv` — et elle existe sans `pv`
|
||||
|
||||
`GetForecastSnapshot` publie désormais `start`, `resolutionS`, `steps` (protocole §5.2), et sur
|
||||
cette grille `baseload` et `price`, alignés index par index. Avec une courbe, c'est **sa** grille
|
||||
(premier → dernier créneau, trous compris). **Sans courbe, la grille par défaut du producteur**
|
||||
(horaire, minuit local → minuit local du surlendemain) : un site non désigné ne doit pas faire
|
||||
disparaître un tarif saisi. Le manque « le pas n'est publié par aucune méthode » est fermé pour
|
||||
le snapshot.
|
||||
|
||||
### 32.2 La base — mesurée à la minute, jamais sur les cumuls
|
||||
|
||||
- **Source** : l'échantillon d'**une minute** du bilan (`powerBalanceEntryAdded`, `SampleRate1Min`),
|
||||
ses **puissances moyennes** — `acquisition + |production| − storage`, la formule de l'amont pour
|
||||
`consumption`, réécrite depuis ses termes. **Jamais** `totalConsumption`/`totalProduction`/
|
||||
`totalAcquisition`/`totalReturn` : 63 117 kWh de production cumulée au bilan contre 4 914 kWh au
|
||||
SDM120 (relevé du 2026-10-13). ⚠️ Le terme `− storage` n'est pas dans la formule du brief
|
||||
(« import − export + production ») : sans batterie il vaut 0 ; avec, une charge de batterie
|
||||
n'est pas une consommation.
|
||||
- **Retranché** : chaque charge pilotée (tables de l'arbitre, **bornes comprises**) selon **sa**
|
||||
`measurement.source` — la même fonction que la télémétrie (`mesurerCharge()`, extraite pour
|
||||
qu'il n'y ait qu'une règle). Sous `none`, la **puissance commandée** (`puissanceCommandee()`,
|
||||
extraite de l'écart commandé/mesuré : la même règle). Une commande inconnue rend la minute
|
||||
**incomplète**, jamais comptée à 0.
|
||||
- **Pas de minute** sans compteur racine (import = zéro de construction) ni en mode dégradé L2
|
||||
(le compteur est muet).
|
||||
- **Négatif gardé** : une base < 0 dit que le compteur racine voit moins que les charges — le
|
||||
constat du banc (« ~1,5 kW de moins », `AGENTS.md`). L'écrêter cacherait ce que la qualité doit
|
||||
montrer.
|
||||
|
||||
**Stockage** — deux tables dédiées dans `energy-forecast.sqlite` : `baseloadMinute (at, siteW,
|
||||
loadsW, baseW, commandedLoads, writtenAt)` et `baseloadMinuteLoad (at, loadId, source, powerW)`.
|
||||
`forecastPv` et `forecastLearning` ne sont **pas touchées** (éprouvé sur le fichier etm67 réel :
|
||||
schéma et lignes identiques). Fil d'écriture dédié (sa connexion, `busy_timeout` 10 s), lots de
|
||||
5 minutes, lecture de la fenêtre **par journée** pour ne jamais tenir le verrou du fichier
|
||||
longtemps (le cycle y écrit les révisions). Rétention **35 jours** (fenêtre + une semaine). Forme
|
||||
inattendue d'une de NOS tables ⇒ refaite (aucun historique antérieur n'existe) ; fichier illisible
|
||||
⇒ hors service, journalisé une fois.
|
||||
|
||||
### 32.3 Le profil
|
||||
|
||||
Médiane, par case de la journée **locale** (largeur = pas de la grille) et par type de jour
|
||||
(ouvré / week-end, fériés inconnus), des **moyennes de case** couvertes à ≥ 90 %, sur **28 jours**.
|
||||
Une case sous **2** valeurs est `null`. **`none` sous 7 jours** couverts à ≥ 90 % de **leurs**
|
||||
minutes (1 380 / 1 440 / 1 500). Un créneau de grille prend la case de l'heure murale de son
|
||||
début (25 h : les deux « 02:00 » ont la même médiane ; 23 h : la case 02:00 n'est pas lue).
|
||||
Recalcul sur le fil au plus tous les quarts d'heure (temps des minutes) et à l'ouverture ; le
|
||||
snapshot lit la dernière copie, sous verrou bref, sans requête.
|
||||
|
||||
**La qualité dit ce que le modèle ne peut pas cacher** : minutes comptées à la **commande**,
|
||||
minutes **négatives**, minutes **écartées**, et par charge sa source et `constantMeasurement`
|
||||
(mesure qui n'a pas bougé d'un watt sur ≥ 60 minutes — la « constante déguisée » du §6 de
|
||||
`DESIGN_COMMANDE_PERDUE.md`). Sur le banc, au 2026-10-14 : chauffe-eau `none` (compté à sa
|
||||
commande), PAC sur `PAC-Meter` figé → `constantMeasurement: true`.
|
||||
|
||||
### 32.4 Le tarif — un réglage mural, rendu en instants
|
||||
|
||||
Saisi par l'installateur (décision du 2026-10-14) : **une** zone 00:00-24:00 (`fixed`) ou des zones
|
||||
horaires (`tou`), chacune `{label, priceCtkWh, ranges[]}`, plusieurs plages par zone. Validation
|
||||
**stricte** (`TariffConfig::fromVariant()`, la même pour le RPC et la relecture) : prix ≥ 0, heures
|
||||
`"HH:MM"`, couverture des 1 440 minutes **sans trou ni chevauchement**, refus codé et localisé
|
||||
(`TARIFF_REFUSED`, `cause` + index + minute). Persisté dans `energy.conf`.
|
||||
|
||||
**Créneau à cheval : moyenne pondérée par la durée — proposé et retenu.** Toutes les séries de la
|
||||
grille sont des moyennes sur `[t, t + pas)` ; pour une puissance constante sur le créneau, le coût
|
||||
est exactement `P × pas × moyenne pondérée`. C'est donc la seule valeur cohérente avec `pv` et
|
||||
`baseload`. Le découpage au pas changerait la grille, qui est une pour toutes les séries. Calcul :
|
||||
minute **absolue** par minute, chaque minute prend la zone de son heure murale — exact pour les
|
||||
jours de 23 et 25 h (oracles Python `zoneinfo` dans les tests).
|
||||
|
||||
**Pour e1 (plus tard, sans être lu maintenant)** : `TariffConfig::zoneAt(instant, fuseau)` rend la
|
||||
zone en vigueur ; « HC » n'a pas de code — le libellé est une donnée de l'installateur. Le lot e1
|
||||
devra décider d'une règle (par exemple : la ou les zones au prix le plus bas), pas la deviner ici.
|
||||
|
||||
@ -40,6 +40,10 @@ optimiseur 1.0 reste conforme (champs inconnus ignorés, §9).
|
||||
rév. 3, avant toute implémentation socket). Aucun consommateur déployé n'en dépend ; ils
|
||||
sont retirés du document sans changement de majeure.
|
||||
|
||||
**Correction avant publication (2026-10-14)** : `price.source` `hphc` devient `tou` (zones
|
||||
horaires saisies, N zones et plusieurs plages par zone — HP/HC en est un cas) ; `hphc` n'a
|
||||
jamais été émis. `baseload.quality` est ajouté (optionnel).
|
||||
|
||||
**Correction avant publication (2026-10-13)** : `conversion` n'admet plus `fallback`. La
|
||||
valeur avait été écrite sans définition ; **aucun émetteur ne l'a jamais produite**. Seule
|
||||
`physics` est émise ; une prévision dégradée se lit dans `corrections`.
|
||||
@ -266,9 +270,18 @@ Règles :
|
||||
depuis le compteur racine moins les charges, chacune selon `measurement.source` (§5.3) ;
|
||||
une charge en `none` est comptée à sa puissance **commandée**, et la qualité de
|
||||
l'estimation le dit.
|
||||
- `profile` (plugin GPL) : médiane par créneau local et par type de jour (ouvré / week-end)
|
||||
sur 28 jours, mesurée sur les échantillons d'une minute du bilan — jamais ses cumuls ;
|
||||
`none` sous 7 jours couverts. Forme et définition : `INTERFACE.md`, `GetForecastSnapshot`.
|
||||
- `quality` (1.1, optionnel) : codes et nombres — jours couverts, minutes comptées à la
|
||||
commande, minutes négatives, et par charge sa source et `constantMeasurement`.
|
||||
- **`price`** : **prix complet payé** — `includes` liste ce qui est inclus. Un prix n'est
|
||||
**pas une prévision** : au-delà de `knownUntil`, `null`. Sources : `fixed`, `hphc`,
|
||||
**pas une prévision** : au-delà de `knownUntil`, `null`. Sources : `fixed`, `tou`,
|
||||
`tempo`, `day-ahead`. `exportCtkWh` : tarif de revente ou 0.
|
||||
- `fixed` / `tou` : tarif **saisi** (une zone sur 24 h / zones horaires en heure locale) ;
|
||||
connu sur tout l'horizon, `knownUntil` = fin de la grille. Un créneau à cheval sur deux
|
||||
zones reçoit la **moyenne pondérée par la durée** — le coût exact d'une puissance
|
||||
constante sur le créneau, seule valeur cohérente avec les autres séries de la grille.
|
||||
- `forecast` peut être `null`. L'optimiseur reste libre de sourcer ses propres prévisions
|
||||
(pont LAN, côté optimiseur), mais **le plugin, lui, ne sort pas** pour les obtenir.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user