Files
Duriin-API/public/admin/assets/js/autonomy.js
T
ImBenjiandClaude Opus 5 6f1d1eee2d fix: restart the stalled autonomy pipeline and make calibration honest
Archive ingestion had been dead since 2026-08-02 because nothing in the
compose stack actually ran it. Everything downstream starved from there.

- add ingest + enrichment services. server.js only starts the scheduler when
  DURIIN_RUN_SCHEDULER is not "false", and workers/index.js was not running at
  all, so articles never got event_id/content/has_embedding and the coordinator
  had nothing to lease.
- pass an explicit origin from coordinatorWorker. it was never passed, so
  acceptProposal defaulted to 'live' and 464 historical backfill predictions
  were recorded as live. that also meant verifyEvidence got a null cutoff and
  skipped its date check entirely.
- coarsen cohortKey to event families + horizon buckets. 201 free text event
  types produced 221 cohorts averaging 2.76 samples, so the n>=30 gate could
  never be reached and everything abstained for the wrong reason.
- gate on cohort diversity, not just sample count. one ticker was roughly half
  of all resolved outcomes, so a pure count gate was measuring one company.
  unknown diversity abstains rather than passing.
- resolve the admin archive db explicitly and probe it. it relied on a
  Dockerfile symlink, and without it better-sqlite3 quietly creates an empty
  file and serves a phantom archive.
- clamp implausible future publication dates at ingest.
- pin the db backend to sqlite by default. compose hardcoded postgres "true",
  which would have overridden the operator's own .env on the next redeploy and
  pointed everything at a stale snapshot.

scripts/repair-autonomy-labels.js relabels the affected rows. it is dry run by
default and has not been applied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnNxwxfXSbeNtjvtz5gayb
2026-08-29 21:43:24 +01:00

186 lines
10 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("");
}
const ORIGIN_LABELS = {
live: "Live",
historical: "Historical backfill",
replay: "Walk-forward replay",
};
// live, historical and replay are deliberately never blended. only the live
// row is an edge claim, the other two are how the model was taught.
function renderOriginSplit(byOrigin, livePredictions) {
const host = byId("origin-split");
if (!host) return;
const rows = (byOrigin || []).filter(row => row.origin !== "live");
if (!rows.length) {
host.innerHTML = "";
return;
}
host.innerHTML = rows.map(row => {
const total = Number(row.total || 0);
const label = ORIGIN_LABELS[row.origin] || row.origin;
const accuracy = total ? formatPercent(Number(row.correct || 0) / total) : "—";
return `<div><span>${escapeHtml(label)}</span><strong>${formatNumber(total)} measured · ${accuracy}</strong></div>`;
}).join("");
byId("origin-note").textContent = livePredictions
? "Historical backfill and walk-forward replay are listed separately. Neither counts toward live edge."
: "No live predictions exist yet, so the numbers above are training and replay only — not evidence of live edge.";
}
function cohortCell(check, format) {
if (!check || !check.known) return '<td class="mono muted">unknown</td>';
return `<td class="mono ${check.ok ? "positive" : "negative"}">${format(check.value)} / ${format(check.threshold)}</td>`;
}
function renderCohorts(rows) {
const host = byId("cohort-list");
if (!host) return;
if (!rows?.length) {
host.innerHTML = '<tr><td colspan="6" class="empty-state">No calibration snapshots yet.</td></tr>';
return;
}
host.innerHTML = rows.map(row => {
const checks = row.qualification?.checks || {};
const qualified = Boolean(row.qualification?.qualified);
const reasons = row.qualification?.reasons || [];
const status = qualified
? '<span class="decision-chip qualifies">QUALIFIES</span>'
: `<span class="decision-chip abstain">ABSTAIN</span><div class="hypothesis-channel">${escapeHtml(reasons.join(" · ") || "does not qualify")}</div>`;
const key = row.legacy_cohort_key
? `${escapeHtml(row.cohort_key || "—")}<div class="hypothesis-channel">legacy key, not comparable to current cohorts</div>`
: escapeHtml(row.cohort_key || "—");
return `<tr>
<td class="mono">${key}</td>
<td class="muted">${escapeHtml(row.source || "unknown")}</td>
${cohortCell(checks.sample_size, value => formatNumber(value))}
${cohortCell(checks.distinct_instruments, value => formatNumber(value))}
${cohortCell(checks.top_instrument_share, value => formatPercent(value, 0))}
<td>${status}</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 byOrigin = data.outcomesByOrigin || [];
const livePredictions = (data.predictionsByOrigin || []).filter(row => row.origin === "live")
.reduce((total, row) => total + Number(row.count || 0), 0);
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 live outcomes`
: (livePredictions ? "Live predictions have not matured yet" : "No live predictions yet");
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 live predictions. Results remain descriptive until the sample is large enough for stable calibration.`;
} else if (livePredictions) {
byId("performance-note").textContent = `No live prediction has matured yet — ${formatNumber(livePredictions)} are still open. Nothing here is a live track record.`;
} else {
byId("performance-note").textContent = "No live predictions yet. Everything measured so far is historical backfill or replay, which is training, not a live track record.";
}
renderOriginSplit(byOrigin, livePredictions);
renderCohorts(data.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);
});
})();