Files
Jyotisha/frontend/tests/rectification-occupation-dated-answer-20260911.test.ts
T
Jesse_ChenandCursor 1fa994ea63
Independent Staging Quality Gate / validate (push) Successful in 13m26s
Independent Staging Quality Gate / publish (push) Successful in 9m33s
fix(rectification): keep dated occupation answers scoreable (BUG-649/650)
Occupation collect was wiping year-month into an unscored note, and idle gap copy never joined the evidence turn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 11:49:52 +08:00

447 lines
15 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
applyOccupationCollectLedgerNorm,
isOccupationCollectFocus,
isPrimaryScoreableEvidence,
trainingScoreableGate,
type EvidenceKind,
} from "../src/lib/rectification-agentic/v9/evidence-model.ts";
import { createRectificationV9Tools } from "../src/mastra/rectification-v9-tools.ts";
import {
CASE_ID,
CANDIDATE_ID,
FOCUS_ID,
SECOND_CANDIDATE_ID,
TURN_ID,
USER_ID,
activeFocusFixture,
candidateSnapshotFixture,
computeFixture,
conversationSummaryFixture,
dossierFixture,
fakeAccounting,
receiptHandlers,
} from "./rectification-v9-test-support.ts";
const OCCUPATION_FOCUS = {
intent: "collect_method_evidence" as const,
targetDomain: "occupation",
targetKind: "occupation_note",
questionId: "collect:occupation:collect_method_evidence",
};
const NOTE_ID = "44444444-4444-4444-8444-444444444451";
const CAREER_ID = "44444444-4444-4444-8444-444444444452";
const EDUCATION_ID = "44444444-4444-4444-8444-444444444441";
const ENGINE_SCORE = {
success: true,
endpoint: "rectification_v5_score",
result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62",
algorithm_version: "rectification-event-contract-v2",
event_contract_version: "rectification-event-contract-v2",
decision_policy_version: "rectification-candidate-policy-v2",
execution_ledger_version: "rectification-execution-ledger-v2",
candidate_decisions: [
{ candidate_id: CANDIDATE_ID, time: "04:50", rank: 1, relative_support: 57, tied_minute_count: 1 },
{ candidate_id: SECOND_CANDIDATE_ID, time: "04:51", rank: 2, relative_support: 25, tied_minute_count: 2 },
],
decision_receipt: {
receipt_version: "candidate-decision-receipt-v2",
contract_version: "v2",
event_contract_version: "rectification-event-contract-v2",
policy_version: "rectification-candidate-policy-v2",
decision_policy_version: "rectification-candidate-policy-v2",
display_allowed: true,
selection_allowed: true,
acceptance_allowed: true,
propose_allowed: true,
confirmation_allowed: false,
accept_allowed: true,
confirm_allowed: false,
representative_candidate_id: CANDIDATE_ID,
representative_time: "04:50",
overall_confidence: "high",
margin_percent: 42.5,
gates: {
event_quality: { scoreable_event_count: 3, minimum: 3 },
domain_diversity: { domains: ["education", "career"], count: 2, minimum: 2 },
},
},
execution_ledger: [
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "d1-rashi", status: "executed", source: "python-engine" },
],
diagnostics: {
window_scan: {
scanned: true,
confirmation_allowed: false,
unique_minute_claim: false,
d9_lagna_count: 2,
d10_lagna_count: 1,
d9_candidates_differ: true,
d10_candidates_differ: false,
d9_sign_names: ["白羊座", "天蝎"],
},
},
};
function stubEngine(response: unknown) {
const previous = globalThis.fetch;
globalThis.fetch = (async () => ({
ok: true,
status: 200,
json: async () => response,
})) as unknown as typeof fetch;
return () => {
globalThis.fetch = previous;
};
}
const educationEvidence = {
id: EDUCATION_ID,
source_turn_id: TURN_ID,
subject: "self",
event_kind: "education_start",
domain: "education",
occurred_from: "2016-09-01",
occurred_to: "2016-09-30",
date_precision: "month",
summary: "2016年9月上大学",
status: "confirmed",
supersedes_evidence_id: null,
created_at: "2026-09-11T00:00:00.000Z",
};
function datedOccupationInput(kind: EvidenceKind = "career_entry") {
return {
domain: "career" as const,
eventKind: kind,
datePrecision: "month" as const,
occurredFrom: "2024-04-01",
occurredTo: null,
summary: "2024年4月开始做程序员",
quote: "2024 年 4 月开始做程序员",
subject: "self" as const,
};
}
test("occupation collect focus is identified from question id, not body text", () => {
assert.equal(isOccupationCollectFocus(OCCUPATION_FOCUS), true);
assert.equal(isOccupationCollectFocus({
intent: "collect_method_evidence",
targetDomain: "career",
targetKind: "anchor:education_completion:2020",
questionId: "collect:anchor:education_completion:2020",
}), false);
});
test("dated occupation collect writes occupation_note plus a scoreable career_entry", () => {
const remapped = applyOccupationCollectLedgerNorm(OCCUPATION_FOCUS, [datedOccupationInput()]);
assert.equal(remapped.length, 2);
assert.equal(remapped[0]?.domain, "occupation");
assert.equal(remapped[0]?.eventKind, "occupation_note");
assert.equal(remapped[0]?.datePrecision, "unknown");
assert.equal(remapped[0]?.occurredFrom, null);
assert.equal(remapped[0]?.occurredTo, null);
assert.equal(remapped[1]?.domain, "career");
assert.equal(remapped[1]?.eventKind, "career_entry");
assert.equal(remapped[1]?.datePrecision, "month");
assert.equal(remapped[1]?.occurredFrom, "2024-04-01");
assert.equal(isPrimaryScoreableEvidence({
status: "confirmed",
domain: remapped[0]!.domain,
datePrecision: remapped[0]!.datePrecision,
occurredFrom: remapped[0]!.occurredFrom,
occurredTo: remapped[0]!.occurredTo,
eventKind: remapped[0]!.eventKind,
}), false);
assert.equal(isPrimaryScoreableEvidence({
status: "confirmed",
domain: remapped[1]!.domain,
datePrecision: remapped[1]!.datePrecision,
occurredFrom: remapped[1]!.occurredFrom,
occurredTo: remapped[1]!.occurredTo,
eventKind: remapped[1]!.eventKind,
}), true);
});
test("undated occupation collect still writes only occupation_note", () => {
const remapped = applyOccupationCollectLedgerNorm(OCCUPATION_FOCUS, [{
domain: "career",
eventKind: "career_entry" as const,
datePrecision: "unknown" as const,
occurredFrom: null,
occurredTo: null,
summary: "程序员",
}]);
assert.equal(remapped.length, 1);
assert.equal(remapped[0]?.eventKind, "occupation_note");
assert.equal(remapped[0]?.occurredFrom, null);
});
test("model career kinds other than career_entry are kept on the dated row", () => {
const remapped = applyOccupationCollectLedgerNorm(
OCCUPATION_FOCUS,
[datedOccupationInput("career_change")],
);
assert.equal(remapped[1]?.eventKind, "career_change");
});
test("unknown precision with leftover dates still writes only the note", () => {
const remapped = applyOccupationCollectLedgerNorm(OCCUPATION_FOCUS, [{
domain: "career",
eventKind: "career_entry" as const,
datePrecision: "unknown" as const,
occurredFrom: "2024-04-01",
occurredTo: null,
}]);
assert.equal(remapped.length, 1);
assert.equal(remapped[0]?.occurredFrom, null);
assert.equal(remapped[0]?.datePrecision, "unknown");
});
test("occupation-norm null dates are not restored by a ?? fallback in tools", () => {
const source = readFileSync(new URL("../src/mastra/rectification-v9-tools.ts", import.meta.url), "utf8");
assert.doesNotMatch(source, /normalized\?\.occurredFrom \?\?/);
assert.doesNotMatch(source, /normalized\?\.occurredTo \?\?/);
assert.doesNotMatch(source, /\?\? item\.occurredFrom/);
assert.doesNotMatch(source, /\?\? occurredFrom/);
assert.match(source, /occupationNormalizedLedgerRows/);
});
test("dated occupation collect raises the training gate by one career event", () => {
const education = [{
status: "confirmed" as const,
domain: "education",
datePrecision: "month" as const,
occurredFrom: "2016-09-01",
occurredTo: "2016-09-30",
eventKind: "education_start",
}, {
status: "confirmed" as const,
domain: "education",
datePrecision: "month" as const,
occurredFrom: "2020-06-01",
occurredTo: "2020-06-30",
eventKind: "education_completion",
}];
const before = trainingScoreableGate(education);
assert.equal(before.trainingCount, 2);
assert.equal(before.open, false);
const remapped = applyOccupationCollectLedgerNorm(OCCUPATION_FOCUS, [datedOccupationInput()]);
const after = trainingScoreableGate([
...education,
...remapped.map((item) => ({
status: "confirmed" as const,
domain: item.domain,
datePrecision: item.datePrecision,
occurredFrom: item.occurredFrom,
occurredTo: item.occurredTo,
eventKind: item.eventKind,
})),
]);
assert.equal(after.trainingCount, before.trainingCount + 1);
assert.equal(after.trainingDomainCount, 2);
assert.equal(after.open, true);
});
test("record-evidence-batch writes note plus career_entry and rescores", async () => {
const restore = stubEngine(ENGINE_SCORE);
let collectFocusResolved = false;
try {
const quote = "2024 年 4 月开始做程序员";
const accounting = fakeAccounting({
...receiptHandlers,
get_agentic_rectification_case_dossier: () => dossierFixture({
evidence: [educationEvidence],
latestResult: null,
conversationSummary: conversationSummaryFixture({
activeFocus: collectFocusResolved
? null
: activeFocusFixture({
intent: "collect_method_evidence",
targetDomain: "occupation",
targetKind: "occupation_note",
questionId: OCCUPATION_FOCUS.questionId,
expectedAnswerSchema: { collect: true, prompt: "你平时主要做什么工作?" },
}),
}),
}),
get_agentic_rectification_case_compute: () => computeFixture(),
record_agentic_rectification_evidence_batch: (_fn, args) => {
const items = Array.isArray(args.p_items) ? args.p_items as Array<Record<string, unknown>> : [];
return {
items: items.map((item, index) => ({
index,
outcome: "accepted",
evidence_id: item.event_kind === "occupation_note" ? NOTE_ID : CAREER_ID,
status: "confirmed",
idempotent: false,
clarification_fields: [],
error_code: null,
})),
accepted_count: items.length,
needs_clarification_count: 0,
rejected_count: 0,
focus_id: FOCUS_ID,
};
},
resolve_agentic_rectification_conversation_focus: () => {
collectFocusResolved = true;
return {
focus_id: FOCUS_ID,
status: "resolved",
evidence_id: NOTE_ID,
idempotent: false,
};
},
set_agentic_rectification_conversation_focus: (_fn, args) => ({
focus: {
id: FOCUS_ID,
case_id: CASE_ID,
question_id: args.p_question_id,
intent: args.p_intent,
target_evidence_id: args.p_target_evidence_id,
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-11T00:00:00.000Z",
resolved_at: null,
asked_turn_id: args.p_asked_turn_id ?? null,
},
idempotent: false,
}),
persist_agentic_rectification_candidate_v2: () => ({
...candidateSnapshotFixture({
representativeTime: "04:50",
selectionAllowed: true,
confirmationAllowed: false,
decisionReceipt: {
gates: {
event_quality: { scoreable_event_count: 3, minimum: 3 },
domain_diversity: { domains: ["education", "career"], count: 2, minimum: 2 },
},
},
}),
cached: false,
}),
});
const tools = createRectificationV9Tools({
userId: USER_ID,
caseId: CASE_ID,
turnId: TURN_ID,
userMessage: quote,
accounting: accounting.client as never,
});
const result = await (tools["rectification-record-evidence-batch"] as unknown as {
execute(input: unknown): Promise<{
accepted_count: number;
accepted_recaps: Array<{ display_date_label?: string }>;
rescore: { status: string; executed_methods: string[] };
}>;
}).execute({
caseId: CASE_ID,
focusId: FOCUS_ID,
items: [{
quote,
proposedKind: "career_entry",
subject: "self",
domain: "career",
datePrecision: "month",
occurredFrom: "2024-04",
summary: "2024年4月开始做程序员",
}],
});
const write = accounting.calls.find((call) => call.fn === "record_agentic_rectification_evidence_batch");
const items = (write?.args.p_items ?? []) as Array<Record<string, unknown>>;
assert.equal(items.length, 2);
assert.equal(items[0]?.event_kind, "occupation_note");
assert.equal(items[0]?.domain, "occupation");
assert.equal(items[0]?.date_precision, "unknown");
assert.equal(items[0]?.occurred_from, null);
assert.equal(items[1]?.event_kind, "career_entry");
assert.equal(items[1]?.domain, "career");
assert.equal(items[1]?.date_precision, "month");
assert.equal(items[1]?.occurred_from, "2024-04-01");
assert.equal(result.accepted_count, 2);
assert.equal(result.accepted_recaps.some((item) => item.display_date_label === "2024-04"), true);
assert.ok(result.rescore.executed_methods.includes("d1-rashi"));
} finally {
restore();
}
});
test("undated occupation collect batch writes only the note", async () => {
const quote = "程序员";
const accounting = fakeAccounting({
...receiptHandlers,
get_agentic_rectification_case_dossier: () => dossierFixture({
evidence: [educationEvidence],
latestResult: null,
conversationSummary: conversationSummaryFixture({
activeFocus: activeFocusFixture({
intent: "collect_method_evidence",
targetDomain: "occupation",
targetKind: "occupation_note",
questionId: OCCUPATION_FOCUS.questionId,
expectedAnswerSchema: { collect: true, prompt: "你平时主要做什么工作?" },
}),
}),
}),
record_agentic_rectification_evidence_batch: (_fn, args) => {
const items = Array.isArray(args.p_items) ? args.p_items as Array<Record<string, unknown>> : [];
return {
items: items.map((item, index) => ({
index,
outcome: "accepted",
evidence_id: NOTE_ID,
status: "confirmed",
idempotent: false,
clarification_fields: [],
error_code: null,
})),
accepted_count: items.length,
needs_clarification_count: 0,
rejected_count: 0,
focus_id: FOCUS_ID,
};
},
resolve_agentic_rectification_conversation_focus: () => ({
focus_id: FOCUS_ID,
status: "resolved",
evidence_id: NOTE_ID,
idempotent: false,
}),
});
const tools = createRectificationV9Tools({
userId: USER_ID,
caseId: CASE_ID,
turnId: TURN_ID,
userMessage: quote,
accounting: accounting.client as never,
});
await (tools["rectification-record-evidence-batch"] as unknown as {
execute(input: unknown): Promise<unknown>;
}).execute({
caseId: CASE_ID,
focusId: FOCUS_ID,
items: [{
quote,
proposedKind: "occupation_note",
subject: "self",
domain: "occupation",
datePrecision: "unknown",
summary: "程序员",
}],
});
const write = accounting.calls.find((call) => call.fn === "record_agentic_rectification_evidence_batch");
const items = (write?.args.p_items ?? []) as Array<Record<string, unknown>>;
assert.equal(items.length, 1);
assert.equal(items[0]?.event_kind, "occupation_note");
assert.equal(items[0]?.occurred_from, null);
});