Add cache source tracking (L1/L2) for Telegram bot responses
Implements cache tier identification in Telegram bot to display data source: - "db" for database queries - "cached L1" for in-memory cache hits - "cached L2" for SQLite cache hits Backend changes: - Added cache metadata fields to TrendsResponse and DashboardSummary models (cache_hit, response_time_ms, cache_source) - Updated /api/dashboard/summary and /api/dashboard/trends endpoints to include cache metadata when X-Include-Cache-Metadata header is present - Cache metadata is extracted from request.state (set by @cached decorator) Telegram bot changes: - Updated API client to send X-Include-Cache-Metadata header - Modified helpers to extract cache_source from backend responses - Updated handlers to pass cache metadata to formatters - Performance footer now displays specific cache tier (L1 vs L2) Fixed Pydantic serialization issue: - Changed field names from _cache_hit to cache_hit (without underscore) - Pydantic excludes underscore-prefixed fields from JSON by default 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -234,15 +234,20 @@ class BackendAPIClient:
|
||||
|
||||
Returns:
|
||||
Dict with dashboard data (sold_total, facturi, plati, etc.)
|
||||
Includes _cache_hit and _response_time_ms metadata
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
"/api/dashboard/summary",
|
||||
params={"company": str(company_id)},
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -270,9 +275,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/treasury-breakdown?company={company_id}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -302,9 +311,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/detailed-data?company={company_id}&data_type={data_type}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -334,9 +347,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/maturity?company={company_id}&period={period}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -364,9 +381,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/performance?company={company_id}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -396,9 +417,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/monthly-flows?company={company_id}&months={months}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -428,9 +453,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/trends?company={company_id}&period={period}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -476,9 +505,6 @@ class BackendAPIClient:
|
||||
if filters:
|
||||
params.update(filters)
|
||||
|
||||
# ⚠️ DEBUGGING: Log exact parameters being sent
|
||||
logger.info(f"📤 Searching invoices with params: {params}")
|
||||
|
||||
response = await self.client.get(
|
||||
"/api/invoices/",
|
||||
params=params,
|
||||
@@ -487,13 +513,10 @@ class BackendAPIClient:
|
||||
|
||||
data = await self._handle_response(response)
|
||||
|
||||
# ⚠️ DEBUGGING: Log response
|
||||
if isinstance(data, dict) and 'invoices' in data:
|
||||
invoice_list = data['invoices']
|
||||
logger.info(f"📥 Received {len(invoice_list)} invoices from backend")
|
||||
return invoice_list
|
||||
elif isinstance(data, list):
|
||||
logger.info(f"📥 Received {len(data)} invoices from backend (direct list)")
|
||||
return data
|
||||
else:
|
||||
logger.warning(f"📥 Unexpected response format: {type(data)}")
|
||||
@@ -626,6 +649,113 @@ class BackendAPIClient:
|
||||
logger.error(f"Failed to export report: {e}")
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# CACHE MANAGEMENT
|
||||
# =========================================================================
|
||||
|
||||
async def invalidate_cache(
|
||||
self,
|
||||
jwt_token: str,
|
||||
company_id: Optional[int] = None,
|
||||
cache_type: Optional[str] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Invalidate cache entries.
|
||||
|
||||
Args:
|
||||
jwt_token: JWT access token
|
||||
company_id: Optional company ID (None = all companies)
|
||||
cache_type: Optional cache type (None = all types)
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
request_data = {}
|
||||
if company_id is not None:
|
||||
request_data['company_id'] = company_id
|
||||
if cache_type is not None:
|
||||
request_data['cache_type'] = cache_type
|
||||
|
||||
response = await self.client.post(
|
||||
"/api/cache/invalidate",
|
||||
json=request_data,
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
logger.info(f"Cache invalidated: company_id={company_id}, cache_type={cache_type}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to invalidate cache: {e}")
|
||||
return False
|
||||
|
||||
async def toggle_user_cache(
|
||||
self,
|
||||
jwt_token: str,
|
||||
enabled: bool
|
||||
) -> bool:
|
||||
"""
|
||||
Toggle cache for current user.
|
||||
|
||||
Args:
|
||||
jwt_token: JWT access token
|
||||
enabled: True to enable cache, False to disable
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
response = await self.client.post(
|
||||
"/api/cache/toggle-user",
|
||||
json={"enabled": enabled},
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
logger.info(f"User cache toggled: enabled={enabled}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to toggle user cache: {e}")
|
||||
return False
|
||||
|
||||
async def get_cache_stats(
|
||||
self,
|
||||
jwt_token: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get cache statistics including user-specific settings.
|
||||
|
||||
Args:
|
||||
jwt_token: JWT access token
|
||||
|
||||
Returns:
|
||||
Dict with cache stats including 'user_enabled' field
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
response = await self.client.get(
|
||||
"/api/cache/stats",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get cache stats: {e}")
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# HEALTH CHECK
|
||||
# =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user