myrmidon / python /src /server /services /health_service.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
6.15 kB
# python/src/server/services/health_service.py
import datetime
import json
from typing import Any
from src.server.repositories.base_repository import BaseRepository
from ..config.logfire_config import get_logger
from ..utils import get_supabase_client
from .search.rag_service import RAGService
logger = get_logger(__name__)
class HealthService(BaseRepository):
"""
Service for checking the health of system components including the database.
"""
def __init__(self, supabase_client=None):
super().__init__(supabase_client or get_supabase_client())
def check_db_health(self) -> bool:
"""Checks if the database is reachable and responding."""
def _query():
return self.supabase_client.table("profiles").select("id", count="exact").limit(1).execute()
success, _ = self.execute_query(_query, "DB health check failed")
return success
def verify_auth_config(self) -> bool:
"""Verifies if the Supabase Auth configuration is valid."""
try:
# Minimal check to see if auth client is initialized
return self.supabase_client.auth is not None
except Exception:
return False
async def check_rag_integrity(self) -> dict[str, Any]:
"""
Performs a deep integrity check of the RAG system.
Calculates a weighted System Integrity Score without polluting the DB.
Weightage: Knowledge Alignment (70%) + DB (15%) + Search (15%)
"""
logger.info("📊 Calculating Composite System Integrity Score (Read-Only)...")
# 1. DB Connectivity Check (15% weight)
db_ok = self.check_db_health()
db_score = 15.0 if db_ok else 0.0
if not db_ok:
return {"status": "unhealthy", "score": 0.0, "details": {"error": "Critical: Database connection lost."}}
# 2. Knowledge Alignment Check (70% weight)
def _query_sources():
return self.supabase_client.table("archon_sources").select("source_id", count="exact").execute()
success, res = self.execute_query(_query_sources, "Error counting sources", require_data=False)
if not success:
logger.error("💥 System Integrity Calculation Failed")
return {"status": "unhealthy", "score": 0.0, "details": {"error": res.get("error")}}
# res['count'] is populated by BaseRepository.execute_query
total_count = int(res.get("count") or 0)
data = res.get("data")
if total_count == 0 and isinstance(data, list):
total_count = len(data)
alignment_score = 0.0
indexed_count = 0
if total_count > 0:
def _query_indexed():
return (
self.supabase_client.table("archon_crawled_pages")
.select("source_id", count="exact")
.not_.is_("embedding", "null")
.execute()
)
idx_success, idx_res = self.execute_query(
_query_indexed, "Error counting indexed sources", require_data=False
)
if idx_success:
idx_data = idx_res.get("data") or []
# Count the number of unique source_ids that have active crawled pages
indexed_count = len({row["source_id"] for row in idx_data})
alignment_score = (indexed_count / total_count) * 70.0
else:
alignment_score = 70.0 # Default full if system is fresh/empty
# 3. Search Responsiveness Check (15% weight)
rag = RAGService()
search_ok = False
try:
test_search = await rag.search_documents(query="Archon", match_count=1)
search_ok = len(test_search) > 0 if isinstance(test_search, list) else False
except Exception as e:
logger.error(f"Probe Search Check failed: {e}")
search_ok = False
search_score = 15.0 if search_ok else 0.0
# 4. Composite Score
final_score = round(db_score + alignment_score + search_score, 2)
return {
"status": "healthy" if final_score >= 90 else ("degraded" if final_score >= 70 else "unhealthy"),
"score": final_score,
"details": {
"alignment_raw": round((alignment_score / 70.0) * 100, 1) if total_count > 0 else 100.0,
"db_connected": db_ok,
"search_active": search_ok,
"total_sources": total_count,
"indexed_sources": indexed_count,
"timestamp": datetime.datetime.now(datetime.UTC).isoformat(),
},
}
async def get_health_history(self, days: int = 30) -> dict[str, Any]:
"""
Retrieves historical integrity audit logs from archon_logs table.
"""
since = (datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=days)).isoformat()
def _query():
return (
self.supabase_client.table("archon_logs")
.select("*")
.eq("source", "clockwork-scheduler")
.gt("created_at", since)
.order("created_at", desc=True)
.execute()
)
success, res = self.execute_query(_query, "History fetch failed", require_data=False)
if not success:
return {"trend": [], "audit": []}
logs = res.get("data") or []
trend = []
audit_trail = []
for log in logs:
details = log.get("details", {})
# Handle if details is a string (JSON string)
if isinstance(details, str):
try:
details = json.loads(details)
except Exception:
details = {}
score = details.get("score") if isinstance(details, dict) else None
if score is not None:
audit_trail.append(log)
trend.append({"date": log["created_at"][:10], "score": score})
trend.sort(key=lambda x: x["date"])
return {"trend": trend, "audit": audit_trail[:10]}