MAINT-21: split app/main.py's Controller into files by job, code unchanged.
What moves.Controller keeps __init__, its roster/N=1 views, the site aggregates, shutdown and log_event in app/main.py. The other 145-odd methods move, unchanged, into mixins: ctl_inputs, ctl_writes, ctl_trade, ctl_cycle (cycle() whole), ctl_alarms, ctl_status, ctl_tasks. rows.py holds the four row/why functions. Module constants move to the first file that reads them; app/main.py imports every one, so everything importable from app.main before still is (192 names, checked; plus the 7 mixin classes). No mixin imports main.
Proof.python3 goodwe_controller/tools/maint21_same_ast.py <base> compares every def, method and module-level assignment of main.py at the base with its new home by qualified name (--docstrings for the strict form): OK: code moved, none changed, 196 of 196 units. The comments-only commit also passes tools/maint20_same_code.py HEAD~ (-OO code objects the same).
Commits. Prep (tests and replay tools read and patch through tools/main_files.py; test_split_patches.py is the tripwire), then one commit per new file, then comments only. The full local suite (102 files) was green on every commit.
Not changed. No behaviour, log or UI text. CHANGELOG and version untouched. The rig test is the next step after merge.
MAINT-21: split app/main.py's Controller into files by job, code unchanged.
**What moves.** `Controller` keeps `__init__`, its roster/N=1 views, the site aggregates, `shutdown` and `log_event` in `app/main.py`. The other 145-odd methods move, unchanged, into mixins: `ctl_inputs`, `ctl_writes`, `ctl_trade`, `ctl_cycle` (`cycle()` whole), `ctl_alarms`, `ctl_status`, `ctl_tasks`. `rows.py` holds the four row/why functions. Module constants move to the first file that reads them; `app/main.py` imports every one, so everything importable from `app.main` before still is (192 names, checked; plus the 7 mixin classes). No mixin imports `main`.
**Proof.** `python3 goodwe_controller/tools/maint21_same_ast.py <base>` compares every def, method and module-level assignment of main.py at the base with its new home by qualified name (`--docstrings` for the strict form): `OK: code moved, none changed`, 196 of 196 units. The comments-only commit also passes `tools/maint20_same_code.py HEAD~` (`-OO` code objects the same).
**Commits.** Prep (tests and replay tools read and patch through `tools/main_files.py`; `test_split_patches.py` is the tripwire), then one commit per new file, then comments only. The full local suite (102 files) was green on every commit.
**Not changed.** No behaviour, log or UI text. CHANGELOG and version untouched. The rig test is the next step after merge.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
Prepares the split of app/main.py's Controller into files by job, with no app
change. tools/main_files.py gives a test the text and the AST of main.py plus
the files split from it, and `live`, which sets a global in every file whose
functions read it (and raises when none does). Tests that read main.py's
source, and tests and replay tools that patch a main global (datetime,
compute, resolve, _mono, WRITE_BUDGET_S, ...), go through it, so they pass on
this layout and on the split one. test_split_patches.py fails when a test or
tool patches an app.main attribute that main.py no longer reads, or that
another file also reads. tools/maint21_same_ast.py proves every function,
method and module-level assignment of main.py at the base is found unchanged
in its new file.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves why_raised, why_key, tariff_plan_row and arbitrage_row out of app/main.py unchanged. main.py imports them, so app.main still exports them. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. InputsMixin: read_inputs, _read_unit, live_units and the other readers of the meter, packs, PV and forecast. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. WritesMixin: write_setpoint, write_refusal, _write_unit, _hand_back, the refusal evidence, heartbeats and the site release/zero commands. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. TradeMixin: trade sizing, the mode runs, the day plan, the peak floors, drain and charge limits, pack actions. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. CycleMixin: cycle() and _shadow_settle(). cycle() is not cut up. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. AlarmsMixin: the check_* alarms and the loop-alarm helpers. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. StatusMixin: checks, status_payload, forecast_payload, publish, mqtt_command, handle_action, why_words. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. TasksMixin: run_control, run_tariff_refresh, fresh_tariff_plan and the store-migration grade. tools/maint21_same_ast.py: OK.
Co-Authored-By: Claude <noreply@anthropic.com>
No code change: tools/maint21_same_ast.py and tools/maint20_same_code.py HEAD~
both print OK, and the -OO code objects are the same.
- Trims 56 comment and docstring lines (2088 -> 2032) in app/main.py and the
new files that only restated their MAIN-orchestration.md bullet, each cut to
its first sentence and a pointer. What the cut comments said and the bullet
did not is now in the bullet: why a SAFETY-02 latch is restored, why the
SAFETY-30 site check is not armed at one inverter, the SAFETY-18 re-derive,
the MAINT-05 one-config rule, the OPT-09 sizing rules, the "no plan" row,
the 5 400 s age bound, and the guard being the only latch repair.
- Adds the missing DECISIONS pointers on the SMA-10, SAFETY-137 and SMA-2
comments (FALLBACK_BUDGET_S, write_setpoint, _await_inflight,
_release_last, the retry wait, the failed-release branch, release_zero_sent,
the in-flight write, the stop_event hand-back, the SMA-2 read and driver).
- DECISIONS/SAFETY-32: the stale `main.py:932-936` now names
Controller.pack_floor_soc.
- main.py's docstring and import block say where the Controller's parts are.
Co-Authored-By: Claude <noreply@anthropic.com>
Router T241: the comments commit had put new rationale into
MAIN-orchestration.md and cut comment lines that the note at 56fc41f did not
hold.
- DECISIONS/MAIN-orchestration.md is the 56fc41f text plus the one sentence
that the Controller is split into app/main.py and app/ctl_*.py.
- Comments whose content is not in a DECISIONS note at 56fc41f are back in the
code verbatim: MAINT-05 one config, the SAFETY-02, SAFETY-42/34,
SAFETY-30 site half, SAFETY-31 and SAFETY-18 latch blocks in __init__, the
_grade_store_migration docstring and comment, the trade_power_w docstring,
the rows.py docstrings.
- Kept: cuts of text already in the note, the SMA-10 / SAFETY-137 / SMA-2
pointers, the SAFETY-32 line fix.
No code change: maint21_same_ast OK, maint20_same_code -OO same.
Co-Authored-By: Claude <noreply@anthropic.com>
test_safety118's "size rule is gone" and test_validate's "no numeric
maintenance_* fallback" read app/main.py only; the code they guard can now
sit in app/ctl_*.py, where a regression passed unseen. Both read
main_files.source() for main.py now. Tests only; no app change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Head at start: 58b6fa3 (ls-remote matched the brief; release/1.0 = 56fc41f). Head now: 8f0b847. I pushed one commit that changes tests only (F1 below).
Results
Pure move: OK.maint21_same_ast.py 56fc41f prints 196/196 identical. maint20_same_code.py 27a867f prints -OO same. I planted a change in a scratch copy and the tool FAILed each time: a changed default argument, a removed @property, a renamed def (one missing, one extra), a def copied into a second mixin, and a mixin renamed to a class without the Mixin suffix.
MRO and globals: OK. The bases are Inputs, Writes, Trade, Cycle, Alarms, Status, Tasks. No name is defined twice, and nothing uses super(), self.__x name mangling, global or __qualname__. I checked at runtime, in the checks image, base against head:
All 161 Controller attributes, looked up through the MRO, have the same bytecode, consts, names and defaults.
All 410 globals that the split files read resolve to the same object or value as base main.py's.
The 44 names read by more than one file are the same object in every file.
Monkeypatch: OK, with F1. I re-ran the grep for main.X =, setattr(M, ...) and aliases myself and got the same 11 sites as the round 2 audit, all for names only main.py reads. main_files.live and the tripwire are sound. Nothing outside the tests reads main.<name>, and nothing in app/ imports app.main.
Imports and startup: OK. No module imports main, and each of the 8 imports alone. dir(app.main) keeps every base name and adds only the 7 mixins. python3 -m app.main with no HA gives the same output on base and head. The Dockerfile copies app/ whole.
Identity: OK. OPT-11 P3/P4 and SAFETY-79/84/87 were compared byte for byte in three ways: base tree with base tools, head tools on base app/, and head. TEL-07 announce: the 5 option sets hash the same.
Comments: OK. Every cut clause is in MAIN-orchestration.md at base, or in MAINT-05-per-pack-maintenance.md (the "never recalibrated" reason). All pointers resolve. The SAFETY-32 line now points at the pack_floor_soc docstring. I accept the 19-line trim.
CI and suite: OK.
58b6fa3: CI run 1586 checks / unit green; local full suite 102 files, 0 failed (1942 s).
8f0b847: CI run 1588 checks / unit green (infra-pin 1589 green); local full suite 102 files, 0 failed (1548 s); same-AST 196/196 OK.
Local runs used goodwe-addon-checks:1, --network none, the sim mounted and GOODWE_REQUIRE_HWSIM=1.
Findings
F1 (Medium, tests; fixed in 8f0b847). Two checks that scan the source still read only app/main.py, but the code they guard can now sit in ctl_*.py:
test_safety118.py:374 checks that "abs(raw) < 50" is gone. The forecast read is in ctl_inputs.py.
test_validate.py:1028 checks that no maintenance_* numeric fallback is left.
Proof: I planted both strings in ctl_inputs.py. The head tests passed (exit 0); the fixed tests fail. Both now read main_files.source(). Same-AST stays OK.
F2 (Low, tool).maint21_same_ast.py does not compare imports or the class header:
Swapping compute and maintenance_charge_floor in ctl_cycle's import passes.
Dropping InputsMixin from Controller's bases passes.
The suite and my runtime check cover the head. Worth knowing if the tool is reused.
F3 (Low, tests).test_safety91.py:302 reads Controller.__dict__["_held_back"]. This works only because an earlier without_fix() call put _held_back into Controller's own dict, and it would raise a KeyError, not pass silently. The tripwire also misses a setattr(M, k, v) where the name is a variable (test_maint10.py:388, restore only). test_batteries_screen.py:486 forbids homeview from importing main but not ctl_writes. None of these is a defect today.
F4 (Info).README.md:23 still shows the old layout (outside the allowed paths). Leave it for a follow-up.
Verdict: MERGE-READY on 8f0b847. I am merging pinned to that sha.
## MAINT-21 review (independent, Opus)
Head at start: `58b6fa3` (ls-remote matched the brief; `release/1.0` = `56fc41f`). Head now: `8f0b847`. I pushed one commit that changes tests only (F1 below).
### Results
1. **Pure move: OK.** `maint21_same_ast.py 56fc41f` prints 196/196 identical. `maint20_same_code.py 27a867f` prints `-OO same`. I planted a change in a scratch copy and the tool FAILed each time: a changed default argument, a removed `@property`, a renamed def (one missing, one extra), a def copied into a second mixin, and a mixin renamed to a class without the `Mixin` suffix.
2. **MRO and globals: OK.** The bases are `Inputs, Writes, Trade, Cycle, Alarms, Status, Tasks`. No name is defined twice, and nothing uses `super()`, `self.__x` name mangling, `global` or `__qualname__`. I checked at runtime, in the checks image, base against head:
- All 161 `Controller` attributes, looked up through the MRO, have the same bytecode, consts, names and defaults.
- All 410 globals that the split files read resolve to the same object or value as base `main.py`'s.
- The 44 names read by more than one file are the same object in every file.
3. **Monkeypatch: OK, with F1.** I re-ran the grep for `main.X =`, `setattr(M, ...)` and aliases myself and got the same 11 sites as the round 2 audit, all for names only `main.py` reads. `main_files.live` and the tripwire are sound. Nothing outside the tests reads `main.<name>`, and nothing in `app/` imports `app.main`.
4. **Imports and startup: OK.** No module imports `main`, and each of the 8 imports alone. `dir(app.main)` keeps every base name and adds only the 7 mixins. `python3 -m app.main` with no HA gives the same output on base and head. The Dockerfile copies `app/` whole.
5. **Identity: OK.** OPT-11 P3/P4 and SAFETY-79/84/87 were compared byte for byte in three ways: base tree with base tools, head tools on base `app/`, and head. TEL-07 announce: the 5 option sets hash the same.
6. **Comments: OK.** Every cut clause is in `MAIN-orchestration.md` at base, or in `MAINT-05-per-pack-maintenance.md` (the "never recalibrated" reason). All pointers resolve. The SAFETY-32 line now points at the `pack_floor_soc` docstring. I accept the 19-line trim.
7. **CI and suite: OK.**
- 58b6fa3: CI run 1586 `checks / unit` green; local full suite 102 files, 0 failed (1942 s).
- 8f0b847: CI run 1588 `checks / unit` green (`infra-pin` 1589 green); local full suite 102 files, 0 failed (1548 s); same-AST 196/196 OK.
- Local runs used `goodwe-addon-checks:1`, `--network none`, the sim mounted and `GOODWE_REQUIRE_HWSIM=1`.
### Findings
- **F1 (Medium, tests; fixed in 8f0b847).** Two checks that scan the source still read only `app/main.py`, but the code they guard can now sit in `ctl_*.py`:
- `test_safety118.py:374` checks that `"abs(raw) < 50"` is gone. The forecast read is in `ctl_inputs.py`.
- `test_validate.py:1028` checks that no `maintenance_*` numeric fallback is left.
Proof: I planted both strings in `ctl_inputs.py`. The head tests passed (exit 0); the fixed tests fail. Both now read `main_files.source()`. Same-AST stays OK.
- **F2 (Low, tool).** `maint21_same_ast.py` does not compare imports or the class header:
- Swapping `compute` and `maintenance_charge_floor` in `ctl_cycle`'s import passes.
- Dropping `InputsMixin` from `Controller`'s bases passes.
The suite and my runtime check cover the head. Worth knowing if the tool is reused.
- **F3 (Low, tests).** `test_safety91.py:302` reads `Controller.__dict__["_held_back"]`. This works only because an earlier `without_fix()` call put `_held_back` into `Controller`'s own dict, and it would raise a `KeyError`, not pass silently. The tripwire also misses a `setattr(M, k, v)` where the name is a variable (`test_maint10.py:388`, restore only). `test_batteries_screen.py:486` forbids homeview from importing `main` but not `ctl_writes`. None of these is a defect today.
- **F4 (Info).** `README.md:23` still shows the old layout (outside the allowed paths). Leave it for a follow-up.
**Verdict: MERGE-READY on 8f0b847.** I am merging pinned to that sha.
admin
merged commit 8508ac33f0 into release/1.02026-11-09 08:06:33 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
MAINT-21: split app/main.py's Controller into files by job, code unchanged.
What moves.
Controllerkeeps__init__, its roster/N=1 views, the site aggregates,shutdownandlog_eventinapp/main.py. The other 145-odd methods move, unchanged, into mixins:ctl_inputs,ctl_writes,ctl_trade,ctl_cycle(cycle()whole),ctl_alarms,ctl_status,ctl_tasks.rows.pyholds the four row/why functions. Module constants move to the first file that reads them;app/main.pyimports every one, so everything importable fromapp.mainbefore still is (192 names, checked; plus the 7 mixin classes). No mixin importsmain.Proof.
python3 goodwe_controller/tools/maint21_same_ast.py <base>compares every def, method and module-level assignment of main.py at the base with its new home by qualified name (--docstringsfor the strict form):OK: code moved, none changed, 196 of 196 units. The comments-only commit also passestools/maint20_same_code.py HEAD~(-OOcode objects the same).Commits. Prep (tests and replay tools read and patch through
tools/main_files.py;test_split_patches.pyis the tripwire), then one commit per new file, then comments only. The full local suite (102 files) was green on every commit.Not changed. No behaviour, log or UI text. CHANGELOG and version untouched. The rig test is the next step after merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
MAINT-21 review (independent, Opus)
Head at start:
58b6fa3(ls-remote matched the brief;release/1.0=56fc41f). Head now:8f0b847. I pushed one commit that changes tests only (F1 below).Results
maint21_same_ast.py 56fc41fprints 196/196 identical.maint20_same_code.py 27a867fprints-OO same. I planted a change in a scratch copy and the tool FAILed each time: a changed default argument, a removed@property, a renamed def (one missing, one extra), a def copied into a second mixin, and a mixin renamed to a class without theMixinsuffix.Inputs, Writes, Trade, Cycle, Alarms, Status, Tasks. No name is defined twice, and nothing usessuper(),self.__xname mangling,globalor__qualname__. I checked at runtime, in the checks image, base against head:Controllerattributes, looked up through the MRO, have the same bytecode, consts, names and defaults.main.py's.main.X =,setattr(M, ...)and aliases myself and got the same 11 sites as the round 2 audit, all for names onlymain.pyreads.main_files.liveand the tripwire are sound. Nothing outside the tests readsmain.<name>, and nothing inapp/importsapp.main.main, and each of the 8 imports alone.dir(app.main)keeps every base name and adds only the 7 mixins.python3 -m app.mainwith no HA gives the same output on base and head. The Dockerfile copiesapp/whole.app/, and head. TEL-07 announce: the 5 option sets hash the same.MAIN-orchestration.mdat base, or inMAINT-05-per-pack-maintenance.md(the "never recalibrated" reason). All pointers resolve. The SAFETY-32 line now points at thepack_floor_socdocstring. I accept the 19-line trim.58b6fa3: CI run 1586checks / unitgreen; local full suite 102 files, 0 failed (1942 s).8f0b847: CI run 1588checks / unitgreen (infra-pin1589 green); local full suite 102 files, 0 failed (1548 s); same-AST 196/196 OK.goodwe-addon-checks:1,--network none, the sim mounted andGOODWE_REQUIRE_HWSIM=1.Findings
F1 (Medium, tests; fixed in
8f0b847). Two checks that scan the source still read onlyapp/main.py, but the code they guard can now sit inctl_*.py:test_safety118.py:374checks that"abs(raw) < 50"is gone. The forecast read is inctl_inputs.py.test_validate.py:1028checks that nomaintenance_*numeric fallback is left.Proof: I planted both strings in
ctl_inputs.py. The head tests passed (exit 0); the fixed tests fail. Both now readmain_files.source(). Same-AST stays OK.F2 (Low, tool).
maint21_same_ast.pydoes not compare imports or the class header:computeandmaintenance_charge_floorinctl_cycle's import passes.InputsMixinfromController's bases passes.The suite and my runtime check cover the head. Worth knowing if the tool is reused.
F3 (Low, tests).
test_safety91.py:302readsController.__dict__["_held_back"]. This works only because an earlierwithout_fix()call put_held_backintoController's own dict, and it would raise aKeyError, not pass silently. The tripwire also misses asetattr(M, k, v)where the name is a variable (test_maint10.py:388, restore only).test_batteries_screen.py:486forbids homeview from importingmainbut notctl_writes. None of these is a defect today.F4 (Info).
README.md:23still shows the old layout (outside the allowed paths). Leave it for a follow-up.Verdict: MERGE-READY on
8f0b847. I am merging pinned to that sha.