68 lines
2.8 KiB
Python
68 lines
2.8 KiB
Python
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.")
|
|
if control.stage is SafetyStage.ACTIVE and not control.active_ready:
|
|
blockers.append("Active ist noch nicht freigegeben. Erst Dry-run-Reife erreichen.")
|
|
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,
|
|
)
|