fix: narrow manual context suggestions

This commit is contained in:
2026-06-14 23:42:50 +02:00
parent 8cd8f3e3b7
commit 658516cd96
5 changed files with 60 additions and 7 deletions

View File

@@ -1,5 +1,11 @@
# Changelog # 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 ## 0.7.4 - 2026-06-14
- Kontext-Auswahl liefert jetzt aktorbezogene Vorschläge statt einer pauschalen - Kontext-Auswahl liefert jetzt aktorbezogene Vorschläge statt einer pauschalen
Roh-Liste aller Sensoren und Zustände Roh-Liste aller Sensoren und Zustände

View File

@@ -1,5 +1,5 @@
name: SillyHome Next name: SillyHome Next
version: "0.7.4" version: "0.7.5"
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

View File

@@ -79,6 +79,23 @@ _MANUAL_CONTEXT_DOMAINS = frozenset({
"weather", "weather",
}) })
_CONTEXT_SUGGESTION_LIMIT = 120 _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: class ActuatorReconciliationService:
@@ -131,6 +148,11 @@ class ActuatorReconciliationService:
selected = entity.entity_id in selected_ids selected = entity.entity_id in selected_ids
if selected: if selected:
score = max(score, 1.0) 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: if not selected and score < 0.1:
continue continue
ranked.append((score, _context_sort_group(entity), entity)) 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}" 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( def _score_candidate(
actuator: HaEntitySummary, actuator: HaEntitySummary,
entity: HaEntitySummary, entity: HaEntitySummary,
@@ -732,8 +780,7 @@ def _score_candidate(
if context and role is EntityRole.BINARY_CONTEXT: if context and role is EntityRole.BINARY_CONTEXT:
score += 0.05 score += 0.05
evidence.append("Binärer Kontextsensor bevorzugt für Zusatzkontext.") 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", "illuminance",
"humidity", "humidity",
"temperature", "temperature",
@@ -801,7 +848,7 @@ def _preferred_device_classes(domain: str, *, context: bool) -> frozenset[str]:
return frozenset(mapping.get(domain, {"power", "energy", "temperature"})) 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 = [ raw_values = [
entity.entity_id, entity.entity_id,
entity.friendly_name, entity.friendly_name,
@@ -813,7 +860,7 @@ def _metadata_tokens(entity: HaEntitySummary) -> set[str]:
if value is None: if value is None:
continue continue
for token in _TOKEN_PATTERN.findall(value.lower().replace("_", " ")): 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 continue
tokens.add(token) tokens.add(token)
return tokens return tokens

View File

@@ -100,7 +100,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app = FastAPI( app = FastAPI(
title="SillyHome Next API", title="SillyHome Next API",
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.", description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
version="0.7.4", version="0.7.5",
lifespan=lifespan, lifespan=lifespan,
) )
app.state.settings = load_settings() app.state.settings = load_settings()

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "sillyhome-next" name = "sillyhome-next"
version = "0.7.4" version = "0.7.5"
description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant" description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [