Add SQLite dashboard cache
This commit is contained in:
49
app/main.py
49
app/main.py
@@ -12,6 +12,7 @@ from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.actuators.cache_db import DashboardCache
|
||||
from app.actuators.lifecycle import ActuatorReconciliationService
|
||||
from app.actuators.store import ActuatorStore
|
||||
from app.api.v1.actuators import router as actuators_router
|
||||
@@ -20,6 +21,7 @@ from app.behavior.engine import BehaviorEngine
|
||||
from app.config import load_settings
|
||||
from app.core.exception_handlers import register_exception_handlers
|
||||
from app.ha.client import HaClient, HaClientSettings
|
||||
from app.ha.discovery import discover_entities
|
||||
from app.ha.models import HaEntitySummary
|
||||
from app.ha.reader import HaReader
|
||||
from app.ml.registry.model_registry import ModelRegistry
|
||||
@@ -47,8 +49,12 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
reconcile_task: asyncio.Task[None] | None = None
|
||||
event_listener_task: asyncio.Task[None] | None = None
|
||||
fallback_task: asyncio.Task[None] | None = None
|
||||
cache_refresh_task: asyncio.Task[None] | None = None
|
||||
app.state.registry = ModelRegistry(settings.model_store)
|
||||
app.state.actuator_store = ActuatorStore(settings.actuator_store)
|
||||
app.state.dashboard_cache = DashboardCache(
|
||||
Path(settings.actuator_store).resolve() / "dashboard_cache.sqlite3"
|
||||
)
|
||||
if hasattr(app.state, "ha_reader"):
|
||||
del app.state.ha_reader
|
||||
if hasattr(app.state, "actuator_service"):
|
||||
@@ -80,6 +86,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
reconcile_task = asyncio.create_task(_periodic_reconciliation(app))
|
||||
event_listener_task = asyncio.create_task(_ha_event_listener(app, client))
|
||||
fallback_task = asyncio.create_task(_fallback_prediction(app))
|
||||
cache_refresh_task = asyncio.create_task(_periodic_dashboard_cache_refresh(app))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
@@ -99,6 +106,10 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
fallback_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await fallback_task
|
||||
if cache_refresh_task is not None:
|
||||
cache_refresh_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await cache_refresh_task
|
||||
if client is not None:
|
||||
client.close()
|
||||
|
||||
@@ -106,7 +117,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
app = FastAPI(
|
||||
title="SillyHome Next API",
|
||||
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
|
||||
version="1.5.2",
|
||||
version="1.5.3",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app.state.settings = load_settings()
|
||||
@@ -160,6 +171,42 @@ async def _periodic_reconciliation(app: FastAPI) -> None:
|
||||
logger.exception("Geplante Reconciliation fehlgeschlagen; nächster Lauf versucht es erneut.")
|
||||
|
||||
|
||||
async def _periodic_dashboard_cache_refresh(app: FastAPI) -> None:
|
||||
await asyncio.sleep(2)
|
||||
while True:
|
||||
await _refresh_dashboard_cache(app, trigger="scheduled")
|
||||
await asyncio.sleep(app.state.settings.dashboard_cache_refresh_seconds)
|
||||
|
||||
|
||||
async def _refresh_dashboard_cache(app: FastAPI, *, trigger: str) -> None:
|
||||
ha_reader = getattr(app.state, "ha_reader", None)
|
||||
cache = getattr(app.state, "dashboard_cache", None)
|
||||
if not isinstance(ha_reader, HaReader) or not isinstance(cache, DashboardCache):
|
||||
return
|
||||
try:
|
||||
entities = await asyncio.to_thread(ha_reader.read_entities)
|
||||
groups = _discovery_group_payload(list(entities))
|
||||
await asyncio.to_thread(
|
||||
cache.save_entities_payload,
|
||||
entities=list(entities),
|
||||
discovery_groups=groups,
|
||||
)
|
||||
logger.info("Dashboard-Cache aktualisiert (%s): %d Entities", trigger, len(entities))
|
||||
except Exception as exc:
|
||||
logger.warning("Dashboard-Cache konnte nicht aktualisiert werden (%s): %s", trigger, exc)
|
||||
|
||||
|
||||
def _discovery_group_payload(entities: list[HaEntitySummary]) -> list[dict[str, object]]:
|
||||
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
|
||||
return [
|
||||
{"category": category, "role": role, "count": count}
|
||||
for (category, role), count in sorted(group_counts.items())
|
||||
]
|
||||
|
||||
|
||||
async def _startup_reconciliation(app: FastAPI) -> None:
|
||||
delay_seconds = 5
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user