386 Commits
Author SHA1 Message Date
glenn schrooyenandClaude Opus 5.5 ea77c63552 CHANGELOG 0.18.2: say how a no-unit forecast shows; who needs a look
checks / unit (pull_request) Successful in 40m19s
infra-pin / fresh (pull_request) Successful in 1s
Review of PR #199. "Warns once" left out the Home row and the Settings
note, which stay while the fault lasts. "No option change" now points to
the one case that needs a look.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:48:05 +02:00
glenn schrooyenandClaude 75f4359aac Release 0.18.2
checks / unit (pull_request) Successful in 40m23s
infra-pin / fresh (pull_request) Successful in 2s
Version bump to 0.18.2 and a CHANGELOG section for SAFETY-118 (#198).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-16 20:54:29 +02:00
glenn schrooyenandClaude 7d52dc1c0f SAFETY-118: a missing peak forecast reading keeps the unit-fault state
infra-pin / fresh (pull_request) Successful in 1s
checks / unit (pull_request) Successful in 40m19s
A missing reading (unavailable, unknown, failed read, non-numeric) no longer
clears the fault: no false 'used again' INFO, no repeated WARNING, the status
row stays. Adds pv.has_reading(). DECISIONS wording fixed; tests added.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-16 18:19:44 +02:00
glenn schrooyenandClaude f0b4e68e49 SAFETY-118: read the peak forecast in its own unit
checks / unit (pull_request) Successful in 40m20s
infra-pin / fresh (pull_request) Successful in 1s
peak_forecast_entity is read through pv.watts() by its unit_of_measurement;
the abs(raw) < 50 size rule is gone. No unit or a non-power unit means the
forecast is not used, with one warning per cause. The validator no longer
claims a kW sensor is taken as watts for this entity.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-16 16:00:34 +02:00
glenn schrooyenandClaude Opus 5.5 0ee3c3468b Release 0.18.1 review: CHANGELOG text fixes
checks / unit (pull_request) Successful in 40m18s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-118 line: the add-on already turns a kW forecast into W
(main.py read_inputs, abs(raw) < 50), so only the settings-page warning is
wrong. Rounding note and rows: the new settings page; UX-44 also changed
one DOCS line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:03:37 +02:00
glenn schrooyenandClaude dec8e7b69f Release 0.18.1
checks / unit (pull_request) Successful in 40m39s
infra-pin / fresh (pull_request) Successful in 2s
Version 0.18.0 -> 0.18.1 (patch, no option changes) and a closed 0.18.1 CHANGELOG section.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-16 12:08:58 +02:00
glenn schrooyenandClaude 1bd2ba1397 UX-44: contract label says every 15 minutes, not hourly
checks / unit (pull_request) Successful in 40m37s
infra-pin / fresh (pull_request) Successful in 2s
The settings page contract choice read "Dynamic, hourly" / "Dynamisch,
per uur". Belgian day-ahead prices are per 15 minutes since 2025-10-01.
Now "Dynamic, every 15 minutes" / "Dynamisch, per kwartier". One DOCS
line that called dynamic pricing "hourly pricing" now says dynamic.
test_ui_text.py part 7 fails if either label says hourly again.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-16 05:26:32 +02:00
UX-43 review 92a3d3400a UX-43 review: plain wording for the DOCS line on five-decimal prices
checks / unit (pull_request) Successful in 40m23s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:47:28 +02:00
glenn schrooyenandClaude 8c954acb36 UX-43: keep each field description to one sentence (STYLE)
checks / unit (pull_request) Successful in 40m31s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-16 02:29:31 +02:00
glenn schrooyenandClaude 8f72ab00d7 UX-43: tariff price fields take five decimals
ui.fixed_price, ui.day_price, ui.night_price and ui.periods step 0.00001
EUR/kWh (the period editor's price input took a hard-coded 0.01). A short
c-EUR/kWh hint is added to the four field descriptions, EN and NL.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-16 01:40:40 +02:00
glenn schrooyenandClaude Opus 5.5 44cd1fde2a Release 0.18.0: review fixes to the CHANGELOG section
checks / unit (pull_request) Successful in 40m17s
infra-pin / fresh (pull_request) Successful in 2s
OPT-15 and OPT-20 rows: the retry is two inverters only, and OPT-20 no
longer claims no other battery moves. SAFETY-111 added to known issues.
MAINT-19 marked as from code reading. Wording fixes in the SAFETY-112 and
MAINT-11 rows, UX-38 and the price service note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:35:47 +02:00
Claude e7c83d3a57 Release 0.18.0: version bump and CHANGELOG section
checks / unit (pull_request) Successful in 40m26s
infra-pin / fresh (pull_request) Successful in 1s
config.yaml version 0.17.0 -> 0.18.0 and a closed ## 0.18.0 CHANGELOG
section for OPT-17, SAFETY-112, OPT-15, MAINT-11, PRICE-01 and OPT-20.
No code change.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 21:37:17 +02:00
glenn schrooyenandClaude ccdb9dc17a OPT-20: test_opt20 points at MUTATION.md (test_bytecode part 5)
checks / unit (pull_request) Successful in 40m25s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 13:12:22 +02:00
glenn schrooyenandClaude 06339a2446 OPT-20: a refusal probe asks only PROBE_W, not the whole trade
checks / unit (pull_request) Failing after 40m47s
infra-pin / fresh (pull_request) Successful in 1s
The probe's trade was sized at the probing battery's rating. Its SAFETY-73
pin held 200 W of that, and the allocator put the rest on a battery with no
mode range (one in a maintenance phase): TEST-23 OPT-15 g7, -2300 W on A in
its maintenance charge for 34 s, grid 3100 W.

modetrade.decide() now asks PROBE_W for the probing battery plus what the
other chargers would have had without the probe; size() adds the probe on top
of their share, or files PROBE_W exactly, or nothing. _mode_trade() carries
the pin only on the ticks it is filed, so the window's first and last cycle
do not move another battery's share either. Modes off and modes on with no
probe are unchanged (bf9019e digests). Tests: test_opt20.py.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 13:08:59 +02:00
adminandClaude Opus 5.5 6862dea52a PRICE-01 review 3: tests for the round 3 note and dismiss; doc wording
checks / unit (pull_request) Successful in 39m4s
infra-pin / fresh (pull_request) Successful in 2s
- test_price_secret.py: the fixed-tariff note on the real /config/validate
  route, EN and NL, and none for a spaces-only profile (web.py lang wiring).
- test_price_service.py: the start line is INFO, not WARNING; a dismiss that
  raises does not stop the cycle.
- test_maint13/test_setup_only/test_watchdog: ignore only the dismiss of the
  old card, so a create with that id is still counted.
- DECISIONS rule 7 quotes the settings note as it is; messages.py comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 22:07:09 +02:00
glenn schrooyenandClaude c68fd275cc PRICE-01 round 3: replace the fixed-tariff notification with a log line and a settings note
checks / unit (pull_request) Successful in 39m6s
infra-pin / fresh (pull_request) Successful in 1s
G1: remove the false 'Fixed tariff not used' notification. One INFO line at
start and an info note beside the setting (EN/NL) when price_service has a
non-blank static_profile; the old notification id is dismissed at start.
DOCS, migration step 5 and DECISIONS rule 7 now say what the code does.
S05: whitespace-only static_profile counts as not set.
G2: maintenance_enabled read through MaintConfig.from_options().

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 20:27:15 +02:00
glenn schrooyenandClaude Opus 5.5 2f261f8ba4 PRICE-01 review: spaced-token check passes the length rule; F3 default pinned
checks / unit (pull_request) Successful in 39m51s
infra-pin / fresh (pull_request) Successful in 1s
The spaced-token check used a 5-character token, which the new 16-character
rule refuses on its own, so a validator that stopped checking spaces passed
(reviewer mutation R06). It now uses a long token and checks the reason.

Also pin F3 with no maintenance_enabled key: the default (off) holds and no
no-price words show (reviewer mutation S04). Tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 19:58:50 +02:00
glenn schrooyenandClaude 0801776645 PRICE-01 round 2: the token on no page, fixed-tariff note, token length, F3 words, test gaps
checks / unit (pull_request) Successful in 39m0s
infra-pin / fresh (pull_request) Successful in 2s
F1: every password option is shown as ******** (set) or empty in /config/model
(options, running, ui) and in a stale refusal; the classic page draws it as a
password field; a save or validate that sends ******** or nothing keeps the
stored value. F2: a static_profile beside price_service is said once at start
(notification and log), not refused. F3 (owner, 2026-10-14): the no-price row,
Home line and notification only while maintenance_enabled is on. F4: tests for
the reviewer's R01 and R10. F5: price_service_token must be 16+ characters.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 18:56:51 +02:00
glenn schrooyenandClaude 4eb88f7fda PRICE-01: migration note, and the tests drive the real control cycle
checks / unit (pull_request) Successful in 39m36s
infra-pin / fresh (pull_request) Successful in 1s
- DOCS: the migration note said markup and taxes do not matter; with the
  provider they are added to the wholesale price. Step 4 and 5 now say what
  they set and what static_profile does while no source has a price.
- The gap tests run the notification through cycle(), where the check is
  called, so a check that is never called goes red.
- test header: the real-transport checks are section 11.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 17:03:05 +02:00
glenn schrooyenandClaude 87ad958842 PRICE-01: Dutch dates, no redirects with the token, more checks
checks / unit (pull_request) Successful in 39m2s
infra-pin / fresh (pull_request) Successful in 1s
- The notification, status row and Home line name an earlier day in the
  language of the text ("13 okt om 21:00"); the Dutch title is "Prijzen
  ontbreken", so the body no longer repeats it.
- The token request does not follow redirects: the host is fixed in code.
- A step back up to the saved prices is one INFO line.
- Tests: whole status payload free of the token, a redirect, the 5-decimal
  edge of the old average, modes plan equal to generic_dayahead's.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 16:54:11 +02:00
glenn schrooyenandClaude 9ac3a9d2bc PRICE-01: price_service provider, fallback chain, and the gate read from it
checks / unit (pull_request) Successful in 39m31s
infra-pin / fresh (pull_request) Successful in 1s
A new price source, `price_provider: price_service`: the owner's price server,
one fixed HTTPS host, a bearer token (new password option
`price_service_token`), wholesale energy only so markup and taxes are added as
for generic_dayahead.

The chain is server, then energy-charts, then the prices the history store
kept. A step counts only if it has a price for now. Each step down and back up
is one log line. With every source down a site plans from what it saved.

No price for now: one status row, one Home plan line and one notification, in
English and Dutch, cleared when a price returns. With this provider the
maintenance charge gate reads the provider's prices and keeps the Home
Assistant rule: the current 15-minute price is at or below today's average.

The token is hidden on the settings page, refused when missing, and taken out
of any error text. Other providers, `off` and the Home Assistant pair take the
road they always took (scripted-day probe against 0ce7d35: one line differs,
the refusal that lists the known providers). See
DECISIONS/PRICE-01-own-price-server.md.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-14 16:22:24 +02:00
adminandClaude Opus 5.5 6aa1c32cbb MAINT-11 review round 2: pin each cycle button to its own disabled flag
checks / unit (pull_request) Successful in 38m46s
infra-pin / fresh (pull_request) Successful in 1s
Reviewer mutations survived the round-2 page checks: Start forced always
disabled (|| true), and the two flags swapped between Start and Stop,
because the checks only looked for each flag somewhere in the file. The
check now pins each flag to its own button. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:39:37 +02:00
glenn schrooyenandClaude 5ee1061398 MAINT-11 round 2: disable the cycle buttons that cannot work
checks / unit (pull_request) Successful in 38m43s
infra-pin / fresh (pull_request) Successful in 1s
Stop cycle is disabled on a battery that is not cycling. Start cycle is
disabled while any battery cycles and on a switched-off battery. Page only;
the add-on still refuses each of these. DECISIONS notes the one-battery case.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-13 23:18:21 +02:00
adminandClaude Opus 5.5 fd059126cb MAINT-11 review: test an empty unit at the web layer and the page's own refusal words; drop a stale comment
checks / unit (pull_request) Successful in 38m45s
infra-pin / fresh (pull_request) Successful in 2s
Two reviewer mutations survived the suite: the web layer routing only a
truthy unit (an empty unit then started the bare pick), and the page
showing the server's English instead of its own sentence by code. Both
are now checked. The bare maint_start comment that said a per-battery
unit would ship half a surface is replaced by a pointer to
_maint_unit_action(). No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:06:47 +02:00
glenn schrooyenandClaude 21d8a944bc MAINT-11: start or stop the maintenance cycle per battery
checks / unit (pull_request) Successful in 38m46s
infra-pin / fresh (pull_request) Successful in 1s
maint_start and maint_abort take an optional unit. With two or more batteries
each Batteries card has Start cycle and Stop cycle buttons. One slot per site
stays: a start while a battery cycles is refused and names it (no queue). A
battery that is switched off, an unknown unit, and a stop on a battery that is
not cycling are refused with a plain message. A request with no unit is the
old call. The scheduler and the phase machine are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-13 21:37:36 +02:00
glenn schrooyenandClaude Opus 5.5 7f0f00b01c OPT-15 review: pin the rolled export at 0 W and a battery past E; note the maintenance replay
checks / unit (pull_request) Successful in 38m30s
infra-pin / fresh (pull_request) Successful in 2s
Two survivors of the review's mutations get a check each in
test_price_run_fixes.py (X1: a held export credited negative in the rolled
quarter; X4: the hold extended by a reading already past E). DECISIONS: the
slow-battery quarter (3456 W, same on e3e4ba6) and the replay of a
maintenance phase starting during the hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-13 14:18:41 +02:00
glenn schrooyenandClaude 065792aad3 Merge release/1.0 (e3e4ba6) into opt15-price-run-fixes
infra-pin / fresh (pull_request) Successful in 3s
checks / unit (pull_request) Successful in 39m30s
Conflicts (5 files, 9 hunks) resolved keeping both sides:
- p1.py: held_t (OPT-15) beside held_since (SAFETY-90).
- modetrade.py: chargers() takes targets (P4) and probe (OPT-15); decide()
  clears the chargers, a probing refuser included, while a battery drains
  (SAFETY-78); the docstring lists both rules.
- main.py: the roll goes into the SAFETY-90/91 projection chain
  (`_peak_proj`); the SAFETY-95 drain-tail grid_w keeps the run-edge hold on
  its other branch; the P4 forecast state and the hold state both stay.
- DOCS.md, DECISIONS: both lines kept, OPT-15 item refreshed.

Adaptations the merge needs:
- SAFETY-81 x F1: a rolled projection is not graded by the saturation alarm
  until the quarter's first sample, as the old edge tick was not, so a
  standing overload is still notified once per quarter.
- test_safety81, test_modes_p4: modes-on pins re-pinned (with a note), modes
  off pinned to e3e4ba6; test_price_run_fixes: identity pins on e3e4ba6,
  F1 reproduced at one inverter (SAFETY-91 already holds two), tests for the
  drain and the day plan against the probe.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 12:15:44 +02:00
adminandClaude Opus 5.5 df6bc0f76b SAFETY-112 review: grade the waiting flag where hass.py sets it
checks / unit (pull_request) Successful in 36m27s
infra-pin / fresh (pull_request) Successful in 1s
- a 502 or refused read turns waiting on, the next good read off, and a
  read timeout never (two mutants survived without this)
- the flag changes no write with live readings, not only with 502 ones
- the page script's amber dot and batteries-tab banner for "waiting"
- DECISIONS: about 5 minutes per start stage, not "at most"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:10:27 +02:00
adminandClaude Opus 5.5 d94cb0c263 SAFETY-112: say plainly that control waits for Home Assistant to start
checks / unit (pull_request) Successful in 36m38s
infra-pin / fresh (pull_request) Successful in 2s
The Supervisor proxy answers 502 to every add-on read, write and websocket
until Core is RUNNING (proxy.py api()/websocket() -> check_api_state()), so
the add-on cannot control while a slow integration holds HA's start, even
with its entities live. Measured on a safety112 repro stack with the
2026.08.0 proxy code: first write 1.0-4.4 s after RUNNING, 238-300 s after
live readings. Not routed around (DECISIONS/SAFETY-112-...).

While the API does not answer, the first status row is red and is the
banner ("Waiting for Home Assistant to finish starting"), Home shows a
"waiting" state in EN/NL instead of "Needs you now", and the log adds a
"still waiting" line every 5 minutes and the wait's length at the end.
A safety stop, a stalled loop or shadow mode keeps its own headline.
Control, writes, heartbeat and the stand-down are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:07:51 +02:00
adminandClaude Opus 5.5 92a80c09b2 OPT-17 review: title the forecast row on Home; test the hint and blank rows
checks / unit (pull_request) Successful in 36m20s
infra-pin / fresh (pull_request) Successful in 2s
The new status row's id `solar_forecast` had no Home title, so Home showed
it under the fallback "Inverter connection". Add "Solar forecast" /
"Zonneprognose" to ui_text, and checks for the title, the tomorrow-gap hint
and blank list rows (review mutations R7, R9, R14 now killed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-11 05:12:35 +02:00
glenn schrooyenandClaude Opus 5.5 9b1fa758ad OPT-17: the solar forecast can come from several entities, summed
checks / unit (pull_request) Successful in 36m13s
infra-pin / fresh (pull_request) Successful in 1s
New list options pv_forecast_entities and pv_forecast_tomorrow_entities
(same shape as pv_entities); the single-entity keys are unchanged.

- Each member is read in its own unit (Wh/kWh/MWh) in one gather and the
  day is summed. Hourly splits are merged when every member has one, else
  the total goes on the bell.
- A member missing, unavailable, stale, non-energy or bad makes that
  day's forecast unusable (no partial sum). A caution row names it by
  position; the action log names the entity once per change.
- Single key and list both set for one day: refused at start.
- A one-member list behaves exactly like the single key (test_opt17.py);
  the single-key path is byte-identical to e36565f over 24 scenarios.
- Supervisor shape measured: tools/supervisor_shape_probe.py section J.

DECISIONS/OPT-17-several-forecast-entities.md has the rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-11 02:55:58 +02:00
goodwe-review 808ec04dc4 REL-0.17.0 review: CHANGELOG wording fixes
checks / unit (pull_request) Successful in 35m16s
infra-pin / fresh (pull_request) Successful in 2s
SAFETY-107 row names the tested base; MAINT-17 says code reading only;
SAFETY-105 figure is from a simulation; SAFETY-97 edge is 175 W; the
maintenance note cites MAINT-14 and MAINT-15; SAFETY-108 "about 15 s".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:21:39 +02:00
glenn schrooyenandClaude Opus 5.5 35432ce065 Release 0.17.0
checks / unit (pull_request) Successful in 35m11s
infra-pin / fresh (pull_request) Successful in 2s
Bump version to 0.17.0 and add the closed 0.17.0 CHANGELOG section.
No code change, no option change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:29:13 +02:00
adminandClaude Opus 5.5 35b2a699de MAINT-15 round 2: in-repo differential check, full-hold with-reading check
checks / unit (pull_request) Successful in 35m8s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:42:01 +02:00
adminandClaude Opus 5.5 cd1bf2a694 MAINT-15: the balancing hold ends on time with no SoC reading
checks / unit (pull_request) Successful in 35m22s
infra-pin / fresh (pull_request) Successful in 1s
HOLD's time check moves into Maintenance._hold(), used by step() and
_no_soc(). A hold that loses its SoC now ends at hold_min, writes
last_completed and logs the same COMPLETE line. The 15 min HOLD line says
the hold still ends at its normal time. Behaviour with a reading is
unchanged (differential trace against db9e231).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:56:29 +02:00
adminandClaude Opus 5.5 848140c29f SAFETY-107 review: test a writer that turns round on a dead pack; say when rule 3 applies
checks / unit (pull_request) Successful in 35m22s
infra-pin / fresh (pull_request) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:52:39 +02:00
glenn schrooyenandClaude Opus 5.5 d12228d986 SAFETY-107: a refusal held under another writer is learned once
checks / unit (pull_request) Successful in 35m8s
infra-pin / fresh (pull_request) Successful in 2s
At one inverter SAFETY-52's "another writer" end tested target != loop_w.
A held refusal makes the loop ask 0 W, so any binding claim (maintenance
charge, peak guard, trade) ended it every cycle, and compute() learned it
again in the same cycle: "BATTERY REFUSES" / "ENDED: another writer" each
second on the rig. Now the held refusal is judged afresh against the
writer's ask (compute() gets the same inputs as before); it ends only if
compute() no longer finds it. Commands unchanged: 300-seed differential
trace against release/1.0, N=1 and N=2, every command and refusal state
identical. DECISIONS/SAFETY-107.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-09 03:03:06 +02:00
adminandClaude Opus 5.5 61a61941bb MAINT-14 review: tests for a HOLD episode after a no-SoC charge, a MAINT-13 state file, and a true twin site at N=2
checks / unit (pull_request) Successful in 34m56s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 22:14:26 +02:00
glenn schrooyenandClaude Opus 5.5 ef35f4b925 MAINT-14: accurate no-level wording per phase; name the limit that ended the charge
checks / unit (pull_request) Successful in 34m54s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:25:35 +02:00
glenn schrooyenandClaude Opus 5.5 f39e52efd3 MAINT-14: maintenance with no SoC reading: charge times out, drain holds at 0 W
checks / unit (pull_request) Successful in 35m13s
infra-pin / fresh (pull_request) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:37:01 +02:00
adminandClaude Opus 5.5 ea31289ec0 MAINT-13 review: tests for a stale charge record and a clock step back; DOCS names the 7 day end
infra-pin / fresh (pull_request) Successful in 2s
checks / unit (pull_request) Successful in 34m49s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:08:40 +02:00
glenn schrooyenandClaude Opus 5.5 6836f61bd4 MAINT-13 round 3: provider site, hourly fixed-tariff check, faster tests
checks / unit (pull_request) Successful in 34m52s
infra-pin / fresh (pull_request) Successful in 1s
- A price-provider site with no fixed tariff is not a gate (its plan never
  sets `cheap`); DECISIONS §3 says so and a test pins it.
- The fixed-tariff check is worked out once an hour, not every tick.
- test_maint13: run() steps really are `step` minutes; the long loops use
  5-minute steps (the per-tick cap); wall backstop checked "not before".
- test_safety93: the drain bound is the rig's configured drain rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:03:55 +02:00
glenn schrooyenandClaude Sonnet 5.5 f7b83cc75c MAINT-13 round 2: read the price gate without a new main.py option read
checks / unit (pull_request) Successful in 34m35s
infra-pin / fresh (pull_request) Successful in 1s
test_static_tariff forbids main.py reading `static_profile`; a small
static_gated() in static_tariff.py answers it, and the Controller keeps the
answer in `_price_gated` beside `cheap`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-08 11:10:33 +02:00
glenn schrooyenandClaude Sonnet 5.5 6f05b2e4be MAINT-13 round 2: gate open when nothing holds the charge back; safety93 scenario restored
checks / unit (pull_request) Failing after 34m45s
infra-pin / fresh (pull_request) Successful in 2s
- gate_open is true with no price gate set up, in a cheap window, or while the
  pack is taking charge; the stall rule and timeout now work on a no-tariff site.
- reset the save timer on a phase change; exact 5 min gap-cap test.
- test_safety93: original timeout scenario kept (B finished) plus a case where a
  due B takes the slot after A's failure.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
2026-10-08 10:29:46 +02:00
glenn schrooyenandClaude Opus 5.5 28eff568b5 MAINT-13: a full pack that stops at 98-99 % is not a failed charge
checks / unit (pull_request) Successful in 34m21s
infra-pin / fresh (pull_request) Successful in 1s
A charge is complete at SoC >= target, or at >= target - 3 once the SoC has
made no new high for 60 min of allowed charging, or when the charging time
runs out at >= target - 3. The 36 h timeout counts only the minutes the price
gate let the pack charge (plus a 7 day wall-clock backstop). A cycle that
fails (stuck drain, charge below target - 3) backs off 7 days, tells the
operator once (EN + NL), and no cycle starts on that pack in that time unless
the operator forces one. State survives a restart; old state files load.

No option added. DECISIONS/MAINT-13-full-pack-is-complete.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:36:06 +02:00
adminandClaude Opus 5.5 c6f23a69d4 SAFETY-103 review: correct the swing after a refusal or lost link in DECISIONS
checks / unit (pull_request) Successful in 34m52s
infra-pin / fresh (pull_request) Successful in 2s
The test table gives 1.5 kW at N=2 and 3.9 kW summed at N=3 under a
surplus, and about 1 kW after a lost link without one; not 1.9 kW.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:13:08 +02:00
adminandClaude Opus 5.5 98dd110a4f SAFETY-103: test a dropped battery is not counted; guard a read
checks / unit (pull_request) Successful in 34m26s
infra-pin / fresh (pull_request) Successful in 1s
Mutation M13 (a battery out of the split counted in the house point)
survived; a direct check now kills it. win[0] is read through a guard
(test_named_absence).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:12:03 +02:00
adminandClaude Opus 5.5 138e45b53f SAFETY-103: no swing on old battery readings after a drain ends
checks / unit (pull_request) Failing after 35m9s
infra-pin / fresh (pull_request) Successful in 2s
While a battery settles from its ended drain (SAFETY-95), the house point
was grid + every battery's reading. A register can be 10 s older than the
meter, so B swung 860 -> 1650 -> 870 W on A's old reading (rig check 5).

Now the house point is held and moved only as far as the meter proves:
each battery is between its last changed reading and its commands since
(a reading that changed this cycle, or a refused side, as it reads).
N=1 and 1 s registers are byte-identical to release/1.0.

DECISIONS/SAFETY-103-old-readings-after-a-drain.md, test_safety103.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:25:55 +02:00
goodwe-review 1c607a961a REL-0.16.0 review: CHANGELOG wording fixes
checks / unit (pull_request) Successful in 32m48s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-103 swing 'by about 800 W for about 5 s'; SAFETY-99 'late, by less
than 30 s'; MAINT-10 row: one-at-a-time is not new; SAFETY-96: drop the
test-only 800 W; SAFETY-90 and MAINT-10 rows trimmed to two sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:09:08 +02:00
glenn schrooyenandClaude Opus 5.5 756b50ddbb Release 0.16.0
checks / unit (pull_request) Successful in 32m41s
infra-pin / fresh (pull_request) Successful in 1s
version 0.15.0 -> 0.16.0 and a closed ## 0.16.0 CHANGELOG section.
No code change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:25:14 +02:00