Le reseau ne prend plus la place du soleil quand la voiture charge

BOOST 28 A en plein soleil, 11/09 17:02 : consigne reseau calculee a
8242 W (maison 1802 + 28 A x 230), PV effondre a 121 W, reseau 7697 W,
batterie 0 A. L'ESS n'a qu'un moyen de prelever 8,2 kW quand le soleil
en donne 4 : brider les MPPT. La production etait refusee par notre
propre consigne.

Le commentaire qui justifiait une consigne genereuse ("elle ne coute
rien") datait de la nuit du 01/09, ou la PV valait zero. En plein jour
il est faux.

On ne peut pas soustraire la PV mesuree : elle vaut 121 W A CAUSE de
cette consigne. Le proprietaire l'a montre en la baissant a la main a
2280 W -- PV 4758 W en 50 secondes, batterie +9 A. On descend donc par
paliers et on laisse la physique repondre, sur deux signaux incensurables
et deja lus : les chargeurs declarent eux-memes s'ils se BRIDENT, et le
courant batterie dit si on est descendu trop bas.

`off` ne compte pas comme bride : la nuit les six le sont, et les
confondre ferait descendre la consigne au crepuscule -- un oscillateur
de plus. C'est ce que verifie le scenario de nuit.

Plancher : la consigne ne descend jamais sous la maison hors voiture,
garantie du 01/09 et du 04/09. On ne libere que la part voiture.

Golden 84 -> 89. Mutation en quatre directions (descente neutralisee /
`off` recompte comme bride / garde batterie retiree / plancher retire) :
chacune fait tomber un jeu DIFFERENT, aucune garde n'est couverte par
le test d'une autre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-11 17:36:37 +02:00
parent cea4a91e20
commit 4d828c3f47
5 changed files with 219 additions and 14 deletions

View File

@@ -359,13 +359,89 @@ const gridSessionActive =
night.nightGridSessionCommitted === true
);
const gridSetpointNeedW = gridSessionActive
? clamp(
Math.round(derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef),
params.gridSetpointIdleW,
GRID_SETPOINT_MAX_W
)
const gridSetpointCeilingW = clamp(
Math.round(derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef),
params.gridSetpointIdleW,
GRID_SETPOINT_MAX_W
);
const gridSetpointCurrentW = Number.isFinite(parsed.gridSetpointW)
? parsed.gridSetpointW
: null;
// =====================================================
// CREDIT SOLAIRE SUR LA CONSIGNE (11/09/2026)
// =====================================================
// Defaut mesure ce jour-la : BOOST 28 A en plein soleil, consigne calculee a
// 8242 W (maison 1802 + 28 A x 230). L'ESS n'a qu'un moyen de prelever 8,2 kW
// quand le soleil en donne 4 : brider les MPPT. Releve 17:02 -- PV 121 W,
// reseau 7697 W, batterie 0 A, SOC 96 %. La production etait refusee, et par
// NOTRE consigne. Le commentaire ci-dessus ("une consigne trop HAUTE ne coute
// rien") a ete ecrit la nuit du 01/09, ou la PV valait zero : il n'y avait
// alors rien a evincer. En plein jour il est faux.
//
// ⚠️ ON NE PEUT PAS SOUSTRAIRE LA PV MESUREE. Elle vaut 121 W *a cause* de
// cette consigne : mesure censuree, la soustraire ne bouge rien et le systeme
// reste sur son mauvais equilibre. Preuve le meme jour a 17:22 -- consigne
// descendue A LA MAIN a 2280 W : PV 4758 W en 50 secondes, batterie +9 A. La
// ressource etait la, seule la consigne l'evincait.
//
// On DESCEND donc, et on laisse la physique repondre. Deux signaux, tous deux
// deja lus et tous deux INCENSURABLES :
// - les chargeurs declarent eux-memes s'ils se BRIDENT (mpptLimitedCount).
// `off` ne compte pas : la nuit ils sont tous `off` et il n'y a rien a
// liberer -- c'est ce qui garantit que le comportement de nuit (§31, §57)
// est strictement inchange ;
// - le courant batterie dit si on est descendu trop bas, via la politique
// par bande de SOC deja calibree (batterySoftBreachNow, §35).
// La valeur courante vit dans le GX : l'etat est deja persiste, aucun helper
// nouveau n'est necessaire.
//
// PLANCHER : la consigne ne descend JAMAIS sous la maison hors voiture. C'est
// la garantie du 01/09 et du 04/09 -- le reseau, quand le contacteur est ferme
// a notre demande, couvre la maison. On ne libere que la part VOITURE, qui est
// exactement celle qui evince le solaire. Le solaire pourrait souvent couvrir
// la maison en plus, mais l'affirmer demanderait ses propres scenarios.
const gridSetpointHouseFloorW = clamp(
Math.round(derived.houseNonEvcsW),
params.gridSetpointIdleW,
GRID_SETPOINT_MAX_W
);
const solarResourceRefused =
parsed.mpptReadableCount > 0 && parsed.mpptLimitedCount > 0;
// Pendant la fenetre d'etablissement (§29) les mesures PV et batterie ne
// decrivent aucun regime permanent : on ne descend pas sur elles.
const gridSetpointDescendOk =
gridSessionActive &&
solarResourceRefused &&
!batteryPolicy.batterySoftBreachNow &&
!derived.gridSettling &&
gridSetpointCurrentW !== null;
let gridSetpointNeedW = gridSessionActive
? gridSetpointCeilingW
: params.gridSetpointIdleW;
let gridSetpointDescending = false;
if (gridSetpointDescendOk) {
// Un chargeur bride PROUVE qu'il reste de la ressource : on peut descendre
// franchement. Une descente trop forte est rattrapee au cycle suivant (30 s
// ou la batterie porte l'ecart, soit quelques dizaines de Wh), alors qu'une
// descente trop timide laisse des kW de solaire refuses pendant dix minutes.
const minStepW = Math.max(
GRID_SETPOINT_DEADBAND_W,
Math.round(params.currentStepUpA * derived.acVoltageRef)
);
const baseW = Math.min(gridSetpointCurrentW, gridSetpointCeilingW);
const gapW = baseW - gridSetpointHouseFloorW;
if (gapW > 0) {
const stepW = Math.max(minStepW, Math.round(gapW / 2));
gridSetpointNeedW = Math.max(gridSetpointHouseFloorW, baseW - stepW);
gridSetpointDescending = true;
}
}
// La bande est ASYMETRIQUE, et c'est le point. Une consigne trop BASSE fait
// porter la difference par la batterie : c'est le defaut de la nuit du 01/09,
@@ -380,7 +456,7 @@ const gridSetpointNeedW = gridSessionActive
// consigne insuffisante au cycle suivant, et on ne redescend que si elle est
// devenue franchement inutile. Sans cela la boucle 30 s reecrivait a chaque
// cycle en poursuivant le bruit.
const gridSetpointSlackW = gridSessionActive
const gridSetpointSlackW = gridSessionActive && !gridSetpointDescending
? Math.max(GRID_SETPOINT_DEADBAND_W, Math.round(params.currentStepUpA * derived.acVoltageRef))
: 0;
@@ -390,9 +466,6 @@ const gridSetpointTargetW = clamp(
GRID_SETPOINT_MAX_W
);
const gridSetpointCurrentW = Number.isFinite(parsed.gridSetpointW)
? parsed.gridSetpointW
: null;
const gridSetpointNeedsWrite =
gridSetpointCurrentW === null ||
gridSetpointCurrentW < gridSetpointNeedW ||
@@ -406,7 +479,11 @@ if (derived.gridSettling) {
reason.push(`ETABLISSEMENT reseau ${Math.round(parsed.gridTransitionAgeSec)}s/${params.gridSettleSeconds}s (mesures PV/batterie non fiables)`);
}
if (gridSessionActive) {
reason.push(`Consigne reseau=${gridSetpointTargetW}W`);
reason.push(
gridSetpointDescending
? `Consigne reseau=${gridSetpointTargetW}W (CREDIT SOLAIRE, ${parsed.mpptLimitedCount}/${parsed.mpptReadableCount} brides)`
: `Consigne reseau=${gridSetpointTargetW}W`
);
}
// Visible dans le logbook uniquement pendant un depassement souple : c'est le
// seul moment ou l'information sert, et la raison est tronquee a 255 caracteres.

