Patrick Schurig 71bb26c81b feat(saisie): l'horloge se qualifie, elle ne s'offre pas en réglage
« 12:00 heure de la box (Europe/London) — pas celle de cet appareil », et non
« Fuseau : Europe/Paris » : sous un champ, un intitulé de réglage laisse croire
qu'on choisit le fuseau DE CETTE échéance. Il n'y en a qu'un, il est système, et
il déplace d'un coup toutes les heures murales de l'installation — échéances,
frontière de la journée de reporting, et demain les plages tarifaires
(LM-1005-b-iii).

Affichage CONDITIONNEL : seulement quand les deux horloges divergent. C'est la
règle déjà appliquée deux fois cette semaine — ligne des ratios instantanés,
avertissement « partiel » du bilan — et sa raison tient en une phrase : un texte
permanent cesse d'être lu le jour exact où il devient vrai.

La divergence se DÉDUIT de ce que la box publie, sans base de fuseaux :
`periodStartedAt` est minuit de l'installation en epoch, donc rendu dans le
fuseau de l'appareil il tombe sur 00:00 si et seulement si les deux partagent le
même décalage. Exact pour l'instant présent, y compris les jours à 23 ou 25
heures — et c'est justement pourquoi nous avions demandé cette frontière plutôt
qu'un nom IANA.

Quatre silences testés : fuseaux identiques, échéance vide, box qui ne publie pas
la date, et l'intitulé « Fuseau : » qui ne doit jamais apparaître.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
2026-09-06 09:22:33 +02:00

