Patrick Schurig a1455290c9 feat(3g): BATTERY_RESERVE branché, funding lu, et l'état d'une borne passe devant
Deux constats du brief moteur, qui touchent le même fichier de lecture.

1. kCodesReserveBatterie était vide, et les clés cherchées n'ont JAMAIS existé côté
   plugin : batteryLevel/soc et threshold/batteryLevelConsideration contre socPercent,
   reservePercent, withheldW. Le motif sortait donc en repli — c'est-à-dire en anglais
   brut — sur le seul cas que le client ne peut déduire d'aucune mesure : « il y a du
   surplus, une règle l'interdit ». Vérifié contre decisionreason.h.

   Les trois paramètres sont des entiers DÉJÀ en pourcentage : reservePercent vaut 40, pas
   0,4 — à ne pas confondre avec batteryLevelConsideration, qui est la fraction du réglage.
   Et withheldW mène la phrase, comme côté plugin : le moteur en fait la condition
   d'émission du motif, parce qu'un motif à 0 W dirait « une règle vous bloque » là où la
   vérité est « il n'y a pas de surplus ».

   Le motif n'a PAS pu être observé en vrai sur .75 : la bascule du seuil à 0,9 a produit
   surplusW à −3 551 W, donc rien à confisquer, donc garde du plugin active et motif tu —
   ce qui est le comportement juste. Il faut du surplus réel sous le seuil. Seuil remis
   à 0,4.

2. funding (« surplus » / « grid ») est lu, et avec lui l'identité que le plugin déclare :
   somme des allocatedW financés au surplus == budget.allocatedW. Sommer sans filtrer
   compte une borne servie au réseau et creuse un écart qu'on ne peut pas interpréter.
   Absent = surplus, pas zéro : c'est le seul financement qui existait avant ce champ.

3. Les bornes sont entrées dans loads[] (+etm22), et adjustEvChargers() re-décide après le
   waterfall : pour une borne, allocatedW et son motif sont la DÉCISION DE L'ARBITRE, pas
   l'état de la borne. L'écran affiche donc d'abord ce que le matériel fait
   (mechanism.chargingEnabled / currentA, qui viennent du Thing), ensuite ce que le moteur
   a voulu, étiqueté comme tel. L'écart mesuré/commandé se tait sur une borne — « sous la
   consigne » y supposerait une consigne. La réserve ne touche PAS l'ECS ni la PAC, où
   l'allocation publiée est bien la commande : elle est portée par allocationIsCommand, et
   tombe avec le lot plugin 3g-2.

EV_SURPLUS, EV_ECO_MIN et EV_IDLE ne sont plus émis. Les branches restent : les retirer
rendrait illisible un journal antérieur, pas l'app plus juste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vrTVifar2GN5rtUh89Wkq
2026-08-27 07:38:48 +02:00

373 lines
18 KiB
Plaintext

