Patrick Schurig 13114a2695 feat(optimiseur): sérialiseur GetPlan, protocole 1.1 §5.1-§5.3 (brief 4 T1)
OptimizerProtocol::getPlanParams(SurplusContext, ContexteFil) rend les params d'une requête
GetPlan. Fonction pure, compilée dans le plugin mais appelée par personne : sans endpoint,
rien ne change.

- La prévision voyage À CÔTÉ du contexte (§11) : ContexteFil::prevision porte la sortie de
  EnergyArbitrator::instantanePrevision(), l'assemblage partagé avec GetForecastSnapshot.
  forecastDepuisInstantane() la pose sur la grille §5.2 : Wh par pas → p50W moyens
  (Wh × 3600 / resolutionS), case sans point = null, pv null sans courbe, weather null.
- ContexteFil porte aussi trigger (sa source viendra en T3), meter.source, la PV
  instantanée, la limite de phase, la réserve, le domaine et la mesure par charge.
- Inconnu = null ou absent, jamais 0 : contractedPowerW, pv.currentW, capacité et
  puissances batterie, tarif → null ; learned, comfort, group, champs réservés, force,
  powerLevels → absents ; borne configurée sans appareil → ni déclaration ni verrous.
- meter.exportW brut (règle 8). Champs 1.0 conservés ; energyTargets dérivés par
  resolveDeadline (installer) et de la dérogation à cible (user-intent).

Tests (loadmodel, logique pure) : 4 goldens dans data/optimizer (site sans PV ; PV + ECS
relay-router + borne + SG-Ready ; prévision absente ; charge available=false figée),
régénérables sous ETM_REGENERER_GOLDEN=1, plus 3 tests d'assertion. Contre-épreuves :
contractedPowerW à 0, Wh non convertis, exportW net → chacune fait échouer au moins un test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 16:01:34 +02:00

239 lines
5.2 KiB
JSON

{
"battery": {
"present": false
},
"forecast": {
"baseload": {
"model": "none",
"p50W": [
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
]
},
"price": {
"currency": "EUR",
"exportCtkWh": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"importCtkWh": [
16.96,
16.96,
16.96,
16.96,
16.96,
16.96,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
16.96,
16.96,
16.96,
16.96,
16.96,
16.96,
16.96,
16.96,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
21.46,
16.96,
16.96
],
"includes": [
"energy",
"grid",
"taxes"
],
"knownUntil": "2026-10-13T22:00:00Z",
"source": "tou"
},
"pv": null,
"resolutionS": 3600,
"start": "2026-10-11T22:00:00Z",
"steps": 48,
"weather": null
},
"loads": [
{
"adapter": "relay-router",
"declared": {
"maxPowerW": 2400,
"minPowerW": 800
},
"domain": "ecs",
"id": "{0d4c6a7e-2222-4c1a-9a01-000000000002}",
"label": "Chauffe-eau",
"limits": {
"minOffS": 300,
"minOnS": 300
},
"needs": {
"dailyDeadline": "18:00",
"energyTargets": [
{
"by": "2026-10-12T18:00:00+02:00",
"minWh": 4000,
"source": "installer"
}
],
"minEnergyWhPerDay": 4000
},
"priority": 2,
"telemetry": {
"available": true,
"currentPowerW": 0,
"lockMaxPowerW": -1,
"lockMinPowerW": 0,
"measurement": {
"powerW": 0,
"source": "meter"
}
}
}
],
"meter": {
"exportW": 0,
"importW": 850,
"perPhaseA": [
1.2,
2.3,
0.4
],
"source": "{0d4c6a7e-1111-4c1a-9a01-000000000001}"
},
"pv": {
"currentW": null
},
"site": {
"contractedPowerW": null,
"gridCapW": null,
"phaseLimitA": [
32,
32,
32
]
},
"tariff": null,
"timestamp": "2026-10-12T13:42:00+02:00",
"trigger": "cycle"
}