From ecd9822861848bddc901e791a3a5baea8c4b562d Mon Sep 17 00:00:00 2001 From: glenn schrooyen Date: Thu, 22 Oct 2026 08:20:50 +0200 Subject: [PATCH] PRICE-02: pin the modes-off identity with a digest that hides only the changed words test_price_run_fixes.py part 5 and test_modes_p3.py part 1 compare the modes-off replay with digests printed on older trees. The trade sentence now says "price period", and a sale quotes another "above the cheapest" figure once the first cheap period is over. ident.trade_digest() hides exactly those two things; re-measured on e3e4ba6, 7a00f12, 90bc901 and this tree, the digests are the same, so every command is unchanged. Co-Authored-By: Claude --- goodwe_controller/test_modes_p3.py | 30 ++++++++------------- goodwe_controller/test_price_run_fixes.py | 25 ++++++++++------- goodwe_controller/tools/opt11p3_identity.py | 23 ++++++++++++++++ 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/goodwe_controller/test_modes_p3.py b/goodwe_controller/test_modes_p3.py index 2680084..cd14088 100644 --- a/goodwe_controller/test_modes_p3.py +++ b/goodwe_controller/test_modes_p3.py @@ -68,33 +68,25 @@ A, B = "inverter-a", "inverter-b" print("1. modes off: byte-identical to 7a00f12 on the trade path") # Printed on release/1.0 7a00f12 (2026-09-29), goodwe-addon-checks:1, 1080 # cycles each; the trade CHARGEs and DISCHARGEs. PRICE-02 (2026-10-22): the -# digest is over every row with the trade's SENTENCE left out (action and -# watts stay). The plan now drops price periods that have ended, so the -# sentence of a sale quotes a smaller "above the cheapest" figure once the -# first cheap period is over, and the idle sentence says "price period". -# Re-measured on 7a00f12, on release/1.0 90bc901 and on this tree: the same -# four digests, so no command, integrator or allocation moved. +# digest is `ident.trade_digest()`, which hides two things in the trade +# sentence: the "above the cheapest" figure (the plan now drops price periods +# that have ended, so it is another number once the first cheap period is +# over) and "hour" -> "price period". Re-measured with it on 7a00f12, on +# release/1.0 90bc901 and on this tree: the same four digests. AT_7A00F12 = { - "n1": "2f3cf03655f19a172e83ccae1e1308a8eef9d1023ab632a68b943920b032800d", - "n2": "59ba1cbfe33aeca74d96b50dbcfde5337a8f82de0fde598a048afeef46fe64de", - "n1_off": "2f3cf03655f19a172e83ccae1e1308a8eef9d1023ab632a68b943920b032800d", - "n2_off": "59ba1cbfe33aeca74d96b50dbcfde5337a8f82de0fde598a048afeef46fe64de", + "n1": "33886ef03f364852883f0370230e6e04823fbb46a4704f88ca75bf9082d98c01", + "n2": "666a31f41491c22750720524853d95944d5fba395d1d14040c460d71414b275f", + "n1_off": "33886ef03f364852883f0370230e6e04823fbb46a4704f88ca75bf9082d98c01", + "n2_off": "666a31f41491c22750720524853d95944d5fba395d1d14040c460d71414b275f", } - - -def _cmd_digest(rows): - """`ident.digest()` over rows whose trade is (action, watts) only.""" - return ident.digest([r[:-1] + (None if r[-1] is None else r[-1][:2],) - for r in rows]) - - +_cmd_digest = ident.trade_digest _rows = {k: ident.run(**ident.SCENARIOS[k])[0] for k in AT_7A00F12} _got = {k: _cmd_digest(v) for k, v in _rows.items()} for k in AT_7A00F12: print(f" {k} {'same' if _got[k] == AT_7A00F12[k] else 'DIFFERS'} " f"{_got[k][:16]}") check("with battery modes off (option absent or false), every command, " - "integrator, allocation and trade action equals 7a00f12's, at one " + "integrator, allocation and trade sentence equals 7a00f12's, at one " "inverter and at two", _got == AT_7A00F12) _acts = {r[-1][0] for r in _rows["n2"] if r[-1]} check("...and the runs reach the trade P3 now skips with modes on: a charge " diff --git a/goodwe_controller/test_price_run_fixes.py b/goodwe_controller/test_price_run_fixes.py index ecd6da1..4e4a191 100644 --- a/goodwe_controller/test_price_run_fixes.py +++ b/goodwe_controller/test_price_run_fixes.py @@ -596,17 +596,22 @@ check("selling is off: no sale on any cycle", # ============================================================================ # print("\n5. modes off: byte-identical to release/1.0 e3e4ba6") -# Printed by tools/opt11p3_identity.py on release/1.0 e3e4ba6 (2026-10-13), -# goodwe-addon-checks:1, with this branch's harness (same API). n1 and n2 are -# also 5a4cdc2's; the lag ones moved with SAFETY-90/91 (n2_lag now equals n2: -# the next-quarter grading removed the edge tick's blindness at N=2). +# Printed on release/1.0 e3e4ba6 (2026-10-13), goodwe-addon-checks:1, with this +# branch's harness (same API). n1 and n2 are also 5a4cdc2's; the lag ones moved +# with SAFETY-90/91 (n2_lag now equals n2: the next-quarter grading removed the +# edge tick's blindness at N=2). PRICE-02 (2026-10-22): the digest is +# `ident.trade_digest()`, which hides two things in the trade sentence: the +# "above the cheapest" figure (the plan now drops price periods that have +# ended) and "hour" -> "price period". Re-measured with it on e3e4ba6, on +# release/1.0 90bc901 and on this tree: the same four digests. AT_BASE = { - "n1": "71245b227ca40bada4f71f57ea6154475f4b0bd60239c9ad4f80bffb0094514c", - "n2": "56cf28010b06765c29e5479abb815d7503d570b39240d82dfc818efa90c93722", - "n1_lag": "86ef0f5ad8da1498ff7dcf59262a177bef847a0b201ce804e947c8eab5f07190", - "n2_lag": "56cf28010b06765c29e5479abb815d7503d570b39240d82dfc818efa90c93722", + "n1": "33886ef03f364852883f0370230e6e04823fbb46a4704f88ca75bf9082d98c01", + "n2": "666a31f41491c22750720524853d95944d5fba395d1d14040c460d71414b275f", + "n1_lag": "f6c67b351bd732fb69e62cafbfdb2bcff6032f5d8845836659d096b8c677d3c0", + "n2_lag": "666a31f41491c22750720524853d95944d5fba395d1d14040c460d71414b275f", } -_got = {k: ident.digest(ident.run(**ident.SCENARIOS[k])[0]) +_cmd_digest = ident.trade_digest +_got = {k: _cmd_digest(ident.run(**ident.SCENARIOS[k])[0]) for k in AT_BASE} for _k in AT_BASE: print(f" {_k} {'same' if _got[_k] == AT_BASE[_k] else 'DIFFERS'} " @@ -619,7 +624,7 @@ _real_project = tariff.PeakTracker.project tariff.PeakTracker.project = staticmethod( lambda a, now, roll=False: _real_project(a, now, roll=True)) try: - _moved = ident.digest(ident.run(**ident.SCENARIOS["n1_lag"])[0]) \ + _moved = _cmd_digest(ident.run(**ident.SCENARIOS["n1_lag"])[0]) \ != AT_BASE["n1_lag"] finally: tariff.PeakTracker.project = _real_project diff --git a/goodwe_controller/tools/opt11p3_identity.py b/goodwe_controller/tools/opt11p3_identity.py index c2eb29e..78e3d52 100644 --- a/goodwe_controller/tools/opt11p3_identity.py +++ b/goodwe_controller/tools/opt11p3_identity.py @@ -17,6 +17,7 @@ test_modes_p3.py part 1 holds this tree to the digests printed on 7a00f12. import functools import hashlib import os +import re import sys import tempfile import time @@ -117,6 +118,28 @@ def digest(rows) -> str: return hashlib.sha256(repr(rows).encode()).hexdigest() +def _trade_words(text): + """PRICE-02: the trade sentence as release/1.0 before it printed it. The + plan now drops price periods that have ended, so a sale's "above the + cheapest" figure is a different number once the first cheap period is + over, and the sentences say "price period" where they said "hour". Both + maps are applied to both sides, so only those two things are hidden.""" + text = text.replace("this hour is neither the buy hour nor the sell hour", + "now is not the cheapest or the dearest price period") + text = re.sub(r"[0-9]+\.[0-9]+ above the cheapest", + "N above the cheapest", text) + return text.replace("firm hour at", "firm price period at") + + +def trade_digest(rows) -> str: + """`digest()` with the trade sentence passed through `_trade_words()`: + every command, integrator and allocation, and the action, watts and the + rest of the sentence, byte for byte.""" + return digest([r[:-1] + (None if r[-1] is None else + (r[-1][0], r[-1][1], _trade_words(r[-1][2])),) + for r in rows]) + + def digests() -> dict: return {k: digest(run(**v)[0]) for k, v in SCENARIOS.items()}