Add support for a new Home Assistant electricity-price integration as a BESS
price provider (e.g. Nordpool, Octopus, ENTSO-e/Belpex). This is a recurring
pattern — every new provider touches the same files in the same order. Follow
the steps exactly; never guess entity names, unique_id formats, or
attribute keys (see CLAUDE.md → "Verification Before Action").
Entity discovery in this codebase keys off the immutable unique_id from
the HA entity registry, filtered by the integration's platform field — this
survives entity renaming (ha_api_controller.py → discover_octopus_entities).
You MUST derive the real unique_id pattern from the integration's source
code, not from a sample entity_id and not from inference:
- Find the integration's GitHub repo (
gh api "search/repositories?q=..."). - Read
custom_components/<domain>/sensor.py(andconst.py,coordinator.py). LocateDOMAIN(= the registryplatform), the_attr_unique_idconstruction, and theextra_state_attributesthat expose the price arrays. Quote the exact lines in the PR description / code comments. - Note the price resolution (hourly = 24/day, 30-min = 48, quarterly = 96)
and whether prices are VAT-inclusive retail or VAT-exclusive spot —
these drive
period_duration_hours/ expansion andprices_are_final.
We have beta testers who export their config + entity registry. The flow:
- Ask the beta tester to export config / provide a debug report
(
docs/bess-debug-*.md) containing their real entity registry + the price sensor's attributes. - Build a regression test fixture from that real registry (not a
hand-invented one) so discovery is verified against production data and
cannot regress. Mirror
core/bess/tests/unit/test_registry_discovery.pyandtest_scenario_discovery.py. - Add the price sensor (and its
prices_today/equivalent attribute) to the debug export so future reports capture it (core/bess/debug_data_exporter.py+debug_report_formatter.py).
- New
core/bess/<provider>_source.py— subclassPriceSource(core/bess/price_manager.py). Model on the closest existing source:- Reads HA sensor attributes → model on
HomeAssistantSource(Nordpool HACS). - Reads HA event entities with separate import/export → model on
OctopusEnergySource. Implement:get_prices_for_date,perform_health_check, and as neededget_sell_prices_for_date,prices_are_final,period_duration_hours. - Expand raw resolution to the system-wide 96 quarterly periods (size the
expansion from the actual count +
time_utils.get_period_countfor DST, asOctopusEnergySource._fetch_ratesdoes — don't hardcode 24/48). - Return VAT-exclusive prices if the source is spot (PriceManager applies
markup/VAT); set
prices_are_final = Trueonly for final retail prices. - No silent fallbacks — raise
PriceDataUnavailableErroron missing data (docs/agents/rules.md).
- Reads HA sensor attributes → model on
core/bess/battery_system_manager.py→_create_price_source(): add aprovider == "<provider>"branch readingconfig["<provider>"].backend/settings_store.py→ add"<provider>": {}to theenergy_providerdefault block. Add a_migrate_schemaclause only if renaming/moving keys.backend/api.py→ setup endpoint (run_setup_discoverypersistence, ~line 3001): persist the new entity whenpayload.provider == "<provider>". The live-apply path (update_settings({"energy_provider": ...})) already re-creates the source, so no restart is needed.backend/api_dataclasses.py→ add the<provider>Entityfield(s) to the setup payload dataclass.
core/bess/ha_api_controller.py→discover_integrations(): set<provider>_found+ entity by filtering entity registry onplatform == "<domain>"and matching the verifiedunique_idpattern. Add an attribute-shape fallback (scan/api/statesfor the price-array attribute) for robustness across integration versions. Surface results in the discovery result dict consumed by the wizard.core/bess/debug_data_exporter.py→ include the new provider entity + price attributes in the export so beta reports capture them.
frontend/src/components/settings/PricingFormSection.tsx— add the provider option to theProviderselect + conditional entity input(s).frontend/src/pages/SetupWizardPage.tsx— add to form state, auto-select on discovery, include in the save payload + the summary screen.
core/bess/tests/unit/test_<provider>_source.py— resolution expansion, VAT handling, date filtering, DST counts, missing-data failure.- Discovery regression test — add a
<provider>case totest_registry_discovery.py/test_scenario_discovery.pyusing the real beta-tester registry fixture. backend/tests/test_settings_contracts.py+ setup-wizard scenario test (backend/tests/test_setup_wizard_scenarios.py) for the new provider key.
The discovery regression test above is backend-only. The full frontend flow
(auto-select provider, show provider-specific fields, complete + save) is
covered by the Playwright wizard E2E, parameterised by the SCENARIO env var.
Wire the new provider in:
13a. scripts/mock_ha/scenarios/ci-wizard-<provider>.json — the mock-HA
scenario (already created as the regression fixture above; the same file
drives both the backend discovery test and this E2E).
13b. e2e/tests/wizard-expectations.ts — add a ci-wizard-<provider>
expectation and extend the autoSelectedProvider union.
13c. e2e/tests/setup-wizard.spec.ts — add the provider's radio label to
PROVIDER_LABEL and a branch in "provider-specific fields shown correctly".
13d. e2e/run-e2e.sh WIZARD_SCENARIOS and .github/workflows/ci.yml
(per-scenario step) — run SCENARIO=ci-wizard-<provider>.
Verify locally: bring up docker-compose.ci.yml with the scenario, confirm
POST /api/setup/discover reports the provider, then
cd e2e && BESS_PORT=8080 SCENARIO=ci-wizard-<provider> npx playwright test --project=wizard.
docs/USER_GUIDE.md— add a "Provider: " subsection (prereqs, entity, how it works) alongside the existing provider sections (~line 258).README.md— add the provider to the supported-providers list (~line 56).CHANGELOG.md— add an entry (project rule: never skip).
pytest -m "not slow" # incl. discovery regression tests
black . && ruff check --fix .
cd frontend && npm run lint:fix && npm run build && npx tsc --noEmitOpen the PR as a draft against main, reference the originating issue, and
mark new providers experimental / not real-world tested until a beta tester
confirms (per docs/agents/memory/ maturity conventions).
| Concern | File |
|---|---|
| Source ABC | core/bess/price_manager.py (PriceSource) |
| Example: attribute-based | core/bess/price_manager.py (HomeAssistantSource) |
| Example: event-entity / expansion | core/bess/octopus_energy_source.py |
| Example: service-call | core/bess/official_nordpool_source.py |
| Provider switch | core/bess/battery_system_manager.py (_create_price_source) |
| Settings default + migration | backend/settings_store.py |
| Setup API persistence | backend/api.py |
| Setup payload | backend/api_dataclasses.py |
| Discovery | core/bess/ha_api_controller.py (discover_integrations, discover_octopus_entities) |
| Debug export | core/bess/debug_data_exporter.py, debug_report_formatter.py |
| Frontend settings | frontend/src/components/settings/PricingFormSection.tsx |
| Frontend wizard | frontend/src/pages/SetupWizardPage.tsx |
| Discovery tests | core/bess/tests/unit/test_registry_discovery.py, test_scenario_discovery.py |
| Settings tests | backend/tests/test_settings_contracts.py, test_setup_wizard_scenarios.py |
| Mock-HA scenario | scripts/mock_ha/scenarios/ci-wizard-<provider>.json |
| Frontend E2E | e2e/tests/wizard-expectations.ts, e2e/tests/setup-wizard.spec.ts, e2e/run-e2e.sh, .github/workflows/ci.yml |
| User docs | docs/USER_GUIDE.md, README.md, CHANGELOG.md |