Skip to content

Feed-in tariffs and on-disk response caching - #5

Merged
aurian merged 2 commits into
mainfrom
feat/feed-in-tariffs
Aug 28, 2026
Merged

Feed-in tariffs and on-disk response caching#5
aurian merged 2 commits into
mainfrom
feat/feed-in-tariffs

Conversation

@aurian

@aurian aurian commented Aug 28, 2026

Copy link
Copy Markdown
Member

Two features the Planning API now supports, plus the repo's first test suite.

Feed-in (injection) tariffs

api-store injects with_feed_in for tokens whose team carries the feature, so energy-core asks the opt-in question on its own — the integration sends nothing to trigger it. What it had to learn is how to render the sub-flow and what to do with the result.

  • The opt-in question renders as a radio list rather than a dropdown, and question hints are now shown under the label in the step description — the feed-in questions lean on them to explain what is being asked.
  • A Feed-in Price sensor, created only for contracts that qualified with an injection tariff. State is the rate per kWh; the country-specific extras (grid fees, scheme, band, free-form conditions) become attributes.
  • feed_in_price is added to each entry of the prices attribute on Planned Run, so consumption and injection can be charted from the same series.
  • Field labels for all 13 feed-in fields added to strings.json, en.json and fr.json.

Every feed_in* key is carried from the price rows to the entities by prefix rather than an allow-list. The decorations differ per country (feed_in_extra_text, feed_in_monthly_fee, feed_in_yearly_fee_per_kw, feed_in_scheme, feed_in_band…) and the set grows, so a new country decoration surfaces without a client release.

The Germany case

DeFeedIn is the one country sub-flow that does not echo feed_in back into the inputs — it resolves the opt-in to an eeg_rate_id instead. Since each qualification response replaces the inputs wholesale, the answer was being dropped, and /planning/prices only decorates the series when it sees the opt-in. German users would have silently got no injection rates. The flow now remembers the answer and re-stamps it, without overriding a value the API did return. Covered by test_opt_in_is_restamped_when_the_api_drops_it.

Response caching

/planning/details is kept for 48 hours, /planning/prices until the next_update the API returns with it.

On disk (one Home Assistant Store per entry) rather than in memory, because the calls worth saving are the ones a restart repeats: details are fetched on every entry setup, and the first price poll fires as the entry loads. In-session pacing already followed next_update; this closes the restart gap.

  • Entries are keyed on a fingerprint of the qualification inputs, so reconfiguring to another contract fetches fresh data instead of serving the previous one's.
  • Removing the integration deletes its store — nothing left behind in .storage/.
  • Nothing secret is written: the two payloads, their deadline, and the fingerprint. Never the token.

One guard worth flagging: cached prices are ignored when every period in them has already ended, even if the deadline still holds. A flat-rate offer refreshes monthly, so a multi-day outage can leave an unexpired payload with nothing left to report — serving it would have pinned the entities to unknown until that deadline.

Tests

The repo had none. This adds 15, running against real Home Assistant via pytest-homeassistant-custom-component, and a Tests workflow on push and PR:

  • config flow — question shown with its hint, full opt-in sub-flow, the Germany re-stamp, and opting out leaving the flow untouched;
  • entities — rate as state, extras as attributes, no entity created without an opt-in, prices attribute in both shapes;
  • cache — both retention windows, expiry on each, invalidation on reconfigure, the run-out guard, and cleanup on removal.

Home Assistant does not run on Windows (homeassistant.runner imports fcntl); requirements-test.txt documents that and the lru-dict wheel gap.

Verified

pytest green (15/15) against Home Assistant 2026.2.3, plus a manual run in a real Home Assistant instance where the feed-in question renders as intended.

Not covered: a live call against a production token for every country's sub-flow. The payload shapes here were read from energy-core (FeedInFlow, the *FeedIn / *FeedInPrice classes, PriceResponse) rather than captured off the wire.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UKLvYuWyejP7VAZ2bxJtMt

aurian and others added 2 commits August 28, 2026 15:44
Two features the Planning API now supports, plus the repo's first tests.

Feed-in (injection) tariffs
---------------------------
api-store injects `with_feed_in` for tokens whose team carries the
feature, so energy-core asks the opt-in question on its own — the
integration sends nothing to trigger it. What it had to learn is how to
render the sub-flow and what to do with the result:

- the opt-in renders as a radio list, and question `hint`s are shown
  under the label (the feed-in questions lean on them);
- `feed_in_price` and every other `feed_in*` decoration is carried from
  the price rows to the entities by prefix rather than an allow-list, so
  a new country decoration surfaces without a client release;
- a `Feed-in Price` sensor is created only for contracts that qualified
  with an injection tariff.

DeFeedIn is the one country sub-flow that does not echo `feed_in` back
into the inputs — it resolves the opt-in to an `eeg_rate_id` instead.
Since each qualification response replaces the inputs wholesale, the
flow remembers the answer and re-stamps it; without that, German users
would silently get no injection rates from /planning/prices.

Response caching
----------------
/planning/details is kept for 48 hours, /planning/prices until the
`next_update` the API returns with it. On disk rather than in memory,
because the calls worth saving are the ones a restart repeats: details
are fetched on every entry setup and the first price poll fires as the
entry loads.

Cache entries are keyed on a fingerprint of the qualification inputs, so
reconfiguring fetches fresh data. Cached prices whose periods have all
ended are ignored even when the deadline holds — a monthly-refreshing
offer plus a multi-day outage would otherwise pin the entities to
`unknown` until that deadline.

Tests
-----
15 tests against real Home Assistant via
pytest-homeassistant-custom-component, and a workflow to run them. Note
that Home Assistant does not run on Windows (homeassistant.runner imports
fcntl) — see requirements-test.txt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKLvYuWyejP7VAZ2bxJtMt
setup-python's pip cache looks for requirements.txt or pyproject.toml and
fails the job before installing anything when it finds neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UKLvYuWyejP7VAZ2bxJtMt
@aurian
aurian merged commit 6eb895c into main Aug 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant