feat(prévision PV): le cycle lit observedGti/observedGtiTime sur le Thing plan

Lus par leur nom, comme kwp. observedGtiTime == 0 ou états absents : rien.
Copies seulement (§11) : aucune décision ne lit ces valeurs, valid n'en
dépend pas.

Mock : pvPlane porte les états observedGti et observedGtiTime (noms
d'openmeteo). testTheLearningLogIsFedByTheCycle : le GTI observé publié pour
t0 + 1 h complète la ligne déjà écrite du pas t0, sur son plan seulement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-10-13 10:21:18 +02:00
parent dd24cda445
commit d80b7d02d6
4 changed files with 54 additions and 1 deletions

View File

@ -221,7 +221,7 @@ qui ne mesure qu'une charge.
| Classe | Porte | Ce qu'elle ouvre |
|---|---|---|
| `pvPlane` | état `forecastCurveTransport` ; paramètres `kwp`, `performanceRatio`, `tempCoeffPctPerK`, `noctC` *(paramètres ajoutés au brief 3 T2)* | une courbe de prévision par le RPC (D2) ; la lecture des paramètres de conversion **sur le Thing**, par leur nom |
| `pvPlane` | état `forecastCurveTransport` ; paramètres `kwp`, `performanceRatio`, `tempCoeffPctPerK`, `noctC` *(paramètres ajoutés au brief 3 T2)* ; états `observedGti` (W/m², défaut 0) et `observedGtiTime` (epoch s, défaut 0), noms d'openmeteo *(etm68)* | une courbe de prévision par le RPC (D2) ; la lecture des paramètres de conversion **sur le Thing**, par leur nom ; le GTI observé par satellite rattaché à son pas (design §31.7) |
| `solarInverter` | interface `smartmeterproducer` (`currentPower`, `totalEnergyProduced`) *(ajoutée au brief 3 T2)* | le seul producteur du mock : sans lui, « le site a-t-il un producteur ? » valait toujours non, et la production mesurée de l'enregistrement pour l'apprentissage n'était jamais publiée |
**Ce qui reste introuvable ici** : un Thing **site** portant `snowfall` (le compteur fait office de

View File

@ -2571,6 +2571,18 @@ void EnergyArbitrator::observerPourApprentissage(const QDateTime &now)
if (!refus.isEmpty())
continue; // déjà journalisé par lireCourbePrevision()
obs.planId = thing->id().toString();
// LE GTI OBSERVÉ par satellite — deux états du Thing plan, lus par leur NOM comme
// `kwp`. `observedGtiTime == 0` est le défaut de l'état : rien n'a été publié.
if (thing->thingClass().hasStateType("observedGti")
&& thing->thingClass().hasStateType("observedGtiTime")) {
const qint64 instant = thing->stateValue("observedGtiTime").toLongLong();
bool ok = false;
const double gti = thing->stateValue("observedGti").toDouble(&ok);
if (instant > 0 && ok) {
obs.gtiObserved = gti;
obs.gtiObservedAt = instant;
}
}
plans.append(obs);
}
// LA NEIGE QUI TOMBE, lue sur le site (état `snowfall`, une valeur de modèle) — pas la

View File

@ -9220,6 +9220,32 @@ void Simulation::testTheLearningLogIsFedByTheCycle()
QCOMPARE(r.value("measuredSource").toString(), QStringLiteral("power-balance-1min"));
QCOMPARE(r.value("curtailed").toInt(), 0);
QCOMPARE(r.value("valid").toInt(), 1);
// Les états observedGti* du mock sont à leur défaut (instant 0) : rien d'observé.
QVERIFY(r.value("gtiObserved").isNull());
QVERIFY(r.value("gtiObservedAt").isNull());
}
// 3. LE GTI OBSERVÉ (etm68), lu sur le Thing plan par le NOM de ses états. Publié pour
// t0 + 1 h, il moyenne la PREMIÈRE heure : il complète la ligne déjà écrite du pas t0,
// sur le plan qui le publie seulement.
const int complements0 = log->complementsTraites();
tSud->setStateValue("observedGti", 640.0);
tSud->setStateValue("observedGtiTime", quint32(t0s + 3600));
cycle(t0.addSecs(7200 + 300));
QTRY_COMPARE(log->complementsTraites(), complements0 + 1);
l = lignes(t0s);
QCOMPARE(l.count(), 2);
for (const QVariantMap &r : l) {
if (r.value("planId").toString() == ThingId(sud).toString()) {
QCOMPARE(r.value("gtiObserved").toDouble(), 640.0);
QCOMPARE(r.value("gtiObservedSource").toString(), QStringLiteral("openmeteo-satellite-state"));
QCOMPARE(r.value("gtiObservedAt").toLongLong(), t0s + 3600);
} else {
QVERIFY(r.value("gtiObserved").isNull());
}
QCOMPARE(r.value("valid").toInt(), 0); // inchangé : valid n'en dépend pas
}
for (const QVariantMap &r : lignes(t0s + 3600))
QVERIFY(r.value("gtiObserved").isNull());
#endif
}

View File

@ -1257,6 +1257,21 @@
"type": "QVariantMap",
"defaultValue": {},
"cached": false
},
{
"id": "dcc47d5e-a1bb-4059-b977-4a9f14eff63a",
"name": "observedGti",
"displayName": "Observed GTI (satellite, W/m²)",
"type": "double",
"defaultValue": 0
},
{
"id": "2663f64b-e4a8-435d-9ef9-75c09c8e1055",
"name": "observedGtiTime",
"displayName": "Observed GTI time (end of the averaged hour)",
"type": "uint",
"unit": "UnixTime",
"defaultValue": 0
}
]
},