""" Global AI feature flags stored in Firestore at config/ai_features. Defaults to all-on when the document does not exist yet. Uses a short in-memory TTL cache so flag reads don't add a Firestore round-trip to every call upload. """ import time from typing import Any from app.internal.logger import logger from app.internal import firestore as fstore _COLLECTION = "config" _DOC_ID = "ai_features" _TTL = 30.0 # seconds before re-reading from Firestore _DEFAULTS: dict[str, bool] = { "stt_enabled": True, "correlation_enabled": True, "summaries_enabled": True, "vocabulary_learning_enabled": True, } _cache: dict[str, Any] = {} _cache_ts: float = 0.0 async def get_flags() -> dict[str, bool]: """Return the current feature flags, using the TTL cache when fresh.""" global _cache, _cache_ts now = time.monotonic() if _cache and (now - _cache_ts) < _TTL: return dict(_cache) try: doc = await fstore.doc_get(_COLLECTION, _DOC_ID) if doc: merged = {**_DEFAULTS, **{k: bool(v) for k, v in doc.items() if k in _DEFAULTS}} else: merged = dict(_DEFAULTS) except Exception as e: logger.warning(f"Feature flags: could not read from Firestore ({e}), using defaults") merged = dict(_DEFAULTS) _cache = merged _cache_ts = now return dict(_cache) async def set_flags(updates: dict[str, bool]) -> dict[str, bool]: """Write flag updates to Firestore and invalidate the cache.""" global _cache, _cache_ts clean = {k: bool(v) for k, v in updates.items() if k in _DEFAULTS} if not clean: raise ValueError(f"No recognised flag keys in update: {list(updates)}") await fstore.doc_set(_COLLECTION, _DOC_ID, clean) _cache_ts = 0.0 # force re-read on next get_flags() logger.info(f"Feature flags updated: {clean}") return await get_flags()