diff --git a/docs/DESIGN_PREVISION_PV.md b/docs/DESIGN_PREVISION_PV.md index baa0079..2790f47 100644 --- a/docs/DESIGN_PREVISION_PV.md +++ b/docs/DESIGN_PREVISION_PV.md @@ -381,3 +381,91 @@ faut que quelqu'un le vérifie à l'exécution, une fois, à l'entrée. `issuedAt` est **dans la charge utile**, pas déduit de l'instant de réception — sinon un redémarrage de `ems` réétiquette toute la série avec l'heure du redémarrage, et la table bitemporelle perd exactement ce qu'elle existe pour retenir. + + +--- + +## 13. La clé de révision — tranché le 2026-09-08 + +**Retenu : la déduplication sur le contenu, et la colonne s'appelle `receivedAt`.** +Pas de requête `meta.json`. Mais la décision n'est bonne qu'avec les trois conditions ci-dessous, +et la première est celle qui la rend acceptable. + +### 13.1 Le coût réel n'est pas un décalage de cycle — c'est un horizon FAUX de plusieurs heures + +`receivedAt` n'est pas « `issuedAt` à une minute près ». Entre l'initialisation d'un run et sa +mise à disposition, il s'écoule la **latence de publication du modèle** — des **heures**, pas des +minutes, pour un modèle global. + +Conséquence directe : `targetAt − receivedAt` **n'est pas l'horizon**. Il est trop court de toute +la latence de publication, **systématiquement et dans le même sens**. Une prévision qu'on croirait +« à H−1 » peut être le run de H−5. + +> **Ce que la déduplication sauve, et ce qu'elle ne sauve pas.** Elle donne les **bornes** des +> révisions exactement : *combien* de révisions, et *laquelle* s'applique. Elle ne donne pas leur +> **âge**. « Quelle prévision » est juste ; « quel horizon » est faux. +> +> C'est sans effet sur ce lot, qui **affiche**. C'est déterminant pour l'apprentissage, dont +> l'objet même est de corréler l'erreur à l'horizon. + +### 13.2 Condition 1 — la colonne s'appelle `receivedAt`, jamais `issuedAt` + +Un nom qui promet l'heure du run alors qu'il porte l'heure de réception fabrique le calcul faux +tout seul : quelqu'un écrira `targetAt - issuedAt` et obtiendra un nombre **plausible**. C'est la +classe qu'on paie le plus cher ici, et le remède est le même que pour `forecast` — **le nom porte +la protection.** + +Et il faut l'écrire, pas seulement le nommer : **aucun horizon ne se calcule depuis `receivedAt`.** + +### 13.3 Condition 2 — la déduplication compare les points COMMUNS, pas la charge utile + +« Courbe identique à la précédente » ne suffit pas si la fenêtre **glisse** : si openmeteo ne +publie que les heures à venir, la charge utile de 11 h a un point de moins que celle de 10 h, et +un `==` sur la charge utile verrait une révision **toutes les heures** — exactement ce que la +déduplication existe pour éviter. + +La règle : *deux charges utiles sont la même révision si, sur leurs `targetAt` COMMUNS, toutes les +valeurs coïncident.* + +> **Question à trancher avec openmeteo, et elle décide de l'implémentation** : la fenêtre +> publiée couvre-t-elle les journées **entières** (aujourd'hui 00 h → J+1 23 h, passé compris), +> ou seulement **les heures à venir** ? Dans le premier cas le jeu de points est stable dans la +> journée et un `==` naïf suffirait — sauf au passage de minuit. Dans le seesecond, il faut la +> comparaison sur l'intersection. **Je code la seconde dans les deux cas** : elle est correcte +> pour les deux, et elle ne dépend pas d'un comportement qu'un changement chez le voisin pourrait +> retirer sans nous prévenir. + +### 13.4 Condition 3 — la dette `meta.json` se rouvre AVEC son consommateur, pas avant + +La décision de ne pas appeler `meta.json` reposait sur *« aucun consommateur ne le demande »*. +Elle **reste juste** : le consommateur d'aujourd'hui est un **affichage**, et un affichage n'a +pas besoin de l'âge du run — il a besoin de savoir *ce qu'on prévoyait*, ce que la déduplication +donne exactement. + +Le consommateur qui en aura besoin est **l'apprentissage**, et il n'existe pas encore. La dette +est donc notée, avec son déclencheur : + +> **Le jour où quelque chose corrèle l'erreur à l'horizon, `meta.json` devient nécessaire** — et +> la décision se reprend avec un consommateur réel, ce qui est le raisonnement qui l'a rendue +> juste la première fois. + +Ce qui serait fautif, c'est de la rouvrir **maintenant** au nom d'un besoin supposé : une requête +par cycle payée aujourd'hui pour un calcul que personne ne fait. + +### 13.5 Volume, confirmé + +48 h d'horizon, **24 à 48 points** selon l'heure. À 4–8 révisions par jour après déduplication : +**~200 à 400 lignes par jour**, contre les 1 150 estimées au §8.2 sur une révision horaire. La +rétention du §8.2 devient largement confortable, et l'effondrement aux deux bouts peut attendre +bien plus que 7 jours. + +### 13.6 La dette `forecastModel` — je ne m'appuie pas dessus, et je n'écris rien de plausible + +`forecastModel` publie le **modèle configuré**, pas celui qui a répondu. Le stocker comme +provenance enregistrerait une **intention**, et une intention rangée dans une colonne de +provenance devient une provenance au premier lecteur. + +Donc : la colonne `model` existe et reste **NULLE** tant que la correction n'est pas livrée. Un +`NULL` qui dit « on ne sait pas » est honnête ; une valeur qui dit « ce qu'on avait demandé » est +fausse d'une façon parfaitement crédible. **Et aucun remplissage rétroactif** : les lignes écrites +avant la correction resteront nulles, parce qu'elles le sont.