6872 lines
355 KiB
JavaScript
6872 lines
355 KiB
JavaScript
const healthStatus = document.getElementById('healthStatus');
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
const instrumentIdInput = document.getElementById('instrumentId');
|
|
const timeframeInput = document.getElementById('timeframe');
|
|
const limitInput = document.getElementById('limit');
|
|
const autoRefreshSecondsInput = document.getElementById('autoRefreshSeconds');
|
|
const executionStatusFilterInput = document.getElementById('executionStatusFilter');
|
|
const sessionFilterInput = document.getElementById('sessionFilter');
|
|
const searchTextInput = document.getElementById('searchText');
|
|
const trustClassFilterInput = document.getElementById('trustClassFilter');
|
|
const lastUpdated = document.getElementById('lastUpdated');
|
|
const activeFilters = document.getElementById('activeFilters');
|
|
const workbenchNav = document.getElementById('workbenchNav');
|
|
const workbenchContent = document.getElementById('workbenchContent');
|
|
const workbenchViewLabel = document.getElementById('workbenchViewLabel');
|
|
const workbenchSections = Array.from(document.querySelectorAll('[data-workbench-section]'));
|
|
const workbenchNavButtons = Array.from(document.querySelectorAll('[data-workbench-target]'));
|
|
const systemOpsLinks = Array.from(document.querySelectorAll('[data-system-ops-target]'));
|
|
const DEFAULT_WORKBENCH_SECTION = 'trader-mode';
|
|
const WORKBENCH_SECTION_ALIASES = {
|
|
'pre-market-plan': 'trading-plan',
|
|
'post-trade-review': 'journal',
|
|
};
|
|
const SYSTEM_OPS_SECTION_IDS = new Set([
|
|
'overview',
|
|
'decision-assistant',
|
|
'trading-status',
|
|
'runtime-supervisor',
|
|
'decision-packet',
|
|
'readiness',
|
|
'session-focus',
|
|
'intraday-candidates',
|
|
'execution-tickets',
|
|
'alerts',
|
|
'visual-reviews',
|
|
'position-management',
|
|
'operator-console',
|
|
'candles',
|
|
]);
|
|
const overviewCards = document.getElementById('overviewCards');
|
|
const decisionAssistantPanel = document.getElementById('decisionAssistantPanel');
|
|
const marketStatePanel = document.getElementById('marketStatePanel');
|
|
const runtimeStatusPanel = document.getElementById('runtimeStatusPanel');
|
|
const decisionPacketPanel = document.getElementById('decisionPacketPanel');
|
|
const trustScorePanel = document.getElementById('trustScorePanel');
|
|
const liveReadinessPanel = document.getElementById('liveReadinessPanel');
|
|
const launchReadinessPanel = document.getElementById('launchReadinessPanel');
|
|
const sessionFocusPanel = document.getElementById('sessionFocusPanel');
|
|
const preMarketPlanPanel = document.getElementById('preMarketPlanPanel');
|
|
const tradeCandidatesPanel = document.getElementById('tradeCandidatesPanel');
|
|
const executionTicketsPanel = document.getElementById('executionTicketsPanel');
|
|
const alertHistoryPanel = document.getElementById('alertHistoryPanel');
|
|
const alertQualityPanel = document.getElementById('alertQualityPanel');
|
|
const strategyBacktestPanel = document.getElementById('strategyBacktestPanel');
|
|
const backtestInstrumentIdInput = document.getElementById('backtestInstrumentId');
|
|
const backtestLimitInput = document.getElementById('backtestLimit');
|
|
const backtestRefreshBtn = document.getElementById('backtestRefreshBtn');
|
|
const visualReviewsPanel = document.getElementById('visualReviewsPanel');
|
|
const positionManagementPanel = document.getElementById('positionManagementPanel');
|
|
const operatorConsolePanel = document.getElementById('operatorConsolePanel');
|
|
const postTradeReviewPanel = document.getElementById('postTradeReviewPanel');
|
|
const canvas = document.getElementById('candlesChart');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
let autoRefreshTimer = null;
|
|
let assistantHistoryKindFilter = loadAssistantHistoryKindFilter();
|
|
let assistantDigestPreferenceMode = loadAssistantDigestPreferenceMode();
|
|
let assistantDigestProfileKey = loadAssistantDigestProfileKey();
|
|
let assistantScanHandoffMarker = loadAssistantScanHandoffMarker();
|
|
let assistantFocusCandidateId = loadAssistantFocusCandidateId();
|
|
let assistantPreferenceDraft = null;
|
|
let assistantPreferenceDraftDirty = false;
|
|
let assistantPreferenceDraftVersion = '';
|
|
let assistantOperatorNoteDraft = {
|
|
body: '',
|
|
context: null,
|
|
};
|
|
let assistantDigestArchiveState = {
|
|
profile_key: '',
|
|
profile_scope: 'selected_profile',
|
|
query_preset: '',
|
|
preferred_history_kind: '',
|
|
digest_mode: '',
|
|
query_text: '',
|
|
created_after: '',
|
|
created_before: '',
|
|
limit: 12,
|
|
next_offset: 0,
|
|
selected_left_id: null,
|
|
selected_right_id: null,
|
|
pinned_baseline_id: null,
|
|
};
|
|
let assistantWorkflowArchiveState = {
|
|
profile_key: '',
|
|
profile_scope: 'selected_profile',
|
|
query_preset: '',
|
|
current_session: '',
|
|
next_phase: '',
|
|
next_artifact_kind: '',
|
|
query_text: '',
|
|
created_after: '',
|
|
created_before: '',
|
|
limit: 12,
|
|
next_offset: 0,
|
|
selected_left_id: null,
|
|
selected_right_id: null,
|
|
pinned_baseline_id: null,
|
|
};
|
|
let assistantWorkflowArchiveBridgeContext = null;
|
|
let assistantDigestArchiveLastExport = null;
|
|
let assistantWorkflowArchiveLastExport = null;
|
|
let assistantSurfaceResolutionFilter = 'all';
|
|
|
|
async function fetchJson(url, options = {}) {
|
|
const fetchOptions = { ...options };
|
|
const timeoutMs = Number(fetchOptions.timeoutMs || 6000);
|
|
delete fetchOptions.timeoutMs;
|
|
const controller = new AbortController();
|
|
const timeout = window.setTimeout(() => controller.abort(), timeoutMs);
|
|
if (!fetchOptions.signal) {
|
|
fetchOptions.signal = controller.signal;
|
|
}
|
|
if (fetchOptions.body) {
|
|
fetchOptions.headers = {
|
|
'Content-Type': 'application/json',
|
|
...(fetchOptions.headers || {}),
|
|
};
|
|
}
|
|
try {
|
|
const response = await fetch(url, fetchOptions);
|
|
if (!response.ok) {
|
|
throw new Error(`${response.status} ${url}`);
|
|
}
|
|
if (response.status === 204) {
|
|
return null;
|
|
}
|
|
return response.json();
|
|
} catch (error) {
|
|
if (error?.name === 'AbortError') {
|
|
throw new Error(`request_timeout ${url}`);
|
|
}
|
|
throw error;
|
|
} finally {
|
|
window.clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
async function postJson(url, body = {}) {
|
|
return fetchJson(url, {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
function downloadJsonFile(filename, payload) {
|
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json;charset=utf-8' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const anchor = document.createElement('a');
|
|
anchor.href = url;
|
|
anchor.download = filename || 'archive-export.json';
|
|
document.body.appendChild(anchor);
|
|
anchor.click();
|
|
document.body.removeChild(anchor);
|
|
window.URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function setText(element, value) {
|
|
if (!element) return;
|
|
if (window.I18N?.setText) {
|
|
window.I18N.setText(element, value);
|
|
return;
|
|
}
|
|
element.textContent = value == null ? '' : String(value);
|
|
}
|
|
|
|
function clearNode(node) {
|
|
if (!node) return;
|
|
while (node.firstChild) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
}
|
|
|
|
function getStoredWorkbenchSection() {
|
|
try {
|
|
const sectionId = normalizeWorkbenchSectionId(window.localStorage?.getItem('aiIctActiveWorkbenchSection') || '');
|
|
return SYSTEM_OPS_SECTION_IDS.has(sectionId) ? '' : sectionId;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function storeWorkbenchSection(sectionId) {
|
|
try {
|
|
window.localStorage?.setItem('aiIctActiveWorkbenchSection', normalizeWorkbenchSectionId(sectionId));
|
|
} catch {
|
|
// Local storage can be disabled in hardened browser sessions.
|
|
}
|
|
}
|
|
|
|
function normalizeWorkbenchSectionId(sectionId) {
|
|
const normalized = String(sectionId || '').trim();
|
|
return WORKBENCH_SECTION_ALIASES[normalized] || normalized;
|
|
}
|
|
|
|
function getWorkbenchSection(sectionId) {
|
|
const normalized = normalizeWorkbenchSectionId(sectionId);
|
|
return workbenchSections.find((section) => section.dataset.workbenchSection === normalized) || null;
|
|
}
|
|
|
|
function getPathWorkbenchSection() {
|
|
const normalizedPath = window.location.pathname.replace(/\/+$/, '') || '/';
|
|
if (normalizedPath === '/trader-mode' || normalizedPath === '/trader-mode.html') {
|
|
return 'trader-mode';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function activateWorkbenchSection(sectionId, options = {}) {
|
|
if (!workbenchSections.length) return;
|
|
const fallback = getWorkbenchSection(DEFAULT_WORKBENCH_SECTION) || workbenchSections[0];
|
|
const target = getWorkbenchSection(sectionId) || fallback;
|
|
const activeId = target.dataset.workbenchSection;
|
|
const activeNavId = SYSTEM_OPS_SECTION_IDS.has(activeId) ? 'system-ops' : activeId;
|
|
workbenchSections.forEach((section) => {
|
|
const isActive = section === target;
|
|
section.classList.toggle('is-active', isActive);
|
|
section.toggleAttribute('hidden', !isActive);
|
|
});
|
|
document.body.dataset.activeWorkbenchSection = activeId;
|
|
workbenchNavButtons.forEach((button) => {
|
|
const isActive = button.dataset.workbenchTarget === activeNavId;
|
|
button.classList.toggle('is-active', isActive);
|
|
button.setAttribute('aria-current', isActive ? 'page' : 'false');
|
|
});
|
|
setText(workbenchViewLabel, target.dataset.workbenchLabel || activeId);
|
|
if (options.persist !== false) {
|
|
storeWorkbenchSection(activeId);
|
|
}
|
|
if (options.updateHash) {
|
|
window.history.replaceState(null, '', `#${activeId}`);
|
|
}
|
|
if (options.scrollToTop && workbenchContent) {
|
|
workbenchContent.scrollTo({ top: 0, behavior: options.smooth ? 'smooth' : 'auto' });
|
|
}
|
|
window.dispatchEvent(new CustomEvent('workbench-section-change', { detail: { sectionId: activeId } }));
|
|
}
|
|
|
|
function initializeWorkbenchNavigation() {
|
|
if (!workbenchNav || !workbenchSections.length) return;
|
|
workbenchNav.addEventListener('click', (event) => {
|
|
const button = event.target.closest?.('[data-workbench-target]');
|
|
if (!button) return;
|
|
activateWorkbenchSection(button.dataset.workbenchTarget, {
|
|
scrollToTop: true,
|
|
smooth: true,
|
|
updateHash: true,
|
|
});
|
|
});
|
|
systemOpsLinks.forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
activateWorkbenchSection(button.dataset.systemOpsTarget, {
|
|
scrollToTop: true,
|
|
smooth: true,
|
|
updateHash: true,
|
|
});
|
|
});
|
|
});
|
|
const hashSection = window.location.hash ? window.location.hash.replace(/^#\/?/, '') : '';
|
|
activateWorkbenchSection(hashSection || getPathWorkbenchSection() || getStoredWorkbenchSection() || DEFAULT_WORKBENCH_SECTION, {
|
|
persist: false,
|
|
scrollToTop: false,
|
|
});
|
|
}
|
|
|
|
function revealPanel(node) {
|
|
const panel = node?.closest?.('.panel') || node;
|
|
const workbenchSection = panel?.closest?.('[data-workbench-section]') || panel;
|
|
if (workbenchSection?.dataset?.workbenchSection) {
|
|
activateWorkbenchSection(workbenchSection.dataset.workbenchSection, {
|
|
scrollToTop: false,
|
|
persist: true,
|
|
updateHash: true,
|
|
});
|
|
}
|
|
if (panel && typeof panel.scrollIntoView === 'function') {
|
|
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
}
|
|
|
|
function loadAssistantHistoryKindFilter() {
|
|
try {
|
|
return window.localStorage?.getItem('assistantHistoryKindFilter') || '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function persistAssistantHistoryKindFilter(value) {
|
|
assistantHistoryKindFilter = value || 'all';
|
|
try {
|
|
window.localStorage?.setItem('assistantHistoryKindFilter', assistantHistoryKindFilter);
|
|
} catch {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
function loadAssistantDigestPreferenceMode() {
|
|
try {
|
|
return window.localStorage?.getItem('assistantDigestPreferenceMode') || '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function persistAssistantDigestPreferenceMode(value) {
|
|
assistantDigestPreferenceMode = value || 'all_reminders';
|
|
try {
|
|
window.localStorage?.setItem('assistantDigestPreferenceMode', assistantDigestPreferenceMode);
|
|
} catch {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
function loadAssistantDigestProfileKey() {
|
|
try {
|
|
return window.localStorage?.getItem('assistantDigestProfileKey') || '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function persistAssistantDigestProfileKey(value) {
|
|
assistantDigestProfileKey = value || 'local_operator';
|
|
try {
|
|
window.localStorage?.setItem('assistantDigestProfileKey', assistantDigestProfileKey);
|
|
} catch {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
async function persistAssistantDigestPreferenceServer({ profileKey, digestMode, preferredHistoryKind, importanceThreshold = 70, payload = null }) {
|
|
try {
|
|
const response = await postJson('/trading/alerts/digest-preferences', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: profileKey || assistantDigestProfileKey || 'local_operator',
|
|
digest_mode: digestMode || 'all_reminders',
|
|
preferred_history_kind: preferredHistoryKind || null,
|
|
importance_threshold: Number(importanceThreshold || 70),
|
|
payload: {
|
|
source: 'decision_assistant_workbench',
|
|
...(payload || {}),
|
|
},
|
|
});
|
|
persistAssistantDigestProfileKey(profileKey || assistantDigestProfileKey || 'local_operator');
|
|
return response;
|
|
} catch {
|
|
// no-op
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function createAssistantPreferenceProfile({ profileKey, profileLabel, sourceProfileKey = null, draft = null }) {
|
|
return postJson('/trading/alerts/digest-preferences/profiles', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: profileKey || null,
|
|
profile_label: profileLabel || null,
|
|
source_profile_key: sourceProfileKey || null,
|
|
digest_mode: draft?.digest_mode || null,
|
|
preferred_history_kind: draft?.preferred_history_kind === 'all' ? null : (draft?.preferred_history_kind || null),
|
|
importance_threshold: draft?.importance_threshold || null,
|
|
payload: draft ? {
|
|
profile_label: draft.profile_label || profileLabel || null,
|
|
history_window_hours: draft.history_window_hours || 24,
|
|
importance_templates: { ...(draft.importance_templates || {}) },
|
|
} : null,
|
|
});
|
|
}
|
|
|
|
async function renameAssistantPreferenceProfile({ profileKey, newProfileKey = null, profileLabel = null }) {
|
|
return postJson(`/trading/alerts/digest-preferences/profiles/${encodeURIComponent(profileKey)}/rename`, {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
new_profile_key: newProfileKey || null,
|
|
profile_label: profileLabel || null,
|
|
});
|
|
}
|
|
|
|
async function deleteAssistantPreferenceProfile(profileKey) {
|
|
return postJson(`/trading/alerts/digest-preferences/profiles/${encodeURIComponent(profileKey)}/delete`, {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
});
|
|
}
|
|
|
|
async function persistAssistantArchiveBaselinesServer({ profileKey, digestSnapshotId = undefined, workflowSnapshotId = undefined }) {
|
|
return postJson('/trading/archive-baselines', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: profileKey || assistantDigestProfileKey || 'local_operator',
|
|
...(digestSnapshotId !== undefined ? { digest_snapshot_id: digestSnapshotId } : {}),
|
|
...(workflowSnapshotId !== undefined ? { workflow_snapshot_id: workflowSnapshotId } : {}),
|
|
});
|
|
}
|
|
|
|
async function persistWorkflowArchiveBridgeStateServer({ summary, exportPayload, profileKey }) {
|
|
const digestSurface = summary?.digest_surface || {};
|
|
const digestProfile = digestSurface.digest_preference_profile || {};
|
|
const saved = await persistAssistantDigestPreferenceServer({
|
|
profileKey: profileKey || assistantDigestProfileKey || 'local_operator',
|
|
digestMode: assistantPreferenceDraft?.digest_mode || digestProfile.persisted_digest_mode || 'all_reminders',
|
|
preferredHistoryKind: assistantPreferenceDraft?.preferred_history_kind === 'all'
|
|
? null
|
|
: (assistantPreferenceDraft?.preferred_history_kind || digestProfile.preferred_history_kind || null),
|
|
importanceThreshold: assistantPreferenceDraft?.importance_threshold || digestProfile.importance_threshold || 70,
|
|
payload: {
|
|
profile_label: assistantPreferenceDraft?.profile_label || digestProfile.profile_label || profileKey || 'local_operator',
|
|
history_window_hours: assistantPreferenceDraft?.history_window_hours || digestProfile.history_window_hours || 24,
|
|
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
|
|
archive_defaults: { ...((assistantPreferenceDraft && assistantPreferenceDraft.archive_defaults) || digestProfile.archive_defaults || {}) },
|
|
workflow_bridge_state: {
|
|
handoff_packet_starter: exportPayload?.handoff_packet_starter || {},
|
|
replay_note_bridge_summary: exportPayload?.replay_note_bridge_summary || {},
|
|
artifact_bundle_summary: exportPayload?.artifact_bundle_summary || {},
|
|
query_context: {
|
|
profile_key: assistantWorkflowArchiveState.profile_key || profileKey || assistantDigestProfileKey || 'local_operator',
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
},
|
|
updated_ts: new Date().toISOString(),
|
|
},
|
|
},
|
|
});
|
|
if (saved) {
|
|
assistantPreferenceDraft = {
|
|
...(assistantPreferenceDraft || {}),
|
|
archive_defaults: { ...((assistantPreferenceDraft && assistantPreferenceDraft.archive_defaults) || digestProfile.archive_defaults || {}) },
|
|
};
|
|
}
|
|
return saved;
|
|
}
|
|
|
|
async function persistWorkflowArchiveCompareWritebackServer({ profileKey, compare, author = 'local_operator' }) {
|
|
return postJson('/trading/workflow-archive/compare-writeback', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: profileKey || assistantDigestProfileKey || 'local_operator',
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
left_id: compare?.left?.snapshot_id ?? assistantWorkflowArchiveState.selected_left_id ?? null,
|
|
right_id: compare?.right?.snapshot_id ?? assistantWorkflowArchiveState.selected_right_id ?? null,
|
|
current_session: assistantWorkflowArchiveState.current_session || compare?.left?.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || compare?.left?.next_phase || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
author,
|
|
note_scope: 'workflow_compare',
|
|
});
|
|
}
|
|
|
|
function loadAssistantScanHandoffMarker() {
|
|
try {
|
|
const raw = window.localStorage?.getItem('assistantScanHandoffMarker');
|
|
return raw ? JSON.parse(raw) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function loadAssistantFocusCandidateId() {
|
|
try {
|
|
return window.localStorage?.getItem('assistantFocusCandidateId') || '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function persistAssistantFocusCandidateId(value) {
|
|
assistantFocusCandidateId = value == null ? '' : String(value);
|
|
try {
|
|
if (assistantFocusCandidateId) {
|
|
window.localStorage?.setItem('assistantFocusCandidateId', assistantFocusCandidateId);
|
|
} else {
|
|
window.localStorage?.removeItem('assistantFocusCandidateId');
|
|
}
|
|
} catch {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
async function persistAssistantFocusCandidateServer(value) {
|
|
try {
|
|
await postJson('/trading/assistant-focus', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantDigestProfileKey || 'local_operator',
|
|
candidate_id: value == null ? null : Number(value),
|
|
});
|
|
} catch {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
async function fetchAssistantDigestArchive(overrides = {}) {
|
|
const append = Boolean(overrides.append);
|
|
const nextState = {
|
|
...assistantDigestArchiveState,
|
|
profile_key: assistantDigestArchiveState.profile_key || assistantDigestProfileKey || 'local_operator',
|
|
...overrides,
|
|
};
|
|
delete nextState.append;
|
|
const params = {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: nextState.profile_key || assistantDigestProfileKey || 'local_operator',
|
|
profile_scope: nextState.profile_scope || 'selected_profile',
|
|
query_preset: nextState.query_preset || null,
|
|
preferred_history_kind: nextState.preferred_history_kind || null,
|
|
digest_mode: nextState.digest_mode || null,
|
|
query_text: nextState.query_text || null,
|
|
created_after: nextState.created_after || null,
|
|
created_before: nextState.created_before || null,
|
|
limit: Number(nextState.limit || 12),
|
|
offset: append ? Number(assistantDigestArchiveState.next_offset || 0) : 0,
|
|
};
|
|
assistantDigestArchiveState = {
|
|
...nextState,
|
|
limit: params.limit,
|
|
profile_key: params.profile_key,
|
|
profile_scope: params.profile_scope,
|
|
query_preset: params.query_preset || '',
|
|
};
|
|
window.__assistantDigestArchiveQuery = params;
|
|
const response = await fetchJson(`/trading/alerts/digest-history-archive?${queryString(params)}`);
|
|
if (append && window.__assistantDigestArchiveResponse?.items?.length) {
|
|
response.items = [...(window.__assistantDigestArchiveResponse.items || []), ...(response.items || [])];
|
|
response.item_count = response.items.length;
|
|
}
|
|
assistantDigestArchiveState.next_offset = Number(response.next_offset || 0);
|
|
window.__assistantDigestArchiveResponse = response;
|
|
if (assistantDigestArchiveState.selected_left_id && assistantDigestArchiveState.selected_right_id) {
|
|
window.__assistantDigestArchiveCompare = await fetchJson(`/trading/alerts/digest-history-archive/compare?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantDigestArchiveState.profile_key || assistantDigestProfileKey || 'local_operator',
|
|
profile_scope: assistantDigestArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantDigestArchiveState.query_preset || '',
|
|
left_id: assistantDigestArchiveState.selected_left_id,
|
|
right_id: assistantDigestArchiveState.selected_right_id,
|
|
})}`);
|
|
} else {
|
|
window.__assistantDigestArchiveCompare = await fetchJson(`/trading/alerts/digest-history-archive/compare?${queryString(params)}`);
|
|
}
|
|
}
|
|
|
|
async function fetchAssistantWorkflowArchive(overrides = {}) {
|
|
const append = Boolean(overrides.append);
|
|
const nextState = {
|
|
...assistantWorkflowArchiveState,
|
|
profile_key: assistantWorkflowArchiveState.profile_key || assistantDigestProfileKey || 'local_operator',
|
|
...overrides,
|
|
};
|
|
delete nextState.append;
|
|
const params = {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: nextState.profile_key || assistantDigestProfileKey || 'local_operator',
|
|
profile_scope: nextState.profile_scope || 'selected_profile',
|
|
query_preset: nextState.query_preset || null,
|
|
current_session: nextState.current_session || null,
|
|
next_phase: nextState.next_phase || null,
|
|
next_artifact_kind: nextState.next_artifact_kind || null,
|
|
query_text: nextState.query_text || null,
|
|
created_after: nextState.created_after || null,
|
|
created_before: nextState.created_before || null,
|
|
limit: Number(nextState.limit || 12),
|
|
offset: append ? Number(assistantWorkflowArchiveState.next_offset || 0) : 0,
|
|
};
|
|
assistantWorkflowArchiveState = {
|
|
...nextState,
|
|
limit: params.limit,
|
|
profile_key: params.profile_key,
|
|
profile_scope: params.profile_scope,
|
|
query_preset: params.query_preset || '',
|
|
};
|
|
window.__assistantWorkflowArchiveQuery = params;
|
|
const response = await fetchJson(`/trading/workflow-archive?${queryString(params)}`);
|
|
if (append && window.__assistantWorkflowArchiveResponse?.items?.length) {
|
|
response.items = [...(window.__assistantWorkflowArchiveResponse.items || []), ...(response.items || [])];
|
|
response.item_count = response.items.length;
|
|
}
|
|
assistantWorkflowArchiveState.next_offset = Number(response.next_offset || 0);
|
|
window.__assistantWorkflowArchiveResponse = response;
|
|
if (assistantWorkflowArchiveState.selected_left_id && assistantWorkflowArchiveState.selected_right_id) {
|
|
window.__assistantWorkflowArchiveCompare = await fetchJson(`/trading/workflow-archive/compare?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || assistantDigestProfileKey || 'local_operator',
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
})}`);
|
|
} else {
|
|
window.__assistantWorkflowArchiveCompare = await fetchJson(`/trading/workflow-archive/compare?${queryString(params)}`);
|
|
}
|
|
}
|
|
|
|
function selectAssistantDigestArchiveSnapshot(side, snapshotId) {
|
|
if (side === 'left') assistantDigestArchiveState.selected_left_id = snapshotId;
|
|
if (side === 'right') assistantDigestArchiveState.selected_right_id = snapshotId;
|
|
}
|
|
|
|
function selectAssistantWorkflowArchiveSnapshot(side, snapshotId) {
|
|
if (side === 'left') assistantWorkflowArchiveState.selected_left_id = snapshotId;
|
|
if (side === 'right') assistantWorkflowArchiveState.selected_right_id = snapshotId;
|
|
}
|
|
|
|
function persistAssistantScanHandoffMarker(marker) {
|
|
assistantScanHandoffMarker = marker || null;
|
|
try {
|
|
if (assistantScanHandoffMarker) {
|
|
window.localStorage?.setItem('assistantScanHandoffMarker', JSON.stringify(assistantScanHandoffMarker));
|
|
} else {
|
|
window.localStorage?.removeItem('assistantScanHandoffMarker');
|
|
}
|
|
} catch {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
function buildAssistantPreferenceDraft(summary) {
|
|
const digestSurface = summary?.digest_surface || {};
|
|
const profile = digestSurface.digest_preference_profile || {};
|
|
const templates = {};
|
|
(profile.importance_templates || []).forEach((item) => {
|
|
if (item?.template) templates[item.template] = Number(item.threshold || item.default_threshold || 0);
|
|
});
|
|
return {
|
|
profile_key: profile.selected_profile_key || assistantDigestProfileKey || 'local_operator',
|
|
profile_label: profile.profile_label || 'Local operator',
|
|
digest_mode: profile.persisted_digest_mode || profile.default_mode || 'all_reminders',
|
|
preferred_history_kind: profile.preferred_history_kind || 'all',
|
|
importance_threshold: Number(profile.importance_threshold || 70),
|
|
history_window_hours: Number(profile.history_window_hours || 24),
|
|
importance_templates: templates,
|
|
focus_candidate_id: profile.focus_candidate_id ?? summary?.focus_workspace_context?.focus_candidate_id ?? null,
|
|
archive_defaults: profile.archive_defaults || {
|
|
digest: { profile_scope: 'selected_profile', query_preset: '' },
|
|
workflow: { profile_scope: 'selected_profile', query_preset: '' },
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildAssistantPreferenceDraftVersion(summary) {
|
|
const profile = summary?.digest_surface?.digest_preference_profile || {};
|
|
return `${profile.selected_profile_key || 'local_operator'}:${profile.persisted_updated_ts || 'none'}`;
|
|
}
|
|
|
|
function ensureAssistantPreferenceDraft(summary) {
|
|
const nextDraft = buildAssistantPreferenceDraft(summary);
|
|
const nextVersion = buildAssistantPreferenceDraftVersion(summary);
|
|
if (!assistantPreferenceDraft || assistantPreferenceDraft.profile_key !== nextDraft.profile_key) {
|
|
assistantPreferenceDraft = nextDraft;
|
|
assistantPreferenceDraftDirty = false;
|
|
assistantPreferenceDraftVersion = nextVersion;
|
|
return;
|
|
}
|
|
if (!assistantPreferenceDraftDirty && assistantPreferenceDraftVersion !== nextVersion) {
|
|
assistantPreferenceDraft = nextDraft;
|
|
assistantPreferenceDraftVersion = nextVersion;
|
|
}
|
|
}
|
|
|
|
function updateAssistantPreferenceDraft(patch = {}) {
|
|
assistantPreferenceDraft = {
|
|
...(assistantPreferenceDraft || {}),
|
|
...(patch || {}),
|
|
};
|
|
assistantPreferenceDraftDirty = true;
|
|
}
|
|
|
|
function updateAssistantPreferenceTemplateDraft(template, threshold) {
|
|
const nextTemplates = {
|
|
...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}),
|
|
};
|
|
nextTemplates[template] = Number(threshold || 0);
|
|
updateAssistantPreferenceDraft({ importance_templates: nextTemplates });
|
|
}
|
|
|
|
function mergeAssistantArchiveDefaults(summary, patch = {}) {
|
|
const profile = summary?.digest_surface?.digest_preference_profile || {};
|
|
const currentDefaults = {
|
|
digest: { profile_scope: 'selected_profile', query_preset: '' },
|
|
workflow: { profile_scope: 'selected_profile', query_preset: '' },
|
|
...((assistantPreferenceDraft && assistantPreferenceDraft.archive_defaults) || profile.archive_defaults || {}),
|
|
};
|
|
return {
|
|
...currentDefaults,
|
|
...(patch || {}),
|
|
};
|
|
}
|
|
|
|
function resetAssistantPreferenceDraftToServer(summary) {
|
|
assistantPreferenceDraft = buildAssistantPreferenceDraft(summary);
|
|
assistantPreferenceDraftDirty = false;
|
|
assistantPreferenceDraftVersion = buildAssistantPreferenceDraftVersion(summary);
|
|
}
|
|
|
|
function resetAssistantPreferenceDraftToDefaults(summary) {
|
|
const profile = summary?.digest_surface?.digest_preference_profile || {};
|
|
const templates = {};
|
|
(profile.importance_templates || []).forEach((item) => {
|
|
if (item?.template) templates[item.template] = Number(item.default_threshold || item.threshold || 0);
|
|
});
|
|
assistantPreferenceDraft = {
|
|
...(assistantPreferenceDraft || buildAssistantPreferenceDraft(summary)),
|
|
digest_mode: 'all_reminders',
|
|
preferred_history_kind: 'all',
|
|
importance_threshold: 70,
|
|
history_window_hours: 24,
|
|
importance_templates: templates,
|
|
};
|
|
assistantPreferenceDraftDirty = true;
|
|
}
|
|
|
|
function snoozeAssistantScanHandoffMarker(seconds, reason = 'operator_snooze') {
|
|
if (!assistantScanHandoffMarker) return;
|
|
const duration = Math.max(60, Number(seconds || 1800));
|
|
persistAssistantScanHandoffMarker({
|
|
...assistantScanHandoffMarker,
|
|
snoozed_until: new Date(Date.now() + duration * 1000).toISOString(),
|
|
snooze_reason: reason,
|
|
});
|
|
}
|
|
|
|
function normalizeAssistantSectionId(title) {
|
|
return String(title || '')
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '');
|
|
}
|
|
|
|
function revealAssistantSection(sectionId) {
|
|
if (!decisionAssistantPanel) return;
|
|
activateWorkbenchSection('decision-assistant', {
|
|
scrollToTop: false,
|
|
persist: true,
|
|
updateHash: true,
|
|
});
|
|
const aliases = {
|
|
watchlist: 'watchlist-review-now',
|
|
notes: 'review-entry',
|
|
replay: 'setup-replay',
|
|
};
|
|
const normalized = aliases[sectionId] || sectionId;
|
|
let target = decisionAssistantPanel.querySelector(`[data-assistant-section="${normalized}"]`);
|
|
if (!target && normalized.startsWith('watchlist')) {
|
|
target = decisionAssistantPanel.querySelector('[data-assistant-section^="watchlist"]');
|
|
}
|
|
if (target && typeof target.scrollIntoView === 'function') {
|
|
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
}
|
|
|
|
function scanCardActionLabel(sectionId) {
|
|
if (sectionId === 'review-entry') return 'Open Review';
|
|
if (sectionId === 'digest') return 'Open Digest';
|
|
if (sectionId === 'external-context') return 'Open Context';
|
|
return 'Open';
|
|
}
|
|
|
|
function filterAssistantReminders(reminders, mode) {
|
|
if (!Array.isArray(reminders) || !reminders.length) return [];
|
|
if (mode === 'high_value_only') {
|
|
return reminders.filter((item) => Boolean(item.high_value));
|
|
}
|
|
if (mode === 'risk_first') {
|
|
return reminders.filter((item) =>
|
|
item.source === 'no_trade_reason_items'
|
|
|| item.importance_template === 'risk_first_change'
|
|
|| item.severity === 'high'
|
|
);
|
|
}
|
|
return reminders;
|
|
}
|
|
|
|
function getAssistantFocusContext(summary = window.__latestDecisionAssistantSummary || null) {
|
|
const assistantSummary = summary || {};
|
|
const focusWorkspace = assistantSummary.focus_workspace_context || {};
|
|
const tiers = assistantSummary.candidate_watchlist_tiers || {};
|
|
const allWatchlistItems = [
|
|
...(tiers.review_now || []),
|
|
...(tiers.strong_watch || []),
|
|
...(tiers.soft_watch || []),
|
|
...(tiers.archive_only || []),
|
|
];
|
|
const focusCandidate = allWatchlistItems.find((item) => String(item.candidate_id || '') === String(focusWorkspace.focus_candidate_id || ''));
|
|
return {
|
|
candidate_id: focusCandidate?.candidate_id ?? focusWorkspace.focus_candidate_id ?? null,
|
|
session_code: focusCandidate?.session_code ?? focusWorkspace.focus_session_code ?? null,
|
|
model_code: focusCandidate?.model_code ?? focusWorkspace.focus_model_code ?? null,
|
|
title: focusCandidate?.title ?? focusWorkspace.focus_title ?? null,
|
|
detail: focusCandidate?.detail ?? focusWorkspace.focus_detail ?? null,
|
|
};
|
|
}
|
|
|
|
function buildOperatorNoteTags(baseTags = [], context = null) {
|
|
const tags = [...(Array.isArray(baseTags) ? baseTags : [])];
|
|
if (context?.candidate_id != null) tags.push(`candidate:${context.candidate_id}`);
|
|
if (context?.session_code) tags.push(`session:${context.session_code}`);
|
|
if (context?.model_code) tags.push(`model:${context.model_code}`);
|
|
return Array.from(new Set(tags.filter(Boolean)));
|
|
}
|
|
|
|
function prefillOperatorNote(body, context = null) {
|
|
const resolvedContext = context || getAssistantFocusContext();
|
|
assistantOperatorNoteDraft = {
|
|
body: body || '',
|
|
context: resolvedContext,
|
|
};
|
|
revealPanel(operatorConsolePanel);
|
|
const textarea = operatorConsolePanel.querySelector('textarea');
|
|
if (textarea) {
|
|
textarea.value = body || '';
|
|
textarea.focus();
|
|
}
|
|
}
|
|
|
|
function buildWorkflowArchiveBridgeContext(item, bridgeMode = 'note') {
|
|
if (!item) return null;
|
|
return {
|
|
bridge_mode: bridgeMode,
|
|
snapshot_id: item.snapshot_id ?? null,
|
|
session_code: item.current_session || null,
|
|
model_code: null,
|
|
next_phase: item.next_phase || null,
|
|
next_artifact_kind: item.next_artifact_kind || null,
|
|
workflow_body: item.workflow_body || '',
|
|
next_artifact_prompt: item.next_artifact_prompt || '',
|
|
phase_briefs: Array.isArray(item.phase_briefs) ? item.phase_briefs : [],
|
|
created_ts: item.created_ts || null,
|
|
};
|
|
}
|
|
|
|
function setWorkflowArchiveBridgeContext(context) {
|
|
assistantWorkflowArchiveBridgeContext = context || null;
|
|
}
|
|
|
|
function buildWorkflowArchiveBridgeNote(item) {
|
|
const phaseLines = (item.phase_briefs || [])
|
|
.map((row) => `- ${row.phase || 'phase'}: ${row.archive_body || row.detail || 'n/a'}`)
|
|
.join('\n');
|
|
return [
|
|
`[workflow-archive] Snapshot #${item.snapshot_id ?? 'n/a'} | ${item.current_session || 'n/a'} -> ${item.next_phase || 'workflow'}`,
|
|
item.workflow_body || '',
|
|
item.next_artifact_prompt ? `Next prompt: ${item.next_artifact_prompt}` : '',
|
|
phaseLines ? `Phase briefs:\n${phaseLines}` : '',
|
|
].filter(Boolean).join('\n\n');
|
|
}
|
|
|
|
function buildWorkflowArchiveCompareReplayNote(compare) {
|
|
const left = compare?.left || {};
|
|
const right = compare?.right || {};
|
|
const changed = (compare?.phase_changes || []).filter((item) => item.changed);
|
|
const changedLabels = changed.map((item) => item.phase || 'phase').join(', ') || 'none';
|
|
return [
|
|
`[workflow-compare] Snapshot #${left.snapshot_id ?? 'n/a'} vs #${right.snapshot_id ?? 'n/a'}`,
|
|
`Session/Phase: ${left.current_session || 'n/a'} -> ${left.next_phase || 'n/a'}`,
|
|
`Changed phases: ${changedLabels}`,
|
|
`Artifact delta: ${compare?.count_delta?.artifact_count ?? 0} | Ready delta: ${compare?.count_delta?.ready_for_handoff_count ?? 0} | Update delta: ${compare?.count_delta?.needs_update_count ?? 0}`,
|
|
compare?.summary || '',
|
|
].filter(Boolean).join('\n\n');
|
|
}
|
|
|
|
function appendSectionTitle(container, title) {
|
|
const element = createElement('div', 'assistant-section-title', title);
|
|
element.dataset.assistantSection = normalizeAssistantSectionId(title);
|
|
container.appendChild(element);
|
|
}
|
|
|
|
function createElement(tag, className, text) {
|
|
const element = document.createElement(tag);
|
|
if (className) element.className = className;
|
|
if (text != null) setText(element, text);
|
|
return element;
|
|
}
|
|
|
|
function formatNumber(value, digits = 2) {
|
|
if (value == null || value === '') return 'n/a';
|
|
const number = Number(value);
|
|
if (!Number.isFinite(number)) return 'n/a';
|
|
return number.toFixed(digits);
|
|
}
|
|
|
|
function formatPercent(value) {
|
|
if (value == null) return 'n/a';
|
|
const number = Number(value);
|
|
if (!Number.isFinite(number)) return 'n/a';
|
|
return `${(number * 100).toFixed(1)}%`;
|
|
}
|
|
|
|
function formatTimestamp(value) {
|
|
if (!value) return 'n/a';
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return String(value);
|
|
}
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
function formatDurationSeconds(value) {
|
|
const seconds = Number(value);
|
|
if (!Number.isFinite(seconds) || seconds < 0) return 'n/a';
|
|
if (seconds < 60) return `${Math.round(seconds)}s`;
|
|
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
|
|
return `${(seconds / 3600).toFixed(1)}h`;
|
|
}
|
|
|
|
function resolveAssistantHandoffAge(marker, fallbackStaleAfterSeconds = 3600) {
|
|
if (!marker?.saved_ts) {
|
|
return {
|
|
status: 'none',
|
|
stale: false,
|
|
snoozed: false,
|
|
age_seconds: null,
|
|
age_label: 'n/a',
|
|
snooze_label: 'n/a',
|
|
warning: 'No persisted handoff marker.',
|
|
};
|
|
}
|
|
const savedAt = new Date(marker.saved_ts);
|
|
if (Number.isNaN(savedAt.getTime())) {
|
|
return {
|
|
status: 'unknown_age',
|
|
stale: true,
|
|
snoozed: false,
|
|
age_seconds: null,
|
|
age_label: 'unknown',
|
|
snooze_label: 'n/a',
|
|
warning: 'Saved handoff timestamp is not readable; reopen before reuse.',
|
|
};
|
|
}
|
|
const snoozedUntil = marker.snoozed_until ? new Date(marker.snoozed_until) : null;
|
|
const snoozed = Boolean(snoozedUntil && !Number.isNaN(snoozedUntil.getTime()) && snoozedUntil.getTime() > Date.now());
|
|
const staleAfterSeconds = Number(marker.stale_after_seconds || marker.handoff_stale_after_seconds || fallbackStaleAfterSeconds || 3600);
|
|
const ageSeconds = Math.max(0, Math.round((Date.now() - savedAt.getTime()) / 1000));
|
|
const stale = ageSeconds > staleAfterSeconds;
|
|
const aging = ageSeconds > staleAfterSeconds / 2;
|
|
return {
|
|
status: snoozed ? 'snoozed_handoff' : (stale ? 'stale_handoff' : (aging ? 'aging_handoff' : 'fresh_handoff')),
|
|
stale,
|
|
snoozed,
|
|
age_seconds: ageSeconds,
|
|
age_label: formatDurationSeconds(ageSeconds),
|
|
stale_after_seconds: staleAfterSeconds,
|
|
snoozed_until: marker.snoozed_until || null,
|
|
snooze_label: snoozed ? formatTimestamp(marker.snoozed_until) : 'n/a',
|
|
warning: snoozed
|
|
? `Saved handoff is snoozed until ${formatTimestamp(marker.snoozed_until)}.`
|
|
: stale
|
|
? (marker.stale_warning || 'Saved handoff is stale; reopen the target section before reuse.')
|
|
: (marker.stale_warning || 'Saved handoff is still inside its freshness window.'),
|
|
};
|
|
}
|
|
|
|
function formatList(values, emptyText = 'n/a') {
|
|
if (!Array.isArray(values) || values.length === 0) return emptyText;
|
|
return values.map((value) => String(value)).join(', ');
|
|
}
|
|
|
|
function formatJsonInline(value, emptyText = 'n/a') {
|
|
if (value == null) return emptyText;
|
|
if (Array.isArray(value) && value.length === 0) return emptyText;
|
|
if (!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length === 0) return emptyText;
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
function statusClass(status) {
|
|
const raw = String(status || 'unknown').toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
return `status-pill status-${raw || 'unknown'}`;
|
|
}
|
|
|
|
function buildStatusPill(status) {
|
|
return createElement('span', statusClass(status), status || 'unknown');
|
|
}
|
|
|
|
function buildMetricCard(label, value, detail = '') {
|
|
const card = createElement('div', 'card');
|
|
card.appendChild(createElement('div', 'metric', label));
|
|
card.appendChild(createElement('div', 'value-small', value));
|
|
if (detail) card.appendChild(createElement('div', 'metric', detail));
|
|
return card;
|
|
}
|
|
|
|
function buildInfoGrid(entries) {
|
|
const grid = createElement('div', 'info-grid');
|
|
entries.forEach(([label, value]) => {
|
|
const row = createElement('div', 'info-row');
|
|
row.appendChild(createElement('div', 'metric', label));
|
|
row.appendChild(createElement('div', 'info-value', value == null || value === '' ? 'n/a' : String(value)));
|
|
grid.appendChild(row);
|
|
});
|
|
return grid;
|
|
}
|
|
|
|
function buildActionBar(actions) {
|
|
const bar = createElement('div', 'actions');
|
|
actions
|
|
.filter((action) => action && typeof action.onClick === 'function')
|
|
.forEach((action) => {
|
|
const button = createElement('button', '', action.label);
|
|
button.type = 'button';
|
|
button.addEventListener('click', action.onClick);
|
|
bar.appendChild(button);
|
|
});
|
|
return bar;
|
|
}
|
|
|
|
function buildItemCard(title, status, entries, actions = [], detail = '') {
|
|
const card = createElement('div', 'card');
|
|
const header = createElement('div', 'card-header');
|
|
header.appendChild(createElement('strong', '', title));
|
|
header.appendChild(buildStatusPill(status));
|
|
card.appendChild(header);
|
|
card.appendChild(buildInfoGrid(entries));
|
|
if (detail) card.appendChild(createElement('div', 'metric card-detail', detail));
|
|
if (actions.length) card.appendChild(buildActionBar(actions));
|
|
return card;
|
|
}
|
|
|
|
function getSearchText() {
|
|
return String(searchTextInput.value || '').trim().toLowerCase();
|
|
}
|
|
|
|
function updateActiveFilters() {
|
|
const active = [];
|
|
const executionStatus = executionStatusFilterInput.value || 'all';
|
|
const session = sessionFilterInput.value || 'all';
|
|
const search = getSearchText();
|
|
const trustClass = trustClassFilterInput.value || 'all';
|
|
if (executionStatus !== 'all') active.push(`execution=${executionStatus}`);
|
|
if (session !== 'all') active.push(`session=${session}`);
|
|
if (trustClass !== 'all') active.push(`trust=${trustClass}`);
|
|
if (search) active.push(`search=${search}`);
|
|
setText(activeFilters, `Filters: ${active.length ? active.join(', ') : 'none'}`);
|
|
}
|
|
|
|
function filterBySearch(items, projector) {
|
|
const search = getSearchText();
|
|
if (!search) return items;
|
|
return items.filter((item) => String(projector(item)).toLowerCase().includes(search));
|
|
}
|
|
|
|
function filterExecutions(items) {
|
|
const statusFilter = executionStatusFilterInput.value || 'all';
|
|
const sessionFilter = sessionFilterInput.value || 'all';
|
|
return filterBySearch(
|
|
items.filter((item) => (statusFilter === 'all' || item.execution_status === statusFilter)
|
|
&& (sessionFilter === 'all' || item.session_code === sessionFilter)),
|
|
(item) => [
|
|
item.execution_id,
|
|
item.execution_status,
|
|
item.side,
|
|
item.session_code,
|
|
item.close_reason,
|
|
item.ai_explanation,
|
|
...(item.error_tags || []),
|
|
].join(' ')
|
|
);
|
|
}
|
|
|
|
function filterBySession(items, accessor) {
|
|
const sessionFilter = sessionFilterInput.value || 'all';
|
|
if (sessionFilter === 'all') return items;
|
|
return items.filter((item) => String(accessor(item) || '') === sessionFilter);
|
|
}
|
|
|
|
function queryString(params) {
|
|
const search = new URLSearchParams();
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value == null || value === '') return;
|
|
search.set(key, String(value));
|
|
});
|
|
return search.toString();
|
|
}
|
|
|
|
async function runAction(url, body = {}) {
|
|
try {
|
|
await postJson(url, body);
|
|
await refreshDashboard();
|
|
} catch (error) {
|
|
showErrorPanel(String(error));
|
|
}
|
|
}
|
|
|
|
async function recordShadowPaperObservation(operatorAgreement = 'pending') {
|
|
await runAction('/trading/shadow-paper/observe', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
timeframe: '1m',
|
|
session_code: sessionFilterInput.value === 'all' ? null : sessionFilterInput.value,
|
|
operator_agreement: operatorAgreement,
|
|
operator_note: operatorAgreement === 'pending'
|
|
? 'Daily shadow observation captured from cockpit.'
|
|
: `Operator marked shadow observation as ${operatorAgreement}.`,
|
|
created_by: 'local_operator',
|
|
});
|
|
}
|
|
|
|
function openExternalContextLink(url) {
|
|
if (!url) return;
|
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
}
|
|
|
|
async function persistExternalContextSurfaceHistory(meta = {}) {
|
|
try {
|
|
return await postJson('/trading/external-context-surface-history', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantDigestProfileKey || 'local_operator',
|
|
surface_id: meta.surface_id || meta.title || 'external_surface',
|
|
title: meta.title || meta.surface_id || 'external surface',
|
|
source_group: meta.source_group || 'external_context',
|
|
selection_kind: meta.selection_kind || 'target_url',
|
|
target_url: meta.target_url || '',
|
|
prompt: meta.prompt || '',
|
|
});
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function acknowledgeExternalContextSurfaceHistory(meta = {}) {
|
|
try {
|
|
return await postJson('/trading/external-context-surface-history/acknowledge', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantDigestProfileKey || 'local_operator',
|
|
surface_id: meta.surface_id || '',
|
|
selected_ts: meta.selected_ts || '',
|
|
acknowledged_by: meta.acknowledged_by || 'local_operator',
|
|
note: meta.note || '',
|
|
});
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function matchesSurfaceResolutionFilter(item, filterId) {
|
|
const normalized = String(filterId || 'all');
|
|
if (normalized === 'all') return true;
|
|
if (normalized === 'with_note') return Boolean(String(item?.revisit_ack_note || '').trim());
|
|
if (normalized === 'without_note') return !String(item?.revisit_ack_note || '').trim();
|
|
if (normalized.startsWith('group:')) return String(item?.source_group || '') === normalized.slice(6);
|
|
if (normalized.startsWith('ack:')) return String(item?.revisit_acknowledged_by || '') === normalized.slice(4);
|
|
return true;
|
|
}
|
|
|
|
function getDefaultSurfaceResolutionFilterOptions(summary = {}) {
|
|
const entryCount = Number(summary?.resolution_history_count ?? 0);
|
|
const noteCount = Number(summary?.resolution_filter_summary?.note_count ?? 0);
|
|
const noteGapCount = Number(summary?.resolution_filter_summary?.note_gap_count ?? 0);
|
|
return [
|
|
{ filter_id: 'all', label: `All Resolved (${entryCount})`, count: entryCount },
|
|
{ filter_id: 'with_note', label: `With Resolution Note (${noteCount})`, count: noteCount },
|
|
{ filter_id: 'without_note', label: `Without Resolution Note (${noteGapCount})`, count: noteGapCount },
|
|
];
|
|
}
|
|
|
|
function openTrackedExternalContextLink(url, meta = null) {
|
|
if (!url) return;
|
|
openExternalContextLink(url);
|
|
if (meta) {
|
|
persistExternalContextSurfaceHistory({ ...meta, target_url: meta.target_url || url })
|
|
.then((result) => {
|
|
if (result) {
|
|
refreshDashboard().catch(() => {});
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
}
|
|
|
|
async function saveOperatorTemplateNote(template) {
|
|
const focusContext = getAssistantFocusContext();
|
|
try {
|
|
await postJson('/trading/operator-notes', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
note_scope: template.note_scope || 'workbench',
|
|
author: 'assistant_template',
|
|
body: template.body || '',
|
|
session_code: focusContext.session_code || (sessionFilterInput.value === 'all' ? null : sessionFilterInput.value),
|
|
candidate_id: focusContext.candidate_id,
|
|
tags: buildOperatorNoteTags(Array.isArray(template.tags) ? template.tags : ['assistant-template'], focusContext),
|
|
});
|
|
await refreshDashboard();
|
|
} catch (error) {
|
|
showErrorPanel(String(error));
|
|
}
|
|
}
|
|
|
|
async function saveOperatorTemplateFeedback(template, outcome) {
|
|
const templateId = String(template?.template_id || 'template');
|
|
const normalizedOutcome = String(outcome || 'useful').toLowerCase().replace(/[^a-z0-9_-]+/g, '_');
|
|
const templateTag = templateId.replace(/_/g, '-');
|
|
const focusContext = getAssistantFocusContext();
|
|
try {
|
|
await postJson('/trading/operator-notes', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
note_scope: template?.note_scope || 'workbench',
|
|
author: 'assistant_template',
|
|
body: `[template-feedback] ${templateId} | outcome:${normalizedOutcome} | ${template?.label || 'template'}`,
|
|
session_code: focusContext.session_code || (sessionFilterInput.value === 'all' ? null : sessionFilterInput.value),
|
|
candidate_id: focusContext.candidate_id,
|
|
tags: buildOperatorNoteTags(['assistant-template', 'template-feedback', templateTag, `template:${templateId}`, `outcome:${normalizedOutcome}`], focusContext),
|
|
});
|
|
await refreshDashboard();
|
|
} catch (error) {
|
|
showErrorPanel(String(error));
|
|
}
|
|
}
|
|
|
|
function renderHealth(health) {
|
|
setText(
|
|
healthStatus,
|
|
[
|
|
health?.app || 'ai-ict',
|
|
health?.database_configured ? 'db ready' : 'db missing',
|
|
health?.session_timezone || 'timezone unknown',
|
|
].join(' | ')
|
|
);
|
|
}
|
|
|
|
function renderOverview(weeklyReview, candidates, tickets, executions) {
|
|
clearNode(overviewCards);
|
|
const openExecutions = executions.filter((item) => item.execution_status === 'open');
|
|
const confirmedTickets = tickets.filter((item) => item.status === 'confirmed');
|
|
const executableCandidates = candidates.filter((item) => item.executable);
|
|
const cards = [
|
|
buildMetricCard('Executable Candidates', String(executableCandidates.length), 'ready to ticket'),
|
|
buildMetricCard('Confirmed Tickets', String(confirmedTickets.length), 'human confirmed'),
|
|
buildMetricCard('Open Positions', String(openExecutions.length), 'live management load'),
|
|
buildMetricCard('Framework Rate', formatPercent(weeklyReview?.framework_execution_rate), 'weekly execution discipline'),
|
|
buildMetricCard('Out-of-Model Rate', formatPercent(weeklyReview?.out_of_model_trade_rate), 'model drift'),
|
|
buildMetricCard('Risk Violations', String(weeklyReview?.risk_violation_count ?? 0), 'weekly count'),
|
|
];
|
|
cards.forEach((card) => overviewCards.appendChild(card));
|
|
}
|
|
|
|
function renderDecisionAssistant(summary) {
|
|
clearNode(decisionAssistantPanel);
|
|
decisionAssistantPanel.classList.add('assistant-dense');
|
|
window.__latestDecisionAssistantSummary = summary;
|
|
if (!summary || summary.status === 'unavailable') {
|
|
decisionAssistantPanel.appendChild(createElement('div', 'empty-state', 'Decision assistant summary unavailable.'));
|
|
return;
|
|
}
|
|
const evidence = summary.evidence_summary || {};
|
|
const traderModeSummary = summary.trader_mode_summary || null;
|
|
const scanSummaryCards = summary.scan_summary_cards || [];
|
|
const scanCompletionSummary = summary.scan_completion_summary || null;
|
|
const scanHandoffSummary = summary.scan_handoff_summary || null;
|
|
const workflowDigest = summary.workflow_digest || [];
|
|
const workflowRhythmSummary = summary.workflow_rhythm_summary || null;
|
|
const workflowSessionCompletionSummary = summary.workflow_session_completion_summary || null;
|
|
const assistantLoopQueue = summary.assistant_loop_queue || [];
|
|
const assistantLoopSummary = summary.assistant_loop_summary || null;
|
|
const opportunities = summary.top_opportunities || [];
|
|
const watchlistTiers = summary.candidate_watchlist_tiers || {};
|
|
const watchlistSummary = summary.candidate_watchlist_summary || {};
|
|
const noTradeItems = summary.no_trade_reason_items || [];
|
|
const evidenceChain = summary.evidence_chain || [];
|
|
const decisionTimeline = summary.decision_timeline || [];
|
|
const explanationDrilldown = summary.explanation_drilldown || [];
|
|
const reasonCitations = summary.reason_citations || [];
|
|
const learningRollup = summary.learning_rollup || null;
|
|
const externalContext = summary.external_context_summary || null;
|
|
const decisionSupportBoundarySummary = summary.decision_support_boundary_summary || null;
|
|
const decisionSupportPromotionSummary = summary.decision_support_promotion_summary || null;
|
|
const liveAnalysisAssistantSummary = summary.live_analysis_assistant_summary || null;
|
|
const readonlyLiveHealthSummary = summary.readonly_live_health_summary || liveAnalysisAssistantSummary?.health_summary || null;
|
|
const shadowPaperValidationSummary = summary.shadow_paper_validation_summary || null;
|
|
const reviewEntrypoints = summary.review_entrypoints || null;
|
|
const personalizedReminders = summary.personalized_reminders || [];
|
|
const digestHistoryPreview = summary.digest_history_preview || [];
|
|
const digestHistoryGroups = summary.digest_history_groups || [];
|
|
const digestSurface = summary.digest_surface || null;
|
|
const digestHistoryBrowser = summary.digest_history_browser || digestSurface?.history_browser || null;
|
|
const digestPreferenceProfile = digestSurface?.digest_preference_profile || null;
|
|
ensureAssistantPreferenceDraft(summary);
|
|
const selectedProfileKey = assistantDigestProfileKey
|
|
|| assistantPreferenceDraft?.profile_key
|
|
|| digestPreferenceProfile?.selected_profile_key
|
|
|| 'local_operator';
|
|
const digestArchiveData = window.__assistantDigestArchiveResponse?.instrument_id === Number(instrumentIdInput.value || 1)
|
|
? window.__assistantDigestArchiveResponse
|
|
: digestSurface?.digest_history_archive;
|
|
const digestArchiveCompare = window.__assistantDigestArchiveCompare?.status
|
|
? window.__assistantDigestArchiveCompare
|
|
: (digestSurface?.digest_archive_compare || summary.digest_archive_compare || null);
|
|
const digestArchiveLongRangeSummary = digestArchiveData?.long_range_trend_summary || digestSurface?.digest_history_archive?.long_range_trend_summary || null;
|
|
const digestArchiveBaselineSummary = digestArchiveData?.baseline_pin_summary || digestSurface?.digest_history_archive?.baseline_pin_summary || null;
|
|
const digestArchiveSavedDefaults = digestPreferenceProfile?.archive_defaults?.digest || { profile_scope: 'selected_profile', query_preset: '' };
|
|
const digestArchiveRankedAggregation = digestArchiveData?.ranked_aggregation_summary || digestSurface?.digest_history_archive?.ranked_aggregation_summary || null;
|
|
const workflowArchiveData = window.__assistantWorkflowArchiveResponse?.instrument_id === Number(instrumentIdInput.value || 1)
|
|
? window.__assistantWorkflowArchiveResponse
|
|
: summary.workflow_artifact_summary?.workflow_history_archive;
|
|
const workflowArchiveCompare = window.__assistantWorkflowArchiveCompare?.status
|
|
? window.__assistantWorkflowArchiveCompare
|
|
: (summary.workflow_artifact_summary?.workflow_archive_compare || summary.workflow_archive_compare || null);
|
|
const workflowArchiveLongRangeSummary = workflowArchiveData?.long_range_trend_summary || summary.workflow_artifact_summary?.workflow_history_archive?.long_range_trend_summary || null;
|
|
const workflowArchiveBaselineSummary = workflowArchiveData?.baseline_pin_summary || summary.workflow_artifact_summary?.workflow_history_archive?.baseline_pin_summary || null;
|
|
const workflowArchiveSavedDefaults = digestPreferenceProfile?.archive_defaults?.workflow || { profile_scope: 'selected_profile', query_preset: '' };
|
|
const workflowArchiveRankedAggregation = workflowArchiveData?.ranked_aggregation_summary || summary.workflow_artifact_summary?.workflow_history_archive?.ranked_aggregation_summary || null;
|
|
const workflowArchiveOperationsSummary = workflowArchiveData?.operations_summary
|
|
|| summary.workflow_artifact_summary?.workflow_archive_operations_summary
|
|
|| summary.workflow_archive_operations_summary
|
|
|| null;
|
|
const workflowArchiveDriftReview = workflowArchiveData?.multi_day_drift_review
|
|
|| summary.workflow_artifact_summary?.workflow_archive_multi_day_drift_review
|
|
|| summary.workflow_archive_multi_day_drift_review
|
|
|| null;
|
|
const workflowArchiveBridgeSummary = workflowArchiveData?.bridge_summary
|
|
|| summary.workflow_artifact_summary?.workflow_archive_bridge_summary
|
|
|| summary.workflow_archive_bridge_summary
|
|
|| null;
|
|
const workflowArchiveBridgePersistenceSummary = summary.workflow_artifact_summary?.workflow_archive_bridge_persistence_summary
|
|
|| summary.workflow_archive_bridge_persistence_summary
|
|
|| null;
|
|
const archiveOperatorOverview = summary.archive_operator_overview || null;
|
|
const workflowArchiveDailyGroups = workflowArchiveData?.daily_groups || [];
|
|
const workflowArchiveSessionGroups = workflowArchiveData?.session_groups || [];
|
|
const workflowArchiveBridgeContext = assistantWorkflowArchiveBridgeContext;
|
|
const setupLearningRollup = summary.setup_learning_rollup || null;
|
|
const focusWorkspaceContext = summary.focus_workspace_context || null;
|
|
if (!assistantDigestProfileKey && digestPreferenceProfile?.selected_profile_key) {
|
|
persistAssistantDigestProfileKey(digestPreferenceProfile.selected_profile_key);
|
|
}
|
|
const continuousWorkspaceRail = summary.continuous_workspace_rail || [];
|
|
const continuousWorkspaceSummary = summary.continuous_workspace_summary || null;
|
|
const workspaceContinuityHandoff = summary.workspace_continuity_handoff || null;
|
|
const effectiveDigestPreferenceMode = assistantPreferenceDraft?.digest_mode
|
|
|| assistantDigestPreferenceMode
|
|
|| digestPreferenceProfile?.persisted_digest_mode
|
|
|| digestPreferenceProfile?.default_mode
|
|
|| 'all_reminders';
|
|
const activeHistoryFilter = assistantPreferenceDraft?.preferred_history_kind
|
|
|| assistantHistoryKindFilter
|
|
|| digestPreferenceProfile?.preferred_history_kind
|
|
|| 'all';
|
|
const visibleReminders = filterAssistantReminders(personalizedReminders, effectiveDigestPreferenceMode);
|
|
const reminderCards = visibleReminders.length ? visibleReminders : personalizedReminders;
|
|
const savedHandoffAge = resolveAssistantHandoffAge(assistantScanHandoffMarker, scanHandoffSummary?.default_stale_after_seconds);
|
|
const allWatchlistItems = [
|
|
...(watchlistTiers.review_now || []),
|
|
...(watchlistTiers.strong_watch || []),
|
|
...(watchlistTiers.soft_watch || []),
|
|
...(watchlistTiers.archive_only || []),
|
|
];
|
|
const resolvedFocusCandidate = allWatchlistItems.find((item) => String(item.candidate_id || '') === String(assistantFocusCandidateId || ''))
|
|
|| allWatchlistItems.find((item) => String(item.candidate_id || '') === String(focusWorkspaceContext?.focus_candidate_id || ''))
|
|
|| allWatchlistItems[0]
|
|
|| null;
|
|
const assistantSections = [
|
|
traderModeSummary ? { title: 'Trader Mode' } : null,
|
|
scanSummaryCards.length ? { title: 'Scan' } : null,
|
|
focusWorkspaceContext ? { title: 'Focus Workspace' } : null,
|
|
assistantLoopQueue.length ? { title: 'Loop Queue' } : null,
|
|
continuousWorkspaceRail.length ? { title: 'Workspace Rail' } : null,
|
|
workflowDigest.length ? { title: 'Workflow' } : null,
|
|
digestPreferenceProfile ? { title: 'Preferences' } : null,
|
|
digestSurface ? { title: 'Digest' } : null,
|
|
reviewEntrypoints ? { title: 'Review Entry' } : null,
|
|
opportunities.length ? { title: 'Opportunities' } : null,
|
|
evidenceChain.length ? { title: 'Evidence' } : null,
|
|
(watchlistTiers.review_now || []).length ? { title: 'Watchlist: Review Now' } : null,
|
|
noTradeItems.length ? { title: 'No-Trade Reasons' } : null,
|
|
setupLearningRollup ? { title: 'Setup Replay' } : null,
|
|
shadowPaperValidationSummary ? { title: 'Shadow Paper' } : null,
|
|
externalContext ? { title: 'External Context' } : null,
|
|
decisionTimeline.length ? { title: 'Timeline' } : null,
|
|
explanationDrilldown.length ? { title: 'Explain' } : null,
|
|
].filter(Boolean);
|
|
if (traderModeSummary) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Trader Mode');
|
|
const opportunity = traderModeSummary.primary_opportunity || {};
|
|
const traderReadout = traderModeSummary.trader_readout || {};
|
|
const traderCard = buildItemCard(
|
|
'Trader Mode / Live Brief',
|
|
traderModeSummary.status_label || traderModeSummary.status || 'unknown',
|
|
[
|
|
['decision now', traderReadout.headline || traderModeSummary.primary_conclusion || 'n/a'],
|
|
['why no trade', traderReadout.why_no_trade || traderModeSummary.why_no_trade || 'analysis only'],
|
|
['watch next', traderReadout.watch_next || traderModeSummary.watch_next || traderModeSummary.next_action || 'continue_observation'],
|
|
['focus', `${opportunity.title || 'no clean opportunity'} / ${opportunity.tier || 'n/a'} / ${opportunity.status || 'n/a'}`],
|
|
['focus reason', traderReadout.focus_reason || traderModeSummary.focus_quality_explanation || 'n/a'],
|
|
['data state', traderReadout.data_state || `${traderModeSummary.data_confidence || 'unknown'} / ${traderModeSummary.status_color || 'unknown'}`],
|
|
['no candidate state', traderReadout.no_candidate_state || traderModeSummary.no_candidate_state || 'n/a'],
|
|
['trade action', traderModeSummary.primary_question_trade_action || 'not_a_buy_sell_instruction'],
|
|
],
|
|
(traderModeSummary.drilldown_sections || []).slice(0, 6).map((section) => ({
|
|
label: `Open ${section}`,
|
|
onClick: () => revealAssistantSection(normalizeAssistantSectionId(section)),
|
|
})),
|
|
traderModeSummary.operator_summary || ''
|
|
);
|
|
traderCard.classList.add('trader-mode-card');
|
|
traderCard.classList.add(`trader-mode-${traderModeSummary.status_color || 'unknown'}`);
|
|
decisionAssistantPanel.appendChild(traderCard);
|
|
}
|
|
if (scanSummaryCards.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Scan');
|
|
const scanGrid = createElement('div', 'cards assistant-scan-grid');
|
|
scanSummaryCards.forEach((item) => {
|
|
const card = buildItemCard(
|
|
item.title || 'summary',
|
|
item.status || 'n/a',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
['context', item.context || 'n/a'],
|
|
['rank', String(item.rank ?? 'n/a')],
|
|
['scan step', item.is_next_scan_step ? 'next' : String(item.scan_step_index ?? 'n/a')],
|
|
['completion', item.completion_state || 'n/a'],
|
|
['completion hint', item.completion_hint || 'n/a'],
|
|
['handoff', item.handoff_status || 'n/a'],
|
|
['handoff marker', item.handoff_marker || 'n/a'],
|
|
['handoff prompt', item.handoff_prompt || 'n/a'],
|
|
['handoff stale after', formatDurationSeconds(item.handoff_stale_after_seconds)],
|
|
['handoff stale warning', item.handoff_stale_warning || 'n/a'],
|
|
['next step', item.transition_hint || 'n/a'],
|
|
],
|
|
[
|
|
...(item.target_section
|
|
? [{
|
|
label: item.action_label || scanCardActionLabel(item.target_section),
|
|
onClick: () => revealAssistantSection(item.target_section),
|
|
}]
|
|
: []),
|
|
item.handoff_marker
|
|
? {
|
|
label: 'Mark Handoff',
|
|
onClick: () => {
|
|
persistAssistantScanHandoffMarker({
|
|
marker: item.handoff_marker,
|
|
title: item.title || 'Scan',
|
|
target_section: item.target_section || '',
|
|
status: item.handoff_status || '',
|
|
stale_after_seconds: item.handoff_stale_after_seconds || scanHandoffSummary?.default_stale_after_seconds || 3600,
|
|
stale_warning: item.handoff_stale_warning || '',
|
|
saved_ts: new Date().toISOString(),
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
}
|
|
: null,
|
|
].filter(Boolean)
|
|
);
|
|
if (item.target_section) {
|
|
card.classList.add('clickable-card');
|
|
card.addEventListener('click', () => revealAssistantSection(item.target_section));
|
|
}
|
|
if (item.emphasis) {
|
|
card.classList.add(`assistant-scan-${item.emphasis}`);
|
|
}
|
|
if (item.is_next_scan_step) {
|
|
card.classList.add('assistant-scan-next');
|
|
}
|
|
scanGrid.appendChild(card);
|
|
});
|
|
decisionAssistantPanel.appendChild(scanGrid);
|
|
if (scanCompletionSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['scan completion', scanCompletionSummary.status || 'idle'],
|
|
['open scan steps', String(scanCompletionSummary.open_count ?? 0)],
|
|
['blocked scan steps', String(scanCompletionSummary.blocked_count ?? 0)],
|
|
['refresh-needed steps', String(scanCompletionSummary.refresh_needed_count ?? 0)],
|
|
['ready steps', String(scanCompletionSummary.ready_count ?? 0)],
|
|
['next scan card', scanCompletionSummary.next_title || 'n/a'],
|
|
['next scan target', scanCompletionSummary.next_target_section || 'n/a'],
|
|
['next scan hint', scanCompletionSummary.next_hint || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (scanHandoffSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['scan handoff', scanHandoffSummary.status || 'idle'],
|
|
['handoff markers', String(scanHandoffSummary.marker_count ?? 0)],
|
|
['next handoff marker', scanHandoffSummary.next_marker || 'n/a'],
|
|
['next handoff target', scanHandoffSummary.next_target_section || 'n/a'],
|
|
['next handoff prompt', scanHandoffSummary.next_prompt || 'n/a'],
|
|
['handoff stale policy', scanHandoffSummary.handoff_stale_policy || 'n/a'],
|
|
['handoff stale warnings', String(scanHandoffSummary.stale_warning_count ?? 0)],
|
|
['handoff cleanup candidates', String(scanHandoffSummary.cleanup_candidate_count ?? 0)],
|
|
['handoff overdue cleanup', String(scanHandoffSummary.overdue_cleanup_count ?? 0)],
|
|
['handoff next cleanup', scanHandoffSummary.next_cleanup_title || scanHandoffSummary.next_cleanup_marker || 'n/a'],
|
|
['handoff cleanup prompt', scanHandoffSummary.next_cleanup_prompt || 'n/a'],
|
|
['handoff lane groups', formatList((scanHandoffSummary.handoff_lane_groups || []).map((item) => `${item.label || item.status}: ${item.count ?? 0}`))],
|
|
['handoff lane overdue', formatList((scanHandoffSummary.handoff_lane_groups || []).filter((item) => Number(item.overdue_count || 0) > 0).map((item) => `${item.label || item.status}: ${item.overdue_count}`))],
|
|
['handoff lifecycle', formatJsonInline(scanHandoffSummary.handoff_lifecycle_summary?.state_counts || {})],
|
|
['resume queue', String(scanHandoffSummary.resume_queue_count ?? 0)],
|
|
['resume queue items', formatList((scanHandoffSummary.operator_resume_queue || []).map((item) => `${item.resume_action_label || 'Resume'}: ${item.title || item.target_section}`))],
|
|
['resume persistence', scanHandoffSummary.resume_queue_persistence_status || 'n/a'],
|
|
['resume persistence key', scanHandoffSummary.resume_queue_persistence_key || 'n/a'],
|
|
['resolved lane states', formatJsonInline(scanHandoffSummary.resolved_lane_state_counts || {})],
|
|
['resolved lanes', String(scanHandoffSummary.resolved_lane_count ?? 0)],
|
|
['unresolved lanes', String(scanHandoffSummary.unresolved_lane_count ?? 0)],
|
|
['next resolved lane prompt', scanHandoffSummary.next_resolved_lane_prompt || 'n/a'],
|
|
['resume history', String(scanHandoffSummary.resume_history_count ?? 0)],
|
|
['resume latest event', scanHandoffSummary.resume_history_latest_event || 'n/a'],
|
|
['stale persistence cleanup', String(scanHandoffSummary.stale_persistence_cleanup_count ?? 0)],
|
|
['next persistence cleanup', scanHandoffSummary.next_stale_persistence_cleanup_key || 'n/a'],
|
|
['persistence cleanup prompt', scanHandoffSummary.next_stale_persistence_cleanup_prompt || 'n/a'],
|
|
['resume backlog groups', formatList((scanHandoffSummary.resume_backlog_groups || []).map((item) => `${item.label || item.resume_backlog_group}: ${item.count ?? 0}`))],
|
|
['grouped resume summary', formatList((scanHandoffSummary.grouped_resume_handoff_summary || []).map((item) => item.summary || `${item.label}: ${item.count ?? 0}`))],
|
|
['cleanup action ready', String(scanHandoffSummary.cleanup_action_ready_count ?? 0)],
|
|
['next cleanup action', scanHandoffSummary.next_cleanup_action_label || 'n/a'],
|
|
['next cleanup action prompt', scanHandoffSummary.next_cleanup_action_prompt || 'n/a'],
|
|
['cleanup execution log', String(scanHandoffSummary.cleanup_execution_log_count ?? 0)],
|
|
['cleanup execution events', formatList((scanHandoffSummary.cleanup_execution_log || []).map((item) => `${item.event}: ${item.status}`))],
|
|
['next cleanup execution', scanHandoffSummary.next_cleanup_execution_prompt || 'n/a'],
|
|
['next resume action', scanHandoffSummary.next_resume_action_label || 'n/a'],
|
|
['next resume prompt', scanHandoffSummary.next_resume_prompt || 'n/a'],
|
|
['handoff cleanup recommendation', scanHandoffSummary.handoff_cleanup_recommendation || 'n/a'],
|
|
['default stale after', formatDurationSeconds(scanHandoffSummary.default_stale_after_seconds)],
|
|
['saved handoff marker', assistantScanHandoffMarker?.marker || 'n/a'],
|
|
['saved handoff title', assistantScanHandoffMarker?.title || 'n/a'],
|
|
['saved handoff time', formatTimestamp(assistantScanHandoffMarker?.saved_ts)],
|
|
['saved handoff age', savedHandoffAge.age_label],
|
|
['saved handoff status', savedHandoffAge.status],
|
|
['saved handoff snoozed until', savedHandoffAge.snooze_label],
|
|
['saved handoff snooze reason', assistantScanHandoffMarker?.snooze_reason || 'n/a'],
|
|
['saved handoff warning', savedHandoffAge.warning],
|
|
])
|
|
);
|
|
const handoffActions = [];
|
|
if (scanHandoffSummary.next_marker) {
|
|
const nextHandoff = (scanHandoffSummary.handoff_markers || []).find((item) => item.handoff_marker === scanHandoffSummary.next_marker) || {};
|
|
handoffActions.push({
|
|
label: 'Persist Next Handoff',
|
|
onClick: () => {
|
|
persistAssistantScanHandoffMarker({
|
|
marker: scanHandoffSummary.next_marker,
|
|
title: nextHandoff.title || scanHandoffSummary.next_target_section || 'Scan',
|
|
target_section: scanHandoffSummary.next_target_section || '',
|
|
status: 'next_handoff',
|
|
stale_after_seconds: nextHandoff.stale_after_seconds || scanHandoffSummary.default_stale_after_seconds || 3600,
|
|
stale_warning: nextHandoff.stale_warning || '',
|
|
snooze_seconds: nextHandoff.snooze_seconds || 1800,
|
|
saved_ts: new Date().toISOString(),
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
});
|
|
}
|
|
if (assistantScanHandoffMarker?.target_section) {
|
|
handoffActions.push({
|
|
label: 'Open Saved Handoff',
|
|
onClick: () => revealAssistantSection(assistantScanHandoffMarker.target_section),
|
|
});
|
|
}
|
|
if (assistantScanHandoffMarker) {
|
|
(scanHandoffSummary.snooze_options || []).forEach((option) => {
|
|
handoffActions.push({
|
|
label: option.label || 'Snooze',
|
|
onClick: () => {
|
|
snoozeAssistantScanHandoffMarker(option.seconds || assistantScanHandoffMarker.snooze_seconds || 1800, 'operator_snooze');
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
});
|
|
});
|
|
if (assistantScanHandoffMarker.snoozed_until) {
|
|
handoffActions.push({
|
|
label: 'Clear Snooze',
|
|
onClick: () => {
|
|
const nextMarker = { ...assistantScanHandoffMarker };
|
|
delete nextMarker.snoozed_until;
|
|
delete nextMarker.snooze_reason;
|
|
persistAssistantScanHandoffMarker(nextMarker);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
});
|
|
}
|
|
handoffActions.push({
|
|
label: 'Clear Handoff',
|
|
onClick: () => {
|
|
persistAssistantScanHandoffMarker(null);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
});
|
|
}
|
|
if (handoffActions.length) {
|
|
decisionAssistantPanel.appendChild(buildActionBar(handoffActions));
|
|
}
|
|
}
|
|
}
|
|
if (focusWorkspaceContext) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Focus Workspace');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['focus status', focusWorkspaceContext.status || 'idle'],
|
|
['focus title', resolvedFocusCandidate?.title || focusWorkspaceContext.focus_title || 'n/a'],
|
|
['focus session', resolvedFocusCandidate?.session_code || focusWorkspaceContext.focus_session_code || 'n/a'],
|
|
['focus tier', resolvedFocusCandidate?.tier || focusWorkspaceContext.focus_tier || 'n/a'],
|
|
['focus model', resolvedFocusCandidate?.model_code || focusWorkspaceContext.focus_model_code || 'n/a'],
|
|
['focus detail', resolvedFocusCandidate?.detail || focusWorkspaceContext.focus_detail || 'n/a'],
|
|
['focus persistence', focusWorkspaceContext.focus_persistence_status || 'n/a'],
|
|
['focus profile', focusWorkspaceContext.focus_profile_label || selectedProfileKey],
|
|
['history window', String(focusWorkspaceContext.focus_history_window_hours ?? 'n/a')],
|
|
['focus candidate notes', String(reviewEntrypoints?.focus_note_count ?? 0)],
|
|
['next focus prompt', focusWorkspaceContext.next_focus_prompt || 'n/a'],
|
|
])
|
|
);
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Persist Focus',
|
|
onClick: async () => {
|
|
if (!resolvedFocusCandidate?.candidate_id) return;
|
|
persistAssistantFocusCandidateId(resolvedFocusCandidate.candidate_id);
|
|
await persistAssistantFocusCandidateServer(resolvedFocusCandidate.candidate_id);
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Clear Focus',
|
|
onClick: async () => {
|
|
persistAssistantFocusCandidateId('');
|
|
await persistAssistantFocusCandidateServer(null);
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
])
|
|
);
|
|
const focusGrid = createElement('div', 'cards assistant-workspace-rail-grid');
|
|
(focusWorkspaceContext.workspace_sections || []).forEach((item) => {
|
|
const card = buildItemCard(
|
|
item.section || 'section',
|
|
item.status || 'idle',
|
|
[
|
|
['prompt', item.prompt || 'n/a'],
|
|
['target', item.target_section || 'n/a'],
|
|
],
|
|
[{
|
|
label: `Open ${item.section || 'Section'}`,
|
|
onClick: () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.section || '')),
|
|
}]
|
|
);
|
|
card.classList.add('clickable-card');
|
|
card.addEventListener('click', () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.section || '')));
|
|
focusGrid.appendChild(card);
|
|
});
|
|
decisionAssistantPanel.appendChild(focusGrid);
|
|
}
|
|
if (assistantLoopQueue.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Loop Queue');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['loop queue', assistantLoopSummary?.status || 'idle'],
|
|
['loop items', String(assistantLoopSummary?.queue_count ?? assistantLoopQueue.length)],
|
|
['loop lanes', formatJsonInline(assistantLoopSummary?.lane_counts || {})],
|
|
['next lane', assistantLoopSummary?.next_lane || 'n/a'],
|
|
['next action', assistantLoopSummary?.next_action_label || 'n/a'],
|
|
['next target', assistantLoopSummary?.next_target_section || 'n/a'],
|
|
['next prompt', assistantLoopSummary?.next_prompt || 'n/a'],
|
|
['queue hint', assistantLoopSummary?.queue_hint || 'n/a'],
|
|
])
|
|
);
|
|
const loopGrid = createElement('div', 'cards assistant-workspace-rail-grid');
|
|
assistantLoopQueue.forEach((item) => {
|
|
const card = buildItemCard(
|
|
item.title || item.queue_id || 'loop item',
|
|
item.status || 'active',
|
|
[
|
|
['lane', item.lane || 'workspace'],
|
|
['prompt', item.prompt || 'n/a'],
|
|
['target', item.target_section || 'n/a'],
|
|
['priority', String(item.priority_rank ?? 'n/a')],
|
|
],
|
|
[{
|
|
label: item.action_label || 'Open',
|
|
onClick: () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.title || 'loop')),
|
|
}]
|
|
);
|
|
card.classList.add('clickable-card');
|
|
card.addEventListener('click', () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.title || 'loop')));
|
|
loopGrid.appendChild(card);
|
|
});
|
|
decisionAssistantPanel.appendChild(loopGrid);
|
|
}
|
|
if (continuousWorkspaceRail.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Workspace Rail');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['continuous workspace', continuousWorkspaceSummary?.status || summary.continuous_workspace_status || 'idle'],
|
|
['workspace rail items', String(continuousWorkspaceSummary?.rail_count ?? continuousWorkspaceRail.length)],
|
|
['workspace active/watch', `${continuousWorkspaceSummary?.active_count ?? 0} / ${continuousWorkspaceSummary?.watch_count ?? 0}`],
|
|
['workspace attention', String(continuousWorkspaceSummary?.attention_count ?? 0)],
|
|
['workspace next', continuousWorkspaceSummary?.next_section || 'n/a'],
|
|
['workspace next action', continuousWorkspaceSummary?.next_action || 'n/a'],
|
|
['workspace next prompt', continuousWorkspaceSummary?.next_handoff_prompt || 'n/a'],
|
|
['workspace path', continuousWorkspaceSummary?.rail_path || formatList(continuousWorkspaceRail.map((item) => item.section || 'section'))],
|
|
['continuity hint', continuousWorkspaceSummary?.continuity_hint || 'n/a'],
|
|
['workspace handoff', workspaceContinuityHandoff?.status || 'n/a'],
|
|
['ready sections', formatList(workspaceContinuityHandoff?.ready_sections || [])],
|
|
['watch sections', formatList(workspaceContinuityHandoff?.watch_sections || [])],
|
|
['loop sections', formatList(workspaceContinuityHandoff?.loop_sections || [])],
|
|
['next loop prompt', workspaceContinuityHandoff?.next_loop_prompt || 'n/a'],
|
|
])
|
|
);
|
|
const railGrid = createElement('div', 'cards assistant-workspace-rail-grid');
|
|
continuousWorkspaceRail.forEach((item) => {
|
|
const card = buildItemCard(
|
|
item.navigation_label || item.section || 'workspace',
|
|
item.status || 'idle',
|
|
[
|
|
['lane', item.lane_group || 'workspace'],
|
|
['attention', item.attention_level || 'low'],
|
|
['summary', item.summary || 'n/a'],
|
|
['next action', item.next_action || 'n/a'],
|
|
['handoff prompt', item.handoff_prompt || 'n/a'],
|
|
['completion hint', item.completion_hint || 'n/a'],
|
|
['target', item.target_section || 'n/a'],
|
|
],
|
|
[{
|
|
label: item.next_action || 'Open Section',
|
|
onClick: () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.section || '')),
|
|
}]
|
|
);
|
|
card.classList.add('clickable-card');
|
|
card.addEventListener('click', () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.section || '')));
|
|
railGrid.appendChild(card);
|
|
});
|
|
decisionAssistantPanel.appendChild(railGrid);
|
|
}
|
|
if (liveAnalysisAssistantSummary) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Read-only Live Analysis');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['read-only live analysis', liveAnalysisAssistantSummary.status || 'n/a'],
|
|
['ready_for_readonly_live_analysis', String(Boolean(liveAnalysisAssistantSummary.ready_for_readonly_live_analysis))],
|
|
['must_not_trade', String(Boolean(liveAnalysisAssistantSummary.must_not_trade))],
|
|
['No order submission', String(Boolean(liveAnalysisAssistantSummary.launch_boundary?.no_order_submission))],
|
|
['Not a buy/sell instruction', String(Boolean(liveAnalysisAssistantSummary.launch_boundary?.not_buy_sell_instruction))],
|
|
['Execution remains disabled', String(Boolean(liveAnalysisAssistantSummary.launch_boundary?.execution_remains_disabled))],
|
|
['health', readonlyLiveHealthSummary?.status || 'n/a'],
|
|
['runtime health', readonlyLiveHealthSummary?.runtime_health || 'n/a'],
|
|
['market freshness', readonlyLiveHealthSummary?.market_freshness || 'n/a'],
|
|
['broker/account context', readonlyLiveHealthSummary?.broker_account_context || 'n/a'],
|
|
['latest decision packet timestamp', formatTimestamp(readonlyLiveHealthSummary?.latest_decision_packet_timestamp)],
|
|
['latest review packet timestamp', formatTimestamp(readonlyLiveHealthSummary?.latest_review_packet_timestamp)],
|
|
['current candidate count', String(readonlyLiveHealthSummary?.current_candidate_count ?? 0)],
|
|
['current blocker', readonlyLiveHealthSummary?.current_blocker || 'none'],
|
|
['recommendation', readonlyLiveHealthSummary?.recommendation || 'n/a'],
|
|
['allowed actions', formatList(liveAnalysisAssistantSummary.allowed_actions || [])],
|
|
['blocked actions', formatList(liveAnalysisAssistantSummary.blocked_actions || [])],
|
|
])
|
|
);
|
|
}
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['focus mode', summary.focus_mode || 'monitor_and_wait'],
|
|
['headline', summary.headline || 'n/a'],
|
|
['market posture', summary.market_posture || 'n/a'],
|
|
['risk posture', summary.risk_posture || 'n/a'],
|
|
['attention reason', summary.attention_reason || 'n/a'],
|
|
['current session', summary.current_session || 'n/a'],
|
|
['changed since last', summary.changed_since_last?.summary || 'n/a'],
|
|
['decision', `${evidence.decision_status || 'unknown'} / ${evidence.decision_action || 'monitor'}`],
|
|
['trust / mode', `${evidence.trust_classification || 'unknown'} / ${evidence.operator_mode || 'unknown'}`],
|
|
['market truth', `${evidence.market_freshness || 'unknown'} | ${evidence.stream_status || 'unknown'} | ${evidence.data_gap_status || 'unknown'}`],
|
|
['launch mode', evidence.launch_mode || 'unknown'],
|
|
['live analysis assistant ready', String(Boolean(evidence.live_analysis_assistant_ready))],
|
|
['readonly live health', evidence.readonly_live_health_status || 'n/a'],
|
|
['readonly account context', evidence.readonly_account_context_status || 'n/a'],
|
|
['must_not_trade', String(Boolean(evidence.must_not_trade))],
|
|
['shadow paper validation', evidence.shadow_paper_status || shadowPaperValidationSummary?.status || 'n/a'],
|
|
['next shadow gate', evidence.next_shadow_paper_gate || shadowPaperValidationSummary?.next_validation_gate || 'n/a'],
|
|
['top opportunity', opportunities[0]?.title || 'n/a'],
|
|
['watchlist tiers', `review ${watchlistSummary.review_now_count ?? 0} | strong ${watchlistSummary.strong_watch_count ?? 0} | soft ${watchlistSummary.soft_watch_count ?? 0}`],
|
|
['next watchlist focus', watchlistSummary.focus_matrix?.next_focus?.title || 'n/a'],
|
|
['watchlist focus prompt', watchlistSummary.next_watchlist_focus_prompt || 'n/a'],
|
|
['section nav', assistantSections.map((item) => item.title).join(' | ') || 'n/a'],
|
|
['no-trade reasons', formatList(summary.no_trade_reasons || [])],
|
|
['next actions', formatList(summary.next_actions || [])],
|
|
])
|
|
);
|
|
if (decisionSupportBoundarySummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['operator boundary', decisionSupportBoundarySummary.label || 'n/a'],
|
|
['boundary summary', decisionSupportBoundarySummary.operator_summary || 'n/a'],
|
|
['current model boundary', decisionSupportBoundarySummary.current_model_boundary || 'n/a'],
|
|
['live execution boundary', decisionSupportBoundarySummary.live_execution_boundary || 'n/a'],
|
|
['allowed operator actions', formatList(decisionSupportBoundarySummary.allowed_operator_actions || [])],
|
|
['blocked operator actions', formatList(decisionSupportBoundarySummary.blocked_operator_actions || [])],
|
|
['boundary next gate', decisionSupportBoundarySummary.next_boundary_gate || 'n/a'],
|
|
['boundary gate reason', decisionSupportBoundarySummary.next_boundary_gate_reason || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (decisionSupportPromotionSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['decision support promotion', decisionSupportPromotionSummary.status || 'n/a'],
|
|
['strict decision support ready', String(Boolean(decisionSupportPromotionSummary.strict_decision_support_ready))],
|
|
['current decision support ready', String(Boolean(decisionSupportPromotionSummary.current_decision_support_ready))],
|
|
['promotion model', decisionSupportPromotionSummary.candidate_model_code || 'n/a'],
|
|
['promotion classification', decisionSupportPromotionSummary.candidate_classification || 'n/a'],
|
|
['operator feedback samples', String(decisionSupportPromotionSummary.operator_review_sample_count ?? 0)],
|
|
['promotion criteria', `${decisionSupportPromotionSummary.passed_criteria_count ?? 0}/${decisionSupportPromotionSummary.required_criteria_count ?? 0}`],
|
|
['next promotion gate', decisionSupportPromotionSummary.next_promotion_gate || 'n/a'],
|
|
['promotion summary', decisionSupportPromotionSummary.operator_summary || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (shadowPaperValidationSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{ label: 'Record Shadow Observation', onClick: () => recordShadowPaperObservation('pending') },
|
|
{ label: 'Agree Shadow Observation', onClick: () => recordShadowPaperObservation('agree') },
|
|
{ label: 'Disagree Shadow Observation', onClick: () => recordShadowPaperObservation('disagree') },
|
|
])
|
|
);
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['shadow paper validation', shadowPaperValidationSummary.status || 'n/a'],
|
|
['shadow validation status', shadowPaperValidationSummary.status || 'n/a'],
|
|
['shadow observed days', `${shadowPaperValidationSummary.observed_shadow_day_count ?? 0}/${shadowPaperValidationSummary.window_days ?? 0}`],
|
|
['shadow observation samples', String(shadowPaperValidationSummary.shadow_observation_count ?? 0)],
|
|
['shadow observation days', String(shadowPaperValidationSummary.shadow_observation_day_count ?? 0)],
|
|
['shadow candidates', String(shadowPaperValidationSummary.candidate_count ?? 0)],
|
|
['paper ticket samples', String(shadowPaperValidationSummary.paper_ticket_count ?? 0)],
|
|
['paper execution samples', String(shadowPaperValidationSummary.paper_execution_count ?? 0)],
|
|
['operator feedback samples', String(shadowPaperValidationSummary.operator_feedback_count ?? 0)],
|
|
['operator observation feedback', String(shadowPaperValidationSummary.operator_observation_feedback_count ?? 0)],
|
|
['journal samples', String(shadowPaperValidationSummary.journal_entry_count ?? 0)],
|
|
['risk violations', String(shadowPaperValidationSummary.risk_violation_count ?? 0)],
|
|
['shadow criteria', `${shadowPaperValidationSummary.passed_criteria_count ?? 0}/${shadowPaperValidationSummary.required_criteria_count ?? 0}`],
|
|
['next shadow gate', shadowPaperValidationSummary.next_validation_gate || 'n/a'],
|
|
['sample collection next actions', formatList(shadowPaperValidationSummary.sample_collection_plan?.next_actions || [])],
|
|
['real live after shadow', shadowPaperValidationSummary.execution_boundary || 'real_live_still_blocked'],
|
|
['shadow validation summary', shadowPaperValidationSummary.operator_summary || 'n/a'],
|
|
['shadow daily coverage', formatList((shadowPaperValidationSummary.daily_coverage || []).slice(-5).map((item) => `${item.date}: o${item.shadow_observation_count}/c${item.candidate_count}/p${item.paper_execution_count}/f${item.operator_feedback_count}`))],
|
|
])
|
|
);
|
|
}
|
|
if (assistantSections.length) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar(
|
|
assistantSections.map((item) => ({
|
|
label: item.title,
|
|
onClick: () => revealAssistantSection(normalizeAssistantSectionId(item.title)),
|
|
}))
|
|
)
|
|
);
|
|
}
|
|
if (digestPreferenceProfile) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Preferences');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['selected profile', selectedProfileKey],
|
|
['profile label', assistantPreferenceDraft?.profile_label || digestPreferenceProfile.profile_label || 'n/a'],
|
|
['draft status', assistantPreferenceDraftDirty ? 'unsaved_changes' : 'synced'],
|
|
['digest mode', assistantPreferenceDraft?.digest_mode || 'all_reminders'],
|
|
['preferred history kind', assistantPreferenceDraft?.preferred_history_kind || 'all'],
|
|
['importance threshold', String(assistantPreferenceDraft?.importance_threshold ?? 70)],
|
|
['history window hours', String(assistantPreferenceDraft?.history_window_hours ?? 24)],
|
|
['focus candidate', String(assistantPreferenceDraft?.focus_candidate_id ?? 'n/a')],
|
|
])
|
|
);
|
|
if (digestPreferenceProfile.available_profiles?.length) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar(
|
|
digestPreferenceProfile.available_profiles.map((profile) => ({
|
|
label: profile.profile_key === selectedProfileKey
|
|
? `${profile.profile_label || profile.profile_key}`
|
|
: (profile.profile_label || profile.profile_key),
|
|
onClick: async () => {
|
|
if (assistantPreferenceDraftDirty && !window.confirm('Switch profile and discard unsaved changes?')) return;
|
|
persistAssistantDigestProfileKey(profile.profile_key || 'local_operator');
|
|
assistantPreferenceDraft = null;
|
|
assistantPreferenceDraftDirty = false;
|
|
await refreshDashboard();
|
|
},
|
|
}))
|
|
)
|
|
);
|
|
}
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'New Profile',
|
|
onClick: async () => {
|
|
const profileKey = window.prompt('New profile key', '');
|
|
if (!profileKey) return;
|
|
const profileLabel = window.prompt('New profile label', assistantPreferenceDraft?.profile_label || profileKey);
|
|
const created = await createAssistantPreferenceProfile({
|
|
profileKey,
|
|
profileLabel,
|
|
sourceProfileKey: null,
|
|
draft: assistantPreferenceDraft,
|
|
}).catch((error) => ({ status: 'error', reason: String(error) }));
|
|
if (created?.status !== 'created') {
|
|
showErrorPanel(created?.reason || created?.status || 'profile_create_failed');
|
|
return;
|
|
}
|
|
persistAssistantDigestProfileKey(created.profile_key || profileKey);
|
|
assistantPreferenceDraft = null;
|
|
assistantPreferenceDraftDirty = false;
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Duplicate',
|
|
onClick: async () => {
|
|
const profileKey = window.prompt('Duplicate to profile key', `${selectedProfileKey}_copy`);
|
|
if (!profileKey) return;
|
|
const profileLabel = window.prompt('Duplicate profile label', `${assistantPreferenceDraft?.profile_label || selectedProfileKey} Copy`);
|
|
const created = await createAssistantPreferenceProfile({
|
|
profileKey,
|
|
profileLabel,
|
|
sourceProfileKey: selectedProfileKey,
|
|
draft: assistantPreferenceDraft,
|
|
}).catch((error) => ({ status: 'error', reason: String(error) }));
|
|
if (created?.status !== 'created') {
|
|
showErrorPanel(created?.reason || created?.status || 'profile_duplicate_failed');
|
|
return;
|
|
}
|
|
persistAssistantDigestProfileKey(created.profile_key || profileKey);
|
|
assistantPreferenceDraft = null;
|
|
assistantPreferenceDraftDirty = false;
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Rename Label',
|
|
onClick: async () => {
|
|
const nextLabel = window.prompt('Profile label', assistantPreferenceDraft?.profile_label || selectedProfileKey);
|
|
if (!nextLabel) return;
|
|
const renamed = await renameAssistantPreferenceProfile({
|
|
profileKey: selectedProfileKey,
|
|
profileLabel: nextLabel,
|
|
}).catch((error) => ({ status: 'error', reason: String(error) }));
|
|
if (renamed?.status !== 'renamed') {
|
|
showErrorPanel(renamed?.reason || renamed?.status || 'profile_rename_failed');
|
|
return;
|
|
}
|
|
assistantPreferenceDraft = null;
|
|
assistantPreferenceDraftDirty = false;
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Delete Profile',
|
|
onClick: async () => {
|
|
if (selectedProfileKey === 'local_operator') {
|
|
showErrorPanel('default_profile_protected');
|
|
return;
|
|
}
|
|
if (!window.confirm(`Delete profile ${selectedProfileKey}?`)) return;
|
|
const removed = await deleteAssistantPreferenceProfile(selectedProfileKey).catch((error) => ({ status: 'error', reason: String(error) }));
|
|
if (removed?.status !== 'deleted') {
|
|
showErrorPanel(removed?.reason || removed?.status || 'profile_delete_failed');
|
|
return;
|
|
}
|
|
persistAssistantDigestProfileKey('local_operator');
|
|
assistantPreferenceDraft = null;
|
|
assistantPreferenceDraftDirty = false;
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
])
|
|
);
|
|
const preferencePanel = createElement('div', 'assistant-preference-panel');
|
|
const fieldGrid = createElement('div', 'assistant-preference-fields');
|
|
const labelField = createElement('label', 'assistant-preference-field');
|
|
labelField.appendChild(createElement('span', 'metric', 'Profile Label'));
|
|
const labelInput = createElement('input', 'assistant-preference-input');
|
|
labelInput.type = 'text';
|
|
labelInput.value = assistantPreferenceDraft?.profile_label || '';
|
|
labelInput.addEventListener('input', (event) => {
|
|
updateAssistantPreferenceDraft({ profile_label: event.target.value });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
});
|
|
labelField.appendChild(labelInput);
|
|
fieldGrid.appendChild(labelField);
|
|
|
|
const modeField = createElement('label', 'assistant-preference-field');
|
|
modeField.appendChild(createElement('span', 'metric', 'Digest Mode'));
|
|
const modeSelect = createElement('select', 'assistant-preference-input');
|
|
(digestPreferenceProfile.available_modes || []).forEach((mode) => {
|
|
const option = createElement('option', '', mode);
|
|
option.value = mode;
|
|
option.selected = mode === (assistantPreferenceDraft?.digest_mode || 'all_reminders');
|
|
modeSelect.appendChild(option);
|
|
});
|
|
modeSelect.addEventListener('change', (event) => {
|
|
updateAssistantPreferenceDraft({ digest_mode: event.target.value || 'all_reminders' });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
});
|
|
modeField.appendChild(modeSelect);
|
|
fieldGrid.appendChild(modeField);
|
|
|
|
const kindField = createElement('label', 'assistant-preference-field');
|
|
kindField.appendChild(createElement('span', 'metric', 'Preferred History Kind'));
|
|
const kindSelect = createElement('select', 'assistant-preference-input');
|
|
[{ kind: 'all', label: 'all' }, ...(digestHistoryGroups || []).map((item) => ({ kind: item.kind, label: item.kind }))].forEach((item) => {
|
|
const option = createElement('option', '', item.label || item.kind);
|
|
option.value = item.kind || 'all';
|
|
option.selected = (item.kind || 'all') === (assistantPreferenceDraft?.preferred_history_kind || 'all');
|
|
kindSelect.appendChild(option);
|
|
});
|
|
kindSelect.addEventListener('change', (event) => {
|
|
updateAssistantPreferenceDraft({ preferred_history_kind: event.target.value || 'all' });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
});
|
|
kindField.appendChild(kindSelect);
|
|
fieldGrid.appendChild(kindField);
|
|
|
|
const thresholdField = createElement('label', 'assistant-preference-field');
|
|
thresholdField.appendChild(createElement('span', 'metric', 'Importance Threshold'));
|
|
const thresholdInput = createElement('input', 'assistant-preference-input');
|
|
thresholdInput.type = 'number';
|
|
thresholdInput.min = '0';
|
|
thresholdInput.max = '100';
|
|
thresholdInput.value = String(assistantPreferenceDraft?.importance_threshold ?? 70);
|
|
thresholdInput.addEventListener('input', (event) => {
|
|
updateAssistantPreferenceDraft({ importance_threshold: Number(event.target.value || 70) });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
});
|
|
thresholdField.appendChild(thresholdInput);
|
|
fieldGrid.appendChild(thresholdField);
|
|
|
|
const historyWindowField = createElement('label', 'assistant-preference-field');
|
|
historyWindowField.appendChild(createElement('span', 'metric', 'History Window Hours'));
|
|
const historyWindowInput = createElement('input', 'assistant-preference-input');
|
|
historyWindowInput.type = 'number';
|
|
historyWindowInput.min = '1';
|
|
historyWindowInput.max = '240';
|
|
historyWindowInput.value = String(assistantPreferenceDraft?.history_window_hours ?? 24);
|
|
historyWindowInput.addEventListener('input', (event) => {
|
|
updateAssistantPreferenceDraft({ history_window_hours: Number(event.target.value || 24) });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
});
|
|
historyWindowField.appendChild(historyWindowInput);
|
|
fieldGrid.appendChild(historyWindowField);
|
|
preferencePanel.appendChild(fieldGrid);
|
|
|
|
if (digestPreferenceProfile.importance_templates?.length) {
|
|
const templateGrid = createElement('div', 'assistant-preference-template-grid');
|
|
(digestPreferenceProfile.importance_templates || []).forEach((item) => {
|
|
const templateField = createElement('label', 'assistant-preference-field');
|
|
templateField.appendChild(createElement('span', 'metric', item.template || 'template'));
|
|
const templateInput = createElement('input', 'assistant-preference-input');
|
|
templateInput.type = 'number';
|
|
templateInput.min = '0';
|
|
templateInput.max = '100';
|
|
templateInput.value = String(assistantPreferenceDraft?.importance_templates?.[item.template] ?? item.threshold ?? item.default_threshold ?? 0);
|
|
templateInput.addEventListener('input', (event) => {
|
|
updateAssistantPreferenceTemplateDraft(item.template, Number(event.target.value || 0));
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
});
|
|
templateField.appendChild(templateInput);
|
|
templateField.appendChild(createElement('div', 'metric', item.description || ''));
|
|
templateGrid.appendChild(templateField);
|
|
});
|
|
preferencePanel.appendChild(templateGrid);
|
|
}
|
|
preferencePanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Save Preferences',
|
|
onClick: async () => {
|
|
const saved = await persistAssistantDigestPreferenceServer({
|
|
profileKey: selectedProfileKey,
|
|
digestMode: assistantPreferenceDraft?.digest_mode || 'all_reminders',
|
|
preferredHistoryKind: assistantPreferenceDraft?.preferred_history_kind === 'all' ? null : (assistantPreferenceDraft?.preferred_history_kind || null),
|
|
importanceThreshold: assistantPreferenceDraft?.importance_threshold || 70,
|
|
payload: {
|
|
profile_label: assistantPreferenceDraft?.profile_label || selectedProfileKey,
|
|
history_window_hours: assistantPreferenceDraft?.history_window_hours || 24,
|
|
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
|
|
archive_defaults: { ...((assistantPreferenceDraft && assistantPreferenceDraft.archive_defaults) || {}) },
|
|
},
|
|
});
|
|
if (!saved) {
|
|
showErrorPanel('preference_save_failed');
|
|
return;
|
|
}
|
|
persistAssistantDigestPreferenceMode(assistantPreferenceDraft?.digest_mode || 'all_reminders');
|
|
persistAssistantHistoryKindFilter(assistantPreferenceDraft?.preferred_history_kind || 'all');
|
|
assistantPreferenceDraftDirty = false;
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Revert Draft',
|
|
onClick: () => {
|
|
resetAssistantPreferenceDraftToServer(summary);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Reset Defaults',
|
|
onClick: () => {
|
|
resetAssistantPreferenceDraftToDefaults(summary);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
])
|
|
);
|
|
if (assistantPreferenceDraftDirty) {
|
|
preferencePanel.appendChild(createElement('div', 'assistant-preference-dirty', 'Unsaved preference changes are staged locally. Save or revert before switching workflow.'));
|
|
}
|
|
decisionAssistantPanel.appendChild(preferencePanel);
|
|
}
|
|
if (digestSurface) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Digest');
|
|
if (archiveOperatorOverview) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['archive operator overview', archiveOperatorOverview.status || 'idle'],
|
|
['digest trend status', archiveOperatorOverview.digest_trend_status || 'n/a'],
|
|
['workflow trend status', archiveOperatorOverview.workflow_trend_status || 'n/a'],
|
|
['digest snapshots', String(archiveOperatorOverview.digest_snapshot_count ?? 0)],
|
|
['workflow snapshots', String(archiveOperatorOverview.workflow_snapshot_count ?? 0)],
|
|
['digest baseline status', archiveOperatorOverview.digest_baseline_status || 'n/a'],
|
|
['workflow baseline status', archiveOperatorOverview.workflow_baseline_status || 'n/a'],
|
|
['digest baseline delta', archiveOperatorOverview.digest_baseline_delta_status || 'n/a'],
|
|
['workflow baseline delta', archiveOperatorOverview.workflow_baseline_delta_status || 'n/a'],
|
|
['digest baseline delta story', archiveOperatorOverview.digest_baseline_delta_story || 'n/a'],
|
|
['workflow baseline delta story', archiveOperatorOverview.workflow_baseline_delta_story || 'n/a'],
|
|
['overview story', archiveOperatorOverview.operator_overview_story || 'n/a'],
|
|
['overview prompt', archiveOperatorOverview.next_overview_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['digest status', digestSurface.status || 'idle'],
|
|
['digest summary', digestSurface.summary || 'n/a'],
|
|
['reminders', String(digestSurface.reminder_count ?? 0)],
|
|
['high priority reminders', String(digestSurface.high_priority_count ?? 0)],
|
|
['high value reminders', String(digestSurface.high_value_reminder_count ?? 0)],
|
|
['digest preference', effectiveDigestPreferenceMode || 'all_reminders'],
|
|
['selected profile', selectedProfileKey],
|
|
['profile label', digestPreferenceProfile?.profile_label || 'n/a'],
|
|
['preference default', digestPreferenceProfile?.default_mode || 'n/a'],
|
|
['preference persistence', digestPreferenceProfile?.persistence_status || 'n/a'],
|
|
['preference threshold', String(digestPreferenceProfile?.importance_threshold ?? 'n/a')],
|
|
['preference updated', formatTimestamp(digestPreferenceProfile?.persisted_updated_ts)],
|
|
['history window hours', String(digestPreferenceProfile?.history_window_hours ?? 'n/a')],
|
|
['preferred history kind', digestPreferenceProfile?.preferred_history_kind || 'n/a'],
|
|
['preferred template', digestPreferenceProfile?.preferred_importance_template || 'n/a'],
|
|
['preference reason', digestPreferenceProfile?.profile_reason || 'n/a'],
|
|
['focus candidate', String(digestPreferenceProfile?.focus_candidate_id ?? digestSurface.focus_candidate_id ?? 'n/a')],
|
|
['focus-related reminders', String(digestSurface.focus_related_reminder_count ?? 0)],
|
|
['next focus reminder', digestSurface.next_focus_related_reminder?.title || 'n/a'],
|
|
['visible reminders', String(reminderCards.length)],
|
|
['important change filter', `${digestSurface.important_change_filter?.mode || 'n/a'} / ${digestSurface.important_change_filter?.threshold ?? 'n/a'}`],
|
|
['importance templates', formatJsonInline(digestSurface.important_change_filter?.template_counts || {})],
|
|
['next high value change', digestSurface.next_high_value_change?.title || 'n/a'],
|
|
['review-back actions', String(digestSurface.digest_review_back_action_count ?? 0)],
|
|
['next review-back', digestSurface.next_digest_review_back_action?.label || 'n/a'],
|
|
['review-back prompts', formatList((digestSurface.digest_review_back_actions || []).map((item) => item.prompt || item.label))],
|
|
['history browser', digestHistoryBrowser?.status || 'idle'],
|
|
['history browser options', String(digestHistoryBrowser?.option_count ?? 0)],
|
|
['next browser filter', digestHistoryBrowser?.next_filter_label || 'n/a'],
|
|
['next browser prompt', digestHistoryBrowser?.next_filter_prompt || 'n/a'],
|
|
['digest action queue', String(digestSurface.digest_action_queue_count ?? 0)],
|
|
['next digest action', digestSurface.next_digest_action?.label || 'n/a'],
|
|
['next digest prompt', digestSurface.next_digest_action?.prompt || 'n/a'],
|
|
['digest archive', String(digestSurface.digest_history_archive_count ?? 0)],
|
|
['next archive review', digestSurface.next_digest_history_archive_review || 'n/a'],
|
|
['archive compare', digestArchiveCompare?.status || 'idle'],
|
|
['archive compare summary', digestArchiveCompare?.summary || 'n/a'],
|
|
['digest persistence', digestSurface.digest_persistence_status || 'n/a'],
|
|
['digest history', String(digestSurface.history_count ?? 0)],
|
|
['digest kinds', formatList(digestSurface.history_summary?.kinds || [])],
|
|
['recommended history kind', digestSurface.history_summary?.recommended_kind || 'n/a'],
|
|
['recommended history count', String(digestSurface.history_summary?.recommended_count ?? 0)],
|
|
['fresh history groups', String(digestSurface.history_summary?.fresh_kind_count ?? 0)],
|
|
['stale history groups', String(digestSurface.history_summary?.stale_kind_count ?? 0)],
|
|
['refresh-needed groups', String(digestSurface.history_summary?.refresh_needed_count ?? 0)],
|
|
['refresh confirmations', String(digestSurface.history_summary?.refresh_confirmation_count ?? 0)],
|
|
['refresh confirmation needed', String(digestSurface.history_summary?.refresh_confirmation_needed_count ?? 0)],
|
|
['freshness proofs', String(digestSurface.history_summary?.freshness_proof_count ?? 0)],
|
|
['freshness proof gaps', String(digestSurface.history_summary?.freshness_proof_gap_count ?? 0)],
|
|
['proof aging trend', formatJsonInline(digestSurface.history_summary?.proof_aging_trend_counts || {})],
|
|
['stale proof escalations', String(digestSurface.history_summary?.stale_proof_escalation_count ?? 0)],
|
|
['next stale proof kind', digestSurface.history_summary?.next_stale_proof_kind || 'n/a'],
|
|
['next stale proof prompt', digestSurface.history_summary?.next_stale_proof_prompt || 'n/a'],
|
|
['proof follow-up queue', String(digestSurface.history_summary?.proof_follow_up_queue_count ?? 0)],
|
|
['stale proof follow-ups', String(digestSurface.history_summary?.stale_proof_follow_up_count ?? 0)],
|
|
['proof follow-up age', formatJsonInline(digestSurface.history_summary?.proof_follow_up_age_counts || {})],
|
|
['oldest proof follow-up', digestSurface.history_summary?.oldest_proof_follow_up_kind || 'n/a'],
|
|
['oldest proof age', formatDurationSeconds(digestSurface.history_summary?.oldest_proof_follow_up_age_seconds)],
|
|
['resolved proofs', String(digestSurface.history_summary?.resolved_proof_count ?? 0)],
|
|
['proof resolution states', formatJsonInline(digestSurface.history_summary?.proof_resolution_state_counts || {})],
|
|
['proof resolution history', formatList((digestSurface.history_summary?.proof_resolution_history || []).map((item) => `${item.kind}:${item.state}`))],
|
|
['proof SLA states', formatJsonInline(digestSurface.history_summary?.proof_resolution_sla_status_counts || {})],
|
|
['proof SLA breaches', String(digestSurface.history_summary?.proof_resolution_sla_breach_count ?? 0)],
|
|
['proof SLA queue', String(digestSurface.history_summary?.proof_resolution_sla_queue_count ?? 0)],
|
|
['stale proof suppressions', String(digestSurface.history_summary?.repeated_stale_proof_suppression_count ?? 0)],
|
|
['next proof SLA kind', digestSurface.history_summary?.next_proof_resolution_sla_kind || 'n/a'],
|
|
['next proof SLA prompt', digestSurface.history_summary?.next_proof_resolution_sla_prompt || 'n/a'],
|
|
['proof SLA backlog', String(digestSurface.history_summary?.proof_sla_backlog_count ?? 0)],
|
|
['proof SLA routes', formatJsonInline(digestSurface.history_summary?.proof_sla_backlog_routes || {})],
|
|
['freshness debt', digestSurface.history_summary?.freshness_debt_summary?.status || 'n/a'],
|
|
['freshness debt score', String(digestSurface.history_summary?.freshness_debt_summary?.debt_score_total ?? 0)],
|
|
['next freshness debt', digestSurface.history_summary?.next_freshness_debt_prompt || 'n/a'],
|
|
['freshness debt trends', formatJsonInline(digestSurface.history_summary?.freshness_debt_trend_counts || {})],
|
|
['SLA recovery recs', String(digestSurface.history_summary?.sla_recovery_recommendation_count ?? 0)],
|
|
['next SLA recovery', digestSurface.history_summary?.next_sla_recovery_recommendation?.label || 'n/a'],
|
|
['next SLA recovery prompt', digestSurface.history_summary?.next_sla_recovery_prompt || 'n/a'],
|
|
['next proof follow-up', digestSurface.history_summary?.next_proof_follow_up_prompt || 'n/a'],
|
|
['confirmation trail items', String(digestSurface.history_summary?.refresh_confirmation_trail_count ?? 0)],
|
|
['confirmation trail', formatList((digestSurface.history_summary?.refresh_confirmation_trail || []).map((item) => `${item.kind}:${item.proof_label}`))],
|
|
['refresh evidence anchors', String(digestSurface.history_summary?.refresh_evidence_anchor_count ?? 0)],
|
|
['refresh prompts', formatList((digestSurface.history_summary?.refresh_prompts || []).map((item) => `${item.kind}: ${item.prompt}`))],
|
|
['history filter', activeHistoryFilter || 'all'],
|
|
['digest latest', formatTimestamp(digestSurface.history_summary?.latest_ts)],
|
|
['digest top opportunity', digestSurface.top_opportunity_title || 'n/a'],
|
|
['learning status', digestSurface.learning_status || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (digestSurface?.digest_action_queue?.length) {
|
|
const digestActionGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(digestSurface.digest_action_queue || []).forEach((item) => {
|
|
digestActionGrid.appendChild(
|
|
buildItemCard(
|
|
item.label || 'digest action',
|
|
item.priority || item.source || 'n/a',
|
|
[
|
|
['source', item.source || 'n/a'],
|
|
['prompt', item.prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(digestActionGrid);
|
|
}
|
|
if (digestHistoryBrowser?.filter_options?.length) {
|
|
const browserBar = buildActionBar(
|
|
digestHistoryBrowser.filter_options.map((item) => ({
|
|
label: `${item.label || item.kind}${item.recommended ? ' *' : ''}`,
|
|
onClick: () => {
|
|
persistAssistantHistoryKindFilter(item.kind || 'all');
|
|
updateAssistantPreferenceDraft({ preferred_history_kind: item.kind || 'all' });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
}))
|
|
);
|
|
decisionAssistantPanel.appendChild(browserBar);
|
|
}
|
|
const digestArchivePanel = createElement('div', 'assistant-archive-panel');
|
|
const digestArchiveControls = createElement('div', 'assistant-archive-controls');
|
|
const digestArchiveProfileField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveProfileField.appendChild(createElement('span', 'metric', 'Archive Profile'));
|
|
const digestArchiveProfileInput = createElement('input', 'assistant-preference-input');
|
|
digestArchiveProfileInput.type = 'text';
|
|
digestArchiveProfileInput.value = assistantDigestArchiveState.profile_key || selectedProfileKey;
|
|
digestArchiveProfileField.appendChild(digestArchiveProfileInput);
|
|
digestArchiveControls.appendChild(digestArchiveProfileField);
|
|
|
|
const digestArchiveScopeField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveScopeField.appendChild(createElement('span', 'metric', 'Profile Scope'));
|
|
const digestArchiveScopeSelect = createElement('select', 'assistant-preference-input');
|
|
['selected_profile', 'all_profiles'].forEach((scope) => {
|
|
const option = createElement('option', '', scope);
|
|
option.value = scope;
|
|
option.selected = scope === (assistantDigestArchiveState.profile_scope || digestArchiveSavedDefaults.profile_scope || 'selected_profile');
|
|
digestArchiveScopeSelect.appendChild(option);
|
|
});
|
|
digestArchiveScopeField.appendChild(digestArchiveScopeSelect);
|
|
digestArchiveControls.appendChild(digestArchiveScopeField);
|
|
|
|
const digestArchivePresetField = createElement('label', 'assistant-preference-field');
|
|
digestArchivePresetField.appendChild(createElement('span', 'metric', 'Query Preset'));
|
|
const digestArchivePresetSelect = createElement('select', 'assistant-preference-input');
|
|
[{ preset_id: '', label: 'custom' }, ...((digestArchiveData?.available_query_presets || []))].forEach((preset) => {
|
|
const option = createElement('option', '', preset.label || preset.preset_id || 'custom');
|
|
option.value = preset.preset_id || '';
|
|
option.selected = (preset.preset_id || '') === (assistantDigestArchiveState.query_preset || digestArchiveSavedDefaults.query_preset || '');
|
|
digestArchivePresetSelect.appendChild(option);
|
|
});
|
|
digestArchivePresetField.appendChild(digestArchivePresetSelect);
|
|
digestArchiveControls.appendChild(digestArchivePresetField);
|
|
|
|
const digestArchiveModeField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveModeField.appendChild(createElement('span', 'metric', 'Archive Mode'));
|
|
const digestArchiveModeSelect = createElement('select', 'assistant-preference-input');
|
|
[{ value: '', label: 'all' }, ...(digestPreferenceProfile?.available_modes || []).map((mode) => ({ value: mode, label: mode }))].forEach((item) => {
|
|
const option = createElement('option', '', item.label);
|
|
option.value = item.value;
|
|
option.selected = item.value === (assistantDigestArchiveState.digest_mode || '');
|
|
digestArchiveModeSelect.appendChild(option);
|
|
});
|
|
digestArchiveModeField.appendChild(digestArchiveModeSelect);
|
|
digestArchiveControls.appendChild(digestArchiveModeField);
|
|
|
|
const digestArchiveKindField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveKindField.appendChild(createElement('span', 'metric', 'Archive Kind'));
|
|
const digestArchiveKindSelect = createElement('select', 'assistant-preference-input');
|
|
[{ value: '', label: 'all' }, ...(digestHistoryGroups || []).map((item) => ({ value: item.kind, label: item.kind }))].forEach((item) => {
|
|
const option = createElement('option', '', item.label);
|
|
option.value = item.value;
|
|
option.selected = item.value === (assistantDigestArchiveState.preferred_history_kind || '');
|
|
digestArchiveKindSelect.appendChild(option);
|
|
});
|
|
digestArchiveKindField.appendChild(digestArchiveKindSelect);
|
|
digestArchiveControls.appendChild(digestArchiveKindField);
|
|
|
|
const digestArchiveSearchField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveSearchField.appendChild(createElement('span', 'metric', 'Search Text'));
|
|
const digestArchiveSearchInput = createElement('input', 'assistant-preference-input');
|
|
digestArchiveSearchInput.type = 'text';
|
|
digestArchiveSearchInput.value = assistantDigestArchiveState.query_text || '';
|
|
digestArchiveSearchInput.placeholder = 'summary, reminder, candidate...';
|
|
digestArchiveSearchField.appendChild(digestArchiveSearchInput);
|
|
digestArchiveControls.appendChild(digestArchiveSearchField);
|
|
|
|
const digestArchiveAfterField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveAfterField.appendChild(createElement('span', 'metric', 'Created After'));
|
|
const digestArchiveAfterInput = createElement('input', 'assistant-preference-input');
|
|
digestArchiveAfterInput.type = 'datetime-local';
|
|
digestArchiveAfterInput.value = assistantDigestArchiveState.created_after || '';
|
|
digestArchiveAfterField.appendChild(digestArchiveAfterInput);
|
|
digestArchiveControls.appendChild(digestArchiveAfterField);
|
|
|
|
const digestArchiveBeforeField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveBeforeField.appendChild(createElement('span', 'metric', 'Created Before'));
|
|
const digestArchiveBeforeInput = createElement('input', 'assistant-preference-input');
|
|
digestArchiveBeforeInput.type = 'datetime-local';
|
|
digestArchiveBeforeInput.value = assistantDigestArchiveState.created_before || '';
|
|
digestArchiveBeforeField.appendChild(digestArchiveBeforeInput);
|
|
digestArchiveControls.appendChild(digestArchiveBeforeField);
|
|
|
|
const digestArchiveLimitField = createElement('label', 'assistant-preference-field');
|
|
digestArchiveLimitField.appendChild(createElement('span', 'metric', 'Archive Limit'));
|
|
const digestArchiveLimitInput = createElement('input', 'assistant-preference-input');
|
|
digestArchiveLimitInput.type = 'number';
|
|
digestArchiveLimitInput.min = '1';
|
|
digestArchiveLimitInput.max = '50';
|
|
digestArchiveLimitInput.value = String(assistantDigestArchiveState.limit || 12);
|
|
digestArchiveLimitField.appendChild(digestArchiveLimitInput);
|
|
digestArchiveControls.appendChild(digestArchiveLimitField);
|
|
digestArchivePanel.appendChild(digestArchiveControls);
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Apply Archive Filters',
|
|
onClick: async () => {
|
|
await fetchAssistantDigestArchive({
|
|
profile_key: digestArchiveProfileInput.value || selectedProfileKey,
|
|
profile_scope: digestArchiveScopeSelect.value || 'selected_profile',
|
|
query_preset: digestArchivePresetSelect.value || '',
|
|
digest_mode: digestArchiveModeSelect.value || '',
|
|
preferred_history_kind: digestArchiveKindSelect.value || '',
|
|
query_text: digestArchiveSearchInput.value || '',
|
|
created_after: digestArchiveAfterInput.value || '',
|
|
created_before: digestArchiveBeforeInput.value || '',
|
|
limit: Number(digestArchiveLimitInput.value || 12),
|
|
selected_left_id: assistantDigestArchiveState.selected_left_id,
|
|
selected_right_id: assistantDigestArchiveState.selected_right_id,
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Load More Archive',
|
|
onClick: async () => {
|
|
await fetchAssistantDigestArchive({
|
|
append: true,
|
|
profile_key: assistantDigestArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantDigestArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantDigestArchiveState.query_preset || '',
|
|
digest_mode: assistantDigestArchiveState.digest_mode || '',
|
|
preferred_history_kind: assistantDigestArchiveState.preferred_history_kind || '',
|
|
query_text: assistantDigestArchiveState.query_text || '',
|
|
created_after: assistantDigestArchiveState.created_after || '',
|
|
created_before: assistantDigestArchiveState.created_before || '',
|
|
limit: Number(assistantDigestArchiveState.limit || 12),
|
|
selected_left_id: assistantDigestArchiveState.selected_left_id,
|
|
selected_right_id: assistantDigestArchiveState.selected_right_id,
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Compare Selected Digests',
|
|
onClick: async () => {
|
|
if (!assistantDigestArchiveState.selected_left_id || !assistantDigestArchiveState.selected_right_id) {
|
|
showErrorPanel('select_two_digest_snapshots');
|
|
return;
|
|
}
|
|
await fetchAssistantDigestArchive({
|
|
profile_key: assistantDigestArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantDigestArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantDigestArchiveState.query_preset || '',
|
|
digest_mode: assistantDigestArchiveState.digest_mode || '',
|
|
preferred_history_kind: assistantDigestArchiveState.preferred_history_kind || '',
|
|
query_text: assistantDigestArchiveState.query_text || '',
|
|
created_after: assistantDigestArchiveState.created_after || '',
|
|
created_before: assistantDigestArchiveState.created_before || '',
|
|
limit: Number(assistantDigestArchiveState.limit || 12),
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Export Archive Results',
|
|
onClick: async () => {
|
|
const exportLimit = Math.max(Number(assistantDigestArchiveState.limit || 12), Number(digestArchiveData?.items?.length || 0));
|
|
const exportPayload = await fetchJson(`/trading/alerts/digest-history-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantDigestArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantDigestArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantDigestArchiveState.query_preset || '',
|
|
digest_mode: assistantDigestArchiveState.digest_mode || '',
|
|
preferred_history_kind: assistantDigestArchiveState.preferred_history_kind || '',
|
|
query_text: assistantDigestArchiveState.query_text || '',
|
|
created_after: assistantDigestArchiveState.created_after || '',
|
|
created_before: assistantDigestArchiveState.created_before || '',
|
|
limit: exportLimit,
|
|
offset: 0,
|
|
})}`);
|
|
assistantDigestArchiveLastExport = exportPayload;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
downloadJsonFile(exportPayload.export_name || 'digest-archive-export.json', exportPayload);
|
|
},
|
|
},
|
|
{
|
|
label: 'Export Archive Compare',
|
|
onClick: async () => {
|
|
if (!assistantDigestArchiveState.selected_left_id || !assistantDigestArchiveState.selected_right_id) {
|
|
showErrorPanel('select_two_digest_snapshots');
|
|
return;
|
|
}
|
|
const exportLimit = Math.max(Number(assistantDigestArchiveState.limit || 12), Number(digestArchiveData?.items?.length || 0));
|
|
const exportPayload = await fetchJson(`/trading/alerts/digest-history-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantDigestArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantDigestArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantDigestArchiveState.query_preset || '',
|
|
digest_mode: assistantDigestArchiveState.digest_mode || '',
|
|
preferred_history_kind: assistantDigestArchiveState.preferred_history_kind || '',
|
|
query_text: assistantDigestArchiveState.query_text || '',
|
|
created_after: assistantDigestArchiveState.created_after || '',
|
|
created_before: assistantDigestArchiveState.created_before || '',
|
|
limit: exportLimit,
|
|
offset: 0,
|
|
left_id: assistantDigestArchiveState.selected_left_id,
|
|
right_id: assistantDigestArchiveState.selected_right_id,
|
|
})}`);
|
|
assistantDigestArchiveLastExport = exportPayload;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
downloadJsonFile(exportPayload.export_name || 'digest-archive-compare-export.json', exportPayload);
|
|
},
|
|
},
|
|
{
|
|
label: 'Save Baseline',
|
|
onClick: async () => {
|
|
const baselineId = assistantDigestArchiveState.pinned_baseline_id || digestArchiveBaselineSummary?.baseline_snapshot_id;
|
|
if (!baselineId) {
|
|
showErrorPanel('digest_baseline_missing');
|
|
return;
|
|
}
|
|
await persistAssistantArchiveBaselinesServer({
|
|
profileKey: assistantDigestArchiveState.profile_key || selectedProfileKey,
|
|
digestSnapshotId: baselineId,
|
|
});
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Save Digest Defaults',
|
|
onClick: async () => {
|
|
const saved = await persistAssistantDigestPreferenceServer({
|
|
profileKey: selectedProfileKey,
|
|
digestMode: assistantPreferenceDraft?.digest_mode || digestPreferenceProfile?.persisted_digest_mode || 'all_reminders',
|
|
preferredHistoryKind: assistantPreferenceDraft?.preferred_history_kind === 'all'
|
|
? null
|
|
: (assistantPreferenceDraft?.preferred_history_kind || digestPreferenceProfile?.preferred_history_kind || null),
|
|
importanceThreshold: assistantPreferenceDraft?.importance_threshold || digestPreferenceProfile?.importance_threshold || 70,
|
|
payload: {
|
|
profile_label: assistantPreferenceDraft?.profile_label || digestPreferenceProfile?.profile_label || selectedProfileKey,
|
|
history_window_hours: assistantPreferenceDraft?.history_window_hours || digestPreferenceProfile?.history_window_hours || 24,
|
|
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
|
|
archive_defaults: mergeAssistantArchiveDefaults(summary, {
|
|
digest: {
|
|
profile_scope: digestArchiveScopeSelect.value || 'selected_profile',
|
|
query_preset: digestArchivePresetSelect.value || '',
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
if (!saved) {
|
|
showErrorPanel('digest_archive_defaults_save_failed');
|
|
return;
|
|
}
|
|
assistantPreferenceDraft = {
|
|
...(assistantPreferenceDraft || {}),
|
|
archive_defaults: mergeAssistantArchiveDefaults(summary, {
|
|
digest: {
|
|
profile_scope: digestArchiveScopeSelect.value || 'selected_profile',
|
|
query_preset: digestArchivePresetSelect.value || '',
|
|
},
|
|
}),
|
|
};
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Use Saved Digest Defaults',
|
|
onClick: () => {
|
|
assistantDigestArchiveState.profile_scope = digestArchiveSavedDefaults.profile_scope || 'selected_profile';
|
|
assistantDigestArchiveState.query_preset = digestArchiveSavedDefaults.query_preset || '';
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Clear Archive Filters',
|
|
onClick: () => {
|
|
assistantDigestArchiveState = {
|
|
profile_key: selectedProfileKey,
|
|
profile_scope: 'selected_profile',
|
|
query_preset: '',
|
|
preferred_history_kind: '',
|
|
digest_mode: '',
|
|
query_text: '',
|
|
created_after: '',
|
|
created_before: '',
|
|
limit: 12,
|
|
next_offset: 0,
|
|
selected_left_id: null,
|
|
selected_right_id: null,
|
|
pinned_baseline_id: null,
|
|
};
|
|
window.__assistantDigestArchiveResponse = null;
|
|
window.__assistantDigestArchiveCompare = null;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
])
|
|
);
|
|
digestArchivePanel.appendChild(
|
|
buildInfoGrid([
|
|
['archive query profile', assistantDigestArchiveState.profile_key || selectedProfileKey],
|
|
['archive profile scope', assistantDigestArchiveState.profile_scope || digestArchiveSavedDefaults.profile_scope || 'selected_profile'],
|
|
['archive preset', assistantDigestArchiveState.query_preset || digestArchiveSavedDefaults.query_preset || 'custom'],
|
|
['archive preset summary', digestArchiveData?.query_preset_summary || 'custom_filters'],
|
|
['saved archive defaults', `${digestArchiveSavedDefaults.profile_scope || 'selected_profile'} / ${digestArchiveSavedDefaults.query_preset || 'custom'}`],
|
|
['archive query mode', assistantDigestArchiveState.digest_mode || 'all'],
|
|
['archive query kind', assistantDigestArchiveState.preferred_history_kind || 'all'],
|
|
['archive query text', assistantDigestArchiveState.query_text || 'n/a'],
|
|
['archive created after', assistantDigestArchiveState.created_after || 'n/a'],
|
|
['archive created before', assistantDigestArchiveState.created_before || 'n/a'],
|
|
['archive trend', digestArchiveLongRangeSummary?.trend_status || 'n/a'],
|
|
['archive trend story', digestArchiveLongRangeSummary?.trend_story || 'n/a'],
|
|
['archive 7d / 30d', `${digestArchiveLongRangeSummary?.recent_7d_count ?? 0} / ${digestArchiveLongRangeSummary?.recent_30d_count ?? 0}`],
|
|
['archive span days', String(digestArchiveLongRangeSummary?.covered_day_span ?? 0)],
|
|
['archive baseline', String(assistantDigestArchiveState.pinned_baseline_id ?? digestArchiveBaselineSummary?.baseline_snapshot_id ?? 'n/a')],
|
|
['archive baseline persistence', digestArchiveBaselineSummary?.persistence_status || digestPreferenceProfile?.archive_baselines?.status || 'n/a'],
|
|
['archive baseline delta', digestArchiveBaselineSummary?.baseline_delta_status || 'n/a'],
|
|
['archive baseline delta story', digestArchiveBaselineSummary?.baseline_delta_story || 'n/a'],
|
|
['archive baseline prompt', digestArchiveBaselineSummary?.suggested_compare_prompt || 'n/a'],
|
|
['archive next offset', String(digestArchiveData?.next_offset ?? assistantDigestArchiveState.next_offset ?? 0)],
|
|
['compare left snapshot', String(assistantDigestArchiveState.selected_left_id ?? 'n/a')],
|
|
['compare right snapshot', String(assistantDigestArchiveState.selected_right_id ?? 'n/a')],
|
|
])
|
|
);
|
|
if (digestArchiveRankedAggregation) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['archive top profile', digestArchiveRankedAggregation.top_profile?.value || 'n/a'],
|
|
['archive top kind', digestArchiveRankedAggregation.top_history_kind?.value || 'n/a'],
|
|
['archive aggregation story', digestArchiveRankedAggregation.aggregation_story || 'n/a'],
|
|
])
|
|
);
|
|
const digestRankGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(digestArchiveRankedAggregation.profile_rankings || []).slice(0, 3).forEach((item) => {
|
|
digestRankGrid.appendChild(
|
|
buildItemCard(
|
|
`Profile ${item.value || 'n/a'}`,
|
|
String(item.count ?? 0),
|
|
[['count', String(item.count ?? 0)]]
|
|
)
|
|
);
|
|
});
|
|
(digestArchiveRankedAggregation.history_kind_rankings || []).slice(0, 3).forEach((item) => {
|
|
digestRankGrid.appendChild(
|
|
buildItemCard(
|
|
`Kind ${item.value || 'n/a'}`,
|
|
String(item.count ?? 0),
|
|
[['count', String(item.count ?? 0)]]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(digestRankGrid);
|
|
}
|
|
decisionAssistantPanel.appendChild(digestArchivePanel);
|
|
if (digestArchiveCompare?.status === 'active') {
|
|
const digestCompareLeft = digestArchiveCompare.left || {};
|
|
const digestCompareRight = digestArchiveCompare.right || {};
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['compare left', formatTimestamp(digestArchiveCompare.left_created_ts || digestCompareLeft.created_ts)],
|
|
['compare right', formatTimestamp(digestArchiveCompare.right_created_ts || digestCompareRight.created_ts)],
|
|
['changed fields', formatList(digestArchiveCompare.changed_fields || [])],
|
|
['added titles', formatList(digestArchiveCompare.added_titles || [])],
|
|
['removed titles', formatList(digestArchiveCompare.removed_titles || [])],
|
|
['reminder delta', String(digestArchiveCompare.count_delta?.reminder_count ?? 0)],
|
|
['high value delta', String(digestArchiveCompare.count_delta?.high_value_reminder_count ?? 0)],
|
|
['left body', digestCompareLeft.summary_body || 'n/a'],
|
|
['right body', digestCompareRight.summary_body || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (assistantDigestArchiveLastExport?.selection_provenance_summary) {
|
|
const exportProvenance = assistantDigestArchiveLastExport.selection_provenance_summary;
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['export provenance', exportProvenance.selection_label || 'n/a'],
|
|
['export scope', exportProvenance.export_scope || 'n/a'],
|
|
['selected snapshots', formatList((exportProvenance.selected_snapshot_ids || []).map((item) => String(item)))],
|
|
['active filters', String(exportProvenance.active_filter_count ?? 0)],
|
|
['baseline snapshot', String(exportProvenance.baseline_snapshot_id ?? 'n/a')],
|
|
['selection fingerprint', exportProvenance.selection_fingerprint || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (assistantDigestArchiveLastExport?.bundle_manifest) {
|
|
const bundleManifest = assistantDigestArchiveLastExport.bundle_manifest;
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['bundle manifest', bundleManifest.manifest_label || 'n/a'],
|
|
['manifest version', bundleManifest.manifest_version || 'n/a'],
|
|
['manifest sections', formatList(bundleManifest.included_sections || [])],
|
|
['manifest section count', String(bundleManifest.included_section_count ?? 0)],
|
|
['manifest story', bundleManifest.manifest_story || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (digestArchiveData?.items?.length) {
|
|
const archiveGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(digestArchiveData.items || []).forEach((item) => {
|
|
const isLeft = String(item.snapshot_id || '') === String(assistantDigestArchiveState.selected_left_id || '');
|
|
const isRight = String(item.snapshot_id || '') === String(assistantDigestArchiveState.selected_right_id || '');
|
|
const baselineId = assistantDigestArchiveState.pinned_baseline_id || digestArchiveBaselineSummary?.baseline_snapshot_id;
|
|
const isBaseline = String(item.snapshot_id || '') === String(baselineId || '');
|
|
const compareLabel = isBaseline ? 'baseline_pinned' : (isLeft ? 'left_selected' : (isRight ? 'right_selected' : (item.digest_mode || 'digest')));
|
|
const actions = [
|
|
{
|
|
label: isLeft ? 'Left Selected' : 'Select Left',
|
|
onClick: async () => {
|
|
selectAssistantDigestArchiveSnapshot('left', item.snapshot_id);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: isRight ? 'Right Selected' : 'Select Right',
|
|
onClick: async () => {
|
|
selectAssistantDigestArchiveSnapshot('right', item.snapshot_id);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: isBaseline ? 'Baseline Pinned' : 'Pin Baseline',
|
|
onClick: async () => {
|
|
assistantDigestArchiveState.pinned_baseline_id = item.snapshot_id;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Save This Baseline',
|
|
onClick: async () => {
|
|
assistantDigestArchiveState.pinned_baseline_id = item.snapshot_id;
|
|
await persistAssistantArchiveBaselinesServer({
|
|
profileKey: assistantDigestArchiveState.profile_key || selectedProfileKey,
|
|
digestSnapshotId: item.snapshot_id,
|
|
});
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Compare To Baseline',
|
|
onClick: async () => {
|
|
const pinnedId = assistantDigestArchiveState.pinned_baseline_id || digestArchiveBaselineSummary?.baseline_snapshot_id;
|
|
if (!pinnedId || String(pinnedId) === String(item.snapshot_id || '')) return;
|
|
assistantDigestArchiveState.selected_left_id = item.snapshot_id;
|
|
assistantDigestArchiveState.selected_right_id = pinnedId;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
];
|
|
const card = buildItemCard(
|
|
item.top_summary || item.top_opportunity_title || 'digest snapshot',
|
|
compareLabel,
|
|
[
|
|
['snapshot', String(item.snapshot_id ?? 'n/a')],
|
|
['profile', item.profile_key || 'n/a'],
|
|
['time', formatTimestamp(item.created_ts)],
|
|
['history kind', item.preferred_history_kind || 'n/a'],
|
|
['summary body', item.summary_body || 'n/a'],
|
|
['next action', item.next_digest_action_label || 'n/a'],
|
|
['high value reminders', String(item.high_value_reminder_count ?? 0)],
|
|
['reminders', String(item.reminder_count ?? 0)],
|
|
['titles', formatList(item.reminder_titles || [])],
|
|
],
|
|
actions
|
|
);
|
|
if (isLeft || isRight || isBaseline) card.classList.add('assistant-compare-selected');
|
|
archiveGrid.appendChild(
|
|
card
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(archiveGrid);
|
|
}
|
|
if (reminderCards.length) {
|
|
const reminderGrid = createElement('div', 'cards assistant-tight-grid');
|
|
reminderCards.forEach((item) => {
|
|
reminderGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'reminder',
|
|
item.severity || 'info',
|
|
[
|
|
['message', item.message || 'n/a'],
|
|
['source', item.source || 'n/a'],
|
|
['importance', String(item.importance_score ?? 'n/a')],
|
|
['template', item.importance_template || 'n/a'],
|
|
['high value', String(Boolean(item.high_value))],
|
|
['high value reason', item.high_value_reason || 'n/a'],
|
|
['focus related', String(Boolean(item.focus_related))],
|
|
['focus candidate', String(item.focus_candidate_id ?? 'n/a')],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(reminderGrid);
|
|
}
|
|
if (digestHistoryGroups.length) {
|
|
const recommendedKind = digestSurface?.history_summary?.recommended_kind || 'all';
|
|
const groupKinds = digestHistoryGroups.map((item) => item.kind);
|
|
if (activeHistoryFilter !== 'all' && !groupKinds.includes(activeHistoryFilter)) {
|
|
persistAssistantHistoryKindFilter(recommendedKind);
|
|
}
|
|
const historyFilterBar = buildActionBar([
|
|
{
|
|
label: 'All History',
|
|
onClick: () => {
|
|
persistAssistantHistoryKindFilter('all');
|
|
updateAssistantPreferenceDraft({ preferred_history_kind: 'all' });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
...(recommendedKind && recommendedKind !== 'all'
|
|
? [{
|
|
label: `Recommended ${recommendedKind}`,
|
|
onClick: () => {
|
|
persistAssistantHistoryKindFilter(recommendedKind);
|
|
updateAssistantPreferenceDraft({ preferred_history_kind: recommendedKind });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
}]
|
|
: []),
|
|
...digestHistoryGroups.map((item) => ({
|
|
label: `${item.kind} (${item.count})${item.recommended ? ' *' : ''}`,
|
|
onClick: () => {
|
|
persistAssistantHistoryKindFilter(item.kind || 'all');
|
|
updateAssistantPreferenceDraft({ preferred_history_kind: item.kind || 'all' });
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
})),
|
|
]);
|
|
decisionAssistantPanel.appendChild(historyFilterBar);
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['active history focus', activeHistoryFilter || 'all'],
|
|
['recommended history focus', recommendedKind || 'n/a'],
|
|
])
|
|
);
|
|
const groupGrid = createElement('div', 'cards assistant-history-grid');
|
|
const visibleGroups = activeHistoryFilter === 'all'
|
|
? digestHistoryGroups
|
|
: digestHistoryGroups.filter((item) => String(item.kind || '') === activeHistoryFilter);
|
|
visibleGroups.forEach((item) => {
|
|
const card = buildItemCard(
|
|
item.recommended ? `Recommended ${item.kind || 'history'}` : `History: ${item.kind || 'history'}`,
|
|
item.group_status || String(item.count ?? 0),
|
|
[
|
|
['headline', item.headline || 'n/a'],
|
|
['browser rank', String(item.browser_rank ?? 'n/a')],
|
|
['filter label', item.filter_label || 'n/a'],
|
|
['browser prompt', item.browser_prompt || 'n/a'],
|
|
['review-back priority', item.review_back_priority || 'n/a'],
|
|
['latest', formatTimestamp(item.latest_ts)],
|
|
['latest item', item.latest_title || 'n/a'],
|
|
['age band', item.age_band || 'n/a'],
|
|
['staleness', item.staleness_hint || 'n/a'],
|
|
['refresh', item.refresh_prompt || 'n/a'],
|
|
['refresh confirmation', item.refresh_confirmation_status || 'n/a'],
|
|
['confirmation prompt', item.refresh_confirmation_prompt || 'n/a'],
|
|
['freshness proof', item.freshness_proof_status || 'n/a'],
|
|
['proof detail', item.freshness_proof_detail || 'n/a'],
|
|
['proof age trend', item.proof_age_trend || 'n/a'],
|
|
['proof escalation', item.proof_escalation_level || 'n/a'],
|
|
['proof escalation prompt', item.proof_escalation_prompt || 'n/a'],
|
|
['proof follow-up', item.proof_follow_up_state || 'n/a'],
|
|
['proof follow-up action', item.proof_follow_up_action_label || 'n/a'],
|
|
['proof resolved state', item.proof_resolved_state || 'n/a'],
|
|
['proof follow-up age', item.proof_follow_up_age_band || 'n/a'],
|
|
['proof stale queue', String(Boolean(item.proof_follow_up_stale_queue))],
|
|
['proof resolution history', formatList((item.proof_resolution_history || []).map((row) => `${row.proof_label || row.state}: ${formatTimestamp(row.ts)}`))],
|
|
['proof SLA status', item.proof_resolution_sla_status || 'n/a'],
|
|
['proof SLA', formatDurationSeconds(item.proof_resolution_sla_seconds)],
|
|
['proof SLA prompt', item.proof_resolution_sla_prompt || 'n/a'],
|
|
['SLA backlog route', item.proof_sla_backlog_route || 'n/a'],
|
|
['SLA backlog priority', item.proof_sla_backlog_priority || 'n/a'],
|
|
['freshness debt', String(item.freshness_debt_score ?? 0)],
|
|
['freshness debt trend', item.freshness_debt_trend || 'n/a'],
|
|
['SLA recovery', item.sla_recovery_recommendation?.label || 'n/a'],
|
|
['SLA recovery prompt', item.sla_recovery_recommendation?.prompt || 'n/a'],
|
|
['stale suppression', item.repeated_stale_proof_suppression || 'n/a'],
|
|
['latest proof trail', item.latest_confirmation_trail_item?.proof_label || 'n/a'],
|
|
['proof trail items', formatList((item.refresh_confirmation_trail || []).map((row) => `${row.title || row.kind}: ${row.proof_label}`))],
|
|
['refresh evidence', item.refresh_evidence_anchor || 'n/a'],
|
|
['evidence path', item.refresh_evidence_path || 'n/a'],
|
|
['count', String(item.count ?? 0)],
|
|
['items', formatList((item.items || []).map((row) => row.title || row.kind || 'item'))],
|
|
],
|
|
[{
|
|
label: item.refresh_action_label || item.action_label || 'Review Group',
|
|
onClick: () => {
|
|
persistAssistantHistoryKindFilter(item.kind || 'all');
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
}]
|
|
);
|
|
if (item.recommended) {
|
|
card.classList.add('assistant-history-recommended');
|
|
}
|
|
groupGrid.appendChild(card);
|
|
});
|
|
if (visibleGroups.length) {
|
|
decisionAssistantPanel.appendChild(groupGrid);
|
|
} else {
|
|
decisionAssistantPanel.appendChild(createElement('div', 'empty-state', 'No history items match the current digest filter.'));
|
|
}
|
|
}
|
|
if (reviewEntrypoints) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Review Entry');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['review entrypoints', reviewEntrypoints.status || 'idle'],
|
|
['notes', String(reviewEntrypoints.note_count ?? 0)],
|
|
['focus candidate notes', String(reviewEntrypoints.focus_note_count ?? 0)],
|
|
['focus session', reviewEntrypoints.focus_session_code || 'n/a'],
|
|
['focus model', reviewEntrypoints.focus_model_code || 'n/a'],
|
|
['latest note author', reviewEntrypoints.latest_note_author || 'n/a'],
|
|
['latest note time', formatTimestamp(reviewEntrypoints.latest_note_ts)],
|
|
['focus latest note', formatTimestamp(reviewEntrypoints.focus_latest_note_ts)],
|
|
['focus note prompt', reviewEntrypoints.focus_note_prompt || 'n/a'],
|
|
['journal entries', String(reviewEntrypoints.journal_entry_count ?? 0)],
|
|
['review queue', String(reviewEntrypoints.review_queue_count ?? 0)],
|
|
['next review action', reviewEntrypoints.next_review_action || 'n/a'],
|
|
['review action queue', String(reviewEntrypoints.review_entry_action_queue_count ?? 0)],
|
|
['next review queue action', reviewEntrypoints.next_review_entry_action?.label || 'n/a'],
|
|
['next review queue prompt', reviewEntrypoints.next_review_entry_action?.prompt || 'n/a'],
|
|
['recommended template', reviewEntrypoints.recommended_template_id || 'n/a'],
|
|
['recommended label', reviewEntrypoints.template_summary?.recommended_label || 'n/a'],
|
|
['recommended priority', reviewEntrypoints.template_summary?.recommended_priority || 'n/a'],
|
|
['recommended reason', reviewEntrypoints.template_summary?.recommended_reason || 'n/a'],
|
|
['recommended reuse hint', reviewEntrypoints.template_summary?.recommended_reuse_hint || 'n/a'],
|
|
['template reuse status', reviewEntrypoints.template_reuse_metrics?.reuse_status || 'n/a'],
|
|
['template notes saved', String(reviewEntrypoints.template_reuse_metrics?.assistant_template_note_count ?? 0)],
|
|
['recommended reuse count', String(reviewEntrypoints.template_reuse_metrics?.recommended_reuse_count ?? 0)],
|
|
['recommended last used', formatTimestamp(reviewEntrypoints.template_reuse_metrics?.recommended_last_used_ts)],
|
|
['latest template note', formatTimestamp(reviewEntrypoints.template_reuse_metrics?.latest_template_note_ts)],
|
|
['recommended outcome', reviewEntrypoints.template_reuse_metrics?.recommended_outcome_status || 'n/a'],
|
|
['recommended usefulness', formatPercent(reviewEntrypoints.template_reuse_metrics?.recommended_usefulness_score)],
|
|
['feedback tuned template', reviewEntrypoints.template_reuse_metrics?.feedback_tuned_template_id || 'n/a'],
|
|
['recommendation tuning', reviewEntrypoints.template_reuse_metrics?.recommendation_tuning_status || 'n/a'],
|
|
['useful feedback', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.useful_feedback_count ?? 0)],
|
|
['actionable feedback', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.actionable_feedback_count ?? 0)],
|
|
['misleading feedback', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.misleading_feedback_count ?? 0)],
|
|
['feedback trend', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.feedback_trend_status || 'n/a'],
|
|
['feedback trend samples', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.feedback_trend_history_count ?? 0)],
|
|
['negative guardrails', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.negative_feedback_guardrail_count ?? 0)],
|
|
['guardrail templates', formatList(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.negative_feedback_guardrail_template_ids || [])],
|
|
['guardrail resolution queue', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_resolution_count ?? 0)],
|
|
['preference recovery queue', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preference_recovery_count ?? 0)],
|
|
['next preference recovery', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_template_id || 'n/a'],
|
|
['guardrail audit trail', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_trail_count ?? 0)],
|
|
['recovery recommendations', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preference_recovery_recommendation_count ?? 0)],
|
|
['next recovery action', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_action || 'n/a'],
|
|
['next recovery prompt', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_preference_recovery_prompt || 'n/a'],
|
|
['audit filters', formatJsonInline(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_filter_counts || {})],
|
|
['filtered audit trail', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.guardrail_audit_filtered_count ?? 0)],
|
|
['audit lane groups', formatList((reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.template_audit_lane_groups || []).map((item) => `${item.label || item.audit_lane_group}: ${item.count ?? 0}`))],
|
|
['recovery shortcuts', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.recovery_action_shortcut_count ?? 0)],
|
|
['next recovery shortcut', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_recovery_action_shortcut?.label || 'n/a'],
|
|
['audit lane recommendations', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.template_audit_lane_recommendation_count ?? 0)],
|
|
['next audit lane prompt', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.next_shortcut_outcome_prompt || 'n/a'],
|
|
['shortcut telemetry', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.shortcut_outcome_telemetry_count ?? 0)],
|
|
['shortcut telemetry states', formatJsonInline(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.shortcut_outcome_status_counts || {})],
|
|
['preferred template decay', reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_status || 'n/a'],
|
|
['preferred decay templates', formatList(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_template_ids || [])],
|
|
['max preference decay', formatPercent(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.preferred_template_decay_max)],
|
|
['rated templates', String(reviewEntrypoints.template_reuse_metrics?.template_feedback_aggregation?.rated_template_count ?? 0)],
|
|
['template outcome avg', formatPercent(reviewEntrypoints.template_reuse_metrics?.template_outcome_summary?.average_usefulness_score)],
|
|
['template feedback gaps', String(reviewEntrypoints.template_reuse_metrics?.template_outcome_summary?.feedback_gap_count ?? 0)],
|
|
['template feedback needed', String(reviewEntrypoints.template_reuse_metrics?.template_outcome_summary?.feedback_capture_needed_count ?? 0)],
|
|
['best outcome template', reviewEntrypoints.template_reuse_metrics?.template_outcome_summary?.best_template_id || 'n/a'],
|
|
['feedback loop status', reviewEntrypoints.template_reuse_metrics?.feedback_loop_summary?.status || 'n/a'],
|
|
['next feedback template', reviewEntrypoints.template_reuse_metrics?.feedback_loop_summary?.next_feedback_template_id || 'n/a'],
|
|
['next feedback prompt', reviewEntrypoints.template_reuse_metrics?.feedback_loop_summary?.next_feedback_prompt || 'n/a'],
|
|
['template priorities', formatJsonInline(reviewEntrypoints.template_summary?.priorities || {})],
|
|
['handoff lanes', formatJsonInline(reviewEntrypoints.review_entry_handoff_summary?.lane_counts || {})],
|
|
['review handoff prompt', reviewEntrypoints.review_entry_handoff_summary?.next_handoff_prompt || 'n/a'],
|
|
])
|
|
);
|
|
if (workflowArchiveBridgeContext) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['archive bridge', workflowArchiveBridgeContext.bridge_mode || 'n/a'],
|
|
['archive snapshot', String(workflowArchiveBridgeContext.snapshot_id ?? 'n/a')],
|
|
['archive session', workflowArchiveBridgeContext.session_code || 'n/a'],
|
|
['archive next phase', workflowArchiveBridgeContext.next_phase || 'n/a'],
|
|
['archive artifact', workflowArchiveBridgeContext.next_artifact_kind || 'n/a'],
|
|
['archive prompt', workflowArchiveBridgeContext.next_artifact_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Open Notes',
|
|
onClick: () => prefillOperatorNote(''),
|
|
},
|
|
{
|
|
label: 'Prefill Focus Note',
|
|
onClick: () => prefillOperatorNote(
|
|
reviewEntrypoints.focus_note_prefill || '',
|
|
{
|
|
candidate_id: reviewEntrypoints.focus_candidate_id,
|
|
session_code: reviewEntrypoints.focus_session_code,
|
|
model_code: reviewEntrypoints.focus_model_code,
|
|
},
|
|
),
|
|
},
|
|
{
|
|
label: 'Open Review',
|
|
onClick: () => revealPanel(postTradeReviewPanel),
|
|
},
|
|
{
|
|
label: 'Generate Journal',
|
|
onClick: () => runAction('/trading/journal/generate', {}),
|
|
},
|
|
...(workflowArchiveBridgeContext ? [{
|
|
label: 'Clear Archive Bridge',
|
|
onClick: () => {
|
|
setWorkflowArchiveBridgeContext(null);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
}] : []),
|
|
])
|
|
);
|
|
if (reviewEntrypoints.review_entry_action_queue?.length) {
|
|
const reviewActionGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(reviewEntrypoints.review_entry_action_queue || []).forEach((item) => {
|
|
reviewActionGrid.appendChild(
|
|
buildItemCard(
|
|
item.label || 'review action',
|
|
item.priority || item.source || 'n/a',
|
|
[
|
|
['source', item.source || 'n/a'],
|
|
['prompt', item.prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(reviewActionGrid);
|
|
}
|
|
const noteTemplates = reviewEntrypoints.note_templates || [];
|
|
if (noteTemplates.length) {
|
|
const recommendedTemplate = noteTemplates.find((item) => item.template_id === reviewEntrypoints.recommended_template_id) || noteTemplates[0];
|
|
const tunedTemplateId = reviewEntrypoints.template_reuse_metrics?.feedback_tuned_template_id || reviewEntrypoints.recommended_template_id;
|
|
const tunedTemplate = noteTemplates.find((item) => item.template_id === tunedTemplateId) || recommendedTemplate;
|
|
const templateGrid = createElement('div', 'cards assistant-tight-grid');
|
|
noteTemplates.forEach((item) => {
|
|
const isRecommended = item.template_id === reviewEntrypoints.recommended_template_id;
|
|
templateGrid.appendChild(
|
|
buildItemCard(
|
|
isRecommended ? `Recommended: ${item.label || 'Template'}` : (item.label || 'Template'),
|
|
item.priority || 'medium',
|
|
[
|
|
['reason', item.reason || 'n/a'],
|
|
['scope', item.note_scope || 'workbench'],
|
|
['tags', formatList(item.tags || [])],
|
|
['context', formatList(item.context_items || [])],
|
|
['carry-over', formatList(item.carry_over_context || [])],
|
|
['reuse count', String(item.reuse_count ?? 0)],
|
|
['reuse status', item.reuse_status || 'n/a'],
|
|
['last used', formatTimestamp(item.last_used_ts)],
|
|
['outcome', item.outcome_status || 'n/a'],
|
|
['usefulness', formatPercent(item.usefulness_score)],
|
|
['outcome evidence', formatList(item.outcome_evidence || [])],
|
|
['feedback loop', item.feedback_loop_status || 'n/a'],
|
|
['feedback prompt', item.feedback_prompt || 'n/a'],
|
|
['negative guardrail', item.negative_feedback_guardrail || 'n/a'],
|
|
['guardrail prompt', item.negative_feedback_guardrail_prompt || 'n/a'],
|
|
['guardrail resolution', item.guardrail_resolution_status || 'n/a'],
|
|
['guardrail resolution prompt', item.guardrail_resolution_prompt || 'n/a'],
|
|
['preference decay', formatPercent(item.preferred_template_decay)],
|
|
['decay reason', item.preferred_template_decay_reason || 'n/a'],
|
|
['preference recovery', item.preference_recovery_status || 'n/a'],
|
|
['recovery prompt', item.preference_recovery_prompt || 'n/a'],
|
|
['recovery recommendation', item.preference_recovery_recommendation?.action || 'n/a'],
|
|
['audit lane', item.audit_lane_group || 'n/a'],
|
|
['audit lane rank', String(item.audit_lane_rank ?? 'n/a')],
|
|
['audit lane recommendation', item.audit_lane_recommendation?.action || 'n/a'],
|
|
['audit lane prompt', item.audit_lane_recommendation?.prompt || 'n/a'],
|
|
['recovery shortcuts', formatList((item.recovery_action_shortcuts || []).map((row) => row.label || row.action_id))],
|
|
['shortcut telemetry', formatList((item.shortcut_outcome_telemetry || []).map((row) => `${row.label || row.action_id}: ${row.telemetry_status}`))],
|
|
['audit filter', item.guardrail_audit_filter || 'n/a'],
|
|
['audit filter reason', item.guardrail_audit_filter_reason || 'n/a'],
|
|
['guardrail audit trail', formatList((item.guardrail_audit_trail || []).map((row) => `${row.label || row.signal}: ${row.resolution_status}`))],
|
|
['filtered audit trail', formatList((item.guardrail_audit_filtered_trail || []).map((row) => `${row.label || row.signal}: ${row.resolution_status}`))],
|
|
['feedback trend history', formatList((item.feedback_trend_history || []).map((row) => `${row.label || row.signal}: ${formatTimestamp(row.created_ts)}`))],
|
|
],
|
|
[
|
|
{
|
|
label: 'Use',
|
|
onClick: () => prefillOperatorNote(item.body || ''),
|
|
},
|
|
{
|
|
label: 'Save',
|
|
onClick: () => saveOperatorTemplateNote(item),
|
|
},
|
|
...(item.feedback_actions || []).map((action) => ({
|
|
label: action.label || action.outcome || 'Feedback',
|
|
onClick: () => saveOperatorTemplateFeedback(item, action.outcome || action.action_id || 'useful'),
|
|
})),
|
|
],
|
|
[item.body || 'n/a', item.follow_up_prompt || '', item.reuse_hint || ''].filter(Boolean).join(' | '),
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(templateGrid);
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Use Recommended',
|
|
onClick: () => prefillOperatorNote(recommendedTemplate?.body || ''),
|
|
},
|
|
{
|
|
label: 'Save Recommended',
|
|
onClick: () => saveOperatorTemplateNote(recommendedTemplate),
|
|
},
|
|
{
|
|
label: 'Use Tuned',
|
|
onClick: () => prefillOperatorNote(tunedTemplate?.body || ''),
|
|
},
|
|
{
|
|
label: 'Save Tuned',
|
|
onClick: () => saveOperatorTemplateNote(tunedTemplate),
|
|
},
|
|
])
|
|
);
|
|
const templateBar = buildActionBar(
|
|
noteTemplates.map((item) => ({
|
|
label: item.template_id === reviewEntrypoints.recommended_template_id
|
|
? `${item.label || 'Use Template'}`
|
|
: (item.label || 'Use Template'),
|
|
onClick: () => prefillOperatorNote(item.body || ''),
|
|
}))
|
|
);
|
|
decisionAssistantPanel.appendChild(templateBar);
|
|
const quickSaveBar = buildActionBar(
|
|
noteTemplates.map((item) => ({
|
|
label: `Save ${item.label || 'Template'}`,
|
|
onClick: () => saveOperatorTemplateNote(item),
|
|
}))
|
|
);
|
|
decisionAssistantPanel.appendChild(quickSaveBar);
|
|
}
|
|
}
|
|
if (workflowDigest.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Workflow');
|
|
if (workflowRhythmSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow rhythm', workflowRhythmSummary.status || 'idle'],
|
|
['rhythm phases', String(workflowRhythmSummary.phase_count ?? 0)],
|
|
['rhythm checks', String(workflowRhythmSummary.check_count ?? 0)],
|
|
['rhythm complete', String(workflowRhythmSummary.completed_count ?? 0)],
|
|
['rhythm pending', String(workflowRhythmSummary.pending_count ?? 0)],
|
|
['next rhythm phase', workflowRhythmSummary.next_phase || 'n/a'],
|
|
['next rhythm action', workflowRhythmSummary.next_action || 'n/a'],
|
|
['cadence', formatList(workflowRhythmSummary.cadence || [])],
|
|
])
|
|
);
|
|
}
|
|
if (workflowSessionCompletionSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['session tasks', workflowSessionCompletionSummary.status || 'idle'],
|
|
['session', workflowSessionCompletionSummary.current_session || 'n/a'],
|
|
['task count', String(workflowSessionCompletionSummary.task_count ?? 0)],
|
|
['tasks complete', String(workflowSessionCompletionSummary.completed_task_count ?? 0)],
|
|
['tasks in progress', String(workflowSessionCompletionSummary.in_progress_task_count ?? 0)],
|
|
['tasks pending', String(workflowSessionCompletionSummary.pending_task_count ?? 0)],
|
|
['next session task', workflowSessionCompletionSummary.next_task_label || 'n/a'],
|
|
['next session prompt', workflowSessionCompletionSummary.next_task_prompt || 'n/a'],
|
|
['completion ratio', formatPercent(workflowSessionCompletionSummary.completion_ratio)],
|
|
])
|
|
);
|
|
}
|
|
if (summary.workflow_artifact_summary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow artifacts', summary.workflow_artifact_summary.status || 'idle'],
|
|
['artifact count', String(summary.workflow_artifact_summary.artifact_count ?? 0)],
|
|
['artifacts ready', String(summary.workflow_artifact_summary.ready_for_handoff_count ?? 0)],
|
|
['artifacts updating', String(summary.workflow_artifact_summary.needs_update_count ?? 0)],
|
|
['artifacts pending', String(summary.workflow_artifact_summary.pending_capture_count ?? 0)],
|
|
['next artifact', summary.workflow_artifact_summary.next_artifact_kind || 'n/a'],
|
|
['next artifact prompt', summary.workflow_artifact_summary.next_artifact_prompt || 'n/a'],
|
|
['workflow archive', String(summary.workflow_artifact_summary.workflow_history_archive_count ?? 0)],
|
|
['workflow archive review', summary.workflow_artifact_summary.next_workflow_history_review || 'n/a'],
|
|
['workflow day groups', String(workflowArchiveData?.daily_group_count ?? workflowArchiveDailyGroups.length ?? 0)],
|
|
['workflow session groups', String(workflowArchiveData?.session_group_count ?? workflowArchiveSessionGroups.length ?? 0)],
|
|
['workflow compare', workflowArchiveCompare?.status || 'idle'],
|
|
['workflow compare summary', workflowArchiveCompare?.summary || 'n/a'],
|
|
['workflow persistence', summary.workflow_artifact_summary.workflow_persistence_status || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveBridgeSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow bridge', workflowArchiveBridgeSummary.status || 'idle'],
|
|
['bridge targets', String(workflowArchiveBridgeSummary.bridge_target_count ?? 0)],
|
|
['replay ready', String(workflowArchiveBridgeSummary.replay_ready_count ?? 0)],
|
|
['note ready', String(workflowArchiveBridgeSummary.note_ready_count ?? 0)],
|
|
['handoff ready', String(workflowArchiveBridgeSummary.handoff_ready_count ?? 0)],
|
|
['bridge snapshot', String(workflowArchiveBridgeSummary.latest_snapshot_id ?? 'n/a')],
|
|
['bridge session', workflowArchiveBridgeSummary.latest_session || 'n/a'],
|
|
['bridge next phase', workflowArchiveBridgeSummary.latest_next_phase || 'n/a'],
|
|
['bridge replay prompt', workflowArchiveBridgeSummary.next_replay_prompt || 'n/a'],
|
|
['bridge handoff prompt', workflowArchiveBridgeSummary.next_handoff_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveBridgePersistenceSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['bridge persistence', workflowArchiveBridgePersistenceSummary.status || 'idle'],
|
|
['bridge saved at', formatTimestamp(workflowArchiveBridgePersistenceSummary.updated_ts)],
|
|
['bridge saved preset', workflowArchiveBridgePersistenceSummary.query_context?.query_preset || 'custom'],
|
|
['bridge saved scope', workflowArchiveBridgePersistenceSummary.query_context?.profile_scope || 'selected_profile'],
|
|
['bridge saved handoff', workflowArchiveBridgePersistenceSummary.handoff_packet_starter?.headline || 'n/a'],
|
|
['bridge saved replay', workflowArchiveBridgePersistenceSummary.replay_note_bridge_summary?.note_title || 'n/a'],
|
|
['bridge saved bundle', workflowArchiveBridgePersistenceSummary.artifact_bundle_summary?.starter_export_prompt || 'n/a'],
|
|
['bridge saved compare note', workflowArchiveBridgePersistenceSummary.compare_writeback_summary?.note_title || 'n/a'],
|
|
['bridge compare note at', formatTimestamp(workflowArchiveBridgePersistenceSummary.compare_writeback_summary?.created_ts)],
|
|
])
|
|
);
|
|
if (workflowArchiveBridgePersistenceSummary.compare_writeback_summary?.note_body) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Prefill Saved Compare Note',
|
|
onClick: () => prefillOperatorNote(
|
|
workflowArchiveBridgePersistenceSummary.compare_writeback_summary?.note_body || '',
|
|
workflowArchiveBridgeContext || null,
|
|
),
|
|
},
|
|
])
|
|
);
|
|
}
|
|
}
|
|
if (workflowArchiveBridgeContext) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Open Bridged Replay',
|
|
onClick: () => revealAssistantSection('Setup Replay'),
|
|
},
|
|
{
|
|
label: 'Prefill Bridged Note',
|
|
onClick: () => prefillOperatorNote(buildWorkflowArchiveBridgeNote(workflowArchiveBridgeContext), workflowArchiveBridgeContext),
|
|
},
|
|
{
|
|
label: 'Clear Workflow Bridge',
|
|
onClick: () => {
|
|
setWorkflowArchiveBridgeContext(null);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
])
|
|
);
|
|
}
|
|
const workflowArchivePanel = createElement('div', 'assistant-archive-panel');
|
|
const workflowArchiveControls = createElement('div', 'assistant-archive-controls');
|
|
const workflowProfileField = createElement('label', 'assistant-preference-field');
|
|
workflowProfileField.appendChild(createElement('span', 'metric', 'Workflow Profile'));
|
|
const workflowProfileInput = createElement('input', 'assistant-preference-input');
|
|
workflowProfileInput.type = 'text';
|
|
workflowProfileInput.value = assistantWorkflowArchiveState.profile_key || selectedProfileKey;
|
|
workflowProfileField.appendChild(workflowProfileInput);
|
|
workflowArchiveControls.appendChild(workflowProfileField);
|
|
|
|
const workflowScopeField = createElement('label', 'assistant-preference-field');
|
|
workflowScopeField.appendChild(createElement('span', 'metric', 'Profile Scope'));
|
|
const workflowScopeSelect = createElement('select', 'assistant-preference-input');
|
|
['selected_profile', 'all_profiles'].forEach((scope) => {
|
|
const option = createElement('option', '', scope);
|
|
option.value = scope;
|
|
option.selected = scope === (assistantWorkflowArchiveState.profile_scope || workflowArchiveSavedDefaults.profile_scope || 'selected_profile');
|
|
workflowScopeSelect.appendChild(option);
|
|
});
|
|
workflowScopeField.appendChild(workflowScopeSelect);
|
|
workflowArchiveControls.appendChild(workflowScopeField);
|
|
|
|
const workflowPresetField = createElement('label', 'assistant-preference-field');
|
|
workflowPresetField.appendChild(createElement('span', 'metric', 'Query Preset'));
|
|
const workflowPresetSelect = createElement('select', 'assistant-preference-input');
|
|
[{ preset_id: '', label: 'custom' }, ...((workflowArchiveData?.available_query_presets || []))].forEach((preset) => {
|
|
const option = createElement('option', '', preset.label || preset.preset_id || 'custom');
|
|
option.value = preset.preset_id || '';
|
|
option.selected = (preset.preset_id || '') === (assistantWorkflowArchiveState.query_preset || workflowArchiveSavedDefaults.query_preset || '');
|
|
workflowPresetSelect.appendChild(option);
|
|
});
|
|
workflowPresetField.appendChild(workflowPresetSelect);
|
|
workflowArchiveControls.appendChild(workflowPresetField);
|
|
|
|
const workflowSessionField = createElement('label', 'assistant-preference-field');
|
|
workflowSessionField.appendChild(createElement('span', 'metric', 'Current Session'));
|
|
const workflowSessionInput = createElement('input', 'assistant-preference-input');
|
|
workflowSessionInput.type = 'text';
|
|
workflowSessionInput.value = assistantWorkflowArchiveState.current_session || '';
|
|
workflowSessionField.appendChild(workflowSessionInput);
|
|
workflowArchiveControls.appendChild(workflowSessionField);
|
|
|
|
const workflowPhaseField = createElement('label', 'assistant-preference-field');
|
|
workflowPhaseField.appendChild(createElement('span', 'metric', 'Next Phase'));
|
|
const workflowPhaseSelect = createElement('select', 'assistant-preference-input');
|
|
['', 'pre_market', 'intraday', 'post_trade'].forEach((phase) => {
|
|
const option = createElement('option', '', phase || 'all');
|
|
option.value = phase;
|
|
option.selected = phase === (assistantWorkflowArchiveState.next_phase || '');
|
|
workflowPhaseSelect.appendChild(option);
|
|
});
|
|
workflowPhaseField.appendChild(workflowPhaseSelect);
|
|
workflowArchiveControls.appendChild(workflowPhaseField);
|
|
|
|
const workflowArtifactField = createElement('label', 'assistant-preference-field');
|
|
workflowArtifactField.appendChild(createElement('span', 'metric', 'Artifact Kind'));
|
|
const workflowArtifactInput = createElement('input', 'assistant-preference-input');
|
|
workflowArtifactInput.type = 'text';
|
|
workflowArtifactInput.value = assistantWorkflowArchiveState.next_artifact_kind || '';
|
|
workflowArtifactField.appendChild(workflowArtifactInput);
|
|
workflowArchiveControls.appendChild(workflowArtifactField);
|
|
|
|
const workflowSearchField = createElement('label', 'assistant-preference-field');
|
|
workflowSearchField.appendChild(createElement('span', 'metric', 'Search Text'));
|
|
const workflowSearchInput = createElement('input', 'assistant-preference-input');
|
|
workflowSearchInput.type = 'text';
|
|
workflowSearchInput.value = assistantWorkflowArchiveState.query_text || '';
|
|
workflowSearchInput.placeholder = 'phase, prompt, body...';
|
|
workflowSearchField.appendChild(workflowSearchInput);
|
|
workflowArchiveControls.appendChild(workflowSearchField);
|
|
|
|
const workflowAfterField = createElement('label', 'assistant-preference-field');
|
|
workflowAfterField.appendChild(createElement('span', 'metric', 'Created After'));
|
|
const workflowAfterInput = createElement('input', 'assistant-preference-input');
|
|
workflowAfterInput.type = 'datetime-local';
|
|
workflowAfterInput.value = assistantWorkflowArchiveState.created_after || '';
|
|
workflowAfterField.appendChild(workflowAfterInput);
|
|
workflowArchiveControls.appendChild(workflowAfterField);
|
|
|
|
const workflowBeforeField = createElement('label', 'assistant-preference-field');
|
|
workflowBeforeField.appendChild(createElement('span', 'metric', 'Created Before'));
|
|
const workflowBeforeInput = createElement('input', 'assistant-preference-input');
|
|
workflowBeforeInput.type = 'datetime-local';
|
|
workflowBeforeInput.value = assistantWorkflowArchiveState.created_before || '';
|
|
workflowBeforeField.appendChild(workflowBeforeInput);
|
|
workflowArchiveControls.appendChild(workflowBeforeField);
|
|
|
|
const workflowLimitField = createElement('label', 'assistant-preference-field');
|
|
workflowLimitField.appendChild(createElement('span', 'metric', 'Workflow Limit'));
|
|
const workflowLimitInput = createElement('input', 'assistant-preference-input');
|
|
workflowLimitInput.type = 'number';
|
|
workflowLimitInput.min = '1';
|
|
workflowLimitInput.max = '50';
|
|
workflowLimitInput.value = String(assistantWorkflowArchiveState.limit || 12);
|
|
workflowLimitField.appendChild(workflowLimitInput);
|
|
workflowArchiveControls.appendChild(workflowLimitField);
|
|
workflowArchivePanel.appendChild(workflowArchiveControls);
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Apply Workflow Filters',
|
|
onClick: async () => {
|
|
await fetchAssistantWorkflowArchive({
|
|
profile_key: workflowProfileInput.value || selectedProfileKey,
|
|
profile_scope: workflowScopeSelect.value || 'selected_profile',
|
|
query_preset: workflowPresetSelect.value || '',
|
|
current_session: workflowSessionInput.value || '',
|
|
next_phase: workflowPhaseSelect.value || '',
|
|
next_artifact_kind: workflowArtifactInput.value || '',
|
|
query_text: workflowSearchInput.value || '',
|
|
created_after: workflowAfterInput.value || '',
|
|
created_before: workflowBeforeInput.value || '',
|
|
limit: Number(workflowLimitInput.value || 12),
|
|
selected_left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
selected_right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Load More Workflow',
|
|
onClick: async () => {
|
|
await fetchAssistantWorkflowArchive({
|
|
append: true,
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: Number(assistantWorkflowArchiveState.limit || 12),
|
|
selected_left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
selected_right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Compare Selected Workflow',
|
|
onClick: async () => {
|
|
if (!assistantWorkflowArchiveState.selected_left_id || !assistantWorkflowArchiveState.selected_right_id) {
|
|
showErrorPanel('select_two_workflow_snapshots');
|
|
return;
|
|
}
|
|
await fetchAssistantWorkflowArchive({
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: Number(assistantWorkflowArchiveState.limit || 12),
|
|
});
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Export Workflow Results',
|
|
onClick: async () => {
|
|
const exportLimit = Math.max(Number(assistantWorkflowArchiveState.limit || 12), Number(workflowArchiveData?.items?.length || 0));
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: exportLimit,
|
|
offset: 0,
|
|
})}`);
|
|
assistantWorkflowArchiveLastExport = exportPayload;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
downloadJsonFile(exportPayload.export_name || 'workflow-archive-export.json', exportPayload);
|
|
},
|
|
},
|
|
{
|
|
label: 'Export Workflow Compare',
|
|
onClick: async () => {
|
|
if (!assistantWorkflowArchiveState.selected_left_id || !assistantWorkflowArchiveState.selected_right_id) {
|
|
showErrorPanel('select_two_workflow_snapshots');
|
|
return;
|
|
}
|
|
const exportLimit = Math.max(Number(assistantWorkflowArchiveState.limit || 12), Number(workflowArchiveData?.items?.length || 0));
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: exportLimit,
|
|
offset: 0,
|
|
left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
})}`);
|
|
assistantWorkflowArchiveLastExport = exportPayload;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
downloadJsonFile(exportPayload.export_name || 'workflow-archive-compare-export.json', exportPayload);
|
|
},
|
|
},
|
|
{
|
|
label: 'Save Workflow Baseline',
|
|
onClick: async () => {
|
|
const baselineId = assistantWorkflowArchiveState.pinned_baseline_id || workflowArchiveBaselineSummary?.baseline_snapshot_id;
|
|
if (!baselineId) {
|
|
showErrorPanel('workflow_baseline_missing');
|
|
return;
|
|
}
|
|
await persistAssistantArchiveBaselinesServer({
|
|
profileKey: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
workflowSnapshotId: baselineId,
|
|
});
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Save Workflow Defaults',
|
|
onClick: async () => {
|
|
const saved = await persistAssistantDigestPreferenceServer({
|
|
profileKey: selectedProfileKey,
|
|
digestMode: assistantPreferenceDraft?.digest_mode || digestPreferenceProfile?.persisted_digest_mode || 'all_reminders',
|
|
preferredHistoryKind: assistantPreferenceDraft?.preferred_history_kind === 'all'
|
|
? null
|
|
: (assistantPreferenceDraft?.preferred_history_kind || digestPreferenceProfile?.preferred_history_kind || null),
|
|
importanceThreshold: assistantPreferenceDraft?.importance_threshold || digestPreferenceProfile?.importance_threshold || 70,
|
|
payload: {
|
|
profile_label: assistantPreferenceDraft?.profile_label || digestPreferenceProfile?.profile_label || selectedProfileKey,
|
|
history_window_hours: assistantPreferenceDraft?.history_window_hours || digestPreferenceProfile?.history_window_hours || 24,
|
|
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
|
|
archive_defaults: mergeAssistantArchiveDefaults(summary, {
|
|
workflow: {
|
|
profile_scope: workflowScopeSelect.value || 'selected_profile',
|
|
query_preset: workflowPresetSelect.value || '',
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
if (!saved) {
|
|
showErrorPanel('workflow_archive_defaults_save_failed');
|
|
return;
|
|
}
|
|
assistantPreferenceDraft = {
|
|
...(assistantPreferenceDraft || {}),
|
|
archive_defaults: mergeAssistantArchiveDefaults(summary, {
|
|
workflow: {
|
|
profile_scope: workflowScopeSelect.value || 'selected_profile',
|
|
query_preset: workflowPresetSelect.value || '',
|
|
},
|
|
}),
|
|
};
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Use Saved Workflow Defaults',
|
|
onClick: () => {
|
|
assistantWorkflowArchiveState.profile_scope = workflowArchiveSavedDefaults.profile_scope || 'selected_profile';
|
|
assistantWorkflowArchiveState.query_preset = workflowArchiveSavedDefaults.query_preset || '';
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Clear Workflow Filters',
|
|
onClick: () => {
|
|
assistantWorkflowArchiveState = {
|
|
profile_key: selectedProfileKey,
|
|
profile_scope: 'selected_profile',
|
|
query_preset: '',
|
|
current_session: '',
|
|
next_phase: '',
|
|
next_artifact_kind: '',
|
|
query_text: '',
|
|
created_after: '',
|
|
created_before: '',
|
|
limit: 12,
|
|
next_offset: 0,
|
|
selected_left_id: null,
|
|
selected_right_id: null,
|
|
pinned_baseline_id: null,
|
|
};
|
|
window.__assistantWorkflowArchiveResponse = null;
|
|
window.__assistantWorkflowArchiveCompare = null;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
])
|
|
);
|
|
workflowArchivePanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow query profile', assistantWorkflowArchiveState.profile_key || selectedProfileKey],
|
|
['workflow profile scope', assistantWorkflowArchiveState.profile_scope || workflowArchiveSavedDefaults.profile_scope || 'selected_profile'],
|
|
['workflow preset', assistantWorkflowArchiveState.query_preset || workflowArchiveSavedDefaults.query_preset || 'custom'],
|
|
['workflow preset summary', workflowArchiveData?.query_preset_summary || 'custom_filters'],
|
|
['saved workflow defaults', `${workflowArchiveSavedDefaults.profile_scope || 'selected_profile'} / ${workflowArchiveSavedDefaults.query_preset || 'custom'}`],
|
|
['workflow query session', assistantWorkflowArchiveState.current_session || 'all'],
|
|
['workflow query phase', assistantWorkflowArchiveState.next_phase || 'all'],
|
|
['workflow query artifact', assistantWorkflowArchiveState.next_artifact_kind || 'all'],
|
|
['workflow query text', assistantWorkflowArchiveState.query_text || 'n/a'],
|
|
['workflow created after', assistantWorkflowArchiveState.created_after || 'n/a'],
|
|
['workflow created before', assistantWorkflowArchiveState.created_before || 'n/a'],
|
|
['workflow trend', workflowArchiveLongRangeSummary?.trend_status || 'n/a'],
|
|
['workflow trend story', workflowArchiveLongRangeSummary?.trend_story || 'n/a'],
|
|
['workflow 7d / 30d', `${workflowArchiveLongRangeSummary?.recent_7d_count ?? 0} / ${workflowArchiveLongRangeSummary?.recent_30d_count ?? 0}`],
|
|
['workflow span days', String(workflowArchiveLongRangeSummary?.covered_day_span ?? 0)],
|
|
['workflow baseline', String(assistantWorkflowArchiveState.pinned_baseline_id ?? workflowArchiveBaselineSummary?.baseline_snapshot_id ?? 'n/a')],
|
|
['workflow baseline persistence', workflowArchiveBaselineSummary?.persistence_status || digestPreferenceProfile?.archive_baselines?.status || 'n/a'],
|
|
['workflow baseline delta', workflowArchiveBaselineSummary?.baseline_delta_status || 'n/a'],
|
|
['workflow baseline delta story', workflowArchiveBaselineSummary?.baseline_delta_story || 'n/a'],
|
|
['workflow baseline prompt', workflowArchiveBaselineSummary?.suggested_compare_prompt || 'n/a'],
|
|
['workflow next offset', String(workflowArchiveData?.next_offset ?? assistantWorkflowArchiveState.next_offset ?? 0)],
|
|
['workflow compare left', String(assistantWorkflowArchiveState.selected_left_id ?? 'n/a')],
|
|
['workflow compare right', String(assistantWorkflowArchiveState.selected_right_id ?? 'n/a')],
|
|
])
|
|
);
|
|
if (assistantWorkflowArchiveLastExport?.selection_provenance_summary) {
|
|
const exportProvenance = assistantWorkflowArchiveLastExport.selection_provenance_summary;
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow export provenance', exportProvenance.selection_label || 'n/a'],
|
|
['workflow export scope', exportProvenance.export_scope || 'n/a'],
|
|
['workflow selected snapshots', formatList((exportProvenance.selected_snapshot_ids || []).map((item) => String(item)))],
|
|
['workflow active filters', String(exportProvenance.active_filter_count ?? 0)],
|
|
['workflow baseline snapshot', String(exportProvenance.baseline_snapshot_id ?? 'n/a')],
|
|
['selection fingerprint', exportProvenance.selection_fingerprint || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (assistantWorkflowArchiveLastExport?.bundle_manifest) {
|
|
const bundleManifest = assistantWorkflowArchiveLastExport.bundle_manifest;
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow bundle manifest', bundleManifest.manifest_label || 'n/a'],
|
|
['workflow manifest version', bundleManifest.manifest_version || 'n/a'],
|
|
['workflow manifest sections', formatList(bundleManifest.included_sections || [])],
|
|
['workflow manifest section count', String(bundleManifest.included_section_count ?? 0)],
|
|
['workflow manifest story', bundleManifest.manifest_story || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveRankedAggregation) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow top profile', workflowArchiveRankedAggregation.top_profile?.value || 'n/a'],
|
|
['workflow top session', workflowArchiveRankedAggregation.top_session?.value || 'n/a'],
|
|
['workflow top phase', workflowArchiveRankedAggregation.top_phase?.value || 'n/a'],
|
|
['workflow aggregation story', workflowArchiveRankedAggregation.aggregation_story || 'n/a'],
|
|
])
|
|
);
|
|
const workflowRankGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(workflowArchiveRankedAggregation.profile_rankings || []).slice(0, 2).forEach((item) => {
|
|
workflowRankGrid.appendChild(
|
|
buildItemCard(`Profile ${item.value || 'n/a'}`, String(item.count ?? 0), [['count', String(item.count ?? 0)]])
|
|
);
|
|
});
|
|
(workflowArchiveRankedAggregation.session_rankings || []).slice(0, 2).forEach((item) => {
|
|
workflowRankGrid.appendChild(
|
|
buildItemCard(`Session ${item.value || 'n/a'}`, String(item.count ?? 0), [['count', String(item.count ?? 0)]])
|
|
);
|
|
});
|
|
(workflowArchiveRankedAggregation.phase_rankings || []).slice(0, 2).forEach((item) => {
|
|
workflowRankGrid.appendChild(
|
|
buildItemCard(`Phase ${item.value || 'n/a'}`, String(item.count ?? 0), [['count', String(item.count ?? 0)]])
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(workflowRankGrid);
|
|
}
|
|
decisionAssistantPanel.appendChild(workflowArchivePanel);
|
|
if (workflowArchiveOperationsSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow operations', workflowArchiveOperationsSummary.status || 'idle'],
|
|
['operation snapshots', String(workflowArchiveOperationsSummary.snapshot_count ?? 0)],
|
|
['day groups', String(workflowArchiveOperationsSummary.day_group_count ?? 0)],
|
|
['session groups', String(workflowArchiveOperationsSummary.session_group_count ?? 0)],
|
|
['latest day group', workflowArchiveOperationsSummary.latest_group_date || 'n/a'],
|
|
['most active session', workflowArchiveOperationsSummary.most_active_session || 'n/a'],
|
|
['phase names', String(workflowArchiveOperationsSummary.phase_name_count ?? 0)],
|
|
['operations prompt', workflowArchiveOperationsSummary.next_operations_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveLongRangeSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow long-range', workflowArchiveLongRangeSummary.status || 'idle'],
|
|
['workflow trend status', workflowArchiveLongRangeSummary.trend_status || 'n/a'],
|
|
['workflow 7d snapshots', String(workflowArchiveLongRangeSummary.recent_7d_count ?? 0)],
|
|
['workflow 30d snapshots', String(workflowArchiveLongRangeSummary.recent_30d_count ?? 0)],
|
|
['workflow 7d ready', String(workflowArchiveLongRangeSummary.recent_7d_ready_count ?? 0)],
|
|
['workflow 30d ready', String(workflowArchiveLongRangeSummary.recent_30d_ready_count ?? 0)],
|
|
['workflow sessions', String(workflowArchiveLongRangeSummary.distinct_session_count ?? 0)],
|
|
['workflow next long-range review', workflowArchiveLongRangeSummary.next_review_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveBaselineSummary) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow baseline status', workflowArchiveBaselineSummary.status || 'idle'],
|
|
['workflow baseline latest', String(workflowArchiveBaselineSummary.latest_snapshot_id ?? 'n/a')],
|
|
['workflow baseline pinned', String(assistantWorkflowArchiveState.pinned_baseline_id ?? workflowArchiveBaselineSummary.baseline_snapshot_id ?? 'n/a')],
|
|
['workflow baseline session', workflowArchiveBaselineSummary.baseline_session || 'n/a'],
|
|
['workflow baseline phase', workflowArchiveBaselineSummary.baseline_next_phase || 'n/a'],
|
|
['workflow baseline prompt', workflowArchiveBaselineSummary.suggested_compare_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveDriftReview) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow drift', workflowArchiveDriftReview.status || 'idle'],
|
|
['drift status', workflowArchiveDriftReview.drift_status || 'n/a'],
|
|
['drift left day', workflowArchiveDriftReview.left_group_date || 'n/a'],
|
|
['drift right day', workflowArchiveDriftReview.right_group_date || 'n/a'],
|
|
['changed phases', String(workflowArchiveDriftReview.changed_phase_count ?? 0)],
|
|
['session changed', workflowArchiveDriftReview.session_changed ? 'yes' : 'no'],
|
|
['next phase changed', workflowArchiveDriftReview.next_phase_changed ? 'yes' : 'no'],
|
|
['drift summary', workflowArchiveDriftReview.summary || 'n/a'],
|
|
['drift next prompt', workflowArchiveDriftReview.next_drift_prompt || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (workflowArchiveDailyGroups.length) {
|
|
const workflowDayGroupGrid = createElement('div', 'cards assistant-tight-grid');
|
|
workflowArchiveDailyGroups.forEach((item) => {
|
|
workflowDayGroupGrid.appendChild(
|
|
buildItemCard(
|
|
`Day Group: ${item.group_date || 'workflow'}`,
|
|
item.status || 'active',
|
|
[
|
|
['snapshots', String(item.snapshot_count ?? 0)],
|
|
['latest time', formatTimestamp(item.latest_created_ts)],
|
|
['sessions', formatList(item.sessions || [])],
|
|
['next phase', item.next_phase || 'n/a'],
|
|
['artifact counts', `${item.artifact_count ?? 0} / ${item.ready_for_handoff_count ?? 0} / ${item.needs_update_count ?? 0} / ${item.pending_capture_count ?? 0}`],
|
|
['phase names', formatList(item.phase_names || [])],
|
|
['workflow body', item.workflow_body || 'n/a'],
|
|
['next prompt', item.next_artifact_prompt || 'n/a'],
|
|
],
|
|
[
|
|
{
|
|
label: 'Export Day Bundle',
|
|
onClick: async () => {
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: workflowArchiveData?.item_count || assistantWorkflowArchiveState.limit || 12,
|
|
offset: 0,
|
|
group_date: item.group_date || '',
|
|
})}`);
|
|
assistantWorkflowArchiveLastExport = exportPayload;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
downloadJsonFile(exportPayload.export_name || `workflow-day-group-${item.group_date || 'export'}.json`, exportPayload);
|
|
},
|
|
},
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(workflowDayGroupGrid);
|
|
}
|
|
if (workflowArchiveSessionGroups.length) {
|
|
const workflowSessionGroupGrid = createElement('div', 'cards assistant-tight-grid');
|
|
workflowArchiveSessionGroups.forEach((item) => {
|
|
workflowSessionGroupGrid.appendChild(
|
|
buildItemCard(
|
|
`Session Group: ${item.session_code || 'workflow'}`,
|
|
item.status || 'active',
|
|
[
|
|
['snapshots', String(item.snapshot_count ?? 0)],
|
|
['day count', String(item.day_count ?? 0)],
|
|
['latest time', formatTimestamp(item.latest_created_ts)],
|
|
['latest day group', item.latest_group_date || 'n/a'],
|
|
['latest next phase', item.latest_next_phase || 'n/a'],
|
|
['artifact counts', `${item.artifact_count ?? 0} / ${item.ready_for_handoff_count ?? 0} / ${item.needs_update_count ?? 0} / ${item.pending_capture_count ?? 0}`],
|
|
['phase names', formatList(item.phase_names || [])],
|
|
],
|
|
[
|
|
{
|
|
label: 'Export Session Bundle',
|
|
onClick: async () => {
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: workflowArchiveData?.item_count || assistantWorkflowArchiveState.limit || 12,
|
|
offset: 0,
|
|
session_group: item.session_code || '',
|
|
})}`);
|
|
assistantWorkflowArchiveLastExport = exportPayload;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
downloadJsonFile(exportPayload.export_name || `workflow-session-group-${item.session_code || 'export'}.json`, exportPayload);
|
|
},
|
|
},
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(workflowSessionGroupGrid);
|
|
}
|
|
if (workflowArchiveCompare?.status === 'active') {
|
|
const workflowPhaseDrilldown = (workflowArchiveCompare.phase_changes || []).map((item) => `${item.phase}: ${item.changed ? 'changed' : 'same'}`).join(', ');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['workflow compare left', formatTimestamp(workflowArchiveCompare.left_created_ts || workflowArchiveCompare.left?.created_ts)],
|
|
['workflow compare right', formatTimestamp(workflowArchiveCompare.right_created_ts || workflowArchiveCompare.right?.created_ts)],
|
|
['phase changes', String((workflowArchiveCompare.phase_changes || []).filter((item) => item.changed).length)],
|
|
['artifact delta', String(workflowArchiveCompare.count_delta?.artifact_count ?? 0)],
|
|
['ready delta', String(workflowArchiveCompare.count_delta?.ready_for_handoff_count ?? 0)],
|
|
['update delta', String(workflowArchiveCompare.count_delta?.needs_update_count ?? 0)],
|
|
['pending delta', String(workflowArchiveCompare.count_delta?.pending_capture_count ?? 0)],
|
|
['phase drilldown', workflowPhaseDrilldown || 'n/a'],
|
|
])
|
|
);
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Prefill Compare Replay Note',
|
|
onClick: () => prefillOperatorNote(buildWorkflowArchiveCompareReplayNote(workflowArchiveCompare)),
|
|
},
|
|
{
|
|
label: 'Persist Compare Replay Note',
|
|
onClick: async () => {
|
|
const payload = await persistWorkflowArchiveCompareWritebackServer({
|
|
profileKey: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
compare: workflowArchiveCompare,
|
|
});
|
|
if (!payload || payload.status !== 'saved') {
|
|
showErrorPanel('workflow_compare_writeback_failed');
|
|
return;
|
|
}
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Export Handoff Starter',
|
|
onClick: async () => {
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: Math.max(Number(assistantWorkflowArchiveState.limit || 12), Number(workflowArchiveData?.items?.length || 0)),
|
|
left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
})}`);
|
|
downloadJsonFile(exportPayload.export_name || 'workflow-handoff-starter.json', {
|
|
export_schema_version: exportPayload.export_schema_version,
|
|
handoff_packet_starter: exportPayload.handoff_packet_starter,
|
|
handoff_bundle_summary: exportPayload.handoff_bundle_summary,
|
|
replay_note_bridge_summary: exportPayload.replay_note_bridge_summary,
|
|
});
|
|
},
|
|
},
|
|
{
|
|
label: 'Persist Bridge State',
|
|
onClick: async () => {
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: Math.max(Number(assistantWorkflowArchiveState.limit || 12), Number(workflowArchiveData?.items?.length || 0)),
|
|
left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
})}`);
|
|
const saved = await persistWorkflowArchiveBridgeStateServer({
|
|
summary,
|
|
exportPayload,
|
|
profileKey: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
});
|
|
if (!saved) {
|
|
showErrorPanel('workflow_bridge_state_persist_failed');
|
|
return;
|
|
}
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Export Artifact Bundle',
|
|
onClick: async () => {
|
|
const exportPayload = await fetchJson(`/trading/workflow-archive/export?${queryString({
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
profile_key: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
profile_scope: assistantWorkflowArchiveState.profile_scope || 'selected_profile',
|
|
query_preset: assistantWorkflowArchiveState.query_preset || '',
|
|
current_session: assistantWorkflowArchiveState.current_session || '',
|
|
next_phase: assistantWorkflowArchiveState.next_phase || '',
|
|
next_artifact_kind: assistantWorkflowArchiveState.next_artifact_kind || '',
|
|
query_text: assistantWorkflowArchiveState.query_text || '',
|
|
created_after: assistantWorkflowArchiveState.created_after || '',
|
|
created_before: assistantWorkflowArchiveState.created_before || '',
|
|
limit: Math.max(Number(assistantWorkflowArchiveState.limit || 12), Number(workflowArchiveData?.items?.length || 0)),
|
|
left_id: assistantWorkflowArchiveState.selected_left_id,
|
|
right_id: assistantWorkflowArchiveState.selected_right_id,
|
|
})}`);
|
|
downloadJsonFile(exportPayload.export_name || 'workflow-artifact-bundle.json', {
|
|
export_schema_version: exportPayload.export_schema_version,
|
|
artifact_bundle_summary: exportPayload.artifact_bundle_summary,
|
|
structured_export_summary: exportPayload.structured_export_summary,
|
|
daily_groups: workflowArchiveData?.daily_groups || [],
|
|
session_groups: workflowArchiveData?.session_groups || [],
|
|
});
|
|
},
|
|
},
|
|
])
|
|
);
|
|
const workflowCompareGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(workflowArchiveCompare.phase_changes || []).forEach((item) => {
|
|
workflowCompareGrid.appendChild(
|
|
buildItemCard(
|
|
item.phase || 'phase',
|
|
item.changed ? 'changed' : 'same',
|
|
[
|
|
['left body', item.left_body || 'n/a'],
|
|
['right body', item.right_body || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(workflowCompareGrid);
|
|
}
|
|
const workflowGrid = createElement('div', 'cards');
|
|
workflowDigest.forEach((item) => {
|
|
workflowGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.phase || 'workflow',
|
|
item.status || 'n/a',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
['next step', item.next_step || 'n/a'],
|
|
['rhythm status', item.rhythm_status || 'n/a'],
|
|
['rhythm pending', String(item.rhythm_pending_count ?? 0)],
|
|
['next rhythm action', item.next_rhythm_action || 'n/a'],
|
|
['session task', item.session_task_label || 'n/a'],
|
|
['session task status', item.session_task_status || 'n/a'],
|
|
['session task prompt', item.session_task_prompt || 'n/a'],
|
|
['checklist', formatList((item.rhythm_checklist || []).map((row) => `${row.label || row.check_id}: ${row.status}`))],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(workflowGrid);
|
|
const workflowArtifacts = summary.workflow_artifacts || [];
|
|
if (workflowArtifacts.length) {
|
|
const artifactGrid = createElement('div', 'cards assistant-tight-grid');
|
|
workflowArtifacts.forEach((item) => {
|
|
artifactGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.phase || 'artifact',
|
|
item.artifact_state || 'pending',
|
|
[
|
|
['kind', item.artifact_kind || 'n/a'],
|
|
['session task', item.session_task_label || 'n/a'],
|
|
['slot', item.daily_rhythm_slot || 'n/a'],
|
|
['prompt', item.prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(artifactGrid);
|
|
}
|
|
if (workflowArchiveData?.items?.length) {
|
|
const workflowArchiveGrid = createElement('div', 'cards assistant-tight-grid');
|
|
(workflowArchiveData.items || []).forEach((item) => {
|
|
const isLeft = String(item.snapshot_id || '') === String(assistantWorkflowArchiveState.selected_left_id || '');
|
|
const isRight = String(item.snapshot_id || '') === String(assistantWorkflowArchiveState.selected_right_id || '');
|
|
const baselineId = assistantWorkflowArchiveState.pinned_baseline_id || workflowArchiveBaselineSummary?.baseline_snapshot_id;
|
|
const isBaseline = String(item.snapshot_id || '') === String(baselineId || '');
|
|
const card = buildItemCard(
|
|
item.next_artifact_kind || item.next_phase || 'workflow archive',
|
|
isBaseline ? 'baseline_pinned' : (isLeft ? 'left_selected' : (isRight ? 'right_selected' : (item.current_session || 'session'))),
|
|
[
|
|
['snapshot', String(item.snapshot_id ?? 'n/a')],
|
|
['profile', item.profile_key || 'n/a'],
|
|
['time', formatTimestamp(item.created_ts)],
|
|
['focus mode', item.focus_mode || 'n/a'],
|
|
['artifacts', String(item.artifact_count ?? 0)],
|
|
['ready/update/pending', `${item.ready_for_handoff_count ?? 0} / ${item.needs_update_count ?? 0} / ${item.pending_capture_count ?? 0}`],
|
|
['workflow body', item.workflow_body || 'n/a'],
|
|
['phase briefs', formatList((item.phase_briefs || []).map((row) => `${row.phase || 'phase'}: ${row.archive_body || row.detail || 'n/a'}`))],
|
|
],
|
|
[
|
|
{
|
|
label: isLeft ? 'Left Selected' : 'Select Left',
|
|
onClick: () => {
|
|
selectAssistantWorkflowArchiveSnapshot('left', item.snapshot_id);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: isRight ? 'Right Selected' : 'Select Right',
|
|
onClick: () => {
|
|
selectAssistantWorkflowArchiveSnapshot('right', item.snapshot_id);
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Replay Bridge',
|
|
onClick: () => {
|
|
setWorkflowArchiveBridgeContext(buildWorkflowArchiveBridgeContext(item, 'replay'));
|
|
revealAssistantSection('Setup Replay');
|
|
},
|
|
},
|
|
{
|
|
label: 'Note Bridge',
|
|
onClick: () => {
|
|
const context = buildWorkflowArchiveBridgeContext(item, 'note');
|
|
setWorkflowArchiveBridgeContext(context);
|
|
prefillOperatorNote(buildWorkflowArchiveBridgeNote(item), context);
|
|
},
|
|
},
|
|
{
|
|
label: 'Handoff Bridge',
|
|
onClick: () => {
|
|
setWorkflowArchiveBridgeContext(buildWorkflowArchiveBridgeContext(item, 'handoff'));
|
|
revealAssistantSection('Workflow');
|
|
},
|
|
},
|
|
{
|
|
label: isBaseline ? 'Baseline Pinned' : 'Pin Baseline',
|
|
onClick: () => {
|
|
assistantWorkflowArchiveState.pinned_baseline_id = item.snapshot_id;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
{
|
|
label: 'Save This Workflow Baseline',
|
|
onClick: async () => {
|
|
assistantWorkflowArchiveState.pinned_baseline_id = item.snapshot_id;
|
|
await persistAssistantArchiveBaselinesServer({
|
|
profileKey: assistantWorkflowArchiveState.profile_key || selectedProfileKey,
|
|
workflowSnapshotId: item.snapshot_id,
|
|
});
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
{
|
|
label: 'Compare To Baseline',
|
|
onClick: () => {
|
|
const pinnedId = assistantWorkflowArchiveState.pinned_baseline_id || workflowArchiveBaselineSummary?.baseline_snapshot_id;
|
|
if (!pinnedId || String(pinnedId) === String(item.snapshot_id || '')) return;
|
|
assistantWorkflowArchiveState.selected_left_id = item.snapshot_id;
|
|
assistantWorkflowArchiveState.selected_right_id = pinnedId;
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
},
|
|
]
|
|
);
|
|
if (isLeft || isRight || isBaseline) card.classList.add('assistant-compare-selected');
|
|
workflowArchiveGrid.appendChild(
|
|
card
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(workflowArchiveGrid);
|
|
}
|
|
}
|
|
if (opportunities.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Opportunities');
|
|
const opportunityGrid = createElement('div', 'cards');
|
|
opportunities.forEach((item) => {
|
|
opportunityGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'opportunity',
|
|
item.status || 'observe',
|
|
[
|
|
['kind', item.kind || 'n/a'],
|
|
['detail', item.detail || 'n/a'],
|
|
['priority', String(item.priority_score ?? 'n/a')],
|
|
['reason', item.reason || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(opportunityGrid);
|
|
}
|
|
[
|
|
['review_now', 'Review Now'],
|
|
['strong_watch', 'Strong Watch'],
|
|
['soft_watch', 'Soft Watch'],
|
|
['archive_only', 'Archive Only'],
|
|
].forEach(([key, label]) => {
|
|
const items = watchlistTiers[key] || [];
|
|
if (!items.length) return;
|
|
appendSectionTitle(decisionAssistantPanel, `Watchlist: ${label}`);
|
|
if (key === 'review_now' && watchlistSummary.focus_matrix) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['watchlist focus', watchlistSummary.focus_matrix.status || 'idle'],
|
|
['recommended tier', watchlistSummary.focus_matrix.recommended_tier || 'n/a'],
|
|
['recommended session', watchlistSummary.focus_matrix.recommended_session_code || 'n/a'],
|
|
['watchlist next prompt', watchlistSummary.next_watchlist_focus_prompt || 'n/a'],
|
|
['tier counts', formatJsonInline(watchlistSummary.focus_matrix.tier_counts || {})],
|
|
['session counts', formatJsonInline(watchlistSummary.focus_matrix.session_counts || {})],
|
|
])
|
|
);
|
|
}
|
|
const tierGrid = createElement('div', 'cards assistant-tight-grid');
|
|
items.forEach((item) => {
|
|
const card = buildItemCard(
|
|
`${label}: ${item.title || 'candidate'}`,
|
|
item.status || key,
|
|
[
|
|
['session', item.session_code || 'n/a'],
|
|
['model', item.model_code || 'n/a'],
|
|
['quality', String(item.quality_score ?? 'n/a')],
|
|
['why this focus', item.trader_focus_reason || item.tier_reason || 'n/a'],
|
|
['why no trade', item.trader_no_trade_reason || 'read-only analysis'],
|
|
['watch next', item.trader_watch_next || item.suggested_action || 'n/a'],
|
|
['missing confirmations', formatList(item.missing_confirmations || [])],
|
|
],
|
|
item.action_path && item.action_method === 'POST'
|
|
? [{
|
|
label: 'Run Confirm Check',
|
|
onClick: () => runAction(item.action_path, {}),
|
|
}]
|
|
: []
|
|
);
|
|
card.classList.add('clickable-card');
|
|
if (resolvedFocusCandidate && String(resolvedFocusCandidate.candidate_id || '') === String(item.candidate_id || '')) {
|
|
card.classList.add('assistant-focus-card');
|
|
}
|
|
card.addEventListener('click', () => {
|
|
(async () => {
|
|
persistAssistantFocusCandidateId(item.candidate_id);
|
|
await persistAssistantFocusCandidateServer(item.candidate_id);
|
|
await refreshDashboard();
|
|
})();
|
|
});
|
|
tierGrid.appendChild(card);
|
|
});
|
|
decisionAssistantPanel.appendChild(tierGrid);
|
|
});
|
|
if (noTradeItems.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'No-Trade Reasons');
|
|
const noTradeGrid = createElement('div', 'cards assistant-tight-grid');
|
|
noTradeItems.forEach((item) => {
|
|
noTradeGrid.appendChild(
|
|
buildItemCard(
|
|
item.reason || 'no-trade',
|
|
item.priority || 'low',
|
|
[
|
|
['category', item.category || 'n/a'],
|
|
['source', item.source || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(noTradeGrid);
|
|
}
|
|
if (decisionTimeline.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Timeline');
|
|
const timelineGrid = createElement('div', 'cards');
|
|
decisionTimeline.forEach((item) => {
|
|
timelineGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'timeline',
|
|
item.ts || 'n/a',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(timelineGrid);
|
|
}
|
|
if (explanationDrilldown.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Explain');
|
|
const drilldownGrid = createElement('div', 'cards');
|
|
explanationDrilldown.forEach((item) => {
|
|
drilldownGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'explanation',
|
|
item.status || 'n/a',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
['reasons', formatList(item.reasons || [])],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(drilldownGrid);
|
|
}
|
|
if (reasonCitations.length) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Reason Citations');
|
|
const citationGrid = createElement('div', 'cards');
|
|
reasonCitations.forEach((item) => {
|
|
citationGrid.appendChild(
|
|
buildItemCard(
|
|
item.reason || 'citation',
|
|
item.severity || 'supporting',
|
|
[
|
|
['source', item.source_type || 'n/a'],
|
|
['path', item.source_path || 'n/a'],
|
|
['detail', item.detail || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(citationGrid);
|
|
}
|
|
if (learningRollup) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Learning');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['learning status', learningRollup.status || 'idle'],
|
|
['review entries', String(learningRollup.entry_count ?? 0)],
|
|
['pending feedback', String(learningRollup.pending_feedback_count ?? 0)],
|
|
['visual mismatches', String(learningRollup.visual_mismatch_count ?? 0)],
|
|
['high-value alerts', String(learningRollup.high_value_alert_count ?? 0)],
|
|
['quality flags', formatList(learningRollup.quality_flags || [])],
|
|
['next review actions', formatList(learningRollup.next_week_actions || [])],
|
|
['learning summary', learningRollup.operator_summary || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (externalContext) {
|
|
appendSectionTitle(decisionAssistantPanel, 'External Context');
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['external context', externalContext.status || 'idle'],
|
|
['external headline', externalContext.headline || 'n/a'],
|
|
['news provider', `${externalContext.provider_name || 'unknown'} / ${externalContext.freshness_code || 'unknown'}`],
|
|
['news window', externalContext.news_window_code || 'unknown'],
|
|
['active events', formatList(externalContext.active_event_titles || [])],
|
|
['macro/news calendar', String(externalContext.macro_news_calendar_count ?? 0)],
|
|
['macro/news providers', String(externalContext.macro_news_provider_count ?? 0)],
|
|
['provider states', formatJsonInline(externalContext.macro_news_provider_status_counts || {})],
|
|
['event window', externalContext.event_window_status || 'n/a'],
|
|
['event window prompt', externalContext.event_window_prompt || 'n/a'],
|
|
['high volatility window', String(Boolean(externalContext.high_volatility_window))],
|
|
['low liquidity window', String(Boolean(externalContext.low_liquidity_window))],
|
|
['exchange anomaly window', String(Boolean(externalContext.exchange_anomaly_window))],
|
|
['chart sources', String(externalContext.chart_readonly_source_count ?? 0)],
|
|
['chart provider registry', String(externalContext.chart_provider_registry_count ?? 0)],
|
|
['chart provider states', formatJsonInline(externalContext.chart_provider_registry_status_counts || {})],
|
|
['stale chart sources', String(externalContext.chart_stale_source_count ?? 0)],
|
|
['chart adapter mode', externalContext.chart_adapter_mode || 'n/a'],
|
|
['chart primary mode', externalContext.chart_primary_mode || 'n/a'],
|
|
['chart context prompt', externalContext.chart_context_prompt || 'n/a'],
|
|
['exchange surfaces', String(externalContext.exchange_context_source_count ?? 0)],
|
|
['exchange surface states', formatJsonInline(externalContext.exchange_context_status_counts || {})],
|
|
['provider health cards', String(externalContext.provider_health_card_count ?? 0)],
|
|
['provider health states', formatJsonInline(externalContext.provider_health_status_counts || {})],
|
|
['provider health next', externalContext.provider_health_summary?.next_health_target || 'n/a'],
|
|
['provider health prompt', externalContext.provider_health_summary?.next_health_prompt || 'n/a'],
|
|
['provider health story', externalContext.provider_health_summary?.health_story || 'n/a'],
|
|
['capture surfaces', String(externalContext.capture_surface_card_count ?? 0)],
|
|
['capture surface states', formatJsonInline(externalContext.capture_surface_status_counts || {})],
|
|
['capture next target', externalContext.capture_surface_summary?.next_capture_target || 'n/a'],
|
|
['capture next prompt', externalContext.capture_surface_summary?.next_capture_prompt || 'n/a'],
|
|
['capture surface story', externalContext.capture_surface_summary?.capture_story || 'n/a'],
|
|
['screenshot readiness', externalContext.screenshot_readiness_summary?.status || 'n/a'],
|
|
['screenshot ready', String(externalContext.screenshot_readiness_summary?.screenshot_ready_count ?? 0)],
|
|
['autodiscovery needed', String(externalContext.screenshot_readiness_summary?.autodiscovery_needed_count ?? 0)],
|
|
['screenshot embed ready', String(externalContext.screenshot_readiness_summary?.embed_ready_count ?? 0)],
|
|
['screenshot next target', externalContext.screenshot_readiness_summary?.next_screenshot_target || 'n/a'],
|
|
['screenshot next prompt', externalContext.screenshot_readiness_summary?.next_screenshot_prompt || 'n/a'],
|
|
['screenshot story', externalContext.screenshot_readiness_summary?.screenshot_story || 'n/a'],
|
|
['visual references', String(externalContext.visual_reference_card_count ?? 0)],
|
|
['visual prompt', externalContext.visual_reference_next_prompt || 'n/a'],
|
|
['visual evidence bridge', externalContext.visual_evidence_bridge_summary?.status || 'n/a'],
|
|
['visual bridge target', externalContext.visual_evidence_bridge_summary?.next_bridge_target || 'n/a'],
|
|
['visual bridge prompt', externalContext.visual_evidence_bridge_summary?.next_bridge_prompt || 'n/a'],
|
|
['visual bridge story', externalContext.visual_evidence_bridge_summary?.bridge_story || 'n/a'],
|
|
['surface history', externalContext.surface_selection_history_summary?.status || 'n/a'],
|
|
['surface history count', String(externalContext.surface_selection_history_summary?.entry_count ?? 0)],
|
|
['latest surface', externalContext.surface_selection_history_summary?.latest_title || 'n/a'],
|
|
['latest selection kind', externalContext.surface_selection_history_summary?.latest_selection_kind || 'n/a'],
|
|
['surface history recency', externalContext.surface_selection_history_summary?.recency_status || 'n/a'],
|
|
['surface history cadence', externalContext.surface_selection_history_summary?.cadence_status || 'n/a'],
|
|
['surface cadence story', externalContext.surface_selection_history_summary?.cadence_story || 'n/a'],
|
|
['surface revisit needed', String(Boolean(externalContext.surface_selection_history_summary?.revisit_needed))],
|
|
['revisit queue count', String(externalContext.surface_selection_history_summary?.revisit_queue_count ?? 0)],
|
|
['acknowledged revisits', String(externalContext.surface_selection_history_summary?.acknowledged_count ?? 0)],
|
|
['resolution notes', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_count ?? 0)],
|
|
['resolution note gaps', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_count ?? 0)],
|
|
['note gap queue', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_queue_count ?? 0)],
|
|
['note gap oldest', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_oldest_title || 'n/a'],
|
|
['note gap avg age', typeof externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_average_age_seconds === 'number' ? `${Math.round(externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_average_age_seconds / 60)}m` : 'n/a'],
|
|
['resolution owners', formatJsonInline(externalContext.surface_selection_history_summary?.resolution_filter_summary?.acknowledged_by_counts || {})],
|
|
['owner spotlight', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_spotlight?.owner || 'n/a'],
|
|
['owner spotlight count', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_spotlight?.count ?? 0)],
|
|
['owner cadence', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_cadence_status || 'n/a'],
|
|
['owner cadence avg gap', typeof externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_cadence_average_gap_seconds === 'number' ? `${Math.round(externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_cadence_average_gap_seconds / 60)}m` : 'n/a'],
|
|
['owner cadence story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_cadence_story || 'n/a'],
|
|
['owner trend', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_trend_status || 'n/a'],
|
|
['owner trend story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_trend_story || 'n/a'],
|
|
['owner handoff', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_handoff_status || 'n/a'],
|
|
['owner handoff story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_handoff_story || 'n/a'],
|
|
['owner handoff follow-up', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_handoff_follow_up_status || 'n/a'],
|
|
['owner handoff follow-up story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_handoff_follow_up_story || 'n/a'],
|
|
['resolved owner drift', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_status || 'n/a'],
|
|
['resolved owner drift story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_story || 'n/a'],
|
|
['resolved owner drift review', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_review_status || 'n/a'],
|
|
['resolved owner drift review story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_review_story || 'n/a'],
|
|
['drift review queue', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_review_queue_count ?? 0)],
|
|
['drift review queue story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_review_queue_story || 'n/a'],
|
|
['drift review queue aging', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_review_queue_aging_status || 'n/a'],
|
|
['drift review queue aging story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.resolved_owner_drift_review_queue_aging_story || 'n/a'],
|
|
['active resolution filter', assistantSurfaceResolutionFilter || 'all'],
|
|
['cleared queue status', externalContext.surface_selection_history_summary?.cleared_queue_status || 'n/a'],
|
|
['cleared queue story', externalContext.surface_selection_history_summary?.cleared_queue_story || 'n/a'],
|
|
['resolution history count', String(externalContext.surface_selection_history_summary?.resolution_history_count ?? 0)],
|
|
['latest acknowledged by', externalContext.surface_selection_history_summary?.latest_acknowledged_by || 'n/a'],
|
|
['latest acknowledged note', externalContext.surface_selection_history_summary?.latest_acknowledged_note || 'n/a'],
|
|
['surface revisit prompt', externalContext.surface_selection_history_summary?.revisit_prompt || 'n/a'],
|
|
['next revisit prompt', externalContext.surface_selection_history_summary?.next_revisit_prompt || 'n/a'],
|
|
['resolution review prompt', externalContext.surface_selection_history_summary?.next_resolution_review_prompt || 'n/a'],
|
|
['note gap prompt', externalContext.surface_selection_history_summary?.resolution_filter_summary?.next_note_gap_prompt || 'n/a'],
|
|
['owner spotlight prompt', externalContext.surface_selection_history_summary?.resolution_filter_summary?.owner_spotlight_prompt || 'n/a'],
|
|
['priority lane status', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_status || 'n/a'],
|
|
['priority lane count', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_count ?? 0)],
|
|
['priority lane story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_story || 'n/a'],
|
|
['priority lane carry-forward', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_carry_forward_status || 'n/a'],
|
|
['priority lane carry-forward story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_carry_forward_story || 'n/a'],
|
|
['carry-forward aging', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_carry_forward_aging_status || 'n/a'],
|
|
['carry-forward aging story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.note_gap_priority_lane_carry_forward_aging_story || 'n/a'],
|
|
['follow-up lane status', externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_status || 'n/a'],
|
|
['follow-up lane count', String(externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_count ?? 0)],
|
|
['follow-up lane story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_story || 'n/a'],
|
|
['follow-up lane rollover', externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_rollover_status || 'n/a'],
|
|
['follow-up lane rollover story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_rollover_story || 'n/a'],
|
|
['rollover handoff', externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_rollover_handoff_status || 'n/a'],
|
|
['rollover handoff story', externalContext.surface_selection_history_summary?.resolution_filter_summary?.carry_forward_follow_up_lane_rollover_handoff_story || 'n/a'],
|
|
['priority lane prompt', externalContext.surface_selection_history_summary?.resolution_filter_summary?.next_priority_lane_prompt || 'n/a'],
|
|
['rollover prompt', externalContext.surface_selection_history_summary?.resolution_filter_summary?.next_follow_up_lane_rollover_prompt || 'n/a'],
|
|
['rollover handoff prompt', externalContext.surface_selection_history_summary?.resolution_filter_summary?.next_rollover_handoff_prompt || 'n/a'],
|
|
['surface history story', externalContext.surface_selection_history_summary?.provenance_story || 'n/a'],
|
|
['dominant surface', externalContext.surface_selection_history_summary?.dominant_surface_title || 'n/a'],
|
|
['dominant surface count', String(externalContext.surface_selection_history_summary?.dominant_surface_count ?? 0)],
|
|
['dominant source group', externalContext.surface_selection_history_summary?.dominant_source_group || 'n/a'],
|
|
['dominant surface story', externalContext.surface_selection_history_summary?.dominant_surface_story || 'n/a'],
|
|
['chart stale evidence', formatList((externalContext.chart_stale_source_evidence || []).map((item) => `${item.source_id}: ${item.evidence}`))],
|
|
['exchange status', `${externalContext.exchange_status?.provider_name || 'unknown'} / ${externalContext.exchange_status?.status || 'unknown'}`],
|
|
['exchange events', formatList(externalContext.exchange_status_titles || [])],
|
|
['announcements', `${externalContext.announcements_snapshot?.provider_name || 'unknown'} / ${externalContext.announcements_snapshot?.status || 'unknown'}`],
|
|
['announcement titles', formatList(externalContext.announcement_titles || [])],
|
|
['waiting items', formatList(externalContext.external_waiting_items || [])],
|
|
['council watch items', formatList(externalContext.council_watch_items || [])],
|
|
['operator questions', formatList(externalContext.operator_questions || [])],
|
|
['next confirmation targets', formatList(externalContext.next_confirmation_targets || [])],
|
|
])
|
|
);
|
|
if (externalContext.visual_evidence_bridge_summary?.next_bridge_url) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Open Visual Bridge',
|
|
onClick: () => openTrackedExternalContextLink(externalContext.visual_evidence_bridge_summary?.next_bridge_url, {
|
|
surface_id: externalContext.visual_evidence_bridge_summary?.next_bridge_target || 'visual_bridge',
|
|
title: externalContext.visual_evidence_bridge_summary?.next_bridge_target || 'visual bridge',
|
|
source_group: 'visual_evidence_bridge',
|
|
selection_kind: 'bridge_target',
|
|
prompt: externalContext.visual_evidence_bridge_summary?.next_bridge_prompt || '',
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
}
|
|
if (externalContext.capture_surface_summary?.next_capture_url) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Open Capture Surface',
|
|
onClick: () => openTrackedExternalContextLink(externalContext.capture_surface_summary?.next_capture_url, {
|
|
surface_id: externalContext.capture_surface_summary?.next_capture_target || 'capture_surface',
|
|
title: externalContext.capture_surface_summary?.next_capture_target || 'capture surface',
|
|
source_group: 'capture_surface',
|
|
selection_kind: 'capture_target',
|
|
prompt: externalContext.capture_surface_summary?.next_capture_prompt || '',
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
}
|
|
if (externalContext.screenshot_readiness_summary?.next_screenshot_url) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Open Screenshot Surface',
|
|
onClick: () => openTrackedExternalContextLink(externalContext.screenshot_readiness_summary?.next_screenshot_url, {
|
|
surface_id: externalContext.screenshot_readiness_summary?.next_screenshot_target || 'screenshot_surface',
|
|
title: externalContext.screenshot_readiness_summary?.next_screenshot_target || 'screenshot surface',
|
|
source_group: 'screenshot_surface',
|
|
selection_kind: 'screenshot_target',
|
|
prompt: externalContext.screenshot_readiness_summary?.next_screenshot_prompt || '',
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
}
|
|
const surfaceHistory = externalContext.surface_selection_history_summary?.items || [];
|
|
if (surfaceHistory.length) {
|
|
if (externalContext.surface_selection_history_summary?.revisit_target_url) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Reopen Latest Surface',
|
|
onClick: () => openTrackedExternalContextLink(
|
|
externalContext.surface_selection_history_summary?.revisit_target_url,
|
|
{
|
|
surface_id: externalContext.surface_selection_history_summary?.revisit_surface_id || 'external_surface',
|
|
title: externalContext.surface_selection_history_summary?.revisit_surface_title || 'external surface',
|
|
source_group: 'surface_history_revisit',
|
|
selection_kind: 'revisit_target',
|
|
prompt: externalContext.surface_selection_history_summary?.revisit_prompt || '',
|
|
},
|
|
),
|
|
},
|
|
])
|
|
);
|
|
}
|
|
const revisitQueue = externalContext.surface_selection_history_summary?.revisit_queue || [];
|
|
if (revisitQueue.length) {
|
|
const revisitGrid = createElement('div', 'cards assistant-tight-grid');
|
|
revisitQueue.forEach((item) => {
|
|
revisitGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'revisit surface',
|
|
item.recency_status || 'aging',
|
|
[
|
|
['group', item.source_group || 'n/a'],
|
|
['age', typeof item.age_seconds === 'number' ? `${Math.round(item.age_seconds / 60)}m` : 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['selected at', formatTimestamp(item.selected_ts)],
|
|
],
|
|
item.target_url ? [{
|
|
label: 'Reopen Revisit',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'revisit_target',
|
|
prompt: externalContext.surface_selection_history_summary?.next_revisit_prompt || '',
|
|
}),
|
|
}, {
|
|
label: 'Acknowledge Revisit',
|
|
onClick: async () => {
|
|
const payload = await acknowledgeExternalContextSurfaceHistory({
|
|
surface_id: item.surface_id || '',
|
|
selected_ts: item.selected_ts || '',
|
|
});
|
|
if (!payload || payload.status !== 'saved') {
|
|
showErrorPanel('surface_history_acknowledge_failed');
|
|
return;
|
|
}
|
|
await refreshDashboard();
|
|
},
|
|
}, {
|
|
label: 'Acknowledge With Note',
|
|
onClick: async () => {
|
|
const note = window.prompt('Resolution note', '');
|
|
if (note == null) return;
|
|
const payload = await acknowledgeExternalContextSurfaceHistory({
|
|
surface_id: item.surface_id || '',
|
|
selected_ts: item.selected_ts || '',
|
|
note,
|
|
});
|
|
if (!payload || payload.status !== 'saved') {
|
|
showErrorPanel('surface_history_acknowledge_failed');
|
|
return;
|
|
}
|
|
await refreshDashboard();
|
|
},
|
|
}] : []
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(revisitGrid);
|
|
}
|
|
const historyGrid = createElement('div', 'cards assistant-tight-grid');
|
|
surfaceHistory.forEach((item) => {
|
|
historyGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'surface history',
|
|
item.selection_kind || 'target_url',
|
|
[
|
|
['group', item.source_group || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['selected at', formatTimestamp(item.selected_ts)],
|
|
['prompt', item.prompt || 'n/a'],
|
|
],
|
|
item.target_url ? [{
|
|
label: 'Reopen Surface',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: item.selection_kind || 'target_url',
|
|
prompt: item.prompt || '',
|
|
}),
|
|
}] : []
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(historyGrid);
|
|
}
|
|
const resolutionHistory = externalContext.surface_selection_history_summary?.resolution_history || [];
|
|
if (resolutionHistory.length) {
|
|
const resolutionFilterSummary = externalContext.surface_selection_history_summary?.resolution_filter_summary || {};
|
|
const resolutionFilterOptions = (resolutionFilterSummary.filter_options && resolutionFilterSummary.filter_options.length)
|
|
? resolutionFilterSummary.filter_options
|
|
: getDefaultSurfaceResolutionFilterOptions(externalContext.surface_selection_history_summary || {});
|
|
if (resolutionFilterOptions.length) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar(
|
|
resolutionFilterOptions.map((item) => ({
|
|
label: item.label || item.filter_id || 'resolved',
|
|
onClick: () => {
|
|
assistantSurfaceResolutionFilter = item.filter_id || 'all';
|
|
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
|
},
|
|
}))
|
|
)
|
|
);
|
|
}
|
|
const noteGapQueue = resolutionFilterSummary.note_gap_queue || [];
|
|
if (noteGapQueue.length) {
|
|
const noteGapGrid = createElement('div', 'cards assistant-tight-grid');
|
|
noteGapQueue.forEach((item) => {
|
|
noteGapGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'note gap surface',
|
|
'note_gap',
|
|
[
|
|
['acknowledged by', item.revisit_acknowledged_by || 'n/a'],
|
|
['group', item.source_group || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['acknowledged at', formatTimestamp(item.revisit_acknowledged_ts)],
|
|
],
|
|
[
|
|
item.target_url && {
|
|
label: 'Reopen Note Gap',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'note_gap_target',
|
|
prompt: resolutionFilterSummary.next_note_gap_prompt || '',
|
|
}),
|
|
},
|
|
{
|
|
label: 'Add Resolution Note',
|
|
onClick: async () => {
|
|
const note = window.prompt('Resolution note', item.revisit_ack_note || '');
|
|
if (note == null) return;
|
|
const payload = await acknowledgeExternalContextSurfaceHistory({
|
|
surface_id: item.surface_id || '',
|
|
selected_ts: item.selected_ts || '',
|
|
note,
|
|
});
|
|
if (!payload || payload.status !== 'saved') {
|
|
showErrorPanel('surface_history_acknowledge_failed');
|
|
return;
|
|
}
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
].filter(Boolean)
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(noteGapGrid);
|
|
}
|
|
const priorityLane = resolutionFilterSummary.note_gap_priority_lane_items || [];
|
|
if (priorityLane.length) {
|
|
const priorityGrid = createElement('div', 'cards assistant-tight-grid');
|
|
priorityLane.forEach((item) => {
|
|
priorityGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'priority lane',
|
|
typeof item.age_seconds === 'number' ? `${Math.round(item.age_seconds / 60)}m` : 'aging',
|
|
[
|
|
['group', item.source_group || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['selected at', formatTimestamp(item.selected_ts)],
|
|
],
|
|
[
|
|
item.target_url && {
|
|
label: 'Open Priority Surface',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'priority_lane_target',
|
|
prompt: resolutionFilterSummary.next_priority_lane_prompt || '',
|
|
}),
|
|
},
|
|
].filter(Boolean)
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(priorityGrid);
|
|
}
|
|
const followUpLane = resolutionFilterSummary.carry_forward_follow_up_lane_items || [];
|
|
if (followUpLane.length) {
|
|
const followUpGrid = createElement('div', 'cards assistant-tight-grid');
|
|
followUpLane.forEach((item) => {
|
|
followUpGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'follow-up lane',
|
|
typeof item.age_seconds === 'number' ? `${Math.round(item.age_seconds / 60)}m` : 'follow_up',
|
|
[
|
|
['group', item.source_group || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['selected at', formatTimestamp(item.selected_ts)],
|
|
],
|
|
[
|
|
item.target_url && {
|
|
label: 'Open Follow-Up Lane',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'follow_up_lane_target',
|
|
prompt: resolutionFilterSummary.next_follow_up_lane_prompt || '',
|
|
}),
|
|
},
|
|
item.target_url && {
|
|
label: 'Open Rollover Surface',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'follow_up_lane_rollover_target',
|
|
prompt: resolutionFilterSummary.next_follow_up_lane_rollover_prompt || '',
|
|
}),
|
|
},
|
|
].filter(Boolean)
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(followUpGrid);
|
|
}
|
|
const driftReviewQueue = resolutionFilterSummary.resolved_owner_drift_review_queue_items || [];
|
|
if (driftReviewQueue.length) {
|
|
const driftReviewGrid = createElement('div', 'cards assistant-tight-grid');
|
|
driftReviewQueue.forEach((item) => {
|
|
driftReviewGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'drift review',
|
|
'drift_review',
|
|
[
|
|
['group', item.source_group || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['selected at', formatTimestamp(item.selected_ts)],
|
|
],
|
|
[
|
|
item.target_url && {
|
|
label: 'Open Drift Review',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'drift_review_target',
|
|
prompt: resolutionFilterSummary.next_follow_up_lane_rollover_prompt || '',
|
|
}),
|
|
},
|
|
item.target_url && {
|
|
label: 'Open Rollover Handoff',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'rollover_handoff_target',
|
|
prompt: resolutionFilterSummary.next_rollover_handoff_prompt || '',
|
|
}),
|
|
},
|
|
].filter(Boolean)
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(driftReviewGrid);
|
|
}
|
|
const filteredResolutionHistory = resolutionHistory.filter((item) => matchesSurfaceResolutionFilter(item, assistantSurfaceResolutionFilter));
|
|
const resolutionGrid = createElement('div', 'cards assistant-tight-grid');
|
|
filteredResolutionHistory.forEach((item) => {
|
|
resolutionGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.surface_id || 'resolved revisit',
|
|
item.revisit_acknowledged_by || 'resolved',
|
|
[
|
|
['acknowledged at', formatTimestamp(item.revisit_acknowledged_ts)],
|
|
['group', item.source_group || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['note', item.revisit_ack_note || 'n/a'],
|
|
],
|
|
[
|
|
item.target_url && {
|
|
label: 'Reopen Cleared Surface',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || 'external_surface',
|
|
title: item.title || item.surface_id || 'external surface',
|
|
source_group: item.source_group || 'external_context',
|
|
selection_kind: 'resolved_target',
|
|
prompt: externalContext.surface_selection_history_summary?.next_resolution_review_prompt || '',
|
|
}),
|
|
},
|
|
{
|
|
label: 'Add Resolution Note',
|
|
onClick: async () => {
|
|
const note = window.prompt('Resolution note', item.revisit_ack_note || '');
|
|
if (note == null) return;
|
|
const payload = await acknowledgeExternalContextSurfaceHistory({
|
|
surface_id: item.surface_id || '',
|
|
selected_ts: item.selected_ts || '',
|
|
note,
|
|
});
|
|
if (!payload || payload.status !== 'saved') {
|
|
showErrorPanel('surface_history_acknowledge_failed');
|
|
return;
|
|
}
|
|
await refreshDashboard();
|
|
},
|
|
},
|
|
].filter(Boolean)
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(resolutionGrid);
|
|
}
|
|
const surfaceRankings = externalContext.surface_selection_history_summary?.surface_rankings || [];
|
|
const sourceGroupRankings = externalContext.surface_selection_history_summary?.source_group_rankings || [];
|
|
if (surfaceRankings.length || sourceGroupRankings.length) {
|
|
const groupingGrid = createElement('div', 'cards assistant-tight-grid');
|
|
surfaceRankings.slice(0, 3).forEach((item) => {
|
|
groupingGrid.appendChild(
|
|
buildItemCard(
|
|
`Surface ${item.value || 'n/a'}`,
|
|
String(item.count ?? 0),
|
|
[['count', String(item.count ?? 0)]]
|
|
)
|
|
);
|
|
});
|
|
sourceGroupRankings.slice(0, 3).forEach((item) => {
|
|
groupingGrid.appendChild(
|
|
buildItemCard(
|
|
`Group ${item.value || 'n/a'}`,
|
|
String(item.count ?? 0),
|
|
[['count', String(item.count ?? 0)]]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(groupingGrid);
|
|
}
|
|
const sourceCards = externalContext.source_cards || [];
|
|
if (sourceCards.length) {
|
|
const sourceGrid = createElement('div', 'cards');
|
|
sourceCards.forEach((item) => {
|
|
sourceGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.source_type || 'context source',
|
|
item.source_type || 'source',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(sourceGrid);
|
|
}
|
|
const providerInventory = externalContext.macro_news_provider_inventory || [];
|
|
if (providerInventory.length) {
|
|
const providerGrid = createElement('div', 'cards assistant-tight-grid');
|
|
providerInventory.forEach((item) => {
|
|
providerGrid.appendChild(
|
|
buildItemCard(
|
|
item.provider_name || 'macro provider',
|
|
item.status || 'unknown',
|
|
[
|
|
['freshness', item.freshness_status || 'n/a'],
|
|
['surface', item.surface_url || 'n/a'],
|
|
['prompt', item.visual_prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(providerGrid);
|
|
}
|
|
const providerHealthCards = externalContext.provider_health_cards || [];
|
|
if (providerHealthCards.length) {
|
|
const healthGrid = createElement('div', 'cards assistant-tight-grid');
|
|
providerHealthCards.forEach((item) => {
|
|
const actions = [];
|
|
if (item.target_url) {
|
|
actions.push({
|
|
label: 'Open Target',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.health_id || item.title || item.provider_name || 'provider_health',
|
|
title: item.title || item.provider_name || 'provider health',
|
|
source_group: 'provider_health',
|
|
selection_kind: 'target_url',
|
|
prompt: item.next_visual_prompt || item.health_reason || '',
|
|
}),
|
|
});
|
|
}
|
|
if (item.embed_url) {
|
|
actions.push({
|
|
label: 'Open Embed',
|
|
onClick: () => openTrackedExternalContextLink(item.embed_url, {
|
|
surface_id: item.health_id || item.title || item.provider_name || 'provider_health_embed',
|
|
title: item.title || item.provider_name || 'provider health embed',
|
|
source_group: 'provider_health',
|
|
selection_kind: 'embed_url',
|
|
prompt: item.next_visual_prompt || item.health_reason || '',
|
|
}),
|
|
});
|
|
}
|
|
healthGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.provider_name || 'provider health',
|
|
item.status || 'monitor',
|
|
[
|
|
['group', item.surface_group || 'n/a'],
|
|
['source status', item.source_status || 'n/a'],
|
|
['freshness', item.freshness_status || 'n/a'],
|
|
['cache', item.cache_state || 'n/a'],
|
|
['capture', String(Boolean(item.capture_supported))],
|
|
['embed', item.embed_url ? 'available' : 'n/a'],
|
|
['reason', item.health_reason || 'n/a'],
|
|
['prompt', item.next_visual_prompt || 'n/a'],
|
|
],
|
|
actions
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(healthGrid);
|
|
}
|
|
const captureSurfaceCards = externalContext.capture_surface_cards || [];
|
|
if (captureSurfaceCards.length) {
|
|
const captureGrid = createElement('div', 'cards assistant-tight-grid');
|
|
captureSurfaceCards.forEach((item) => {
|
|
const actions = [];
|
|
if (item.target_url) {
|
|
actions.push({
|
|
label: 'Open Capture Target',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.surface_id || item.title || item.provider_name || 'capture_surface',
|
|
title: item.title || item.provider_name || 'capture surface',
|
|
source_group: 'capture_surface_card',
|
|
selection_kind: 'target_url',
|
|
prompt: item.capture_surface_prompt || '',
|
|
}),
|
|
});
|
|
}
|
|
if (item.embed_url) {
|
|
actions.push({
|
|
label: 'Open Capture Embed',
|
|
onClick: () => openTrackedExternalContextLink(item.embed_url, {
|
|
surface_id: item.surface_id || item.title || item.provider_name || 'capture_surface_embed',
|
|
title: item.title || item.provider_name || 'capture surface embed',
|
|
source_group: 'capture_surface_card',
|
|
selection_kind: 'embed_url',
|
|
prompt: item.capture_surface_prompt || '',
|
|
}),
|
|
});
|
|
}
|
|
captureGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.provider_name || 'capture surface',
|
|
item.capture_readiness || item.status || 'surface',
|
|
[
|
|
['group', item.surface_group || 'n/a'],
|
|
['capture', String(Boolean(item.capture_supported))],
|
|
['screenshot status', item.screenshot_capture_status || 'n/a'],
|
|
['embed status', item.screenshot_embed_status || 'n/a'],
|
|
['embed', item.embed_ready ? 'ready' : 'n/a'],
|
|
['bridge', item.evidence_bridge_status || 'n/a'],
|
|
['browser capture', String(Boolean(item.browser_capture_enabled))],
|
|
['browser configured', String(Boolean(item.browser_path_configured))],
|
|
['screenshot timeout', String(item.screenshot_timeout_seconds ?? 'n/a')],
|
|
['viewport', formatJsonInline(item.screenshot_viewport || {})],
|
|
['target', item.target_url || 'n/a'],
|
|
['embed url', item.embed_url || 'n/a'],
|
|
['prompt', item.capture_surface_prompt || 'n/a'],
|
|
],
|
|
actions
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(captureGrid);
|
|
}
|
|
const calendarEvents = externalContext.macro_news_calendar || [];
|
|
if (calendarEvents.length) {
|
|
const calendarGrid = createElement('div', 'cards assistant-tight-grid');
|
|
calendarEvents.forEach((item) => {
|
|
calendarGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'event',
|
|
item.window_type || item.source_type || 'window',
|
|
[
|
|
['source', item.source_type || 'n/a'],
|
|
['impact', item.impact || 'n/a'],
|
|
['time', formatTimestamp(item.event_ts)],
|
|
['detail', item.detail || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(calendarGrid);
|
|
}
|
|
const chartSources = externalContext.chart_readonly_sources || [];
|
|
if (chartSources.length) {
|
|
const chartGrid = createElement('div', 'cards assistant-tight-grid');
|
|
chartSources.forEach((item) => {
|
|
chartGrid.appendChild(
|
|
buildItemCard(
|
|
item.source_id || 'chart source',
|
|
item.status || 'source',
|
|
[
|
|
['type', item.source_type || 'n/a'],
|
|
['freshness', item.freshness_status || 'n/a'],
|
|
['status', item.status || 'n/a'],
|
|
['stale evidence', item.stale_source_evidence || 'n/a'],
|
|
['prompt', item.prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(chartGrid);
|
|
}
|
|
const chartProviderRegistry = externalContext.chart_provider_registry || [];
|
|
if (chartProviderRegistry.length) {
|
|
const chartProviderGrid = createElement('div', 'cards assistant-tight-grid');
|
|
chartProviderRegistry.forEach((item) => {
|
|
chartProviderGrid.appendChild(
|
|
buildItemCard(
|
|
item.provider_name || 'chart provider',
|
|
item.status || 'unknown',
|
|
[
|
|
['symbol', item.symbol || 'n/a'],
|
|
['interval', item.interval || 'n/a'],
|
|
['freshness', item.freshness_status || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['embed', item.embed_url || 'n/a'],
|
|
['capture', String(Boolean(item.capture_supported))],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(chartProviderGrid);
|
|
}
|
|
const exchangeSources = externalContext.exchange_context_sources || [];
|
|
if (exchangeSources.length) {
|
|
const exchangeGrid = createElement('div', 'cards assistant-tight-grid');
|
|
exchangeSources.forEach((item) => {
|
|
exchangeGrid.appendChild(
|
|
buildItemCard(
|
|
item.provider_name || item.surface_type || 'exchange surface',
|
|
item.status || 'unknown',
|
|
[
|
|
['surface', item.surface_type || 'n/a'],
|
|
['freshness', item.freshness_status || 'n/a'],
|
|
['cache', item.cache_state || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['capture', String(Boolean(item.capture_supported))],
|
|
['prompt', item.visual_prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(exchangeGrid);
|
|
}
|
|
const visualReferences = externalContext.visual_reference_cards || [];
|
|
if (visualReferences.length) {
|
|
const visualGrid = createElement('div', 'cards assistant-tight-grid');
|
|
visualReferences.forEach((item) => {
|
|
const actions = [];
|
|
if (item.target_url) {
|
|
actions.push({
|
|
label: 'Open Target',
|
|
onClick: () => openTrackedExternalContextLink(item.target_url, {
|
|
surface_id: item.visual_id || item.title || item.provider_name || 'visual_reference',
|
|
title: item.title || item.provider_name || 'visual reference',
|
|
source_group: 'visual_reference_card',
|
|
selection_kind: 'target_url',
|
|
prompt: item.visual_prompt || '',
|
|
}),
|
|
});
|
|
}
|
|
if (item.embed_url) {
|
|
actions.push({
|
|
label: 'Open Embed',
|
|
onClick: () => openTrackedExternalContextLink(item.embed_url, {
|
|
surface_id: item.visual_id || item.title || item.provider_name || 'visual_reference_embed',
|
|
title: item.title || item.provider_name || 'visual reference embed',
|
|
source_group: 'visual_reference_card',
|
|
selection_kind: 'embed_url',
|
|
prompt: item.visual_prompt || '',
|
|
}),
|
|
});
|
|
}
|
|
visualGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.provider_name || 'visual reference',
|
|
item.visual_type || item.status || 'visual',
|
|
[
|
|
['provider', item.provider_name || 'n/a'],
|
|
['status', item.status || 'n/a'],
|
|
['summary', item.summary || 'n/a'],
|
|
['target', item.target_url || 'n/a'],
|
|
['embed', item.embed_url || 'n/a'],
|
|
['capture', String(Boolean(item.capture_supported))],
|
|
['prompt', item.visual_prompt || 'n/a'],
|
|
],
|
|
actions
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(visualGrid);
|
|
}
|
|
}
|
|
if (setupLearningRollup) {
|
|
appendSectionTitle(decisionAssistantPanel, 'Setup Replay');
|
|
if (workflowArchiveBridgeContext) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['archive replay bridge', workflowArchiveBridgeContext.bridge_mode || 'n/a'],
|
|
['archive snapshot', String(workflowArchiveBridgeContext.snapshot_id ?? 'n/a')],
|
|
['archive session', workflowArchiveBridgeContext.session_code || 'n/a'],
|
|
['archive next phase', workflowArchiveBridgeContext.next_phase || 'n/a'],
|
|
['archive artifact', workflowArchiveBridgeContext.next_artifact_kind || 'n/a'],
|
|
['archive prompt', workflowArchiveBridgeContext.next_artifact_prompt || 'n/a'],
|
|
['archive workflow body', workflowArchiveBridgeContext.workflow_body || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['setup learning', setupLearningRollup.status || 'idle'],
|
|
['setup type', setupLearningRollup.setup_type || 'unknown'],
|
|
['setup session', setupLearningRollup.session_code || 'n/a'],
|
|
['session source', setupLearningRollup.session_code_source || 'n/a'],
|
|
['focus replay scope', setupLearningRollup.focus_replay_scope || 'n/a'],
|
|
['focus replay candidate', String(setupLearningRollup.focus_candidate_id ?? 'n/a')],
|
|
['focus replay session', setupLearningRollup.focus_session_code || 'n/a'],
|
|
['focus replay model', setupLearningRollup.focus_model_code || 'n/a'],
|
|
['focus replay prompt', setupLearningRollup.focus_replay_prompt || 'n/a'],
|
|
['lesson count', String(setupLearningRollup.lesson_count ?? 0)],
|
|
['failure attributions', String(setupLearningRollup.learning_attribution_summary?.failure_reason_count ?? 0)],
|
|
['missed opportunities', String(setupLearningRollup.learning_attribution_summary?.missed_opportunity_count ?? 0)],
|
|
['learning focus queue', String(setupLearningRollup.learning_focus_queue_count ?? 0)],
|
|
['next learning focus', setupLearningRollup.next_learning_focus?.title || 'n/a'],
|
|
['next learning prompt', setupLearningRollup.next_learning_focus_prompt || 'n/a'],
|
|
['learning focus session', setupLearningRollup.learning_focus_matrix?.recommended_session_code || 'n/a'],
|
|
['learning focus state', setupLearningRollup.learning_focus_matrix?.recommended_market_state || 'n/a'],
|
|
['learning matrix hint', setupLearningRollup.learning_focus_matrix?.matrix_hint || 'n/a'],
|
|
['weekly trend summary', setupLearningRollup.weekly_trend_summary || 'n/a'],
|
|
['weekly best session', setupLearningRollup.weekly_trend_breakdown?.best_session || 'n/a'],
|
|
['weekly weakest session', setupLearningRollup.weekly_trend_breakdown?.weakest_session || 'n/a'],
|
|
['weekly liquid/thin', `${setupLearningRollup.weekly_trend_breakdown?.liquid_session_count ?? 0} / ${setupLearningRollup.weekly_trend_breakdown?.thin_session_count ?? 0}`],
|
|
['failure reasons', formatList((setupLearningRollup.failure_reason_attribution || []).map((item) => `${item.reason}: ${item.severity}`))],
|
|
['missed opportunity reasons', formatList((setupLearningRollup.missed_opportunity_attribution || []).map((item) => `${item.session_code}: ${item.reason}`))],
|
|
['setup/session stats', formatList((setupLearningRollup.setup_session_market_state_stats || []).map((item) => `${item.session_code}/${item.market_state}: ${formatPercent(item.framework_execution_rate)}`))],
|
|
['replay samples', String(setupLearningRollup.replay_statistics?.sample_count ?? 0)],
|
|
['framework rate', formatPercent(setupLearningRollup.replay_statistics?.framework_execution_rate)],
|
|
['model outside rate', formatPercent(setupLearningRollup.replay_statistics?.model_outside_rate)],
|
|
['avg R', formatNumber(setupLearningRollup.replay_statistics?.avg_r_multiple, 2)],
|
|
['risk violations', String(setupLearningRollup.replay_statistics?.risk_violation_count ?? 0)],
|
|
['framework gap vs high', formatPercent(setupLearningRollup.replay_statistics?.comparative_summary?.framework_execution_gap_vs_high)],
|
|
['avg R gap vs high', formatNumber(setupLearningRollup.replay_statistics?.comparative_summary?.avg_r_gap_vs_high, 2)],
|
|
['framework gap vs low', formatPercent(setupLearningRollup.replay_statistics?.comparative_summary?.framework_execution_gap_vs_low)],
|
|
['avg R gap vs low', formatNumber(setupLearningRollup.replay_statistics?.comparative_summary?.avg_r_gap_vs_low, 2)],
|
|
['comparison status', setupLearningRollup.replay_statistics?.comparative_summary?.comparison_status || 'n/a'],
|
|
['confidence band', setupLearningRollup.replay_statistics?.comparative_summary?.confidence_band || 'n/a'],
|
|
['session alignment', setupLearningRollup.replay_statistics?.comparative_summary?.session_alignment || 'n/a'],
|
|
['comparison notes', formatList(setupLearningRollup.replay_statistics?.comparative_summary?.notes || [])],
|
|
['comparison story', setupLearningRollup.replay_statistics?.comparative_summary?.story || 'n/a'],
|
|
['global framework avg', formatPercent(setupLearningRollup.replay_statistics?.broader_baseline_summary?.framework_execution_global_avg)],
|
|
['global avg R', formatNumber(setupLearningRollup.replay_statistics?.broader_baseline_summary?.avg_r_global_avg, 2)],
|
|
['global risk avg', formatNumber(setupLearningRollup.replay_statistics?.broader_baseline_summary?.risk_violation_global_avg, 2)],
|
|
['framework rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.framework_rank ?? 'n/a')],
|
|
['framework percentile', formatPercent(setupLearningRollup.replay_statistics?.broader_baseline_summary?.framework_percentile)],
|
|
['avg R rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.avg_r_rank ?? 'n/a')],
|
|
['avg R percentile', formatPercent(setupLearningRollup.replay_statistics?.broader_baseline_summary?.avg_r_percentile)],
|
|
['risk rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.risk_rank ?? 'n/a')],
|
|
['risk percentile', formatPercent(setupLearningRollup.replay_statistics?.broader_baseline_summary?.risk_percentile)],
|
|
['composite percentile', formatPercent(setupLearningRollup.replay_statistics?.broader_baseline_summary?.composite_percentile)],
|
|
['risk band', setupLearningRollup.replay_statistics?.broader_baseline_summary?.risk_band || 'n/a'],
|
|
['composite band', setupLearningRollup.replay_statistics?.broader_baseline_summary?.composite_band || 'n/a'],
|
|
['composite rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.composite_rank ?? 'n/a')],
|
|
['position band', setupLearningRollup.replay_statistics?.broader_baseline_summary?.position_band || 'n/a'],
|
|
['replay verdict', setupLearningRollup.replay_statistics?.broader_baseline_summary?.replay_verdict || 'n/a'],
|
|
['peer summary', setupLearningRollup.replay_statistics?.broader_baseline_summary?.peer_summary || 'n/a'],
|
|
['setup family', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family || 'n/a'],
|
|
['setup family status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_status || 'n/a'],
|
|
['setup family peers', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_peer_summary || 'n/a'],
|
|
['setup family focus', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_focus || 'n/a'],
|
|
['family trend status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.trend_status || 'n/a'],
|
|
['family watchlist status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.watchlist_status || 'n/a'],
|
|
['family watchlist priority', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.watchlist_priority || 'n/a'],
|
|
['family watchlist score', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.watchlist_priority_score ?? 'n/a')],
|
|
['promotion readiness', String(Boolean(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_ready))],
|
|
['promotion readiness hint', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_readiness_hint || 'n/a'],
|
|
['promotion blockers', formatList(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_blockers || [])],
|
|
['promotion queue status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_queue_status || 'n/a'],
|
|
['promotion queue rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_queue_rank ?? 'n/a')],
|
|
['promotion next action', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_next_action || 'n/a'],
|
|
['promotion decision history', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_decision_history_count ?? 0)],
|
|
['promotion decision reasons', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_decision_history || []).map((item) => item.reason || item.event))],
|
|
['review lane age', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_age_status || 'n/a'],
|
|
['review lane age score', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_age_score ?? 'n/a')],
|
|
['review lane aging hint', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_aging_hint || 'n/a'],
|
|
['review lane SLA status', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_sla_status || 'n/a'],
|
|
['review lane SLA', formatDurationSeconds(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.review_lane_sla_seconds)],
|
|
['promotion backlog priority', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_priority || 'n/a'],
|
|
['promotion backlog rank', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_rank ?? 'n/a')],
|
|
['promotion backlog reason', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_reason || 'n/a'],
|
|
['promotion evidence queue', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_queue_count ?? 0)],
|
|
['promotion evidence', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_queue || []).map((item) => `${item.evidence_type}: ${item.status}`))],
|
|
['backlog trend', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_trend_status || 'n/a'],
|
|
['backlog handoff', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_trend_handoff || 'n/a'],
|
|
['evidence aging', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_aging_status || 'n/a'],
|
|
['evidence aging prompt', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_aging_prompt || 'n/a'],
|
|
['evidence aging queue', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_evidence_aging_queue || []).map((item) => `${item.evidence_type}: ${item.aging_status}`))],
|
|
['backlog comparison', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_comparison_status || 'n/a'],
|
|
['backlog comparison summary', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.promotion_backlog_comparison_summary?.summary || 'n/a'],
|
|
['demotion history', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.demotion_history_count ?? 0)],
|
|
['demotion reasons', formatList((setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.demotion_history || []).map((item) => item.reason || item.event))],
|
|
['family sample threshold', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.sample_threshold ?? 'n/a')],
|
|
['family sample gap', String(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.sample_gap ?? 'n/a')],
|
|
['family watchlist items', formatList(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.drift_watchlist_items || [])],
|
|
['family framework delta', formatPercent(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.framework_delta)],
|
|
['family avg R delta', formatNumber(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.avg_r_delta, 2)],
|
|
['family risk delta', formatNumber(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.risk_delta, 2)],
|
|
['family drift alert', String(Boolean(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.drift_alert))],
|
|
['family drift severity', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.drift_severity || 'n/a'],
|
|
['family focus recommendation', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.focus_recommendation || 'n/a'],
|
|
['family focus reasons', formatList(setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.focus_reasons || [])],
|
|
['family trend story', setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_trend_delta?.trend_story || 'n/a'],
|
|
['broader story', setupLearningRollup.replay_statistics?.broader_baseline_summary?.story || 'n/a'],
|
|
['high-quality session', setupLearningRollup.high_quality_session?.session_code || 'n/a'],
|
|
['low-quality session', setupLearningRollup.low_quality_session?.session_code || 'n/a'],
|
|
])
|
|
);
|
|
if (workflowArchiveBridgeContext) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Prefill Replay Note',
|
|
onClick: () => prefillOperatorNote(buildWorkflowArchiveBridgeNote(workflowArchiveBridgeContext), workflowArchiveBridgeContext),
|
|
},
|
|
{
|
|
label: 'Jump to Review Entry',
|
|
onClick: () => revealAssistantSection('Review Entry'),
|
|
},
|
|
])
|
|
);
|
|
}
|
|
const broadTopSessions = setupLearningRollup.replay_statistics?.broader_baseline_summary?.top_sessions || [];
|
|
const broadTopRSessions = setupLearningRollup.replay_statistics?.broader_baseline_summary?.top_r_sessions || [];
|
|
const broadTopSafe = setupLearningRollup.replay_statistics?.broader_baseline_summary?.top_safe_sessions || [];
|
|
const broadTopComposite = setupLearningRollup.replay_statistics?.broader_baseline_summary?.top_composite_sessions || [];
|
|
const peerSessions = setupLearningRollup.replay_statistics?.broader_baseline_summary?.peer_sessions || [];
|
|
const setupFamilyPeers = setupLearningRollup.replay_statistics?.broader_baseline_summary?.setup_family_peer_sessions || [];
|
|
if (broadTopSessions.length || broadTopRSessions.length || broadTopSafe.length || broadTopComposite.length || peerSessions.length || setupFamilyPeers.length) {
|
|
const leaderboardGrid = createElement('div', 'cards assistant-tight-grid');
|
|
broadTopSessions.forEach((item) => {
|
|
leaderboardGrid.appendChild(
|
|
buildItemCard(
|
|
`Top Framework: ${item.session_code || 'session'}`,
|
|
formatPercent(item.metric),
|
|
[
|
|
['samples', String(item.count ?? 0)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
broadTopRSessions.forEach((item) => {
|
|
leaderboardGrid.appendChild(
|
|
buildItemCard(
|
|
`Top Avg R: ${item.session_code || 'session'}`,
|
|
formatNumber(item.metric, 2),
|
|
[
|
|
['samples', String(item.count ?? 0)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
broadTopSafe.forEach((item) => {
|
|
leaderboardGrid.appendChild(
|
|
buildItemCard(
|
|
`Safest: ${item.session_code || 'session'}`,
|
|
formatNumber(item.metric, 0),
|
|
[
|
|
['samples', String(item.count ?? 0)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
peerSessions.forEach((item) => {
|
|
leaderboardGrid.appendChild(
|
|
buildItemCard(
|
|
`Peer ${String(item.relative_position || 'nearby')}: ${item.session_code || 'session'}`,
|
|
`rank ${String(item.rank ?? 'n/a')}`,
|
|
[
|
|
['framework', formatPercent(item.framework_execution_rate)],
|
|
['avg R', formatNumber(item.avg_r_multiple, 2)],
|
|
['risk violations', String(item.risk_violation_count ?? 0)],
|
|
['samples', String(item.count ?? 0)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
setupFamilyPeers.forEach((item) => {
|
|
leaderboardGrid.appendChild(
|
|
buildItemCard(
|
|
`Family Peer: ${item.session_code || 'session'}`,
|
|
item.setup_family || 'setup family',
|
|
[
|
|
['relative', item.relative_position || 'n/a'],
|
|
['rank', String(item.rank ?? 'n/a')],
|
|
['framework', formatPercent(item.framework_execution_rate)],
|
|
['avg R', formatNumber(item.avg_r_multiple, 2)],
|
|
['risk violations', String(item.risk_violation_count ?? 0)],
|
|
['samples', String(item.count ?? 0)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
broadTopComposite.forEach((item) => {
|
|
leaderboardGrid.appendChild(
|
|
buildItemCard(
|
|
`Top Composite: ${item.session_code || 'session'}`,
|
|
`${formatPercent(item.framework_execution_rate)} | ${formatNumber(item.avg_r_multiple, 2)}`,
|
|
[
|
|
['risk violations', String(item.risk_violation_count ?? 0)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(leaderboardGrid);
|
|
}
|
|
const lessonItems = setupLearningRollup.top_lessons || [];
|
|
const learningFocusQueue = setupLearningRollup.learning_focus_queue || [];
|
|
if (lessonItems.length) {
|
|
const lessonGrid = createElement('div', 'cards');
|
|
lessonItems.forEach((item) => {
|
|
lessonGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'lesson',
|
|
item.lesson_type || 'lesson',
|
|
[
|
|
['summary', item.summary || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(lessonGrid);
|
|
}
|
|
if (learningFocusQueue.length) {
|
|
const focusGrid = createElement('div', 'cards assistant-tight-grid');
|
|
learningFocusQueue.forEach((item) => {
|
|
focusGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'learning focus',
|
|
item.priority || item.focus_type || 'focus',
|
|
[
|
|
['type', item.focus_type || 'n/a'],
|
|
['session', item.target_session_code || 'n/a'],
|
|
['market state', item.target_market_state || 'n/a'],
|
|
['prompt', item.prompt || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(focusGrid);
|
|
}
|
|
if (setupLearningRollup.learning_focus_matrix) {
|
|
decisionAssistantPanel.appendChild(
|
|
buildInfoGrid([
|
|
['focus matrix', setupLearningRollup.learning_focus_matrix.status || 'idle'],
|
|
['session focus counts', formatJsonInline(setupLearningRollup.learning_focus_matrix.session_focus_counts || {})],
|
|
['market-state counts', formatJsonInline(setupLearningRollup.learning_focus_matrix.market_state_focus_counts || {})],
|
|
['focus cards', String(setupLearningRollup.learning_focus_matrix.focus_card_count ?? 0)],
|
|
])
|
|
);
|
|
}
|
|
}
|
|
if (digestHistoryPreview.length) {
|
|
const filteredHistory = activeHistoryFilter === 'all'
|
|
? digestHistoryPreview
|
|
: digestHistoryPreview.filter((item) => String(item.kind || '') === activeHistoryFilter);
|
|
const historyGrid = createElement('div', 'cards assistant-history-grid');
|
|
filteredHistory.forEach((item) => {
|
|
historyGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.kind || 'history',
|
|
item.kind || 'history',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
['time', formatTimestamp(item.ts)],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(historyGrid);
|
|
}
|
|
const items = summary.top_priority_items || [];
|
|
if (!items.length) {
|
|
decisionAssistantPanel.appendChild(createElement('div', 'empty-state', 'No priority items yet. Monitor and wait.'));
|
|
} else {
|
|
const grid = createElement('div', 'cards');
|
|
items.forEach((item) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
item.title || item.kind || 'priority',
|
|
item.status || 'observe',
|
|
[
|
|
['kind', item.kind || 'n/a'],
|
|
['detail', item.detail || 'n/a'],
|
|
['reason', item.reason || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(grid);
|
|
}
|
|
if (!evidenceChain.length) {
|
|
return;
|
|
}
|
|
appendSectionTitle(decisionAssistantPanel, 'Evidence');
|
|
const evidenceGrid = createElement('div', 'cards');
|
|
evidenceChain.forEach((item) => {
|
|
evidenceGrid.appendChild(
|
|
buildItemCard(
|
|
item.title || 'evidence',
|
|
item.status || 'n/a',
|
|
[
|
|
['detail', item.detail || 'n/a'],
|
|
['reason', item.reason || 'n/a'],
|
|
['focus related', String(Boolean(item.focus_related))],
|
|
['focus candidate', String(item.focus_candidate_id ?? 'n/a')],
|
|
['evidence keys', formatList(item.evidence_keys || [])],
|
|
['missing confirmations', formatList(item.missing_confirmations || [])],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionAssistantPanel.appendChild(evidenceGrid);
|
|
}
|
|
|
|
function renderMarketState(payload, executions) {
|
|
clearNode(marketStatePanel);
|
|
const marketState = payload?.market_state || {};
|
|
const freshness = marketState.freshness || {};
|
|
const session = marketState.session || {};
|
|
const newsWindow = marketState.news_window || {};
|
|
const riskDecision = payload?.risk_decision || {};
|
|
const brokerState = payload?.broker_state || {};
|
|
const brokerAccount = brokerState.account || {};
|
|
const brokerAlerts = brokerState.alerts || [];
|
|
const panel = buildInfoGrid([
|
|
['symbol', String(instrumentIdInput.value || '1')],
|
|
['session', session.session_code || 'OFF_HOURS'],
|
|
['data freshness', freshness.code || 'unknown'],
|
|
['news state', newsWindow.code || 'unknown'],
|
|
['broker state', brokerState.status || 'unknown'],
|
|
['broker equity', formatNumber(brokerAccount.equity, 2)],
|
|
['broker available balance', formatNumber(brokerAccount.available_balance, 2)],
|
|
['broker open positions', String((brokerState.positions || []).length)],
|
|
['broker pending orders', String((brokerState.orders || []).length)],
|
|
['broker alerts', formatList(brokerAlerts)],
|
|
['account risk state', formatJsonInline(riskDecision.reject_codes || [])],
|
|
['open position', String(executions.filter((item) => item.execution_status === 'open').length)],
|
|
['kill switch', (riskDecision.reject_codes || []).includes('execution_kill_switch_enabled') ? 'active' : 'off'],
|
|
]);
|
|
marketStatePanel.appendChild(panel);
|
|
}
|
|
|
|
function renderRuntimeStatus(runtimeStatus, runtimeIncidents, maintenanceSummary) {
|
|
clearNode(runtimeStatusPanel);
|
|
const tasks = runtimeStatus?.tasks || {};
|
|
const blockers = runtimeStatus?.current_blockers || [];
|
|
const incidents = runtimeStatus?.incidents || [];
|
|
const watchdog = runtimeStatus?.runtime_watchdog || {};
|
|
const incidentSummary = runtimeIncidents || runtimeStatus?.incident_summary || {};
|
|
const maintenance = maintenanceSummary || {};
|
|
const readonlyReliability = maintenance.readonly_live_reliability || {};
|
|
const stalledTasks = watchdog?.stalled_tasks || [];
|
|
const stalledMap = new Map(stalledTasks.map((item) => [item.name, item]));
|
|
runtimeStatusPanel.appendChild(
|
|
buildInfoGrid([
|
|
['scheduler state', runtimeStatus?.scheduler_state || 'unknown'],
|
|
['supervisor state', runtimeStatus?.supervisor_state || 'unknown'],
|
|
['enabled', String(Boolean(runtimeStatus?.enabled))],
|
|
['degraded', String(Boolean(runtimeStatus?.degraded_mode_state?.is_degraded))],
|
|
['symbols', formatList(runtimeStatus?.symbols || [])],
|
|
['blockers', formatList(blockers)],
|
|
['stalled tasks', formatList(watchdog?.stalled_task_names || [])],
|
|
['watchdog checked', formatTimestamp(watchdog?.checked_ts)],
|
|
['incident journal', `${incidentSummary.status || 'idle'} | ${incidentSummary.incident_count ?? 0}`],
|
|
['incident severity', formatJsonInline(incidentSummary.severity_counts || {})],
|
|
['maintenance', `${maintenance.status || 'unavailable'} | blockers ${(maintenance.blockers || []).length}`],
|
|
['read-only use today', readonlyReliability.label || 'not checked'],
|
|
['last incident', incidents.length ? `${incidents[incidents.length - 1].code} @ ${formatTimestamp(incidents[incidents.length - 1].observed_ts)}` : 'none'],
|
|
])
|
|
);
|
|
const grid = createElement('div', 'cards');
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Incident Journal',
|
|
incidentSummary.open_incident_count ? 'review' : (incidentSummary.status || 'idle'),
|
|
[
|
|
['persisted', String(Boolean(incidentSummary.persisted))],
|
|
['open / volatile', `${incidentSummary.open_incident_count ?? 0} / ${incidentSummary.volatile_incident_count ?? 0}`],
|
|
['sources', formatJsonInline(incidentSummary.source_counts || {})],
|
|
['recommended actions', formatList(incidentSummary.recommended_actions || [])],
|
|
['latest', incidentSummary.latest_incident ? `${incidentSummary.latest_incident.severity || 'unknown'} ${incidentSummary.latest_incident.reason || 'runtime_incident'}` : 'none'],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Read-Only Live Reliability',
|
|
readonlyReliability.label || 'not checked',
|
|
[
|
|
['trader conclusion', readonlyReliability.conclusion || 'not checked'],
|
|
['proxy', formatBacktestDecision(readonlyReliability.proxy_status || 'unknown')],
|
|
['market freshness', formatBacktestDecision(readonlyReliability.market_freshness || 'unknown')],
|
|
['runtime', formatBacktestDecision(readonlyReliability.runtime_status || 'unknown')],
|
|
['review packet', formatBacktestDecision(readonlyReliability.review_packet_status || 'unknown')],
|
|
['REST fallback', formatBacktestDecision(readonlyReliability.rest_fallback || 'unknown')],
|
|
['stream status', formatBacktestDecision(readonlyReliability.stream_status || 'unknown')],
|
|
['news / account warnings', formatList(readonlyReliability.news_account_warnings || [])],
|
|
['boundary', readonlyReliability.execution_boundary || 'read_only_analysis_no_order_submission'],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Maintenance Rollup',
|
|
maintenance.status || 'unavailable',
|
|
[
|
|
['blockers', formatList(maintenance.blockers || [])],
|
|
['warnings', formatList(maintenance.warnings || [])],
|
|
['automatic actions', formatList((maintenance.maintenance_actions || {}).automatic_actions || [])],
|
|
['operator actions', formatList((maintenance.maintenance_actions || {}).operator_actions || [])],
|
|
['p23 assertions', formatJsonInline(maintenance.p23_assertions || {})],
|
|
]
|
|
)
|
|
);
|
|
Object.entries(tasks).forEach(([name, task]) => {
|
|
const stalled = stalledMap.get(name) || null;
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
name,
|
|
stalled ? 'warning' : (task.last_status || 'idle'),
|
|
[
|
|
['last success', formatTimestamp(task.last_success_ts)],
|
|
['next run', formatTimestamp(task.next_run_ts)],
|
|
['run count', String(task.run_count ?? 0)],
|
|
['success / failure', `${task.success_count ?? 0} / ${task.failure_count ?? 0}`],
|
|
['stalled', stalled ? `${stalled.reason} (${formatNumber(stalled.age_seconds, 1)}s > ${stalled.threshold_seconds}s)` : 'no'],
|
|
['last error', task.last_error || 'none'],
|
|
['details', formatJsonInline(task.details || {})],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
runtimeStatusPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderDecisionPacket(packet, alertRows) {
|
|
clearNode(decisionPacketPanel);
|
|
if (!packet || packet.status === 'unavailable') {
|
|
decisionPacketPanel.appendChild(createElement('div', 'empty-state', 'Decision packet unavailable.'));
|
|
return;
|
|
}
|
|
decisionPacketPanel.appendChild(
|
|
buildInfoGrid([
|
|
['status', packet.status || 'unknown'],
|
|
['recommendation', packet.action_recommendation || 'monitor'],
|
|
['operator mode', packet.operator_mode || 'unknown'],
|
|
['trust classification', packet.trust_classification || 'unknown'],
|
|
['bias', packet.bias || 'unknown'],
|
|
['setup', packet.setup_type || 'n/a'],
|
|
['candidate', packet.candidate_id || 'n/a'],
|
|
['ticket', packet.execution_ticket_id || 'n/a'],
|
|
['risk result', formatJsonInline(packet.risk_result?.reasons || [])],
|
|
['why allowed', formatList(packet.why_allowed || [])],
|
|
['why blocked', formatList(packet.why_blocked || [])],
|
|
['ai explanation', packet.ai_explanation || 'n/a'],
|
|
['council summary', packet.research_council?.research_summary || 'n/a'],
|
|
['council confidence', packet.research_council?.committee_confidence != null ? formatNumber(packet.research_council.committee_confidence, 2) : 'n/a'],
|
|
['council follow-up', formatList(packet.research_council?.follow_up_checks || [])],
|
|
['council watch', formatList(packet.research_council?.recommended_watch_items || [])],
|
|
])
|
|
);
|
|
const grid = createElement('div', 'cards');
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Plan Context',
|
|
packet.action_recommendation || 'monitor',
|
|
[
|
|
['narrative', packet.narrative || 'n/a'],
|
|
['primary draw', formatJsonInline(packet.primary_draw || {})],
|
|
['confirmation', formatJsonInline(packet.confirmation_chain || {})],
|
|
['entry zone', `${formatNumber(packet.entry_zone?.low, 3)} - ${formatNumber(packet.entry_zone?.high, 3)}`],
|
|
['stop', formatNumber(packet.stop_loss, 3)],
|
|
['targets', formatJsonInline(packet.targets || {})],
|
|
['invalidation', formatJsonInline(packet.invalidation || {})],
|
|
['expiry', formatTimestamp(packet.expiry_ts)],
|
|
]
|
|
)
|
|
);
|
|
const recentAlerts = alertRows?.items || packet.alerts || [];
|
|
recentAlerts.slice(0, 3).forEach((alert) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
alert.alert_type || 'alert',
|
|
alert.status || 'queued',
|
|
[
|
|
['channel', alert.channel || 'workbench'],
|
|
['message', alert.message || 'n/a'],
|
|
['payload', formatJsonInline(alert.payload || {})],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
decisionPacketPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderTrustScore(payload) {
|
|
clearNode(trustScorePanel);
|
|
const trustClass = trustClassFilterInput.value || 'all';
|
|
const models = (payload?.models || []).filter((model) => trustClass === 'all' || model.classification === trustClass);
|
|
trustScorePanel.appendChild(
|
|
buildInfoGrid([
|
|
['whitelist', formatList(payload?.whitelist || [])],
|
|
['observe', formatList(payload?.observe_list || [])],
|
|
['blacklist', formatList(payload?.blacklist || [])],
|
|
['next actions', formatList(payload?.next_actions || [])],
|
|
])
|
|
);
|
|
if (!models.length) {
|
|
trustScorePanel.appendChild(createElement('div', 'empty-state', 'No trust-score samples yet.'));
|
|
return;
|
|
}
|
|
const grid = createElement('div', 'cards');
|
|
models.forEach((model) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
model.model_code || 'model',
|
|
model.classification || 'observe',
|
|
[
|
|
['score', formatNumber(model.score, 1)],
|
|
['recommendation', model.recommendation || 'observe_only'],
|
|
['operator mode', model.operator_mode || 'unknown'],
|
|
['sample size', String(model.sample_size ?? 0)],
|
|
['real trades', String(model.real_trade_count ?? 0)],
|
|
['framework rate', formatPercent(model.framework_execution_rate)],
|
|
['out-of-model rate', formatPercent(model.out_of_model_trade_rate)],
|
|
['risk violation rate', formatPercent(model.risk_violation_rate)],
|
|
['expectancy R', formatNumber(model.expectancy_r, 2)],
|
|
['quality gate', String(Boolean(model.quality_gate_passed))],
|
|
['reasons', formatList(model.reasons || [])],
|
|
],
|
|
[],
|
|
formatJsonInline(model.session_recommendations || [])
|
|
)
|
|
);
|
|
});
|
|
trustScorePanel.appendChild(grid);
|
|
}
|
|
|
|
function renderStrategyBacktests(payload) {
|
|
if (!strategyBacktestPanel) return;
|
|
clearNode(strategyBacktestPanel);
|
|
if (payload?.unavailable) {
|
|
strategyBacktestPanel.appendChild(createElement('div', 'empty-state error-state', 'Strategy backtest data unavailable.'));
|
|
return;
|
|
}
|
|
const latest = payload?.latest || (payload?.items || [])[0] || null;
|
|
if (!latest) {
|
|
strategyBacktestPanel.appendChild(createElement('div', 'empty-state', 'No stored backtest runs yet.'));
|
|
strategyBacktestPanel.appendChild(
|
|
buildInfoGrid([
|
|
['Next Action', 'Run replay no-lookahead backtest'],
|
|
['Boundary', 'Read-only analysis; no order submission'],
|
|
])
|
|
);
|
|
return;
|
|
}
|
|
const summary = latest.summary || {};
|
|
const qualityGate = payload?.quality_gate || summary.quality_gate || {};
|
|
const passed = Boolean(qualityGate.passed);
|
|
const operatorSummary = payload?.operator_summary || {};
|
|
const doctrineCohort = latest.doctrine_backtest_cohort || summary.doctrine_backtest_cohort || {};
|
|
const doctrinePromotionGate = doctrineCohort.promotion_gate || {};
|
|
const liquidityStatus =
|
|
latest.liquidity_kernel_status ||
|
|
summary.liquidity_kernel_status ||
|
|
doctrineCohort.liquidity_kernel_status ||
|
|
{};
|
|
const liquidityFeatures =
|
|
latest.liquidity_backtest_features ||
|
|
summary.liquidity_backtest_features ||
|
|
doctrineCohort.liquidity_backtest_features ||
|
|
liquidityStatus.features ||
|
|
{};
|
|
|
|
strategyBacktestPanel.appendChild(
|
|
buildInfoGrid([
|
|
['Latest Backtest', `#${latest.id ?? 'n/a'} ${latest.model_code || ''}`.trim()],
|
|
['Quality Gate', passed ? 'Passed' : 'Blocked'],
|
|
['Decision', formatBacktestDecision(operatorSummary.decision)],
|
|
['Reason', formatBacktestDecision(operatorSummary.reason)],
|
|
['Setup Playbook', doctrineCohort.setup_playbook_id || 'missing_playbook'],
|
|
['Doctrine Cohort', doctrineCohort.cohort_key || 'missing_playbook::incomplete'],
|
|
['Doctrine Completeness', doctrineCohort.doctrine_completeness || 'incomplete'],
|
|
['Liquidity Kernel', formatBacktestDecision(liquidityStatus.status || 'study_only')],
|
|
['Liquidity Reason', formatBacktestDecision(liquidityStatus.reason || 'missing_liquidity_target')],
|
|
['Liquidity Target', liquidityFeatures.liquidity_target_side || 'missing_liquidity_target'],
|
|
['Liquidity Pool', liquidityFeatures.liquidity_pool_id || 'missing_liquidity_pool'],
|
|
['Promotion Gate', doctrinePromotionGate.passed ? 'Passed' : 'Blocked'],
|
|
['Instrument ID', latest.instrument_id ?? 'n/a'],
|
|
['Timeframe', formatJsonInline(latest.timeframe_set || {})],
|
|
['Started', formatTimestamp(latest.started_ts)],
|
|
['Ended', formatTimestamp(latest.ended_ts)],
|
|
['Mode', latest.config?.mode || latest.config?.backtest_mode || 'replay_no_lookahead'],
|
|
])
|
|
);
|
|
|
|
const cards = createElement('div', 'cards');
|
|
cards.appendChild(
|
|
buildMetricCard('Sample Count', String(summary.total_signals ?? summary.candidate_count ?? 0), 'total replay samples')
|
|
);
|
|
cards.appendChild(
|
|
buildMetricCard('Executed Samples', String(summary.executed_signals ?? 0), `executed rate ${formatPercent(summary.executed_rate)}`)
|
|
);
|
|
cards.appendChild(
|
|
buildMetricCard('Total R', formatNumber(summary.total_r, 2), `expectancy ${formatNumber(summary.expectancy_r, 2)} R`)
|
|
);
|
|
cards.appendChild(
|
|
buildMetricCard('Profit Factor', formatNumber(summary.profit_factor_r, 2), `win rate ${formatPercent(summary.win_rate)}`)
|
|
);
|
|
cards.appendChild(
|
|
buildMetricCard('Max Drawdown R', formatNumber(summary.max_drawdown_r, 2), `loss streak ${summary.max_consecutive_losses ?? 0}`)
|
|
);
|
|
cards.appendChild(
|
|
buildMetricCard('Quality Gate', passed ? 'Passed' : 'Blocked', formatList((qualityGate.reasons || []).map(formatBacktestDecision), 'No gate blockers'))
|
|
);
|
|
cards.appendChild(
|
|
buildMetricCard(
|
|
'Liquidity Kernel',
|
|
liquidityStatus.passed ? 'Mapped' : 'Blocked',
|
|
formatList((liquidityStatus.reasons || []).map(formatBacktestDecision), 'Liquidity chain mapped')
|
|
)
|
|
);
|
|
strategyBacktestPanel.appendChild(cards);
|
|
|
|
strategyBacktestPanel.appendChild(buildBacktestCurve(summary.cumulative_r_curve || []));
|
|
|
|
const detailGrid = createElement('div', 'cards compact-cards');
|
|
detailGrid.appendChild(
|
|
buildItemCard(
|
|
'Top Sessions',
|
|
'review',
|
|
topBacktestBuckets(summary.session_performance || summary.by_session || {}).map((item) => [
|
|
item.key,
|
|
`${formatNumber(item.expectancy_r, 2)} R / ${formatPercent(item.win_rate)} / ${item.count ?? 0} samples`,
|
|
])
|
|
)
|
|
);
|
|
detailGrid.appendChild(
|
|
buildItemCard(
|
|
'Failure Reasons',
|
|
'review',
|
|
topCountEntries(summary.failure_reasons || {}).map((item) => [formatBacktestDecision(item.key), String(item.value)])
|
|
)
|
|
);
|
|
detailGrid.appendChild(
|
|
buildItemCard(
|
|
'Doctrine Cohorts',
|
|
`${payload?.doctrine_cohort_summary?.cohort_count ?? 0} cohorts`,
|
|
(payload?.doctrine_cohort_summary?.cohorts || []).slice(0, 5).map((item) => [
|
|
item.cohort_key || 'missing_playbook::incomplete',
|
|
`${item.run_count ?? 0} runs / ${item.quality_gate_passed_count ?? 0} passed / ${item.promotable_count ?? 0} promotable`,
|
|
])
|
|
)
|
|
);
|
|
detailGrid.appendChild(
|
|
buildItemCard(
|
|
'Liquidity Kernel',
|
|
`${payload?.liquidity_kernel_summary?.passed_count ?? 0} mapped / ${payload?.liquidity_kernel_summary?.blocked_count ?? 0} blocked`,
|
|
topCountEntries(payload?.liquidity_kernel_summary?.reason_counts || summary.liquidity_kernel_reason_counts || {})
|
|
.slice(0, 5)
|
|
.map((item) => [formatBacktestDecision(item.key), String(item.value)])
|
|
)
|
|
);
|
|
detailGrid.appendChild(
|
|
buildItemCard(
|
|
'Recent Backtest Runs',
|
|
`${payload?.item_count ?? (payload?.items || []).length} runs`,
|
|
(payload?.items || []).slice(0, 5).map((item) => [
|
|
`#${item.id}`,
|
|
`${item.doctrine_backtest_cohort?.cohort_key || item.model_code || 'model'} / ${formatTimestamp(item.started_ts)} / ${item.summary?.quality_gate?.passed ? 'Passed' : 'Blocked'}`,
|
|
])
|
|
)
|
|
);
|
|
strategyBacktestPanel.appendChild(detailGrid);
|
|
}
|
|
|
|
function buildBacktestCurve(curve) {
|
|
const panel = createElement('div', 'backtest-curve-panel');
|
|
panel.appendChild(createElement('div', 'assistant-section-title', 'Equity Curve'));
|
|
if (!Array.isArray(curve) || !curve.length) {
|
|
panel.appendChild(createElement('div', 'empty-state', 'No equity curve points recorded.'));
|
|
return panel;
|
|
}
|
|
const numbers = curve.map((value) => Number(value)).filter((value) => Number.isFinite(value));
|
|
if (!numbers.length) {
|
|
panel.appendChild(createElement('div', 'empty-state', 'No equity curve points recorded.'));
|
|
return panel;
|
|
}
|
|
const min = Math.min(...numbers);
|
|
const max = Math.max(...numbers);
|
|
const range = Math.max(1, max - min);
|
|
const chart = createElement('div', 'backtest-curve');
|
|
numbers.slice(-80).forEach((value) => {
|
|
const bar = createElement('span', `backtest-curve-bar${value < 0 ? ' is-negative' : ''}`);
|
|
bar.style.height = `${16 + ((value - min) / range) * 64}px`;
|
|
bar.title = `${formatNumber(value, 2)} R`;
|
|
chart.appendChild(bar);
|
|
});
|
|
panel.appendChild(chart);
|
|
panel.appendChild(
|
|
buildInfoGrid([
|
|
['Curve Points', String(numbers.length)],
|
|
['Last Total R', formatNumber(numbers[numbers.length - 1], 2)],
|
|
['Best Total R', formatNumber(max, 2)],
|
|
['Worst Total R', formatNumber(min, 2)],
|
|
])
|
|
);
|
|
return panel;
|
|
}
|
|
|
|
function topBacktestBuckets(bucketMap) {
|
|
return Object.entries(bucketMap || {})
|
|
.map(([key, value]) => ({ key, ...(value || {}) }))
|
|
.sort((left, right) => Number(right.expectancy_r || 0) - Number(left.expectancy_r || 0))
|
|
.slice(0, 5);
|
|
}
|
|
|
|
function topCountEntries(countMap) {
|
|
return Object.entries(countMap || {})
|
|
.map(([key, value]) => ({ key, value: Number(value || 0) }))
|
|
.sort((left, right) => right.value - left.value)
|
|
.slice(0, 5);
|
|
}
|
|
|
|
function formatBacktestDecision(value) {
|
|
const text = String(value || 'n/a').replace(/_/g, ' ');
|
|
return text.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
}
|
|
|
|
function renderLiveReadiness(payload) {
|
|
clearNode(liveReadinessPanel);
|
|
const okxSandboxPreflight = payload?.okx_sandbox_preflight_summary || null;
|
|
liveReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['ready for real live', String(Boolean(payload?.ready_for_real_live))],
|
|
['blocking reasons', formatList(payload?.blocking_reasons || [])],
|
|
['protocol version', payload?.operator_confirmation_protocol?.protocol_version || 'n/a'],
|
|
['latest live audits', String((payload?.recent_live_audits || []).length)],
|
|
['latest ticket', payload?.latest_ticket?.id || 'n/a'],
|
|
])
|
|
);
|
|
if (okxSandboxPreflight) {
|
|
liveReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['okx sandbox preflight', okxSandboxPreflight.status || 'n/a'],
|
|
['sandbox next gate', okxSandboxPreflight.next_gate || 'n/a'],
|
|
['simulated trading header', formatJsonInline(okxSandboxPreflight.sandbox_header || {})],
|
|
['sandbox credentials', String(Boolean(okxSandboxPreflight.sandbox_credentials_present))],
|
|
['submit dry-run', String(Boolean(okxSandboxPreflight.submit_dry_run_ready))],
|
|
['cancel dry-run', String(Boolean(okxSandboxPreflight.cancel_dry_run_ready))],
|
|
['query order dry-run', String(Boolean(okxSandboxPreflight.query_order_dry_run_ready))],
|
|
['query fills dry-run', String(Boolean(okxSandboxPreflight.query_fills_dry_run_ready))],
|
|
['idempotency key', okxSandboxPreflight.idempotency_key_preview || 'n/a'],
|
|
['error normalization', formatList(Object.keys(okxSandboxPreflight.error_normalization || {}))],
|
|
['real live after sandbox', okxSandboxPreflight.execution_boundary || 'real_live_still_blocked'],
|
|
['sandbox summary', okxSandboxPreflight.operator_summary || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
const grid = createElement('div', 'cards');
|
|
(payload?.checklist || []).forEach((item) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
item.code || 'check',
|
|
item.passed ? 'passed' : 'blocked',
|
|
[
|
|
['passed', String(Boolean(item.passed))],
|
|
['detail', item.detail || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
if ((payload?.checklist || []).length) {
|
|
liveReadinessPanel.appendChild(grid);
|
|
}
|
|
}
|
|
|
|
function renderLaunchReadiness(payload) {
|
|
clearNode(launchReadinessPanel);
|
|
const liveAnalysisAssistantSummary = payload?.live_analysis_assistant_summary || null;
|
|
const readonlyLiveHealthSummary = payload?.readonly_live_health_summary || liveAnalysisAssistantSummary?.health_summary || null;
|
|
launchReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['live analysis assistant ready', String(Boolean(payload?.live_analysis_assistant_ready))],
|
|
['analysis only ready', String(Boolean(payload?.analysis_only_ready))],
|
|
['restricted decision support ready', String(Boolean(payload?.restricted_decision_support_ready))],
|
|
['decision support ready', String(Boolean(payload?.decision_support_ready))],
|
|
['execution assist ready', String(Boolean(payload?.execution_assist_ready))],
|
|
['real trade loop ready', String(Boolean(payload?.real_trade_loop_ready))],
|
|
['assisted trading ready', String(Boolean(payload?.assisted_trading_ready))],
|
|
['real live ready', String(Boolean(payload?.real_live_ready))],
|
|
['product mode', payload?.product_operating_mode || 'unknown'],
|
|
['product capabilities', formatJsonInline(payload?.product_capabilities || {})],
|
|
['boundary notes', formatList(payload?.product_boundary_notes || [])],
|
|
['blocking reasons', formatList(payload?.blocking_reasons || [])],
|
|
['warning reasons', formatList(payload?.warning_reasons || [])],
|
|
['next actions', formatList(payload?.next_actions || [])],
|
|
])
|
|
);
|
|
if (liveAnalysisAssistantSummary) {
|
|
launchReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['Read-only Live Analysis', liveAnalysisAssistantSummary.status || 'n/a'],
|
|
['ready_for_readonly_live_analysis', String(Boolean(liveAnalysisAssistantSummary.ready_for_readonly_live_analysis))],
|
|
['must_not_trade', String(Boolean(liveAnalysisAssistantSummary.must_not_trade))],
|
|
['No order submission', String(Boolean(liveAnalysisAssistantSummary.launch_boundary?.no_order_submission))],
|
|
['Not a buy/sell instruction', String(Boolean(liveAnalysisAssistantSummary.launch_boundary?.not_buy_sell_instruction))],
|
|
['Execution remains disabled', String(Boolean(liveAnalysisAssistantSummary.launch_boundary?.execution_remains_disabled))],
|
|
['api health', readonlyLiveHealthSummary?.api_health || 'n/a'],
|
|
['runtime health', readonlyLiveHealthSummary?.runtime_health || 'n/a'],
|
|
['market freshness', readonlyLiveHealthSummary?.market_freshness || 'n/a'],
|
|
['market stream', readonlyLiveHealthSummary?.market_stream || 'n/a'],
|
|
['broker/account context', readonlyLiveHealthSummary?.broker_account_context || 'n/a'],
|
|
['latest decision packet timestamp', formatTimestamp(readonlyLiveHealthSummary?.latest_decision_packet_timestamp)],
|
|
['latest review packet timestamp', formatTimestamp(readonlyLiveHealthSummary?.latest_review_packet_timestamp)],
|
|
['current candidate count', String(readonlyLiveHealthSummary?.current_candidate_count ?? 0)],
|
|
['current blocker', readonlyLiveHealthSummary?.current_blocker || 'none'],
|
|
['read-only recommendation', readonlyLiveHealthSummary?.recommendation || 'n/a'],
|
|
['allowed actions', formatList(liveAnalysisAssistantSummary.allowed_actions || [])],
|
|
['blocked actions', formatList(liveAnalysisAssistantSummary.blocked_actions || [])],
|
|
])
|
|
);
|
|
}
|
|
if (payload?.decision_support_boundary_summary) {
|
|
const boundary = payload.decision_support_boundary_summary;
|
|
launchReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['operator boundary', boundary.label || 'n/a'],
|
|
['boundary summary', boundary.operator_summary || 'n/a'],
|
|
['current model boundary', boundary.current_model_boundary || 'n/a'],
|
|
['current model reason', boundary.current_model_reason || 'n/a'],
|
|
['live execution boundary', boundary.live_execution_boundary || 'n/a'],
|
|
['live execution reason', boundary.live_execution_reason || 'n/a'],
|
|
['allowed operator actions', formatList(boundary.allowed_operator_actions || [])],
|
|
['blocked operator actions', formatList(boundary.blocked_operator_actions || [])],
|
|
['boundary next gate', boundary.next_boundary_gate || 'n/a'],
|
|
['boundary gate reason', boundary.next_boundary_gate_reason || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (payload?.decision_support_promotion_summary) {
|
|
const promotion = payload.decision_support_promotion_summary;
|
|
launchReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['decision support promotion', promotion.status || 'n/a'],
|
|
['strict decision support ready', String(Boolean(promotion.strict_decision_support_ready))],
|
|
['current decision support ready', String(Boolean(promotion.current_decision_support_ready))],
|
|
['execution after promotion', promotion.execution_boundary_after_promotion || 'n/a'],
|
|
['real live after promotion', promotion.real_live_boundary_after_promotion || 'n/a'],
|
|
['promotion model', promotion.candidate_model_code || 'n/a'],
|
|
['promotion classification', promotion.candidate_classification || 'n/a'],
|
|
['operator feedback samples', String(promotion.operator_review_sample_count ?? 0)],
|
|
['promotion criteria', `${promotion.passed_criteria_count ?? 0}/${promotion.required_criteria_count ?? 0}`],
|
|
['failed promotion criteria', formatList(promotion.failed_criteria || [])],
|
|
['next promotion gate', promotion.next_promotion_gate || 'n/a'],
|
|
['promotion summary', promotion.operator_summary || 'n/a'],
|
|
])
|
|
);
|
|
}
|
|
if (payload?.shadow_paper_validation_summary) {
|
|
const shadowPaper = payload.shadow_paper_validation_summary;
|
|
launchReadinessPanel.appendChild(
|
|
buildActionBar([
|
|
{ label: 'Record Shadow Observation', onClick: () => recordShadowPaperObservation('pending') },
|
|
{ label: 'Agree Shadow Observation', onClick: () => recordShadowPaperObservation('agree') },
|
|
{ label: 'Disagree Shadow Observation', onClick: () => recordShadowPaperObservation('disagree') },
|
|
])
|
|
);
|
|
launchReadinessPanel.appendChild(
|
|
buildInfoGrid([
|
|
['shadow paper validation', shadowPaper.status || 'n/a'],
|
|
['shadow validation status', shadowPaper.status || 'n/a'],
|
|
['shadow observed days', `${shadowPaper.observed_shadow_day_count ?? 0}/${shadowPaper.window_days ?? 0}`],
|
|
['shadow observation samples', String(shadowPaper.shadow_observation_count ?? 0)],
|
|
['shadow observation days', String(shadowPaper.shadow_observation_day_count ?? 0)],
|
|
['shadow candidates', String(shadowPaper.candidate_count ?? 0)],
|
|
['paper ticket samples', String(shadowPaper.paper_ticket_count ?? 0)],
|
|
['paper execution samples', String(shadowPaper.paper_execution_count ?? 0)],
|
|
['operator feedback samples', String(shadowPaper.operator_feedback_count ?? 0)],
|
|
['operator observation feedback', String(shadowPaper.operator_observation_feedback_count ?? 0)],
|
|
['journal samples', String(shadowPaper.journal_entry_count ?? 0)],
|
|
['shadow criteria', `${shadowPaper.passed_criteria_count ?? 0}/${shadowPaper.required_criteria_count ?? 0}`],
|
|
['failed shadow criteria', formatList(shadowPaper.failed_criteria || [])],
|
|
['next shadow gate', shadowPaper.next_validation_gate || 'n/a'],
|
|
['sample collection next actions', formatList(shadowPaper.sample_collection_plan?.next_actions || [])],
|
|
['real live after shadow', shadowPaper.execution_boundary || 'real_live_still_blocked'],
|
|
['shadow validation summary', shadowPaper.operator_summary || 'n/a'],
|
|
['shadow daily coverage', formatList((shadowPaper.daily_coverage || []).slice(-5).map((item) => `${item.date}: o${item.shadow_observation_count}/c${item.candidate_count}/p${item.paper_execution_count}/f${item.operator_feedback_count}`))],
|
|
])
|
|
);
|
|
}
|
|
const grid = createElement('div', 'cards');
|
|
(payload?.checklist || []).forEach((item) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
item.code || 'check',
|
|
item.passed ? 'passed' : item.severity || 'warning',
|
|
[
|
|
['passed', String(Boolean(item.passed))],
|
|
['severity', item.severity || 'n/a'],
|
|
['detail', item.detail || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
if ((payload?.checklist || []).length) {
|
|
launchReadinessPanel.appendChild(grid);
|
|
}
|
|
}
|
|
|
|
function renderSessionFocus(payload) {
|
|
clearNode(sessionFocusPanel);
|
|
const items = payload?.items || [];
|
|
const filtered = filterBySession(items, (item) => item.session_code);
|
|
if (!filtered.length) {
|
|
sessionFocusPanel.appendChild(createElement('div', 'empty-state', 'No session focus rows available.'));
|
|
return;
|
|
}
|
|
const grid = createElement('div', 'cards');
|
|
filtered.forEach((item) => {
|
|
const recommendation = item.session_recommendation || {};
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
item.session_code || 'session',
|
|
recommendation.classification || item.status || 'observe',
|
|
[
|
|
['status', item.status || 'unknown'],
|
|
['window', `${formatTimestamp(item.start_ts)} -> ${formatTimestamp(item.end_ts)}`],
|
|
['candidates', String(item.candidate_count ?? 0)],
|
|
['executable', String(item.executable_candidate_count ?? 0)],
|
|
['open executions', String(item.open_execution_count ?? 0)],
|
|
['alerts', String(item.alert_count ?? 0)],
|
|
['recommendation', `${recommendation.classification || 'observe'} @ ${formatNumber(recommendation.score, 1)}`],
|
|
['action hint', item.action_hint || 'observe'],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
sessionFocusPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderAlertHistory(alertRows) {
|
|
clearNode(alertHistoryPanel);
|
|
const items = filterBySession(alertRows?.items || [], (item) => (item.payload || {}).session_code);
|
|
if (!items.length) {
|
|
alertHistoryPanel.appendChild(createElement('div', 'empty-state', 'No alert history for current filters.'));
|
|
return;
|
|
}
|
|
const grid = createElement('div', 'cards');
|
|
items.slice(0, 8).forEach((alert) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
alert.alert_type || 'alert',
|
|
alert.status || 'queued',
|
|
[
|
|
['message', alert.message || 'n/a'],
|
|
['created', formatTimestamp(alert.created_ts)],
|
|
['channel', alert.channel || 'workbench'],
|
|
['payload', formatJsonInline(alert.payload || {})],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
alertHistoryPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderAlertQuality(summary, operatorDigest) {
|
|
clearNode(alertQualityPanel);
|
|
const delivery = summary?.delivery_summary || {};
|
|
const alertFeedback = summary?.alert_feedback_summary || {};
|
|
const visualFeedback = summary?.visual_feedback_summary || {};
|
|
const reviewSummary = summary?.visual_review_summary || {};
|
|
const silentWindow = summary?.silent_window || {};
|
|
const digestCounts = operatorDigest?.summary_counts || {};
|
|
const reviewQueue = operatorDigest?.review_queue || [];
|
|
alertQualityPanel.appendChild(
|
|
buildInfoGrid([
|
|
['recent alerts', String(summary?.recent_alert_count ?? 0)],
|
|
['recent visual reviews', String(summary?.recent_visual_review_count ?? 0)],
|
|
['silent window', `${silentWindow.active ? 'active' : 'inactive'} | ${silentWindow.window_label || 'n/a'}`],
|
|
['operator digest', `${operatorDigest?.status || 'idle'} | queue ${reviewQueue.length}`],
|
|
['delivery success', formatPercent(delivery.success_rate)],
|
|
['alert usefulness', formatPercent(alertFeedback.positive_rate)],
|
|
['visual helpfulness', formatPercent(visualFeedback.positive_rate)],
|
|
['quality flags', formatList(summary?.quality_flags || [])],
|
|
['operator summary', summary?.operator_summary || 'n/a'],
|
|
])
|
|
);
|
|
const grid = createElement('div', 'cards');
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Operator Digest',
|
|
reviewQueue.length ? 'review' : (operatorDigest?.status || 'idle'),
|
|
[
|
|
['high value alerts', String(digestCounts.high_value_alert_count ?? 0)],
|
|
['suppressed / failed-retry', `${digestCounts.suppressed_delivery_count ?? 0} / ${digestCounts.failed_or_retry_delivery_count ?? 0}`],
|
|
['visual mismatches', String(digestCounts.visual_mismatch_count ?? 0)],
|
|
['pending feedback', String(digestCounts.pending_feedback_count ?? 0)],
|
|
['review queue', formatList(reviewQueue.slice(0, 4).map((item) => `${item.priority || 'n/a'}:${item.queue_type || 'item'}`))],
|
|
['digest summary', operatorDigest?.operator_summary || 'n/a'],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Delivery',
|
|
delivery.failed_count ? 'warning' : 'ok',
|
|
[
|
|
['attempts', String(delivery.count ?? 0)],
|
|
['delivered / failed / skipped', `${delivery.delivered_count ?? 0} / ${delivery.failed_count ?? 0} / ${delivery.skipped_count ?? 0}`],
|
|
['silent-window skips', String(delivery.silent_window_skip_count ?? 0)],
|
|
['noise-policy skips', String(delivery.noise_policy_skip_count ?? 0)],
|
|
['workbench-only skips', String(delivery.workbench_only_skip_count ?? 0)],
|
|
['retried deliveries', String(delivery.retried_delivery_count ?? 0)],
|
|
['retry exhausted', String(delivery.retry_exhausted_skip_count ?? 0)],
|
|
['transport retried', String(delivery.transport_retried_delivery_count ?? 0)],
|
|
['transport retry failed', String(delivery.transport_retry_failed_count ?? 0)],
|
|
['policy external allowed', String(delivery.policy_external_allowed_count ?? 0)],
|
|
['media attached / linked / unavailable', `${delivery.media_attached_count ?? 0} / ${delivery.media_linked_count ?? 0} / ${delivery.media_unavailable_count ?? 0}`],
|
|
['channel disabled', String(delivery.channel_disabled_count ?? 0)],
|
|
['channel status counts', formatJsonInline(delivery.channel_status_counts || {})],
|
|
['external send attempts', String(delivery.external_attempt_count ?? 0)],
|
|
['latest delivery', `${delivery.latest_status || 'n/a'} @ ${delivery.latest_channel || 'n/a'}`],
|
|
['latest attempted', formatTimestamp(delivery.latest_attempted_ts)],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Silent Window',
|
|
silentWindow.active ? 'warning' : 'ok',
|
|
[
|
|
['status', silentWindow.active ? 'active' : (silentWindow.enabled ? 'inactive' : 'disabled')],
|
|
['window', silentWindow.window_label || 'n/a'],
|
|
['timezone', silentWindow.timezone || 'n/a'],
|
|
['next allowed', formatTimestamp(silentWindow.next_allowed_ts)],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Alert Feedback',
|
|
alertFeedback.negative_count ? 'warning' : 'ok',
|
|
[
|
|
['feedback rows', String(alertFeedback.count ?? 0)],
|
|
['positive / negative', `${alertFeedback.positive_count ?? 0} / ${alertFeedback.negative_count ?? 0}`],
|
|
['labels', formatJsonInline(alertFeedback.label_counts || {})],
|
|
['latest label', alertFeedback.latest_label || 'n/a'],
|
|
['latest feedback', formatTimestamp(alertFeedback.latest_created_ts)],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Visual Feedback',
|
|
visualFeedback.negative_count ? 'warning' : 'ok',
|
|
[
|
|
['feedback rows', String(visualFeedback.count ?? 0)],
|
|
['positive / negative', `${visualFeedback.positive_count ?? 0} / ${visualFeedback.negative_count ?? 0}`],
|
|
['labels', formatJsonInline(visualFeedback.label_counts || {})],
|
|
['latest label', visualFeedback.latest_label || 'n/a'],
|
|
['latest feedback', formatTimestamp(visualFeedback.latest_created_ts)],
|
|
]
|
|
)
|
|
);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
'Visual States',
|
|
reviewSummary.latest_visual_state || 'n/a',
|
|
[
|
|
['review rows', String(reviewSummary.count ?? 0)],
|
|
['state counts', formatJsonInline(reviewSummary.state_counts || {})],
|
|
['trigger counts', formatJsonInline(reviewSummary.trigger_type_counts || {})],
|
|
['latest state', reviewSummary.latest_visual_state || 'n/a'],
|
|
['latest trigger', reviewSummary.latest_trigger_type || 'n/a'],
|
|
['latest reviewed', formatTimestamp(reviewSummary.latest_reviewed_ts)],
|
|
]
|
|
)
|
|
);
|
|
alertQualityPanel.appendChild(grid);
|
|
}
|
|
|
|
function getVisualSnapshotArtifactPath(review) {
|
|
const snapshotPayload = review?.snapshot_payload || {};
|
|
return snapshotPayload.artifact_path || snapshotPayload?.payload?.artifact_path || review?.snapshot_artifact_path || 'n/a';
|
|
}
|
|
|
|
function getVisualSnapshotBrowserStatus(review) {
|
|
const snapshotPayload = review?.snapshot_payload || {};
|
|
return snapshotPayload.browser_capture?.status || snapshotPayload?.payload?.browser_capture?.status || 'n/a';
|
|
}
|
|
|
|
function getVisualSnapshotHtmlPath(review) {
|
|
const snapshotPayload = review?.snapshot_payload || {};
|
|
return snapshotPayload.html_path || snapshotPayload?.payload?.html_path || 'n/a';
|
|
}
|
|
|
|
function getVisualMediaSummary(review) {
|
|
return review?.media_evidence_summary || review?.media_evidence || {};
|
|
}
|
|
|
|
function getVisualFeedbackSummary(review) {
|
|
const summary = review?.feedback_summary || {};
|
|
const labels = summary.labels || {};
|
|
const labelParts = Object.entries(labels).map(([label, count]) => `${label}:${count}`);
|
|
return {
|
|
count: summary.count ?? (Array.isArray(review?.feedback) ? review.feedback.length : 0),
|
|
latestLabel: summary.latest_label || 'n/a',
|
|
labels: labelParts.length ? labelParts.join(', ') : 'none',
|
|
usefulCount: summary.useful_count ?? 0,
|
|
misleadingCount: summary.misleading_count ?? 0,
|
|
actionableCount: summary.actionable_count ?? 0,
|
|
};
|
|
}
|
|
|
|
function renderVisualReviews(reviewRows, decisionPacketId) {
|
|
clearNode(visualReviewsPanel);
|
|
const items = (reviewRows?.items || []).slice(0, 10);
|
|
const latest = items[0] || null;
|
|
const latestFeedback = getVisualFeedbackSummary(latest);
|
|
const latestMedia = getVisualMediaSummary(latest);
|
|
visualReviewsPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Cleanup Old Captures',
|
|
onClick: () => runAction('/trading/visual-watch/cleanup-captures', {
|
|
retention_hours: 168,
|
|
dry_run: false,
|
|
max_delete_files: 500,
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
visualReviewsPanel.appendChild(
|
|
buildInfoGrid([
|
|
['decision packet', decisionPacketId || 'n/a'],
|
|
['reviews', String(items.length)],
|
|
['latest state', latest?.visual_state || 'n/a'],
|
|
['latest trigger', latest?.trigger_type || latest?.payload?.trigger_type || 'n/a'],
|
|
['latest source', latest?.snapshot_source_name || 'n/a'],
|
|
['latest browser capture', getVisualSnapshotBrowserStatus(latest)],
|
|
['latest media', latestMedia.status || 'n/a'],
|
|
['latest artifact', getVisualSnapshotArtifactPath(latest)],
|
|
['latest feedback', `${latestFeedback.count} | ${latestFeedback.latestLabel}`],
|
|
['feedback labels', latestFeedback.labels],
|
|
])
|
|
);
|
|
if (!items.length) {
|
|
visualReviewsPanel.appendChild(createElement('div', 'empty-state', 'No visual reviews recorded for the current decision packet.'));
|
|
return;
|
|
}
|
|
const grid = createElement('div', 'cards');
|
|
items.forEach((review) => {
|
|
const feedbackSummary = getVisualFeedbackSummary(review);
|
|
const mediaSummary = getVisualMediaSummary(review);
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
`Watch ${review.watch_run_id || 'n/a'}`,
|
|
review.visual_state || 'unknown',
|
|
[
|
|
['trigger', review.trigger_type || review?.payload?.trigger_type || 'n/a'],
|
|
['snapshot', review.snapshot_id || 'n/a'],
|
|
['source', review.snapshot_source_name || 'n/a'],
|
|
['browser capture', getVisualSnapshotBrowserStatus(review)],
|
|
['media status', mediaSummary.status || 'n/a'],
|
|
['media primary', mediaSummary.primary_path || review?.media_evidence?.primary_item?.path || 'n/a'],
|
|
['image path', review.snapshot_image_path || 'n/a'],
|
|
['html path', getVisualSnapshotHtmlPath(review)],
|
|
['artifact path', getVisualSnapshotArtifactPath(review)],
|
|
['reviewed', formatTimestamp(review.reviewed_ts)],
|
|
['confidence', formatNumber(review.visual_confidence, 2)],
|
|
['anomalies', formatList(review.ui_anomalies || [])],
|
|
['feedback', `${feedbackSummary.count} | ${feedbackSummary.latestLabel}`],
|
|
['feedback labels', feedbackSummary.labels],
|
|
['useful / misleading / actionable', `${feedbackSummary.usefulCount} / ${feedbackSummary.misleadingCount} / ${feedbackSummary.actionableCount}`],
|
|
],
|
|
[
|
|
{
|
|
label: 'Useful',
|
|
onClick: () => runAction(`/trading/visual-watch/reviews/${review.id}/feedback`, {
|
|
label: 'visual_helpful',
|
|
note: 'marked from workbench',
|
|
created_by: 'local_operator',
|
|
payload: { source: 'workbench', feedback_type: 'visual_helpful' },
|
|
}),
|
|
},
|
|
{
|
|
label: 'Misleading',
|
|
onClick: () => runAction(`/trading/visual-watch/reviews/${review.id}/feedback`, {
|
|
label: 'visual_misleading',
|
|
note: 'marked from workbench',
|
|
created_by: 'local_operator',
|
|
payload: { source: 'workbench', feedback_type: 'visual_misleading' },
|
|
}),
|
|
},
|
|
{
|
|
label: 'Actionable',
|
|
onClick: () => runAction(`/trading/visual-watch/reviews/${review.id}/feedback`, {
|
|
label: 'actionable',
|
|
note: 'marked from workbench',
|
|
created_by: 'local_operator',
|
|
payload: { source: 'workbench', feedback_type: 'actionable' },
|
|
}),
|
|
},
|
|
],
|
|
review.visual_notes || ''
|
|
)
|
|
);
|
|
});
|
|
visualReviewsPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderOperatorConsole(noteRows, reviewPacket) {
|
|
clearNode(operatorConsolePanel);
|
|
const assistantSummary = reviewPacket?.decision_assistant_summary || window.__latestDecisionAssistantSummary || null;
|
|
const focusContext = assistantOperatorNoteDraft.context || getAssistantFocusContext(assistantSummary);
|
|
const reviewEntrypoints = assistantSummary?.review_entrypoints || {};
|
|
const authorInput = createElement('input');
|
|
authorInput.type = 'text';
|
|
authorInput.placeholder = 'operator';
|
|
authorInput.value = 'local_operator';
|
|
const bodyInput = createElement('textarea');
|
|
bodyInput.placeholder = 'Add a session, execution, or decision note.';
|
|
bodyInput.rows = 4;
|
|
bodyInput.className = 'operator-note-textarea';
|
|
bodyInput.value = assistantOperatorNoteDraft.body || reviewEntrypoints.focus_note_prefill || '';
|
|
const controls = createElement('div', 'actions');
|
|
const saveButton = createElement('button', '', 'Save Note');
|
|
saveButton.type = 'button';
|
|
saveButton.addEventListener('click', async () => {
|
|
try {
|
|
const noteContext = assistantOperatorNoteDraft.context || focusContext;
|
|
await postJson('/trading/operator-notes', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
note_scope: reviewEntrypoints.focus_note_scope || 'workbench',
|
|
author: authorInput.value || 'local_operator',
|
|
body: bodyInput.value || '',
|
|
session_code: noteContext?.session_code || (sessionFilterInput.value === 'all' ? null : sessionFilterInput.value),
|
|
candidate_id: noteContext?.candidate_id ?? null,
|
|
tags: buildOperatorNoteTags(['workbench', ...(reviewEntrypoints.focus_note_tags || [])], noteContext),
|
|
});
|
|
bodyInput.value = '';
|
|
assistantOperatorNoteDraft = { body: '', context: null };
|
|
await refreshDashboard();
|
|
} catch (error) {
|
|
showErrorPanel(String(error));
|
|
}
|
|
});
|
|
const packetButton = createElement('button', '', 'Generate Review Packet');
|
|
packetButton.type = 'button';
|
|
packetButton.addEventListener('click', async () => {
|
|
try {
|
|
await fetchJson(`/trading/review-packet?${queryString({ instrument_id: instrumentIdInput.value || '1', profile_key: assistantDigestProfileKey || 'local_operator' })}`);
|
|
await refreshDashboard();
|
|
} catch (error) {
|
|
showErrorPanel(String(error));
|
|
}
|
|
});
|
|
const prefillFocusButton = createElement('button', '', 'Prefill Focus Note');
|
|
prefillFocusButton.type = 'button';
|
|
prefillFocusButton.addEventListener('click', () => {
|
|
prefillOperatorNote(
|
|
reviewEntrypoints.focus_note_prefill || '',
|
|
focusContext,
|
|
);
|
|
});
|
|
controls.appendChild(saveButton);
|
|
controls.appendChild(packetButton);
|
|
controls.appendChild(prefillFocusButton);
|
|
operatorConsolePanel.appendChild(authorInput);
|
|
operatorConsolePanel.appendChild(bodyInput);
|
|
operatorConsolePanel.appendChild(controls);
|
|
operatorConsolePanel.appendChild(
|
|
buildInfoGrid([
|
|
['focus candidate', String(focusContext?.candidate_id ?? 'n/a')],
|
|
['focus session', focusContext?.session_code || 'n/a'],
|
|
['focus model', focusContext?.model_code || 'n/a'],
|
|
['focus note prompt', reviewEntrypoints.focus_note_prompt || 'n/a'],
|
|
['review packet', formatTimestamp(reviewPacket?.generated_ts)],
|
|
['packet executions', String((reviewPacket?.recent_executions || []).length)],
|
|
['packet alerts', String((reviewPacket?.recent_alerts || []).length)],
|
|
['packet notes', String((reviewPacket?.recent_notes || []).length)],
|
|
['council stance', reviewPacket?.research_council?.payload?.decision?.overall_stance || 'n/a'],
|
|
['council confidence', reviewPacket?.research_council?.payload?.decision?.committee_confidence != null
|
|
? formatNumber(reviewPacket?.research_council?.payload?.decision?.committee_confidence, 2)
|
|
: 'n/a'],
|
|
['optimization health', reviewPacket?.research_council_optimization_review?.health_state || 'n/a'],
|
|
['optimization summary', reviewPacket?.research_council_optimization_review?.operator_summary || 'n/a'],
|
|
['optimization next actions', formatList(reviewPacket?.research_council_optimization_review?.recommended_actions || [])],
|
|
])
|
|
);
|
|
const councilDecision = reviewPacket?.research_council?.payload?.decision || null;
|
|
if (councilDecision) {
|
|
const bullCase = councilDecision.bull_case || {};
|
|
const bearCase = councilDecision.bear_case || {};
|
|
const newsAnalysis = councilDecision.news_event_analysis || {};
|
|
const visualAnalysis = councilDecision.visual_analysis || {};
|
|
operatorConsolePanel.appendChild(
|
|
buildItemCard(
|
|
'Research Council',
|
|
councilDecision.overall_stance || 'observe_only',
|
|
[
|
|
['research summary', councilDecision.research_summary || 'n/a'],
|
|
['operator summary', councilDecision.operator_summary || 'n/a'],
|
|
['committee confidence', formatNumber(councilDecision.committee_confidence, 2)],
|
|
['agreement', formatList(councilDecision.agreement_points || [])],
|
|
['disagreement', formatList(councilDecision.disagreement_points || [])],
|
|
['follow-up checks', formatList(councilDecision.follow_up_checks || [])],
|
|
['recommended watch', formatList(councilDecision.recommended_watch_items || [])],
|
|
['operator questions', formatList(councilDecision.operator_questions || [])],
|
|
['execution veto', formatList(councilDecision.execution_veto_signals || [])],
|
|
['news/event', newsAnalysis.summary || 'n/a'],
|
|
['visual', visualAnalysis.summary || 'n/a'],
|
|
],
|
|
[],
|
|
`Bull: ${bullCase.summary || 'n/a'} | Bear: ${bearCase.summary || 'n/a'}`
|
|
)
|
|
);
|
|
}
|
|
const notes = noteRows?.items || [];
|
|
const grid = createElement('div', 'cards');
|
|
notes.slice(0, 6).forEach((note) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
note.author || 'operator',
|
|
note.note_scope || 'general',
|
|
[
|
|
['session', note.session_code || 'n/a'],
|
|
['created', formatTimestamp(note.created_ts)],
|
|
['tags', formatList(note.tags || [])],
|
|
['body', note.body || ''],
|
|
]
|
|
)
|
|
);
|
|
});
|
|
if (!notes.length) {
|
|
operatorConsolePanel.appendChild(createElement('div', 'empty-state', 'No operator notes yet.'));
|
|
} else {
|
|
operatorConsolePanel.appendChild(grid);
|
|
}
|
|
}
|
|
|
|
function renderPreMarketPlan(plan) {
|
|
clearNode(preMarketPlanPanel);
|
|
preMarketPlanPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Generate Active Plan',
|
|
onClick: () => runAction('/trading/plans/generate', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
bias_tf: timeframeInput.value || '1m',
|
|
setup_tf: timeframeInput.value || '1m',
|
|
trigger_tf: timeframeInput.value || '1m',
|
|
activate: true,
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
|
|
if (!plan) {
|
|
preMarketPlanPanel.appendChild(createElement('div', 'empty-state', 'No active plan for this instrument.'));
|
|
return;
|
|
}
|
|
|
|
preMarketPlanPanel.appendChild(
|
|
buildInfoGrid([
|
|
['narrative', plan.narrative || 'n/a'],
|
|
['primary draw', formatJsonInline(plan.primary_draw)],
|
|
['secondary draw', formatJsonInline(plan.secondary_draw)],
|
|
['bias', plan.bias || 'n/a'],
|
|
['allowed sessions', formatList(plan.allowed_sessions || [])],
|
|
['invalidations', formatJsonInline(plan.invalidations || [])],
|
|
['no-trade conditions', formatList(plan.no_trade_conditions || [])],
|
|
['status', plan.status || 'unknown'],
|
|
['activated', formatTimestamp(plan.activated_ts)],
|
|
])
|
|
);
|
|
}
|
|
|
|
function renderCandidates(items) {
|
|
clearNode(tradeCandidatesPanel);
|
|
tradeCandidatesPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Run Watch',
|
|
onClick: () => runAction('/trading/watch/run', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
setup_tf: timeframeInput.value || '1m',
|
|
trigger_tf: timeframeInput.value || '1m',
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
|
|
const filtered = filterBySearch(filterBySession(items, (item) => item.session_code), (item) => [
|
|
item.id,
|
|
item.status,
|
|
item.side,
|
|
item.session_code,
|
|
item.ai_explanation,
|
|
...(item.reason_codes || []),
|
|
...(item.missing_conditions || []),
|
|
].join(' '));
|
|
|
|
if (!filtered.length) {
|
|
tradeCandidatesPanel.appendChild(createElement('div', 'empty-state', 'No candidate rows match the current filters.'));
|
|
return;
|
|
}
|
|
|
|
const trustModels = new Map();
|
|
const trustPayload = window.__latestTrustScorePayload || { models: [] };
|
|
(trustPayload.models || []).forEach((model) => trustModels.set(model.model_code, model));
|
|
const grid = createElement('div', 'cards');
|
|
filtered.forEach((candidate) => {
|
|
const trustModel = trustModels.get(candidate.model_code) || {};
|
|
const executionAllowed = trustModel.execution_allowed !== false && candidate.executable;
|
|
const canBuildShadowPaper = !executionAllowed && candidate.executable;
|
|
const detail = `quality ${formatNumber(candidate.quality_score)} | ${candidate.ai_explanation || 'no explanation'}`;
|
|
const ticketActions = [];
|
|
if (executionAllowed) {
|
|
ticketActions.push(
|
|
{
|
|
label: 'Build Paper Ticket',
|
|
onClick: () => runAction(`/trading/candidates/${candidate.id}/build-ticket`, {
|
|
execution_mode: 'paper',
|
|
account_equity: 10000,
|
|
risk_pct: 0.01,
|
|
}),
|
|
},
|
|
{
|
|
label: 'Build Semi-Auto Ticket',
|
|
onClick: () => runAction(`/trading/candidates/${candidate.id}/build-ticket`, {
|
|
execution_mode: 'semi_auto',
|
|
account_equity: 10000,
|
|
risk_pct: 0.01,
|
|
}),
|
|
}
|
|
);
|
|
} else if (canBuildShadowPaper) {
|
|
ticketActions.push({
|
|
label: 'Build Shadow Paper',
|
|
onClick: () => runAction(`/trading/candidates/${candidate.id}/build-ticket`, {
|
|
execution_mode: 'paper',
|
|
account_equity: 10000,
|
|
risk_pct: 0.01,
|
|
allow_shadow_paper: true,
|
|
}),
|
|
});
|
|
}
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
`Candidate #${candidate.id}`,
|
|
candidate.status,
|
|
[
|
|
['model', candidate.model_code],
|
|
['side', candidate.side || 'n/a'],
|
|
['session', candidate.session_code || 'n/a'],
|
|
['confirmation chain', formatList(candidate.present_conditions || [])],
|
|
['missing requirements', formatList(candidate.missing_conditions || [])],
|
|
['entry zone', `${formatNumber(candidate.entry_low, 3)} - ${formatNumber(candidate.entry_high, 3)}`],
|
|
['stop', formatNumber(candidate.stop_loss, 3)],
|
|
['TP1 / TP2', `${formatNumber(candidate.tp1, 3)} / ${formatNumber(candidate.tp2, 3)}`],
|
|
['RR TP1 / TP2', `${formatNumber(candidate.rr_tp1)} / ${formatNumber(candidate.rr_tp2)}`],
|
|
['expiry', formatTimestamp(candidate.expiry_ts)],
|
|
['trust mode', trustModel.operator_mode || 'unknown'],
|
|
['management rules', formatJsonInline(candidate.management_rules)],
|
|
['evidence links', formatJsonInline(candidate.evidence)],
|
|
],
|
|
ticketActions,
|
|
detail
|
|
)
|
|
);
|
|
});
|
|
tradeCandidatesPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderTickets(items) {
|
|
clearNode(executionTicketsPanel);
|
|
const filtered = filterBySearch(filterBySession(items, (item) => (item.ticket_snapshot || {}).signal?.session_code), (item) => {
|
|
const snapshot = item.ticket_snapshot || {};
|
|
return [
|
|
item.id,
|
|
item.status,
|
|
item.execution_mode,
|
|
snapshot.signal?.side,
|
|
snapshot.signal?.session_code,
|
|
snapshot.decision?.meta?.ai_explanation,
|
|
...(snapshot.decision?.reasons || []),
|
|
].join(' ');
|
|
});
|
|
|
|
if (!filtered.length) {
|
|
executionTicketsPanel.appendChild(createElement('div', 'empty-state', 'No execution tickets match the current filters.'));
|
|
return;
|
|
}
|
|
|
|
const grid = createElement('div', 'cards');
|
|
filtered.forEach((ticket) => {
|
|
const snapshot = ticket.ticket_snapshot || {};
|
|
const signal = snapshot.signal || {};
|
|
const decision = snapshot.decision || {};
|
|
const riskGovernor = decision.meta?.risk_governor || {};
|
|
const riskAmount = Number(snapshot.account_equity || 0) * Number(decision.risk_pct || 0);
|
|
const actions = [];
|
|
actions.push({
|
|
label: 'Risk Check',
|
|
onClick: () => runAction(`/trading/tickets/${ticket.id}/risk-check`, {
|
|
account_equity: Number(snapshot.account_equity || 10000),
|
|
risk_pct: Number(decision.risk_pct || 0.01),
|
|
}),
|
|
});
|
|
if (ticket.status === 'awaiting_confirmation') {
|
|
actions.push({
|
|
label: 'Confirm',
|
|
onClick: () => runAction(`/trading/tickets/${ticket.id}/confirm`, { confirmed_by: 'local_operator' }),
|
|
});
|
|
}
|
|
if (ticket.status === 'confirmed' && ticket.execution_mode === 'paper') {
|
|
actions.push({
|
|
label: 'Paper Execute',
|
|
onClick: () => runAction(`/trading/tickets/${ticket.id}/paper-execute`, { confirmed_by: 'local_operator' }),
|
|
});
|
|
}
|
|
if (ticket.status === 'confirmed' && ticket.execution_mode === 'semi_auto') {
|
|
actions.push({
|
|
label: 'Semi-Auto Submit',
|
|
onClick: () => runAction(`/trading/tickets/${ticket.id}/semi-auto-submit`, { confirmed_by: 'local_operator' }),
|
|
});
|
|
}
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
`Ticket #${ticket.id}`,
|
|
ticket.status,
|
|
[
|
|
['mode', ticket.execution_mode],
|
|
['side', signal.side || 'n/a'],
|
|
['entry zone', `${formatNumber(signal.entry_low, 3)} - ${formatNumber(signal.entry_high, 3)}`],
|
|
['stop', formatNumber(signal.stop_loss, 3)],
|
|
['TP1 / TP2', `${formatNumber(signal.tp1, 3)} / ${formatNumber(signal.tp2, 3)}`],
|
|
['RR', `${formatNumber(decision.meta?.tp1 ? Math.abs((Number(decision.meta.tp1) - Number(decision.entry_price || 0)) / Math.max(Math.abs(Number(decision.entry_price || 0) - Number(decision.meta.stop_loss || 0)), 1e-9)) : 0)} / ${formatNumber(decision.meta?.tp2 ? Math.abs((Number(decision.meta.tp2) - Number(decision.entry_price || 0)) / Math.max(Math.abs(Number(decision.entry_price || 0) - Number(decision.meta.stop_loss || 0)), 1e-9)) : 0)}`],
|
|
['risk amount', formatNumber(riskAmount, 2)],
|
|
['expiry', formatTimestamp(ticket.expires_ts)],
|
|
['management rules', formatJsonInline(signal.target_plan?.management_rules || [])],
|
|
['risk governor result', formatJsonInline(riskGovernor.reject_codes || [])],
|
|
['confirmed by', ticket.confirmed_by || 'n/a'],
|
|
['confirmed at', formatTimestamp(ticket.confirmed_at)],
|
|
],
|
|
actions,
|
|
decision.meta?.ai_explanation || ''
|
|
)
|
|
);
|
|
});
|
|
executionTicketsPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderExecutions(items) {
|
|
clearNode(positionManagementPanel);
|
|
positionManagementPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Reconcile Real Trades',
|
|
onClick: () => runAction('/trading/reconcile', {
|
|
instrument_id: Number(instrumentIdInput.value || 1),
|
|
}),
|
|
},
|
|
])
|
|
);
|
|
const filtered = filterExecutions(items);
|
|
if (!filtered.length) {
|
|
positionManagementPanel.appendChild(createElement('div', 'empty-state', 'No execution rows match the current filters.'));
|
|
return;
|
|
}
|
|
|
|
const grid = createElement('div', 'cards');
|
|
filtered.forEach((execution) => {
|
|
const tpProgress = [
|
|
execution.tp1_hit ? 'TP1 hit' : 'TP1 open',
|
|
execution.tp2_hit ? 'TP2 hit' : 'TP2 open',
|
|
].join(' | ');
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
`Execution #${execution.execution_id}`,
|
|
execution.execution_status,
|
|
[
|
|
['mode', execution.execution_mode],
|
|
['side', execution.side || 'n/a'],
|
|
['fill state', execution.fill_state || 'n/a'],
|
|
['position state', execution.position_state || 'n/a'],
|
|
['TP progress', tpProgress],
|
|
['stop state', execution.close_reason || 'active'],
|
|
['invalidation alerts', formatList(execution.error_tags || [])],
|
|
['ticket', execution.execution_ticket_id || 'n/a'],
|
|
['opened', formatTimestamp(execution.opened_ts)],
|
|
['closed', formatTimestamp(execution.closed_ts)],
|
|
['quality', formatNumber(execution.quality_score)],
|
|
['real trade mapping', formatJsonInline(execution.reconciliation_state || {})],
|
|
['bracket state', formatJsonInline(execution.bracket_state || {})],
|
|
['reconciliation', formatJsonInline(execution.reconciliation_state || {})],
|
|
],
|
|
[
|
|
{
|
|
label: 'Cancel',
|
|
onClick: () => runAction(`/trading/executions/${execution.execution_id}/cancel`, {}),
|
|
},
|
|
{
|
|
label: 'Close',
|
|
onClick: () => runAction(`/trading/executions/${execution.execution_id}/close`, {}),
|
|
},
|
|
{
|
|
label: 'Sync',
|
|
onClick: () => runAction(`/trading/executions/${execution.execution_id}/sync`, {}),
|
|
},
|
|
],
|
|
execution.ai_explanation || ''
|
|
)
|
|
);
|
|
});
|
|
positionManagementPanel.appendChild(grid);
|
|
}
|
|
|
|
function renderPostTradeReview(summary, journalItems, weeklyReview) {
|
|
clearNode(postTradeReviewPanel);
|
|
postTradeReviewPanel.appendChild(
|
|
buildActionBar([
|
|
{
|
|
label: 'Generate Journal Backfill',
|
|
onClick: () => runAction('/trading/journal/generate', {}),
|
|
},
|
|
])
|
|
);
|
|
|
|
const top = buildInfoGrid([
|
|
['entry count', String(summary?.entry_count ?? weeklyReview?.entry_count ?? 0)],
|
|
['real trades', String(weeklyReview?.real_trade_count ?? 0)],
|
|
['framework execution rate', formatPercent(weeklyReview?.framework_execution_rate)],
|
|
['out-of-model rate', formatPercent(weeklyReview?.out_of_model_trade_rate)],
|
|
['out-of-model real trades', formatPercent(weeklyReview?.out_of_model_real_trade_rate)],
|
|
['risk violation count', String(weeklyReview?.risk_violation_count ?? 0)],
|
|
['reconciliation issues', String(weeklyReview?.reconciliation_issue_count ?? 0)],
|
|
['high quality session', formatJsonInline(weeklyReview?.high_quality_session)],
|
|
['low quality session', formatJsonInline(weeklyReview?.low_quality_session)],
|
|
['next actions', formatList(weeklyReview?.next_week_actions || [])],
|
|
]);
|
|
postTradeReviewPanel.appendChild(top);
|
|
|
|
const filteredJournal = filterBySession(journalItems, (item) => item.session_code);
|
|
if (!filteredJournal.length) {
|
|
postTradeReviewPanel.appendChild(createElement('div', 'empty-state', 'No journal entries available for this instrument.'));
|
|
return;
|
|
}
|
|
|
|
const grid = createElement('div', 'cards');
|
|
filteredJournal.slice(0, 6).forEach((entry) => {
|
|
grid.appendChild(
|
|
buildItemCard(
|
|
`Journal #${entry.id}`,
|
|
entry.compliance_state || 'review',
|
|
[
|
|
['model compliance', String(Boolean(entry.model_compliance))],
|
|
['risk compliance', String(!entry.risk_violation)],
|
|
['execution violations', formatList(entry.execution_violations || [])],
|
|
['model failure', String(Boolean(entry.model_failure))],
|
|
['execution failure', String(Boolean(entry.execution_violation))],
|
|
['next actions', formatList(entry.next_actions || [])],
|
|
],
|
|
[],
|
|
formatJsonInline(entry.summary || {})
|
|
)
|
|
);
|
|
});
|
|
postTradeReviewPanel.appendChild(grid);
|
|
}
|
|
|
|
function drawCandles(items) {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
if (!Array.isArray(items) || items.length === 0) {
|
|
ctx.fillStyle = '#9db0d3';
|
|
ctx.font = '14px sans-serif';
|
|
ctx.fillText('No candle data available.', 24, 36);
|
|
return;
|
|
}
|
|
|
|
const candles = [...items].reverse();
|
|
const highs = candles.map((item) => Number(item.high || 0));
|
|
const lows = candles.map((item) => Number(item.low || 0));
|
|
const closes = candles.map((item) => Number(item.close || 0));
|
|
const minPrice = Math.min(...lows);
|
|
const maxPrice = Math.max(...highs);
|
|
const range = Math.max(maxPrice - minPrice, 1e-9);
|
|
const chartLeft = 40;
|
|
const chartRight = canvas.width - 16;
|
|
const chartTop = 16;
|
|
const chartBottom = canvas.height - 24;
|
|
const chartHeight = chartBottom - chartTop;
|
|
const chartWidth = chartRight - chartLeft;
|
|
|
|
ctx.strokeStyle = '#24304f';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i <= 4; i += 1) {
|
|
const y = chartTop + (chartHeight / 4) * i;
|
|
ctx.beginPath();
|
|
ctx.moveTo(chartLeft, y);
|
|
ctx.lineTo(chartRight, y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.strokeStyle = '#4f7cff';
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
closes.forEach((close, index) => {
|
|
const x = chartLeft + (chartWidth * index) / Math.max(closes.length - 1, 1);
|
|
const y = chartBottom - ((close - minPrice) / range) * chartHeight;
|
|
if (index === 0) ctx.moveTo(x, y);
|
|
else ctx.lineTo(x, y);
|
|
});
|
|
ctx.stroke();
|
|
|
|
ctx.fillStyle = '#9db0d3';
|
|
ctx.font = '12px sans-serif';
|
|
ctx.fillText(maxPrice.toFixed(2), 4, chartTop + 10);
|
|
ctx.fillText(minPrice.toFixed(2), 4, chartBottom);
|
|
}
|
|
|
|
function showErrorPanel(message) {
|
|
[
|
|
marketStatePanel,
|
|
runtimeStatusPanel,
|
|
decisionPacketPanel,
|
|
trustScorePanel,
|
|
liveReadinessPanel,
|
|
launchReadinessPanel,
|
|
sessionFocusPanel,
|
|
preMarketPlanPanel,
|
|
strategyBacktestPanel,
|
|
tradeCandidatesPanel,
|
|
executionTicketsPanel,
|
|
alertHistoryPanel,
|
|
visualReviewsPanel,
|
|
positionManagementPanel,
|
|
operatorConsolePanel,
|
|
postTradeReviewPanel,
|
|
alertQualityPanel,
|
|
].forEach((panel) => {
|
|
clearNode(panel);
|
|
panel.appendChild(createElement('div', 'empty-state error-state', message));
|
|
});
|
|
}
|
|
|
|
function updateLastUpdated() {
|
|
setText(lastUpdated, `Last updated: ${new Date().toLocaleTimeString()}`);
|
|
}
|
|
|
|
function renderDashboardData({
|
|
health,
|
|
candles,
|
|
tradingPlan,
|
|
marketState,
|
|
runtimeStatus,
|
|
runtimeIncidents,
|
|
maintenanceSummary,
|
|
decisionPacket,
|
|
decisionAlerts,
|
|
visualReviews,
|
|
alertQualitySummary,
|
|
operatorDigest,
|
|
decisionPacketId,
|
|
trustScore,
|
|
liveReadiness,
|
|
launchReadiness,
|
|
sessionView,
|
|
strategyBacktests,
|
|
tradeCandidates,
|
|
executionTickets,
|
|
tradingExecutions,
|
|
journalSummary,
|
|
operatorNotes,
|
|
reviewPacket,
|
|
journalItems,
|
|
weeklyReview,
|
|
}) {
|
|
window.__latestTrustScorePayload = trustScore;
|
|
renderHealth(health);
|
|
renderOverview(weeklyReview, tradeCandidates.items || [], executionTickets.items || [], tradingExecutions.items || []);
|
|
renderDecisionAssistant(reviewPacket?.decision_assistant_summary || null);
|
|
renderMarketState(marketState, tradingExecutions.items || []);
|
|
renderRuntimeStatus(runtimeStatus, runtimeIncidents, maintenanceSummary);
|
|
renderDecisionPacket(decisionPacket, decisionAlerts);
|
|
renderTrustScore(trustScore);
|
|
renderLiveReadiness(liveReadiness);
|
|
renderLaunchReadiness(launchReadiness);
|
|
renderSessionFocus(sessionView);
|
|
renderStrategyBacktests(strategyBacktests);
|
|
renderPreMarketPlan(tradingPlan);
|
|
renderCandidates(tradeCandidates.items || []);
|
|
renderTickets(executionTickets.items || []);
|
|
renderAlertHistory(decisionAlerts);
|
|
renderAlertQuality(alertQualitySummary, operatorDigest);
|
|
renderVisualReviews(visualReviews, decisionPacketId);
|
|
renderExecutions(tradingExecutions.items || []);
|
|
renderOperatorConsole(operatorNotes, reviewPacket);
|
|
renderPostTradeReview(journalSummary, journalItems.items || [], weeklyReview);
|
|
drawCandles(candles.items || []);
|
|
updateActiveFilters();
|
|
updateLastUpdated();
|
|
}
|
|
|
|
async function refreshDashboard() {
|
|
const instrumentId = instrumentIdInput.value || '1';
|
|
const timeframe = timeframeInput.value || '1m';
|
|
const limit = limitInput.value || '20';
|
|
const backtestInstrumentId = backtestInstrumentIdInput?.value || instrumentId;
|
|
const backtestLimit = backtestLimitInput?.value || '10';
|
|
const profileKey = assistantDigestProfileKey || assistantPreferenceDraft?.profile_key || 'local_operator';
|
|
|
|
try {
|
|
const [health, candles, tradingPlan, marketState, runtimeStatus, runtimeIncidents, maintenanceSummary, decisionPacket, decisionAlerts, alertQualitySummary, operatorDigest, trustScore, liveReadiness, launchReadiness, sessionView, strategyBacktests, tradeCandidates, executionTickets, tradingExecutions, journalSummary, operatorNotes, reviewPacket, journalItems, weeklyReview] = await Promise.all([
|
|
fetchJson('/health'),
|
|
fetchJson(`/candles?${queryString({ instrument_id: instrumentId, timeframe, limit })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/plans/latest?${queryString({ instrument_id: instrumentId })}`).catch(() => null),
|
|
fetchJson(`/trading/market-state?${queryString({ instrument_id: instrumentId, timeframe })}`).catch(() => ({ market_state: {} })),
|
|
fetchJson('/trading/runtime-status').catch(() => ({ scheduler_state: 'unavailable', tasks: {}, current_blockers: ['runtime_status_unavailable'] })),
|
|
fetchJson(`/trading/runtime-incidents?${queryString({ limit: 20, refresh: 0 })}`).catch(() => ({ status: 'unavailable', persisted: false, incident_count: 0, severity_counts: {}, source_counts: {}, recommended_actions: [], items: [] })),
|
|
fetchJson(`/trading/maintenance-summary?${queryString({ instrument_id: instrumentId, alert_limit: 50, visual_limit: 50, incident_limit: 50 })}`).catch(() => ({ status: 'unavailable', blockers: ['maintenance_summary_unavailable'], warnings: [], maintenance_actions: {}, p23_assertions: {} })),
|
|
fetchJson(`/trading/decision-packet?${queryString({ instrument_id: instrumentId })}`).catch(() => ({ status: 'unavailable', action_recommendation: 'monitor' })),
|
|
fetchJson(`/trading/alerts?${queryString({ instrument_id: instrumentId, limit: 10 })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/alerts/quality-summary?${queryString({ instrument_id: instrumentId, limit: 50, visual_limit: 50 })}`).catch(() => ({ recent_alert_count: 0, recent_visual_review_count: 0, delivery_summary: {}, alert_feedback_summary: {}, visual_feedback_summary: {}, visual_review_summary: {}, quality_flags: [], operator_summary: 'alert_quality_unavailable' })),
|
|
fetchJson(`/trading/alerts/operator-digest?${queryString({ instrument_id: instrumentId, limit: 50, visual_limit: 50, top_limit: 10 })}`).catch(() => ({ status: 'unavailable', summary_counts: {}, review_queue: [], operator_summary: 'operator_digest_unavailable' })),
|
|
fetchJson(`/trading/trust-score?${queryString({ instrument_id: instrumentId })}`).catch(() => ({ models: [], whitelist: [], observe_list: [], blacklist: [], next_actions: [] })),
|
|
fetchJson(`/trading/live-readiness?${queryString({ instrument_id: instrumentId, timeframe })}`).catch(() => ({ ready_for_real_live: false, blocking_reasons: ['live_readiness_unavailable'], checklist: [] })),
|
|
fetchJson(`/trading/launch-readiness?${queryString({ instrument_id: instrumentId, timeframe })}`).catch(() => ({ assisted_trading_ready: false, real_live_ready: false, blocking_reasons: ['launch_readiness_unavailable'], warning_reasons: [], checklist: [], next_actions: [] })),
|
|
fetchJson(`/trading/session-view?${queryString({ instrument_id: instrumentId, limit: 12 })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/backtests?${queryString({ instrument_id: backtestInstrumentId, limit: backtestLimit })}`).catch(() => ({ unavailable: true, items: [], latest: null })),
|
|
fetchJson(`/trading/candidates?${queryString({ instrument_id: instrumentId, limit })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/tickets?${queryString({ instrument_id: instrumentId, limit })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/executions?${queryString({ instrument_id: instrumentId, limit })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/journal/summary?${queryString({ instrument_id: instrumentId })}`).catch(() => ({ entry_count: 0, next_week_actions: [] })),
|
|
fetchJson(`/trading/operator-notes?${queryString({ instrument_id: instrumentId, limit: 10 })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/review-packet?${queryString({ instrument_id: instrumentId, profile_key: profileKey })}`).catch(() => ({ generated_ts: null, recent_executions: [], recent_alerts: [], recent_notes: [] })),
|
|
fetchJson(`/trading/journal?${queryString({ instrument_id: instrumentId, limit })}`).catch(() => ({ items: [] })),
|
|
fetchJson(`/trading/research/weekly-review?${queryString({ instrument_id: instrumentId })}`).catch(() => ({ entry_count: 0, next_week_actions: [] })),
|
|
]);
|
|
const decisionPacketId = decisionPacket?.id || reviewPacket?.decision_packet?.id || null;
|
|
const visualReviews = decisionPacketId
|
|
? await fetchJson(`/trading/visual-watch/reviews?${queryString({ decision_packet_id: decisionPacketId, limit: 10 })}`).catch(() => ({ items: [] }))
|
|
: ({ items: [] });
|
|
|
|
renderDashboardData({
|
|
health,
|
|
candles,
|
|
tradingPlan,
|
|
marketState,
|
|
runtimeStatus,
|
|
runtimeIncidents,
|
|
maintenanceSummary,
|
|
decisionPacket,
|
|
decisionAlerts,
|
|
alertQualitySummary,
|
|
operatorDigest,
|
|
visualReviews,
|
|
decisionPacketId,
|
|
trustScore,
|
|
liveReadiness,
|
|
launchReadiness,
|
|
sessionView,
|
|
strategyBacktests,
|
|
tradeCandidates,
|
|
executionTickets,
|
|
tradingExecutions,
|
|
journalSummary,
|
|
operatorNotes,
|
|
reviewPacket,
|
|
journalItems,
|
|
weeklyReview,
|
|
});
|
|
} catch (error) {
|
|
showErrorPanel(String(error));
|
|
}
|
|
}
|
|
|
|
function configureAutoRefresh() {
|
|
if (autoRefreshTimer) {
|
|
clearInterval(autoRefreshTimer);
|
|
autoRefreshTimer = null;
|
|
}
|
|
const seconds = Number(autoRefreshSecondsInput.value || 0);
|
|
if (seconds > 0) {
|
|
autoRefreshTimer = setInterval(refreshDashboard, seconds * 1000);
|
|
}
|
|
}
|
|
|
|
refreshBtn.addEventListener('click', refreshDashboard);
|
|
backtestRefreshBtn?.addEventListener('click', refreshDashboard);
|
|
backtestInstrumentIdInput?.addEventListener('change', refreshDashboard);
|
|
backtestLimitInput?.addEventListener('change', refreshDashboard);
|
|
executionStatusFilterInput.addEventListener('change', refreshDashboard);
|
|
sessionFilterInput.addEventListener('change', refreshDashboard);
|
|
searchTextInput.addEventListener('input', refreshDashboard);
|
|
trustClassFilterInput.addEventListener('change', refreshDashboard);
|
|
autoRefreshSecondsInput.addEventListener('change', configureAutoRefresh);
|
|
|
|
initializeWorkbenchNavigation();
|
|
configureAutoRefresh();
|
|
refreshDashboard();
|