From 35808f17bd87c11b01ffc2075511b59836c436ec Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Mon, 19 Oct 2026 08:43:30 +0200 Subject: [PATCH] =?UTF-8?q?test(decisions):=20comparateur=20--sous-palier,?= =?UTF-8?q?=20seul=20ECO=5FFLOOR=5FGRID=20=E2=86=92=20ECO=5FFLOOR=5FBELOW?= =?UTF-8?q?=5FSTAGE=20est=20admis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retire le motif des niveaux « eco » portant l'un des deux codes et les confronte deux à deux : tout autre passage, ou un ECO_FLOOR_GRID aux paramètres modifiés, est un ÉCART. targetW, counts, progress du niveau restent comparés. Les changements sont listés par scénario et par charge. Co-Authored-By: Claude Opus 5.5 --- .../etm-powersync-compare-hors-motifs.py | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/tests/auto/decisions/etm-powersync-compare-hors-motifs.py b/tests/auto/decisions/etm-powersync-compare-hors-motifs.py index 5da79fb..e01cd8d 100755 --- a/tests/auto/decisions/etm-powersync-compare-hors-motifs.py +++ b/tests/auto/decisions/etm-powersync-compare-hors-motifs.py @@ -2,7 +2,8 @@ # Comparateur « IDENTIQUE HORS MOTIFS » (correctifs moteur a et b, etm70). # # etm-powersync-compare-hors-motifs.py [--cas ] -# [--achat] [--strategie] [--lignes ] +# [--achat] [--strategie] [--sous-palier] +# [--lignes ] # # Un lot qui ne change QUE des motifs doit laisser tout le reste de la référence `decisions` # intact à l'octet près. Ce script retire de chaque ligne, et SEULEMENT d'elle : @@ -15,6 +16,11 @@ # change cette VALEUR PUBLIÉE sans toucher aux consignes doit passer ce mode-là. # Avec --strategie (brief 4 T6, décision du 2026-10-17), il ramène EN PLUS `plan.strategy` # `rule-based` à `rules` d'une ligne de plan direct : le renommage du plan rule-based, et lui seul. +# Avec --sous-palier (lot éco sous palier, 2026-10-19), il retire EN PLUS le motif des niveaux +# « eco » portant ECO_FLOOR_GRID ou ECO_FLOOR_BELOW_STAGE, puis les confronte deux à deux : seul +# ECO_FLOOR_GRID → ECO_FLOOR_BELOW_STAGE est admis ; un ECO_FLOOR_GRID dont les paramètres +# changent, ou tout autre passage, est un ÉCART. Le reste du niveau (targetW, counts, progress) +# reste comparé. # Tout le reste — consignes, powerW, estimatedPowerW, états, relais, courants, budget, draw, # counts, niveaux et leurs motifs, avancement — doit être IDENTIQUE. Code 0 si c'est le cas, # 1 sinon (et le premier écart est montré). La liste des motifs qui changent est imprimée, ou @@ -50,11 +56,23 @@ def sans_achat(niveau, charge, achats): achats.append((charge, prog.pop('purchasedWh', ABSENT))) -def normaliser(ligne, achat=False, strategie=False): - """Rend (ligne sans motifs, [(charge, motif retiré)], [(charge, purchasedWh retiré)]).""" +ECO_SOUS_PALIER = ('ECO_FLOOR_GRID', 'ECO_FLOOR_BELOW_STAGE') + + +def sans_motif_eco(niveau, cle_motif, charge, motifs_eco): + motif = niveau.get(cle_motif) + if niveau.get('level') == 'eco' and isinstance(motif, dict) \ + and motif.get('code') in ECO_SOUS_PALIER: + motifs_eco.append((charge, niveau.pop(cle_motif))) + + +def normaliser(ligne, achat=False, strategie=False, sous_palier=False): + """Rend (ligne sans motifs, [(charge, motif retiré)], [(charge, purchasedWh retiré)], + [(charge, motif de niveau éco retiré)]).""" d = json.loads(ligne) retires = [] achats = [] + motifs_eco = [] if strategie and d.get('src') == 'plan' and (d.get('plan') or {}).get('strategy') == 'rule-based': d['plan']['strategy'] = 'rules' if d.get('src') == 'cycle': @@ -64,6 +82,8 @@ def normaliser(ligne, achat=False, strategie=False): sans_manque(n, 'decision') if achat: sans_achat(n, ch.get('loadId'), achats) + if sous_palier: + sans_motif_eco(n, 'decision', ch.get('loadId'), motifs_eco) elif d.get('src') == 'plan': for s in (d.get('plan') or {}).get('timeSlots', []) or []: for a in s.get('actions', []) or []: @@ -72,7 +92,9 @@ def normaliser(ligne, achat=False, strategie=False): sans_manque(n, 'reason') if achat: sans_achat(n, a.get('loadId'), achats) - return json.dumps(d, sort_keys=True, ensure_ascii=False), retires, achats + if sous_palier: + sans_motif_eco(n, 'reason', a.get('loadId'), motifs_eco) + return json.dumps(d, sort_keys=True, ensure_ascii=False), retires, achats, motifs_eco def achat_court(v): @@ -100,6 +122,7 @@ def main(): sortie_lignes = sys.argv[sys.argv.index('--lignes') + 1] if '--lignes' in sys.argv else None achat = '--achat' in sys.argv strategie = '--strategie' in sys.argv + sous_palier = '--sous-palier' in sys.argv lignes_changees = [] if len(attendu) != len(obtenu): @@ -112,15 +135,25 @@ def main(): if a == b: brutes_identiques += 1 continue - na, ra, aa = normaliser(a, achat, strategie) - nb, rb, ab = normaliser(b, achat, strategie) + na, ra, aa, ea = normaliser(a, achat, strategie, sous_palier) + nb, rb, ab, eb = normaliser(b, achat, strategie, sous_palier) lignes_changees.append((i, a, b)) - if na != nb: + if na != nb or len(ea) != len(eb): print(f'ÉCART HORS MOTIFS{" ET HORS ACHAT" if achat else ""}' - f'{" ET HORS STRATÉGIE" if strategie else ""} à la ligne {i} :') + f'{" ET HORS STRATÉGIE" if strategie else ""}' + f'{" ET HORS MOTIF ÉCO SOUS PALIER" if sous_palier else ""} à la ligne {i} :') print('- ' + a[:3000]) print('+ ' + b[:3000]) return 1 + # Le motif d'un niveau éco : identique, ou ECO_FLOOR_GRID devenu ECO_FLOOR_BELOW_STAGE. + for (charge, ma), (_, mb) in zip(ea, eb): + if ma == mb: + continue + if (ma.get('code'), mb.get('code')) != ECO_SOUS_PALIER: + print(f'ÉCART DE MOTIF ÉCO à la ligne {i} ({charge}) : {ma} → {mb}') + return 1 + cas[(json.loads(a).get('fn'), charge, f'niveau éco {ma["code"]}', + f'niveau éco {mb["code"]}')] += 1 fn = json.loads(a).get('fn') for (charge, ma), (_, mb) in zip(ra, rb): if ma != mb: @@ -134,10 +167,11 @@ def main(): if sa != sb: cas[(fn, '(plan)', f'strategy {sa}', f'strategy {sb}')] += 1 # Le manque d'un niveau change sans que le motif de tête change : à dire aussi. - elif ra == rb and aa == ab: + elif ra == rb and aa == ab and ea == eb: cas[(fn, '(niveau)', 'missingWh', 'missingWh corrigé')] += 1 - hors = 'HORS MOTIFS' + (' ET HORS ACHAT' if achat else '') + (' ET HORS STRATÉGIE' if strategie else '') + hors = ('HORS MOTIFS' + (' ET HORS ACHAT' if achat else '') + (' ET HORS STRATÉGIE' if strategie else '') + + (' ET HORS MOTIF ÉCO SOUS PALIER' if sous_palier else '')) print(f'IDENTIQUE {hors} : {len(attendu)} lignes, dont {brutes_identiques} identiques ' f'à l\'octet et {len(attendu) - brutes_identiques} qui ne diffèrent que par ' f'{"les motifs" + (", purchasedWh" if achat else "") + (" ou plan.strategy" if strategie else "")}')