214 lines
6.8 KiB
Python
214 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from enum import StrEnum
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class HandoffMode(StrEnum):
|
|
SHADOW = "shadow"
|
|
CANDIDATE = "candidate"
|
|
CONTROLLED = "controlled"
|
|
CONFLICT = "conflict"
|
|
ROLLBACK = "rollback"
|
|
|
|
|
|
class SafetyStage(StrEnum):
|
|
OBSERVE = "observe"
|
|
DRY_RUN = "dry_run"
|
|
ACTIVE = "active"
|
|
BLOCKED = "blocked"
|
|
|
|
|
|
class ProposalStatus(StrEnum):
|
|
DRAFT = "draft"
|
|
APPROVED = "approved"
|
|
REJECTED = "rejected"
|
|
|
|
|
|
class JobStatus(StrEnum):
|
|
QUEUED = "queued"
|
|
RUNNING = "running"
|
|
SUCCEEDED = "succeeded"
|
|
FAILED = "failed"
|
|
|
|
|
|
class EntityState(BaseModel):
|
|
entity_id: str = Field(pattern=r"^[a-z0-9_]+\.[a-z0-9_]+$")
|
|
domain: str
|
|
state: str | None = None
|
|
changed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
area_name: str | None = None
|
|
device_id: str | None = None
|
|
friendly_name: str | None = None
|
|
|
|
|
|
class StateEvent(BaseModel):
|
|
entity_id: str = Field(pattern=r"^[a-z0-9_]+\.[a-z0-9_]+$")
|
|
new_state: str | None = None
|
|
changed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
attributes: dict[str, str] = Field(default_factory=dict)
|
|
|
|
|
|
class BehaviorPatternV2(BaseModel):
|
|
actuator_entity_id: str
|
|
target_state: str
|
|
trigger_entity_id: str | None = None
|
|
trigger_state: str | None = None
|
|
context: dict[str, str] = Field(default_factory=dict)
|
|
support: int = Field(default=1, ge=1)
|
|
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
source: str = Field(default="observed", max_length=40)
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class LearningProfile(BaseModel):
|
|
actuator_entity_id: str
|
|
patterns: list[BehaviorPatternV2] = Field(default_factory=list)
|
|
feedback_positive: int = Field(default=0, ge=0)
|
|
feedback_negative: int = Field(default=0, ge=0)
|
|
model_version: str = "empty"
|
|
|
|
|
|
class ControlProfile(BaseModel):
|
|
actuator_entity_id: str
|
|
stage: SafetyStage = SafetyStage.OBSERVE
|
|
min_confidence: float = Field(default=0.82, ge=0.0, le=1.0)
|
|
manual_block: bool = False
|
|
cooldown_seconds: int = Field(default=900, ge=0)
|
|
handoff_mode: HandoffMode = HandoffMode.SHADOW
|
|
related_automation_ids: list[str] = Field(default_factory=list)
|
|
paused_automation_ids: list[str] = Field(default_factory=list)
|
|
dry_run_events: int = Field(default=0, ge=0)
|
|
dry_run_successes: int = Field(default=0, ge=0)
|
|
dry_run_failures: int = Field(default=0, ge=0)
|
|
active_ready: bool = False
|
|
active_readiness_reason: str = "Noch nicht bewertet."
|
|
|
|
|
|
class RoomProfile(BaseModel):
|
|
room_id: str = Field(pattern=r"^[a-z0-9_-]{1,64}$")
|
|
name: str
|
|
actuator_entity_ids: list[str] = Field(default_factory=list)
|
|
context_entity_ids: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class SceneProfile(BaseModel):
|
|
scene_id: str = Field(pattern=r"^[a-z0-9_-]{1,64}$")
|
|
name: str
|
|
actuator_entity_ids: list[str] = Field(default_factory=list)
|
|
trigger_entity_id: str | None = None
|
|
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
|
|
|
|
class AutomationProposal(BaseModel):
|
|
proposal_id: str
|
|
name: str
|
|
trigger_entity_id: str
|
|
trigger_state: str | None = None
|
|
actuator_entity_id: str
|
|
target_state: str
|
|
status: ProposalStatus = ProposalStatus.DRAFT
|
|
revision: int = 1
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
decided_at: datetime | None = None
|
|
|
|
|
|
class ModelRecord(BaseModel):
|
|
model_id: str
|
|
actuator_entity_id: str
|
|
pattern_count: int
|
|
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
trained_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class ModelEvaluation(BaseModel):
|
|
evaluation_id: str
|
|
model_id: str
|
|
actuator_entity_id: str
|
|
score: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
coverage: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
dry_run_success_rate: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
feedback_score: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
verdict: str
|
|
reasons: list[str] = Field(default_factory=list)
|
|
evaluated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class JobQueueItem(BaseModel):
|
|
job_id: str
|
|
kind: str
|
|
status: JobStatus = JobStatus.QUEUED
|
|
message: str = ""
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
finished_at: datetime | None = None
|
|
|
|
|
|
class SensorWeightOverride(BaseModel):
|
|
actuator_entity_id: str
|
|
sensor_weights: dict[str, float] = Field(default_factory=dict)
|
|
note: str | None = Field(default=None, max_length=500)
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class HistoryAnalysis(BaseModel):
|
|
entity_id: str
|
|
samples: int
|
|
last_state: str | None = None
|
|
changed_at: datetime | None = None
|
|
unique_states: int = 0
|
|
transitions: int = 0
|
|
recommendation: str
|
|
|
|
|
|
class Decision(BaseModel):
|
|
actuator_entity_id: str
|
|
target_state: str | None = None
|
|
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
allowed: bool = False
|
|
executed: bool = False
|
|
dry_run: bool = False
|
|
reason: str
|
|
blockers: list[str] = Field(default_factory=list)
|
|
trigger_entity_id: str | None = None
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class AuditEvent(BaseModel):
|
|
event_id: str
|
|
kind: str = Field(max_length=40)
|
|
entity_id: str | None = None
|
|
message: str = Field(max_length=700)
|
|
decision: Decision | None = None
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class RuntimeState(BaseModel):
|
|
entities: dict[str, EntityState] = Field(default_factory=dict)
|
|
audit: list[AuditEvent] = Field(default_factory=list)
|
|
websocket_status: str = "unavailable"
|
|
|
|
|
|
class LearningState(BaseModel):
|
|
profiles: dict[str, LearningProfile] = Field(default_factory=dict)
|
|
rooms: dict[str, RoomProfile] = Field(default_factory=dict)
|
|
scenes: dict[str, SceneProfile] = Field(default_factory=dict)
|
|
automation_proposals: dict[str, AutomationProposal] = Field(default_factory=dict)
|
|
models: dict[str, ModelRecord] = Field(default_factory=dict)
|
|
model_evaluations: dict[str, ModelEvaluation] = Field(default_factory=dict)
|
|
jobs: dict[str, JobQueueItem] = Field(default_factory=dict)
|
|
weight_overrides: dict[str, SensorWeightOverride] = Field(default_factory=dict)
|
|
|
|
|
|
class ControlState(BaseModel):
|
|
profiles: dict[str, ControlProfile] = Field(default_factory=dict)
|
|
global_enabled: bool = True
|
|
|
|
|
|
class BackupBundle(BaseModel):
|
|
exported_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
runtime: RuntimeState
|
|
learning: LearningState
|
|
control: ControlState
|