fix: add health checks and repair dashboard contracts

This commit is contained in:
Hermes Agent
2026-07-26 23:15:37 +00:00
parent f9e992c2b4
commit b06cabf3aa
2 changed files with 73 additions and 32 deletions
+44 -32
View File
@@ -118,6 +118,26 @@ def save_cache(data):
atomic_write_json(CACHE_PATH, data)
@app.get("/health/live")
def health_live():
"""Report that the API process is responsive."""
return {"status": "ok"}
@app.get("/health/ready")
def health_ready():
"""Report readiness only after a usable score has been persisted."""
scored = load_cache().get("_scored", {})
score = scored.get("composite_score")
count = scored.get("scored_count", 0)
if score is None or count < 1:
return JSONResponse(
{"status": "not_ready", "reason": "no usable persisted score"},
status_code=503,
)
return {"status": "ready", "score": score, "scored_metrics": count}
def append_history(score_data):
"""Append a daily score entry to history."""
entry = {
@@ -982,8 +1002,8 @@ function highlightMetricPeriods(metricKey, currentRaw, margin) {
// Build an array of {date, rawValue} for the selected metric
const metricSeries = fullDailyScores
.filter(d => d.metrics && d.metrics[metricKey] != null)
.map(d => ({ date: d.date, value: d.metrics[metricKey], isSimilar: Math.abs(d.metrics[metricKey] - currentRaw) <= margin }));
.filter(d => d.metric_values && d.metric_values[metricKey] != null)
.map(d => ({ date: d.date, value: d.metric_values[metricKey], isSimilar: Math.abs(d.metric_values[metricKey] - currentRaw) <= margin }));
// Store for use in chart rendering
window._highlightMetric = { key: metricKey, series: metricSeries, currentRaw, margin };
@@ -1206,7 +1226,27 @@ function renderHistoryFromData(history) {
});
}
// Load backtest daily scores for the chart
// Load backtest daily scores and historical context with one request.
function renderHistoricalContext(ctx) {
if (!ctx) return;
const el = document.getElementById('histContext');
const txt = document.getElementById('histContextText');
let html = 'Score <strong>' + ctx.current_score + '</strong> is in the <strong style="color:#22d3ee">top ' + (100 - ctx.percentile).toFixed(1) + '%</strong> historically.<br>';
const fmtR = (v) => v == null ? null : (v >= 0 ? '+' : '') + v.toFixed(1) + '%';
const cR = (v) => v >= 0 ? '#22c55e' : '#ef4444';
const periods = [
['30d', ctx.avg_30d_return], ['90d', ctx.avg_90d_return],
['180d', ctx.avg_180d_return], ['1yr', ctx.avg_1yr_return]
];
const parts = [];
for (const [label, val] of periods) {
if (val != null) parts.push('<strong style="color:' + cR(val) + '">' + label + ': ' + fmtR(val) + '</strong>');
}
if (parts.length) html += 'Average returns from this level: ' + parts.join(' · ');
txt.innerHTML = html;
el.style.display = 'block';
}
async function loadBacktestChart() {
try {
const r = await fetch('/api/backtest?mode=' + currentMode);
@@ -1215,6 +1255,7 @@ async function loadBacktestChart() {
fullDailyScores = data.chart_data;
applyChartRange(currentRange);
}
renderHistoricalContext(data.current_context);
} catch(e) { console.error('Backtest chart load failed:', e); }
}
@@ -1343,35 +1384,6 @@ function setMode(mode) {
drawScoreRing(0);
poll();
setInterval(poll, 30000);
// Load historical context from backtest
(async function() {
try {
const r = await fetch('/api/backtest/status');
const s = await r.json();
if (!s.exists) return;
const br = await fetch('/api/backtest');
const bt = await br.json();
if (bt.error || !bt.current_context) return;
const ctx = bt.current_context;
const el = document.getElementById('histContext');
const txt = document.getElementById('histContextText');
let html = 'Score <strong>' + ctx.current_score + '</strong> is in the <strong style="color:#22d3ee">top ' + (100 - ctx.percentile).toFixed(1) + '%</strong> historically.<br>';
const fmtR = (v) => v == null ? null : (v >= 0 ? '+' : '') + v.toFixed(1) + '%';
const cR = (v) => v >= 0 ? '#22c55e' : '#ef4444';
const periods = [
['30d', ctx.avg_30d_return], ['90d', ctx.avg_90d_return],
['180d', ctx.avg_180d_return], ['1yr', ctx.avg_1yr_return]
];
let parts = [];
for (const [label, val] of periods) {
if (val != null) parts.push('<strong style="color:' + cR(val) + '">' + label + ': ' + fmtR(val) + '</strong>');
}
if (parts.length) html += 'Average returns from this level: ' + parts.join(' · ');
txt.innerHTML = html;
el.style.display = 'block';
} catch(e) { /* backtest data not available yet */ }
})();
</script>
</body>
</html>"""