View File

@@ -264,10 +264,20 @@ const parsed = {
.map(s => s.trim().toLowerCase())
.filter(s => s !== '' && s !== 'unavailable' && s !== 'unknown');
const atMaxPower = modes.filter(s => s === 'mppt_active').length;
// Un chargeur qui SE RETIENT (`voltage_current_limited`) prouve qu'il reste
// de la ressource refusee. Un chargeur `off` n'a rien a donner : la nuit,
// tous le sont. `mpptHoldingBackCount` confond les deux, ce qui est sans
// consequence pour la garde de provocation solaire (§37, demarrage de jour
// seulement) mais ferait descendre la consigne reseau au crepuscule si on
// s'en servait ici — un oscillateur de plus. On compte donc separement ce
// qui se declare BRIDE, et tout libelle inconnu retombe du cote prudent
// (pas de ressource refusee, donc comportement anterieur).
const limited = modes.filter(s => s.includes('limited')).length;
return {
mpptReadableCount: modes.length,
mpptAtMaxPowerCount: atMaxPower,
mpptHoldingBackCount: modes.length - atMaxPower,
mpptLimitedCount: limited,
};
})(),
pvPowerAvg10mW: Math.max(0, num(input.pv_power_10m_avg_w, num(input.pv_power_w, 0))),