diff --git a/dashboard/server.py b/dashboard/server.py
index e6109ae..8ab16a8 100644
--- a/dashboard/server.py
+++ b/dashboard/server.py
@@ -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 ' + ctx.current_score + ' is in the top ' + (100 - ctx.percentile).toFixed(1) + '% historically.
';
+ 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('' + label + ': ' + fmtR(val) + '');
+ }
+ 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 ' + ctx.current_score + ' is in the top ' + (100 - ctx.percentile).toFixed(1) + '% historically.
';
- 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('' + label + ': ' + fmtR(val) + '');
- }
- 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 */ }
-})();