Patrick Schurig 801ba15094 feat(3g): la réserve batterie sort de l'usine — un seuil qui peut tout geler doit se voir
`batteryLevelConsideration` ne gouverne aujourd'hui que la recharge du véhicule. Une
fois la règle montée dans le budget, il gouvernera TOUTE l'installation : sous le
seuil le budget de surplus est annulé — pas réduit — donc ni ECS, ni PAC, ni bornes.

Le défaut d'usine est 0,9. Le banc `.75` est exactement dans ce cas : seuil 0,9,
batterie à 50 %. Le symptôme chez un client serait le pire qui soit — un HEMS qui ne
pilote plus rien par grand soleil, sans erreur, sans panne, rien à lire dans un
journal. Un paramètre qui peut produire ça n'est pas un paramètre d'usine.

- `BatteryReserveCard` lit et écrit le réglage, et ALERTE quand le seuil dépasse le SOC
  réellement atteint — le cas silencieux. Elle n'invente aucune valeur par défaut : si
  la box ne l'expose pas, la carte se tait plutôt que d'afficher un curseur mort.
- La portée future est dite sur la carte : régler ce seuil « pour la voiture »
  couperait le reste demain.
- Clé ARB `decisionBatteryReserve` posée, et `kCodesReserveBatterie` volontairement
  VIDE côté `telemetry_text` : le crochet attend le code que publiera le moteur, on ne
  devine pas son nom.

Vérifié sur appareil contre `.75` : réglage lu (0.9 / SOC 50 %), carte affichée,
campagne verte. L'assertion des cartes de charge fait désormais DÉFILER avant de
compter — dans un `ListView` ce qui n'est pas à l'écran n'est pas construit, et
chercher sans défiler ne prouvait que la hauteur de l'écran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HajXLUczEyZd22JeewRfff
2026-08-26 12:01:30 +02:00

337 lines
15 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" } }
},
"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" }
}
},
"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": "À 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" }
}
}
}