484 lines
27 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"@@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": "Commandé, pas encore mesuré",
"@budgetEvReserved": {
"description": "budget.evReservedW. Le libellé précédent, « Réservé au véhicule », affirmait deux choses fausses : que c'est réservé, et que ça concerne le véhicule plutôt que la LATENCE de sa mesure. Définition livrée par le moteur le 2026-08-30, après que la part réseau d'EV_GRID_START en a été retirée — un champ à deux natures ne se documente pas. Ce n'est PAS un registre d'allocation : ne rien réconcilier dessus. La clé garde le nom du champ de la box, pas celui de l'ancienne affirmation."
},
"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" } }
},
"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 est hors de budget.allocatedW, et sans registre où la retrouver (evReservedW est une correction du budget, pas une destination — constaté 2026-08-30). Sans cette mention, l'utilisateur voit une charge servie alors que le surplus est nul et croit à un défaut."
},
"niveauEco": "ÉCO",
"@niveauEco": {
"description": "Étiquette du niveau plancher dans la décomposition d'une commande. Majuscules courtes : la carte en porte deux l'une sous l'autre."
},
"niveauConfort": "CONFORT",
"decisionEcoFloorGrid": "Plancher servi — {gridW} W achetés au réseau",
"@decisionEcoFloorGrid": {
"description": "ECO_FLOOR_GRID. L'achat est le fait actionnable : il coûte, contrairement au surplus.",
"placeholders": { "gridW": { "type": "int" } }
},
"decisionEcoFloorMet": "Plancher atteint — {cible} dus, {livre} livrés",
"@decisionEcoFloorMet": {
"description": "ECO_FLOOR_MET. Le SEUL état qui ferme l'obligation, et il est interdit sous le régime unmeasurable (R6) : on n'annonce pas tenu ce qu'on ne mesure pas. ⚠️ « X sur Y livrés » était FAUX une fois le plancher dépassé — vu sur appareil le 2026-09-06, « 29,9 kWh sur 3,0 kWh livrés », un numérateur dix fois le dénominateur. Le compteur continue de courir après que l'obligation est tenue : la fraction cesse alors d'en être une. On dit donc ce qui était DÛ, puis ce qui a été livré.",
"placeholders": { "livre": { "type": "String" }, "cible": { "type": "String" } }
},
"decisionEcoFloorMissed": "Obligation abandonnée à la bascule — {raison}",
"@decisionEcoFloorMissed": {
"description": "ECO_FLOOR_MISSED. La cause décide de la phrase ET du geste. Pas de cause « surplus insuffisant » : l'éco achète au réseau, donc le surplus ne peut jamais l'empêcher.",
"placeholders": { "raison": { "type": "String" } }
},
"ecoCauseDrawCap": "le plafond de soutirage l'a refusée",
"ecoCauseLoadCapacity": "la charge ne pouvait pas absorber davantage",
"ecoCauseUnmeasurable": "avancement non vérifiable — ni tenue, ni chiffrable",
"@ecoCauseUnmeasurable": {
"description": "Sous unmeasurable, ECO_FLOOR_MISSED sort TOUS LES JOURS par construction, R6 interdisant ECO_FLOOR_MET. Ne jamais écrire « non tenue » : on ne sait pas."
},
"ecoCauseUnavailable": "la charge était indisponible",
"ecoManque": " · il manquait {wh}",
"@ecoManque": {
"description": "missingWh, ABSENT sous unmeasurable — on ne sait pas ce qui a été livré, donc pas ce qui manquait ; un chiffre inventerait la grandeur dont l'absence est le problème.",
"placeholders": { "wh": { "type": "String" } }
},
"ecoAvancementMesure": "{livre} sur {cible} livrés",
"@ecoAvancementMesure": {
"placeholders": { "livre": { "type": "String" }, "cible": { "type": "String" } }
},
"ecoAvancementNonVerifiable": "reste {wh} à livrer — avancement non vérifiable",
"@ecoAvancementNonVerifiable": {
"description": "Sous unmeasurable, remainingWh == targetWh : rien n'est soustrait. « 4,0 sur 4,0 » veut dire « rien de mesurable », jamais « rien livré ».",
"placeholders": { "wh": { "type": "String" } }
},
"ecoMesureDepuis": "mesuré depuis {heure}",
"@ecoMesureDepuis": {
"placeholders": { "heure": { "type": "String" } }
},
"ecoMesureIntermittente": "mesuré par intermittence depuis {heure}",
"@ecoMesureIntermittente": {
"description": "measuredGaps présent. La date reste le PREMIER instant : redater à chaque retour effacerait l'histoire, se taire nierait le battement.",
"placeholders": { "heure": { "type": "String" } }
},
"ecoTauxConstant": "taux constant (cible ÷ jour) — sans mesure, rien ne le corrige",
"@ecoTauxConstant": {
"description": "LM-1013-b-v. NE JAMAIS écrire que l'achat va s'intensifier sous unmeasurable : depuis +etm55 il n'y a plus d'escalade. Et atMaximum:false n'y veut PAS dire « on n'est pas au plafond » — il veut dire qu'il n'y a AUCUN étalement, donc rien qui puisse cesser. Un booléen à false se lit spontanément comme rassurant ; c'est la nuance qu'un lecteur pressé inversera."
},
"ecoAuMaximum": "au maximum de la charge",
"ecoAchete": "{wh} achetés pour cette obligation",
"@ecoAchete": {
"description": "purchasedWh. Publié dans TOUS les régimes : c'est la commande du moteur, donc un fait connu sans mesure. Ne coïncide avec le livré que quand tout va bien — c'est sa divergence qui informe.",
"placeholders": { "wh": { "type": "String" } }
},
"obligationTitre": "Obligation quotidienne",
"obligationSousTitre": "Le plancher que la box servira quoi qu'il arrive, au réseau s'il le faut.",
"obligationEnergie": "Énergie due par jour",
"obligationEcheance": "Avant",
"obligationEcheanceHorloge": "{heure} heure de la box ({fuseau}) — pas celle de cet appareil.",
"@obligationEcheanceHorloge": {
"description": "QUALIFIE UNE HORLOGE, N'OFFRE PAS UN CHOIX. « Fuseau : Europe/Paris » sous un champ laisserait croire qu'on règle le fuseau DE CETTE ÉCHÉANCE : il n'y en a qu'un, c'est un réglage système, et il déplace d'un coup toutes les heures murales de l'installation — échéances, frontière de journée de reporting, et demain les plages tarifaires (LM-1005-b-iii). D'où une phrase qui dit l'heure lue, pas un réglage offert.\n\nAFFICHAGE CONDITIONNEL : seulement quand le fuseau de la box diffère de celui de l'appareil. Sinon c'est un texte permanent — et un avertissement permanent cesse d'être lu le jour exact où il devient vrai.",
"placeholders": { "heure": { "type": "String" }, "fuseau": { "type": "String" } }
},
"obligationNonMesurableTitre": "Cette obligation ne pourra jamais être close",
"@obligationNonMesurableTitre": {
"description": "Sous unmeasurable : ni tenue (R6 interdit ECO_FLOOR_MET), ni chiffrée dans son manque (missingWh absent). ECO_FLOOR_MISSED sortira tous les jours par construction."
},
"obligationNonMesurableCorps": "La box ne saura pas mesurer ce qu'elle livre ici : l'obligation ne pourra être annoncée ni tenue, ni manquée d'une quantité connue. Elle sera achetée au réseau à taux constant — {cout} par jour — sans que rien ne vienne le corriger.",
"@obligationNonMesurableCorps": {
"description": "Le coût se CHIFFRE, il ne s'estime pas : cible ÷ jour depuis +etm55, et purchasedWh le recoupe à 0,3 % une fois l'obligation en route.",
"placeholders": { "cout": { "type": "String" } }
},
"obligationCauseNoMeter": "Aucun compteur n'est rattaché à cette charge. Rattachez-en un ci-dessus : c'est la cause la plus fréquente, et elle se répare en une manipulation.",
"obligationCauseNoEnergyMeter": "Le compteur rattaché publie une puissance, pas une énergie. Il faut un compteur qui totalise les kWh — la puissance seule ne suffit pas à mesurer ce qui a été livré.",
"obligationCauseNoSessionEnergy": "Cette borne ne publie pas l'énergie de session. C'est le matériel : aucun réglage ne le changera, et il vaut mieux le savoir maintenant qu'après avoir cherché.",
"obligationCauseInconnue": "La box ne dit pas pourquoi (cause « {cause} » inconnue de cette version).",
"@obligationCauseInconnue": {
"placeholders": { "cause": { "type": "String" } }
},
"ecoIncoherenceTenue": "Incohérence de la box : plancher annoncé atteint alors que l'avancement n'est pas mesurable",
"@ecoIncoherenceTenue": {
"description": "R6 est armée des deux côtés : ECO_FLOOR_MET ne peut pas sortir sous le régime unmeasurable. Si la combinaison arrive quand même, l'écran la DÉNONCE au lieu de la rendre — servie oui, tenue jamais."
},
"confortEnAttente": "En attente — {motif}",
"@confortEnAttente": {
"placeholders": { "motif": { "type": "String" } }
},
"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" }
}
},
"decisionEvGridStart": "Démarrage soutiré : {grid} W achetés au réseau pour compléter les {budget} W de surplus et atteindre le plancher de {floor} W.",
"@decisionEvGridStart": {
"description": "EV_GRID_START. La borne démarre alors que le surplus ne paie pas son plancher : acquisitionTolerance autorise l'appoint. gridW = floorW − budgetW est CE QUI EST ACHETÉ — c'est le chiffre qui fait de cette ligne une décision et non un effet de bord, donc il vient en tête. Motif préparé mais JAMAIS vu sur machine au 2026-08-27 : ne rien bâtir autour.",
"placeholders": {
"grid": { "type": "int", "format": "decimalPattern" },
"budget": { "type": "int", "format": "decimalPattern" },
"floor": { "type": "int", "format": "decimalPattern" }
}
},
"decisionPhaseLimit": "Limite de phase atteinte : {limit} W disponibles pour {required} W exigés — ce n'est pas le surplus qui manque.",
"@decisionPhaseLimit": {
"description": "PHASE_LIMIT. Deux causes, deux gestes : la limite de phase se règle en délestant la maison ou en revoyant l'abonnement, le manque de surplus s'attend. Confondre les deux envoie chercher du soleil là où il faut lire un compteur — d'où la seconde moitié de la phrase.",
"placeholders": {
"limit": { "type": "int", "format": "decimalPattern" },
"required": { "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é n'existe plus dans nymea — retiré, ou jamais réinstallé après un remplacement. La charge, elle, est toujours déclarée : c'est pour ça qu'elle apparaît ici.",
"@faultThingMissing": {
"description": "À NE PAS lire comme « votre configuration est cassée ». Le cas normal est un appareil physiquement remplacé : le Thing disparaît, l'entrée LoadConfig reste — le plugin crée d'office une entrée par borne détectée mais ne la retire pas quand le Thing s'en va. Constaté sur .75 le 2026-08-28 avec une ABB Terra AC remplacée par une V2C."
},
"faultThingMissingAction": "Rien à lever ici : le défaut ne partira pas tant qu'aucun appareil ne répond. Réinstallez-le dans nymea, ou retirez cette charge de la configuration.",
"@faultThingMissingAction": {
"description": "Remplace le bouton « Lever le défaut » sur THING_MISSING. ClearLoadFault y répondrait EnergyErrorNoError sans rien changer, et le défaut reviendrait au cycle suivant — un bouton qui ne peut pas tenir sa promesse est pire que pas de bouton."
},
"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" }
}
}
}