TARIFF-11: the plan's account of itself, corrected and put on the page
Four defects of one shape - the add-on's report of its own tariff plan was wrong, always in the direction that flattered it - and one check, which is the deliverable. 1. `TariffPlan.words` counted POINTS and called them HOURS. The live add-on logged `dynamic_dayahead from generic_dayahead, 132 h` for 132 quarter-hourly points = 33 hours. Belgium moved to a 15-minute MTU on 2025-10-01, so this was wrong every time it was printed, and NOT by a constant factor: a 40-hour soak measured 1.79x .. 5.33x, because §8.3's carried points are hourly while the firm ones are quarter-hourly. `words` now reports both quantities, plus the firm/carried split and the spacing, in FCST-04's *Price history* wording so the two rows can be compared. 2. §8.3's firm-pair refusal and the flat-tariff refusal did the same, and they reach an operator through `self.arb.explain()`. This is exactly the point<->hour conflation TARIFF-14 removed from `at()`, left standing in the prose. `price_forecast.py`'s §8.3 carry-forward log line carried the third instance - a carried-HOUR count over a POINT count - and DOCS quotes it verbatim, so it is corrected here too. 3. The plan reached no HTTP surface at all. `rows["Tariff"]` was `static_status()`'s 30 s-tick sentence and never saw the provider plan - it read 'no static profile' at all 802 soak samples on a site whose feed was answering. It now carries the plan's shape. ⚠️ The two sources are kept distinguishable: the plan half is stamped with its own age, measured from `tariff_at`, because it can be up to TARIFF_MAX_AGE_S (5400 s) old. FCST-04 pins that row to DOCS as a pair; DOCS moves in this commit. 4. `main.py`'s "if and only if" was false. `cycle()` returns at the input hold before `self.arb = None`, so an armed site read `off (arbitrage_enabled)` - naming an option that is on - and, after one completed cycle, showed a decision up to 15 s stale as a current one. The row now reads the OPTION and separates three facts; `cycle_at` and `arb_at` make "this cycle reached no decision" an observation. THE DELIVERABLE IS THE CHECK. All four survived 29 green suites because nothing graded the strings a human is shown. `test_price.py` section 12 renders through the shipped `status_payload()`, parses each number out of its OWN SLOT and compares it against a figure computed from the fixture's construction parameters - not from the property the row was built out of (habit 4), and not as a substring of a sentence the same file wrote (habit 14). The fixture set is four plan shapes and the check computes and prints the field pairs no fixture separates. The *Arbitrage* row's states are driven through real cycles with a sensor answering None and graded by INEQUALITY. No behaviour changed: no threshold, refusal rung, claim or setpoint moved. MUTATIONS: 16, no survivors, one at +0 B (the risky delta) and red. Two were found by the battery INSIDE THIS CARD'S OWN CHECK: a -190 B survivor whose fixture varied two things at once (habit 4), and a mutation whose red was a TRACEBACK in an f-string operand and therefore named nothing (FCST-06). Both closed; the table is on the PR. All 29 test files green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
@@ -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-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
|
||||
ONE.** TARIFF-07 withdraws `engie_dynamic`, `totalenergies_dynamic` and
|
||||
@@ -57,6 +58,95 @@ 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-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
|
||||
it was.** They shipped together because nothing in this repository graded the
|
||||
strings a human is shown — every tariff check inspects the `TariffPlan`
|
||||
*object*, and all four were in the *sentences* built out of it.
|
||||
|
||||
**1. `TariffPlan.words` counted points and called them hours.** The live
|
||||
add-on logged `dynamic_dayahead from generic_dayahead, 132 h` for **132
|
||||
quarter-hourly points = 33 hours**. Belgium moved to a 15-minute MTU on
|
||||
2025-10-01, so on the real feed this was wrong every time it was printed, and
|
||||
⚠️ **not by a constant factor**: measured across a 40-hour soak the
|
||||
overstatement ranged **1.79× to 5.33×**, because §8.3's carried points are
|
||||
hourly while the firm ones are quarter-hourly and the ratio moves with the mix
|
||||
through the day. It now reads
|
||||
|
||||
```
|
||||
dynamic_dayahead from generic_dayahead, 132 points over 33 h,
|
||||
132 firm / 0 carried, 900 s spacing
|
||||
```
|
||||
|
||||
— the same plan, with each number in its own unit. On the weekday morning that
|
||||
plan is partly carried and reads `60 points over 33 h, 36 firm / 24 carried,
|
||||
900+3600 s spacing, provisional` instead, which is the state the soak found an
|
||||
operator could learn nothing about. The `N points over H h` wording is
|
||||
FCST-04's *Price history* wording on purpose, so the two rows describing the
|
||||
same feed can be compared rather than being two conventions on one page.
|
||||
|
||||
**2. The §8.3 and flat-tariff refusals did the same, and they reach an
|
||||
operator.** `§8.3: only 1 of 96 hours carry a published price` is 96 points,
|
||||
which is 24 hours; the flat rung counted the same points and called them `firm
|
||||
hours`. ⚠️ **This is exactly the point≠hour conflation TARIFF-14 removed from
|
||||
`at()`, left standing in the prose that describes it** — and it is not confined
|
||||
to a log line, because `self.arb.explain()` puts it on the status page. Both
|
||||
now print the two quantities separately: `only 1 of 96 points carry a published
|
||||
price - 1 of 24 hours`. The same conflation in `price_forecast.py`'s §8.3
|
||||
carry-forward log line — `%d of %d hours` with a carried-HOUR count over a
|
||||
POINT count — is corrected in the same commit.
|
||||
|
||||
**3. The plan reached no HTTP surface at all.** `origin`, `product`, the
|
||||
firm/carried split and the spacing appeared in **no** status row. `rows["Tariff"]`
|
||||
was `static_status()`'s sentence, computed on the 30 s I/O-free tick from its
|
||||
own static plan, and it **never saw the provider plan** — it read `'no static
|
||||
profile'` at all **802** soak samples on a site whose feed was answering. So an
|
||||
operator could not tell a working dynamic tariff from no tariff machinery at
|
||||
all, and could not learn that the plan was `provisional` for 76 % of the soak
|
||||
or mixed 900/3600 s for 70 %. The *Tariff* row now carries the plan.
|
||||
|
||||
⚠️ **The two halves of that row are not computed at the same moment and the row
|
||||
says so.** `static_status()` runs on the 30 s tick; the provider plan comes off
|
||||
the 1800 s background refresh and is served for up to `TARIFF_MAX_AGE_S`
|
||||
(5400 s since TARIFF-12). So the plan half is stamped with **its own age**,
|
||||
measured from `tariff_at` — which TARIFF-12 deliberately does not move on a
|
||||
kept plan. A provider site with no usable plan says that too, rather than
|
||||
falling silently back to the static sentence.
|
||||
|
||||
**4. `main.py`'s "if and only if" was false, and the row named an option that
|
||||
was on.** TARIFF-10's comment asserted that `self.arb is None` **iff**
|
||||
`arbitrage.enabled(self.o)` was False. `cycle()` returns at the input hold —
|
||||
inputs missing, `bad_for < stale_input_s`, 15 s by default — **before** it
|
||||
reaches `self.arb = None`. Driven: `arbitrage_enabled: true` plus a sensor
|
||||
answering `None` gives `arbitrage.enabled(opts) = True`, `self.arb = None` and
|
||||
a row reading `off (arbitrage_enabled)`. Past the first completed cycle the
|
||||
same early return leaves the **previous** decision standing, which the row
|
||||
rendered as a current one, up to ~15 s stale. The row now distinguishes three
|
||||
facts with three remedies — trading is off; trading is on and this cycle
|
||||
reached no decision; a decision, with its age — and it reads the **option**
|
||||
rather than inferring it from a missing decision. The comment states what is
|
||||
true.
|
||||
|
||||
⚠️ **THE DELIVERABLE IS THE CHECK, NOT THE FIXES.** All four survived 29 green
|
||||
suites because no check in this repository had ever read a rendered row.
|
||||
`test_price.py` section 12 renders through the shipped `Controller.status_payload()`,
|
||||
parses each number out of the row **by its own slot**, and compares it against
|
||||
a figure computed from the fixture's own construction parameters — never from
|
||||
the property the row was built out of (MUTATION.md habit 4). It is deliberately
|
||||
**not** a substring test over a sentence the same file wrote: `"132 points" in
|
||||
row` is satisfied by the number appearing in the *other* number's slot, which
|
||||
habit 14 shows is a zero-byte edit away. The fixtures are a **set** of four
|
||||
plan shapes, and the check computes and prints the table of field pairs no
|
||||
fixture separates — `none` — so a pair that became degenerate would go red
|
||||
rather than quietly vacuous. The *Arbitrage* row's three states are driven
|
||||
through real cycles with a sensor answering `None`, and graded by
|
||||
**inequality**: two facts that must not render identically.
|
||||
|
||||
**Nothing about what the battery decides changes.** No threshold, no refusal
|
||||
rung, no claim, no setpoint. Every edit is to a sentence, to a derived counter
|
||||
behind one, or to where a sentence is rendered.
|
||||
|
||||
### TARIFF-15 - your own tariff card, as two coefficients
|
||||
|
||||
**New price provider `custom_formula`, and one new option, `price_formula`.**
|
||||
|
||||
Reference in New Issue
Block a user