{"id":49478617,"url":"https://github.com/dag0d/electricity_price_suite","last_synced_at":"2026-04-30T21:00:55.949Z","repository":{"id":342704042,"uuid":"1174729406","full_name":"Dag0d/electricity_price_suite","owner":"Dag0d","description":"Home Assistant custom integration for multi-source electricity price timelines, device runtime planning, and learned consumption profiles.","archived":false,"fork":false,"pushed_at":"2026-04-30T09:56:27.000Z","size":13015,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T10:24:55.645Z","etag":null,"topics":["consumption-profile","custom-integration","electricity-prices","energy","epex-spot","hacs","hacs-integration","home-assistant","home-assistant-integration","load-shifting","price-optimization","smart-home","tibber"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dag0d.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-06T19:20:14.000Z","updated_at":"2026-04-30T09:56:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Dag0d/electricity_price_suite","commit_stats":null,"previous_names":["dag0d/electricity_price_suite"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/Dag0d/electricity_price_suite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dag0d%2Felectricity_price_suite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dag0d%2Felectricity_price_suite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dag0d%2Felectricity_price_suite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dag0d%2Felectricity_price_suite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dag0d","download_url":"https://codeload.github.com/Dag0d/electricity_price_suite/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dag0d%2Felectricity_price_suite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32476682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["consumption-profile","custom-integration","electricity-prices","energy","epex-spot","hacs","hacs-integration","home-assistant","home-assistant-integration","load-shifting","price-optimization","smart-home","tibber"],"created_at":"2026-04-30T21:00:39.764Z","updated_at":"2026-04-30T21:00:55.941Z","avatar_url":"https://github.com/Dag0d.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Electricity Price Suite\n\n`electricity_price_suite` is a Home Assistant custom integration for:\n\n- Building and maintaining a price timeline (today/tomorrow) from direct market providers\n- Merging source data by strict priority (authoritative source wins)\n- Learning consumption profiles from real device runs\n- Optimizing device start times against stored timeline data\n- Exposing automation-friendly entities and services\n\nThe integration keeps one internal timeline store per entry. Provider selection, fallback order, planning, and logger-based optimization all work against that internal timeline rather than against proxy entities.\n\n## Core Concepts\n\n### 1) Entry Types\n\nEach config entry is one of two types:\n\n- `timeline`\n- `profile_logger`\n\nTimeline entries manage prices and plans.\n\nProfile logger entries learn reusable device programs from one energy meter.\n\n### 2) Timeline Instance\n\nEach timeline entry creates one timeline instance (for example one meter/tariff/provider context).\n\nPer timeline, the integration exposes:\n\n- `sensor.\u003ctimeline_slug\u003e_pricing_meta` (main timeline sensor)\n- `sensor.\u003ctimeline_slug\u003e_status` (high-level status state for automations)\n- `sensor.\u003ctimeline_slug\u003e_current_price`\n- `sensor.\u003ctimeline_slug\u003e_current_market_price`\n- Optional consumption/cost sensors when a total-increasing consumption entity is configured:\n  - `sensor.\u003ctimeline_slug\u003e_consumption_today_kwh`\n  - `sensor.\u003ctimeline_slug\u003e_consumption_current_hour_kwh`\n  - `sensor.\u003ctimeline_slug\u003e_consumption_month_kwh`\n  - `sensor.\u003ctimeline_slug\u003e_consumption_yesterday_kwh`\n  - `sensor.\u003ctimeline_slug\u003e_cost_today`\n  - `sensor.\u003ctimeline_slug\u003e_cost_yesterday`\n  - `sensor.\u003ctimeline_slug\u003e_cost_month`\n  - `sensor.\u003ctimeline_slug\u003e_cost_last_month`\n  - `sensor.\u003ctimeline_slug\u003e_cost_today_incl_basic_fee`\n  - `sensor.\u003ctimeline_slug\u003e_cost_yesterday_incl_basic_fee`\n  - `sensor.\u003ctimeline_slug\u003e_cost_month_incl_basic_fee`\n  - `sensor.\u003ctimeline_slug\u003e_cost_last_month_incl_basic_fee`\n  - `sensor.\u003ctimeline_slug\u003e_avg_paid_price_today`\n  - `sensor.\u003ctimeline_slug\u003e_avg_paid_price_yesterday`\n  - `sensor.\u003ctimeline_slug\u003e_avg_paid_price_month`\n  - `sensor.\u003ctimeline_slug\u003e_avg_paid_price_last_month`\n- Dynamic plan entities: `sensor.\u003ctimeline_slug\u003e_\u003cplanner_slug\u003e_\u003cdevice_slug\u003e`\n\n### 3) Profile Logger Instance\n\nEach profile logger entry exposes:\n\n- `sensor.\u003clogger_slug\u003e_profile_logger_meta`\n- `sensor.\u003clogger_slug\u003e_profile_\u003cprogram_key\u003e`\n\nThe meta sensor represents logger state and active run details.\n\nEach program sensor represents one learned profile and exposes its average total energy and profile metadata.\n\n### 4) Provider Chain with Priority\n\nProviders are ordered by priority:\n\n- Lower numeric value = higher priority\n- Priority `0` is typically the authoritative source\n- Merge policy per slot start time:\n  - Better priority replaces worse priority\n  - Same priority replaces old value (refresh behavior)\n  - Worse priority is ignored\n\n### 5) Explicit Refresh, Deterministic Behavior\n\nTimeline data updates on explicit calls (`refresh_timeline`, `inject_slots`) and optional scheduled checks implemented by the integration runtime. Provider fallback behavior is transparent via response logs and sensor attributes.\n\n### 6) Optimizer Works on Internal Store\n\nThe optimizer never needs price slots in its payload. It reads already stored timeline data and computes the best candidate start.\n\nWhen a logger profile is used, the optimizer reads it directly from the internal logger runtime via `profile_logger_entity + program_key`. No external service hop is required.\n\n## Features\n\n- Direct provider timeline refresh for:\n  - `Tibber`\n  - `SMARD`\n  - `Energy-Charts`\n  - `ENTSO-E`\n- Ordered provider fallback chain per timeline\n- `15 -\u003e 60` slot aggregation where needed\n- Never `60 -\u003e 15` slot expansion\n- Priority-based slot merge with replace/ignore logic\n- Weighted timeline metrics (including mixed slot durations)\n- Current price sensor (always enabled)\n- Separate current market price sensor (raw provider market price before EPS surcharges/tax)\n- Optional consumption/cost tracking from one total-increasing energy entity\n- Status sensor with fixed machine-readable states\n- Device plan entity lifecycle: one persistent plan entity per device per planner within a timeline\n- Consumption profile logger entries with per-program sensors\n- Fine-grained optimizer (profile slot can be smaller than billing slot)\n- Direct internal profile loading from suite logger entries\n- Separate plan management service for reset/delete lifecycle actions\n- Shared suite helpers for datetime parsing/formatting, profile resampling, logger program-key normalization, and input validation\n\n## Notifications\n\nThe integration no longer creates logger error notifications on its own.\n\nInstead, it exposes the relevant machine-readable state for Home Assistant automations:\n\n- logger state via `sensor.\u003clogger_slug\u003e_profile_logger_meta`\n- logger reason via the `reason` attribute\n- plan status and `reason` via `sensor.\u003ctimeline_slug\u003e_\u003cplanner_slug\u003e_\u003cdevice_slug\u003e`\n\nThis keeps notification policy outside the integration:\n\n- choose your own language\n- choose your own channels\n- decide which error cases should notify and which should stay silent\n\nGeneric automation examples are available in:\n\n- `examples/logger_error_notification.yaml`\n- `examples/plan_no_candidate_notification.yaml`\n\n## Examples\n\nThe repository includes small generic examples that you can adapt to your own setup:\n\n- `examples/logger_error_notification.yaml`\n  - watches a logger meta sensor\n  - reads `reason`, `active_program`, and `started_at`\n  - shows how to build your own notification policy\n- `examples/plan_no_candidate_notification.yaml`\n  - watches a plan entity\n  - reacts to `status=no-candidate`\n  - reports `reason`, `timeline_entity`, and `requested_latest_start`\n\nReplace the placeholder entity IDs and notify services in those examples with your own Home Assistant entities.\n\n## Internal Structure\n\nThe integration keeps the external feature set stable, but the runtime internals are split by responsibility:\n\n- `runtime.py`\n  - timeline orchestration, service-facing runtime behavior, scheduling, and entity lifecycle\n- `logger_runtime.py`\n  - profile logger orchestration, sampling, profile persistence, and logger-specific services\n- `timeline_stats.py`\n  - timeline state building, weighted metrics, current-price detection, and high-level status evaluation\n- `plan_manager.py`\n  - plan payload creation, reset handling, profile loading, and plan re-optimization helpers\n- `resolvers.py`\n  - target-to-runtime and target-to-plan resolution helpers\n- `time_utils.py`\n  - shared timezone-aware ISO parsing and formatting helpers\n- `profile_utils.py`\n  - shared profile export normalization and slot resampling helpers\n- `logger_utils.py`\n  - shared `program_key` normalization and display-name helpers\n- `validation.py`\n  - shared validation helpers for logger config input\n\nThis split was introduced to reduce duplication in the original monolithic runtime and make future changes easier to validate.\n\n## Installation\n\n1. Copy this integration into your Home Assistant config:\n   - `custom_components/electricity_price_suite`\n2. Restart Home Assistant.\n3. Add integration in UI:\n   - **Settings -\u003e Devices \u0026 Services -\u003e Add Integration -\u003e Electricity Price Suite**\n\n## Configuration Flow\n\nThe config flow starts with an entry-type selection:\n\n- `Price Timeline`\n- `Consumption Profile Logger`\n\n### Timeline flow\n\n1. Timeline core:\n   - Timeline name\n   - Billing resolution (`15` or `60` minutes)\n   - Cache retention days\n   - Price rounding decimals\n2. Provider chain:\n   - Number of providers (`1` to `4`)\n   - Ordered provider selection\n   - Provider-specific settings per step\n3. Consumption and cost tracking:\n   - Optional total-increasing consumption energy entity\n   - Percentage surcharge on market price\n   - Absolute surcharge per kWh\n   - Energy tax / VAT percent\n   - Optional simplified basic-fee settings\n   - Optional flag whether average paid price should include the configured basic fee\n4. Planner devices:\n   - One or more planner device names for this timeline\n\nProvider chain notes:\n\n- The integration is EUR-native for now.\n- `15 -\u003e 60` aggregation is allowed for providers that only expose 15-minute slots.\n- `60 -\u003e 15` expansion is never performed.\n- Tibber defaults to the first home and only asks for `home_index` when multiple homes are enabled.\n\n### Profile logger flow\n\n1. Logger name\n2. Total-increasing energy entity\n3. Slot minutes\n4. Maximum allowed power delta\n5. Auto-create programs on unknown runs\n6. Optional allowed/block lists for program keys\n\n## Entities\n\n### `sensor.\u003ctimeline_slug\u003e_pricing_meta`\n\nMain timeline sensor with:\n\n- State: average price today (rounded) or `unknown`\n- Attributes: timeline metrics, day rows, source/fetch metadata, merge-relevant info, and the configured energy price formula values\n\n### `sensor.\u003ctimeline_slug\u003e_status`\n\nAutomation-friendly status state:\n\n- `no_data`\n- `today_only`\n- `tomorrow_only`\n- `tomorrow_not_from_provider_1`\n- `today_and_tomorrow`\n\nIncludes attributes like `today_rows`, `tomorrow_rows`, and `last_source_chain_fetch_at`.\n\n### `sensor.\u003ctimeline_slug\u003e_current_price`\n\n- State: current slot price (rounded)\n- Minimal attributes for current price context\n\n### `sensor.\u003ctimeline_slug\u003e_current_market_price`\n\n- State: current raw market price before EPS energy surcharges and tax\n- Minimal attributes for current market price context\n\n### Optional consumption/cost sensors\n\nIf a timeline is configured with a total-increasing consumption energy entity, the integration exposes dedicated consumption/cost sensors.\n\n- Consumption sensors expose `kWh`\n- Cost sensors expose `EUR`\n- Average paid price sensors expose `EUR/kWh`\n- The consumption path is re-sampled every 30 seconds, so `current hour` and running averages are near-live without keeping 30-second raw rows\n- Recorder will store their history like normal Home Assistant sensors\n- `last_month` values are preserved via monthly rollups and do not require retention beyond 31 days\n\nBasic fee modes:\n\n- `none`\n- `monthly`\n  - Internally prorated to a daily share (`monthly_fee / days_in_month`)\n- `daily`\n  - Treated as a fixed fee per elapsed day\n\nFor month-level `incl_basic_fee` sensors, the fee is shown as the current accumulated month-to-date share.\n\nAverage paid price sensors can optionally include the configured basic fee through the timeline option `avg_price_include_basic_fee`.\n\n### `sensor.\u003ctimeline_slug\u003e_\u003cplanner_slug\u003e_\u003cdevice_slug\u003e`\n\nPer-device planning entity:\n\n- State: planned start timestamp (or `unknown`)\n- Attributes: optimization window, duration, profile details, cost result, run metadata\n- Variant-related attributes include:\n  - `program_key_used`\n  - `program_display_name_used`\n\n### `sensor.\u003clogger_slug\u003e_profile_logger_meta`\n\nLogger meta sensor with:\n\n- State: `idle | running | error`\n- Attributes: active run details, known profiles, last error, sampling metadata\n\n### `sensor.\u003clogger_slug\u003e_profile_\u003cprogram_key\u003e`\n\nPer-program learned profile sensor with:\n\n- State: average total energy in kWh\n- Attributes: `program_key`, `program_name`, `run_count`, `slot_minutes`, `slot_count`, `runtime_minutes`, `last_updated`\n\n## Services\n\nAll services are in domain `electricity_price_suite`.\n\n- `refresh_timeline`, `inject_slots`, `optimize_device` use a timeline target.\n- `manage_plan` uses one or more plan entity targets.\n- `manage_profile_run`, `manage_profile` use a profile logger target.\n\n---\n\n### `refresh_timeline`\n\nRefreshes timeline slots from configured sources and merges them by priority.\n\n#### Inputs\n\n- `target` (required): timeline entity target (`sensor.\u003ctimeline_slug\u003e_pricing_meta`).\n  - Expected: exactly one sensor entity in `target.entity_id`.\n  - Effect: selects which timeline instance is refreshed.\n- `sources` (optional): temporary source override for this call.\n  - Expected: list of source objects with the same shape as stored pull sources.\n  - Effect: only this refresh call uses these sources; stored source chain is unchanged.\n- `overwrite` (optional, default `false`): explicit fresh re-fetch mode.\n  - Expected: boolean.\n  - Effect: deletes currently stored rows for today and tomorrow before fetching again from the source chain.\n\n#### Response (typical)\n\n- `status`: `ok | no_data`\n- `timeline_entity`: resolved timeline entity id.\n- `timeline_status`: high-level timeline status (`no_data`, `today_only`, ...).\n- `used_source`: first source that produced usable data in this run.\n- `used_sources`: all sources that contributed rows in this run.\n- `attempt_log`: list of attempts (`source_id`, `source_type`, `success`, `rows`, `reason`).\n- `rows_today`: number of stored rows for today after merge.\n- `rows_tomorrow`: number of stored rows for tomorrow after merge.\n- `has_primary_data_for_tomorrow`: whether tomorrow is currently covered by priority-0 rows.\n- `pending_primary`: whether fallback rows still exist where primary is expected.\n- `merge_debug`: counters (`inserted`, `replaced`, `ignored`) for this run.\n- `cleared_rows`: number of today/tomorrow rows removed before fetch when `overwrite=true`.\n- `last_source_chain_fetch_at`: timestamp of latest source-chain fetch.\n\n---\n\n### `inject_slots`\n\nDirectly injects slots into timeline storage.\n\n#### Inputs\n\n- `target` (required).\n  - Expected: exactly one timeline target entity.\n  - Effect: chooses which timeline store gets injected data.\n- `slots` (required): list of slot objects.\n  - Expected per item: `start_time` (ISO datetime with timezone), `price_per_kwh` (number).\n  - Effect: slots are normalized and merged by priority rules.\n- `source_name` (optional, default `manual_inject`).\n  - Expected: string identifier.\n  - Effect: stored as slot source id for traceability.\n- `source_priority` (optional, default `9999`).\n  - Expected: integer, lower = stronger source.\n  - Effect: controls whether injected rows replace existing rows.\n- `is_primary` (optional, default `false`).\n  - Expected: boolean.\n  - Effect: marks injected rows as primary-source rows.\n- `overwrite` (optional, default `false`).\n  - Expected: boolean.\n  - Effect: deletes stored rows for the same local dates before injecting the new rows.\n\n#### Response (typical)\n\n- `status`: `ok | no_data`\n- `timeline_entity`: resolved timeline entity id.\n- `rows_received`: number of normalized rows accepted from payload.\n- `merge_debug`: counters (`inserted`, `replaced`, `ignored`).\n- `pending_primary`: whether fallback rows remain in active window.\n- `cleared_rows`: number of stored rows removed before injection when `overwrite=true`.\n\n---\n\n### `optimize_device`\n\nComputes best start for one device using timeline data.\n\n#### Inputs\n\n- `target` (required).\n  - Expected: exactly one timeline target entity.\n  - Effect: optimization uses that timeline's stored slots.\n- `planner_name` (required).\n  - Expected: name of a configured planner device for that timeline, for example `Geräteplanung`.\n  - Effect: selects which planner device should own the resulting plan entity.\n- `device_name` (required).\n  - Expected: string.\n  - Effect: identifies the per-planner plan entity.\n- `duration_minutes` (optional unless profile source provides duration).\n  - Expected: positive number.\n  - Effect: runtime length used for cost window.\n- `energy_profile` (optional).\n  - Expected: numeric list of weights/energy segments.\n  - Effect: weighted optimization profile; if shorter/longer than required it is normalized internally.\n- `profile_slot_minutes` (optional).\n  - Expected: positive integer.\n  - Effect: slot resolution of `energy_profile`; also candidate grid base when not aligned to billing.\n- `billing_slot_minutes` (optional).\n  - Expected: positive integer.\n  - Effect: override billing price raster; by default detected from timeline slots.\n- `profile_logger_entity` (optional).\n  - Expected: entity id of a suite profile logger meta sensor.\n  - Effect: loads a profile directly from the internal logger runtime.\n- `program_key` (required when `profile_logger_entity` is used).\n  - Expected: stable program key, for example `auto_2`.\n  - Effect: chooses which learned logger profile is used for optimization.\n- `program_display_name` (optional).\n  - Expected: user-facing compact display label, for example `Auto 2 [I,D,S]`.\n  - Effect: persists a readable variant label on the plan without changing the technical `program_key`.\n- `align_start_to_billing_slot` (optional, default `false`).\n  - Expected: boolean.\n  - Effect: candidate starts are forced to billing boundaries.\n- `max_extra_cost_percent` (optional, default `1`).\n  - Expected: float \u003e= 0.\n  - Effect: maximum additional cost in percent that is still acceptable when `prefer_earliest=true`.\n- `prefer_earliest` (optional, default `true`).\n  - Expected: boolean.\n  - Effect: pick the earliest candidate within the allowed extra-cost threshold instead of the strict absolute minimum.\n- `start_mode` (optional, default `now`).\n  - Expected: `now | in`.\n  - Effect: defines start anchor (`now` or `now + start_in_minutes`).\n- `start_in_minutes` (optional, default `0`).\n  - Expected: number \u003e= 0.\n  - Effect: used only for `start_mode=in`.\n- `deadline_mode` (optional, default `none`).\n  - Expected: `none | start_within | finish_within`.\n  - Effect: applies relative deadline constraint.\n- `deadline_minutes` (optional).\n  - Expected: number \u003e= 0.\n  - Effect: relative limit for selected `deadline_mode`.\n- `latest_start` (optional).\n  - Expected: ISO datetime string.\n  - Effect: expert override for absolute latest allowed start. Internally normalized to the optimizer's `latest_start` boundary.\n- `latest_finish` (optional).\n  - Expected: ISO datetime string.\n  - Effect: expert override for absolute latest allowed finish. Internally converted to a derived `latest_start`.\n#### Response (typical)\n\n- `status`: `ok | no-candidate`\n- `plan_entity_id`: per-device per-planner plan entity id.\n- `best_start`: planned start datetime (ISO) or `null`.\n- `best_end`: planned finish datetime (ISO) or `null`.\n- `best_cost`: computed optimization cost or `null`.\n- `reason`: explanatory reason for `no-candidate`.\n- `requested_latest_start`: the originally requested latest-start boundary before any truncation by missing price data.\n\nWhen `profile_logger_entity + program_key` is used, optimization tries sources in this order:\n\n1. learned profile from the selected logger\n2. estimated runtime configured on that logger for the same `program_key`\n\nIf neither exists, the optimizer returns `no-candidate` with a specific reason.\n\n#### Common `reason` values\n\n- `no_valid_slots_after_parse`: no usable price slots were available after parsing.\n- `no_duration_or_profile`: neither duration nor usable energy profile was provided.\n- `invalid_energy_profile`: the supplied profile could not be parsed as numbers.\n- `invalid_duration_minutes`: duration was missing, zero, negative, or not finite.\n- `invalid_deadline_minutes`: deadline offset was negative or not finite.\n- `invalid_latest_start`: `latest_start` was provided but not parseable as ISO datetime.\n- `invalid_latest_finish`: `latest_finish` was provided but not parseable as ISO datetime.\n- `invalid_max_extra_cost_percent`: extra-cost threshold was negative or not finite.\n- `window_too_short_for_duration`: the allowed search window is shorter than the runtime.\n- `all_candidates_in_past`: all candidate starts fell at or before the current time.\n- `incomplete_price_coverage_for_candidates`: price data did not fully cover any candidate run.\n- `candidates_blocked_by_time_and_price_coverage`: some candidates were already in the past and the remaining ones had incomplete price coverage.\n- `no_candidate_after_constraints`: constraints left no valid candidate, but no more specific optimizer reason applied.\n\n---\n\n### `manage_plan`\n\nResets, deletes, or re-optimizes existing plan entities.\n\n#### Inputs\n\n- `target` (required).\n  - Expected: one or more existing plan entities (`sensor.\u003ctimeline_slug\u003e_\u003cplanner_slug\u003e_\u003cdevice_slug\u003e`).\n  - Effect: selected plan entities are managed.\n- `mode` (required).\n  - Expected: `reset | delete | reoptimize`.\n  - Effect: chooses which plan-management action is executed.\n\n#### Response (typical)\n\n- `results`: list of per-target results:\n  - `status`: `reset | deleted | ok | no-candidate | not_found | not_reoptimized`\n  - `plan_entity_id`\n  - `reason`\n\n---\n\n### `manage_profile_run`\n\nStarts, finishes, or aborts a logger run for the selected profile logger.\n\n#### Inputs\n\n- `target` (required).\n  - Expected: one profile logger meta sensor or one program profile sensor.\n- `mode` (required).\n  - Expected: `start | finish | abort`.\n  - Effect: chooses the run-lifecycle action.\n- `program_key` (optional when target is already a profile sensor).\n  - Expected: program key string.\n  - Effect: program to start, finish, or guard during abort.\n- `program_display_name` (optional).\n  - Only used for mode=`start`.\n  - Expected: human-readable display name such as `Auto 2 [I,D]`.\n  - Effect: stored as the profile name when a new profile is created or an existing profile name should be refreshed.\n- `reason` (optional).\n  - Only used for mode=`abort`.\n  - Expected: one of `manual_abort`, `program_mismatch`, `restart_recovery`, `sampling_delay_exceeded`.\n\n### `manage_profile`\n\nReturns, resets, deletes, or manages fallback estimated runtimes for a profile logger.\n\n#### Inputs\n\n- `target` (required).\n- `mode` (required).\n  - Expected: `get | reset | delete | add_estimated_runtimes | list_estimated_runtimes | delete_estimated_runtime | clear_estimated_runtimes`.\n  - Effect: chooses which profile-management action is executed.\n- `program_key` (optional).\n  - Only used for mode=`get`, `reset`, `delete`, `list_estimated_runtimes`, `delete_estimated_runtime`.\n  - If omitted for mode=`get` on the meta sensor, the response returns the known program list.\n- `desired_slot_minutes` (optional).\n  - Only used for mode=`get`.\n  - Resamples the profile when the requested slot length is an integer multiple or divisor of the stored slot length.\n- `debug` (optional).\n  - Only used for mode=`get`.\n- `items` (optional).\n  - Only used for mode=`add_estimated_runtimes`.\n  - Expected: mapping of `program_key -\u003e duration_minutes`.\n\n#### Typical use\n\n```yaml\naction: electricity_price_suite.manage_profile\ntarget:\n  entity_id: sensor.dishwasher_profile_logger_meta\ndata:\n  mode: add_estimated_runtimes\n  items:\n    auto_2: 180\n    auto_2_i_d_s: 140\n```\n\n#### Response (typical)\n\n- mode=`get`\n  - profile list or one profile payload\n- mode=`reset`\n  - `status`: `ok | not_found`\n  - `program_key`\n- mode=`delete`\n  - `status`: `ok | not_found`\n  - `program_key`\n- mode=`add_estimated_runtimes`\n  - `status`: `ok`\n  - `count`\n  - `estimated_runtimes`\n- mode=`list_estimated_runtimes`\n  - without `program_key`:\n    - `ok`: `true`\n    - `count`\n    - `estimated_runtimes`\n  - with `program_key`:\n    - `ok`: `true | false`\n    - `program_key`\n    - `estimated_runtime_minutes`\n    - `reason`: `estimated_runtime_not_found` when missing\n- mode=`delete_estimated_runtime`\n  - `status`: `ok | not_found`\n  - `program_key`\n- mode=`clear_estimated_runtimes`\n  - `status`: `ok`\n  - `count`\n\n---\n\n## Optimizer Model Notes\n\n- Billing slot and profile slot can differ\n- Candidate start grid:\n  - profile slot grid by default\n  - billing slot grid if `align_start_to_billing_slot=true`\n- Costs are overlap-weighted across price segments\n- Deadlines can be constrained by currently available price coverage\n- If a previous plan was data-truncated and new price coverage arrives before planned start, the integration can re-optimize and update the plan\n\n## Cache and Persistence\n\n- Timeline slots are stored in integration-managed storage per timeline entry\n- Provider metadata and plan payloads are persisted\n- Cache retention controls historical cleanup behavior\n\n## Branding\n\nThis integration includes local brand assets:\n\n- `custom_components/electricity_price_suite/brand/icon.png`\n- `custom_components/electricity_price_suite/brand/logo.png`\n\n## Testing\n\nRepository includes unit tests in `tests/` for key logic:\n\n- slot normalization\n- priority merge behavior\n- optimizer candidate behavior and edge cases\n\nThese tests are recommended to keep, because they protect core algorithm behavior during refactors.\n\n## Development Notes\n\n- Requires Home Assistant with support for this integration version (`manifest.json`)\n- Use Home Assistant service developer tools to test provider and optimizer flows\n- For production usage, configure at least one reliable priority-0 provider\n\n## Acknowledgements\n\nThanks to the Home Assistant ecosystem and maintainers of related integrations that make flexible price workflows possible, especially:\n\n- [EPEX Spot for Home Assistant](https://github.com/mampfes/ha_epex_spot)\n- The official Home Assistant Tibber integration\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdag0d%2Felectricity_price_suite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdag0d%2Felectricity_price_suite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdag0d%2Felectricity_price_suite/lists"}