Files
Jyotisha/frontend/tests/rectification-distinguish-contract.test.ts
T
Jesse_Chen a0ce55f066
Independent Staging Quality Gate / validate (push) Successful in 11m17s
Independent Staging Quality Gate / publish (push) Failing after 9m28s
fix(web): drive rectification from candidate contrast and allow range close
Showing a choice card is no longer treated as completion. Distinguish probes
require real candidate groups, holdout stays out of scoring, and ordinary
sessions can finish with a credible range instead of an exact-minute gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:19:26 +08:00

136 lines
5.0 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { applyProbeOutcome } from "../src/lib/rectification-agentic/core/apply-probe-outcome.ts";
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import { distinguishContractErrors } from "../src/lib/rectification-agentic/core/distinguish-contract.ts";
import { probeFromEngine } from "../src/lib/rectification-agentic/core/probes-from-engine.ts";
import { parseDiscriminatingEventProbes } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
test("CI forbids distinguish probes with empty mapping or non-positive gain", () => {
assert.deepEqual(distinguishContractErrors({
role: "distinguish",
information_gain: 0,
candidate_ids: ["05:00", "05:20"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
],
}), ["distinguish_non_positive_information_gain"]);
assert.deepEqual(distinguishContractErrors({
role: "distinguish",
information_gain: 0.4,
candidate_ids: [],
expected_outcomes: [
{ answer_class: "yes", supports: [], conflicts: [] },
{ answer_class: "no", supports: [], conflicts: [] },
],
}), ["distinguish_empty_candidate_ids"]);
assert.deepEqual(distinguishContractErrors({
role: "distinguish",
information_gain: 0.4,
candidate_ids: ["05:00", "05:20"],
expected_outcomes: [],
}), ["distinguish_empty_expected_outcomes"]);
});
test("receipt parser drops invalid distinguish probes and known_event_quality", () => {
const parsed = parseDiscriminatingEventProbes([
{
year: 2016,
year_label: "2016 年前后",
domain: "education",
event_family: "学业变化",
source: "known_event_quality",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: "clarification only",
role: "distinguish",
information_gain: 0,
},
{
year: 2018,
year_label: "2018 年前后",
domain: "career",
event_family: "职责变化",
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: "engine locked year and family",
role: "distinguish",
information_gain: 0.4,
candidate_ids: ["05:00", "05:20"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
],
},
]);
assert.equal(parsed.length, 1);
assert.equal(parsed[0]?.domain, "career");
assert.equal(parsed[0]?.source, "dasha_activation");
assert.ok((parsed[0]?.information_gain ?? 0) > 0);
});
test("randomized hidden mutated answers change posterior only when mapped", () => {
const probe = probeFromEngine({
year: 2018,
year_label: "2018 年前后",
domain: "career",
event_family: "职责变化",
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: "engine locked year and family",
role: "distinguish",
information_gain: 0.4,
semantic_key: "career.2018",
candidate_split_hash: "set:career:2018",
candidate_ids: ["05:00", "05:20"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
{ answer_class: "unsure", supports: [], conflicts: [] },
],
});
assert.ok(probe);
const scores = { "05:00": 10, "05:20": 10 };
const yes = applyProbeOutcome(scores, probe, "yes");
assert.equal(yes.kind, "informative");
assert.notDeepEqual(yes.scores, scores);
assert.ok(yes.deltas["05:00"] !== 0);
const mutated = applyProbeOutcome(scores, probe, "unsure");
assert.equal(mutated.kind, "low_information");
assert.deepEqual(mutated.scores, scores);
const state = buildInferenceState({
range_start: "05:00",
range_end: "05:20",
candidates: [
{ id: "05:00", time: "05:00", relative_support: 10 },
{ id: "05:20", time: "05:20", relative_support: 10 },
],
events: [
{ id: "e1", domain: "education", year: 2016, precision: "month" },
{ id: "e2", domain: "career", year: 2018, precision: "year" },
{ id: "e3", domain: "family", year: 2020, precision: "year" },
],
probes: [probe],
answered_probes: [{
probe_id: probe.id,
semantic_key: probe.semantic_key,
candidate_split_hash: probe.candidate_split_hash,
answer_class: "unsure",
classified_from: "choice",
}],
});
assert.equal(state.last_inference_round?.kind, "low_information");
assert.deepEqual(state.last_inference_round?.scores_before, state.last_inference_round?.scores_after);
assert.equal(
state.rounds.filter((item) => item.kind === "informative").length,
0,
);
});