price_taxes_eur_kwh shipped at 0.06 EUR/kWh with no recorded provenance
anywhere — no card, no date, no component list — and roughly half the true
Flemish all-in non-energy figure. It now ships at 0.11736, and the rows it is
made of are in the code (app/price.py's FLEMISH_TAXES_COMPONENTS_EUR_KWH),
not only here:
component
c€/kWh
Fluvius distribution (Antwerpen)
5,35
Bijzondere accijns, "Alle verbruik" — no volume bands
4,876
Bijdrage groene stroom en WKK Vlaanderen
1,51
Energiebijdrage — 0 EUR/MWh since 2026-08-01, merged into the accijns
0
total
11,736 c€/kWh = 0.11736 €/kWh
Eneco residential card for Flanders, September 2026, incl. 6 % VAT. The exact sum
is shipped rather than the rounded 0.1174, so test_price.py section 1b can
assert an equality rather than a tolerance.
The zero row is deliberate."Vanaf 1 augustus 2026 bedraagt de
energiebijdrage 0 EUR/MWh" — it was merged into the excise. Carried as a row
rather than an absence so the next person re-deriving this from an older guide
finds it already accounted for instead of adding it a second time. test_price.py
1b grades the row's presence, because deleting it does not change the sum.
Why Antwerpen and not an average. Only the distribution row moves between
Fluvius DSOs (5,28 Midden-Vlaanderen → 6,70 West; total 0.117–0.131). The default
is a real DSO's real row, and it sits near the low end on purpose: the two
error directions are not symmetric. Too high carries a genuinely negative hour
above zero and the add-on exports into paid-for disposal — §17's own case. Too low only declines a discharge that would have paid.
What this does NOT do
It is not an arbitrage-correctness fix and it cannot be. This scalar is a LEVEL component: an additive constant cancels exactly in max − min and
leaves the argmin where it was. TARIFF-17 shipped that proof and none of it
changes — test_price.py 16b drives six flat scalars spanning and exceeding
the whole per-DSO range and gets the identical cheapest hour and the identical
spread to the last digit. DOCS and the CHANGELOG say so in those words.
What it does do
Every number an operator reads — at half the true value the status page, custom_formula's preview and the price history made the add-on look
considerably better than it is.
§17's negative-price guard (arbitrage.py), the one rung that tests an absolute all-in price rather than a difference. It fires when spot < −(markup + taxes), so its boundary moves −0.09000 → −0.14736 EUR/kWh
of spot. A −0.11868 spot hour used to read −0.02868 all-in and the discharge
was refused as paid-for disposal; at the real tax level it reads +0.02868
and discharges, because that hour genuinely paid. test_arbitrage.py2c
drives both sides through the shipped decide() and pins the boundary from
below as well.
TARIFF-17's rung — what was found, and what was chosen
Nothing there hardcoded 0.06.validate.py read PRICE_OPTION_DEFAULTS["price_taxes_eur_kwh"], so moving the default moves what
the rung refuses — a behaviour change rather than a drift, which is why it had to
be decided rather than noticed.
The Supervisor sets App.options = deepmerge(config.yaml defaults, stored options) (tools/supervisor_selfapi_probe.py §D; the reference install's export
is 70 of 71 keys), so a saved blob carries price_taxes_eur_kwh whether the
operator typed it or not. Moving the default turns every stored 0.06 into
"non-default" with nobody editing anything — and a working Walloon or Brussels
site with a price_taxes_profile would have met SystemExit(78) on the
upgrade, over an inert number the table overrides anyway.
Chosen: "untouched" becomes a set.price.py carries SUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,) beside the default it supersedes,
and validate.py reads a superseded default as untouched too. The rung's subject
is a number the operator typed; a default this add-on wrote into the blob is
not one. The rung is not weakened — 0.1198 beside a table is still refused by
name, and the refusal now says which figures it accepts. Graded by four new
checks plus a difference precondition (habit 1) and a non-empty-set guard, and M5/M5b/M6 below drive each half red on its own.
CFG-03
config.yaml and PRICE_OPTION_DEFAULTS moved together; test_price.py section
1 and test_defaults_pin.py stayed green by agreeing, not by being widened — M8 reverts config.yaml alone and section 1 goes red.
⚠️ One check had to be rewritten rather than re-pointed. test_price.py
section 0's fourth check justified the file's own grading constants by observing
that the shipped pair was degenerate — 0.06 is exactly twice0.03.
TARIFF-18 ends that coincidence, so the sentence is no longer true and is not
kept. The file now asserts per figure that neither shipped default is what it
grades with, which is the property that was actually load-bearing (and is
stronger than the tuple comparison it replaces, which one matching figure
satisfied).
Mutation ledger
Harness: /home/homelab/.cache/t18-mut/mutate.sh — mktemp backup per
invocation inside that directory (habit 18), the graded run in a container with
the tree mounted :ro so no __pycache__ can exist at all (TEST-15's
structural immunity, stronger than -B), the mutated line grep-ed on disk
before the run, and the revert fingerprinted against the pre-mutation sha256 afterwards. Every revert verified.
#
mutation
file
byte delta
result
M1
default 0.11736 → 0.12
app/price.py
−3 B
RED — test_price.py §1 mirror-vs-config.yaml, §1b "the 4 rows re-add to the shipped default: 0.11736 against 0.12", §1b card-literal; test_price_forecast.py §7 "…and it still holds TARIFF-18's Flemish figure"
RED — §1b "the 4 rows re-add to the shipped default: 0.11666 against 0.11736". The card-literal check stayed green, which is the point of having two sides
M3
delete the energiebijdragezero row
app/price.py
−79 B
RED — §1b "the energiebijdrage is carried as a row worth ZERO rather than left out…". The sum check stayed green — deleting a zero changes no total, which is exactly why presence is graded separately
M4
SUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,) → ()
app/price.py
−5 B
RED — test_price.py §1b "…it is no longer the superseded None"; test_validate.py difference-precondition, non-empty-set guard, and the refusal-sentence check. ⚠️ Both files read the tuple defensively so that emptying it is a named red rather than an IndexError inside an f-string — a crash takes a file red with nothing on the record saying which claim broke
M5
untouched drops the superseded half
app/validate.py
−43 B
RED — test_validate.py"…a blob that still carries the SUPERSEDED default 0.06 beside the table STARTS"
M5b
untouched drops the shipped half
app/validate.py
−13 B
RED — "…the SHIPPED scalar beside the table is NOT refused" + the accepted baseline. Both halves individually live; no masking (MUTATION.md TEST-16)
M6
the rung stops refusing anything (and False)
app/validate.py
−47 B
RED — 4 named checks, including "…a value that is NEITHER the shipped default NOR a superseded one is still REFUSED"
M7
§17 guard given the old margin as a tolerance: price < 0 → price < -0.06
app/arbitrage.py
+4 B
RED — 8 named checks, incl. both new 2c ones ("at the SUPERSEDED 0.06 … §17 REFUSES", "a spot hour at −0.14836 … is still refused") and SAFETY-26's ε-sweep
M8
config.yaml default reverted to 0.06, mirror left alone
config.yaml
−3 B
RED — CFG-03's tripwire, test_price.py §1 "…each shipped default equals the in-code mirror"
M9
the per-DSO warning loses its needle in the Supervisor description
translations/en.yaml
+4 B
RED — test_translations.py"Uncited: ['price_taxes_eur_kwh does not cite 'YOUR OWN DSO'']"
M10
⚠️rows AND default AND config.yaml moved together to West-Vlaanderen's row (0.0670 / 0.13086)
app/price.py + config.yaml
+0 B / +0 B⚠️
RED — but only on the card literal typed in the test file and the per-DSO band check. ok the 4 rows re-add to the shipped default: 0.13086 against 0.13086 — the re-sum check cannot see this, which is why §1b grades the total from two independent sides
Ten mutations, ten red, every revert fingerprinted against its pre-mutation sha256. Two are +0 B (M2, M10) — the delta this repository treats as
risky — and both were produced under the :ro mount, where no .pyc can exist
to go stale.
What the battery cannot see, with sizes.test_price.py 1b grades the
default against the card as a number; it cannot grade the card against
Belgium. Two figures copied off the wrong month agree with each other perfectly
and pass — TARIFF-15's own §7 limit, one option over. And M1 left test_validate.pygreen, correctly: that file reads the default out of the
dict, so it is insensitive to its value by design and the CFG-03 pin is what
covers it.
Suite
29/29 test files green, baseline established first at 7796f16 (29/29 before
any edit). Every run in the checks container with the worktree mounted :ro.
What is NOT in this branch
No version number.## Unreleased only — the owner's decision, pending
whether 0.9.0's image has been built and pulled.
No deploy. Nothing touched /addons/, the box or the inverter.
An existing install does not pick this up on the upgrade alone. The
Supervisor's deepmerge keeps the stored 0.06, so the new default reaches a
fresh install and an existing one only when its options are re-saved. Said
plainly in the CHANGELOG rather than rounded off, and tools/predeploy_check.py --options <live blob> is what would confirm the live shape before deploying.
## The number
`price_taxes_eur_kwh` shipped at **0.06 EUR/kWh** with **no recorded provenance
anywhere** — no card, no date, no component list — and roughly **half** the true
Flemish all-in non-energy figure. It now ships at **0.11736**, and the rows it is
made of are **in the code** (`app/price.py`'s `FLEMISH_TAXES_COMPONENTS_EUR_KWH`),
not only here:
| component | c€/kWh |
|---|---|
| Fluvius distribution (**Antwerpen**) | 5,35 |
| Bijzondere accijns, *"Alle verbruik"* — no volume bands | 4,876 |
| Bijdrage groene stroom en WKK Vlaanderen | 1,51 |
| Energiebijdrage — **0 EUR/MWh since 2026-08-01**, merged into the accijns | 0 |
| **total** | **11,736 c€/kWh = 0.11736 €/kWh** |
Eneco residential card for Flanders, September 2026, incl. 6 % VAT. The exact sum
is shipped rather than the rounded 0.1174, so `test_price.py` section 1b can
assert an **equality** rather than a tolerance.
**The zero row is deliberate.** *"Vanaf 1 augustus 2026 bedraagt de
energiebijdrage 0 EUR/MWh"* — it was merged into the excise. Carried as a **row**
rather than an absence so the next person re-deriving this from an older guide
finds it already accounted for instead of adding it a second time. `test_price.py`
1b grades the row's **presence**, because deleting it does not change the sum.
**Why Antwerpen and not an average.** Only the distribution row moves between
Fluvius DSOs (5,28 Midden-Vlaanderen → 6,70 West; total 0.117–0.131). The default
is a **real DSO's real row**, and it sits near the **low** end on purpose: the two
error directions are not symmetric. Too **high** carries a genuinely negative hour
above zero and the add-on exports into paid-for disposal — §17's own case. Too
**low** only declines a discharge that would have paid.
## What this does NOT do
**It is not an arbitrage-correctness fix and it cannot be.** This scalar is a
**LEVEL** component: an additive constant cancels exactly in `max − min` and
leaves the argmin where it was. TARIFF-17 shipped that proof and none of it
changes — `test_price.py` 16b drives **six** flat scalars spanning and exceeding
the whole per-DSO range and gets the identical cheapest hour and the identical
spread to the last digit. DOCS and the CHANGELOG say so in those words.
## What it does do
* **Every number an operator reads** — at half the true value the status page,
`custom_formula`'s preview and the price history made the add-on look
considerably better than it is.
* **§17's negative-price guard** (`arbitrage.py`), the one rung that tests an
**absolute** all-in price rather than a difference. It fires when
`spot < −(markup + taxes)`, so its boundary moves **−0.09000 → −0.14736 EUR/kWh
of spot**. A −0.11868 spot hour used to read −0.02868 all-in and the discharge
was refused as paid-for disposal; at the real tax level it reads **+0.02868**
and discharges, because that hour genuinely paid. `test_arbitrage.py` **2c**
drives both sides through the shipped `decide()` and pins the boundary **from
below** as well.
## TARIFF-17's rung — what was found, and what was chosen
**Nothing there hardcoded `0.06`.** `validate.py` read
`PRICE_OPTION_DEFAULTS["price_taxes_eur_kwh"]`, so moving the default moves what
the rung refuses — a behaviour change rather than a drift, which is why it had to
be decided rather than noticed.
The Supervisor sets `App.options = deepmerge(config.yaml defaults, stored
options)` (`tools/supervisor_selfapi_probe.py` §D; the reference install's export
is **70 of 71 keys**), so a saved blob carries `price_taxes_eur_kwh` whether the
operator typed it or not. Moving the default turns **every stored `0.06` into
"non-default" with nobody editing anything** — and a working Walloon or Brussels
site with a `price_taxes_profile` would have met **`SystemExit(78)` on the
upgrade**, over an inert number the table overrides anyway.
**Chosen: "untouched" becomes a set.** `price.py` carries
`SUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,)` beside the default it supersedes,
and `validate.py` reads a superseded default as untouched too. The rung's subject
is a number the **operator typed**; a default this add-on wrote into the blob is
not one. **The rung is not weakened** — 0.1198 beside a table is still refused by
name, and the refusal now says which figures it accepts. Graded by four new
checks plus a difference precondition (habit 1) and a non-empty-set guard, and
`M5`/`M5b`/`M6` below drive each half red on its own.
## CFG-03
`config.yaml` and `PRICE_OPTION_DEFAULTS` moved together; `test_price.py` section
1 and `test_defaults_pin.py` stayed green **by agreeing**, not by being widened —
`M8` reverts `config.yaml` alone and section 1 goes red.
⚠️ One check had to be **rewritten rather than re-pointed**. `test_price.py`
section 0's fourth check justified the file's own grading constants by observing
that the shipped pair was degenerate — `0.06` is *exactly twice* `0.03`.
TARIFF-18 ends that coincidence, so the sentence is **no longer true** and is not
kept. The file now asserts **per figure** that neither shipped default is what it
grades with, which is the property that was actually load-bearing (and is
stronger than the tuple comparison it replaces, which one matching figure
satisfied).
## Mutation ledger
Harness: `/home/homelab/.cache/t18-mut/mutate.sh` — `mktemp` backup **per
invocation** inside that directory (habit 18), the graded run in a container with
the tree mounted **`:ro`** so no `__pycache__` can exist at all (TEST-15's
structural immunity, stronger than `-B`), the mutated line `grep`-ed on disk
before the run, and the **revert fingerprinted** against the pre-mutation
`sha256` afterwards. Every revert verified.
| # | mutation | file | byte delta | result |
|---|---|---|---|---|
| **M1** | default `0.11736` → `0.12` | `app/price.py` | −3 B | **RED** — `test_price.py` §1 mirror-vs-`config.yaml`, §1b *"the 4 rows re-add to the shipped default: 0.11736 against 0.12"*, §1b card-literal; `test_price_forecast.py` §7 *"…and it still holds TARIFF-18's Flemish figure"* |
| **M2** | Fluvius row `0.0535` → `0.0528` (Midden-Vlaanderen's row) | `app/price.py` | **+0 B** ⚠️ | **RED** — §1b *"the 4 rows re-add to the shipped default: 0.11666 against 0.11736"*. The card-literal check stayed green, which is the point of having two sides |
| **M3** | delete the `energiebijdrage` **zero** row | `app/price.py` | −79 B | **RED** — §1b *"the energiebijdrage is carried as a row worth ZERO rather than left out…"*. The **sum** check stayed green — deleting a zero changes no total, which is exactly why presence is graded separately |
| **M4** | `SUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,)` → `()` | `app/price.py` | −5 B | **RED** — `test_price.py` §1b *"…it is no longer the superseded **None**"*; `test_validate.py` difference-precondition, non-empty-set guard, and the refusal-sentence check. ⚠️ Both files read the tuple **defensively** so that emptying it is a *named* red rather than an `IndexError` inside an f-string — a crash takes a file red with nothing on the record saying which claim broke |
| **M5** | `untouched` drops the **superseded** half | `app/validate.py` | −43 B | **RED** — `test_validate.py` *"…a blob that still carries the SUPERSEDED default 0.06 beside the table STARTS"* |
| **M5b** | `untouched` drops the **shipped** half | `app/validate.py` | −13 B | **RED** — *"…the SHIPPED scalar beside the table is NOT refused"* + the accepted baseline. Both halves individually live; no masking (MUTATION.md TEST-16) |
| **M6** | the rung stops refusing anything (`and False`) | `app/validate.py` | −47 B | **RED** — 4 named checks, including *"…a value that is NEITHER the shipped default NOR a superseded one is still REFUSED"* |
| **M7** | §17 guard given the old margin as a tolerance: `price < 0` → `price < -0.06` | `app/arbitrage.py` | +4 B | **RED** — 8 named checks, incl. **both** new 2c ones (*"at the SUPERSEDED 0.06 … §17 REFUSES"*, *"a spot hour at −0.14836 … is still refused"*) and SAFETY-26's ε-sweep |
| **M8** | `config.yaml` default reverted to `0.06`, mirror left alone | `config.yaml` | −3 B | **RED** — CFG-03's tripwire, `test_price.py` §1 *"…each shipped default equals the in-code mirror"* |
| **M9** | the per-DSO warning loses its needle in the Supervisor description | `translations/en.yaml` | +4 B | **RED** — `test_translations.py` *"Uncited: ['price_taxes_eur_kwh does not cite 'YOUR OWN DSO'']"* |
| **M10** | ⚠️ **rows AND default AND `config.yaml` moved together** to West-Vlaanderen's row (`0.0670` / `0.13086`) | `app/price.py` + `config.yaml` | **+0 B / +0 B** ⚠️ | **RED** — but *only* on the **card literal typed in the test file** and the per-DSO band check. `ok the 4 rows re-add to the shipped default: 0.13086 against 0.13086` — the re-sum check **cannot see this**, which is why §1b grades the total from two independent sides |
Ten mutations, ten red, **every revert fingerprinted** against its pre-mutation
`sha256`. Two are `+0 B` (**M2**, **M10**) — the delta this repository treats as
risky — and both were produced under the `:ro` mount, where no `.pyc` can exist
to go stale.
**What the battery cannot see, with sizes.** `test_price.py` 1b grades the
default against the card as a **number**; it cannot grade the card against
Belgium. Two figures copied off the wrong month agree with each other perfectly
and pass — TARIFF-15's own §7 limit, one option over. And `M1` left
`test_validate.py` **green**, correctly: that file reads the default out of the
dict, so it is insensitive to its value by design and the CFG-03 pin is what
covers it.
## Suite
**29/29 test files green**, baseline established first at `7796f16` (29/29 before
any edit). Every run in the checks container with the worktree mounted `:ro`.
## What is NOT in this branch
* **No version number.** `## Unreleased` only — the owner's decision, pending
whether 0.9.0's image has been built and pulled.
* **No deploy.** Nothing touched `/addons/`, the box or the inverter.
* **An existing install does not pick this up on the upgrade alone.** The
Supervisor's `deepmerge` keeps the stored `0.06`, so the new default reaches a
fresh install and an existing one only when its options are re-saved. Said
plainly in the CHANGELOG rather than rounded off, and `tools/predeploy_check.py
--options <live blob>` is what would confirm the live shape before deploying.
**Do not merge.**
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
`price_taxes_eur_kwh` shipped at 0.06 EUR/kWh with no recorded provenance -
no card, no date, no component list - and roughly HALF the true Flemish
all-in non-energy figure. It now ships at 0.11736, and the rows it is made
of are in the code rather than in this message:
Fluvius distribution (Antwerpen) 5,35 c EUR/kWh
Bijzondere accijns, "Alle verbruik" 4,876
Bijdrage groene stroom en WKK Vlaanderen 1,51
Energiebijdrage (0 EUR/MWh since 2026-08-01) 0
------
11,736 c EUR/kWh = 0.11736
Eneco residential card for Flanders, September 2026, incl. 6 % VAT.
`app/price.py`'s FLEMISH_TAXES_COMPONENTS_EUR_KWH carries the rows and
test_price.py section 1b re-adds them - and also compares the total against
the card's own figures typed in the test file, so an edit that moved the
rows and the default together is still caught.
WHAT THIS IS NOT. It is not an arbitrage-correctness fix and it cannot be.
This scalar is a LEVEL component: an additive constant cancels exactly in
`max - min` and leaves the argmin where it was. TARIFF-17 shipped that proof
and it is unchanged - test_price.py 16b drives six flat scalars spanning and
exceeding the whole per-DSO range and gets the identical cheapest hour and
the identical spread to the last digit. DOCS and the CHANGELOG say so in
those words.
WHAT IT IS. A display and guard fix:
* every number an operator reads, which at half the true value made the
add-on look considerably better than it is; and
* §17's negative-price guard in arbitrage.py - the one rung that tests an
ABSOLUTE all-in price rather than a difference. It fires when
`spot < -(markup + taxes)`, so its boundary moves -0.09 -> -0.14736
EUR/kWh of spot. test_arbitrage.py section 2c drives both sides through
the shipped decide() and pins the boundary from below as well.
The default is Antwerpen's real row, near the LOW end of the 0.117-0.131
per-DSO band, and that is deliberate: the error directions are not
symmetric. Too high carries a genuinely negative hour above zero and the
add-on exports into paid-for disposal - §17's own case. Too low only
declines a discharge that would have paid.
TARIFF-17's INTERACTION, WHICH HAD TO BE DECIDED. Nothing in that rung
hardcoded 0.06; it read PRICE_OPTION_DEFAULTS, so moving the default moves
what it refuses. The Supervisor sets `App.options = deepmerge(config.yaml
defaults, stored options)`, so a saved blob carries the key whether it was
typed or not - every stored 0.06 became "non-default" with nobody editing
anything, and a working Walloon or Brussels site with a taxes table would
have met SystemExit(78) on the upgrade over an inert number. price.py now
carries SUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,) and validate.py reads a
superseded default as untouched too. The rung is NOT weakened: 0.1198 beside
a table is still refused by name, and the refusal now names both figures.
config.yaml and PRICE_OPTION_DEFAULTS moved together; CFG-03's tripwire
(test_price.py section 1, test_defaults_pin.py) stayed green by agreeing.
test_price.py section 0's fourth check was REWRITTEN rather than re-pointed:
it used to justify the file's own grading constants by observing that the
shipped pair was degenerate (0.06 is exactly twice 0.03). TARIFF-18 ends
that coincidence, so the sentence is no longer true and is not kept.
29/29 test files green. Mutation ledger on the PR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
admin
merged commit 4dcd9b0fc6 into release/1.02026-09-13 16:33:38 +02: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.
The number
price_taxes_eur_kwhshipped at 0.06 EUR/kWh with no recorded provenanceanywhere — no card, no date, no component list — and roughly half the true
Flemish all-in non-energy figure. It now ships at 0.11736, and the rows it is
made of are in the code (
app/price.py'sFLEMISH_TAXES_COMPONENTS_EUR_KWH),not only here:
Eneco residential card for Flanders, September 2026, incl. 6 % VAT. The exact sum
is shipped rather than the rounded 0.1174, so
test_price.pysection 1b canassert an equality rather than a tolerance.
The zero row is deliberate. "Vanaf 1 augustus 2026 bedraagt de
energiebijdrage 0 EUR/MWh" — it was merged into the excise. Carried as a row
rather than an absence so the next person re-deriving this from an older guide
finds it already accounted for instead of adding it a second time.
test_price.py1b grades the row's presence, because deleting it does not change the sum.
Why Antwerpen and not an average. Only the distribution row moves between
Fluvius DSOs (5,28 Midden-Vlaanderen → 6,70 West; total 0.117–0.131). The default
is a real DSO's real row, and it sits near the low end on purpose: the two
error directions are not symmetric. Too high carries a genuinely negative hour
above zero and the add-on exports into paid-for disposal — §17's own case. Too
low only declines a discharge that would have paid.
What this does NOT do
It is not an arbitrage-correctness fix and it cannot be. This scalar is a
LEVEL component: an additive constant cancels exactly in
max − minandleaves the argmin where it was. TARIFF-17 shipped that proof and none of it
changes —
test_price.py16b drives six flat scalars spanning and exceedingthe whole per-DSO range and gets the identical cheapest hour and the identical
spread to the last digit. DOCS and the CHANGELOG say so in those words.
What it does do
custom_formula's preview and the price history made the add-on lookconsiderably better than it is.
arbitrage.py), the one rung that tests anabsolute all-in price rather than a difference. It fires when
spot < −(markup + taxes), so its boundary moves −0.09000 → −0.14736 EUR/kWhof spot. A −0.11868 spot hour used to read −0.02868 all-in and the discharge
was refused as paid-for disposal; at the real tax level it reads +0.02868
and discharges, because that hour genuinely paid.
test_arbitrage.py2cdrives both sides through the shipped
decide()and pins the boundary frombelow as well.
TARIFF-17's rung — what was found, and what was chosen
Nothing there hardcoded
0.06.validate.pyreadPRICE_OPTION_DEFAULTS["price_taxes_eur_kwh"], so moving the default moves whatthe rung refuses — a behaviour change rather than a drift, which is why it had to
be decided rather than noticed.
The Supervisor sets
App.options = deepmerge(config.yaml defaults, stored options)(tools/supervisor_selfapi_probe.py§D; the reference install's exportis 70 of 71 keys), so a saved blob carries
price_taxes_eur_kwhwhether theoperator typed it or not. Moving the default turns every stored
0.06into"non-default" with nobody editing anything — and a working Walloon or Brussels
site with a
price_taxes_profilewould have metSystemExit(78)on theupgrade, over an inert number the table overrides anyway.
Chosen: "untouched" becomes a set.
price.pycarriesSUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,)beside the default it supersedes,and
validate.pyreads a superseded default as untouched too. The rung's subjectis a number the operator typed; a default this add-on wrote into the blob is
not one. The rung is not weakened — 0.1198 beside a table is still refused by
name, and the refusal now says which figures it accepts. Graded by four new
checks plus a difference precondition (habit 1) and a non-empty-set guard, and
M5/M5b/M6below drive each half red on its own.CFG-03
config.yamlandPRICE_OPTION_DEFAULTSmoved together;test_price.pysection1 and
test_defaults_pin.pystayed green by agreeing, not by being widened —M8revertsconfig.yamlalone and section 1 goes red.⚠️ One check had to be rewritten rather than re-pointed.
test_price.pysection 0's fourth check justified the file's own grading constants by observing
that the shipped pair was degenerate —
0.06is exactly twice0.03.TARIFF-18 ends that coincidence, so the sentence is no longer true and is not
kept. The file now asserts per figure that neither shipped default is what it
grades with, which is the property that was actually load-bearing (and is
stronger than the tuple comparison it replaces, which one matching figure
satisfied).
Mutation ledger
Harness:
/home/homelab/.cache/t18-mut/mutate.sh—mktempbackup perinvocation inside that directory (habit 18), the graded run in a container with
the tree mounted
:roso no__pycache__can exist at all (TEST-15'sstructural immunity, stronger than
-B), the mutated linegrep-ed on diskbefore the run, and the revert fingerprinted against the pre-mutation
sha256afterwards. Every revert verified.0.11736→0.12app/price.pytest_price.py§1 mirror-vs-config.yaml, §1b "the 4 rows re-add to the shipped default: 0.11736 against 0.12", §1b card-literal;test_price_forecast.py§7 "…and it still holds TARIFF-18's Flemish figure"0.0535→0.0528(Midden-Vlaanderen's row)app/price.pyenergiebijdragezero rowapp/price.pySUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,)→()app/price.pytest_price.py§1b "…it is no longer the superseded None";test_validate.pydifference-precondition, non-empty-set guard, and the refusal-sentence check. ⚠️ Both files read the tuple defensively so that emptying it is a named red rather than anIndexErrorinside an f-string — a crash takes a file red with nothing on the record saying which claim brokeuntoucheddrops the superseded halfapp/validate.pytest_validate.py"…a blob that still carries the SUPERSEDED default 0.06 beside the table STARTS"untoucheddrops the shipped halfapp/validate.pyand False)app/validate.pyprice < 0→price < -0.06app/arbitrage.pyconfig.yamldefault reverted to0.06, mirror left aloneconfig.yamltest_price.py§1 "…each shipped default equals the in-code mirror"translations/en.yamltest_translations.py"Uncited: ['price_taxes_eur_kwh does not cite 'YOUR OWN DSO'']"config.yamlmoved together to West-Vlaanderen's row (0.0670/0.13086)app/price.py+config.yamlok the 4 rows re-add to the shipped default: 0.13086 against 0.13086— the re-sum check cannot see this, which is why §1b grades the total from two independent sidesTen mutations, ten red, every revert fingerprinted against its pre-mutation
sha256. Two are+0 B(M2, M10) — the delta this repository treats asrisky — and both were produced under the
:romount, where no.pyccan existto go stale.
What the battery cannot see, with sizes.
test_price.py1b grades thedefault against the card as a number; it cannot grade the card against
Belgium. Two figures copied off the wrong month agree with each other perfectly
and pass — TARIFF-15's own §7 limit, one option over. And
M1lefttest_validate.pygreen, correctly: that file reads the default out of thedict, so it is insensitive to its value by design and the CFG-03 pin is what
covers it.
Suite
29/29 test files green, baseline established first at
7796f16(29/29 beforeany edit). Every run in the checks container with the worktree mounted
:ro.What is NOT in this branch
## Unreleasedonly — the owner's decision, pendingwhether 0.9.0's image has been built and pulled.
/addons/, the box or the inverter.Supervisor's
deepmergekeeps the stored0.06, so the new default reaches afresh install and an existing one only when its options are re-saved. Said
plainly in the CHANGELOG rather than rounded off, and
tools/predeploy_check.py --options <live blob>is what would confirm the live shape before deploying.Do not merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
`price_taxes_eur_kwh` shipped at 0.06 EUR/kWh with no recorded provenance - no card, no date, no component list - and roughly HALF the true Flemish all-in non-energy figure. It now ships at 0.11736, and the rows it is made of are in the code rather than in this message: Fluvius distribution (Antwerpen) 5,35 c EUR/kWh Bijzondere accijns, "Alle verbruik" 4,876 Bijdrage groene stroom en WKK Vlaanderen 1,51 Energiebijdrage (0 EUR/MWh since 2026-08-01) 0 ------ 11,736 c EUR/kWh = 0.11736 Eneco residential card for Flanders, September 2026, incl. 6 % VAT. `app/price.py`'s FLEMISH_TAXES_COMPONENTS_EUR_KWH carries the rows and test_price.py section 1b re-adds them - and also compares the total against the card's own figures typed in the test file, so an edit that moved the rows and the default together is still caught. WHAT THIS IS NOT. It is not an arbitrage-correctness fix and it cannot be. This scalar is a LEVEL component: an additive constant cancels exactly in `max - min` and leaves the argmin where it was. TARIFF-17 shipped that proof and it is unchanged - test_price.py 16b drives six flat scalars spanning and exceeding the whole per-DSO range and gets the identical cheapest hour and the identical spread to the last digit. DOCS and the CHANGELOG say so in those words. WHAT IT IS. A display and guard fix: * every number an operator reads, which at half the true value made the add-on look considerably better than it is; and * §17's negative-price guard in arbitrage.py - the one rung that tests an ABSOLUTE all-in price rather than a difference. It fires when `spot < -(markup + taxes)`, so its boundary moves -0.09 -> -0.14736 EUR/kWh of spot. test_arbitrage.py section 2c drives both sides through the shipped decide() and pins the boundary from below as well. The default is Antwerpen's real row, near the LOW end of the 0.117-0.131 per-DSO band, and that is deliberate: the error directions are not symmetric. Too high carries a genuinely negative hour above zero and the add-on exports into paid-for disposal - §17's own case. Too low only declines a discharge that would have paid. TARIFF-17's INTERACTION, WHICH HAD TO BE DECIDED. Nothing in that rung hardcoded 0.06; it read PRICE_OPTION_DEFAULTS, so moving the default moves what it refuses. The Supervisor sets `App.options = deepmerge(config.yaml defaults, stored options)`, so a saved blob carries the key whether it was typed or not - every stored 0.06 became "non-default" with nobody editing anything, and a working Walloon or Brussels site with a taxes table would have met SystemExit(78) on the upgrade over an inert number. price.py now carries SUPERSEDED_TAXES_DEFAULTS_EUR_KWH = (0.06,) and validate.py reads a superseded default as untouched too. The rung is NOT weakened: 0.1198 beside a table is still refused by name, and the refusal now names both figures. config.yaml and PRICE_OPTION_DEFAULTS moved together; CFG-03's tripwire (test_price.py section 1, test_defaults_pin.py) stayed green by agreeing. test_price.py section 0's fourth check was REWRITTEN rather than re-pointed: it used to justify the file's own grading constants by observing that the shipped pair was degenerate (0.06 is exactly twice 0.03). TARIFF-18 ends that coincidence, so the sentence is no longer true and is not kept. 29/29 test files green. Mutation ledger on the PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU