from __future__ import annotations from dataclasses import dataclass, field from datetime import date, datetime from typing import Any, Literal MetricKind = Literal["gauge", "counter", "text"] @dataclass(frozen=True) class MetricDefinition: id: str entity_id: str measurement: str unit: str = "" label: str = "" kind: MetricKind = "gauge" precision: int = 2 enabled: bool = True @dataclass class MetricValue: metric_id: str label: str unit: str = "" value: float | str | None = None timestamp: datetime | None = None precision: int = 2 kind: MetricKind = "gauge" status: str = "neutral" @dataclass class SeriesPoint: timestamp: datetime value: float | None = None @dataclass class SeriesPayload: metric_id: str label: str unit: str points: list[SeriesPoint] = field(default_factory=list) @dataclass class SnapshotGroupRow: id: str label: str values: dict[str, MetricValue] = field(default_factory=dict) meta: dict[str, Any] = field(default_factory=dict) @dataclass class HeroCard: metric_id: str label: str value: float | str | None = None unit: str = "" accent: str = "neutral" subtitle: str = "" @dataclass class SnapshotPayload: updated_at: datetime | None = None hero_cards: list[HeroCard] = field(default_factory=list) kpis: dict[str, MetricValue] = field(default_factory=dict) strings: list[SnapshotGroupRow] = field(default_factory=list) phases: list[SnapshotGroupRow] = field(default_factory=list) status: list[MetricValue] = field(default_factory=list) faults: list[str] = field(default_factory=list) @dataclass class BucketPoint: label: str start: datetime end: datetime value: float | None @dataclass class AnalyticsSummary: total: float = 0.0 unit: str = "kWh" average_bucket: float = 0.0 best_bucket_label: str = "" best_bucket_value: float = 0.0 co2_saved_kg: float = 0.0 comparison_total: float | None = None comparison_delta_pct: float | None = None @dataclass class DailyEnergyRecord: day: date energy_kwh: float source: str samples_count: int imported_at: datetime | None = None @dataclass class HistoricalCoverage: imported_days: int = 0 first_day: date | None = None last_day: date | None = None total_energy_kwh: float = 0.0 available_days: int = 0 missing_days: int = 0 coverage_pct: float | None = None @dataclass class HistoricalChunkProgress: chunk_index: int total_chunks: int start_date: date end_date: date processed_days: int = 0 imported_days: int = 0 skipped_days: int = 0 energy_kwh: float = 0.0 state: str = "pending" started_at: datetime | None = None finished_at: datetime | None = None duration_seconds: float | None = None note: str = "" @dataclass class HistoricalActivityEvent: timestamp: datetime level: str = "info" title: str = "" message: str = "" day: date | None = None chunk_index: int | None = None @dataclass class HistoricalImportStatus: enabled: bool = True running: bool = False state: str = "idle" job_id: str | None = None started_at: datetime | None = None finished_at: datetime | None = None requested_start_date: date | None = None requested_end_date: date | None = None total_days: int = 0 processed_days: int = 0 imported_days: int = 0 skipped_days: int = 0 chunk_days: int = 1 total_chunks: int = 0 active_chunk_index: int = 0 current_date: date | None = None current_chunk_start: date | None = None current_chunk_end: date | None = None elapsed_seconds: float | None = None estimated_remaining_seconds: float | None = None avg_days_per_minute: float | None = None last_error: str | None = None message: str = "" coverage: HistoricalCoverage = field(default_factory=HistoricalCoverage) available_start_date: date | None = None available_end_date: date | None = None default_chunk_days: int = 1 recent_chunks: list[HistoricalChunkProgress] = field(default_factory=list) recent_events: list[HistoricalActivityEvent] = field(default_factory=list)