From 787516ac673d72643aaff202b98a79794d259fd8 Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 17 Jun 2026 01:19:59 +0200 Subject: [PATCH] Avoid per-request discovery classification in dashboard --- app/api/v1/actuators.py | 65 +++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/app/api/v1/actuators.py b/app/api/v1/actuators.py index 2231cec..989cc04 100644 --- a/app/api/v1/actuators.py +++ b/app/api/v1/actuators.py @@ -208,7 +208,11 @@ def context_options( @router.get("/summary", response_model=list[ActuatorSummary]) def list_configured_summary(request: Request) -> list[ActuatorSummary]: - entity_map = {entity.entity_id: entity for entity in _load_cached_entities(request)} + records = _service(request).list_configured() + entity_map = _load_cached_entity_map( + request, + {record.actuator_entity_id for record in records}, + ) return [ ActuatorSummary( actuator_entity_id=record.actuator_entity_id, @@ -247,7 +251,7 @@ def list_configured_summary(request: Request) -> list[ActuatorSummary]: ), updated_at=record.updated_at.isoformat(), ) - for record in _service(request).list_configured() + for record in records ] @@ -259,16 +263,12 @@ def dashboard_overview(request: Request) -> DashboardOverview: raw_entities = [] raw_updated_at = cache_payload.get("updated_at") updated_at = raw_updated_at if isinstance(raw_updated_at, str) else None - cached_entities = [ - HaEntitySummary.model_validate(entity) - for entity in raw_entities - if isinstance(entity, dict) - ] - discovered = discover_entities(cached_entities) if cached_entities else [] - group_counts: dict[tuple[str, str], int] = {} - for entity in discovered: - key = (entity.category, entity.role.value) - group_counts[key] = group_counts.get(key, 0) + 1 + raw_groups = cache_payload.get("discovery_groups", []) + cached_groups = [ + DashboardDiscoveryGroup.model_validate(group) + for group in raw_groups + if isinstance(group, dict) + ] if isinstance(raw_groups, list) else [] reconciliation = _reconciliation_state_or_default(request) ws_status = getattr(request.app.state, "ws_status", None) actuators = list_configured_summary(request) @@ -286,15 +286,12 @@ def dashboard_overview(request: Request) -> DashboardOverview: review_required=reconciliation.review_required, ), cache=EntityCacheStatus( - available=bool(cached_entities), + available=bool(raw_entities), updated_at=updated_at, - entity_count=len(cached_entities), + entity_count=len(raw_entities), ), actuators=actuators, - discovery_groups=[ - DashboardDiscoveryGroup(category=category, role=role, count=count) - for (category, role), count in sorted(group_counts.items()) - ], + discovery_groups=cached_groups, ) @@ -521,6 +518,30 @@ def _load_cached_entities(request: Request) -> list[HaEntitySummary]: return [] +def _load_cached_entity_map( + request: Request, + entity_ids: set[str], +) -> dict[str, HaEntitySummary]: + if not entity_ids: + return {} + payload = _load_entity_cache_payload(request) + raw_entities = payload.get("entities", []) + if not isinstance(raw_entities, list): + return {} + result: dict[str, HaEntitySummary] = {} + for raw_entity in raw_entities: + if not isinstance(raw_entity, dict): + continue + entity_id = raw_entity.get("entity_id") + if not isinstance(entity_id, str) or entity_id not in entity_ids: + continue + try: + result[entity_id] = HaEntitySummary.model_validate(raw_entity) + except ValueError: + continue + return result + + def _load_entity_cache_payload(request: Request) -> dict[str, object]: path = _entity_cache_path(request) if not path.exists(): @@ -535,8 +556,16 @@ def _load_entity_cache_payload(request: Request) -> dict[str, object]: def _save_cached_entities(request: Request, entities: list[HaEntitySummary]) -> None: path = _entity_cache_path(request) path.parent.mkdir(parents=True, exist_ok=True) + group_counts: dict[tuple[str, str], int] = {} + for entity in discover_entities(entities): + key = (entity.category, entity.role.value) + group_counts[key] = group_counts.get(key, 0) + 1 payload = { "updated_at": datetime.now(timezone.utc).isoformat(), + "discovery_groups": [ + {"category": category, "role": role, "count": count} + for (category, role), count in sorted(group_counts.items()) + ], "entities": [entity.model_dump(mode="json") for entity in entities], } temporary = path.with_suffix(".json.tmp")