This commit is contained in:
2025-09-08 13:29:43 -07:00
parent 574feb1ea1
commit a62b4e8c12
12 changed files with 442 additions and 336 deletions

View File

@@ -11,6 +11,7 @@ from .routes import rule as rule_routes
from .routes import plan as plan_routes
from .routes import workouts as workout_routes
from .routes import prompts as prompt_routes
from .routes import dashboard as dashboard_routes
from .config import settings
app = FastAPI(
@@ -46,6 +47,7 @@ app.include_router(rule_routes.router)
app.include_router(plan_routes.router)
app.include_router(workout_routes.router, prefix="/workouts", tags=["workouts"])
app.include_router(prompt_routes.router, prefix="/prompts", tags=["prompts"])
app.include_router(dashboard_routes.router, prefix="/api/dashboard", tags=["dashboard"])
async def check_migration_status():
"""Check if database migrations are up to date."""

View File

@@ -0,0 +1,52 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.models.workout import Workout
from app.models.plan import Plan
from app.models.garmin_sync_log import GarminSyncLog
from sqlalchemy import select, desc
from datetime import datetime, timedelta
router = APIRouter()
@router.get("/dashboard")
async def get_dashboard_data(db: AsyncSession = Depends(get_db)):
"""Get consolidated dashboard data"""
try:
# Recent workouts (last 7 days)
workout_result = await db.execute(
select(Workout)
.where(Workout.start_time >= datetime.now() - timedelta(days=7))
.order_by(desc(Workout.start_time))
.limit(5)
)
recent_workouts = [w.to_dict() for w in workout_result.scalars().all()]
# Current active plan
plan_result = await db.execute(
select(Plan)
.where(Plan.active == True)
.order_by(desc(Plan.created_at))
)
current_plan = plan_result.scalar_one_or_none()
# Sync status
sync_result = await db.execute(
select(GarminSyncLog)
.order_by(desc(GarminSyncLog.created_at))
.limit(1)
)
last_sync = sync_result.scalar_one_or_none()
return {
"recent_workouts": recent_workouts,
"current_plan": current_plan.to_dict() if current_plan else None,
"last_sync": last_sync.to_dict() if last_sync else None,
"metrics": {
"weekly_volume": sum(w.duration_seconds for w in recent_workouts) / 3600,
"plan_progress": current_plan.progress if current_plan else 0
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Dashboard data error: {str(e)}")

View File

@@ -0,0 +1,9 @@
from fastapi import APIRouter
from app.services.health_monitor import HealthMonitor
router = APIRouter()
monitor = HealthMonitor()
@router.get("/health")
async def get_health():
return monitor.check_system_health()

View File

@@ -0,0 +1,80 @@
import psutil
from datetime import datetime
import logging
from typing import Dict, Any
from sqlalchemy import text
from app.database import get_db
from app.models.garmin_sync_log import GarminSyncLog, SyncStatus
import requests
from app.config import settings
logger = logging.getLogger(__name__)
class HealthMonitor:
def __init__(self):
self.warning_thresholds = {
'cpu_percent': 80,
'memory_percent': 75,
'disk_percent': 85
}
def check_system_health(self) -> Dict[str, Any]:
"""Check vital system metrics and log warnings"""
metrics = {
'timestamp': datetime.utcnow().isoformat(),
'cpu': psutil.cpu_percent(),
'memory': psutil.virtual_memory().percent,
'disk': psutil.disk_usage('/').percent,
'services': self._check_service_health()
}
self._log_anomalies(metrics)
return metrics
def _check_service_health(self) -> Dict[str, str]:
"""Check critical application services"""
return {
'database': self._check_database(),
'garmin_sync': self._check_garmin_sync(),
'ai_service': self._check_ai_service()
}
def _check_database(self) -> str:
try:
with get_db() as db:
db.execute(text("SELECT 1"))
return "ok"
except Exception as e:
logger.error(f"Database check failed: {str(e)}")
return "down"
def _check_garmin_sync(self) -> str:
try:
last_sync = GarminSyncLog.get_latest()
if last_sync and last_sync.status == SyncStatus.FAILED:
return "warning"
return "ok"
except Exception as e:
logger.error(f"Garmin sync check failed: {str(e)}")
return "down"
def _check_ai_service(self) -> str:
try:
response = requests.get(
f"{settings.AI_SERVICE_URL}/ping",
timeout=5,
headers={"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}"}
)
return "ok" if response.ok else "down"
except Exception as e:
logger.error(f"AI service check failed: {str(e)}")
return "down"
def _log_anomalies(self, metrics: Dict[str, Any]):
alerts = []
for metric, value in metrics.items():
if metric in self.warning_thresholds and value > self.warning_thresholds[metric]:
alerts.append(f"{metric} {value}%")
if alerts:
logger.warning(f"System thresholds exceeded: {', '.join(alerts)}")

View File

@@ -1,6 +1,6 @@
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.services.garmin import GarminService, GarminAPIError
from app.services.garmin import GarminService, GarminAPIError, GarminAuthError
from app.models.workout import Workout
from app.models.garmin_sync_log import GarminSyncLog
from datetime import datetime, timedelta