Commit graph

6 commits

Author SHA1 Message Date
Julian Pawlowski
51a99980df feat(sensors)!: add configurable median/mean display for average sensors
Add user-configurable option to choose between median and arithmetic mean
as the displayed value for all 14 average price sensors, with the alternate
value exposed as attribute.

BREAKING CHANGE: Average sensor default changed from arithmetic mean to
median. Users who rely on arithmetic mean behavior may use the price_mean attribue now, or must manually reconfigure
via Settings → Devices & Services → Tibber Prices → Configure → General
Settings → "Average Sensor Display" → Select "Arithmetic Mean" to get this as sensor state.

Affected sensors (14 total):
- Daily averages: average_price_today, average_price_tomorrow
- 24h windows: trailing_price_average, leading_price_average
- Rolling hour: current_hour_average_price, next_hour_average_price
- Future forecasts: next_avg_3h, next_avg_6h, next_avg_9h, next_avg_12h

Implementation:
- All average calculators now return (mean, median) tuples
- User preference controls which value appears in sensor state
- Alternate value automatically added to attributes
- Period statistics (best_price/peak_price) extended with both values

Technical changes:
- New config option: CONF_AVERAGE_SENSOR_DISPLAY (default: "median")
- Calculator functions return tuples: (avg, median)
- Attribute builders: add_alternate_average_attribute() helper function
- Period statistics: price_avg → price_mean + price_median
- Translations: Updated all 5 languages (de, en, nb, nl, sv)
- Documentation: AGENTS.md, period-calculation.md, recorder-optimization.md

Migration path:
Users can switch back to arithmetic mean via:
Settings → Integrations → Tibber Prices → Configure
→ General Settings → "Average Sensor Display" → "Arithmetic Mean"

Impact: Median is more resistant to price spikes, providing more stable
automation triggers. Statistical analysis from coordinator still uses
arithmetic mean (e.g., trailing_avg_24h for rating calculations).

Co-developed-with: GitHub Copilot <copilot@github.com>
2025-12-08 17:53:40 +00:00
Julian Pawlowski
981fb08a69 refactor(price_info): price data handling to use unified interval retrieval
- Introduced `get_intervals_for_day_offsets` helper to streamline access to price intervals for yesterday, today, and tomorrow.
- Updated various components to replace direct access to `priceInfo` with the new helper, ensuring a flat structure for price intervals.
- Adjusted calculations and data processing methods to accommodate the new data structure.
- Enhanced documentation to reflect changes in caching strategy and data structure.
2025-11-24 10:49:34 +00:00
Julian Pawlowski
189d3ba84d feat(sensor): add data lifecycle diagnostic sensor with push updates
Add comprehensive data_lifecycle_status sensor showing real-time cache
vs fresh API data status with 6 states and 13+ detailed attributes.

Key features:
- 6 lifecycle states: cached, fresh, refreshing, searching_tomorrow,
  turnover_pending, error
- Push-update system for instant state changes (refreshing→fresh→error)
- Quarter-hour polling for turnover_pending detection at 23:45
- Accurate next_api_poll prediction using Timer #1 offset tracking
- Tomorrow prediction with actual timer schedule (not fixed 13:00)
- 13+ formatted attributes: cache_age, data_completeness, api_calls_today,
  next_api_poll, etc.

Implementation:
- sensor/calculators/lifecycle.py: New calculator with state logic
- sensor/attributes/lifecycle.py: Attribute builders with formatting
- coordinator/core.py: Lifecycle tracking + callback system (+16 lines)
- sensor/core.py: Push callback registration (+3 lines)
- coordinator/constants.py: Added to TIME_SENSITIVE_ENTITY_KEYS
- Translations: All 5 languages (de, en, nb, nl, sv)

Timing optimization:
- Extended turnover warning: 5min → 15min (catches 23:45 quarter boundary)
- No minute-timer needed: quarter-hour updates + push = optimal
- Push-updates: <1sec latency for refreshing/fresh/error states
- Timer offset tracking: Accurate tomorrow predictions

Removed obsolete sensors:
- data_timestamp (replaced by lifecycle attributes)
- price_forecast (never implemented, removed from definitions)

Impact: Users can monitor data freshness, API call patterns, cache age,
and understand integration behavior. Perfect for troubleshooting and
visibility into when data updates occur.
2025-11-20 15:12:41 +00:00
Julian Pawlowski
c2b9908e69 refactor(naming): complete class naming convention alignment
Renamed 25 public classes + 1 Enum to include TibberPrices prefix
following Home Assistant integration naming standards.

All classes now follow pattern: TibberPrices{SemanticPurpose}
No package hierarchy in names (import path is namespace).

Key changes:
- Coordinator module: DataFetcher, DataTransformer, ListenerManager,
  PeriodCalculator, TimeService (203 usages), CacheData
- Config flow: CannotConnectError, InvalidAuthError
- Entity utils: IconContext
- Sensor calculators: BaseCalculator + 8 subclasses
- Period handlers: 5 NamedTuples (PeriodConfig, PeriodData,
  PeriodStatistics, ThresholdConfig, IntervalCriteria)
- Period handlers: SpikeCandidateContext (dataclass → NamedTuple)
- API: QueryType Enum

Documentation updates:
- AGENTS.md: Added Pyright code generation guidelines
- planning/class-naming-refactoring-plan.md: Complete execution log

Quality metrics:
- 0 Pyright errors (strict type checking)
- 0 Ruff errors (linting + formatting)
- All hassfest checks passed
- 79 files validated

