170 lines
5.8 KiB
Python
170 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from enum import StrEnum
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.ha.discovery import EntityRole
|
|
|
|
|
|
class AssignmentSource(StrEnum):
|
|
NONE = "none"
|
|
AUTOMATIC = "automatic"
|
|
MANUAL = "manual"
|
|
|
|
|
|
class LifecycleStatus(StrEnum):
|
|
PENDING_ASSIGNMENT = "pending_assignment"
|
|
REVIEW_REQUIRED = "review_required"
|
|
PENDING_HISTORY = "pending_history"
|
|
TRAINED = "trained"
|
|
STALE = "stale"
|
|
INVALID = "invalid"
|
|
ORPHANED = "orphaned"
|
|
ARCHIVED = "archived"
|
|
|
|
|
|
class BehaviorMode(StrEnum):
|
|
SHADOW = "shadow"
|
|
ACTIVE = "active"
|
|
PAUSED = "paused"
|
|
|
|
|
|
class BehaviorStatus(StrEnum):
|
|
COLLECTING = "collecting"
|
|
TRAINED = "trained"
|
|
BLOCKED = "blocked"
|
|
|
|
|
|
class AssignmentCandidate(BaseModel):
|
|
entity_id: str
|
|
domain: str
|
|
role: EntityRole
|
|
device_class: str | None = None
|
|
state_class: str | None = None
|
|
unit_of_measurement: str | None = None
|
|
friendly_name: str | None = None
|
|
area_name: str | None = None
|
|
device_name: str | None = None
|
|
score: float = Field(ge=0.0)
|
|
confidence: float = Field(ge=0.0, le=1.0)
|
|
auto_accepted: bool = False
|
|
evidence: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class AssignmentSelection(BaseModel):
|
|
selected_numeric_entity_id: str | None = None
|
|
selected_context_entity_ids: list[str] = Field(default_factory=list)
|
|
source: AssignmentSource = AssignmentSource.NONE
|
|
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
review_required: bool = True
|
|
reason: str = "Noch keine Zuordnung vorhanden."
|
|
|
|
|
|
class ManualOverride(BaseModel):
|
|
numeric_entity_id: str | None = None
|
|
context_entity_ids: list[str] = Field(default_factory=list)
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
note: str | None = None
|
|
|
|
|
|
class LifecycleAuditEntry(BaseModel):
|
|
at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
action: str = Field(min_length=1, max_length=120)
|
|
reason: str = Field(min_length=1, max_length=500)
|
|
|
|
|
|
class ModelLifecycleState(BaseModel):
|
|
model_id: str
|
|
status: LifecycleStatus = LifecycleStatus.PENDING_ASSIGNMENT
|
|
last_reconciled_at: datetime | None = None
|
|
last_trained_at: datetime | None = None
|
|
last_history_signature: str | None = None
|
|
last_history_point_count: int = Field(default=0, ge=0)
|
|
reason: str = "Noch keine Trainingsdaten ausgewertet."
|
|
next_action: str = "Aktor auswählen; Kontext und Historie werden automatisch geprüft."
|
|
audit: list[LifecycleAuditEntry] = Field(default_factory=list)
|
|
|
|
|
|
class BehaviorPattern(BaseModel):
|
|
target_state: str = Field(min_length=1, max_length=100)
|
|
minute_of_day: int = Field(ge=0, le=1439)
|
|
weekday: int = Field(ge=0, le=6)
|
|
context_states: dict[str, str] = Field(default_factory=dict)
|
|
trigger_entity_id: str | None = None
|
|
trigger_from_state: str | None = None
|
|
trigger_to_state: str | None = None
|
|
source: str = Field(default="observed", max_length=40)
|
|
weight: float = Field(default=1.0, ge=0.1, le=1.0)
|
|
observed_at: datetime
|
|
|
|
|
|
class BehaviorPrediction(BaseModel):
|
|
target_state: str
|
|
confidence: float = Field(ge=0.0, le=1.0)
|
|
generated_at: datetime
|
|
reason: str
|
|
matching_patterns: int = Field(default=0, ge=0)
|
|
executed: bool = False
|
|
execution_reason: str = "Vorhersage wurde noch nicht ausgeführt."
|
|
|
|
|
|
class ExecutionEvent(BaseModel):
|
|
target_state: str
|
|
executed_at: datetime
|
|
|
|
|
|
class RelatedAutomation(BaseModel):
|
|
entity_id: str = Field(pattern=r"^automation\.[a-z0-9_]+$")
|
|
config_id: str = Field(min_length=1, max_length=120)
|
|
friendly_name: str = Field(min_length=1, max_length=200)
|
|
enabled: bool
|
|
|
|
|
|
class BehaviorState(BaseModel):
|
|
mode: BehaviorMode = BehaviorMode.SHADOW
|
|
status: BehaviorStatus = BehaviorStatus.COLLECTING
|
|
approved_at: datetime | None = None
|
|
sample_count: int = Field(default=0, ge=0)
|
|
high_confidence_sample_count: int = Field(default=0, ge=0)
|
|
patterns: list[BehaviorPattern] = Field(default_factory=list)
|
|
prediction: BehaviorPrediction | None = None
|
|
last_trained_at: datetime | None = None
|
|
last_evaluated_at: datetime | None = None
|
|
last_executed_at: datetime | None = None
|
|
execution_events: list[ExecutionEvent] = Field(default_factory=list)
|
|
activation_ready: bool = False
|
|
activation_reason: str = "Noch nicht genügend Verhalten für eine Freigabe gelernt."
|
|
related_automations: list[RelatedAutomation] = Field(default_factory=list)
|
|
paused_automation_entity_ids: list[str] = Field(default_factory=list)
|
|
reason: str = "Historische Aktorhandlungen werden analysiert."
|
|
|
|
|
|
class ActuatorRecord(BaseModel):
|
|
actuator_entity_id: str = Field(pattern=r"^[a-z0-9_]+\.[a-z0-9_]+$")
|
|
enabled: bool = True
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
assignment: AssignmentSelection = Field(default_factory=AssignmentSelection)
|
|
manual_override: ManualOverride | None = None
|
|
numeric_candidates: list[AssignmentCandidate] = Field(default_factory=list)
|
|
context_candidates: list[AssignmentCandidate] = Field(default_factory=list)
|
|
lifecycle: ModelLifecycleState
|
|
behavior: BehaviorState = Field(default_factory=BehaviorState)
|
|
|
|
|
|
class ReconciliationState(BaseModel):
|
|
last_started_at: datetime | None = None
|
|
last_completed_at: datetime | None = None
|
|
last_trigger: str | None = None
|
|
running: bool = False
|
|
configured_actuators: int = Field(default=0, ge=0)
|
|
review_required: int = Field(default=0, ge=0)
|
|
trained_models: int = Field(default=0, ge=0)
|
|
last_summary: str = "Noch keine Reconciliation ausgeführt."
|
|
|
|
|
|
def model_id_for_actuator(actuator_entity_id: str) -> str:
|
|
return f"actuator.{actuator_entity_id}"
|