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

66
app/core/decision.py Normal file
View File

@@ -0,0 +1,66 @@
from __future__ import annotations
from app.core.models import ControlProfile, Decision, LearningProfile, RuntimeState, SafetyStage
SAFE_DOMAINS = frozenset({"light", "switch", "fan", "cover", "humidifier"})
class DecisionEngineV2:
def decide(
self,
*,
actuator_entity_id: str,
trigger_entity_id: str,
runtime: RuntimeState,
learning: LearningProfile,
control: ControlProfile,
) -> Decision:
blockers: list[str] = []
actuator = runtime.entities.get(actuator_entity_id)
trigger = runtime.entities.get(trigger_entity_id)
if actuator is None:
blockers.append("Aktor ist im Runtime-State nicht bekannt.")
if trigger is None:
blockers.append("Trigger ist im Runtime-State nicht bekannt.")
domain = actuator_entity_id.split(".", 1)[0]
if domain not in SAFE_DOMAINS:
blockers.append(f"Domain {domain} ist nicht fuer Active-Control freigegeben.")
if control.manual_block:
blockers.append("Manuelle Sicherheitssperre ist aktiv.")
best = None
for pattern in learning.patterns:
if pattern.trigger_entity_id != trigger_entity_id:
continue
if trigger is not None and pattern.trigger_state != trigger.state:
continue
if best is None or pattern.confidence > best.confidence:
best = pattern
if best is None:
blockers.append("Kein passendes kausales Muster gefunden.")
return Decision(
actuator_entity_id=actuator_entity_id,
allowed=False,
reason="Keine Aktion faellig.",
blockers=blockers,
trigger_entity_id=trigger_entity_id,
)
if best.confidence < control.min_confidence:
blockers.append(
f"Confidence {best.confidence:.0%} liegt unter {control.min_confidence:.0%}."
)
if actuator is not None and actuator.state == best.target_state:
blockers.append("Zielzustand ist bereits erreicht.")
dry_run = control.stage is SafetyStage.DRY_RUN
allowed = not blockers and control.stage in {SafetyStage.ACTIVE, SafetyStage.DRY_RUN}
return Decision(
actuator_entity_id=actuator_entity_id,
target_state=best.target_state,
confidence=best.confidence,
allowed=allowed,
executed=False,
dry_run=dry_run,
reason="Ausfuehrung freigegeben." if allowed and not dry_run else "Dry-run oder blockiert.",
blockers=blockers,
trigger_entity_id=trigger_entity_id,
)