Impact: Aligns with HA Core standards (TibberDataCoordinator pattern).
No user-facing changes - internal refactor only.
2025-11-20 11:22:53 +00:00
Julian Pawlowski
625bc222ca refactor(coordinator): centralize time operations through TimeService
Introduce TimeService as single source of truth for all datetime operations,
replacing direct dt_util calls throughout the codebase. This establishes
consistent time context across update cycles and enables future time-travel
testing capability.

Core changes:
- NEW: coordinator/time_service.py with timezone-aware datetime API
- Coordinator now creates TimeService per update cycle, passes to calculators
- Timer callbacks (#2, #3) inject TimeService into entity update flow
- All sensor calculators receive TimeService via coordinator reference
- Attribute builders accept time parameter for timestamp calculations

Key patterns replaced:
- dt_util.now() → time.now() (single reference time per cycle)
- dt_util.parse_datetime() + as_local() → time.get_interval_time()
- Manual interval arithmetic → time.get_interval_offset_time()
- Manual day boundaries → time.get_day_boundaries()
- round_to_nearest_quarter_hour() → time.round_to_nearest_quarter()

Import cleanup:
- Removed dt_util imports from ~30 files (calculators, attributes, utils)
- Restricted dt_util to 3 modules: time_service.py (operations), api/client.py
  (rate limiting), entity_utils/icons.py (cosmetic updates)
- datetime/timedelta only for TYPE_CHECKING (type hints) or duration arithmetic

Interval resolution abstraction:
- Removed hardcoded MINUTES_PER_INTERVAL constant from 10+ files
- New methods: time.minutes_to_intervals(), time.get_interval_duration()
- Supports future 60-minute resolution (legacy data) via TimeService config

Timezone correctness:
- API timestamps (startsAt) already localized by data transformation
- TimeService operations preserve HA user timezone throughout
- DST transitions handled via get_expected_intervals_for_day() (future use)

Timestamp ordering preserved:
- Attribute builders generate default timestamp (rounded quarter)
- Sensors override when needed (next interval, daily midnight, etc.)
- Platform ensures timestamp stays FIRST in attribute dict

Timer integration:
- Timer #2 (quarter-hour): Creates TimeService, calls _handle_time_sensitive_update(time)
- Timer #3 (30-second): Creates TimeService, calls _handle_minute_update(time)
- Consistent time reference for all entities in same update batch

Time-travel readiness:
- TimeService.with_reference_time() enables time injection (not yet used)
- All calculations use time.now() → easy to simulate past/future states
- Foundation for debugging period calculations with historical data

Impact: Eliminates timestamp drift within update cycles (previously 60+ independent
dt_util.now() calls could differ by milliseconds). Establishes architecture for
time-based testing and debugging features.
2025-11-19 18:36:12 +00:00
Julian Pawlowski
a962289682 refactor(sensor): implement Calculator Pattern with specialized modules
Massive refactoring of sensor platform reducing core.py from 2,170 to 909
lines (58% reduction). Extracted business logic into specialized calculators
and attribute builders following separation of concerns principles.

Changes:
- Created sensor/calculators/ package (8 specialized calculators, 1,838 lines):
  * base.py: Abstract BaseCalculator with coordinator access
  * interval.py: Single interval calculations (current/next/previous)
  * rolling_hour.py: 5-interval rolling windows
  * daily_stat.py: Calendar day min/max/avg statistics
  * window_24h.py: Trailing/leading 24h windows
  * volatility.py: Price volatility analysis
  * trend.py: Complex trend analysis with caching (640 lines)
  * timing.py: Best/peak price period timing
  * metadata.py: Home/metering metadata

- Created sensor/attributes/ package (8 specialized modules, 1,209 lines):
  * Modules match calculator types for consistent organization
  * __init__.py: Routing logic + unified builders
  * Handles state presentation separately from business logic

- Created sensor/chart_data.py (144 lines):
  * Extracted chart data export functionality from entity class
  * YAML parsing, service calls, metadata formatting

- Created sensor/value_getters.py (276 lines):
  * Centralized handler mapping for all 80+ sensor types
  * Single source of truth for sensor routing

- Extended sensor/helpers.py (+88 lines):
  * Added aggregate_window_data() unified aggregator
  * Added get_hourly_price_value() for backward compatibility
  * Consolidated sensor-specific helper functions

- Refactored sensor/core.py (909 lines, was 2,170):
  * Instantiates all calculators in __init__
  * Delegates value calculations to appropriate calculator
  * Uses unified handler methods via value_getters mapping
  * Minimal platform-specific logic remains (icon callbacks, entity lifecycle)

- Deleted sensor/attributes.py (1,106 lines):
  * Functionality split into attributes/ package (8 modules)

- Updated AGENTS.md:
  * Documented Calculator Pattern architecture
  * Added guidance for adding new sensors with calculation groups
  * Updated file organization with new package structure

Architecture Benefits:
- Clear separation: Calculators (business logic) vs Attributes (presentation)
- Improved testability: Each calculator independently testable
- Better maintainability: 21 focused modules vs monolithic file
- Easy extensibility: Add sensors by choosing calculation pattern
- Reusable components: Calculators and attribute builders shared across sensors

Impact: Significantly improved code organization and maintainability while
preserving all functionality. All 80+ sensor types continue working with
cleaner, more modular architecture. Developer experience improved with
logical file structure and clear separation of concerns.
2025-11-18 21:25:55 +00:00