From 658516cd96a79bfec51a38cbda2ffb773003bde1 Mon Sep 17 00:00:00 2001 From: Otto Date: Sun, 14 Jun 2026 23:42:50 +0200 Subject: [PATCH] fix: narrow manual context suggestions --- CHANGELOG.md | 6 +++++ addon/config.yaml | 2 +- app/actuators/lifecycle.py | 55 +++++++++++++++++++++++++++++++++++--- app/main.py | 2 +- pyproject.toml | 2 +- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95d79f0..7643a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.7.5 - 2026-06-14 +- Kontextvorschläge weiter geschärft: Standardliste zeigt nur gleiche Räume, + gemeinsame Geräte/Tokens oder echte globale Außenwerte +- Diagnosewerte wie MQTT-, WiFi-, Restart- und Connect-Zähler werden nicht mehr + als fachliche Kontextvorschläge angeboten + ## 0.7.4 - 2026-06-14 - Kontext-Auswahl liefert jetzt aktorbezogene Vorschläge statt einer pauschalen Roh-Liste aller Sensoren und Zustände diff --git a/addon/config.yaml b/addon/config.yaml index 7f2ae29..28a0ebe 100644 --- a/addon/config.yaml +++ b/addon/config.yaml @@ -1,5 +1,5 @@ name: SillyHome Next -version: "0.7.4" +version: "0.7.5" slug: sillyhome_next description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren url: http://192.168.6.31:3000/pino/sillyhome-next diff --git a/app/actuators/lifecycle.py b/app/actuators/lifecycle.py index 41681ce..1951fb7 100644 --- a/app/actuators/lifecycle.py +++ b/app/actuators/lifecycle.py @@ -79,6 +79,23 @@ _MANUAL_CONTEXT_DOMAINS = frozenset({ "weather", }) _CONTEXT_SUGGESTION_LIMIT = 120 +_OUTDOOR_TOKENS = frozenset({"aussen", "außen", "outdoor", "garten", "terrasse", "balkon"}) +_DIAGNOSTIC_TOKENS = frozenset({ + "connect", + "count", + "diagnostic", + "firmware", + "last", + "linkquality", + "mqtt", + "reason", + "restart", + "rssi", + "signal", + "ssid", + "uptime", + "wifi", +}) class ActuatorReconciliationService: @@ -131,6 +148,11 @@ class ActuatorReconciliationService: selected = entity.entity_id in selected_ids if selected: score = max(score, 1.0) + if not selected and ( + _is_diagnostic_context(entity) + or not _has_context_relationship(actuator, entity) + ): + continue if not selected and score < 0.1: continue ranked.append((score, _context_sort_group(entity), entity)) @@ -687,6 +709,32 @@ def _context_sort_group(entity: HaEntitySummary) -> str: return f"20_{entity.domain}_{device_class}" +def _is_diagnostic_context(entity: HaEntitySummary) -> bool: + tokens = _metadata_tokens(entity, include_stopwords=True) + return bool(tokens.intersection(_DIAGNOSTIC_TOKENS)) + + +def _has_context_relationship(actuator: HaEntitySummary, entity: HaEntitySummary) -> bool: + if ( + actuator.area_name + and entity.area_name + and actuator.area_name == entity.area_name + and actuator.area_name.lower() not in _GENERIC_AREA_NAMES + ): + return True + if actuator.device_id and entity.device_id and actuator.device_id == entity.device_id: + return True + if actuator.device_name and entity.device_name and actuator.device_name == entity.device_name: + return True + if _metadata_tokens(actuator).intersection(_metadata_tokens(entity)): + return True + entity_tokens = _metadata_tokens(entity, include_stopwords=True) + return bool( + entity_tokens.intersection(_OUTDOOR_TOKENS) + and entity.device_class in {"illuminance", "humidity", "temperature"} + ) + + def _score_candidate( actuator: HaEntitySummary, entity: HaEntitySummary, @@ -732,8 +780,7 @@ def _score_candidate( if context and role is EntityRole.BINARY_CONTEXT: score += 0.05 evidence.append("Binärer Kontextsensor bevorzugt für Zusatzkontext.") - outdoor_tokens = {"aussen", "außen", "outdoor", "garten", "terrasse", "balkon"} - if entity_tokens.intersection(outdoor_tokens) and entity.device_class in { + if entity_tokens.intersection(_OUTDOOR_TOKENS) and entity.device_class in { "illuminance", "humidity", "temperature", @@ -801,7 +848,7 @@ def _preferred_device_classes(domain: str, *, context: bool) -> frozenset[str]: return frozenset(mapping.get(domain, {"power", "energy", "temperature"})) -def _metadata_tokens(entity: HaEntitySummary) -> set[str]: +def _metadata_tokens(entity: HaEntitySummary, *, include_stopwords: bool = False) -> set[str]: raw_values = [ entity.entity_id, entity.friendly_name, @@ -813,7 +860,7 @@ def _metadata_tokens(entity: HaEntitySummary) -> set[str]: if value is None: continue for token in _TOKEN_PATTERN.findall(value.lower().replace("_", " ")): - if len(token) < 3 or token in _STOPWORDS: + if len(token) < 3 or (not include_stopwords and token in _STOPWORDS): continue tokens.add(token) return tokens diff --git a/app/main.py b/app/main.py index 43d1387..8863c95 100644 --- a/app/main.py +++ b/app/main.py @@ -100,7 +100,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app = FastAPI( title="SillyHome Next API", description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.", - version="0.7.4", + version="0.7.5", lifespan=lifespan, ) app.state.settings = load_settings() diff --git a/pyproject.toml b/pyproject.toml index 6f0abb6..bb32f31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sillyhome-next" -version = "0.7.4" +version = "0.7.5" description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant" requires-python = ">=3.11" dependencies = [