UI-002: align context and activation status
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.5.4 - 2026-06-14
|
||||||
|
- Tür-, Bewegungs- und andere belastbare Kontextsensoren werden auch ohne
|
||||||
|
numerischen Sensor als vollständige automatische Kontextzuordnung angezeigt
|
||||||
|
- Status und Zuordnungssicherheit bilden das aktive Verhaltenslernen ab statt
|
||||||
|
eines optionalen numerischen Modells
|
||||||
|
- Ausführungsfreigabe erscheint erst, wenn genügend eindeutig manuelle
|
||||||
|
Bedienungen vorliegen; bis dahin nennt die Oberfläche die noch fehlende Anzahl
|
||||||
|
|
||||||
## 0.5.3 - 2026-06-14
|
## 0.5.3 - 2026-06-14
|
||||||
- Verhindert fachlich falsche Sensorzuordnungen nur aufgrund generischer Namen wie
|
- Verhindert fachlich falsche Sensorzuordnungen nur aufgrund generischer Namen wie
|
||||||
`Licht` oder `Lichtschalter`
|
`Licht` oder `Lichtschalter`
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: SillyHome Next
|
name: SillyHome Next
|
||||||
version: "0.5.3"
|
version: "0.5.4"
|
||||||
slug: sillyhome_next
|
slug: sillyhome_next
|
||||||
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
|
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
|
||||||
url: http://192.168.6.31:3000/pino/sillyhome-next
|
url: http://192.168.6.31:3000/pino/sillyhome-next
|
||||||
|
|||||||
@@ -239,12 +239,25 @@ class ActuatorReconciliationService:
|
|||||||
(candidate for candidate in numeric_candidates if candidate.auto_accepted),
|
(candidate for candidate in numeric_candidates if candidate.auto_accepted),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
top_contexts = [
|
accepted_contexts = [
|
||||||
candidate.entity_id
|
candidate
|
||||||
for candidate in context_candidates
|
for candidate in context_candidates
|
||||||
if candidate.auto_accepted
|
if candidate.auto_accepted
|
||||||
][: _MAX_CONTEXT_SELECTIONS]
|
][: _MAX_CONTEXT_SELECTIONS]
|
||||||
|
top_contexts = [candidate.entity_id for candidate in accepted_contexts]
|
||||||
if top_numeric is None:
|
if top_numeric is None:
|
||||||
|
if accepted_contexts:
|
||||||
|
return AssignmentSelection(
|
||||||
|
selected_numeric_entity_id=None,
|
||||||
|
selected_context_entity_ids=top_contexts,
|
||||||
|
source=AssignmentSource.AUTOMATIC,
|
||||||
|
confidence=max(candidate.confidence for candidate in accepted_contexts),
|
||||||
|
review_required=False,
|
||||||
|
reason=(
|
||||||
|
"Passender Schaltkontext automatisch erkannt. Für diese "
|
||||||
|
"Verhaltensvorhersage ist kein numerischer Sensor erforderlich."
|
||||||
|
),
|
||||||
|
)
|
||||||
return AssignmentSelection(
|
return AssignmentSelection(
|
||||||
selected_numeric_entity_id=None,
|
selected_numeric_entity_id=None,
|
||||||
selected_context_entity_ids=top_contexts,
|
selected_context_entity_ids=top_contexts,
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ async function api(path, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function lifecycleLabel(record) {
|
function lifecycleLabel(record) {
|
||||||
|
if (record.behavior.status === "trained") return "Kontext erkannt";
|
||||||
const labels = {
|
const labels = {
|
||||||
trained: "lernt",
|
trained: "lernt",
|
||||||
pending_history: "sammelt Historie",
|
pending_history: "sammelt Historie",
|
||||||
@@ -124,6 +125,7 @@ function lifecycleLabel(record) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function statusClass(record) {
|
function statusClass(record) {
|
||||||
|
if (record.behavior.status === "trained") return "ok";
|
||||||
if (record.lifecycle.status === "trained") return "ok";
|
if (record.lifecycle.status === "trained") return "ok";
|
||||||
if (["pending_history", "pending_assignment", "archived"].includes(record.lifecycle.status)) return "warn";
|
if (["pending_history", "pending_assignment", "archived"].includes(record.lifecycle.status)) return "warn";
|
||||||
return "bad";
|
return "bad";
|
||||||
@@ -237,11 +239,18 @@ async function showActuator(actuatorId) {
|
|||||||
.map(candidate => `<li><strong>${escapeHtml(candidate.friendly_name || candidate.entity_id)}</strong>: ${candidate.evidence.map(escapeHtml).join(", ") || "statistisch relevanter Kandidat"}</li>`)
|
.map(candidate => `<li><strong>${escapeHtml(candidate.friendly_name || candidate.entity_id)}</strong>: ${candidate.evidence.map(escapeHtml).join(", ") || "statistisch relevanter Kandidat"}</li>`)
|
||||||
.join("");
|
.join("");
|
||||||
const prediction = record.behavior.prediction;
|
const prediction = record.behavior.prediction;
|
||||||
|
const requiredUserActions = 3;
|
||||||
|
const missingUserActions = Math.max(
|
||||||
|
0,
|
||||||
|
requiredUserActions - record.behavior.high_confidence_sample_count,
|
||||||
|
);
|
||||||
const activationButton = record.behavior.mode === "active"
|
const activationButton = record.behavior.mode === "active"
|
||||||
? `<button class="danger" onclick="setActivation('${escapeHtml(record.actuator_entity_id)}', false)">Autonomes Schalten stoppen</button>`
|
? `<button class="danger" onclick="setActivation('${escapeHtml(record.actuator_entity_id)}', false)">Autonomes Schalten stoppen</button>`
|
||||||
: record.behavior.status === "trained"
|
: record.behavior.status === "trained" && missingUserActions === 0
|
||||||
? `<button onclick="setActivation('${escapeHtml(record.actuator_entity_id)}', true)">Lernen und Schalten freigeben</button>`
|
? `<button onclick="setActivation('${escapeHtml(record.actuator_entity_id)}', true)">Lernen und Schalten freigeben</button>`
|
||||||
: "<p class='muted'>Freigabe wird möglich, sobald genügend Handlungen gelernt wurden.</p>";
|
: record.behavior.status === "trained"
|
||||||
|
? `<p class='muted'>Freigabe noch gesperrt: ${missingUserActions} eindeutig manuelle Bedienung${missingUserActions === 1 ? "" : "en"} fehlen. Bediene das Licht dafür direkt über Home Assistant.</p>`
|
||||||
|
: "<p class='muted'>Freigabe wird möglich, sobald genügend Handlungen gelernt wurden.</p>";
|
||||||
box.innerHTML = `
|
box.innerHTML = `
|
||||||
<div class="grid-two">
|
<div class="grid-two">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from app.actuators.lifecycle import ActuatorReconciliationService
|
from app.actuators.lifecycle import ActuatorReconciliationService
|
||||||
from app.actuators.models import (
|
from app.actuators.models import (
|
||||||
|
AssignmentSource,
|
||||||
LifecycleStatus,
|
LifecycleStatus,
|
||||||
ManualOverride,
|
ManualOverride,
|
||||||
model_id_for_actuator,
|
model_id_for_actuator,
|
||||||
@@ -233,6 +234,9 @@ def test_reconciliation_does_not_cross_assign_other_room_light_energy(
|
|||||||
assert record.assignment.selected_context_entity_ids == [
|
assert record.assignment.selected_context_entity_ids == [
|
||||||
"binary_sensor.abstellraum_ture"
|
"binary_sensor.abstellraum_ture"
|
||||||
]
|
]
|
||||||
|
assert record.assignment.source is AssignmentSource.AUTOMATIC
|
||||||
|
assert record.assignment.confidence == 1.0
|
||||||
|
assert record.assignment.review_required is False
|
||||||
assert record.lifecycle.status is LifecycleStatus.ARCHIVED
|
assert record.lifecycle.status is LifecycleStatus.ARCHIVED
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,5 +14,8 @@ def test_dashboard_is_served_at_root() -> None:
|
|||||||
assert "Wie gewohnt bedienen" in response.text
|
assert "Wie gewohnt bedienen" in response.text
|
||||||
assert "Ohne deine spätere Freigabe wird nichts geschaltet" in response.text
|
assert "Ohne deine spätere Freigabe wird nichts geschaltet" in response.text
|
||||||
assert "Du wählst keine Sensoren und erstellst keine Regeln" in response.text
|
assert "Du wählst keine Sensoren und erstellst keine Regeln" in response.text
|
||||||
|
assert "Freigabe noch gesperrt" in response.text
|
||||||
|
assert "Bediene das Licht dafür direkt über Home Assistant" in response.text
|
||||||
|
assert 'record.behavior.status === "trained" && missingUserActions === 0' in response.text
|
||||||
assert "Automation-Entwurf" not in response.text
|
assert "Automation-Entwurf" not in response.text
|
||||||
assert "Manuelle Overrides" not in response.text
|
assert "Manuelle Overrides" not in response.text
|
||||||
|
|||||||
Reference in New Issue
Block a user