Un rapport vivant sur l'etat du parc batterie

Genere par script pour que les mesures restent en tete du fichier et
la mise en page en dessous : mettre a jour une valeur, relancer,
republier.

La figure centrale trace l'amplitude balayee par chaque cellule. C'est
la grandeur qui separe une cellule saine d'une cellule morte sans
demonter quoi que ce soit : une cellule ayant perdu sa capacite se
remplit et se vide avant ses voisines, donc elle balaye une plage bien
plus large a courant identique.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
Tallagrand Thierry
2026-09-05 13:43:55 +02:00
parent 8e2b4f3e34
commit 20b21dd46d
3 changed files with 576 additions and 0 deletions

View File

@@ -323,3 +323,24 @@ heure_reelle,etat,courant_mA,c1_mV,c2_mV,c3_mV,c4_mV,c5_mV,c6_mV,c7_mV,c8_mV,c9_
2026-09-05T11:41:18.038Z,module1,,3385,3386,3386,3383,3388,3487,3460,3386,3388,3385,3410,3462,3388,3522,3386,139 2026-09-05T11:41:18.038Z,module1,,3385,3386,3386,3383,3388,3487,3460,3386,3388,3385,3410,3462,3388,3522,3386,139
2026-09-05T11:41:18.195Z,module2,,3416,3415,3416,3416,3416,3412,3415,3414,3416,3402,3414,3416,3416,3413,3412,14 2026-09-05T11:41:18.195Z,module2,,3416,3415,3416,3416,3416,3412,3415,3414,3416,3402,3414,3416,3416,3413,3412,14
2026-09-05T11:41:18.382Z,module3,,3394,3418,3391,3392,3392,3480,3391,3392,3394,3425,3395,3494,3392,3394,3486,103 2026-09-05T11:41:18.382Z,module3,,3394,3418,3391,3392,3392,3480,3391,3392,3394,3425,3395,3494,3392,3394,3486,103
2026-09-05T11:41:38.528Z,module1,,3385,3387,3387,3384,3389,3486,3460,3387,3389,3386,3411,3461,3389,3522,3386,138
2026-09-05T11:41:38.686Z,module2,,3416,3415,3416,3416,3416,3412,3415,3414,3416,3402,3414,3416,3416,3413,3412,14
2026-09-05T11:41:38.843Z,module3,,3395,3419,3392,3393,3393,3479,3391,3393,3394,3424,3396,3492,3393,3395,3484,101
2026-09-05T11:41:58.990Z,module1,,3385,3386,3386,3384,3388,3482,3457,3387,3389,3386,3410,3458,3389,3519,3386,135
2026-09-05T11:41:59.147Z,module2,,3415,3413,3415,3415,3414,3410,3414,3413,3414,3400,3413,3415,3415,3412,3410,15
2026-09-05T11:41:59.302Z,module3,,3395,3419,3392,3393,3393,3476,3392,3393,3395,3422,3396,3489,3393,3395,3481,97
2026-09-05T11:42:19.449Z,module1,,3385,3386,3386,3383,3388,3480,3456,3387,3389,3385,3410,3456,3389,3518,3385,135
2026-09-05T11:42:19.606Z,module2,,3415,3413,3415,3415,3415,3410,3414,3412,3414,3401,3412,3415,3415,3412,3411,14
2026-09-05T11:42:19.761Z,module3,,3396,3420,3392,3394,3393,3474,3392,3394,3395,3420,3396,3487,3393,3395,3479,95
2026-09-05T11:42:39.905Z,module1,,3384,3386,3386,3384,3389,3476,3454,3387,3389,3386,3409,3453,3389,3516,3386,132
2026-09-05T11:42:40.063Z,module2,,3416,3414,3416,3416,3416,3411,3415,3414,3416,3402,3413,3416,3416,3413,3413,14
2026-09-05T11:42:40.220Z,module3,,3396,3420,3392,3394,3393,3472,3392,3394,3395,3418,3396,3484,3394,3395,3476,92
2026-09-05T11:43:00.363Z,module1,,3387,3388,3388,3386,3391,3478,3455,3389,3391,3388,3411,3455,3391,3518,3388,132
2026-09-05T11:43:00.579Z,module2,,3417,3415,3417,3417,3417,3413,3416,3414,3416,3403,3415,3417,3417,3414,3413,14
2026-09-05T11:43:00.737Z,module3,,3397,3421,3393,3395,3394,3471,3393,3395,3397,3417,3397,3482,3395,3396,3474,89
2026-09-05T11:43:20.866Z,module1,,3388,3389,3389,3387,3392,3478,3456,3390,3392,3389,3413,3456,3392,3518,3389,131
2026-09-05T11:43:21.022Z,module2,,3418,3416,3418,3418,3417,3413,3416,3415,3417,3403,3415,3417,3417,3414,3414,15
2026-09-05T11:43:21.179Z,module3,,3397,3422,3393,3395,3394,3469,3394,3395,3397,3415,3397,3480,3395,3397,3473,87
2026-09-05T11:43:41.325Z,module1,,3389,3390,3390,3388,3392,3477,3455,3391,3393,3390,3414,3454,3393,3517,3390,129
2026-09-05T11:43:41.483Z,module2,,3418,3416,3418,3418,3417,3413,3416,3415,3417,3403,3415,3417,3417,3415,3414,15
2026-09-05T11:43:41.640Z,module3,,3395,3418,3391,3393,3392,3464,3392,3393,3395,3408,3395,3473,3393,3395,3466,82
1 heure_reelle etat courant_mA c1_mV c2_mV c3_mV c4_mV c5_mV c6_mV c7_mV c8_mV c9_mV c10_mV c11_mV c12_mV c13_mV c14_mV c15_mV ecart_mV
323 2026-09-05T11:41:18.038Z module1 3385 3386 3386 3383 3388 3487 3460 3386 3388 3385 3410 3462 3388 3522 3386 139
324 2026-09-05T11:41:18.195Z module2 3416 3415 3416 3416 3416 3412 3415 3414 3416 3402 3414 3416 3416 3413 3412 14
325 2026-09-05T11:41:18.382Z module3 3394 3418 3391 3392 3392 3480 3391 3392 3394 3425 3395 3494 3392 3394 3486 103
326 2026-09-05T11:41:38.528Z module1 3385 3387 3387 3384 3389 3486 3460 3387 3389 3386 3411 3461 3389 3522 3386 138
327 2026-09-05T11:41:38.686Z module2 3416 3415 3416 3416 3416 3412 3415 3414 3416 3402 3414 3416 3416 3413 3412 14
328 2026-09-05T11:41:38.843Z module3 3395 3419 3392 3393 3393 3479 3391 3393 3394 3424 3396 3492 3393 3395 3484 101
329 2026-09-05T11:41:58.990Z module1 3385 3386 3386 3384 3388 3482 3457 3387 3389 3386 3410 3458 3389 3519 3386 135
330 2026-09-05T11:41:59.147Z module2 3415 3413 3415 3415 3414 3410 3414 3413 3414 3400 3413 3415 3415 3412 3410 15
331 2026-09-05T11:41:59.302Z module3 3395 3419 3392 3393 3393 3476 3392 3393 3395 3422 3396 3489 3393 3395 3481 97
332 2026-09-05T11:42:19.449Z module1 3385 3386 3386 3383 3388 3480 3456 3387 3389 3385 3410 3456 3389 3518 3385 135
333 2026-09-05T11:42:19.606Z module2 3415 3413 3415 3415 3415 3410 3414 3412 3414 3401 3412 3415 3415 3412 3411 14
334 2026-09-05T11:42:19.761Z module3 3396 3420 3392 3394 3393 3474 3392 3394 3395 3420 3396 3487 3393 3395 3479 95
335 2026-09-05T11:42:39.905Z module1 3384 3386 3386 3384 3389 3476 3454 3387 3389 3386 3409 3453 3389 3516 3386 132
336 2026-09-05T11:42:40.063Z module2 3416 3414 3416 3416 3416 3411 3415 3414 3416 3402 3413 3416 3416 3413 3413 14
337 2026-09-05T11:42:40.220Z module3 3396 3420 3392 3394 3393 3472 3392 3394 3395 3418 3396 3484 3394 3395 3476 92
338 2026-09-05T11:43:00.363Z module1 3387 3388 3388 3386 3391 3478 3455 3389 3391 3388 3411 3455 3391 3518 3388 132
339 2026-09-05T11:43:00.579Z module2 3417 3415 3417 3417 3417 3413 3416 3414 3416 3403 3415 3417 3417 3414 3413 14
340 2026-09-05T11:43:00.737Z module3 3397 3421 3393 3395 3394 3471 3393 3395 3397 3417 3397 3482 3395 3396 3474 89
341 2026-09-05T11:43:20.866Z module1 3388 3389 3389 3387 3392 3478 3456 3390 3392 3389 3413 3456 3392 3518 3389 131
342 2026-09-05T11:43:21.022Z module2 3418 3416 3418 3418 3417 3413 3416 3415 3417 3403 3415 3417 3417 3414 3414 15
343 2026-09-05T11:43:21.179Z module3 3397 3422 3393 3395 3394 3469 3394 3395 3397 3415 3397 3480 3395 3397 3473 87
344 2026-09-05T11:43:41.325Z module1 3389 3390 3390 3388 3392 3477 3455 3391 3393 3390 3414 3454 3393 3517 3390 129
345 2026-09-05T11:43:41.483Z module2 3418 3416 3418 3418 3417 3413 3416 3415 3417 3403 3415 3417 3417 3415 3414 15
346 2026-09-05T11:43:41.640Z module3 3395 3418 3391 3393 3392 3464 3392 3393 3395 3408 3395 3473 3393 3395 3466 82

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,326 @@
# -*- coding: utf-8 -*-
"""Genere le rapport sur l'etat du parc batterie.
Rejouable : les mesures vivent dans les constantes en tete, la mise en page
en dessous. Mettre a jour une mesure, relancer, republier.
"""
import pathlib, datetime
MAJ = "5 septembre 2026, 14 h"
# Amplitude min->max de chaque cellule, relevee sur l'historique du BMS.
# C'est LA grandeur qui separe une cellule saine d'une cellule morte : une
# cellule qui a perdu sa capacite se remplit et se vide avant ses voisines,
# donc elle balaye une plage bien plus large a courant identique.
MODULES = [
("K220803C30160322", "pack ancien · adresse 1", "1681 relevés, 14/08 → 05/09", [
182,178,178,178,177,548,371,181,179,179,182,691,178,251,178]),
("K220803C30160319", "pack ancien · adresse 3", "1798 relevés, 30/07 → 05/09", [
184,521,187,186,183,318,185,186,187,473,187,445,186,189,424]),
("K220803C30160324", "isolée (BAT3) · adresse 4", "322 relevés, 23/07 → 05/09", [
138,124,138,137,139,132,134,142,143,135,136,137,128,135,683]),
]
PARC = [
("K220803C30160322", "ancien", "1", "3", "6 · 7 · 12", 498, 86, "en service"),
("—", "ancien", "2", "0", "—", 0, 0, "en service"),
("K220803C30160319", "ancien", "3", "5", "2 · 6 · 10 · 12 · 15", 416, 179, "en service"),
("K220803C30160324", "ancien", "4", "1", "15", 29, 59, "isolée"),
("Y230202C30371426", "récent", "1", "0", "—", 1, 1, "en service"),
("Y230202C30371425", "récent", "2", "0", "—", 1, 1, "en service"),
("Y230202C30371431", "récent", "3", "0", "—", 1, 1, "en service"),
("Y230202C30371427", "récent", "4", "0", "—", 1, 0, "en service"),
]
CHRONO = [
("13:04:48", "cellule 7 du module …0322 à <b>3526 mV</b>, cellule 6 du module …0319 à <b>3541</b>", "sous le seuil"),
("13:05:08", "les mêmes à <b>3555</b> et <b>3575 mV</b>", "niveau 3 franchi"),
("13:05:13", "le BMS abaisse le courant de charge autorisé de <b>50 à 30 A</b>", "cinq secondes plus tard"),
("13:15:10", "les cellules redescendent à 3499 et 3522 mV, l’alarme repasse au vert", "le plafond, lui, reste à 30 A"),
("13:22:40", "plafond <b>30 → 40 A</b>", "après 7 min 31 s"),
("13:26:39", "plafond <b>40 → 35 A</b>, meneuses à 3563 et 3548 mV", "il redéclenche"),
("13:38:10", "plafond <b>35 → 40 A</b>", "après 11 min 31 s"),
]
SEUILS = [(3480, "niveau 1"), (3520, "niveau 2"), (3550, "niveau 3 — le courant se réduit"), (3600, "niveau 4")]
def couleur(a):
if a >= 600: return "var(--mort)"
if a >= 400: return "var(--grave)"
if a >= 250: return "var(--attention)"
return "var(--sain)"
def figure():
"""Barres horizontales : amplitude de chaque cellule, par module."""
LARG, LIGNE, GAUCHE, HAUT = 760, 15, 96, 26
ech = 0.86 # px par mV
out, y = [], 0
for code, place, _n, amps in MODULES:
out.append(f'<text x="0" y="{y+13}" class="fm">{code[-4:]}</text>'
f'<text x="0" y="{y+25}" class="fp">{place}</text>')
y += HAUT
for i, a in enumerate(amps, 1):
larg = a * ech
out.append(
f'<text x="{GAUCHE-8}" y="{y+10}" class="fc">{i}</text>'
f'<rect x="{GAUCHE}" y="{y+2}" width="{larg:.1f}" height="10" rx="2" fill="{couleur(a)}"/>'
f'<text x="{GAUCHE+larg+6}" y="{y+11}" class="fv">{a}</text>')
y += LIGNE
y += 14
reperes = []
for mv in (200, 400, 600):
x = GAUCHE + mv * ech
reperes.append(f'<line x1="{x}" y1="0" x2="{x}" y2="{y-14}" class="rep"/>'
f'<text x="{x}" y="{y-2}" class="fr">{mv} mV</text>')
return (f'<svg viewBox="0 0 {LARG} {y+4}" role="img" '
f'aria-label="Amplitude de chaque cellule, par module">'
+ "".join(reperes) + "".join(out) + '</svg>')
lignes_parc = "".join(
f'<tr class="{"mal" if int(n) else ""}"><td class="mono">{c}</td><td>{p}</td>'
f'<td class="num">{a}</td><td class="num gros">{n}</td><td class="cel">{d}</td>'
f'<td class="num">{hv}</td><td class="num">{ov}</td><td class="etat">{e}</td></tr>'
for c, p, a, n, d, hv, ov, e in PARC)
lignes_chrono = "".join(
f'<tr><td class="mono h">{h}</td><td>{q}</td><td class="note">{n}</td></tr>'
for h, q, n in CHRONO)
lignes_seuils = "".join(
f'<li><span class="mono">{mv} mV</span> {nom}</li>' for mv, nom in SEUILS)
HTML = f"""<title>Les cent cinq cellules</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Chivo:wght@400;700;900&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&family=JetBrains+Mono:wght@400;700&display=swap">
<style>
:root {{
--fond:#F6F8F7; --carte:#FFFFFF; --encre:#14191B; --sourd:#5B6669;
--trait:#DCE2E1; --trait-fort:#B9C3C1;
--sain:#2E7D62; --attention:#A8760C; --grave:#C2521E; --mort:#9B1D20;
--lueur:rgba(46,125,98,.08);
}}
@media (prefers-color-scheme: dark) {{
:root:not([data-theme="light"]) {{
--fond:#0E1112; --carte:#161A1B; --encre:#E7EBEA; --sourd:#97A2A4;
--trait:#252B2C; --trait-fort:#3A4344;
--sain:#5BB894; --attention:#D9A63C; --grave:#E8794A; --mort:#E05C60;
--lueur:rgba(91,184,148,.10);
}}
}}
:root[data-theme="dark"] {{
--fond:#0E1112; --carte:#161A1B; --encre:#E7EBEA; --sourd:#97A2A4;
--trait:#252B2C; --trait-fort:#3A4344;
--sain:#5BB894; --attention:#D9A63C; --grave:#E8794A; --mort:#E05C60;
--lueur:rgba(91,184,148,.10);
}}
* {{ box-sizing:border-box; }}
body {{ background:var(--fond); color:var(--encre);
font:400 17px/1.62 "Source Serif 4", Georgia, serif; margin:0;
-webkit-font-smoothing:antialiased; }}
.page {{ max-width:1000px; margin:0 auto; padding:0 24px 96px; }}
.mesure {{ max-width:64ch; }}
h1, h2, h3, .fm, .etiq {{ font-family:Chivo, "Helvetica Neue", Arial, sans-serif; }}
h1 {{ font-weight:900; font-size:clamp(2.4rem,6.5vw,4rem); line-height:1.02;
letter-spacing:-.025em; margin:0 0 .4em; text-wrap:balance; }}
h2 {{ font-weight:700; font-size:1.5rem; letter-spacing:-.012em;
margin:4.2rem 0 .2em; text-wrap:balance; }}
h2 .num {{ color:var(--sourd); font-weight:400; margin-right:.5em;
font-variant-numeric:tabular-nums; }}
h3 {{ font-weight:700; font-size:1.05rem; margin:2.4rem 0 .4em; }}
p {{ margin:0 0 1.05em; }}
a {{ color:var(--sain); }}
strong, b {{ font-weight:600; }}
.mono {{ font-family:"JetBrains Mono", ui-monospace, monospace; font-size:.86em;
font-variant-numeric:tabular-nums; }}
header {{ padding:72px 0 8px; border-bottom:1px solid var(--trait); margin-bottom:2.5rem; }}
.etiq {{ font-size:.72rem; font-weight:700; letter-spacing:.14em;
text-transform:uppercase; color:var(--sourd); margin-bottom:1.6rem; }}
.chapeau {{ font-size:1.24rem; line-height:1.5; color:var(--sourd);
max-width:56ch; margin-bottom:2rem; }}
.maj {{ font-size:.8rem; color:var(--sourd); padding-bottom:1.4rem; }}
.verdict {{ display:grid; gap:1px; background:var(--trait);
grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
border:1px solid var(--trait); border-radius:10px; overflow:hidden; margin:2rem 0 0; }}
.verdict div {{ background:var(--carte); padding:18px 20px; }}
.verdict .n {{ font-family:Chivo,sans-serif; font-weight:900; font-size:2.3rem;
line-height:1; letter-spacing:-.03em; font-variant-numeric:tabular-nums; }}
.verdict .l {{ font-size:.86rem; color:var(--sourd); margin-top:.45em; line-height:1.35; }}
figure {{ margin:2rem 0; }}
.cadre {{ background:var(--carte); border:1px solid var(--trait); border-radius:10px;
padding:22px 20px; overflow-x:auto; }}
svg {{ display:block; min-width:640px; width:100%; height:auto; }}
.fm {{ font-size:12.5px; font-weight:700; fill:var(--encre); }}
.fp {{ font-size:10.5px; fill:var(--sourd); font-family:"Source Serif 4",serif; }}
.fc {{ font-size:9.5px; fill:var(--sourd); text-anchor:end;
font-family:"JetBrains Mono",monospace; }}
.fv {{ font-size:9.5px; fill:var(--sourd); font-family:"JetBrains Mono",monospace; }}
.fr {{ font-size:9.5px; fill:var(--sourd); text-anchor:middle;
font-family:"JetBrains Mono",monospace; }}
.rep {{ stroke:var(--trait-fort); stroke-width:1; stroke-dasharray:2 4; }}
figcaption {{ font-size:.88rem; color:var(--sourd); margin-top:.9rem; max-width:62ch; }}
.tabl {{ overflow-x:auto; margin:1.6rem 0; border:1px solid var(--trait);
border-radius:10px; background:var(--carte); }}
table {{ border-collapse:collapse; width:100%; min-width:660px;
font-family:"Source Serif 4",serif; font-size:.94rem; }}
th {{ font-family:Chivo,sans-serif; font-size:.7rem; font-weight:700;
letter-spacing:.09em; text-transform:uppercase; color:var(--sourd);
text-align:left; padding:13px 14px; border-bottom:1px solid var(--trait); }}
td {{ padding:11px 14px; border-bottom:1px solid var(--trait); vertical-align:top; }}
tr:last-child td {{ border-bottom:none; }}
tr.mal td {{ background:var(--lueur); }}
.num {{ text-align:right; font-variant-numeric:tabular-nums;
font-family:"JetBrains Mono",monospace; font-size:.85rem; }}
.gros {{ font-weight:700; font-size:1rem; }}
.cel {{ font-family:"JetBrains Mono",monospace; font-size:.8rem; color:var(--sourd); }}
.etat {{ font-size:.85rem; color:var(--sourd); }}
.h {{ font-weight:700; white-space:nowrap; }}
.note {{ color:var(--sourd); font-size:.88rem; }}
.legende {{ display:flex; flex-wrap:wrap; gap:18px; font-size:.85rem;
color:var(--sourd); margin-top:1rem; }}
.legende span {{ display:inline-flex; align-items:center; gap:7px; }}
.pastille {{ width:11px; height:11px; border-radius:3px; display:inline-block; }}
.encart {{ border-left:3px solid var(--grave); background:var(--carte);
padding:16px 20px; margin:1.8rem 0; border-radius:0 8px 8px 0; }}
.encart p:last-child {{ margin-bottom:0; }}
ul {{ padding-left:1.2em; }} li {{ margin-bottom:.45em; }}
footer {{ margin-top:5rem; padding-top:1.6rem; border-top:1px solid var(--trait);
font-size:.85rem; color:var(--sourd); }}
</style>
<div class="page">
<header>
<div class="etiq">Parc Pylontech · diagnostic en cours</div>
<h1>Les cent cinq cellules</h1>
<p class="chapeau">Huit modules, quinze cellules chacun. Neuf de ces cellules
brident la charge des cent cinq autres. Voici lesquelles, comment on le sait,
et ce qu’on peut en faire.</p>
<div class="maj">Dernière mise à jour&nbsp;: {MAJ}</div>
</header>
<div class="verdict">
<div><div class="n">9</div><div class="l">cellules atteintes sur 105</div></div>
<div><div class="n">3</div><div class="l">modules concernés sur 8</div></div>
<div><div class="n">5&nbsp;s</div><div class="l">entre le franchissement du seuil et la coupure</div></div>
<div><div class="n">30&nbsp;A</div><div class="l">plafond imposé au parc entier, pour 150 A nominaux</div></div>
</div>
<div class="mesure">
<h2><span class="num">1</span>Le verdict</h2>
<p>Le parc n’est pas usé. Quatre modules sur huit sont <strong>irréprochables</strong> :
soixante cellules sans une seule anomalie, après plus de trois cents cycles. Un
cinquième l’est tout autant.</p>
<p>Trois modules portent l’ensemble du problème, et à l’intérieur de ces trois,
<strong>neuf cellules</strong>. Quand l’une d’elles atteint 3550 mV, le BMS réduit
le courant de charge — non pas de son module, mais <strong>de tout le parc</strong>.
Cinq modules sains sont bridés par neuf cellules.</p>
</div>
<figure>
<div class="cadre">{figure()}</div>
<div class="legende">
<span><i class="pastille" style="background:var(--sain)"></i>saine</span>
<span><i class="pastille" style="background:var(--attention)"></i>décalée</span>
<span><i class="pastille" style="background:var(--grave)"></i>gravement atteinte</span>
<span><i class="pastille" style="background:var(--mort)"></i>terminale</span>
</div>
<figcaption>Amplitude balayée par chaque cellule entre sa tension minimale et
sa tension maximale sur la période d’observation. Une cellule saine suit ses
voisines à quelques millivolts près. Une cellule qui a perdu de la capacité se
remplit et se vide avant elles, donc elle balaye une plage bien plus large à
courant identique — c’est le signe qui ne trompe pas, et il ne demande aucun
démontage.</figcaption>
</figure>
<div class="mesure">
<h2><span class="num">2</span>Le parc, module par module</h2>
<p>L’identité d’un module est son code-barres, jamais son adresse&nbsp;: chaque
chaîne renumérote ses modules à partir de 1, il existe donc deux «&nbsp;module
1&nbsp;» dans cette installation.</p>
</div>
<div class="tabl"><table>
<thead><tr><th>Code-barres</th><th>Pack</th><th>Adr.</th><th>Cellules atteintes</th>
<th>Lesquelles</th><th>Surtensions</th><th>Sous-tensions</th><th></th></tr></thead>
<tbody>{lignes_parc}</tbody>
</table></div>
<div class="mesure">
<p>Le contraste est net&nbsp;: <strong>498 surtensions</strong> pour le premier
module contre <strong>1</strong> pour chacun des quatre modules du pack récent,
qui ont pourtant encaissé dix fois plus de cycles.</p>
<h2><span class="num">3</span>Le déclenchement, chronométré</h2>
<p>Le 5 septembre, la console a été branchée sur les trois modules du pack ancien
pendant que l’alarme montait. Ce qui suit est le relevé, à la seconde.</p>
</div>
<div class="tabl"><table>
<thead><tr><th>Heure</th><th>Ce qui se passe</th><th></th></tr></thead>
<tbody>{lignes_chrono}</tbody>
</table></div>
<div class="mesure">
<div class="encart">
<p><strong>La découverte la plus utile de la journée</strong> tient dans la
quatrième ligne&nbsp;: l’alarme repasse au vert bien avant que le plafond ne
remonte. Chercher l’alarme dans les journaux fait donc manquer l’essentiel du
phénomène — elle dure quelques minutes, le bridage dure des heures.</p>
</div>
<p>Les seuils ne sont pas des valeurs choisies&nbsp;: ce sont ceux que le
matériel déclare lui-même.</p>
<ul>{lignes_seuils}</ul>
<h2><span class="num">4</span>Ce qui a été essayé</h2>
<p>La consigne de tension de charge a été abaissée de <span class="mono">51,3</span>
à <span class="mono">51,0 V</span> pour voir si le contournement suffisait à ne
plus jamais déclencher.</p>
<p><strong>Il ne suffit pas.</strong> À 92&nbsp;% de charge, les cellules meneuses
ont atteint 3563 et 3548 mV malgré la consigne abaissée, et le plafond a
recommencé à osciller entre 35 et 40 A. Le système ne reste pas «&nbsp;bloqué&nbsp;»
à une valeur&nbsp;: il cherche en permanence le courant maximal que la cellule la
plus faible tolère.</p>
<p>Une mesure explique pourquoi la tension seule ne peut pas régler la question.
À tension de parc identique, la cellule meneuse était à <span class="mono">3555 mV
sous 50 A</span> et à <span class="mono">3496 mV sous 30 A</span>. Son excursion
est donc largement produite par sa résistance interne multipliée par le courant,
et pas seulement par son état de charge.</p>
<h2><span class="num">5</span>D’où viennent les dégâts</h2>
<p>Pas de l’usure. Le pack récent a encaissé trois cent quarante cycles en un an
et ses soixante cellules sont intactes&nbsp;: cycler n’abîme pas ces modules.</p>
<p>Les deux cellules terminales du parc sont descendues à
<span class="mono">2953</span> et <span class="mono">2939 mV</span>, sous le seuil
d’alarme basse. Un accumulateur au lithium-fer-phosphate mené là sous charge perd
de la capacité de façon irréversible. <strong>Ces cellules n’ont pas vieilli,
elles ont été vidées trop bas.</strong></p>
<div class="encart">
<p>Et il y a une boucle à rompre. La jauge d’état de charge du parc suit la
cellule la plus faible. Quand le système croit toucher son plancher de sécurité
à 22&nbsp;%, cette cellule est déjà bien plus bas que le chiffre ne le laisse
croire. <strong>Le plancher est exprimé dans une grandeur que la cellule
endommagée fausse elle-même</strong>&nbsp;: il ne protège pas ce qu’il croit
protéger, et rien n’empêche le même mécanisme d’atteindre le pack sain.</p>
</div>
<h2><span class="num">6</span>Ce qui vient</h2>
<p>Une alimentation stabilisée permettra de reprendre le rééquilibrage au banc,
en commençant par le module isolé&nbsp;: il n’a qu’une cellule à traiter, il est
hors service, et rien de ce qu’on y tentera ne peut nuire au parc.</p>
<p>Une limite à garder en tête&nbsp;: un rééquilibrage corrige un <em>décalage</em>
de charge, pas une <em>perte</em> de capacité. Les cellules modérément décalées
sont de bonnes candidates. Les deux cellules terminales le sont beaucoup moins —
et c’est précisément ce que l’essai sur le module isolé dira, sans risque.</p>
</div>
<footer>Relevés faits sur la console série des BMS Pylontech, que ni Home
Assistant ni le GX Victron n’exposent&nbsp;: le système ne publie que l’agrégat
du parc. Ce rapport évolue au fil des travaux.</footer>
</div>
"""
p = pathlib.Path("docs/pages/rapport-batteries.html")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(HTML, encoding="utf-8")
print(f"{p} {len(HTML)} caracteres")