{
"@@locale": "fr",
"installerLoadsTitle": "Rôles & appareils",
"@installerLoadsTitle": {
"description": "Titre de l'écran installateur de configuration des charges pilotables"
},
"installerLoadsSubtitle": "Ordre de service des charges pilotables",
"loadsEmpty": "Aucune charge pilotable déclarée sur cette installation.",
"loadsLoadError": "Lecture de la configuration impossible : {error}",
"@loadsLoadError": {
"placeholders": { "error": { "type": "String" } }
},
"sectionUnclassified": "Non classé",
"@sectionUnclassified": {
"description": "Bucket des charges sans o:domain — jamais rangées d'office dans un domaine"
},
"domainEv": "Véhicule électrique",
"domainEcs": "Eau chaude sanitaire",
"domainHvac": "Climatisation",
"domainHeating": "Chauffage",
"domainUnknown": "Domaine « {code} »",
"@domainUnknown": {
"description": "Repli quand la box renvoie un domaine que cette version d'app ne connaît pas",
"placeholders": { "code": { "type": "String" } }
},
"adapterRelayRouter": "Routeur de relais",
"adapterEtmVariableLoad": "Charge modulable",
"adapterSgReady": "SG-Ready",
"adapterEvCharger": "Borne de recharge",
"adapterUnknown": "Mécanisme « {code} »",
"@adapterUnknown": {
"placeholders": { "code": { "type": "String" } }
},
"loadEnabled": "Active",
"loadDisabled": "Exclue de l'arbitrage",
"loadPriority": "Rang {rank}",
"@loadPriority": {
"placeholders": { "rank": { "type": "int" } }
},
"loadMinOn": "Maintien ON minimal : {seconds} s",
"@loadMinOn": {
"placeholders": { "seconds": { "type": "int" } }
},
"loadMinOff": "Maintien OFF minimal : {seconds} s",
"@loadMinOff": {
"placeholders": { "seconds": { "type": "int" } }
},
"loadRelayCount": "{count, plural, =1{1 relais} other{{count} relais}}",
"@loadRelayCount": {
"placeholders": { "count": { "type": "int" } }
},
"loadMeterSlot": "Compteur dédié : non rattachable",
"@loadMeterSlot": {
"description": "Emplacement visuel réservé — LoadConfig n'a aucun champ pour rattacher un compteur à une charge"
},
"orderNotGroupedTitle": "Ordre non groupé",
"orderNotGroupedBody": "L'ordre en place sur la box n'est pas groupé par domaine : les charges d'un même domaine ne se suivent pas. L'ordre réel est affiché ci-dessous, tel que la box l'applique.",
"orderNotGroupedAction": "Normaliser l'ordre",
"orderNotGroupedWarning": "Normaliser modifiera l'ordre d'arbitrage réel de l'installation. Aucune écriture ne part tant que vous n'avez pas confirmé.",
"orderNormalizeConfirm": "Normaliser et enregistrer",
"cancel": "Annuler",
"saveOrder": "Enregistrer l'ordre",
"savePending": "Envoi en cours…",
"saveWaitingBox": "En attente de confirmation de la box…",
"saveConfirmed": "Configuration confirmée par la box.",
"saveRejected": "La box a refusé : {reason}",
"@saveRejected": {
"placeholders": { "reason": { "type": "String" } }
},
"saveDiverged": "La box a enregistré un état différent de celui demandé. L'affichage montre ce que la box applique réellement.",
"validationDuplicateId": "L'identifiant de charge « {id} » apparaît deux fois.",
"@validationDuplicateId": {
"placeholders": { "id": { "type": "String" } }
},
"validationThingClaimedTwice": "L'appareil {thingId} est commandé par deux charges actives, « {first} » et « {second} » : deux commandes contradictoires et une puissance comptée deux fois.",
"@validationThingClaimedTwice": {
"placeholders": {
"thingId": { "type": "String" },
"first": { "type": "String" },
"second": { "type": "String" }
}
},
"telemetryUnavailableTitle": "Télémétrie indisponible",
"telemetryUnavailableBody": "Cette box n'expose pas GetLoadTelemetry. Puissance allouée, verrous, disponibilité et motifs de décision ne sont pas lisibles depuis l'app.",
"telemetryStale": "Données anciennes — dernier point il y a {seconds} s",
"@telemetryStale": {
"placeholders": { "seconds": { "type": "int" } }
},
"telemetryDegradedMode": "Mode dégradé L2 — compteur muet, planification suspendue",
"faultTitle": "En défaut",
"faultClearAction": "Lever le défaut",
"faultCleared": "Défaut levé — confirmé par la télémétrie relue.",
"faultClearNoop": "Aucun défaut à lever sur cette charge.",
"faultUnknown": "Défaut « {code} »",
"@faultUnknown": {
"placeholders": { "code": { "type": "String" } }
},
"decisionUnknown": "Décision « {code} »{params}",
"@decisionUnknown": {
"description": "Repli obligatoire quand la box renvoie un decision.code absent de cet ARB — box plus récente que l'app. Affiche le code brut et ses paramètres, jamais une phrase vide.",
"placeholders": {
"code": { "type": "String" },
"params": { "type": "String" }
}
},
"telemetryTitle": "Arbitrage en direct",
"telemetryNeverRan": "Aucun cycle d'arbitrage depuis le démarrage du service.",
"@telemetryNeverRan": {
"description": "timestamp absent — l'absence de cycle est une information, pas un zéro"
},
"telemetryLastCycle": "Dernier cycle il y a {seconds} s",
"@telemetryLastCycle": {
"placeholders": { "seconds": { "type": "int" } }
},
"telemetryBudgetTitle": "Budget de surplus du cycle",
"telemetryBudgetAbsent": "Aucun budget publié pour ce cycle : la planification est suspendue.",
"budgetSurplus": "Surplus entrant",
"budgetEvReserved": "Réservé au véhicule",
"budgetRecredited": "Recrédité",
"budgetAllocated": "Alloué",
"budgetRemaining": "Reliquat",
"watts": "{watts} W",
"@watts": {
"placeholders": { "watts": { "type": "int", "format": "decimalPattern" } }
},
"telemetryAllocated": "Alloué {watts} W",
"@telemetryAllocated": {
"placeholders": { "watts": { "type": "int", "format": "decimalPattern" } }
},
"telemetryEvArbiterIntent": "L'arbitre a décidé {watts} W",
"@telemetryEvArbiterIntent": {
"description": "Remplace « Alloué X W » pour une BORNE. Tant que le lot plugin 3g-2 n'est pas livré, adjustEvChargers() re-décide après le waterfall : l'allocation publiée est une intention, pas un ordre. Le verbe doit donc nommer QUI a décidé, pas ce que la borne fait.",
"placeholders": { "watts": { "type": "int", "format": "decimalPattern" } }
},
"telemetryEvIntentDisclaimer": "Décision de l'arbitre — l'état réel de la borne est ci-dessus.",
"@telemetryEvIntentDisclaimer": {
"description": "Ligne qui suit le motif d'une borne. Sans elle, un écran qui affiche « budget sous le plancher » à côté d'une borne qui charge à 6 A a l'air faux — et c'est le moteur qui se contredit, pas l'écran. Tombe avec 3g-2."
},
"telemetryEvCharging": "Borne : en charge, {current} A sur {phases} phase(s)",
"@telemetryEvCharging": {
"description": "ÉTAT RÉEL, lu dans mechanism.chargingEnabled / currentA / phaseCount — qui viennent du Thing, pas de l'arbitre.",
"placeholders": {
"current": { "type": "double", "format": "decimalPattern" },
"phases": { "type": "int" }
}
},
"telemetryEvIdle": "Borne : ne charge pas",
"telemetryEvUnplugged": "Borne : aucun véhicule branché",
"telemetryEvStateUnknown": "Borne : état non publié ce cycle",
"@telemetryEvStateUnknown": {
"description": "mechanism.chargingEnabled absent. Ne rien dire vaut mieux que déduire l'état du motif d'arbitrage, qui peut le contredire."
},
"telemetryFundedByGrid": "Financée au réseau (échéance ou tarif dynamique) — hors budget de surplus",
"@telemetryFundedByGrid": {
"description": "funding == « grid » : l'allocation vit dans budget.evReservedW, pas dans budget.allocatedW. Sans cette mention, l'utilisateur voit une charge servie alors que le surplus est nul et croit à un défaut."
},
"telemetryNotArbitrated": "Non arbitrée — absente du dernier cycle publié",
"@telemetryNotArbitrated": {
"description": "Charge présente dans GetLoadConfig mais absente de loads[] : elle est désactivée, jamais perdue"
},
"lockMinOn": "Maintien ON — {seconds} s restantes",
"@lockMinOn": {
"placeholders": { "seconds": { "type": "int" } }
},
"lockMinOff": "Maintien OFF — {seconds} s restantes",
"@lockMinOff": {
"placeholders": { "seconds": { "type": "int" } }
},
"lockMinStateHold": "Maintien d'état — {seconds} s restantes",
"@lockMinStateHold": {
"placeholders": { "seconds": { "type": "int" } }
},
"lockUnknown": "Verrou « {kind} » — {seconds} s restantes",
"@lockUnknown": {
"placeholders": {
"kind": { "type": "String" },
"seconds": { "type": "int" }
}
},
"lockAsOfCycle": "au dernier cycle",
"@lockAsOfCycle": {
"description": "Les secondes restantes ne sont PAS décomptées côté app : elles datent du cycle publié"
},
"mechanismRelayStage": "Palier {stage} W sur {max} W",
"@mechanismRelayStage": {
"placeholders": {
"stage": { "type": "int", "format": "decimalPattern" },
"max": { "type": "int", "format": "decimalPattern" }
}
},
"mechanismVariableSetpoint": "Consigne {setpoint} W ({percent} %)",
"@mechanismVariableSetpoint": {
"placeholders": {
"setpoint": { "type": "int", "format": "decimalPattern" },
"percent": { "type": "int" }
}
},
"mechanismSgReadyState": "État SG-Ready {state} — estimation {estimated} W",
"@mechanismSgReadyState": {
"placeholders": {
"state": { "type": "int" },
"estimated": { "type": "int", "format": "decimalPattern" }
}
},
"mechanismEvCharger": "{current} A sur {phases} phase(s)",
"@mechanismEvCharger": {
"placeholders": {
"current": { "type": "double", "format": "decimalPattern" },
"phases": { "type": "int" }
}
},
"mechanismUnknown": "Mécanisme « {kind} »",
"@mechanismUnknown": {
"placeholders": { "kind": { "type": "String" } }
},
"decisionEvSurplus": "Véhicule rechargé sur le surplus photovoltaïque.",
"decisionEvSpotMarket": "Véhicule rechargé sur un créneau tarifaire favorable.",
"decisionEvDeadline": "Véhicule prioritaire : l'échéance approche.",
"decisionEvEcoMin": "Pas de surplus : le courant minimum est maintenu.",
"decisionEvIdle": "Pas de surplus : la recharge est suspendue.",
"decisionLoadUnavailable": "Charge en défaut : aucune commande n'est émise, et {frozen} W restent comptés au budget.",
"@decisionLoadUnavailable": {
"placeholders": { "frozen": { "type": "int", "format": "decimalPattern" } }
},
"decisionLockMinOn": "Le maintien ON a relevé la consigne à {applied} W ; le budget en payait {budget} W.",
"@decisionLockMinOn": {
"placeholders": {
"applied": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionLockMinOff": "Le maintien OFF interdit l'enclenchement ; le budget payait pourtant {requested} W sur {budget} W disponibles.",
"@decisionLockMinOff": {
"placeholders": {
"requested": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionLockMinOffCapped": "Le maintien OFF plafonne la consigne à {applied} W ; {requested} W étaient demandés sur {budget} W de budget.",
"@decisionLockMinOffCapped": {
"placeholders": {
"applied": { "type": "int", "format": "decimalPattern" },
"requested": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionSurplusSetpoint": "Consigne de {setpoint} W servie par le surplus ({budget} W de budget).",
"@decisionSurplusSetpoint": {
"placeholders": {
"setpoint": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionSurplusSetpointStepped": "Consigne de {setpoint} W servie par le surplus, arrondie sur un palier déclaré ({budget} W de budget).",
"@decisionSurplusSetpointStepped": {
"placeholders": {
"setpoint": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionSurplusInsufficient": "Aucun surplus à distribuer ({budget} W) — rien n'est commandé.",
"@decisionSurplusInsufficient": {
"placeholders": { "budget": { "type": "int", "format": "decimalPattern" } }
},
"decisionLockMinStateHold": "État {state} maintenu par protection anti court-cycle.",
"@decisionLockMinStateHold": {
"placeholders": { "state": { "type": "int" } }
},
"decisionSgForced": "Pompe à chaleur en état 4 (forcé) — estimation {estimated} W sur {budget} W de budget.",
"@decisionSgForced": {
"placeholders": {
"estimated": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionSgRecommended": "Pompe à chaleur en état 3 (recommandation) — estimation {estimated} W sur {budget} W de budget.",
"@decisionSgRecommended": {
"placeholders": {
"estimated": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" }
}
},
"decisionSgNormal": "Aucun surplus ({budget} W) — pompe à chaleur en état 2 (normal, sans consigne).",
"@decisionSgNormal": {
"placeholders": { "budget": { "type": "int", "format": "decimalPattern" } }
},
"decisionBelowMinPower": "Budget de {budget} W sous le premier palier ({min} W) — rien n'est commandé, le budget passe à la charge suivante.",
"@decisionBelowMinPower": {
"description": "Il Y A du surplus, mais pas au bon format : sous le plus petit palier commandable. À ne pas confondre avec SURPLUS_INSUFFICIENT, qui dit qu'il n'y a rien.",
"placeholders": {
"budget": { "type": "int", "format": "decimalPattern" },
"min": { "type": "int", "format": "decimalPattern" }
}
},
"decisionBelowMinPowerState": "Budget de {budget} W sous l'état {state} ({min} W estimés) — maintien en état 2, le budget passe à la charge suivante.",
"@decisionBelowMinPowerState": {
"description": "Même sens, mécanisme à états : le premier état utile coûterait plus que ce qui reste.",
"placeholders": {
"budget": { "type": "int", "format": "decimalPattern" },
"state": { "type": "int" },
"min": { "type": "int", "format": "decimalPattern" }
}
},
"decisionBatteryReserveWithheld": "{withheld} W de surplus retenus par la réserve batterie ({soc} % sous le seuil de {seuil} %) — il y a du surplus, mais une règle interdit de le distribuer.",
"@decisionBatteryReserveWithheld": {
"description": "Variante NOMINALE : le plugin publie toujours les trois paramètres ensemble, et sa garde interne exige withheldW > 0 — un motif à 0 W dirait « une règle vous bloque » là où il n'y a simplement pas de surplus. Le nombre de watts retenus vient en TÊTE parce que c'est lui qui rend la cause lisible : « 3 200 W retenus » se comprend, « batterie à 47 % » se subit.",
"placeholders": {
"withheld": { "type": "int", "format": "decimalPattern" },
"soc": { "type": "int" },
"seuil": { "type": "int" }
}
},
"decisionBatteryReserve": "Budget annulé par la réserve batterie ({soc} % sous le seuil de {seuil} %) — il y a du surplus, mais une règle interdit de le distribuer.",
"@decisionBatteryReserve": {
"description": "Repli si « withheldW » manque (box plus ancienne). À NE PAS confondre avec « pas de surplus » : le surplus existe, une règle l'annule. Le client voit une installation qui ne pilote rien par grand soleil, sans erreur ni panne — la phrase doit donc nommer la cause, pas décrire l'effet.",
"placeholders": {
"soc": { "type": "int" },
"seuil": { "type": "int" }
}
},
"decisionBatteryReserveShort": "Budget annulé par la réserve batterie — il y a du surplus, mais une règle interdit de le distribuer.",
"@decisionBatteryReserveShort": {
"description": "Variante sans paramètres, si le motif n'en porte pas."
},
"decisionDegradedL2": "Consigne de repli : le compteur est muet et le watchdog a suspendu la planification.",
"decisionSafeStateRelay": "État sûr avant retrait : tous les relais sont ouverts.",
"decisionSafeStateSetpoint": "État sûr avant retrait : consigne ramenée à 0 W.",
"decisionSafeStateSgReady": "État sûr avant retrait : état 2 (normal), jamais le blocage.",
"faultWriteFailed": "Échec d'écriture : l'échelle de commande est épuisée, plus aucune commande n'est émise tant que le défaut n'est pas levé.",
"faultThingMissing": "L'appareil commandé est absent de la configuration nymea.",
"faultUnusableEncoding": "Encodage SG-Ready inexploitable : l'état 2 ne peut pas être exprimé, cette pompe à chaleur n'est pas pilotable.",
"faultClearPending": "Levée demandée — en attente de la télémétrie…",
"faultClearStillFaulty": "La charge est toujours en défaut après la levée.",
"faultClearRejected": "La box a refusé la levée : {reason}",
"@faultClearRejected": {
"placeholders": { "reason": { "type": "String" } }
},
"faultClearHint": "La levée est une action délibérée : le défaut ne se lève jamais tout seul.",
"domainBattery": "Batterie",
"domainAssignTitle": "Classer « {label} »",
"@domainAssignTitle": {
"placeholders": { "label": { "type": "String" } }
},
"domainAssignAction": "Changer de domaine",
"domainAssignNone": "Retirer du classement",
"domainAssignHint": "Le domaine est une intention déclarée par l'installateur. L'arbitre ne la lit pas : elle ne sert qu'à grouper l'ordre de service.",
"localeRequested": "Locale demandée : {requested} · retenue par la box : {effective}",
"@localeRequested": {
"placeholders": {
"requested": { "type": "String" },
"effective": { "type": "String" }
}
}
}