refactor(sensors): rename current price sensors for clarity

Renamed internal sensor keys to be more explicit about their temporal scope:
- current_price → current_interval_price
- price_level → current_interval_price_level
- price_rating → current_interval_price_rating

This naming makes it clearer that these sensors represent the current
15-minute interval, distinguishing them from hourly averages and other
time-based calculations.

Updated across all components:
- Sensor entity descriptions and handlers (sensor.py)
- Time-sensitive entity keys list (coordinator.py)
- Config flow step IDs (config_flow.py)
- Translation keys in all 5 languages (de, en, nb, nl, sv)
- Custom translations (entity descriptions, usage tips)
- Price level/rating lookups (const.py, sensor.py)
- Documentation examples (AGENTS.md, README.md)

Impact: Sensor entity IDs remain unchanged due to translation_key system.
Existing automations continue to work. Only internal code references and
translation structures updated for consistency.
This commit is contained in:
Julian Pawlowski 2025-11-15 08:30:25 +00:00
parent c4f36d04de
commit 7737dccd49
17 changed files with 199 additions and 178 deletions

View file

@ -1150,14 +1150,16 @@ We use **Ruff** (which replaces Black, Flake8, isort, and more) as our sole lint
**Critical principle:** Logs must enable logic tracing without reading code. Each log message should make the current state and decision-making process crystal clear. **Critical principle:** Logs must enable logic tracing without reading code. Each log message should make the current state and decision-making process crystal clear.
**Why good logging matters beyond debugging:** **Why good logging matters beyond debugging:**
- Clear logs become the foundation for good documentation (see "Documentation Writing Strategy")
- If you spend hours making logs explain the logic, that clarity transfers directly to user docs - Clear logs become the foundation for good documentation (see "Documentation Writing Strategy")
- Logs show state transitions and decisions that users need to understand - If you spend hours making logs explain the logic, that clarity transfers directly to user docs
- Pattern: Good hierarchical logs → Easy to extract examples and explanations for documentation - Logs show state transitions and decisions that users need to understand
- Pattern: Good hierarchical logs → Easy to extract examples and explanations for documentation
**Log Level Strategy:** **Log Level Strategy:**
- **INFO Level** - User-facing results and high-level progress: - **INFO Level** - User-facing results and high-level progress:
- Compact 1-line summaries (no multi-line blocks) - Compact 1-line summaries (no multi-line blocks)
- Important results only (success/failure outcomes) - Important results only (success/failure outcomes)
- No indentation (scannability) - No indentation (scannability)
@ -1165,6 +1167,7 @@ We use **Ruff** (which replaces Black, Flake8, isort, and more) as our sole lint
- Example: `"Day 2025-11-11: Success after 1 relaxation phase (2 periods)"` - Example: `"Day 2025-11-11: Success after 1 relaxation phase (2 periods)"`
- **DEBUG Level** - Detailed execution trace: - **DEBUG Level** - Detailed execution trace:
- Full context headers with all relevant configuration - Full context headers with all relevant configuration
- Step-by-step progression through logic - Step-by-step progression through logic
- Hierarchical indentation to show call depth/logic structure - Hierarchical indentation to show call depth/logic structure
@ -1201,10 +1204,11 @@ _LOGGER.debug("%sExtended baseline period from %s to %s", INDENT_L4, old_end, ne
``` ```
**Why indentation?** **Why indentation?**
- Makes call stack and decision tree visible at a glance
- Enables quick problem localization (which phase/step failed?) - Makes call stack and decision tree visible at a glance
- Shows parent-child relationships between operations - Enables quick problem localization (which phase/step failed?)
- Distinguishes between sequential steps vs nested logic - Shows parent-child relationships between operations
- Distinguishes between sequential steps vs nested logic
**Configuration Context:** **Configuration Context:**
@ -1328,6 +1332,7 @@ When writing or updating user-facing documentation (`docs/user/`), follow these
Understanding **how** good documentation emerges is as important as knowing what makes it good: Understanding **how** good documentation emerges is as important as knowing what makes it good:
- **Live Understanding vs. Code Analysis** - **Live Understanding vs. Code Analysis**
- ✅ **DO:** Write docs during/after active development - ✅ **DO:** Write docs during/after active development
- When implementing complex logic, document it while the "why" is fresh - When implementing complex logic, document it while the "why" is fresh
- Use real examples from debugging sessions (actual logs, real data) - Use real examples from debugging sessions (actual logs, real data)
@ -1338,6 +1343,7 @@ Understanding **how** good documentation emerges is as important as knowing what
- No user perspective: What's actually confusing? - No user perspective: What's actually confusing?
- **User Feedback Loop** - **User Feedback Loop**
- Key insight: Documentation improves when users question it - Key insight: Documentation improves when users question it
- Pattern: - Pattern:
1. User asks: "Does this still match the code?" 1. User asks: "Does this still match the code?"
@ -1347,16 +1353,19 @@ Understanding **how** good documentation emerges is as important as knowing what
- Why it works: User questions force critical thinking, real confusion points get addressed - Why it works: User questions force critical thinking, real confusion points get addressed
- **Log-Driven Documentation** - **Log-Driven Documentation**
- Observation: When logs explain logic clearly, documentation becomes easier - Observation: When logs explain logic clearly, documentation becomes easier
- Why: Logs show state transitions ("Baseline insufficient → Starting relaxation"), decisions ("Replaced period X with larger Y"), and are already written for humans - Why: Logs show state transitions ("Baseline insufficient → Starting relaxation"), decisions ("Replaced period X with larger Y"), and are already written for humans
- Pattern: If you spent hours making logs clear → use that clarity in documentation too - Pattern: If you spent hours making logs clear → use that clarity in documentation too
- **Concrete Examples > Abstract Descriptions** - **Concrete Examples > Abstract Descriptions**
- ✅ **Good:** "Day 2025-11-11 found 2 periods at flex=12.0% +volatility_any (stopped early, no need to try higher flex)" - ✅ **Good:** "Day 2025-11-11 found 2 periods at flex=12.0% +volatility_any (stopped early, no need to try higher flex)"
- ❌ **Bad:** "The relaxation algorithm uses a configurable threshold multiplier with filter combination strategies" - ❌ **Bad:** "The relaxation algorithm uses a configurable threshold multiplier with filter combination strategies"
- Use real data from debug sessions, show actual attribute values, demonstrate with timeline diagrams - Use real data from debug sessions, show actual attribute values, demonstrate with timeline diagrams
- **Context Accumulation in Long Sessions** - **Context Accumulation in Long Sessions**
- Advantage: AI builds mental model incrementally, sees evolution of logic (not just final state), understands trade-offs - Advantage: AI builds mental model incrementally, sees evolution of logic (not just final state), understands trade-offs
- Disadvantage of short sessions: Cold start every time, missing "why" context, documentation becomes spec-writing - Disadvantage of short sessions: Cold start every time, missing "why" context, documentation becomes spec-writing
- Lesson: Complex documentation benefits from focused, uninterrupted work with accumulated context - Lesson: Complex documentation benefits from focused, uninterrupted work with accumulated context
@ -1406,7 +1415,7 @@ name = 'tibber_prices' # Ruff will change to double quotes
```python ```python
# ✅ Always use trailing commas in multi-line structures # ✅ Always use trailing commas in multi-line structures
SENSOR_TYPES = [ SENSOR_TYPES = [
"current_price", "current_interval_price",
"min_price", "min_price",
"max_price", # ← Trailing comma "max_price", # ← Trailing comma
] ]
@ -1421,7 +1430,7 @@ def calculate_average(
# ❌ Missing trailing comma # ❌ Missing trailing comma
SENSOR_TYPES = [ SENSOR_TYPES = [
"current_price", "current_interval_price",
"min_price", "min_price",
"max_price" # Ruff will add trailing comma "max_price" # Ruff will add trailing comma
] ]
@ -1483,7 +1492,7 @@ df = (
```python ```python
# ✅ Annotate function signatures (public functions) # ✅ Annotate function signatures (public functions)
def get_current_price(coordinator: DataUpdateCoordinator) -> float: def get_current_interval_price(coordinator: DataUpdateCoordinator) -> float:
"""Get current price from coordinator.""" """Get current price from coordinator."""
return coordinator.data["priceInfo"]["today"][0]["total"] return coordinator.data["priceInfo"]["today"][0]["total"]

142
README.md
View file

@ -14,10 +14,10 @@ A Home Assistant integration that provides advanced price information and rating
## 📖 Documentation ## 📖 Documentation
- **[User Guide](docs/user/)** - Installation, configuration, and usage guides - **[User Guide](docs/user/)** - Installation, configuration, and usage guides
- **[Period Calculation](docs/user/period-calculation.md)** - How Best/Peak Price periods are calculated - **[Period Calculation](docs/user/period-calculation.md)** - How Best/Peak Price periods are calculated
- **[Developer Guide](docs/development/)** - Contributing, architecture, and release process - **[Developer Guide](docs/development/)** - Contributing, architecture, and release process
- **[Changelog](https://github.com/jpawlowski/hass.tibber_prices/releases)** - Release history and notes - **[Changelog](https://github.com/jpawlowski/hass.tibber_prices/releases)** - Release history and notes
## ✨ Features ## ✨ Features
@ -44,6 +44,7 @@ Click the button below to open the integration directly in HACS:
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=jpawlowski&repository=hass.tibber_prices&category=integration) [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=jpawlowski&repository=hass.tibber_prices&category=integration)
Then: Then:
1. Click "Download" to install the integration 1. Click "Download" to install the integration
2. **Restart Home Assistant** (required after installation) 2. **Restart Home Assistant** (required after installation)
@ -60,6 +61,7 @@ Click the button below to open the configuration dialog:
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=tibber_prices) [![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=tibber_prices)
This will guide you through: This will guide you through:
1. Enter your Tibber API token ([get one here](https://developer.tibber.com/settings/access-token)) 1. Enter your Tibber API token ([get one here](https://developer.tibber.com/settings/access-token))
2. Select your Tibber home 2. Select your Tibber home
3. Configure price thresholds (optional) 3. Configure price thresholds (optional)
@ -73,9 +75,9 @@ This will guide you through:
### Step 3: Start Using! ### Step 3: Start Using!
- 30+ sensors are now available (key sensors enabled by default) - 30+ sensors are now available (key sensors enabled by default)
- Configure additional sensors in **Settings****Devices & Services****Tibber Price Information & Ratings** → **Entities** - Configure additional sensors in **Settings****Devices & Services****Tibber Price Information & Ratings** → **Entities**
- Use sensors in automations, dashboards, and scripts - Use sensors in automations, dashboards, and scripts
📖 **[Full Installation Guide →](docs/user/installation.md)** 📖 **[Full Installation Guide →](docs/user/installation.md)**
@ -87,66 +89,66 @@ The integration provides **30+ sensors** across different categories. Key sensor
### Core Price Sensors (Enabled by Default) ### Core Price Sensors (Enabled by Default)
| Entity | Description | | Entity | Description |
| ----------------------------- | ------------------------------------------------- | | -------------------------- | ------------------------------------------------- |
| Current Electricity Price | Current 15-minute interval price | | Current Electricity Price | Current 15-minute interval price |
| Next Interval Price | Price for the next 15-minute interval | | Next Interval Price | Price for the next 15-minute interval |
| Current Hour Average Price | Average of current hour's 4 intervals | | Current Hour Average Price | Average of current hour's 4 intervals |
| Next Hour Average Price | Average of next hour's 4 intervals | | Next Hour Average Price | Average of next hour's 4 intervals |
| Current Price Level | API classification (VERY_CHEAP to VERY_EXPENSIVE) | | Current Price Level | API classification (VERY_CHEAP to VERY_EXPENSIVE) |
| Next Interval Price Level | Price level for next interval | | Next Interval Price Level | Price level for next interval |
| Current Hour Price Level | Price level for current hour average | | Current Hour Price Level | Price level for current hour average |
| Next Hour Price Level | Price level for next hour average | | Next Hour Price Level | Price level for next hour average |
### Statistical Sensors (Enabled by Default) ### Statistical Sensors (Enabled by Default)
| Entity | Description | | Entity | Description |
| ------------------------------ | -------------------------------------------- | | ------------------------- | ------------------------------------------- |
| Today's Lowest Price | Minimum price for today | | Today's Lowest Price | Minimum price for today |
| Today's Highest Price | Maximum price for today | | Today's Highest Price | Maximum price for today |
| Today's Average Price | Mean price across today's intervals | | Today's Average Price | Mean price across today's intervals |
| Tomorrow's Lowest Price | Minimum price for tomorrow (when available) | | Tomorrow's Lowest Price | Minimum price for tomorrow (when available) |
| Tomorrow's Highest Price | Maximum price for tomorrow (when available) | | Tomorrow's Highest Price | Maximum price for tomorrow (when available) |
| Tomorrow's Average Price | Mean price for tomorrow (when available) | | Tomorrow's Average Price | Mean price for tomorrow (when available) |
| Leading 24h Average Price | Average of next 24 hours from now | | Leading 24h Average Price | Average of next 24 hours from now |
| Leading 24h Minimum Price | Lowest price in next 24 hours | | Leading 24h Minimum Price | Lowest price in next 24 hours |
| Leading 24h Maximum Price | Highest price in next 24 hours | | Leading 24h Maximum Price | Highest price in next 24 hours |
### Price Rating Sensors (Enabled by Default) ### Price Rating Sensors (Enabled by Default)
| Entity | Description | | Entity | Description |
| --------------------------- | ---------------------------------------------------------- | | -------------------------- | --------------------------------------------------------- |
| Current Price Rating | % difference from 24h trailing average (current interval) | | Current Price Rating | % difference from 24h trailing average (current interval) |
| Next Interval Price Rating | % difference from 24h trailing average (next interval) | | Next Interval Price Rating | % difference from 24h trailing average (next interval) |
| Current Hour Price Rating | % difference for current hour average | | Current Hour Price Rating | % difference for current hour average |
| Next Hour Price Rating | % difference for next hour average | | Next Hour Price Rating | % difference for next hour average |
> **How ratings work**: Compares each interval to the average of the previous 96 intervals (24 hours). Positive values mean prices are above average, negative means below average. > **How ratings work**: Compares each interval to the average of the previous 96 intervals (24 hours). Positive values mean prices are above average, negative means below average.
### Binary Sensors (Enabled by Default) ### Binary Sensors (Enabled by Default)
| Entity | Description | | Entity | Description |
| -------------------------- | -------------------------------------------------------------- | | ------------------------- | ----------------------------------------------------------------------------------------- |
| Peak Price Period | ON when in a detected peak price period ([how it works](docs/user/period-calculation.md)) | | Peak Price Period | ON when in a detected peak price period ([how it works](docs/user/period-calculation.md)) |
| Best Price Period | ON when in a detected best price period ([how it works](docs/user/period-calculation.md)) | | Best Price Period | ON when in a detected best price period ([how it works](docs/user/period-calculation.md)) |
| Tibber API Connection | Connection status to Tibber API | | Tibber API Connection | Connection status to Tibber API |
| Tomorrow's Data Available | Whether tomorrow's price data is available | | Tomorrow's Data Available | Whether tomorrow's price data is available |
### Diagnostic Sensors (Enabled by Default) ### Diagnostic Sensors (Enabled by Default)
| Entity | Description | | Entity | Description |
| ----------------- | ------------------------------------------ | | --------------- | ------------------------------------------ |
| Data Expiration | Timestamp when current data expires | | Data Expiration | Timestamp when current data expires |
| Price Forecast | Formatted list of upcoming price intervals | | Price Forecast | Formatted list of upcoming price intervals |
### Additional Sensors (Disabled by Default) ### Additional Sensors (Disabled by Default)
The following sensors are available but disabled by default. Enable them in `Settings > Devices & Services > Tibber Price Information & Ratings > Entities`: The following sensors are available but disabled by default. Enable them in `Settings > Devices & Services > Tibber Price Information & Ratings > Entities`:
- **Previous Interval Price** & **Previous Interval Price Level**: Historical data for the last 15-minute interval - **Previous Interval Price** & **Previous Interval Price Level**: Historical data for the last 15-minute interval
- **Previous Interval Price Rating**: Rating for the previous interval - **Previous Interval Price Rating**: Rating for the previous interval
- **Trailing 24h Average Price**: Average of the past 24 hours from now - **Trailing 24h Average Price**: Average of the past 24 hours from now
- **Trailing 24h Minimum/Maximum Price**: Min/max in the past 24 hours - **Trailing 24h Minimum/Maximum Price**: Min/max in the past 24 hours
> **Note**: All monetary sensors use minor currency units (ct/kWh, øre/kWh, ¢/kWh, p/kWh) automatically based on your Tibber account's currency. Supported: EUR, NOK, SEK, DKK, USD, GBP. > **Note**: All monetary sensors use minor currency units (ct/kWh, øre/kWh, ¢/kWh, p/kWh) automatically based on your Tibber account's currency. Supported: EUR, NOK, SEK, DKK, USD, GBP.
@ -186,7 +188,7 @@ automation:
- alias: "Notify on Very Expensive Electricity" - alias: "Notify on Very Expensive Electricity"
trigger: trigger:
- platform: state - platform: state
entity_id: sensor.tibber_current_price_level entity_id: sensor.tibber_current_interval_price_level
to: "VERY_EXPENSIVE" to: "VERY_EXPENSIVE"
action: action:
- service: notify.mobile_app - service: notify.mobile_app
@ -204,14 +206,14 @@ automation:
- alias: "Reduce Heating During High Price Ratings" - alias: "Reduce Heating During High Price Ratings"
trigger: trigger:
- platform: numeric_state - platform: numeric_state
entity_id: sensor.tibber_current_price_rating entity_id: sensor.tibber_current_interval_price_rating
above: 20 # More than 20% above 24h average above: 20 # More than 20% above 24h average
action: action:
- service: climate.set_temperature - service: climate.set_temperature
target: target:
entity_id: climate.living_room entity_id: climate.living_room
data: data:
temperature: 19 # Lower target temperature temperature: 19 # Lower target temperature
``` ```
### Smart EV Charging Based on Tomorrow's Prices ### Smart EV Charging Based on Tomorrow's Prices
@ -227,8 +229,8 @@ automation:
to: "on" to: "on"
condition: condition:
- condition: numeric_state - condition: numeric_state
entity_id: sensor.tibber_current_price_rating entity_id: sensor.tibber_current_interval_price_rating
below: -15 # At least 15% below average below: -15 # At least 15% below average
- condition: numeric_state - condition: numeric_state
entity_id: sensor.ev_battery_level entity_id: sensor.ev_battery_level
below: 80 below: 80
@ -273,15 +275,18 @@ automation:
Every sensor includes rich attributes beyond just the state value. These attributes provide context, timestamps, and additional data useful for automations and templates. Every sensor includes rich attributes beyond just the state value. These attributes provide context, timestamps, and additional data useful for automations and templates.
**Standard attributes available on most sensors:** **Standard attributes available on most sensors:**
- `timestamp` - ISO 8601 timestamp for the data point
- `description` - Brief explanation of what the sensor represents - `timestamp` - ISO 8601 timestamp for the data point
- `level_id` and `level_value` - For price level sensors (e.g., `VERY_CHEAP` = -2) - `description` - Brief explanation of what the sensor represents
- `level_id` and `level_value` - For price level sensors (e.g., `VERY_CHEAP` = -2)
**Extended descriptions** (enable in integration options): **Extended descriptions** (enable in integration options):
- `long_description` - Detailed explanation of the sensor's purpose
- `usage_tips` - Practical suggestions for using the sensor in automations - `long_description` - Detailed explanation of the sensor's purpose
- `usage_tips` - Practical suggestions for using the sensor in automations
**Example - Current Price sensor attributes:** **Example - Current Price sensor attributes:**
```yaml ```yaml
timestamp: "2025-11-03T14:15:00+01:00" timestamp: "2025-11-03T14:15:00+01:00"
description: "The current electricity price per kWh" description: "The current electricity price per kWh"
@ -290,14 +295,15 @@ usage_tips: "Use this to track prices or to create automations that run when ele
``` ```
**Example template using attributes:** **Example template using attributes:**
```yaml ```yaml
template: template:
- sensor: - sensor:
- name: "Price Status" - name: "Price Status"
state: > state: >
{% set price = states('sensor.tibber_current_electricity_price') | float %} {% set price = states('sensor.tibber_current_electricity_price') | float %}
{% set timestamp = state_attr('sensor.tibber_current_electricity_price', 'timestamp') %} {% set timestamp = state_attr('sensor.tibber_current_electricity_price', 'timestamp') %}
Price at {{ timestamp }}: {{ price }} ct/kWh Price at {{ timestamp }}: {{ price }} ct/kWh
``` ```
📖 **[View all sensors and attributes →](docs/user/sensors.md)** 📖 **[View all sensors and attributes →](docs/user/sensors.md)**
@ -325,9 +331,9 @@ Contributions are welcome! Please read the [Contributing Guidelines](CONTRIBUTIN
### For Contributors ### For Contributors
- **[Developer Setup](docs/development/setup.md)** - Get started with DevContainer - **[Developer Setup](docs/development/setup.md)** - Get started with DevContainer
- **[Architecture Guide](docs/development/architecture.md)** - Understand the codebase - **[Architecture Guide](docs/development/architecture.md)** - Understand the codebase
- **[Release Management](docs/development/release-management.md)** - Release process and versioning - **[Release Management](docs/development/release-management.md)** - Release process and versioning
## 🤖 Development Note ## 🤖 Development Note

View file

@ -488,7 +488,7 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
_TOTAL_STEPS: ClassVar[int] = 6 _TOTAL_STEPS: ClassVar[int] = 6
_STEP_INFO: ClassVar[dict[str, int]] = { _STEP_INFO: ClassVar[dict[str, int]] = {
"init": 1, "init": 1,
"price_rating": 2, "current_interval_price_rating": 2,
"volatility": 3, "volatility": 3,
"best_price": 4, "best_price": 4,
"peak_price": 5, "peak_price": 5,
@ -553,7 +553,7 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
return await self.async_step_volatility() return await self.async_step_volatility()
return self.async_show_form( return self.async_show_form(
step_id="price_rating", step_id="current_interval_price_rating",
data_schema=vol.Schema( data_schema=vol.Schema(
{ {
vol.Optional( vol.Optional(
@ -592,7 +592,7 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
), ),
} }
), ),
description_placeholders=self._get_step_description_placeholders("price_rating"), description_placeholders=self._get_step_description_placeholders("current_interval_price_rating"),
) )
async def async_step_best_price(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: async def async_step_best_price(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
@ -666,7 +666,7 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
SelectSelectorConfig( SelectSelectorConfig(
options=BEST_PRICE_MAX_LEVEL_OPTIONS, options=BEST_PRICE_MAX_LEVEL_OPTIONS,
mode=SelectSelectorMode.DROPDOWN, mode=SelectSelectorMode.DROPDOWN,
translation_key="price_level", translation_key="current_interval_price_level",
), ),
), ),
vol.Optional( vol.Optional(
@ -817,7 +817,7 @@ class TibberPricesOptionsFlowHandler(OptionsFlow):
SelectSelectorConfig( SelectSelectorConfig(
options=PEAK_PRICE_MIN_LEVEL_OPTIONS, options=PEAK_PRICE_MIN_LEVEL_OPTIONS,
mode=SelectSelectorMode.DROPDOWN, mode=SelectSelectorMode.DROPDOWN,
translation_key="price_level", translation_key="current_interval_price_level",
), ),
), ),
vol.Optional( vol.Optional(

View file

@ -267,7 +267,7 @@ PRICE_LEVEL_COLOR_MAPPING = {
} }
# Icon mapping for current price sensors (dynamic icons based on price level) # Icon mapping for current price sensors (dynamic icons based on price level)
# Used by current_price and current_hour_average sensors # Used by current_interval_price and current_hour_average sensors
# Icon shows price level (cheap/normal/expensive), icon_color reinforces with color # Icon shows price level (cheap/normal/expensive), icon_color reinforces with color
PRICE_LEVEL_CASH_ICON_MAPPING = { PRICE_LEVEL_CASH_ICON_MAPPING = {
PRICE_LEVEL_VERY_CHEAP: "mdi:cash-multiple", # Many coins (save a lot!) PRICE_LEVEL_VERY_CHEAP: "mdi:cash-multiple", # Many coins (save a lot!)
@ -667,7 +667,9 @@ async def async_get_price_level_translation(
The localized price level if found, None otherwise The localized price level if found, None otherwise
""" """
return await async_get_translation(hass, ["sensor", "price_level", "price_levels", level], language) return await async_get_translation(
hass, ["sensor", "current_interval_price_level", "price_levels", level], language
)
def get_price_level_translation( def get_price_level_translation(
@ -687,7 +689,7 @@ def get_price_level_translation(
The localized price level if found in cache, None otherwise The localized price level if found in cache, None otherwise
""" """
return get_translation(["sensor", "price_level", "price_levels", level], language) return get_translation(["sensor", "current_interval_price_level", "price_levels", level], language)
async def async_get_home_type_translation( async def async_get_home_type_translation(

View file

@ -107,11 +107,11 @@ TOMORROW_DATA_CHECK_HOUR = 13
TIME_SENSITIVE_ENTITY_KEYS = frozenset( TIME_SENSITIVE_ENTITY_KEYS = frozenset(
{ {
# Current/next/previous price sensors # Current/next/previous price sensors
"current_price", "current_interval_price",
"next_interval_price", "next_interval_price",
"previous_interval_price", "previous_interval_price",
# Current/next/previous price levels # Current/next/previous price levels
"price_level", "current_interval_price_level",
"next_interval_price_level", "next_interval_price_level",
"previous_interval_price_level", "previous_interval_price_level",
# Rolling hour calculations (5-interval windows) # Rolling hour calculations (5-interval windows)
@ -120,7 +120,7 @@ TIME_SENSITIVE_ENTITY_KEYS = frozenset(
"current_hour_price_level", "current_hour_price_level",
"next_hour_price_level", "next_hour_price_level",
# Current/next/previous price ratings # Current/next/previous price ratings
"price_rating", "current_interval_price_rating",
"next_interval_price_rating", "next_interval_price_rating",
"previous_interval_price_rating", "previous_interval_price_rating",
"current_hour_price_rating", "current_hour_price_rating",
@ -222,7 +222,7 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
""" """
Listen for time-sensitive updates that occur every quarter-hour. Listen for time-sensitive updates that occur every quarter-hour.
Time-sensitive entities (like current_price, next_interval_price, etc.) should use this Time-sensitive entities (like current_interval_price, next_interval_price, etc.) should use this
method instead of async_add_listener to receive updates at quarter-hour boundaries. method instead of async_add_listener to receive updates at quarter-hour boundaries.
Returns: Returns:

View file

@ -1,6 +1,6 @@
{ {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"description": "Der aktuelle Strompreis pro kWh", "description": "Der aktuelle Strompreis pro kWh",
"long_description": "Zeigt den aktuellen Preis pro kWh von deinem Tibber-Abonnement an", "long_description": "Zeigt den aktuellen Preis pro kWh von deinem Tibber-Abonnement an",
"usage_tips": "Nutze dies, um Preise zu verfolgen oder Automatisierungen zu erstellen, die bei günstigem Strom ausgeführt werden" "usage_tips": "Nutze dies, um Preise zu verfolgen oder Automatisierungen zu erstellen, die bei günstigem Strom ausgeführt werden"
@ -85,7 +85,7 @@
"long_description": "Zeigt den höchsten Preis pro kWh für die nächsten 24 Stunden (vorlaufendes Maximum) von deinem Tibber-Abonnement an. Dies bietet den höchsten erwarteten Preis in den nächsten 24 Stunden basierend auf Prognosedaten.", "long_description": "Zeigt den höchsten Preis pro kWh für die nächsten 24 Stunden (vorlaufendes Maximum) von deinem Tibber-Abonnement an. Dies bietet den höchsten erwarteten Preis in den nächsten 24 Stunden basierend auf Prognosedaten.",
"usage_tips": "Nutze dies, um den Betrieb von Geräten während kommender Spitzenpreiszeiten zu vermeiden." "usage_tips": "Nutze dies, um den Betrieb von Geräten während kommender Spitzenpreiszeiten zu vermeiden."
}, },
"price_level": { "current_interval_price_level": {
"description": "Die aktuelle Preislevelklassifikation", "description": "Die aktuelle Preislevelklassifikation",
"long_description": "Zeigt die Klassifizierung von Tibber für den aktuellen Preis im Vergleich zu historischen Preisen an", "long_description": "Zeigt die Klassifizierung von Tibber für den aktuellen Preis im Vergleich zu historischen Preisen an",
"usage_tips": "Nutze dies, um Automatisierungen auf Basis des relativen Preisniveaus anstelle der absoluten Preise zu erstellen" "usage_tips": "Nutze dies, um Automatisierungen auf Basis des relativen Preisniveaus anstelle der absoluten Preise zu erstellen"
@ -110,7 +110,7 @@
"long_description": "Zeigt das mediane Preisniveau über 5 Intervalle, die eine Stunde voraus zentriert sind. Hilft bei der Verbrauchsplanung basierend auf kommenden Preistrends statt momentanen zukünftigen Preisen.", "long_description": "Zeigt das mediane Preisniveau über 5 Intervalle, die eine Stunde voraus zentriert sind. Hilft bei der Verbrauchsplanung basierend auf kommenden Preistrends statt momentanen zukünftigen Preisen.",
"usage_tips": "Nutze dies, um Aktivitäten für die nächste Stunde basierend auf einer geglätteten Preisniveau-Prognose zu planen." "usage_tips": "Nutze dies, um Aktivitäten für die nächste Stunde basierend auf einer geglätteten Preisniveau-Prognose zu planen."
}, },
"price_rating": { "current_interval_price_rating": {
"description": "Wie sich der Preis des aktuellen Intervalls mit historischen Daten vergleicht", "description": "Wie sich der Preis des aktuellen Intervalls mit historischen Daten vergleicht",
"long_description": "Zeigt, wie sich der Preis des aktuellen Intervalls im Vergleich zu historischen Preisdaten als Prozentsatz verhält", "long_description": "Zeigt, wie sich der Preis des aktuellen Intervalls im Vergleich zu historischen Preisdaten als Prozentsatz verhält",
"usage_tips": "Ein positiver Prozentsatz bedeutet, dass der aktuelle Preis überdurchschnittlich ist, negativ bedeutet unterdurchschnittlich" "usage_tips": "Ein positiver Prozentsatz bedeutet, dass der aktuelle Preis überdurchschnittlich ist, negativ bedeutet unterdurchschnittlich"

View file

@ -1,6 +1,6 @@
{ {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"description": "The current electricity price per kWh", "description": "The current electricity price per kWh",
"long_description": "Shows the current price per kWh from your Tibber subscription", "long_description": "Shows the current price per kWh from your Tibber subscription",
"usage_tips": "Use this to track prices or to create automations that run when electricity is cheap" "usage_tips": "Use this to track prices or to create automations that run when electricity is cheap"
@ -85,7 +85,7 @@
"long_description": "Shows the maximum price per kWh from the next 24 hours (leading maximum) from your Tibber subscription. This provides the highest price expected in the next 24 hours based on forecast data.", "long_description": "Shows the maximum price per kWh from the next 24 hours (leading maximum) from your Tibber subscription. This provides the highest price expected in the next 24 hours based on forecast data.",
"usage_tips": "Use this to avoid running appliances during upcoming peak price periods." "usage_tips": "Use this to avoid running appliances during upcoming peak price periods."
}, },
"price_level": { "current_interval_price_level": {
"description": "The current price level classification", "description": "The current price level classification",
"long_description": "Shows Tibber's classification of the current price compared to historical prices", "long_description": "Shows Tibber's classification of the current price compared to historical prices",
"usage_tips": "Use this to create automations based on relative price levels rather than absolute prices" "usage_tips": "Use this to create automations based on relative price levels rather than absolute prices"
@ -110,7 +110,7 @@
"long_description": "Shows the median price level across 5 intervals centered one hour ahead. Helps plan consumption based on upcoming price trends rather than instantaneous future prices.", "long_description": "Shows the median price level across 5 intervals centered one hour ahead. Helps plan consumption based on upcoming price trends rather than instantaneous future prices.",
"usage_tips": "Use to schedule activities for the next hour based on a smoothed price level forecast." "usage_tips": "Use to schedule activities for the next hour based on a smoothed price level forecast."
}, },
"price_rating": { "current_interval_price_rating": {
"description": "How the current interval's price compares to historical data", "description": "How the current interval's price compares to historical data",
"long_description": "Shows how the current interval's price compares to historical price data as a percentage", "long_description": "Shows how the current interval's price compares to historical price data as a percentage",
"usage_tips": "A positive percentage means the current price is above average, negative means below average" "usage_tips": "A positive percentage means the current price is above average, negative means below average"

View file

@ -1,6 +1,6 @@
{ {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"description": "Den nåværende elektrisitetsprisen per kWh", "description": "Den nåværende elektrisitetsprisen per kWh",
"long_description": "Viser nåværende pris per kWh fra ditt Tibber-abonnement", "long_description": "Viser nåværende pris per kWh fra ditt Tibber-abonnement",
"usage_tips": "Bruk dette til å spore priser eller lage automatiseringer som kjører når strøm er billig" "usage_tips": "Bruk dette til å spore priser eller lage automatiseringer som kjører når strøm er billig"
@ -85,7 +85,7 @@
"long_description": "Viser maksimumsprisen per kWh fra de neste 24 timene (fremtidsrettet maksimum) fra ditt Tibber-abonnement. Dette gir den høyeste prisen forventet i de neste 24 timene basert på prognosedata.", "long_description": "Viser maksimumsprisen per kWh fra de neste 24 timene (fremtidsrettet maksimum) fra ditt Tibber-abonnement. Dette gir den høyeste prisen forventet i de neste 24 timene basert på prognosedata.",
"usage_tips": "Bruk dette til å unngå å kjøre apparater i kommende topprisperioder." "usage_tips": "Bruk dette til å unngå å kjøre apparater i kommende topprisperioder."
}, },
"price_level": { "current_interval_price_level": {
"description": "Den nåværende prisnivåklassifiseringen", "description": "Den nåværende prisnivåklassifiseringen",
"long_description": "Viser Tibbers klassifisering av nåværende pris sammenlignet med historiske priser", "long_description": "Viser Tibbers klassifisering av nåværende pris sammenlignet med historiske priser",
"usage_tips": "Bruk dette til å lage automatiseringer basert på relative prisnivåer i stedet for absolutte priser" "usage_tips": "Bruk dette til å lage automatiseringer basert på relative prisnivåer i stedet for absolutte priser"
@ -110,7 +110,7 @@
"long_description": "Viser median prisnivå på tvers av 5 intervaller sentrert en time frem. Hjelper med å planlegge forbruk basert på kommende pristrender i stedet for øyeblikkelige fremtidige priser.", "long_description": "Viser median prisnivå på tvers av 5 intervaller sentrert en time frem. Hjelper med å planlegge forbruk basert på kommende pristrender i stedet for øyeblikkelige fremtidige priser.",
"usage_tips": "Bruk for å planlegge aktiviteter for neste time basert på en utjevnet prisnivåprognose." "usage_tips": "Bruk for å planlegge aktiviteter for neste time basert på en utjevnet prisnivåprognose."
}, },
"price_rating": { "current_interval_price_rating": {
"description": "Hvordan nåværende intervalls pris sammenlignes med historiske data", "description": "Hvordan nåværende intervalls pris sammenlignes med historiske data",
"long_description": "Viser hvordan nåværende intervalls pris sammenlignes med historiske prisdata som en prosentandel", "long_description": "Viser hvordan nåværende intervalls pris sammenlignes med historiske prisdata som en prosentandel",
"usage_tips": "En positiv prosentandel betyr at nåværende pris er over gjennomsnittet, negativ betyr under gjennomsnittet" "usage_tips": "En positiv prosentandel betyr at nåværende pris er over gjennomsnittet, negativ betyr under gjennomsnittet"

View file

@ -1,6 +1,6 @@
{ {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"description": "De huidige elektriciteitsprijs per kWh", "description": "De huidige elektriciteitsprijs per kWh",
"long_description": "Toont de huidige prijs per kWh van uw Tibber-abonnement", "long_description": "Toont de huidige prijs per kWh van uw Tibber-abonnement",
"usage_tips": "Gebruik dit om prijzen bij te houden of om automatiseringen te maken die worden uitgevoerd wanneer elektriciteit goedkoop is" "usage_tips": "Gebruik dit om prijzen bij te houden of om automatiseringen te maken die worden uitgevoerd wanneer elektriciteit goedkoop is"
@ -85,7 +85,7 @@
"long_description": "Toont de maximumprijs per kWh van de komende 24 uur (vooruitlopend maximum) van uw Tibber-abonnement. Dit geeft de hoogste prijs die wordt verwacht in de komende 24 uur op basis van prognosegegevens.", "long_description": "Toont de maximumprijs per kWh van de komende 24 uur (vooruitlopend maximum) van uw Tibber-abonnement. Dit geeft de hoogste prijs die wordt verwacht in de komende 24 uur op basis van prognosegegevens.",
"usage_tips": "Gebruik dit om te voorkomen dat u apparaten draait tijdens aanstaande piekprijsperioden." "usage_tips": "Gebruik dit om te voorkomen dat u apparaten draait tijdens aanstaande piekprijsperioden."
}, },
"price_level": { "current_interval_price_level": {
"description": "De huidige prijsniveauclassificatie", "description": "De huidige prijsniveauclassificatie",
"long_description": "Toont de classificatie van Tibber van de huidige prijs vergeleken met historische prijzen", "long_description": "Toont de classificatie van Tibber van de huidige prijs vergeleken met historische prijzen",
"usage_tips": "Gebruik dit om automatiseringen te maken op basis van relatieve prijsniveaus in plaats van absolute prijzen" "usage_tips": "Gebruik dit om automatiseringen te maken op basis van relatieve prijsniveaus in plaats van absolute prijzen"
@ -110,7 +110,7 @@
"long_description": "Toont het mediane prijsniveau over 5 intervallen gecentreerd één uur vooruit. Helpt verbruik te plannen op basis van aanstaande prijstrends in plaats van momentane toekomstige prijzen.", "long_description": "Toont het mediane prijsniveau over 5 intervallen gecentreerd één uur vooruit. Helpt verbruik te plannen op basis van aanstaande prijstrends in plaats van momentane toekomstige prijzen.",
"usage_tips": "Gebruik om activiteiten voor het volgende uur te plannen op basis van een vloeiende prijsniveauprognose." "usage_tips": "Gebruik om activiteiten voor het volgende uur te plannen op basis van een vloeiende prijsniveauprognose."
}, },
"price_rating": { "current_interval_price_rating": {
"description": "Hoe de prijs van het huidige interval zich verhoudt tot historische gegevens", "description": "Hoe de prijs van het huidige interval zich verhoudt tot historische gegevens",
"long_description": "Toont hoe de prijs van het huidige interval zich verhoudt tot historische prijsgegevens als een percentage", "long_description": "Toont hoe de prijs van het huidige interval zich verhoudt tot historische prijsgegevens als een percentage",
"usage_tips": "Een positief percentage betekent dat de huidige prijs boven het gemiddelde ligt, negatief betekent onder het gemiddelde" "usage_tips": "Een positief percentage betekent dat de huidige prijs boven het gemiddelde ligt, negatief betekent onder het gemiddelde"

View file

@ -1,6 +1,6 @@
{ {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"description": "Det nuvarande elpriset per kWh", "description": "Det nuvarande elpriset per kWh",
"long_description": "Visar nuvarande pris per kWh från ditt Tibber-abonnemang", "long_description": "Visar nuvarande pris per kWh från ditt Tibber-abonnemang",
"usage_tips": "Använd detta för att spåra priser eller skapa automationer som körs när el är billig" "usage_tips": "Använd detta för att spåra priser eller skapa automationer som körs när el är billig"
@ -85,7 +85,7 @@
"long_description": "Visar maximipriset per kWh från nästa 24 timmar (framåtblickande maximum) från ditt Tibber-abonnemang. Detta ger det högsta priset som förväntas nästa 24 timmar baserat på prognosdata.", "long_description": "Visar maximipriset per kWh från nästa 24 timmar (framåtblickande maximum) från ditt Tibber-abonnemang. Detta ger det högsta priset som förväntas nästa 24 timmar baserat på prognosdata.",
"usage_tips": "Använd detta för att undvika att köra apparater under kommande toppprisperioder." "usage_tips": "Använd detta för att undvika att köra apparater under kommande toppprisperioder."
}, },
"price_level": { "current_interval_price_level": {
"description": "Den nuvarande prisnivåklassificeringen", "description": "Den nuvarande prisnivåklassificeringen",
"long_description": "Visar Tibbers klassificering av nuvarande pris jämfört med historiska priser", "long_description": "Visar Tibbers klassificering av nuvarande pris jämfört med historiska priser",
"usage_tips": "Använd detta för att skapa automationer baserade på relativa prisnivåer istället för absoluta priser" "usage_tips": "Använd detta för att skapa automationer baserade på relativa prisnivåer istället för absoluta priser"
@ -110,7 +110,7 @@
"long_description": "Visar median prisnivå över 5 intervaller centrerade en timme framåt. Hjälper att planera konsumtion baserat på kommande pristrender istället för ögonblickliga framtida priser.", "long_description": "Visar median prisnivå över 5 intervaller centrerade en timme framåt. Hjälper att planera konsumtion baserat på kommande pristrender istället för ögonblickliga framtida priser.",
"usage_tips": "Använd för att schemalägga aktiviteter för nästa timme baserat på en utjämnad prisnivåprognos." "usage_tips": "Använd för att schemalägga aktiviteter för nästa timme baserat på en utjämnad prisnivåprognos."
}, },
"price_rating": { "current_interval_price_rating": {
"description": "Hur nuvarande intervalls pris jämförs med historiska data", "description": "Hur nuvarande intervalls pris jämförs med historiska data",
"long_description": "Visar hur nuvarande intervalls pris jämförs med historiska prisdata som en procentsats", "long_description": "Visar hur nuvarande intervalls pris jämförs med historiska prisdata som en procentsats",
"usage_tips": "En positiv procentsats betyder att nuvarande pris är över genomsnittet, negativ betyder under genomsnittet" "usage_tips": "En positiv procentsats betyder att nuvarande pris är över genomsnittet, negativ betyder under genomsnittet"

View file

@ -149,7 +149,7 @@ def calculate_trailing_average_for_interval(
def calculate_difference_percentage( def calculate_difference_percentage(
current_price: float, current_interval_price: float,
trailing_average: float | None, trailing_average: float | None,
) -> float | None: ) -> float | None:
""" """
@ -158,7 +158,7 @@ def calculate_difference_percentage(
This mimics the API's "difference" field from priceRating endpoint. This mimics the API's "difference" field from priceRating endpoint.
Args: Args:
current_price: The current interval's price current_interval_price: The current interval's price
trailing_average: The 24-hour trailing average price trailing_average: The 24-hour trailing average price
Returns: Returns:
@ -169,7 +169,7 @@ def calculate_difference_percentage(
if trailing_average is None or trailing_average == 0: if trailing_average is None or trailing_average == 0:
return None return None
return ((current_price - trailing_average) / trailing_average) * 100 return ((current_interval_price - trailing_average) / trailing_average) * 100
def calculate_rating_level( def calculate_rating_level(
@ -238,9 +238,9 @@ def _process_price_interval(
return return
starts_at = dt_util.as_local(starts_at) starts_at = dt_util.as_local(starts_at)
current_price = price_interval.get("total") current_interval_price = price_interval.get("total")
if current_price is None: if current_interval_price is None:
return return
# Calculate trailing average # Calculate trailing average
@ -248,7 +248,7 @@ def _process_price_interval(
# Calculate and set the difference and rating_level # Calculate and set the difference and rating_level
if trailing_avg is not None: if trailing_avg is not None:
difference = calculate_difference_percentage(float(current_price), trailing_avg) difference = calculate_difference_percentage(float(current_interval_price), trailing_avg)
price_interval["difference"] = difference price_interval["difference"] = difference
# Calculate rating_level based on difference # Calculate rating_level based on difference
@ -486,7 +486,7 @@ def aggregate_period_ratings(
def calculate_price_trend( def calculate_price_trend(
current_price: float, current_interval_price: float,
future_average: float, future_average: float,
threshold_rising: float = 5.0, threshold_rising: float = 5.0,
threshold_falling: float = -5.0, threshold_falling: float = -5.0,
@ -495,7 +495,7 @@ def calculate_price_trend(
Calculate price trend by comparing current price with future average. Calculate price trend by comparing current price with future average.
Args: Args:
current_price: Current interval price current_interval_price: Current interval price
future_average: Average price of future intervals future_average: Average price of future intervals
threshold_rising: Percentage threshold for rising trend (positive, default 5%) threshold_rising: Percentage threshold for rising trend (positive, default 5%)
threshold_falling: Percentage threshold for falling trend (negative, default -5%) threshold_falling: Percentage threshold for falling trend (negative, default -5%)
@ -506,12 +506,12 @@ def calculate_price_trend(
difference_percentage: % change from current to future ((future - current) / current * 100) difference_percentage: % change from current to future ((future - current) / current * 100)
""" """
if current_price == 0: if current_interval_price == 0:
# Avoid division by zero # Avoid division by zero
return "stable", 0.0 return "stable", 0.0
# Calculate percentage difference from current to future # Calculate percentage difference from current to future
diff_pct = ((future_average - current_price) / current_price) * 100 diff_pct = ((future_average - current_interval_price) / current_interval_price) * 100
# Determine trend based on thresholds # Determine trend based on thresholds
# threshold_falling is negative, so we compare with it directly # threshold_falling is negative, so we compare with it directly

View file

@ -80,8 +80,8 @@ MIN_HOURS_FOR_LATER_HALF = 3 # Minimum hours needed to calculate later half ave
# Main price sensors that users will typically use in automations # Main price sensors that users will typically use in automations
PRICE_SENSORS = ( PRICE_SENSORS = (
SensorEntityDescription( SensorEntityDescription(
key="current_price", key="current_interval_price",
translation_key="current_price", translation_key="current_interval_price",
name="Current Electricity Price", name="Current Electricity Price",
icon="mdi:cash", # Dynamic: will show cash-multiple/plus/cash/minus/remove based on level icon="mdi:cash", # Dynamic: will show cash-multiple/plus/cash/minus/remove based on level
device_class=SensorDeviceClass.MONETARY, device_class=SensorDeviceClass.MONETARY,
@ -124,8 +124,8 @@ PRICE_SENSORS = (
# import timing issues with Home Assistant's entity platform initialization. # import timing issues with Home Assistant's entity platform initialization.
# Keep in sync with PRICE_LEVEL_OPTIONS in const.py! # Keep in sync with PRICE_LEVEL_OPTIONS in const.py!
SensorEntityDescription( SensorEntityDescription(
key="price_level", key="current_interval_price_level",
translation_key="price_level", translation_key="current_interval_price_level",
name="Current Price Level", name="Current Price Level",
icon="mdi:gauge", icon="mdi:gauge",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
@ -314,8 +314,8 @@ VOLATILITY_SENSORS = (
# Keep in sync with PRICE_RATING_OPTIONS in const.py! # Keep in sync with PRICE_RATING_OPTIONS in const.py!
RATING_SENSORS = ( RATING_SENSORS = (
SensorEntityDescription( SensorEntityDescription(
key="price_rating", key="current_interval_price_rating",
translation_key="price_rating", translation_key="current_interval_price_rating",
name="Current Price Rating", name="Current Price Rating",
icon="mdi:star-outline", icon="mdi:star-outline",
device_class=SensorDeviceClass.ENUM, device_class=SensorDeviceClass.ENUM,
@ -620,13 +620,13 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
# Map sensor keys to their handler methods # Map sensor keys to their handler methods
handlers = { handlers = {
# Price level sensors # Price level sensors
"price_level": self._get_price_level_value, "current_interval_price_level": self._get_price_level_value,
"next_interval_price_level": lambda: self._get_interval_level_value(interval_offset=1), "next_interval_price_level": lambda: self._get_interval_level_value(interval_offset=1),
"previous_interval_price_level": lambda: self._get_interval_level_value(interval_offset=-1), "previous_interval_price_level": lambda: self._get_interval_level_value(interval_offset=-1),
"current_hour_price_level": lambda: self._get_rolling_hour_level_value(hour_offset=0), "current_hour_price_level": lambda: self._get_rolling_hour_level_value(hour_offset=0),
"next_hour_price_level": lambda: self._get_rolling_hour_level_value(hour_offset=1), "next_hour_price_level": lambda: self._get_rolling_hour_level_value(hour_offset=1),
# Price sensors # Price sensors
"current_price": lambda: self._get_interval_price_value(interval_offset=0, in_euro=False), "current_interval_price": lambda: self._get_interval_price_value(interval_offset=0, in_euro=False),
"next_interval_price": lambda: self._get_interval_price_value(interval_offset=1, in_euro=False), "next_interval_price": lambda: self._get_interval_price_value(interval_offset=1, in_euro=False),
"previous_interval_price": lambda: self._get_interval_price_value(interval_offset=-1, in_euro=False), "previous_interval_price": lambda: self._get_interval_price_value(interval_offset=-1, in_euro=False),
# Rolling hour average (5 intervals: 2 before + current + 2 after) # Rolling hour average (5 intervals: 2 before + current + 2 after)
@ -692,7 +692,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
decimals=2, decimals=2,
), ),
# Rating sensors # Rating sensors
"price_rating": lambda: self._get_rating_value(rating_type="current"), "current_interval_price_rating": lambda: self._get_rating_value(rating_type="current"),
"next_interval_price_rating": lambda: self._get_interval_rating_value(interval_offset=1), "next_interval_price_rating": lambda: self._get_interval_rating_value(interval_offset=1),
"previous_interval_price_rating": lambda: self._get_interval_rating_value(interval_offset=-1), "previous_interval_price_rating": lambda: self._get_interval_rating_value(interval_offset=-1),
"current_hour_price_rating": lambda: self._get_rolling_hour_rating_value(hour_offset=0), "current_hour_price_rating": lambda: self._get_rolling_hour_rating_value(hour_offset=0),
@ -1115,11 +1115,11 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
if ( if (
translations translations
and "sensor" in translations and "sensor" in translations
and "price_rating" in translations["sensor"] and "current_interval_price_rating" in translations["sensor"]
and "price_levels" in translations["sensor"]["price_rating"] and "price_levels" in translations["sensor"]["current_interval_price_rating"]
and level in translations["sensor"]["price_rating"]["price_levels"] and level in translations["sensor"]["current_interval_price_rating"]["price_levels"]
): ):
return translations["sensor"]["price_rating"]["price_levels"][level] return translations["sensor"]["current_interval_price_rating"]["price_levels"][level]
# Fallback to English if not found # Fallback to English if not found
if language != "en": if language != "en":
en_cache_key = f"{DOMAIN}_translations_en" en_cache_key = f"{DOMAIN}_translations_en"
@ -1127,11 +1127,11 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
if ( if (
en_translations en_translations
and "sensor" in en_translations and "sensor" in en_translations
and "price_rating" in en_translations and "current_interval_price_rating" in en_translations
and "price_levels" in en_translations["sensor"]["price_rating"] and "price_levels" in en_translations["sensor"]["current_interval_price_rating"]
and level in en_translations["sensor"]["price_rating"]["price_levels"] and level in en_translations["sensor"]["current_interval_price_rating"]["price_levels"]
): ):
return en_translations["sensor"]["price_rating"]["price_levels"][level] return en_translations["sensor"]["current_interval_price_rating"]["price_levels"][level]
return level return level
def _get_rating_value(self, *, rating_type: str) -> str | None: def _get_rating_value(self, *, rating_type: str) -> str | None:
@ -1285,7 +1285,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
if not current_interval or "total" not in current_interval: if not current_interval or "total" not in current_interval:
return None return None
current_price = float(current_interval["total"]) current_interval_price = float(current_interval["total"])
current_starts_at = dt_util.parse_datetime(current_interval["startsAt"]) current_starts_at = dt_util.parse_datetime(current_interval["startsAt"])
if current_starts_at is None: if current_starts_at is None:
return None return None
@ -1311,7 +1311,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
# Calculate trend with configured thresholds # Calculate trend with configured thresholds
trend_state, diff_pct = calculate_price_trend( trend_state, diff_pct = calculate_price_trend(
current_price, future_avg, threshold_rising=threshold_rising, threshold_falling=threshold_falling current_interval_price, future_avg, threshold_rising=threshold_rising, threshold_falling=threshold_falling
) )
# Determine icon color based on trend state # Determine icon color based on trend state
@ -1340,8 +1340,8 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
self._trend_attributes[f"second_half_{hours}h_avg"] = round(later_half_avg * 100, 2) self._trend_attributes[f"second_half_{hours}h_avg"] = round(later_half_avg * 100, 2)
# Calculate incremental change: how much does the later half differ from current? # Calculate incremental change: how much does the later half differ from current?
if current_price > 0: if current_interval_price > 0:
later_half_diff = ((later_half_avg - current_price) / current_price) * 100 later_half_diff = ((later_half_avg - current_interval_price) / current_interval_price) * 100
self._trend_attributes[f"second_half_{hours}h_diff_from_current_%"] = round(later_half_diff, 1) self._trend_attributes[f"second_half_{hours}h_diff_from_current_%"] = round(later_half_diff, 1)
# Cache the trend value for consistency # Cache the trend value for consistency
@ -1711,7 +1711,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
if not self.coordinator.data or not self._value_getter: if not self.coordinator.data or not self._value_getter:
return None return None
# For price_level, ensure we return the translated value as state # For price_level, ensure we return the translated value as state
if self.entity_description.key == "price_level": if self.entity_description.key == "current_interval_price_level":
return self._get_price_level_value() return self._get_price_level_value()
return self._value_getter() return self._value_getter()
except (KeyError, ValueError, TypeError) as ex: except (KeyError, ValueError, TypeError) as ex:
@ -1771,11 +1771,11 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
""" """
Get icon for current price sensors (dynamic based on price level). Get icon for current price sensors (dynamic based on price level).
Only current_price and current_hour_average have dynamic icons. Only current_interval_price and current_hour_average have dynamic icons.
Other price sensors (next/previous) use static icons from entity description. Other price sensors (next/previous) use static icons from entity description.
""" """
# Only current price sensors get dynamic icons # Only current price sensors get dynamic icons
if key == "current_price": if key == "current_interval_price":
level = self._get_price_level_for_sensor(key) level = self._get_price_level_for_sensor(key)
if level: if level:
return PRICE_LEVEL_CASH_ICON_MAPPING.get(level.upper()) return PRICE_LEVEL_CASH_ICON_MAPPING.get(level.upper())
@ -1790,7 +1790,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
def _get_level_sensor_icon(self, key: str, value: Any) -> str | None: def _get_level_sensor_icon(self, key: str, value: Any) -> str | None:
"""Get icon for price level sensors.""" """Get icon for price level sensors."""
if key not in [ if key not in [
"price_level", "current_interval_price_level",
"next_interval_price_level", "next_interval_price_level",
"previous_interval_price_level", "previous_interval_price_level",
"current_hour_price_level", "current_hour_price_level",
@ -1803,7 +1803,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
def _get_rating_sensor_icon(self, key: str, value: Any) -> str | None: def _get_rating_sensor_icon(self, key: str, value: Any) -> str | None:
"""Get icon for price rating sensors.""" """Get icon for price rating sensors."""
if key not in [ if key not in [
"price_rating", "current_interval_price_rating",
"next_interval_price_rating", "next_interval_price_rating",
"previous_interval_price_rating", "previous_interval_price_rating",
"current_hour_price_rating", "current_hour_price_rating",
@ -1830,7 +1830,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
# Map sensor key to interval offset # Map sensor key to interval offset
offset_map = { offset_map = {
"current_price": 0, "current_interval_price": 0,
"next_interval_price": 1, "next_interval_price": 1,
"previous_interval_price": -1, "previous_interval_price": -1,
} }
@ -1988,8 +1988,8 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
# Group sensors by type and delegate to specific handlers # Group sensors by type and delegate to specific handlers
if key in [ if key in [
"current_price", "current_interval_price",
"price_level", "current_interval_price_level",
"next_interval_price", "next_interval_price",
"previous_interval_price", "previous_interval_price",
"current_hour_average", "current_hour_average",
@ -2003,7 +2003,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
"current_hour_price_rating", "current_hour_price_rating",
"next_hour_price_rating", "next_hour_price_rating",
]: ]:
self._add_current_price_attributes(attributes) self._add_current_interval_price_attributes(attributes)
elif key in [ elif key in [
"trailing_price_average", "trailing_price_average",
"leading_price_average", "leading_price_average",
@ -2022,7 +2022,11 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
elif key.endswith("_volatility"): elif key.endswith("_volatility"):
self._add_volatility_attributes(attributes) self._add_volatility_attributes(attributes)
# For price_level, add the original level as attribute # For price_level, add the original level as attribute
if key == "price_level" and hasattr(self, "_last_price_level") and self._last_price_level is not None: if (
key == "current_interval_price_level"
and hasattr(self, "_last_price_level")
and self._last_price_level is not None
):
attributes["level_id"] = self._last_price_level attributes["level_id"] = self._last_price_level
except (KeyError, ValueError, TypeError) as ex: except (KeyError, ValueError, TypeError) as ex:
self.coordinator.logger.exception( self.coordinator.logger.exception(
@ -2035,8 +2039,8 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
else: else:
return attributes if attributes else None return attributes if attributes else None
def _add_current_price_attributes(self, attributes: dict) -> None: def _add_current_interval_price_attributes(self, attributes: dict) -> None:
"""Add attributes for current price sensors.""" """Add attributes for current interval price sensors."""
key = self.entity_description.key key = self.entity_description.key
price_info = self.coordinator.data.get("priceInfo", {}) if self.coordinator.data else {} price_info = self.coordinator.data.get("priceInfo", {}) if self.coordinator.data else {}
now = dt_util.now() now = dt_util.now()
@ -2085,7 +2089,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
attributes["timestamp"] = current_interval_data["startsAt"] if current_interval_data else None attributes["timestamp"] = current_interval_data["startsAt"] if current_interval_data else None
# Add icon_color for price sensors (based on their price level) # Add icon_color for price sensors (based on their price level)
if key in ["current_price", "next_interval_price", "previous_interval_price"]: if key in ["current_interval_price", "next_interval_price", "previous_interval_price"]:
# For interval-based price sensors, get level from interval_data # For interval-based price sensors, get level from interval_data
if interval_data and "level" in interval_data: if interval_data and "level" in interval_data:
level = interval_data["level"] level = interval_data["level"]
@ -2115,7 +2119,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
if level_value and isinstance(level_value, str): if level_value and isinstance(level_value, str):
self._add_price_level_attributes(attributes, level_value.upper()) self._add_price_level_attributes(attributes, level_value.upper())
# For current price level sensor # For current price level sensor
elif key == "price_level": elif key == "current_interval_price_level":
current_interval_data = self._get_current_interval_data() current_interval_data = self._get_current_interval_data()
if current_interval_data and "level" in current_interval_data: if current_interval_data and "level" in current_interval_data:
self._add_price_level_attributes(attributes, current_interval_data["level"]) self._add_price_level_attributes(attributes, current_interval_data["level"])
@ -2149,7 +2153,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
if rating_value and isinstance(rating_value, str): if rating_value and isinstance(rating_value, str):
self._add_price_rating_attributes(attributes, rating_value.upper()) self._add_price_rating_attributes(attributes, rating_value.upper())
# For current price rating sensor # For current price rating sensor
elif key == "price_rating": elif key == "current_interval_price_rating":
current_interval_data = self._get_current_interval_data() current_interval_data = self._get_current_interval_data()
if current_interval_data and "rating_level" in current_interval_data: if current_interval_data and "rating_level" in current_interval_data:
self._add_price_rating_attributes(attributes, current_interval_data["rating_level"]) self._add_price_rating_attributes(attributes, current_interval_data["rating_level"])
@ -2201,7 +2205,7 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
latest_timestamp = self._get_data_timestamp() latest_timestamp = self._get_data_timestamp()
if latest_timestamp: if latest_timestamp:
attributes["timestamp"] = latest_timestamp.isoformat() attributes["timestamp"] = latest_timestamp.isoformat()
elif key == "price_rating": elif key == "current_interval_price_rating":
interval_data = find_price_data_for_interval(price_info, now) interval_data = find_price_data_for_interval(price_info, now)
attributes["timestamp"] = interval_data["startsAt"] if interval_data else None attributes["timestamp"] = interval_data["startsAt"] if interval_data else None
if hasattr(self, "_last_rating_difference") and self._last_rating_difference is not None: if hasattr(self, "_last_rating_difference") and self._last_rating_difference is not None:

View file

@ -88,7 +88,7 @@
}, },
"submit": "Weiter zu Schritt 2" "submit": "Weiter zu Schritt 2"
}, },
"price_rating": { "current_interval_price_rating": {
"title": "Preisbewertungs-Schwellwerte", "title": "Preisbewertungs-Schwellwerte",
"description": "{step_progress}\n\nKonfiguration der Schwellwerte für Preisbewertungsstufen (niedrig/normal/hoch) basierend auf dem Vergleich mit dem gleitenden 24-Stunden-Durchschnitt.", "description": "{step_progress}\n\nKonfiguration der Schwellwerte für Preisbewertungsstufen (niedrig/normal/hoch) basierend auf dem Vergleich mit dem gleitenden 24-Stunden-Durchschnitt.",
"data": { "data": {
@ -183,7 +183,7 @@
}, },
"entity": { "entity": {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"name": "Aktueller Strompreis" "name": "Aktueller Strompreis"
}, },
"next_interval_price": { "next_interval_price": {
@ -198,7 +198,7 @@
"next_hour_average": { "next_hour_average": {
"name": "Nächster Stunden-Durchschnittspreis" "name": "Nächster Stunden-Durchschnittspreis"
}, },
"price_level": { "current_interval_price_level": {
"name": "Aktuelles Preisniveau", "name": "Aktuelles Preisniveau",
"state": { "state": {
"very_cheap": "Sehr günstig", "very_cheap": "Sehr günstig",
@ -284,7 +284,7 @@
"leading_price_max": { "leading_price_max": {
"name": "Vorlaufender 24h-Höchstpreis" "name": "Vorlaufender 24h-Höchstpreis"
}, },
"price_rating": { "current_interval_price_rating": {
"name": "Aktuelle Preisbewertung", "name": "Aktuelle Preisbewertung",
"state": { "state": {
"low": "Niedrig", "low": "Niedrig",
@ -507,7 +507,7 @@
"very_high": "Sehr hoch" "very_high": "Sehr hoch"
} }
}, },
"price_level": { "current_interval_price_level": {
"options": { "options": {
"any": "Beliebig", "any": "Beliebig",
"very_cheap": "Sehr günstig", "very_cheap": "Sehr günstig",

View file

@ -88,7 +88,7 @@
}, },
"submit": "Next to Step 2" "submit": "Next to Step 2"
}, },
"price_rating": { "current_interval_price_rating": {
"title": "Price Rating Thresholds", "title": "Price Rating Thresholds",
"description": "{step_progress}\n\nConfigure thresholds for price rating levels (low/normal/high) based on comparison with trailing 24-hour average.", "description": "{step_progress}\n\nConfigure thresholds for price rating levels (low/normal/high) based on comparison with trailing 24-hour average.",
"data": { "data": {
@ -179,7 +179,7 @@
}, },
"entity": { "entity": {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"name": "Current Electricity Price" "name": "Current Electricity Price"
}, },
"next_interval_price": { "next_interval_price": {
@ -194,7 +194,7 @@
"next_hour_average": { "next_hour_average": {
"name": "Next Hour Average Price" "name": "Next Hour Average Price"
}, },
"price_level": { "current_interval_price_level": {
"name": "Current Price Level", "name": "Current Price Level",
"state": { "state": {
"very_cheap": "Very Cheap", "very_cheap": "Very Cheap",
@ -280,7 +280,7 @@
"leading_price_max": { "leading_price_max": {
"name": "Leading 24h Maximum Price" "name": "Leading 24h Maximum Price"
}, },
"price_rating": { "current_interval_price_rating": {
"name": "Current Price Rating", "name": "Current Price Rating",
"state": { "state": {
"low": "Low", "low": "Low",
@ -503,7 +503,7 @@
"very_high": "Very high" "very_high": "Very high"
} }
}, },
"price_level": { "current_interval_price_level": {
"options": { "options": {
"any": "Any", "any": "Any",
"very_cheap": "Very cheap", "very_cheap": "Very cheap",

View file

@ -88,7 +88,7 @@
}, },
"submit": "Neste til steg 2" "submit": "Neste til steg 2"
}, },
"price_rating": { "current_interval_price_rating": {
"title": "Prisvurderingsterskler", "title": "Prisvurderingsterskler",
"description": "{step_progress}\n\nKonfigurer terskler for prisvurderingsnivåer (lav/normal/høy) basert på sammenligning med 24-timers glidende gjennomsnitt.", "description": "{step_progress}\n\nKonfigurer terskler for prisvurderingsnivåer (lav/normal/høy) basert på sammenligning med 24-timers glidende gjennomsnitt.",
"data": { "data": {
@ -179,7 +179,7 @@
}, },
"entity": { "entity": {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"name": "Nåværende strømpris" "name": "Nåværende strømpris"
}, },
"next_interval_price": { "next_interval_price": {
@ -194,7 +194,7 @@
"next_hour_average": { "next_hour_average": {
"name": "Neste timepris gjennomsnitt" "name": "Neste timepris gjennomsnitt"
}, },
"price_level": { "current_interval_price_level": {
"name": "Nåværende prisnivå", "name": "Nåværende prisnivå",
"state": { "state": {
"very_cheap": "Veldig billig", "very_cheap": "Veldig billig",
@ -280,7 +280,7 @@
"leading_price_max": { "leading_price_max": {
"name": "Fremtidig 24t maksimumspris" "name": "Fremtidig 24t maksimumspris"
}, },
"price_rating": { "current_interval_price_rating": {
"name": "Nåværende prisvurdering", "name": "Nåværende prisvurdering",
"state": { "state": {
"low": "Lav", "low": "Lav",
@ -503,7 +503,7 @@
"very_high": "Svært høy" "very_high": "Svært høy"
} }
}, },
"price_level": { "current_interval_price_level": {
"options": { "options": {
"any": "Alle", "any": "Alle",
"very_cheap": "Svært billig", "very_cheap": "Svært billig",

View file

@ -88,7 +88,7 @@
}, },
"submit": "Volgende naar stap 2" "submit": "Volgende naar stap 2"
}, },
"price_rating": { "current_interval_price_rating": {
"title": "Prijsbeoordelingsdrempels", "title": "Prijsbeoordelingsdrempels",
"description": "{step_progress}\n\nConfigureer drempels voor prijsbeoordelingsniveaus (laag/normaal/hoog) op basis van vergelijking met het voortschrijdend 24-uurs gemiddelde.", "description": "{step_progress}\n\nConfigureer drempels voor prijsbeoordelingsniveaus (laag/normaal/hoog) op basis van vergelijking met het voortschrijdend 24-uurs gemiddelde.",
"data": { "data": {
@ -179,7 +179,7 @@
}, },
"entity": { "entity": {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"name": "Huidige elektriciteitsprijs" "name": "Huidige elektriciteitsprijs"
}, },
"next_interval_price": { "next_interval_price": {
@ -194,7 +194,7 @@
"next_hour_average": { "next_hour_average": {
"name": "Volgend uurgemiddelde prijs" "name": "Volgend uurgemiddelde prijs"
}, },
"price_level": { "current_interval_price_level": {
"name": "Huidig prijsniveau", "name": "Huidig prijsniveau",
"state": { "state": {
"very_cheap": "Zeer goedkoop", "very_cheap": "Zeer goedkoop",
@ -280,7 +280,7 @@
"leading_price_max": { "leading_price_max": {
"name": "Vooruitlopend 24u maximumprijs" "name": "Vooruitlopend 24u maximumprijs"
}, },
"price_rating": { "current_interval_price_rating": {
"name": "Huidige prijsbeoordeling", "name": "Huidige prijsbeoordeling",
"state": { "state": {
"low": "Laag", "low": "Laag",
@ -503,7 +503,7 @@
"very_high": "Zeer hoog" "very_high": "Zeer hoog"
} }
}, },
"price_level": { "current_interval_price_level": {
"options": { "options": {
"any": "Alle", "any": "Alle",
"very_cheap": "Zeer goedkoop", "very_cheap": "Zeer goedkoop",

View file

@ -88,7 +88,7 @@
}, },
"submit": "Nästa till steg 2" "submit": "Nästa till steg 2"
}, },
"price_rating": { "current_interval_price_rating": {
"title": "Prisvärderingströsklar", "title": "Prisvärderingströsklar",
"description": "{step_progress}\n\nKonfigurera trösklar för prisvärderingsnivåer (låg/normal/hög) baserat på jämförelse med rullande 24-timmars genomsnitt.", "description": "{step_progress}\n\nKonfigurera trösklar för prisvärderingsnivåer (låg/normal/hög) baserat på jämförelse med rullande 24-timmars genomsnitt.",
"data": { "data": {
@ -179,7 +179,7 @@
}, },
"entity": { "entity": {
"sensor": { "sensor": {
"current_price": { "current_interval_price": {
"name": "Nuvarande elpris" "name": "Nuvarande elpris"
}, },
"next_interval_price": { "next_interval_price": {
@ -194,7 +194,7 @@
"next_hour_average": { "next_hour_average": {
"name": "Nästa timgenomsnitt pris" "name": "Nästa timgenomsnitt pris"
}, },
"price_level": { "current_interval_price_level": {
"name": "Nuvarande prisnivå", "name": "Nuvarande prisnivå",
"state": { "state": {
"very_cheap": "Mycket billigt", "very_cheap": "Mycket billigt",
@ -280,7 +280,7 @@
"leading_price_max": { "leading_price_max": {
"name": "Framåtblickande 24t maximumpris" "name": "Framåtblickande 24t maximumpris"
}, },
"price_rating": { "current_interval_price_rating": {
"name": "Nuvarande prisvärdering", "name": "Nuvarande prisvärdering",
"state": { "state": {
"low": "Låg", "low": "Låg",
@ -503,7 +503,7 @@
"very_high": "Mycket hög" "very_high": "Mycket hög"
} }
}, },
"price_level": { "current_interval_price_level": {
"options": { "options": {
"any": "Alla", "any": "Alla",
"very_cheap": "Mycket billigt", "very_cheap": "Mycket billigt",