d94049769e
Focuses now carry asked_turn_id so GET rebuilds stem and options on the same turn. Agent writes spokenPrompt; the live question slot is gone. Co-authored-by: Cursor <cursoragent@cursor.com>
605 lines
21 KiB
TypeScript
605 lines
21 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import { buildCandidateContrastPacket } from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
|
import { decideRectification } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
|
|
import {
|
|
USER_COLLECT_QUESTION,
|
|
USER_COLLECT_QUESTION_RETRY,
|
|
} from "../src/lib/rectification-agentic/user-copy.ts";
|
|
import { isStalePreAdoptFocus } from "../src/lib/rectification-agentic/v9/answer-choice.ts";
|
|
import {
|
|
buildChoiceFrame,
|
|
parseRectificationChoiceCard,
|
|
serverOwnedChoiceCopy,
|
|
} from "../src/lib/rectification-agentic/v9/choice-card.ts";
|
|
import { choiceCardFromCaseDossier } from "../src/lib/rectification-agentic/v9/interview-state.ts";
|
|
import {
|
|
buildMethodFollowupPlan,
|
|
buildNextUserAction,
|
|
spokenFollowupForUser,
|
|
type MethodFollowup,
|
|
} from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
|
import { completeStyleOptions } from "../src/lib/rectification-agentic/v9/probe-question-contract.ts";
|
|
import {
|
|
openQuestionFromPersistedFocus,
|
|
persistServerOwnedFocus,
|
|
} from "../src/lib/rectification-agentic/v9/server-focus.ts";
|
|
import { projectCurrentQuestion } from "../src/lib/rectification-agentic/v9/turn-decision.ts";
|
|
import { d9StyleLabel } from "../src/lib/rectification-agentic/v9/varga-type-tables.ts";
|
|
import {
|
|
CASE_ID,
|
|
FOCUS_ID,
|
|
OPEN_ENGINE_CAPABILITY_CEILING,
|
|
USER_ID,
|
|
fakeAccounting,
|
|
} from "./rectification-v9-test-support.ts";
|
|
|
|
const D9_QUESTION = "亲密关系里,你更接近哪一种相处方式?";
|
|
|
|
const WALKTHROUGH_EVIDENCE = [
|
|
{
|
|
status: "confirmed" as const,
|
|
domain: "education",
|
|
datePrecision: "year" as const,
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
eventKind: "education_start",
|
|
},
|
|
{
|
|
status: "confirmed" as const,
|
|
domain: "relationship",
|
|
datePrecision: "year" as const,
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
eventKind: "relationship_start",
|
|
},
|
|
{
|
|
status: "confirmed" as const,
|
|
domain: "career",
|
|
datePrecision: "year" as const,
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
eventKind: "career_entry",
|
|
},
|
|
{
|
|
status: "confirmed" as const,
|
|
domain: "family",
|
|
datePrecision: "year" as const,
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
eventKind: "family_event",
|
|
},
|
|
];
|
|
|
|
function d9WalkthroughPacket() {
|
|
return buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: ["05:00", "05:04"],
|
|
transitions: [
|
|
{ layer: "d9", at: "05:04", from_sign: "巨蟹座", to_sign: "狮子座" },
|
|
],
|
|
});
|
|
}
|
|
|
|
function d9StyleOptions() {
|
|
const completed = completeStyleOptions({
|
|
choiceKind: "varga_style",
|
|
styleOptions: [
|
|
{ sign: "巨蟹座", label: d9StyleLabel("巨蟹座"), answer_class: "yes" },
|
|
{ sign: "狮子座", label: d9StyleLabel("狮子座"), answer_class: "weak_yes" },
|
|
],
|
|
});
|
|
assert.equal(completed.ok, true);
|
|
if (!completed.ok) throw new Error("D9 style options");
|
|
return completed.options;
|
|
}
|
|
|
|
function d9Followup(frame: NonNullable<ReturnType<typeof buildChoiceFrame>>): MethodFollowup {
|
|
const probe = d9WalkthroughPacket().probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
|
assert.ok(probe);
|
|
return {
|
|
method_id: "d9_relationship",
|
|
intent: "distinguish_candidates",
|
|
ask_theme: "relationship_style",
|
|
domain: "relationship",
|
|
kind_hint: "relationship_change",
|
|
user_prompt_hint: probe.question,
|
|
must_not_label: false,
|
|
choice_frame: frame,
|
|
source: "event_probe",
|
|
information_gain: probe.informationGain,
|
|
semantic_key: probe.semanticKey,
|
|
candidate_split_hash: probe.candidateSplitHash,
|
|
choice_kind: "varga_style",
|
|
candidate_ids: ["05:00", "05:04"],
|
|
expected_outcomes: probe.expectedOutcomes.map((row) => ({
|
|
answer_class: row.outcomeId,
|
|
supports: row.supportsCandidateIds,
|
|
conflicts: row.conflictsCandidateIds,
|
|
})),
|
|
style_options: d9StyleOptions(),
|
|
};
|
|
}
|
|
|
|
function focusFromArgs(args: Record<string, unknown>) {
|
|
return {
|
|
id: FOCUS_ID,
|
|
caseId: CASE_ID,
|
|
questionId: String(args.p_question_id ?? ""),
|
|
intent: String(args.p_intent ?? ""),
|
|
targetEvidenceId: null,
|
|
targetDomain: typeof args.p_target_domain === "string" ? args.p_target_domain : null,
|
|
targetKind: null,
|
|
expectedAnswerSchema: (args.p_expected_answer_schema ?? {}) as Record<string, unknown>,
|
|
status: "active" as const,
|
|
askedAt: "2026-09-02T00:00:00.000Z",
|
|
resolvedAt: null,
|
|
};
|
|
}
|
|
|
|
function persistAccounting() {
|
|
return fakeAccounting({
|
|
set_agentic_rectification_conversation_focus: (_fn, args) => ({
|
|
id: FOCUS_ID,
|
|
case_id: CASE_ID,
|
|
question_id: args.p_question_id,
|
|
intent: args.p_intent,
|
|
target_evidence_id: null,
|
|
target_domain: args.p_target_domain,
|
|
target_kind: args.p_target_kind,
|
|
expected_answer_schema: args.p_expected_answer_schema,
|
|
status: "active",
|
|
asked_at: "2026-09-02T00:00:00.000Z",
|
|
resolved_at: null,
|
|
idempotent: false,
|
|
}),
|
|
});
|
|
}
|
|
|
|
function assertChoiceInvariants(input: {
|
|
card: ReturnType<typeof parseRectificationChoiceCard> | ReturnType<typeof choiceCardFromCaseDossier>;
|
|
question: ReturnType<typeof projectCurrentQuestion>;
|
|
}) {
|
|
if (input.card) {
|
|
assert.ok(input.card.prompt?.trim(), "choice_card nonempty ⇒ prompt nonempty");
|
|
}
|
|
if (input.question?.kind === "choice") {
|
|
assert.ok(input.question.prompt?.trim(), "active choice focus ⇒ projectCurrentQuestion prompt nonempty");
|
|
}
|
|
}
|
|
|
|
test("walkthrough D9 probe question is in the 4-80 window and must survive frame assembly", () => {
|
|
const packet = d9WalkthroughPacket();
|
|
const probe = packet.probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
|
assert.ok(probe);
|
|
assert.equal(probe.question, D9_QUESTION);
|
|
assert.ok(probe.question.length >= 4 && probe.question.length <= 80);
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
contrastPacket: packet,
|
|
sessionOutcome: "discriminate_candidates",
|
|
topCandidateTimes: ["05:00", "05:04"],
|
|
candidatesSeparated: false,
|
|
});
|
|
const followup = plan.next_followup;
|
|
assert.equal(followup?.intent, "distinguish_candidates", JSON.stringify({
|
|
intent: followup?.intent,
|
|
source: followup?.source,
|
|
method_id: followup?.method_id,
|
|
dropped: plan.dropped_probes,
|
|
}));
|
|
assert.equal(followup?.method_id, "d9_relationship");
|
|
const frame = followup?.choice_frame ?? null;
|
|
assert.ok(frame, "D9 probe must assemble a choice_frame");
|
|
assert.equal(frame?.prompt, D9_QUESTION);
|
|
const copy = frame ? serverOwnedChoiceCopy(frame) : null;
|
|
assert.ok(copy?.prompt, "assembled D9 copy must keep the probe question");
|
|
});
|
|
|
|
test("D9 probe shape with unwired prompt makes the choice invariants red", async () => {
|
|
const packet = d9WalkthroughPacket();
|
|
const probe = packet.probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
|
assert.ok(probe);
|
|
const wired = buildChoiceFrame({
|
|
method_id: "d9_relationship",
|
|
ask_theme: "relationship_style",
|
|
domain: "relationship",
|
|
user_prompt_hint: probe.question,
|
|
choice_kind: "varga_style",
|
|
semantic_key: probe.semanticKey,
|
|
style_options: d9StyleOptions(),
|
|
}, {
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
probes: [{
|
|
year: 0,
|
|
year_label: "当前这几个候选",
|
|
domain: "relationship",
|
|
event_family: "相处方式更接近其中一种",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: probe.question,
|
|
role: "distinguish",
|
|
information_gain: probe.informationGain,
|
|
semantic_key: probe.semanticKey,
|
|
candidate_split_hash: probe.candidateSplitHash,
|
|
candidate_ids: ["05:00", "05:04"],
|
|
expected_outcomes: probe.expectedOutcomes.map((row) => ({
|
|
answer_class: row.outcomeId,
|
|
supports: row.supportsCandidateIds,
|
|
conflicts: row.conflictsCandidateIds,
|
|
})),
|
|
choice_kind: "varga_style",
|
|
style_options: d9StyleOptions(),
|
|
}],
|
|
});
|
|
assert.ok(wired);
|
|
const unwired = { ...wired, prompt: "", period: "" };
|
|
assert.equal(serverOwnedChoiceCopy(unwired), null);
|
|
|
|
const accounting = persistAccounting();
|
|
const persisted = await persistServerOwnedFocus({
|
|
accounting: accounting.client,
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
activeFocus: null,
|
|
decisionReceipt: null,
|
|
followup: d9Followup(unwired),
|
|
});
|
|
const open = openQuestionFromPersistedFocus(persisted);
|
|
const question = persisted.focus
|
|
? projectCurrentQuestion(persisted.focus)
|
|
: null;
|
|
const card = persisted.focus
|
|
? choiceCardFromCaseDossier({
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
conversationSummary: {
|
|
activeFocus: persisted.focus,
|
|
declinedSkippedTopics: [],
|
|
},
|
|
latestResult: {
|
|
decisionReceipt: null,
|
|
selectionAllowed: false,
|
|
confirmationAllowed: false,
|
|
candidates: [
|
|
{ time: "05:00", rank: 1, relativeSupport: 0.6 },
|
|
{ time: "05:04", rank: 2, relativeSupport: 0.4 },
|
|
],
|
|
},
|
|
case: { acceptedTime: null, status: "collecting_evidence" },
|
|
})
|
|
: null;
|
|
|
|
assertChoiceInvariants({ card, question });
|
|
assert.ok(persisted.status === "created" || persisted.status === "already_open");
|
|
assert.equal(open?.kind, "collect_spoken");
|
|
assert.ok(open?.prompt?.trim());
|
|
assert.notEqual(question?.kind, "choice");
|
|
});
|
|
|
|
test("agent persistPlanFocus path must fail-closed when D9 choice copy is null", () => {
|
|
const tools = readFileSync(new URL("../src/mastra/rectification-v9-tools.ts", import.meta.url), "utf8");
|
|
const persistPlan = tools.slice(tools.indexOf("const persistPlanFocus"));
|
|
assert.match(persistPlan, /spokenCollectFallbackFollowup/);
|
|
});
|
|
|
|
test("wired D9 persist keeps a nonempty choice prompt", async () => {
|
|
const packet = d9WalkthroughPacket();
|
|
const probe = packet.probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
|
assert.ok(probe);
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d9_relationship",
|
|
ask_theme: "relationship_style",
|
|
domain: "relationship",
|
|
user_prompt_hint: probe.question,
|
|
choice_kind: "varga_style",
|
|
semantic_key: probe.semanticKey,
|
|
style_options: d9StyleOptions(),
|
|
}, {
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
probes: [{
|
|
year: 0,
|
|
year_label: "当前这几个候选",
|
|
domain: "relationship",
|
|
event_family: "相处方式更接近其中一种",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: probe.question,
|
|
role: "distinguish",
|
|
information_gain: probe.informationGain,
|
|
semantic_key: probe.semanticKey,
|
|
candidate_split_hash: probe.candidateSplitHash,
|
|
candidate_ids: ["05:00", "05:04"],
|
|
expected_outcomes: probe.expectedOutcomes.map((row) => ({
|
|
answer_class: row.outcomeId,
|
|
supports: row.supportsCandidateIds,
|
|
conflicts: row.conflictsCandidateIds,
|
|
})),
|
|
choice_kind: "varga_style",
|
|
style_options: d9StyleOptions(),
|
|
}],
|
|
});
|
|
assert.ok(frame);
|
|
const persisted = await persistServerOwnedFocus({
|
|
accounting: persistAccounting().client,
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
activeFocus: null,
|
|
decisionReceipt: null,
|
|
followup: d9Followup(frame),
|
|
});
|
|
const open = openQuestionFromPersistedFocus(persisted);
|
|
const question = persisted.focus ? projectCurrentQuestion(persisted.focus) : null;
|
|
const card = parseRectificationChoiceCard({
|
|
question_id: persisted.focus?.questionId,
|
|
method_id: "d9_relationship",
|
|
prompt: persisted.prompt,
|
|
choice_mode: "A/B/C/D",
|
|
options: [
|
|
{ key: "A", label: d9StyleOptions()[0]!.label, answer_class: "yes", role: "primary" },
|
|
{ key: "B", label: d9StyleOptions()[1]!.label, answer_class: "weak_yes", role: "primary" },
|
|
{ key: "C", label: d9StyleOptions()[2]!.label, answer_class: "no", role: "primary" },
|
|
{ key: "D", label: d9StyleOptions()[3]!.label, answer_class: "unsure", role: "primary" },
|
|
],
|
|
stop_label: "先这样,先看当前范围",
|
|
stop_message: "先这样",
|
|
scoring: true,
|
|
focus_id: FOCUS_ID,
|
|
});
|
|
assert.equal(persisted.status, "created");
|
|
assert.equal(open?.kind, "choice");
|
|
assert.equal(open?.prompt, D9_QUESTION);
|
|
assertChoiceInvariants({ card, question });
|
|
});
|
|
|
|
test("unwired D9 persist falls back to spoken collect instead of an empty-prompt card", async () => {
|
|
const packet = d9WalkthroughPacket();
|
|
const probe = packet.probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
|
assert.ok(probe);
|
|
const wired = buildChoiceFrame({
|
|
method_id: "d9_relationship",
|
|
ask_theme: "relationship_style",
|
|
domain: "relationship",
|
|
user_prompt_hint: probe.question,
|
|
choice_kind: "varga_style",
|
|
semantic_key: probe.semanticKey,
|
|
style_options: d9StyleOptions(),
|
|
}, {
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
probes: [{
|
|
year: 0,
|
|
year_label: "当前这几个候选",
|
|
domain: "relationship",
|
|
event_family: "相处方式更接近其中一种",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: probe.question,
|
|
role: "distinguish",
|
|
information_gain: probe.informationGain,
|
|
semantic_key: probe.semanticKey,
|
|
candidate_split_hash: probe.candidateSplitHash,
|
|
candidate_ids: ["05:00", "05:04"],
|
|
expected_outcomes: probe.expectedOutcomes.map((row) => ({
|
|
answer_class: row.outcomeId,
|
|
supports: row.supportsCandidateIds,
|
|
conflicts: row.conflictsCandidateIds,
|
|
})),
|
|
choice_kind: "varga_style",
|
|
style_options: d9StyleOptions(),
|
|
}],
|
|
});
|
|
assert.ok(wired);
|
|
const persisted = await persistServerOwnedFocus({
|
|
accounting: persistAccounting().client,
|
|
userId: USER_ID,
|
|
caseId: CASE_ID,
|
|
activeFocus: null,
|
|
decisionReceipt: null,
|
|
followup: d9Followup({ ...wired, prompt: "", period: "" }),
|
|
});
|
|
const open = openQuestionFromPersistedFocus(persisted);
|
|
const question = persisted.focus ? projectCurrentQuestion(persisted.focus) : null;
|
|
assert.ok(persisted.status === "created" || persisted.status === "already_open");
|
|
assert.equal(open?.kind, "collect_spoken");
|
|
assert.ok(open?.prompt?.trim());
|
|
assert.notEqual(question?.kind, "choice");
|
|
assertChoiceInvariants({ card: null, question });
|
|
});
|
|
|
|
test("agent instructions do not assert that the next question is already on screen", () => {
|
|
const agent = readFileSync(new URL("../src/mastra/agentic-rectification.ts", import.meta.url), "utf8");
|
|
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
|
|
assert.match(agent, /正文不得断言界面当前状态/);
|
|
assert.match(agent, /接下来我们继续/);
|
|
assert.doesNotMatch(agent, /自然过渡到界面上的下一步/);
|
|
assert.match(chat, /await loadCaseSnapshot\(\)/);
|
|
assert.match(chat, /mergeTurnQuestions/);
|
|
assert.match(chat, /afterAnswer=\{afterAnswer\}/);
|
|
assert.doesNotMatch(chat, /isLatestMessage/);
|
|
});
|
|
|
|
test("same-domain collect retry uses structured closed-focus state, not body matching", () => {
|
|
const base = {
|
|
method_id: "d9_relationship" as const,
|
|
intent: "collect_method_evidence",
|
|
ask_theme: "relationship_style" as const,
|
|
domain: "relationship",
|
|
kind_hint: "relationship_start",
|
|
user_prompt_hint: "ask",
|
|
must_not_label: false as const,
|
|
choice_frame: null,
|
|
source: "method_coverage" as const,
|
|
};
|
|
const first = spokenFollowupForUser(base);
|
|
const retry = spokenFollowupForUser({ ...base, collect_retry: true });
|
|
assert.equal(first, USER_COLLECT_QUESTION.relationship);
|
|
assert.equal(retry, USER_COLLECT_QUESTION_RETRY.relationship);
|
|
assert.notEqual(first, retry);
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
eventKind: "career_entry",
|
|
}],
|
|
closedCollectFocuses: [{
|
|
target_domain: "relationship",
|
|
intent: "collect_method_evidence",
|
|
status: "resolved",
|
|
question_id: "collect:relationship:collect_method_evidence",
|
|
}],
|
|
sessionOutcome: "collect_evidence",
|
|
});
|
|
assert.equal(plan.next_followup?.domain, "relationship");
|
|
assert.equal(plan.next_followup?.collect_retry, true);
|
|
assert.equal(spokenFollowupForUser(plan.next_followup), USER_COLLECT_QUESTION_RETRY.relationship);
|
|
const declined = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
eventKind: "career_entry",
|
|
}],
|
|
declinedTopics: [{
|
|
target_domain: "relationship",
|
|
intent: "collect_method_evidence",
|
|
status: "declined",
|
|
}],
|
|
sessionOutcome: "collect_evidence",
|
|
});
|
|
assert.notEqual(declined.next_followup?.domain, "relationship");
|
|
});
|
|
|
|
test("accepted time replaces leftover collect with reverse_verify or consultation handoff", () => {
|
|
assert.equal(isStalePreAdoptFocus("04:53", { intent: "collect_method_evidence" }), true);
|
|
assert.equal(isStalePreAdoptFocus("04:53", { intent: "distinguish_candidates" }), true);
|
|
assert.equal(isStalePreAdoptFocus("04:53", { intent: "reverse_verify" }), false);
|
|
assert.equal(isStalePreAdoptFocus(null, { intent: "collect_method_evidence" }), false);
|
|
const idle = readFileSync(new URL("../src/lib/rectification-agentic/v9/answer-choice.ts", import.meta.url), "utf8");
|
|
const accept = readFileSync(new URL("../src/app/api/rectification/cases/[caseId]/candidates/accept/route.ts", import.meta.url), "utf8");
|
|
assert.match(idle, /isStalePreAdoptFocus/);
|
|
assert.match(idle, /resolveV10ConversationFocus/);
|
|
assert.match(accept, /persistNextInterviewIfIdle/);
|
|
const leftover = {
|
|
intent: "collect_method_evidence",
|
|
targetDomain: "finance",
|
|
targetKind: null,
|
|
expectedAnswerSchema: { collect: true, prompt: USER_COLLECT_QUESTION.finance },
|
|
};
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
activeFocus: leftover,
|
|
accepted: true,
|
|
eventProbes: [{
|
|
year: 2021,
|
|
year_label: "2021 年前后",
|
|
domain: "finance",
|
|
event_family: "收入或债务明显变化",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "2021 年前后财务变化",
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
semantic_key: "finance.2021",
|
|
candidate_split_hash: "finance:2021",
|
|
candidate_ids: ["04:53", "05:00"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["04:53"], conflicts: ["05:00"] },
|
|
{ answer_class: "no", supports: ["05:00"], conflicts: ["04:53"] },
|
|
],
|
|
}],
|
|
});
|
|
assert.equal(plan.next_followup?.intent, "reverse_verify");
|
|
const action = buildNextUserAction({
|
|
scorableCount: 4,
|
|
evidenceCount: 4,
|
|
hasLatestResult: true,
|
|
selectionAllowed: true,
|
|
sessionOutcome: "adopt_representative",
|
|
nextFollowup: plan.next_followup,
|
|
workingTime: "04:53",
|
|
accepted: true,
|
|
});
|
|
assert.equal(action.id, "verify_adopted_time");
|
|
const empty = buildMethodFollowupPlan({
|
|
evidence: WALKTHROUGH_EVIDENCE,
|
|
activeFocus: leftover,
|
|
accepted: true,
|
|
eventProbes: [],
|
|
});
|
|
assert.equal(empty.next_followup, null);
|
|
assert.equal(buildNextUserAction({
|
|
scorableCount: 4,
|
|
evidenceCount: 4,
|
|
hasLatestResult: true,
|
|
selectionAllowed: true,
|
|
sessionOutcome: "adopt_representative",
|
|
nextFollowup: empty.next_followup,
|
|
workingTime: "04:53",
|
|
accepted: true,
|
|
}).id, "start_consultation");
|
|
});
|
|
|
|
test("coverage does not block overlay can_adopt when the engine allows accept", () => {
|
|
const offered = decideRectification({
|
|
engineCeiling: {
|
|
acceptanceAllowed: true,
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
confirmationAllowed: false,
|
|
},
|
|
methodCoverageAll: false,
|
|
trainingGateOpen: true,
|
|
candidateScores: [
|
|
{ time: "04:53", score: 34 },
|
|
{ time: "04:51", score: 33 },
|
|
{ time: "04:47", score: 33 },
|
|
],
|
|
discriminatorProbe: null,
|
|
});
|
|
assert.equal(offered.canAdopt, true);
|
|
assert.equal(offered.canConfirmExactMinute, false);
|
|
const refused = decideRectification({
|
|
engineCeiling: {
|
|
acceptanceAllowed: false,
|
|
selectionAllowed: false,
|
|
proposeAllowed: false,
|
|
confirmationAllowed: false,
|
|
},
|
|
methodCoverageAll: true,
|
|
trainingGateOpen: true,
|
|
candidateScores: [
|
|
{ time: "04:53", score: 34 },
|
|
{ time: "04:51", score: 33 },
|
|
{ time: "04:47", score: 33 },
|
|
],
|
|
discriminatorProbe: null,
|
|
});
|
|
assert.equal(refused.canAdopt, false);
|
|
assert.equal(refused.canConfirmExactMinute, false);
|
|
const trainingClosed = decideRectification({
|
|
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
|
|
methodCoverageAll: true,
|
|
trainingGateOpen: false,
|
|
candidateScores: [
|
|
{ time: "04:53", score: 34 },
|
|
{ time: "04:51", score: 33 },
|
|
],
|
|
discriminatorProbe: null,
|
|
});
|
|
assert.equal(trainingClosed.canAdopt, false);
|
|
});
|