Conflict was the one anticipated: both cards prepend to CHANGELOG.md's Unreleased section. Both entries kept, SAFETY-07 first. Nothing else of TARIFF-02's surface (app/price.py, validate.py rule 7, config.yaml, DOCS.md) overlaps this card - config.yaml and validate.py are untouched here, so the CFG-03 check still reads a config.yaml that only TARIFF-02 has widened. Full suite green on the merge result, test_price.py included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VXjA3LWccN1rJsk3okyPC
This commit is contained in:
@@ -642,6 +642,97 @@ unchanged is 12 W, which is inside the 15 W deadband — so the command there is
|
||||
parked at 0 W and the two halves of the rule cannot both be true. At the schema
|
||||
floor of 60 s that figure is 60 W and only the margin is left.
|
||||
|
||||
### Energy price providers
|
||||
|
||||
> **Nothing in 0.5.x acts on a price.** This release ships the *plumbing*: the
|
||||
> five provider plugins, the canonical all-in format they emit, and the config
|
||||
> that selects one. The arbitrage strategy that would use it is a later ticket.
|
||||
> Turning a provider on today fetches, validates and publishes prices and
|
||||
> changes no charging decision. That is deliberate — the seam lands first.
|
||||
|
||||
| option | meaning |
|
||||
|---|---|
|
||||
| `price_provider` | `off` (default) or one of the five plugins below |
|
||||
| `price_markup_eur_kwh` | Your supplier's margin, EUR/kWh. **Added only when the chosen plugin's feed does not already contain it** |
|
||||
| `price_taxes_eur_kwh` | Levies and VAT, EUR/kWh. Same rule |
|
||||
| `export_price_eur_kwh` | The **injection** price. A separate scalar, not a series — see below |
|
||||
| `price_csv_path` | Where `manual_csv` reads its file |
|
||||
| `price_feed_url` | Override the plugin's default feed. Only a host the plugin itself declares is accepted |
|
||||
| `price_horizon_h` | How far ahead to ask for, hours |
|
||||
| `price_timeout_s` | Per-request timeout |
|
||||
|
||||
**Which plugin sources what.** This is the one thing to get right, because
|
||||
adding your markup to a price that already contains it makes every future
|
||||
arbitrage decision wrong by that amount, in a direction nothing reports.
|
||||
|
||||
| plugin | product | the published figure contains | the add-on adds |
|
||||
|---|---|---|---|
|
||||
| `generic_dayahead` | day-ahead | energy only (wholesale EPEX BE) | markup **and** taxes |
|
||||
| `engie_dynamic` | day-ahead | energy + Engie's markup | taxes |
|
||||
| `totalenergies_dynamic` | day-ahead | energy + TotalEnergies' markup | taxes |
|
||||
| `eneco_eddy` | **real-time / imbalance** | everything, all-in | nothing |
|
||||
| `manual_csv` | day-ahead | everything, all-in | nothing |
|
||||
|
||||
You cannot get this wrong by editing the markup: each plugin *declares* what its
|
||||
feed contains and the add-on adds only the rest. Set a markup under a plugin
|
||||
that already carries one and it is **ignored, with a line in the log saying so**
|
||||
— never applied twice.
|
||||
|
||||
That holds **given a correct declaration**, and it is worth knowing where the
|
||||
line is. What the add-on enforces mechanically is that a plugin cannot obtain
|
||||
the markup or apply it: the numbers are not reachable from plugin code, and a
|
||||
plugin whose code mentions them fails to load at all. What it cannot check is
|
||||
whether a plugin described its supplier's feed truthfully — that is a claim
|
||||
about somebody else's tariff, and no code can verify it. If you write a plugin,
|
||||
the `sources` declaration is the part a reviewer has to read.
|
||||
|
||||
`eneco_eddy` is a real-time/imbalance product rather than a day-ahead one. It is
|
||||
flagged as high-volatility, which widens arbitrage thresholds once the
|
||||
optimiser lands. That comes from the plugin's own declaration, not from its
|
||||
name.
|
||||
|
||||
**`manual_csv` — the offline fallback.** It needs no internet, no supplier
|
||||
account and no HTTP stack at all, and it is the supported way to run this
|
||||
add-on price-aware on a site with no outbound connectivity. Write a file and
|
||||
point `price_csv_path` at it:
|
||||
|
||||
```csv
|
||||
start,eur_per_kwh,provisional
|
||||
2026-01-15T00:00:00+01:00,0.1834,false
|
||||
2026-01-15T01:00:00+01:00,-0.0210,true
|
||||
2026-01-15T02:00:00+01:00,0.3115,false
|
||||
```
|
||||
|
||||
- `start` **must carry its UTC offset** (`+01:00` in winter, `+02:00` in
|
||||
summer). A bare `2026-01-15 00:00` is refused, because for one hour every
|
||||
October a local wall clock names two different moments.
|
||||
- `eur_per_kwh` is **all-in** — energy *plus* markup *plus* taxes. This plugin
|
||||
has no way to know what your numbers already include, so it assumes
|
||||
everything and adds nothing.
|
||||
- Negative prices are valid and are kept. They are the best charging hours of
|
||||
the year.
|
||||
- `provisional` is optional; omit the column and every hour is treated as
|
||||
settled.
|
||||
|
||||
**When there are no prices at all** — provider off, feed unreachable, CSV
|
||||
missing — the add-on does not guess. It reports *no price data*, which is a
|
||||
distinct state from "prices, all of them zero": the plan is marked provisional
|
||||
and arbitrage is disabled, per the degradation matrix. It never falls back to a
|
||||
price of zero, which would read as free electricity.
|
||||
|
||||
**Injection is priced separately** (`export_price_eur_kwh`). In Belgium it is
|
||||
often near zero, which by itself strongly favours self-consumption over export.
|
||||
It is a single number rather than a curve because no supported supplier
|
||||
publishes a dynamic injection tariff; negative values are accepted.
|
||||
|
||||
**The old Home Assistant price sensors.** `price_now_entity` and
|
||||
`price_avg_entity` still work and are unchanged: the add-on compares them and
|
||||
uses the result as a cheap-window hint for the maintenance charge. They are
|
||||
**not compatible with `price_provider`** — configure both and the add-on
|
||||
refuses to start, naming the conflict, because the Home Assistant pair would
|
||||
silently keep deciding while the status page named your supplier. Use one or
|
||||
the other.
|
||||
|
||||
### Site
|
||||
|
||||
| option | meaning |
|
||||
|
||||
Reference in New Issue
Block a user