278 lines
9.3 KiB
TypeScript
278 lines
9.3 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
||
import {
|
||
applyChoiceWithoutEvidence,
|
||
stampChoiceSchemaWithProbe,
|
||
} from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
|
||
|
||
import {
|
||
projectRectificationChoiceCard,
|
||
yearlessPersonalityCanAsk,
|
||
type MethodFollowup,
|
||
} from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
||
import { expectedAnswerSchemaFor } from "../src/lib/rectification-agentic/v9/server-focus.ts";
|
||
import { buildChoiceFrame } from "../src/lib/rectification-agentic/v9/choice-card.ts";
|
||
import { buildRangeDelivery } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
|
||
import { parseWindowScan } from "../src/lib/rectification-agentic/v9/varga-observations.ts";
|
||
import {
|
||
attachVargaDistinguishIdentity,
|
||
withFollowupOwnedProbe,
|
||
} from "../src/lib/rectification-agentic/v9/varga-distinguish-probe.ts";
|
||
import { FOCUS_ID } from "./rectification-v9-test-support.ts";
|
||
|
||
const DATED = [
|
||
{
|
||
status: "confirmed",
|
||
domain: "health_pressure",
|
||
datePrecision: "month",
|
||
occurredFrom: "2019-03-01",
|
||
occurredTo: "2019-03-31",
|
||
},
|
||
{
|
||
status: "confirmed",
|
||
domain: "career",
|
||
datePrecision: "year",
|
||
occurredFrom: "2018-01-01",
|
||
occurredTo: "2018-12-31",
|
||
},
|
||
{
|
||
status: "confirmed",
|
||
domain: "education",
|
||
datePrecision: "year",
|
||
occurredFrom: "2016-01-01",
|
||
occurredTo: "2016-12-31",
|
||
},
|
||
{
|
||
status: "confirmed",
|
||
domain: "occupation",
|
||
datePrecision: "unknown",
|
||
occurredFrom: null,
|
||
occurredTo: null,
|
||
},
|
||
];
|
||
|
||
const D24 = {
|
||
id: "contrast:varga.d24.白羊座|金牛座|双子座|巨蟹座|狮子座|处女座",
|
||
semantic_key: "varga.d24.白羊座|金牛座|双子座|巨蟹座|狮子座|处女座",
|
||
candidate_split_hash: "set:varga.d24.白羊座|金牛座|双子座|巨蟹座|狮子座|处女座",
|
||
domain: "education",
|
||
year: 0,
|
||
question: "学业对照",
|
||
candidate_ids: ["04:48", "04:59"],
|
||
expected_outcomes: [
|
||
{ answer_class: "yes" as const, supports: ["04:48"], conflicts: ["04:59"] },
|
||
{ answer_class: "weak_yes" as const, supports: ["04:59"], conflicts: ["04:48"] },
|
||
{ answer_class: "no" as const, supports: [], conflicts: [] },
|
||
{ answer_class: "unsure" as const, supports: [], conflicts: [] },
|
||
],
|
||
information_gain: 0.9,
|
||
source: "engine",
|
||
choice_kind: "event_quality" as const,
|
||
};
|
||
|
||
const WINDOW = parseWindowScan({
|
||
scanned: true,
|
||
d9_lagna_count: 2,
|
||
d10_lagna_count: 1,
|
||
d9_candidates_differ: true,
|
||
d10_candidates_differ: false,
|
||
d9_sign_names: ["天蝎座", "天秤座"],
|
||
d10_sign_names: ["巨蟹座"],
|
||
transitions: [
|
||
{ layer: "d9", at: "04:53", from_sign: "天蝎座", to_sign: "天秤座" },
|
||
{ layer: "d10", at: "04:40", from_sign: "巨蟹座", to_sign: "巨蟹座" },
|
||
],
|
||
});
|
||
|
||
function state() {
|
||
return buildInferenceState({
|
||
range_start: "04:48",
|
||
range_end: "05:07",
|
||
candidates: [
|
||
{ id: "04:48", time: "04:48", relative_support: 44 },
|
||
{ id: "04:53", time: "04:53", relative_support: 35 },
|
||
{ id: "04:59", time: "04:59", relative_support: 21 },
|
||
],
|
||
events: Array.from({ length: 5 }, (_, index) => ({
|
||
id: `e${index}`,
|
||
domain: "health",
|
||
year: 2019,
|
||
precision: "month" as const,
|
||
})),
|
||
probes: [D24],
|
||
});
|
||
}
|
||
|
||
function d9Followup() {
|
||
const attached = attachVargaDistinguishIdentity({
|
||
method_id: "d9_relationship",
|
||
intent: "distinguish_candidates",
|
||
ask_theme: "relationship_style",
|
||
domain: "relationship",
|
||
kind_hint: "relationship_change",
|
||
user_prompt_hint: "当前候选在关系主题上仍分不开。",
|
||
source: "varga_observation",
|
||
must_not_label: false,
|
||
choice_frame: null,
|
||
}, {
|
||
windowScan: WINDOW,
|
||
candidates: [
|
||
{ id: "04:48", time: "04:48" },
|
||
{ id: "04:53", time: "04:53" },
|
||
{ id: "04:59", time: "04:59" },
|
||
],
|
||
}) as MethodFollowup;
|
||
const frame = buildChoiceFrame(attached, {
|
||
evidence: DATED,
|
||
probes: attached.style_options ? [{
|
||
year: 0,
|
||
year_label: "当前这几个候选",
|
||
domain: "relationship",
|
||
event_family: "开始认真关系、分手或结婚",
|
||
source: "dasha_activation",
|
||
tracks: ["vimshottari", "narayana"],
|
||
tracks_agree: true,
|
||
unique_minute_claim: false,
|
||
user_meaning: attached.user_prompt_hint,
|
||
role: "distinguish",
|
||
phase: "candidate_discriminator",
|
||
information_gain: attached.information_gain,
|
||
semantic_key: attached.semantic_key,
|
||
candidate_split_hash: attached.candidate_split_hash,
|
||
candidate_ids: attached.candidate_ids,
|
||
expected_outcomes: attached.expected_outcomes,
|
||
choice_kind: "varga_style" as const,
|
||
style_options: attached.style_options,
|
||
}] : [] as never,
|
||
});
|
||
return { ...attached, choice_frame: frame } as MethodFollowup;
|
||
}
|
||
|
||
test("year-stage D9 followup does not borrow the D24 contrast probe (BUG-912)", () => {
|
||
const inference = state();
|
||
const borrowed = stampChoiceSchemaWithProbe({
|
||
choice: {
|
||
options: [
|
||
{ key: "A", answer_class: "yes" },
|
||
{ key: "B", answer_class: "weak_yes" },
|
||
{ key: "C", answer_class: "no" },
|
||
{ key: "D", answer_class: "unsure" },
|
||
],
|
||
},
|
||
}, inference, "d9_relationship:relationship_style:score");
|
||
assert.notEqual(borrowed.probe_id, D24.id);
|
||
|
||
const followup = d9Followup();
|
||
assert.equal(followup.semantic_key, "d9_relationship:relationship_style");
|
||
assert.ok(followup.choice_frame);
|
||
const schema = expectedAnswerSchemaFor(
|
||
followup.choice_frame!,
|
||
followup.choice_frame!.question_id,
|
||
{ inference_state: inference, window_scan: WINDOW },
|
||
followup,
|
||
);
|
||
assert.ok(schema);
|
||
assert.equal(schema.semantic_key, "d9_relationship:relationship_style");
|
||
assert.notEqual(String(schema.probe_id ?? ""), D24.id);
|
||
assert.match(String(schema.probe_id ?? ""), /d9_relationship:relationship_style/);
|
||
|
||
const applied = applyChoiceWithoutEvidence(withFollowupOwnedProbe(inference, followup)!, {
|
||
choiceKey: "C",
|
||
schema,
|
||
questionId: "d9_relationship:relationship_style:score",
|
||
domain: "relationship",
|
||
classifiedFrom: "choice",
|
||
});
|
||
assert.equal(applied.applied, true);
|
||
assert.equal(applied.state.answered_probes.at(-1)?.semantic_key, "d9_relationship:relationship_style");
|
||
assert.ok(!applied.state.answered_probes.some((row) => row.probe_id === D24.id));
|
||
});
|
||
|
||
test("GET projects the persisted distinguish copy when its probe is still live (BUG-912)", () => {
|
||
const inference = state();
|
||
const followup = d9Followup();
|
||
const schema = expectedAnswerSchemaFor(
|
||
followup.choice_frame!,
|
||
"d9_relationship:relationship_style:score",
|
||
{ inference_state: inference, window_scan: WINDOW },
|
||
followup,
|
||
);
|
||
const withOwned = buildInferenceState({
|
||
range_start: "04:48",
|
||
range_end: "05:07",
|
||
candidates: [
|
||
{ id: "04:48", time: "04:48", relative_support: 44 },
|
||
{ id: "04:53", time: "04:53", relative_support: 35 },
|
||
{ id: "04:59", time: "04:59", relative_support: 21 },
|
||
],
|
||
events: inference.events,
|
||
probes: [
|
||
D24,
|
||
{
|
||
id: String(schema?.probe_id),
|
||
semantic_key: String(schema?.semantic_key),
|
||
candidate_split_hash: String(schema?.candidate_split_hash),
|
||
domain: "relationship",
|
||
year: 0,
|
||
question: "D9",
|
||
candidate_ids: ["04:48", "04:59"],
|
||
expected_outcomes: D24.expected_outcomes,
|
||
information_gain: 0.4,
|
||
source: "varga_observation",
|
||
choice_kind: "varga_style" as const,
|
||
style_options: followup.style_options as never,
|
||
},
|
||
],
|
||
});
|
||
const card = projectRectificationChoiceCard({
|
||
evidence: DATED,
|
||
activeFocus: {
|
||
id: FOCUS_ID,
|
||
questionId: "d9_relationship:relationship_style:score",
|
||
intent: "distinguish_candidates",
|
||
targetDomain: "relationship",
|
||
targetKind: null,
|
||
expectedAnswerSchema: schema,
|
||
},
|
||
observations: [{ layer: "d9", candidates_differ: true, ask_theme: "relationship_style" }],
|
||
windowScan: WINDOW,
|
||
inferenceState: withOwned,
|
||
sessionOutcome: "collect_evidence",
|
||
caseRevision: 1,
|
||
});
|
||
assert.equal(card?.question_id, "d9_relationship:relationship_style:score");
|
||
assert.equal(card?.options.length, 4);
|
||
});
|
||
|
||
test("five dated events do not hold for yearless personality (BUG-913 closed_by_design)", () => {
|
||
assert.equal(yearlessPersonalityCanAsk({ datedCount: 5, candidatesSeparated: false }), false);
|
||
assert.equal(yearlessPersonalityCanAsk({ datedCount: 0, candidatesSeparated: false, topCandidateTimes: ["04:48", "04:59"] }), true);
|
||
});
|
||
|
||
test("nakshatra opposite poles are not both lifted as shared traits (BUG-914)", () => {
|
||
const earlier = "希望两边都能说得过去";
|
||
const later = "必要时会直接选边";
|
||
const delivery = buildRangeDelivery({
|
||
inference: state(),
|
||
publicCandidates: [
|
||
{ candidateId: "a", time: "04:48" },
|
||
{ candidateId: "b", time: "04:53" },
|
||
{ candidateId: "c", time: "04:59" },
|
||
],
|
||
credibleRange: ["04:48", "04:59"],
|
||
representativeTime: "04:59",
|
||
nakshatraBoundary: {
|
||
near_boundary: true,
|
||
user_meaning: `A:${earlier}。B:${later}。`,
|
||
options: [
|
||
{ key: "A", time_bias: "earlier", traits: [earlier] },
|
||
{ key: "B", time_bias: "later", traits: [later] },
|
||
],
|
||
},
|
||
});
|
||
const shared = delivery.shared_traits.join(" ");
|
||
assert.equal(shared.includes(earlier) && shared.includes(later), false);
|
||
});
|