Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions tests/console_runtime_state_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -264,14 +264,23 @@ for (const scenario of ['ready','stale','missing','wrong-platform','duplicate-so
});
}

for (const pending of [false,true]) {
test(`human decision panel appears only when needed: ${pending}`, () => {
for (const scenario of [
{name:'no visible candidate',allowed:true,candidates:[],pending:false,visible:false},
{name:'human decision',allowed:true,candidates:[{}],pending:true,visible:true},
{name:'forward-only signed out',allowed:false,candidates:[{forward_observation:{}}],pending:false,visible:false},
{name:'forward-only signed in',allowed:true,candidates:[{forward_observation:{}}],pending:false,visible:true},
]) {
test(`control-plane visibility: ${scenario.name}`, () => {
const nodes=Object.fromEntries(['switch-view','health-view','control-plane-view'].map(id=>[id,{hidden:false}]));
const fn=frontendFunction('renderConsoleView',{el:id=>nodes[id],state:{auth:{allowed:true},controlPlane:{payload:{candidates:pending?[{}]:[]}}},candidateNeedsOperatorAction:()=>pending});
const fn=frontendFunction('renderConsoleView',{
el:id=>nodes[id],
state:{auth:{allowed:scenario.allowed},controlPlane:{payload:{candidates:scenario.candidates}}},
candidateIsControlPlaneVisible:item=>scenario.pending||Boolean(item?.forward_observation),
});
fn();
assert.equal(nodes['switch-view'].hidden,false);
assert.equal(nodes['health-view'].hidden,true);
assert.equal(nodes['control-plane-view'].hidden,!pending);
assert.equal(nodes['control-plane-view'].hidden,!scenario.visible);
});
}

Expand Down
112 changes: 112 additions & 0 deletions tests/strategy_switch_worker_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ assert.equal(indexHtml.includes('id="health-view-button"'), false);
assert.match(indexHtml, /<details class="health-view advanced-workspace" id="health-view" hidden>/);
assert.ok(indexHtml.includes('id="control-plane-view"'));
assert.ok(indexHtml.includes('id="control-plane-list"'));
assert.ok(indexHtml.includes('function forwardObservationDisplayText('));
assert.ok(indexHtml.includes('function candidateIsControlPlaneVisible('));
assert.ok(indexHtml.includes('function renderControlPlaneHeading('));
assert.ok(indexHtml.includes('观察进度 {completed} / {required} 个交易日'));
assert.ok(indexHtml.includes('No-order observation progress: {completed} / {required} trading days'));
assert.ok(indexHtml.includes('自动观察的最新记录,无需操作。'));
assert.ok(indexHtml.includes('Latest automated observation. No action is needed.'));
assert.ok(indexHtml.includes('id="m0-research-notice"'));
assert.ok(indexHtml.includes('id="m0-research-list"'));
assert.match(indexHtml, /<details class="diagnostic-details">\s*<summary data-i18n="diagnosticDetails">/);
Expand Down Expand Up @@ -302,6 +309,9 @@ const servedAppJs = await servedAppResponse.text();
assert.equal(servedAppResponse.status, 200);
assert.equal(servedAppResponse.headers.get("Cache-Control"), "no-store");
assert.ok(servedAppJs.includes("function hasPrivateConfig()"));
assert.ok(servedAppJs.includes("function forwardObservationDisplayText("));
assert.ok(servedAppJs.includes("function candidateIsControlPlaneVisible("));
assert.ok(servedAppJs.includes("function renderControlPlaneHeading("));
assert.equal(servedAppJs.includes("ibitZscoreExit"), false);
assert.equal(servedAppJs.includes("ibit_zscore_exit_mode"), false);
assert.equal(servedAppJs.includes("ibkr-primary"), false);
Expand Down Expand Up @@ -2358,6 +2368,108 @@ assert.deepEqual(sourceControlPayload.summary, { candidate_count: 1, deferred: 0
assert.deepEqual(sourceControlPayload.attention, { status: "research_only", reason_codes: [] });
assert.equal(sourceControlPayload.candidates[0].candidate_id, "tqqq_core_only_p2_v5");

const forwardControlStore = new Map();
const forwardControlEnv = {
...controlEnv,
STRATEGY_SWITCH_CONFIG: {
async get(key) { return forwardControlStore.get(key) || null; },
async put(key, value) { forwardControlStore.set(key, value); },
async list({ prefix = "", limit = 1000 } = {}) {
return {
keys: [...forwardControlStore.keys()]
.filter((key) => key.startsWith(prefix))
.slice(0, limit)
.map((name) => ({ name })),
};
},
},
};
const forwardControlCookie = await __test.makeSession("health-user", [], forwardControlEnv);
const forwardControlHeaders = { Cookie: `qsl_switch_session=${forwardControlCookie}` };
const forwardCandidate = {
...controlSourcePayload.candidates[0],
candidate_id: "soxl_soxx_core_only_p2_v7_longterm_compounding_cash_reserve",
lifecycle: { stage: "P4", status: "shadow" },
forward_observation: {
state: "FORWARD_ACTIVE",
observations_completed: 8,
required_trading_sessions: 252,
last_observed_session: "2026-09-08",
observed_at: controlNow,
no_order: true,
live_authority_granted: false,
},
};
const forwardControlSource = {
...controlSourcePayload,
source_id: "uesp.soxl_v7_forward",
candidates: [forwardCandidate],
};
const forwardControlSync = await worker.fetch(
new Request("https://switch.example/api/internal/sync-control-plane-source", {
method: "POST",
headers: { Authorization: `Bearer ${controlSyncValue}`, "Content-Type": "application/json" },
body: JSON.stringify(forwardControlSource),
}),
forwardControlEnv,
);
assert.equal(forwardControlSync.status, 200);
const forwardControlRead = await worker.fetch(
new Request("https://switch.example/api/control-plane", { headers: forwardControlHeaders }),
forwardControlEnv,
);
const forwardControlPayload = await forwardControlRead.json();
assert.deepEqual(forwardControlPayload.candidates[0].forward_observation, forwardCandidate.forward_observation);

for (const [index, invalidForwardObservation] of [
{ ...forwardCandidate.forward_observation, observations_completed: 253 },
{ ...forwardCandidate.forward_observation, no_order: false },
{ ...forwardCandidate.forward_observation, live_authority_granted: true },
{ ...forwardCandidate.forward_observation, observed_at: null },
{ ...forwardCandidate.forward_observation, observed_at: "not-a-timestamp" },
{ ...forwardCandidate.forward_observation, last_observed_session: "2026-02-30" },
{ ...forwardCandidate.forward_observation, unexpected: "field" },
].entries()) {
const invalidForwardSync = await worker.fetch(
new Request("https://switch.example/api/internal/sync-control-plane-source", {
method: "POST",
headers: { Authorization: `Bearer ${controlSyncValue}`, "Content-Type": "application/json" },
body: JSON.stringify({
...forwardControlSource,
source_id: `uesp.soxl_v7_invalid_${index}`,
candidates: [{ ...forwardCandidate, forward_observation: invalidForwardObservation }],
}),
}),
forwardControlEnv,
);
assert.equal(invalidForwardSync.status, 400);
}
const p6ForwardSync = await worker.fetch(
new Request("https://switch.example/api/internal/sync-control-plane-source", {
method: "POST",
headers: { Authorization: `Bearer ${controlSyncValue}`, "Content-Type": "application/json" },
body: JSON.stringify({
...forwardControlSource,
source_id: "uesp.soxl_v7_p6_rejected",
candidates: [{
...forwardCandidate,
lifecycle: { stage: "P6", status: "owner_decision_required" },
recommendation: { code: "owner_live_decision", reason: "must not carry forward observation" },
}],
}),
}),
forwardControlEnv,
);
assert.equal(p6ForwardSync.status, 400);
assert.equal(
__test.normalizeControlPlaneSourceSnapshot({
...forwardControlSource,
source_id: "uesp.legacy_candidate",
candidates: [{ ...forwardCandidate, forward_observation: undefined }],
}).candidates[0].forward_observation,
undefined,
);

const adaptiveSelectionSyncValue = ["adaptive", "selection", "sync"].join("-");
const adaptiveSelectionEnv = { ...controlEnv, ADAPTIVE_SELECTION_SYNC_TOKEN: adaptiveSelectionSyncValue };
const adaptiveSelectionCookie = await __test.makeSession("health-user", [], adaptiveSelectionEnv);
Expand Down
84 changes: 78 additions & 6 deletions web/strategy-switch-console/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,19 @@
controlPlaneEyebrow: "待处理事项",
controlPlaneTitle: "需要你确认",
controlPlaneSubtitle: "这里只显示需要你亲自确认的事项。",
controlPlaneProgressTitle: "观察进度",
controlPlaneProgressSubtitle: "自动观察的最新记录,无需操作。",
controlPlaneMixedTitle: "研究进度与待办",
controlPlaneMixedSubtitle: "自动观察与需要确认的事项。",
controlCandidateTotal: "监控对象",
controlDeferred: "待复核",
controlParked: "暂停中",
controlOwnerDecision: "待处理",
controlQueueEyebrow: "优先处理",
controlQueueHint: "有风险或需要确认时才出现",
controlCandidateBoard: "需要你处理",
controlProgressBoard: "观察中的策略",
controlMixedBoard: "研究进度与待办",
controlDataReady: "已更新",
controlDataStale: "更新延迟",
controlDataUnavailable: "暂时无法读取",
Expand All @@ -246,6 +252,10 @@
controlEmptyCandidates: "当前没有待处理事项。",
controlNoRecommendation: "暂未给出处理建议。",
controlItemMeta: "{kind} · {domain} · 最近更新:{freshness}",
forwardObservationProgress: "观察进度 {completed} / {required} 个交易日 · 最近观察 {session} · 不下单",
forwardObservationActive: "继续观察",
forwardObservationPaused: "保持暂停",
forwardObservationComplete: "观察完成,等待复核",
controlNext: "处理建议",
controlStatus: "当前状态",
ownerDecisionTitle: "请选择下一步",
Expand Down Expand Up @@ -750,13 +760,19 @@
controlPlaneEyebrow: "To do",
controlPlaneTitle: "Your decision needed",
controlPlaneSubtitle: "Only items that need your confirmation appear here.",
controlPlaneProgressTitle: "Observation progress",
controlPlaneProgressSubtitle: "Latest automated observation. No action is needed.",
controlPlaneMixedTitle: "Research progress & tasks",
controlPlaneMixedSubtitle: "Automated observations and items needing your confirmation.",
controlCandidateTotal: "Monitored items",
controlDeferred: "To review",
controlParked: "Paused",
controlOwnerDecision: "To do",
controlQueueEyebrow: "Priority",
controlQueueHint: "Only appears when action is needed",
controlCandidateBoard: "Needs your attention",
controlProgressBoard: "Strategies under observation",
controlMixedBoard: "Research progress & tasks",
controlDataReady: "Up to date",
controlDataStale: "Update delayed",
controlDataUnavailable: "Unavailable",
Expand All @@ -777,6 +793,10 @@
controlEmptyCandidates: "There is nothing to handle right now.",
controlNoRecommendation: "No action is recommended yet.",
controlItemMeta: "{kind} · {domain} · updated {freshness}",
forwardObservationProgress: "No-order observation progress: {completed} / {required} trading days · last observed {session}",
forwardObservationActive: "Keep monitoring",
forwardObservationPaused: "Keep paused",
forwardObservationComplete: "Observation complete — review",
controlNext: "Recommended action",
controlStatus: "Current status",
ownerDecisionTitle: "Choose the next step",
Expand Down Expand Up @@ -4104,6 +4124,43 @@
|| recommendation === "owner_live_decision";
}

function candidateIsControlPlaneVisible(item) {
return candidateNeedsOperatorAction(item) || Boolean(item?.forward_observation);
}

function forwardObservationDisplayText(observation) {
if (!observation || typeof observation !== "object") return "";
return t("forwardObservationProgress")
.replace("{completed}", String(observation.observations_completed ?? "—"))
.replace("{required}", String(observation.required_trading_sessions ?? "—"))
.replace("{session}", String(observation.last_observed_session || "—"));
}

function forwardObservationActionText(observation) {
if (observation?.state === "FORWARD_COMPLETE_HUMAN_REVIEW") return t("forwardObservationComplete");
if (observation?.state === "FORWARD_ACTIVE") return t("forwardObservationActive");
return t("forwardObservationPaused");
}

function renderControlPlaneHeading({ hasActionable, hasForwardObservation }) {
const title = el("control-plane-view-title");
const subtitle = title?.nextElementSibling;
const board = el("control-queue-title");
if (hasActionable && hasForwardObservation) {
title.textContent = t("controlPlaneMixedTitle");
subtitle.textContent = t("controlPlaneMixedSubtitle");
board.textContent = t("controlMixedBoard");
} else if (hasForwardObservation) {
title.textContent = t("controlPlaneProgressTitle");
subtitle.textContent = t("controlPlaneProgressSubtitle");
board.textContent = t("controlProgressBoard");
} else {
title.textContent = t("controlPlaneTitle");
subtitle.textContent = t("controlPlaneSubtitle");
board.textContent = t("controlCandidateBoard");
}
}

function renderControlPlane() {
const payload = state.controlPlane.payload;
const summary = payload.summary || {};
Expand All @@ -4121,9 +4178,14 @@
const notice = el("control-plane-notice");
const statePanel = notice.closest(".decision-state");
const actionableCandidates = payload.candidates.filter(candidateNeedsOperatorAction);
el("control-plane-view").hidden = !state.auth.allowed || !actionableCandidates.length;
const displayedCandidates = payload.candidates.filter(candidateIsControlPlaneVisible);
renderControlPlaneHeading({
hasActionable: actionableCandidates.length > 0,
hasForwardObservation: displayedCandidates.some((item) => item.forward_observation),
});
el("control-plane-view").hidden = !state.auth.allowed || !displayedCandidates.length;
const queue = el("control-plane-queue");
queue.hidden = !actionableCandidates.length;
queue.hidden = !displayedCandidates.length;
statePanel.classList.toggle("is-attention", actionableCandidates.length > 0);
statePanel.classList.toggle("is-stale", payload.data_status === "stale");
statePanel.classList.toggle("is-unavailable", !state.auth.allowed || payload.data_status === "unavailable");
Expand Down Expand Up @@ -4154,7 +4216,7 @@

const list = el("control-plane-list");
list.replaceChildren();
for (const item of actionableCandidates) {
for (const item of displayedCandidates) {
const card = document.createElement("article");
card.className = "health-card";
const main = document.createElement("div");
Expand All @@ -4167,20 +4229,30 @@
title.textContent = String(item.candidate_id || "unknown");
const reason = document.createElement("p");
reason.className = "health-card__reason";
reason.textContent = t("controlAttentionSummary");
reason.textContent = item.forward_observation
? forwardObservationActionText(item.forward_observation)
: t("controlAttentionSummary");
const detail = document.createElement("div");
detail.className = "health-card__meta";
detail.textContent = t("controlItemMeta")
.replace("{kind}", operatorLabel("status", item.lifecycle?.status))
.replace("{domain}", domainLabel(item.domain || ""))
.replace("{freshness}", operatorLabel("freshness", item.freshness?.status || "unknown"));
main.append(meta, title, reason, detail);
if (item.forward_observation) {
const observation = document.createElement("div");
observation.className = "health-card__meta";
observation.textContent = forwardObservationDisplayText(item.forward_observation);
main.appendChild(observation);
}
const stateBlock = document.createElement("div");
stateBlock.className = "health-card__score";
const label = document.createElement("small");
label.textContent = t("controlNext");
const stage = document.createElement("strong");
stage.textContent = operatorLabel("action", item.recommendation?.code || "none");
stage.textContent = item.forward_observation
? forwardObservationActionText(item.forward_observation)
: operatorLabel("action", item.recommendation?.code || "none");
const recommendation = document.createElement("small");
recommendation.textContent = `${t("controlStatus")}:${operatorLabel("status", item.lifecycle?.status)}`;
stateBlock.append(label, stage, recommendation);
Expand Down Expand Up @@ -5236,7 +5308,7 @@
el("switch-view").hidden = false;
el("health-view").hidden = true;
el("control-plane-view").hidden = !state.auth.allowed
|| !state.controlPlane.payload.candidates.some(candidateNeedsOperatorAction);
|| !state.controlPlane.payload.candidates.some(candidateIsControlPlaneVisible);
}

function render() {
Expand Down
2 changes: 1 addition & 1 deletion web/strategy-switch-console/app_js.js

Large diffs are not rendered by default.

Loading