Bootstrap SillyHome Future v2 core

This commit is contained in:
2026-06-18 12:16:54 +02:00
commit 549f87523a
15 changed files with 729 additions and 0 deletions

130
app/core/models.py Normal file
View File

@@ -0,0 +1,130 @@
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 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)
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 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)
class ControlState(BaseModel):
profiles: dict[str, ControlProfile] = Field(default_factory=dict)
class BackupBundle(BaseModel):
exported_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
runtime: RuntimeState
learning: LearningState
control: ControlState