95 lines
3.6 KiB
Python
95 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
|
|
class HealthServiceMixin:
|
|
def health_status(self) -> dict[str, Any]:
|
|
"""Return a cheap public readiness summary without provider secrets."""
|
|
database_ready = False
|
|
database_status: dict[str, Any] = {}
|
|
try:
|
|
with self.database.connect() as connection:
|
|
database_ready = connection.execute("SELECT 1").fetchone() is not None
|
|
database_status = self.database.status()
|
|
except Exception:
|
|
database_ready = False
|
|
|
|
try:
|
|
recent_jobs = self.jobs.repository.recent(12)
|
|
except Exception:
|
|
recent_jobs = []
|
|
|
|
last_sync = database_status.get("last_sync") or {}
|
|
snapshot_dates = int(database_status.get("snapshot_dates") or 0)
|
|
data_configured = bool(self.configured)
|
|
last_sync_success = last_sync.get("status") == "success"
|
|
if data_configured and snapshot_dates and last_sync_success:
|
|
data_status = "ready"
|
|
elif snapshot_dates:
|
|
data_status = "degraded"
|
|
else:
|
|
data_status = "unavailable"
|
|
|
|
latest_job = recent_jobs[0] if recent_jobs else {}
|
|
failed_jobs = sum(1 for job in recent_jobs if job.get("status") == "failed")
|
|
if not database_ready:
|
|
jobs_status = "unavailable"
|
|
elif latest_job.get("status") == "running":
|
|
jobs_status = "running"
|
|
elif latest_job.get("status") == "failed":
|
|
jobs_status = "degraded"
|
|
else:
|
|
jobs_status = "ready"
|
|
|
|
platform = self._platform_llm_profile()
|
|
primary_ready = self._profile_configured(platform["primary"])
|
|
fallback_ready = self._profile_configured(platform["fallback"])
|
|
models_status = "ready" if primary_ready else "unavailable"
|
|
|
|
component_states = (
|
|
"ready",
|
|
"ready" if database_ready else "unavailable",
|
|
data_status,
|
|
jobs_status,
|
|
models_status,
|
|
)
|
|
return {
|
|
"ok": database_ready,
|
|
"status": (
|
|
"ready"
|
|
if all(state in {"ready", "running"} for state in component_states)
|
|
else "degraded"
|
|
),
|
|
"account_required": True,
|
|
"time": datetime.now().astimezone().isoformat(timespec="seconds"),
|
|
"components": {
|
|
"process": {"status": "ready"},
|
|
"database": {
|
|
"status": "ready" if database_ready else "unavailable",
|
|
"snapshot_dates": snapshot_dates,
|
|
"last_snapshot_at": database_status.get("updated_at") or "",
|
|
},
|
|
"data_sources": {
|
|
"status": data_status,
|
|
"configured": data_configured,
|
|
"snapshot_available": snapshot_dates > 0,
|
|
"latest_trade_date": str(last_sync.get("trade_date") or ""),
|
|
"last_success_at": (
|
|
str(last_sync.get("finished_at") or "") if last_sync_success else ""
|
|
),
|
|
},
|
|
"jobs": {
|
|
"status": jobs_status,
|
|
"recent_failures": failed_jobs,
|
|
"latest_finished_at": str(latest_job.get("finished_at") or ""),
|
|
},
|
|
"models": {
|
|
"status": models_status,
|
|
"primary": "ready" if primary_ready else "unavailable",
|
|
"fallback": "ready" if fallback_ready else "disabled",
|
|
},
|
|
},
|
|
}
|