mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 13:23:41 +00:00
Implementation flaw discovered: gradient_stop calculated as `(avg - min) / (max - min)` for combined data produces one value applied to ALL series. Each series (VERY_CHEAP, NORMAL, VERY_EXPENSIVE) has different min/max ranges, so the same gradient stop position represents a different absolute price in each series. Example failure case: - VERY_CHEAP: 10-20 ct → 50% at 15 ct (below overall avg!) - VERY_EXPENSIVE: 40-50 ct → 50% at 45 ct (above overall avg!) Conclusion: Gradient shows middle of each series range, not average price position. Solution: Fixed 50% gradient purely for visual appeal. Semantic information provided by: - Series colors (CHEAP/NORMAL/EXPENSIVE) - Grid lines (implicitly show average) - Dynamic Y-axis bounds (optimal scaling via chart_metadata sensor) Changes: - sensor/chart_metadata.py: Remove gradient_stop extraction - services/get_apexcharts_yaml.py: Fixed gradient at [50, 100] - custom_translations/*.json: Remove gradient_stop references Impact: Honest visualization with no false semantic signals. Feature was never released, clean removal without migration.
142 lines
4.6 KiB
Python
142 lines
4.6 KiB
Python
"""Chart metadata export functionality for Tibber Prices sensors."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from custom_components.tibber_prices.const import DATA_CHART_METADATA_CONFIG, DOMAIN
|
|
|
|
if TYPE_CHECKING:
|
|
from datetime import datetime
|
|
|
|
from custom_components.tibber_prices.coordinator import TibberPricesDataUpdateCoordinator
|
|
from custom_components.tibber_prices.data import TibberPricesConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
|
|
async def call_chartdata_service_for_metadata_async(
|
|
hass: HomeAssistant,
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
config_entry: TibberPricesConfigEntry,
|
|
) -> tuple[dict | None, str | None]:
|
|
"""
|
|
Call get_chartdata service with configuration from configuration.yaml for metadata (async).
|
|
|
|
Returns:
|
|
Tuple of (response, error_message).
|
|
If successful: (response_dict, None)
|
|
If failed: (None, error_string)
|
|
|
|
"""
|
|
# Get configuration from hass.data (loaded from configuration.yaml)
|
|
domain_data = hass.data.get(DOMAIN, {})
|
|
chart_metadata_config = domain_data.get(DATA_CHART_METADATA_CONFIG, {})
|
|
|
|
# Use chart_metadata_config directly (already a dict from async_setup)
|
|
service_params = dict(chart_metadata_config) if chart_metadata_config else {}
|
|
|
|
# Add required entry_id parameter
|
|
service_params["entry_id"] = config_entry.entry_id
|
|
|
|
# Force metadata to "only" - this sensor ONLY provides metadata
|
|
service_params["metadata"] = "only"
|
|
|
|
# Default to minor_currency=True for ApexCharts compatibility (can be overridden in configuration.yaml)
|
|
if "minor_currency" not in service_params:
|
|
service_params["minor_currency"] = True
|
|
|
|
# Call get_chartdata service using official HA service system
|
|
try:
|
|
response = await hass.services.async_call(
|
|
DOMAIN,
|
|
"get_chartdata",
|
|
service_params,
|
|
blocking=True,
|
|
return_response=True,
|
|
)
|
|
except Exception as ex:
|
|
coordinator.logger.exception("Chart metadata service call failed")
|
|
return None, str(ex)
|
|
else:
|
|
return response, None
|
|
|
|
|
|
def get_chart_metadata_state(
|
|
chart_metadata_response: dict | None,
|
|
chart_metadata_error: str | None,
|
|
) -> str | None:
|
|
"""
|
|
Return state for chart_metadata sensor.
|
|
|
|
Args:
|
|
chart_metadata_response: Last service response (or None)
|
|
chart_metadata_error: Last error message (or None)
|
|
|
|
Returns:
|
|
"error" if error occurred
|
|
"ready" if metadata available
|
|
"pending" if no data yet
|
|
|
|
"""
|
|
if chart_metadata_error:
|
|
return "error"
|
|
if chart_metadata_response:
|
|
return "ready"
|
|
return "pending"
|
|
|
|
|
|
def build_chart_metadata_attributes(
|
|
chart_metadata_response: dict | None,
|
|
chart_metadata_last_update: datetime | None,
|
|
chart_metadata_error: str | None,
|
|
) -> dict[str, object] | None:
|
|
"""
|
|
Return chart metadata from last service call as attributes.
|
|
|
|
Attribute order: timestamp, error (if any), metadata fields (at the end).
|
|
|
|
Args:
|
|
chart_metadata_response: Last service response (should contain "metadata" key)
|
|
chart_metadata_last_update: Timestamp of last update
|
|
chart_metadata_error: Error message if service call failed
|
|
|
|
Returns:
|
|
Dict with timestamp, optional error, and metadata fields.
|
|
|
|
"""
|
|
# Build base attributes with timestamp FIRST
|
|
attributes: dict[str, object] = {
|
|
"timestamp": chart_metadata_last_update,
|
|
}
|
|
|
|
# Add error message if service call failed
|
|
if chart_metadata_error:
|
|
attributes["error"] = chart_metadata_error
|
|
|
|
if not chart_metadata_response:
|
|
# No data - only timestamp (and error if present)
|
|
return attributes
|
|
|
|
# Extract metadata from response (get_chartdata returns {"metadata": {...}})
|
|
metadata = chart_metadata_response.get("metadata", {})
|
|
|
|
# Extract the fields we care about for charts
|
|
# These are the universal chart metadata fields useful for any chart card
|
|
if metadata:
|
|
yaxis_suggested = metadata.get("yaxis_suggested", {})
|
|
|
|
# Add yaxis bounds (useful for all chart cards)
|
|
if "min" in yaxis_suggested:
|
|
attributes["yaxis_min"] = yaxis_suggested["min"]
|
|
if "max" in yaxis_suggested:
|
|
attributes["yaxis_max"] = yaxis_suggested["max"]
|
|
|
|
# Add currency info (useful for labeling)
|
|
if "currency" in metadata:
|
|
attributes["currency"] = metadata["currency"]
|
|
|
|
# Add resolution info (interval duration in minutes)
|
|
if "resolution" in metadata:
|
|
attributes["resolution"] = metadata["resolution"]
|
|
|
|
return attributes
|