fix(rectification): skip covered-domain existence probes, then pick max gain
Dated evidence in a domain no longer yields another existence question in that domain. Remaining varga discriminators all stay in the pool so the next card is whichever unused split scores highest. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-2
@@ -6214,8 +6214,8 @@
|
||||
- 用户现象:训练事件已经够、推理层已有高信息量 D24 探针,界面仍出低信息量事业存在题;题干修复后选题内容仍不对。
|
||||
- 触发条件:快照 `latest_result.candidates` 为空,或可信区间与推理活跃时刻不一致导致 `authoritativeCandidateProjection` fail-closed;Python `discriminating_event_probes` 仍给出低分事业题;已打开的事业区分卡把后续排序锁在原题上。
|
||||
- 根因:BUG-405 把排序公式改成按信息量取最高,但读路径组包不读 `inference_state.probes`,remaining D24 又依赖快照时刻。时刻被投影饿死后目录只剩职业题。评分落库用 `score.candidates` 能把 D24 写进推理层,GET/工具随后丢掉。已打开的低分 distinguish Focus 还在 method-followup 里优先于重新排序。
|
||||
- 修复:GET、Agent、工具共用一份 `rectificationFollowupCatalog`。合并未回答的推理探针与 Python 事件探针,按 `semantic_key` 去重留更高信息量。拼 remaining splits 用推理未淘汰时刻,不把 adopt 投影的空 `scores` 当成出题目录。`varga.d24`/`d5` 按质量题、`d9`/`d10` 按风格题补全。method-followup 对 packet 里全部 contrast 探针排序;已打开但 semantic_key 不是当前目录赢家的区分卡让位。评分落库仍用当次 `score.candidates`。Skill 版本保持 `10.0.13`。
|
||||
- 验证:`frontend/tests/rectification-decision-authority.test.ts` 空快照仍选出 D24;`frontend/tests/rectification-eight-method.test.ts` 低分事业题与已打开事业卡都不得压过可渲染 D24。
|
||||
- 修复:GET、Agent、工具共用一份 `rectificationFollowupCatalog`。合并未回答的推理探针与 Python 事件探针,按 `semantic_key` 去重留更高信息量。拼 remaining splits 用推理未淘汰时刻,不把 adopt 投影的空 `scores` 当成出题目录。已有日期证据的领域不再出存在题(例如已记事业就不再问另一年入职);D9/D10/D24 等分盘区分题仍进池,按信息量全局取最高,不绑定某个领域。`varga.d24`/`d5` 按质量题、`d9`/`d10` 按风格题补全。已打开但 semantic_key 不是当前目录赢家的区分卡让位。评分落库仍用当次 `score.candidates`。Skill 版本保持 `10.0.13`。
|
||||
- 验证:`frontend/tests/rectification-decision-authority.test.ts` 空快照仍选出目录最高分;`frontend/tests/rectification-eight-method.test.ts` 已覆盖领域的存在题让位,D10 与 D24 谁分高问谁。
|
||||
- 防复发:出题目录必须来自推理探针加引擎探针,不得只吃 Python 事件探针或快照投影时刻。Adopt/展示投影 fail-closed 不得饿死出题。已打开的低分区分卡不得挡住更高分目录赢家。
|
||||
- 相关记录:BUG-405、BUG-406
|
||||
- 复发自:BUG-405(排序公式对,目录被投影饿死,已打开低分卡锁题)
|
||||
|
||||
@@ -251,6 +251,24 @@ export function askedEventProbeKeysFromLedgerEvidence(
|
||||
return [...keys];
|
||||
}
|
||||
|
||||
export function datedDomainsFromEvidence(
|
||||
evidence: readonly Readonly<{
|
||||
status?: string | null;
|
||||
domain?: string | null;
|
||||
occurredFrom?: string | null;
|
||||
occurredTo?: string | null;
|
||||
}>[],
|
||||
): string[] {
|
||||
const domains = new Set<string>();
|
||||
for (const item of evidence) {
|
||||
if (item.status && !LIVE_EVIDENCE.has(item.status)) continue;
|
||||
if (!item.domain) continue;
|
||||
const dated = [item.occurredFrom, item.occurredTo].some((value) => /^\d{4}/.test(value ?? ""));
|
||||
if (dated) domains.add(item.domain);
|
||||
}
|
||||
return [...domains];
|
||||
}
|
||||
|
||||
export function volunteeredDomainsFromEvidence(
|
||||
evidence: readonly Readonly<{
|
||||
status?: string | null;
|
||||
@@ -286,18 +304,21 @@ export function buildCandidateContrastPacket(input: {
|
||||
transitions?: readonly WindowScanTransition[];
|
||||
askedKeys?: readonly string[];
|
||||
volunteeredDomains?: readonly string[];
|
||||
providedDomains?: readonly string[];
|
||||
}): CandidateContrastPacket {
|
||||
const asked = new Set(input.askedKeys ?? []);
|
||||
const provided = new Set(input.providedDomains ?? []);
|
||||
const fromEngine = (input.engineProbes ?? []).flatMap((probe) => {
|
||||
const built = probeFromEngine(probe, input.candidateSetVersion, input.calculationResultId ?? null);
|
||||
if (!built) return [];
|
||||
const eventKey = built.domain && built.year ? `${built.domain}.${built.year}` : null;
|
||||
const isStructured = built.choiceKind === "varga_style" || built.semanticKey.startsWith("varga.");
|
||||
const isStructured = isStructuredDiscriminator(built);
|
||||
if (
|
||||
asked.has(built.semanticKey)
|
||||
|| asked.has(built.candidateSplitHash)
|
||||
|| asked.has(built.probeId)
|
||||
|| (!isStructured && eventKey && asked.has(eventKey))
|
||||
|| (!isStructured && built.domain && provided.has(built.domain))
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
@@ -316,13 +337,13 @@ export function buildCandidateContrastPacket(input: {
|
||||
transitions: input.transitions ?? [],
|
||||
});
|
||||
const presentKeys = new Set(fromEngine.map((item) => item.semanticKey));
|
||||
const fromVarga = vargaProbe(
|
||||
const fromVarga = vargaProbes(
|
||||
remainingSplits,
|
||||
input.candidateSetVersion,
|
||||
input.calculationResultId ?? null,
|
||||
new Set([...asked, ...presentKeys]),
|
||||
);
|
||||
const probes = [...fromEngine, ...(fromVarga ? [fromVarga] : [])]
|
||||
const probes = [...fromEngine, ...fromVarga]
|
||||
.sort((left, right) => right.informationGain - left.informationGain);
|
||||
return {
|
||||
candidateSetVersion: input.candidateSetVersion,
|
||||
@@ -596,15 +617,23 @@ function styleOptionsFromEngine(
|
||||
return parsed.length > 0 ? parsed : undefined;
|
||||
}
|
||||
|
||||
function vargaProbe(
|
||||
export function isStructuredDiscriminator(probe: Pick<CandidateDiscriminatorProbe, "choiceKind" | "semanticKey">): boolean {
|
||||
return probe.choiceKind === "varga_style"
|
||||
|| probe.choiceKind === "event_quality"
|
||||
|| probe.semanticKey.startsWith("varga.");
|
||||
}
|
||||
|
||||
function vargaProbes(
|
||||
remainingSplits: readonly RemainingVargaSplit[],
|
||||
candidateSetVersion: string,
|
||||
calculationResultId: string | null,
|
||||
asked: ReadonlySet<string>,
|
||||
): CandidateDiscriminatorProbe | null {
|
||||
const remaining = remainingSplits.find((item) => !vargaLayerAsked(asked, item.layer));
|
||||
if (!remaining) return null;
|
||||
return vargaProbeFromRemaining(remaining, candidateSetVersion, calculationResultId);
|
||||
): CandidateDiscriminatorProbe[] {
|
||||
return remainingSplits.flatMap((item) => {
|
||||
if (vargaLayerAsked(asked, item.layer)) return [];
|
||||
const probe = vargaProbeFromRemaining(item, candidateSetVersion, calculationResultId);
|
||||
return probe ? [probe] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function vargaLayerAsked(asked: ReadonlySet<string>, layer: string): boolean {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import {
|
||||
buildCandidateContrastPacket,
|
||||
datedDomainsFromEvidence,
|
||||
selectDiscriminatorProbe,
|
||||
volunteeredDomainsFromEvidence,
|
||||
type CandidateContrastPacket,
|
||||
@@ -175,6 +176,7 @@ export function contrastPacketFromLatestResult(
|
||||
transitions: windowScan?.transitions ?? [],
|
||||
askedKeys: askedDiscriminatorKeys(latest?.decisionReceipt, evidence),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(evidence),
|
||||
providedDomains: datedDomainsFromEvidence(evidence),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@
|
||||
* Method coverage asks for dated events in natural language.
|
||||
* Known-event quality probes (exam went badly for a year already
|
||||
* in the ledger) stamp a choice card as soon as that year is
|
||||
* recorded. Dasha conflict probes wait until training event
|
||||
* quality (3 training events in 2 training domains; holdout
|
||||
* excluded), then jump ahead of remaining method rotation and
|
||||
* block offering time cards so the window can be filtered.
|
||||
* recorded. Dasha existence probes skip a domain once that domain
|
||||
* already has dated evidence. Remaining chart discriminators
|
||||
* (D9/D10/D24 and other varga splits) stay in the pool and the
|
||||
* highest information-gain renderable probe is asked next, with
|
||||
* no preferred domain.
|
||||
* If holdout is already reserved but training is still short,
|
||||
* keep collecting a dated event instead of discriminating.
|
||||
* Once blocking methods are covered, move into candidate discrimination.
|
||||
@@ -52,6 +53,8 @@ import { overlayChoicePromptFromSpoken } from "./turn-narration.ts";
|
||||
import { decideRectification, type HoldoutValidationStatus } from "../core/rectification-decision.ts";
|
||||
import {
|
||||
askedKeysFromLedgerEvidence,
|
||||
datedDomainsFromEvidence,
|
||||
isStructuredDiscriminator,
|
||||
selectDiscriminatorProbe,
|
||||
type CandidateContrastPacket,
|
||||
type CandidateDiscriminatorProbe,
|
||||
@@ -203,6 +206,19 @@ function existenceNearbyYears(domain: string): number {
|
||||
return domain === "education" ? EDUCATION_EXISTENCE_NEARBY_YEARS : 0;
|
||||
}
|
||||
|
||||
function probeDomainAlreadyCovered(
|
||||
evidence: readonly MethodFollowupEvidence[],
|
||||
domain: string,
|
||||
): boolean {
|
||||
return evidence.some((item) => {
|
||||
if (item.status !== "confirmed" && item.status !== "draft" && item.status !== "pending_confirmation") {
|
||||
return false;
|
||||
}
|
||||
if (item.domain !== domain) return false;
|
||||
return evidenceYear(item) !== null;
|
||||
});
|
||||
}
|
||||
|
||||
function probeYearAlreadyCovered(
|
||||
evidence: readonly MethodFollowupEvidence[],
|
||||
domain: string,
|
||||
@@ -362,7 +378,7 @@ function remainingConflictProbes(
|
||||
if (probe.source === "known_event_quality" || probe.role === "clarify") continue;
|
||||
if (!isValidDistinguishProbe({ ...probe, role: "distinguish" })) continue;
|
||||
if (declined.has(probe.domain)) continue;
|
||||
if (probeYearAlreadyCovered(evidence, probe.domain, probe.year)) continue;
|
||||
if (probeDomainAlreadyCovered(evidence, probe.domain)) continue;
|
||||
const semantic = probe.semantic_key ?? `${probe.domain}.${probe.year}`;
|
||||
const split = probe.candidate_split_hash ?? "";
|
||||
if (askedKeys.has(semantic) || (split && askedKeys.has(split))) continue;
|
||||
@@ -516,8 +532,10 @@ function rankRenderableDiscriminators(input: {
|
||||
contrastProbes: readonly CandidateDiscriminatorProbe[];
|
||||
askedKeys: ReadonlySet<string>;
|
||||
topCandidateTimes?: readonly string[];
|
||||
providedDomains?: readonly string[];
|
||||
}): RankedDiscriminator[] {
|
||||
const top = input.topCandidateTimes ?? [];
|
||||
const provided = new Set(input.providedDomains ?? []);
|
||||
const rows: RankedDiscriminator[] = [];
|
||||
const seen = new Set<string>();
|
||||
const push = (row: RankedDiscriminator | null) => {
|
||||
@@ -533,6 +551,7 @@ function rankRenderableDiscriminators(input: {
|
||||
push(renderableEventProbe(probe, input.askedKeys, top));
|
||||
}
|
||||
for (const probe of input.contrastProbes) {
|
||||
if (!isStructuredDiscriminator(probe) && probe.domain && provided.has(probe.domain)) continue;
|
||||
push(renderableContrastProbe(probe, input.askedKeys, top));
|
||||
}
|
||||
return rows.sort((left, right) => right.score - left.score || (right.eventProbe?.information_gain ?? right.contrastProbe?.informationGain ?? 0) - (left.eventProbe?.information_gain ?? left.contrastProbe?.informationGain ?? 0));
|
||||
@@ -912,6 +931,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
contrastProbes,
|
||||
askedKeys,
|
||||
topCandidateTimes: input.topCandidateTimes,
|
||||
providedDomains: datedDomainsFromEvidence(input.evidence),
|
||||
})
|
||||
: [];
|
||||
const bestDiscriminator = rankedDiscriminators[0] ?? null;
|
||||
|
||||
@@ -99,6 +99,7 @@ import {
|
||||
import {
|
||||
buildCandidateContrastPacket,
|
||||
conflictProbesFromContrast,
|
||||
datedDomainsFromEvidence,
|
||||
selectDiscriminatorProbe,
|
||||
volunteeredDomainsFromEvidence,
|
||||
} from "@/lib/rectification-agentic/core/candidate-contrast-packet";
|
||||
@@ -1002,6 +1003,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
|
||||
parsed.evidence,
|
||||
),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(parsed.evidence),
|
||||
providedDomains: datedDomainsFromEvidence(parsed.evidence),
|
||||
});
|
||||
const inference = buildCaseInferenceState({
|
||||
range: parsed.case.candidateRange,
|
||||
|
||||
@@ -24,6 +24,8 @@ test("remaining D10 three-way outranks a skewed D24 split", () => {
|
||||
assert.equal(probe.choiceKind, "varga_style");
|
||||
assert.match(probe.semanticKey, /varga\.d10/);
|
||||
assert.doesNotMatch(probe.semanticKey, /varga\.d24/);
|
||||
assert.ok(packet.probes.some((item) => item.semanticKey.startsWith("varga.d24.")));
|
||||
assert.ok(packet.probes.some((item) => item.semanticKey.startsWith("varga.d10.")));
|
||||
assert.equal(probe.styleOptions?.length, 4);
|
||||
assert.equal(probe.expectedOutcomes.length, 4);
|
||||
assert.deepEqual(probe.expectedOutcomes.filter((row) => row.outcomeId !== "unsure").map((row) => row.supportsCandidateIds), [
|
||||
@@ -83,3 +85,28 @@ test("finance remaining splits stay out unless volunteered", () => {
|
||||
);
|
||||
assert.equal(shown[0]?.layer, "d2");
|
||||
});
|
||||
|
||||
test("existence probes leave the catalog once that domain already has dated evidence", () => {
|
||||
const packet = buildCandidateContrastPacket({
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
engineProbes: [{
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
domain: "career",
|
||||
year: 2023,
|
||||
user_meaning: "时间范围锁定 2023 年前后。",
|
||||
information_gain: 0.56,
|
||||
expected_outcomes: [
|
||||
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:14"] },
|
||||
{ answer_class: "no", supports: ["05:14"], conflicts: ["05:00"] },
|
||||
],
|
||||
}],
|
||||
providedDomains: ["career"],
|
||||
candidateTimes: ["05:00", "05:07", "05:14"],
|
||||
transitions: [
|
||||
{ layer: "d24", at: "05:07", from_sign: "白羊座", to_sign: "金牛座" },
|
||||
{ layer: "d24", at: "05:14", from_sign: "金牛座", to_sign: "双子座" },
|
||||
],
|
||||
});
|
||||
assert.equal(packet.probes.some((item) => item.semanticKey.includes("career.2023")), false);
|
||||
assert.match(selectDiscriminatorProbe(packet)?.semanticKey ?? "", /^varga\.d24\./);
|
||||
});
|
||||
|
||||
@@ -265,6 +265,7 @@ test("scored inference catalog outranks a low-gain Python career probe when snap
|
||||
};
|
||||
const packet = contrastPacketFromDossier(dossier);
|
||||
const selected = selectDiscriminatorProbe(packet);
|
||||
assert.equal(packet.probes.some((probe) => probe.semanticKey.includes("career.2023")), false);
|
||||
assert.match(selected?.semanticKey ?? "", /^varga\.d24\./);
|
||||
assert.ok((selected?.informationGain ?? 0) > 2);
|
||||
assert.doesNotMatch(selected?.semanticKey ?? "", /career\.2023/);
|
||||
|
||||
@@ -1057,19 +1057,19 @@ test("evidence batch returns the persisted choice prompt as open_question", asyn
|
||||
discriminating_event_probes: [{
|
||||
year: 2023,
|
||||
year_label: "2023 年前后",
|
||||
domain: "career",
|
||||
event_family: "入职、升职或职责明显加重",
|
||||
domain: "relocation",
|
||||
event_family: "搬家、离乡或长期异地",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: false,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "年份锁定 2023 年前后。事件家族:入职、升职或职责明显加重。",
|
||||
user_meaning: "年份锁定 2023 年前后。事件家族:搬家、离乡或长期异地。",
|
||||
role: "distinguish",
|
||||
phase: "candidate_discriminator",
|
||||
information_gain: 1.09,
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
semantic_key: "relocation.2023.dasha_activation",
|
||||
candidate_set_version: "set-test",
|
||||
candidate_split_hash: "set-test:career:2023",
|
||||
candidate_split_hash: "set-test:relocation:2023",
|
||||
candidate_ids: ["04:50", "05:20"],
|
||||
expected_outcomes: [
|
||||
{ answer_class: "yes", supports: ["04:50"], conflicts: ["05:20"] },
|
||||
@@ -1158,7 +1158,7 @@ test("evidence batch returns the persisted choice prompt as open_question", asyn
|
||||
const schema = setFocus?.args.p_expected_answer_schema as { choice?: { prompt?: string } } | undefined;
|
||||
assert.match(schema?.choice?.prompt ?? "", /2023 年前后/);
|
||||
assert.match(result.open_question?.prompt ?? "", /2023 年前后/);
|
||||
assert.match(result.open_question?.prompt ?? "", /入职、升职或职责明显加重/);
|
||||
assert.match(result.open_question?.prompt ?? "", /搬家、离乡或长期异地/);
|
||||
assert.doesNotMatch(result.open_question?.prompt ?? "", /高考/);
|
||||
} finally {
|
||||
restore();
|
||||
@@ -1548,6 +1548,30 @@ test("confirmed relationship evidence skips generic D9 followups unless a real p
|
||||
const probed = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
precisionStage: "d9_refine",
|
||||
contrastPacket: {
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
vargaDifferences: [],
|
||||
probes: [{
|
||||
probeId: "contrast:varga.d9.05:00|05:14",
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
question: "当前几个候选在关系盘上还分得开。",
|
||||
expectedOutcomes: [
|
||||
{ outcomeId: "yes", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:14"] },
|
||||
{ outcomeId: "weak_yes", supportsCandidateIds: ["05:14"], conflictsCandidateIds: ["05:00"] },
|
||||
],
|
||||
candidateSplitHash: "varga.d9.05:00|05:14",
|
||||
informationGain: 1.4,
|
||||
sourceFeatures: [{ technique: "D9", calculationResultId: RESULT_ID }],
|
||||
domain: "relationship",
|
||||
year: null,
|
||||
semanticKey: "varga.d9.05:00|05:14",
|
||||
choiceKind: "varga_style",
|
||||
styleOptions: [
|
||||
{ label: "相处更主动热情", answerClass: "yes", sign: "白羊座" },
|
||||
{ label: "相处更深刻占有", answerClass: "weak_yes", sign: "天蝎座" },
|
||||
],
|
||||
}],
|
||||
},
|
||||
eventProbes: [{
|
||||
...CAREER_CONFLICT_PROBE,
|
||||
year: 2021,
|
||||
@@ -1560,7 +1584,8 @@ test("confirmed relationship evidence skips generic D9 followups unless a real p
|
||||
});
|
||||
assert.equal(probed.next_followup?.domain, "relationship");
|
||||
assert.equal(probed.next_followup?.source, "event_probe");
|
||||
assert.equal(probed.next_followup?.semantic_key, "relationship.2021.dasha_activation");
|
||||
assert.equal(probed.next_followup?.semantic_key, "varga.d9.05:00|05:14");
|
||||
assert.doesNotMatch(probed.next_followup?.semantic_key ?? "", /relationship\.2021/);
|
||||
});
|
||||
|
||||
test("d9_refine after relationship still asks uncovered career first", () => {
|
||||
@@ -2065,22 +2090,88 @@ test("structured paused state ends evidence collection without parsing user copy
|
||||
});
|
||||
|
||||
|
||||
test("same domain different year still asks a conflict probe", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [
|
||||
datedEvidence("education", "2016"),
|
||||
datedEvidence("relationship", "2018"),
|
||||
datedEvidence("career", "2015"),
|
||||
datedEvidence("family", "2023"),
|
||||
],
|
||||
test("covered-domain existence probes are skipped; the highest remaining discriminator wins", () => {
|
||||
const evidence = [
|
||||
datedEvidence("education", "2016"),
|
||||
datedEvidence("relationship", "2018"),
|
||||
datedEvidence("career", "2015"),
|
||||
datedEvidence("family", "2023"),
|
||||
];
|
||||
const skipped = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
eventProbes: [{
|
||||
...CAREER_CONFLICT_PROBE,
|
||||
information_gain: 0.21,
|
||||
information_gain: 0.56,
|
||||
semantic_key: "career.2018.dasha_activation",
|
||||
}],
|
||||
});
|
||||
assert.equal(plan.next_followup?.source, "event_probe");
|
||||
assert.equal(plan.next_followup?.domain, "career");
|
||||
assert.notEqual(skipped.next_followup?.domain, "career");
|
||||
assert.notEqual(skipped.next_followup?.source, "event_probe");
|
||||
|
||||
const d24 = {
|
||||
probeId: "contrast:varga.d24.05:00|05:07|05:14",
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
question: "当前几个候选在学业盘上还分得开。",
|
||||
expectedOutcomes: [
|
||||
{ outcomeId: "yes", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:07", "05:14"] },
|
||||
{ outcomeId: "no", supportsCandidateIds: ["05:07", "05:14"], conflictsCandidateIds: ["05:00"] },
|
||||
],
|
||||
candidateSplitHash: "varga.d24.05:00|05:07|05:14",
|
||||
informationGain: 2.5,
|
||||
sourceFeatures: [{ technique: "D24", calculationResultId: RESULT_ID }],
|
||||
domain: "education",
|
||||
year: null,
|
||||
semanticKey: "varga.d24.05:00|05:07|05:14",
|
||||
choiceKind: "event_quality" as const,
|
||||
};
|
||||
const d10 = {
|
||||
probeId: "contrast:varga.d10.05:00|05:07|05:14",
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
question: "当前几个候选在事业盘上还分得开。",
|
||||
expectedOutcomes: [
|
||||
{ outcomeId: "yes", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:07", "05:14"] },
|
||||
{ outcomeId: "weak_yes", supportsCandidateIds: ["05:07"], conflictsCandidateIds: ["05:00", "05:14"] },
|
||||
{ outcomeId: "no", supportsCandidateIds: ["05:14"], conflictsCandidateIds: ["05:00", "05:07"] },
|
||||
],
|
||||
candidateSplitHash: "varga.d10.05:00|05:07|05:14",
|
||||
informationGain: 3.1,
|
||||
sourceFeatures: [{ technique: "D10", calculationResultId: RESULT_ID }],
|
||||
domain: "career",
|
||||
year: null,
|
||||
semanticKey: "varga.d10.05:00|05:07|05:14",
|
||||
choiceKind: "varga_style" as const,
|
||||
styleOptions: [
|
||||
{ label: "做事偏领导推进", answerClass: "yes" as const, sign: "白羊座" },
|
||||
{ label: "做事偏研究转化", answerClass: "weak_yes" as const, sign: "天蝎座" },
|
||||
],
|
||||
};
|
||||
const highest = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
eventProbes: [{
|
||||
...CAREER_CONFLICT_PROBE,
|
||||
information_gain: 0.56,
|
||||
semantic_key: "career.2018.dasha_activation",
|
||||
}],
|
||||
contrastPacket: {
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
vargaDifferences: [],
|
||||
probes: [d24, d10],
|
||||
},
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
assert.equal(highest.next_followup?.semantic_key, d10.semanticKey);
|
||||
assert.doesNotMatch(highest.next_followup?.semantic_key ?? "", /career\.2018/);
|
||||
|
||||
const d24Wins = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
contrastPacket: {
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
vargaDifferences: [],
|
||||
probes: [d24, { ...d10, informationGain: 1.1 }],
|
||||
},
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
assert.equal(d24Wins.next_followup?.semantic_key, d24.semanticKey);
|
||||
});
|
||||
|
||||
test("three dated events with one holdout keep collecting instead of discriminating", () => {
|
||||
|
||||
Reference in New Issue
Block a user