Files
Duriin-API/public/admin/assets/js/autonomy.js
T

106 lines
6.6 KiB
JavaScript

(function () {
const byId = id => document.getElementById(id);
const count = (rows, key, value) => Number((rows || []).find(row => row[key] === value)?.count || 0);
const sum = (rows, predicate) => (rows || []).filter(predicate).reduce((total, row) => total + Number(row.count || 0), 0);
function renderHypotheses(rows) {
const host = byId("hypothesis-list");
if (!rows?.length) {
host.innerHTML = '<div class="empty-state">No autonomy hypotheses yet. The coordinator is working through the evidence queue.</div>';
return;
}
host.innerHTML = rows.slice(0, 7).map(row => {
const action = row.action || (row.status === "resolved" ? "MEASURED" : "OPEN");
const direction = row.direction === "positive" ? "positive" : "negative";
return `<article class="hypothesis-row">
<div><div class="hypothesis-symbol">${escapeHtml(row.instrument)}</div><div class="direction-tag ${direction}">${escapeHtml(row.direction)}</div></div>
<div><div class="hypothesis-type">${escapeHtml(String(row.event_type || "event").replaceAll("_", " "))}</div><div class="hypothesis-channel">${escapeHtml(row.causal_channel || "Evidence-backed market hypothesis")}</div></div>
<div class="hypothesis-meta">${row.evidence_count || 0} evidence source${row.evidence_count === 1 ? "" : "s"}<br>${row.horizon_days} trading-day horizon<br>${formatRelative(row.created_at)}</div>
<span class="decision-chip ${String(action).toLowerCase()}">${escapeHtml(action)}</span>
</article>`;
}).join("");
}
function renderLedger(rows) {
const host = byId("decision-ledger");
const decisions = (rows || []).filter(row => row.action);
if (!decisions.length) return;
host.innerHTML = decisions.map(row => `<tr>
<td class="mono">${escapeHtml(row.instrument)}</td>
<td><span class="decision-chip ${String(row.action).toLowerCase()}">${escapeHtml(row.action)}</span></td>
<td class="${row.direction === "positive" ? "positive" : "negative"}">${escapeHtml(row.direction)}</td>
<td class="mono">${formatPercent(row.calibrated_probability)}</td>
<td>${row.horizon_days}d</td>
<td class="muted">${formatRelative(row.created_at)}</td>
</tr>`).join("");
}
function render(data) {
if (!data.enabled) throw new Error(data.reason || "Autonomy is unavailable");
const mode = String(data.mode || "shadow").toUpperCase();
const open = count(data.predictionCounts, "status", "open");
const resolved = count(data.predictionCounts, "status", "resolved");
const outcomes = Number(data.outcomes?.total || 0);
const correct = Number(data.outcomes?.correct || 0);
const accuracy = outcomes ? correct / outcomes : null;
const pendingHistorical = count((data.jobs || []).filter(row => row.lane === "historical"), "status", "pending");
const completedJobs = sum(data.jobs, row => row.status === "complete");
const proposals = sum(data.proposalCounts, row => row.status === "accepted");
const decisions = sum(data.decisionCounts, () => true);
byId("execution-mode").textContent = mode;
byId("ledger-mode").textContent = mode;
byId("runtime-execution").textContent = mode;
byId("broker-state").textContent = data.broker?.configured ? `${data.broker.name} connected` : "Broker credentials unavailable";
byId("runtime-state").textContent = `${mode} runtime active`;
byId("hero-title").textContent = mode === "PAPER" ? "Duriin is trading in simulation." : "Duriin is learning before it acts.";
byId("hero-description").textContent = mode === "PAPER"
? "Every order is backed by measured evidence, empirical calibration and deterministic risk policy. No model has direct execution authority."
: "It is turning evidence into hypotheses, waiting for outcomes, and calibrating its judgment without placing broker orders.";
byId("metric-open").textContent = formatNumber(open);
byId("metric-resolved").textContent = `${formatNumber(resolved)} resolved`;
byId("metric-accuracy").textContent = formatPercent(accuracy);
byId("metric-sample").textContent = outcomes ? `${formatNumber(outcomes)} measured outcomes` : "Waiting for outcomes";
byId("metric-alpha").textContent = formatPercent(data.outcomes?.average_excess_return, 2);
byId("metric-universe").textContent = formatNumber(data.allowlistedInstruments, true);
byId("pipe-observe").textContent = formatNumber(completedJobs, true);
byId("pipe-propose").textContent = formatNumber(proposals, true);
byId("pipe-measure").textContent = formatNumber(outcomes, true);
byId("pipe-calibrate").textContent = formatNumber(data.calibration?.length || 0);
byId("pipe-act").textContent = formatNumber(decisions);
byId("freshness").textContent = `Updated ${formatRelative(data.generatedAt)}`;
byId("orbit-value").textContent = formatPercent(accuracy, 0);
byId("accuracy-orbit").style.setProperty("--accuracy", `${(accuracy || 0) * 360}deg`);
byId("perf-resolved").textContent = formatNumber(outcomes);
byId("perf-correct").textContent = formatNumber(correct);
byId("perf-cohorts").textContent = formatNumber(data.calibration?.length || 0);
if (outcomes) byId("performance-note").textContent = `Measured on ${outcomes} matured predictions. Results remain descriptive until the sample is large enough for stable calibration.`;
const replay = data.replay;
byId("replay-status").textContent = replay ? replay.status : "Not started";
byId("replay-articles").textContent = replay ? formatNumber(replay.processed_articles || 0) : "—";
byId("replay-evaluations").textContent = replay ? formatNumber(replay.evaluations || 0) : "—";
byId("replay-watermark").textContent = replay?.watermark_at ? formatRelative(replay.watermark_at) : "—";
byId("runtime-queue").textContent = `${formatNumber(pendingHistorical, true)} pending`;
byId("runtime-coordinator").textContent = pendingHistorical ? "Processing" : "Watching";
if (data.account) {
byId("account-equity").textContent = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(data.account.equity || 0);
byId("account-meta").textContent = `${data.account.broker} · sampled ${formatRelative(data.account.captured_at)}`;
}
renderHypotheses(data.latestPredictions);
renderLedger(data.latestPredictions);
}
api("/admin/api/autonomy/overview").then(render).catch(error => {
byId("hero-title").textContent = "Duriin cannot read its autonomy state.";
byId("hero-description").textContent = error.message;
byId("hypothesis-list").innerHTML = '<div class="empty-state">Runtime data is unavailable.</div>';
toast("Autonomy overview unavailable", true);
});
})();