fix(3g-1): sous la réserve de stockage, pas de recrédit
LM-1203-b dit « le budget est ANNULÉ, pas réduit », et le motif publié dit « aucune charge n'est servie ». Le code disait autre chose : la réserve ne mordait que sur les AUGMENTATIONS. Le recrédit anti-clignotement (correction B) rend à chaque charge sa consommation de début de cycle avant d'arrondir. Il le faisait aussi sous la réserve — donc toute charge déjà en marche gardait exactement sa puissance, indéfiniment, pendant que la télémétrie annonçait qu'aucune n'était servie. Le recrédit suppose que le budget est une MESURE dont la consommation de la charge a déjà été retranchée. Sous la réserve, le budget n'est pas une mesure mais une DÉCISION de retenir : recréditer dedans rend exactement ce qu'on venait de confisquer. Le recrédit est donc supprimé dans ce seul cas, pour les charges en watts comme pour les états SG-Ready — sans quoi une PAC en état 3 ou 4 y resterait elle aussi indéfiniment. Conséquence sur le motif : ce que la réserve retient à une charge a DEUX termes, et il faut les deux. Le surplus annulé, le même pour toutes, et sa propre consommation, que le recrédit lui aurait rendue. Sans le second, une charge en marche coupée par la réserve sur un surplus nul publiait « surplus insuffisant » — vrai sur le surplus, faux sur la cause : sans la réserve, elle aurait continué de tourner. Le défaut ne s'est vu qu'en exécutant réellement les décisions du waterfall : batteryLevelConsiderationStopCharging échouait, la borne restant allumée là où l'amont la coupe. Suite charging vert (dont les deux tests de réserve), simulation `run` 16/16, testEcsSurplusPV / testSgReadySurplus / testLoadTelemetryRpc verts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TVywtayEeoRJ2yWUW4iZj
This commit is contained in:
parent
38a831407a
commit
e1fbb224fa
@ -117,6 +117,18 @@ et **aucune n'est neutre** :
|
||||
> changelog comme un changement de comportement assumé. Une batterie sous son seuil de réserve
|
||||
> ne devrait pas plus alimenter un chauffe-eau qu'une voiture.
|
||||
|
||||
**DÉCISION : montée dans le budget**, appliquée en 3g-1.
|
||||
|
||||
**Complément 3g-2, trouvé en exécutant vraiment les décisions du waterfall.** La règle ne
|
||||
mordait que sur les AUGMENTATIONS : le recrédit anti-clignotement rendait à chaque charge sa
|
||||
propre consommation, y compris sous la réserve, si bien qu'une charge déjà en marche le restait.
|
||||
Le motif publié disait pourtant « aucune charge n'est servie ». **Sous la réserve, le recrédit
|
||||
est désormais supprimé** — le budget est une décision de retenir, pas une mesure, et recréditer
|
||||
dedans rend exactement ce qu'on venait de confisquer (LM-1203-b : « annulé, pas réduit »).
|
||||
Le motif `BATTERY_RESERVE` chiffre en conséquence les deux termes retenus : le surplus annulé
|
||||
**et** la consommation propre de la charge. Éprouvé par `batteryLevelConsiderationStopCharging`,
|
||||
qui échouait avant cette correction.
|
||||
|
||||
### 3.3 Le nombre de phases se décide en ampères, le waterfall parle en watts
|
||||
|
||||
`getBestPhaseCount()` choisit 1 ou 3 phases **à partir du courant brut**, puis divise. Le
|
||||
|
||||
@ -236,8 +236,17 @@ LoadAction RuleBasedScheduler::buildSetpointAction(const LoadContext &lc,
|
||||
// Recrédit anti-clignotement (Correction B / contrat rév. 2 §5) : la conso de DÉBUT de cycle
|
||||
// est déjà soustraite de l'export mesuré → on la rend au budget local avant d'arrondir.
|
||||
// PAS de relecture post-setpoint (invariant 8 : aucune boucle de feedback).
|
||||
const double budgetW = remainingSurplusW + lc.telemetry.currentPowerW;
|
||||
budget.recreditedW += lc.telemetry.currentPowerW;
|
||||
// 3g-2 — SAUF sous la réserve de stockage, et c'est la seule exception. Le recrédit
|
||||
// suppose que le budget est une MESURE dont la conso de la charge a déjà été retranchée ;
|
||||
// sous la réserve, le budget n'est pas une mesure mais une DÉCISION de retenir
|
||||
// (LM-1203-b : « le budget est annulé, pas réduit »). Recréditer dans un budget décidé nul
|
||||
// rend exactement ce qu'on venait de confisquer : toute charge déjà en marche le restait,
|
||||
// la réserve ne mordait que sur les augmentations, et le motif publié — « aucune charge
|
||||
// n'est servie » — était faux. Attrapé par batteryLevelConsiderationStopCharging au moment
|
||||
// où les bornes ont commencé à exécuter réellement les décisions du waterfall.
|
||||
const double recreditW = m_reserveActive ? 0.0 : lc.telemetry.currentPowerW;
|
||||
const double budgetW = remainingSurplusW + recreditW;
|
||||
budget.recreditedW += recreditW;
|
||||
|
||||
const QList<int> &levels = lc.declared.powerLevels;
|
||||
const bool dynamic = levels.isEmpty();
|
||||
@ -384,14 +393,20 @@ LoadAction RuleBasedScheduler::buildSetpointAction(const LoadContext &lc,
|
||||
la.reason = { DecisionCode::SurplusSetpoint,
|
||||
{{"budgetW", qRound(budgetW)}, {"setpointW", qRound(setpointW)},
|
||||
{"stepped", !dynamic}} };
|
||||
else if (m_reserveActive && m_reserveWithheldW > 0)
|
||||
else if (m_reserveActive && (m_reserveWithheldW + lc.telemetry.currentPowerW) > 0)
|
||||
// 3g-1 — il Y A du surplus, et une règle l'interdit. À ne pas confondre avec
|
||||
// « il n'y en a pas » : le geste attendu n'est pas le même, et le second envoie
|
||||
// chercher une panne inexistante.
|
||||
//
|
||||
// 3g-2 — ce que la réserve retient à CETTE charge a deux termes, et il faut les deux :
|
||||
// le surplus annulé (le même pour toutes), ET sa propre consommation, que le recrédit
|
||||
// lui aurait rendue. Sans le second, une charge en marche coupée par la réserve sur un
|
||||
// surplus nul publiait « surplus insuffisant » — vrai sur le surplus, faux sur la
|
||||
// cause : sans la réserve, elle aurait continué de tourner.
|
||||
la.reason = { DecisionCode::BatteryReserve,
|
||||
{{"socPercent", qRound(m_reserveSocPct)},
|
||||
{"reservePercent", qRound(m_reserveSeuilPct)},
|
||||
{"withheldW", qRound(m_reserveWithheldW)}} };
|
||||
{"withheldW", qRound(m_reserveWithheldW + lc.telemetry.currentPowerW)}} };
|
||||
else if (sousPlancher)
|
||||
// §12 — dire QUE le budget est sous le plancher, pas qu'il est insuffisant : la
|
||||
// seconde phrase enverrait chercher du soleil là où il faut lire une fiche technique.
|
||||
@ -416,7 +431,12 @@ LoadAction RuleBasedScheduler::buildSgReadyStateAction(const LoadContext &lc,
|
||||
|
||||
// Recrédit (correction B) : la puissance ALLOUÉE de l'état courant (déclaré, 0 pour 1/2)
|
||||
// revient au budget — comme l'ECS. Base : "quel surplus si la PAC n'était pas pilotée ?"
|
||||
const double allocatedNowW = lc.declared.estimatedPowerW.value(currentState, 0.0);
|
||||
// 3g-2 — même exception que pour les charges en watts : sous la réserve, pas de recrédit.
|
||||
// Une PAC en état 3 ou 4 y resterait sinon indéfiniment, la réserve n'agissant que sur les
|
||||
// montées d'état. Voir la note détaillée dans buildSetpointAction().
|
||||
const double allocatedNowW = m_reserveActive
|
||||
? 0.0
|
||||
: lc.declared.estimatedPowerW.value(currentState, 0.0);
|
||||
const double budgetW = remainingSurplusW + allocatedNowW;
|
||||
budget.recreditedW += allocatedNowW;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user