import assert from "node:assert/strict"; import test from "node:test"; import { decideRectification, publicCanAdopt, rangeMatchesOpeningWindow, sessionOutcomeAllowsDelivery, } from "../src/lib/rectification-agentic/core/rectification-decision.ts"; import { RECTIFICATION_DELIVERED_COPY, RECTIFICATION_QUESTION_RETRY_LIMIT, RECTIFICATION_QUESTION_UNAVAILABLE_COPY, interviewDeliveredGap, rectificationQuestionGapState, } from "../src/lib/rectification-surface-state.ts"; import { OPEN_ENGINE_CAPABILITY_CEILING } from "./rectification-v9-test-support.ts"; import type { CandidateDiscriminatorProbe } from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts"; const DATED_PROBE: CandidateDiscriminatorProbe = { probeId: "probe-2023-career", candidateSetVersion: "set-1", question: "2023 年 5 月前后,你有没有入职、换工作,或者当时手上的职责明显变重?", expectedOutcomes: [ { outcomeId: "yes", supportsCandidateIds: ["05:06"], conflictsCandidateIds: ["05:08", "05:14"], }, { outcomeId: "no", supportsCandidateIds: ["05:08", "05:14"], conflictsCandidateIds: ["05:06"], }, ], candidateSplitHash: "05:06|05:08|05:14", informationGain: 0.4, sourceFeatures: [{ technique: "test", calculationResultId: null }], domain: "career", year: 2023, semanticKey: "career.2023.entry", }; const TIED_THREE = [ { time: "05:06", score: 13 }, { time: "05:08", score: 13 }, { time: "05:14", score: 13 }, { time: "05:03", score: 12 }, { time: "04:59", score: 11 }, { time: "04:46", score: 10 }, { time: "04:53", score: 10 }, { time: "05:15", score: 10 }, { time: "04:50", score: 8 }, ] as const; const OPENING = ["04:45", "05:15"] as const; function tiedInput(overrides: Partial[0]> = {}) { return { engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING, methodCoverageAll: false, trainingGateOpen: true, candidateScores: TIED_THREE.map((item) => ({ time: item.time, score: item.score })), holdoutValidation: "unavailable" as const, datedEventCount: 3, datedDomainCount: 2, inferenceCredibleRange: OPENING, openingCandidateRange: OPENING, ...overrides, }; } test("tied first place with a dated probe keeps collecting instead of delivering", () => { const decision = decideRectification(tiedInput({ discriminatorProbe: DATED_PROBE, targetedCollectExhausted: true, refreshExhausted: false, })); assert.notEqual(decision.nextAction, "complete_with_range"); assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), false); assert.equal(publicCanAdopt(decision), false); assert.equal(decision.canAdopt, false); assert.equal(decision.nextAction, "ask_candidate_discriminator"); }); test("tied first place with no probe still collects while targeted questions remain", () => { const decision = decideRectification(tiedInput({ discriminatorProbe: null, targetedCollectExhausted: false, refreshExhausted: true, })); assert.notEqual(decision.nextAction, "complete_with_range"); assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), false); assert.equal(publicCanAdopt(decision), false); assert.equal(decision.nextAction, "ask_fact_collection"); }); test("tied first place with no probe and targeted collect exhausted delivers a range", () => { const decision = decideRectification(tiedInput({ discriminatorProbe: null, targetedCollectExhausted: true, refreshExhausted: false, })); assert.equal(decision.stopReason, "tied_first"); assert.equal(decision.nextAction, "complete_with_range"); assert.equal(decision.canOfferRange, true); assert.equal(sessionOutcomeAllowsDelivery(decision.sessionOutcome), true); }); test("a live first-place tie with remaining probes does not announce adopt_representative", () => { const decision = decideRectification(tiedInput({ discriminatorProbe: DATED_PROBE, datedEventCount: 3, targetedCollectExhausted: false, refreshExhausted: false, })); assert.notEqual(decision.sessionOutcome, "adopt_representative"); assert.equal(publicCanAdopt(decision), false); assert.equal(rangeMatchesOpeningWindow(decision.credibleRange, OPENING), true); }); test("an unnarrowed delivered range withholds adopt", () => { const unnarrowed = decideRectification(tiedInput({ discriminatorProbe: null, targetedCollectExhausted: true, inferenceCredibleRange: OPENING, openingCandidateRange: OPENING, })); assert.equal(sessionOutcomeAllowsDelivery(unnarrowed.sessionOutcome), true); assert.equal(unnarrowed.sessionOutcome === "provisional_range" || unnarrowed.sessionOutcome === "completed_with_range", true); assert.equal(unnarrowed.canAdopt, false); assert.equal(publicCanAdopt(unnarrowed), false); assert.equal(unnarrowed.canOfferRange, true); const narrowed = decideRectification(tiedInput({ discriminatorProbe: null, targetedCollectExhausted: true, inferenceCredibleRange: ["04:48", "05:07"], openingCandidateRange: OPENING, })); assert.equal(sessionOutcomeAllowsDelivery(narrowed.sessionOutcome), true); assert.equal(narrowed.canAdopt, true); assert.equal(narrowed.sessionOutcome, "adopt_representative"); }); test("a delivered dead choice is the delivered gap, not unavailable repair", () => { const warnings: string[] = []; const original = console.warn; console.warn = (value: unknown) => { warnings.push(String(value)); }; try { assert.equal( interviewDeliveredGap({ questionMissing: false, deadChoice: true, sessionOutcome: "completed_with_range", stopReason: "tied_first", questionId: "distinguish:career.2023.entry", hasChoiceCard: false, }), true, ); assert.equal( rectificationQuestionGapState({ liveQuestionVisible: false, questionMissing: false, questionLoadFailed: true, deadChoice: true, questionId: "distinguish:career.2023.entry", hasChoiceCard: false, busy: false, readonly: false, regenerating: false, snapshotLoaded: true, resumableCase: true, retryAttempts: RECTIFICATION_QUESTION_RETRY_LIMIT, sessionOutcome: "completed_with_range", stopReason: "tied_first", }), "delivered", ); assert.ok(warnings.some((line) => line.includes("rectification_delivered_state_dead_choice"))); } finally { console.warn = original; } assert.equal(RECTIFICATION_DELIVERED_COPY.includes("没有拿到下一个问题"), false); }); test("a non-delivery dead choice stays on the unavailable repair path", () => { assert.equal( interviewDeliveredGap({ questionMissing: false, deadChoice: true, sessionOutcome: "collect_evidence", hasChoiceCard: false, }), false, ); assert.equal( rectificationQuestionGapState({ liveQuestionVisible: false, questionMissing: false, questionLoadFailed: true, deadChoice: true, questionPersisted: false, collectWaiting: false, busy: false, readonly: false, regenerating: false, snapshotLoaded: true, resumableCase: true, retryAttempts: RECTIFICATION_QUESTION_RETRY_LIMIT, sessionOutcome: "collect_evidence", }), "unavailable", ); assert.equal(RECTIFICATION_QUESTION_UNAVAILABLE_COPY, "没有拿到下一个问题。"); });