Compare commits
1 Commits
fix/addon-
...
fix/sensor
| Author | SHA1 | Date | |
|---|---|---|---|
| 47e8c7e549 |
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.3 - 2026-06-14
|
||||
- Verhindert fachlich falsche Sensorzuordnungen nur aufgrund generischer Namen wie
|
||||
`Licht` oder `Lichtschalter`
|
||||
- Übernimmt numerische Sensoren nur noch bei einem belastbaren absoluten Score und
|
||||
einer eindeutigen Abgrenzung zum zweitbesten Kandidaten
|
||||
- Begrenzt Zusatzkontext auf relevante Sensoren und bevorzugt bei Lichtaktoren
|
||||
echte Beleuchtungsstärke gegenüber fremden Leistungs- oder Energiezählern
|
||||
|
||||
## 0.5.2 - 2026-06-14
|
||||
- Add-on-Build invalidiert den Docker-Cache bei jeder Versionsänderung, damit
|
||||
Versionsmetadaten und tatsächlich ausgelieferter Anwendungscode übereinstimmen
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: SillyHome Next
|
||||
version: "0.5.2"
|
||||
version: "0.5.3"
|
||||
slug: sillyhome_next
|
||||
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
|
||||
url: http://192.168.6.31:3000/pino/sillyhome-next
|
||||
|
||||
@@ -45,6 +45,8 @@ _STOPWORDS = frozenset(
|
||||
"humidity",
|
||||
"illuminance",
|
||||
"light",
|
||||
"licht",
|
||||
"lichtschalter",
|
||||
"power",
|
||||
"sensor",
|
||||
"state",
|
||||
@@ -54,8 +56,10 @@ _STOPWORDS = frozenset(
|
||||
}
|
||||
)
|
||||
_NUMERIC_AUTO_ACCEPT_SCORE = 0.82
|
||||
_NUMERIC_AUTO_ACCEPT_MIN_SCORE = 0.5
|
||||
_NUMERIC_MIN_MARGIN = 0.18
|
||||
_CONTEXT_AUTO_ACCEPT_SCORE = 0.78
|
||||
_CONTEXT_AUTO_ACCEPT_MIN_SCORE = 0.3
|
||||
_MAX_CONTEXT_SELECTIONS = 5
|
||||
_AUDIT_LIMIT = 20
|
||||
|
||||
@@ -231,10 +235,14 @@ class ActuatorReconciliationService:
|
||||
numeric_candidates: list[AssignmentCandidate],
|
||||
context_candidates: list[AssignmentCandidate],
|
||||
) -> AssignmentSelection:
|
||||
top_numeric = numeric_candidates[0] if numeric_candidates else None
|
||||
top_numeric = next(
|
||||
(candidate for candidate in numeric_candidates if candidate.auto_accepted),
|
||||
None,
|
||||
)
|
||||
top_contexts = [
|
||||
candidate.entity_id
|
||||
for candidate in context_candidates
|
||||
if candidate.auto_accepted
|
||||
][: _MAX_CONTEXT_SELECTIONS]
|
||||
if top_numeric is None:
|
||||
return AssignmentSelection(
|
||||
@@ -433,8 +441,15 @@ class ActuatorReconciliationService:
|
||||
confidence = candidate.score / highest if highest else 0.0
|
||||
margin = candidate.score - second_score if index == 0 else 0.0
|
||||
auto_score = _CONTEXT_AUTO_ACCEPT_SCORE if context else _NUMERIC_AUTO_ACCEPT_SCORE
|
||||
auto_accepted = confidence >= auto_score and (
|
||||
context or margin >= _NUMERIC_MIN_MARGIN
|
||||
minimum_score = (
|
||||
_CONTEXT_AUTO_ACCEPT_MIN_SCORE
|
||||
if context
|
||||
else _NUMERIC_AUTO_ACCEPT_MIN_SCORE
|
||||
)
|
||||
auto_accepted = (
|
||||
candidate.score >= minimum_score
|
||||
and confidence >= auto_score
|
||||
and (context or margin >= _NUMERIC_MIN_MARGIN)
|
||||
)
|
||||
sorted_candidates[index] = candidate.model_copy(
|
||||
update={
|
||||
@@ -510,6 +525,9 @@ def _score_candidate(
|
||||
if entity.device_class in preferred_device_classes:
|
||||
score += 0.2
|
||||
evidence.append(f"Passende device_class: {entity.device_class}")
|
||||
if not context and actuator.domain == "light" and entity.device_class == "illuminance":
|
||||
score += 0.2
|
||||
evidence.append("Beleuchtungsstärke wird für Lichtaktoren bevorzugt.")
|
||||
if not context and entity.unit_of_measurement is not None:
|
||||
score += 0.05
|
||||
evidence.append(f"Numerische Einheit vorhanden: {entity.unit_of_measurement}")
|
||||
|
||||
@@ -141,7 +141,7 @@ def test_reconciliation_auto_assigns_and_trains_numeric_model(tmp_path: Path) ->
|
||||
assert "binary_sensor.abstellkammer_motion" not in artifact.supported_sensors
|
||||
|
||||
|
||||
def test_reconciliation_uses_best_automatic_mapping_when_ambiguous(tmp_path: Path) -> None:
|
||||
def test_reconciliation_rejects_ambiguous_numeric_mapping(tmp_path: Path) -> None:
|
||||
start = datetime(2026, 6, 1, tzinfo=timezone.utc)
|
||||
entities = [
|
||||
HaEntitySummary(
|
||||
@@ -181,8 +181,59 @@ def test_reconciliation_uses_best_automatic_mapping_when_ambiguous(tmp_path: Pat
|
||||
record = service.configure_actuator("switch.garage_pump")
|
||||
|
||||
assert record.assignment.review_required is True
|
||||
assert record.assignment.selected_numeric_entity_id == "sensor.garage_energy"
|
||||
assert record.lifecycle.status is LifecycleStatus.TRAINED
|
||||
assert record.assignment.selected_numeric_entity_id is None
|
||||
assert record.lifecycle.status is LifecycleStatus.ARCHIVED
|
||||
|
||||
|
||||
def test_reconciliation_does_not_cross_assign_other_room_light_energy(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
start = datetime(2026, 6, 1, tzinfo=timezone.utc)
|
||||
entities = [
|
||||
HaEntitySummary(
|
||||
entity_id=(
|
||||
"light.lichtschalter_abstellraum_"
|
||||
"lichtschalter_abstellraum_s1"
|
||||
),
|
||||
domain="light",
|
||||
friendly_name="Licht Abstellraum",
|
||||
),
|
||||
HaEntitySummary(
|
||||
entity_id="sensor.licht_badezimmer_energy",
|
||||
domain="sensor",
|
||||
device_class="energy",
|
||||
state_class="total_increasing",
|
||||
unit_of_measurement="kWh",
|
||||
friendly_name="Lichtschalter_Badezimmer Licht Badezimmer energy",
|
||||
),
|
||||
HaEntitySummary(
|
||||
entity_id="binary_sensor.abstellraum_ture",
|
||||
domain="binary_sensor",
|
||||
device_class="door",
|
||||
friendly_name="Abstellraum Türe",
|
||||
),
|
||||
HaEntitySummary(
|
||||
entity_id="binary_sensor.briefkasten_open",
|
||||
domain="binary_sensor",
|
||||
device_class="opening",
|
||||
friendly_name="Briefkasten open",
|
||||
),
|
||||
]
|
||||
service = _service(
|
||||
tmp_path,
|
||||
entities,
|
||||
{"sensor.licht_badezimmer_energy": _points(8, start, 1.0)},
|
||||
)
|
||||
|
||||
record = service.configure_actuator(
|
||||
"light.lichtschalter_abstellraum_lichtschalter_abstellraum_s1"
|
||||
)
|
||||
|
||||
assert record.assignment.selected_numeric_entity_id is None
|
||||
assert record.assignment.selected_context_entity_ids == [
|
||||
"binary_sensor.abstellraum_ture"
|
||||
]
|
||||
assert record.lifecycle.status is LifecycleStatus.ARCHIVED
|
||||
|
||||
|
||||
def test_legacy_manual_override_is_cleared_and_automatic_mapping_wins(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user