Merge remote-tracking branch 'origin/release/1.0' into TARIFF-11
checks / unit (pull_request) Successful in 7m20s

# Conflicts:
#	goodwe_controller/CHANGELOG.md
This commit is contained in:
2026-09-13 13:51:35 +02:00
4 changed files with 759 additions and 17 deletions
+51
View File
@@ -22,6 +22,7 @@ if it has, it needs 0.10.0. Whoever deploys decides.
| _this branch_ | TARIFF-14 - the price a trade is decided against is the price of the interval being traded |
| _this branch_ | TARIFF-07 - three plugins that never fetched a price are withdrawn, and a site configured on one is told so **⚠️ BACKWARD-INCOMPATIBLE** |
| _this branch_ | TARIFF-15 - `custom_formula`: your own tariff card's two coefficients, graded against the card's own printed price |
| _this branch_ | TARIFF-13 - the capacity-tariff row's "quarter(s) banked" stops resetting to 0 at every restart, and a tied peak's quarter stops moving |
| _this branch_ | TARIFF-11 - the add-on's account of the tariff plan stops flattering it, and the status page is graded as a surface |
⚠️ **A CONFIG THAT STARTS ON THE CURRENT VERSION CAN BE REFUSED ON THE NEXT
@@ -58,6 +59,56 @@ separate card. *(Annotated 2026-09-13: that card is **TARIFF-15**, below, and it
shipped on this branch as `custom_formula`. It is still not an alias — the
operator types their own coefficients; nothing is invented on their behalf.)*
### TARIFF-13 - the coverage figure beside the peak, and which quarter gets it
**The capacity-tariff status row's `N quarter(s) banked` is read from the
database instead of from process memory.** It was an `int` on `PeakTracker`,
starting at 0 and counted up inside `sync()`, printed next to a peak read from
SQLite. A 40-hour soak caught what that means at a restart, on both instances:
```
before: 2026-09: peak 365 W, set by the quarter at 2026-09-11T18:45:00+00:00
- 10 % of the 3500 W soft limit; 120 quarter(s) banked
after : 2026-09: peak 365 W, set by the quarter at 2026-09-11T18:45:00+00:00
- 10 % of the 3500 W soft limit; 0 quarter(s) banked
```
The peak is durable and was never wrong. The **count** is what says how much of
the billing month that peak covers, and 365 W over 120 quarters and 365 W over
0 quarters are different claims about the same number — so mid-month, after
every restart, the page made the weaker claim while showing the stronger one's
value. TARIFF-01's rule that an unreadable store must not read as a zero peak,
one column over. Two more defects in the same variable, neither in the ticket:
it was **never scoped to a month** (on 1 October the row still showed
September's tally beside October's empty month), and it **missed the block
`restore()` banks** from the checkpoint at startup.
⚠️ **AN UNREADABLE STORE NOW SAYS SO IN THAT CLAUSE.** Where the database will
not answer a count, the row reads `an unreadable number of quarter(s) banked`.
`0` was never available; `None` was rejected too, because `None quarter(s)
banked` reads as the same zero. `0 quarter(s) banked` stays true and reachable —
it is what a month nothing has been written to says.
⚠️ **AND A TIED PEAK'S ATTRIBUTED QUARTER IS NOW DECIDED: the EARLIEST wins.**
The wattage is what the utility bills, so this changes no bill and no guard —
`peak_w` is still only ever raised and SAFETY-07 reads the same watts. What it
changes is the answer to "when did my monthly peak happen", which an operator
goes and investigates; it now stops moving. The two readers of that record
**did** disagree: the `monthly_peak` upsert's strict `>` kept whichever tied
block was *written* first, while `recompute_month()` has always kept the
earliest — the same by accident only while blocks arrive in time order, and
measured to diverge (09:45 against 09:00) when they do not.
⚠️ **THE SOAK'S DRIFTING QUARTER WAS NOT A TIE, AND IS LEFT ALONE.** Driven,
exactly equal quarters never re-attributed at all. What moved the timestamp was
a genuine new maximum **below the row's whole-watt resolution** — 365.0000001 W
is a new peak to the database and the same `365 W` to the reader. That is the
instrument being right, and printing more decimals to explain it would put
noise in the one row an operator reads when the guard engages.
Reasoning in `DECISIONS/TARIFF-13-the-coverage-of-a-peak.md`; parts 11 and 12
of `test_tariff.py` drive a real restart — a second `Controller` against the
same file — and pin both decisions.
### TARIFF-11 - the plan's own account of itself, corrected and put on the page
**Four defects, one shape: every one of them made the add-on look better than