fix(rectification): ask from the scored probe catalog, not snapshot leftovers
Empty snapshot candidates were starving remaining D24 splits, so the TypeScript follow-up chain asked the low-gain Python career probe. Read paths now share one inference+engine catalog and yield a stale low-gain distinguish card to the current winner. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,16 +21,15 @@ import { resolveSessionLanguageModel } from "@/lib/model-catalog";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
import { defaultMessageOrigin, isRectificationMessageOrigin } from "@/lib/rectification-agentic/v9/message-origin";
|
||||
import { previousInferenceFromReceipt, askedDiscriminatorKeys } from "@/lib/rectification-agentic/v9/inference-adapter";
|
||||
import { previousInferenceFromReceipt } from "@/lib/rectification-agentic/v9/inference-adapter";
|
||||
import { parseAgentChoiceCopy } from "@/lib/rectification-agentic/v9/choice-card";
|
||||
import {
|
||||
classifyRectificationTurnIntent,
|
||||
optionIdForAnswerClass,
|
||||
} from "@/lib/rectification-agentic/v9/turn-intent-classifier";
|
||||
import { decideFromDossier, contrastPacketFromDossier } from "@/lib/rectification-agentic/v9/decision-from-dossier";
|
||||
import { decideFromDossier, rectificationFollowupCatalog } from "@/lib/rectification-agentic/v9/decision-from-dossier";
|
||||
import { persistServerOwnedFocus, openQuestionFromPersistedFocus } from "@/lib/rectification-agentic/v9/server-focus";
|
||||
import { buildMethodFollowupPlan } from "@/lib/rectification-agentic/v9/method-followup";
|
||||
import { refinementFromDecisionReceipt } from "@/lib/rectification-agentic/v9/refinement-packet";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 240;
|
||||
@@ -383,17 +382,15 @@ export async function POST(request: Request) {
|
||||
} else {
|
||||
const decision = decideFromDossier(dossier);
|
||||
if (decision.nextAction === "ask_candidate_discriminator") {
|
||||
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const catalog = rectificationFollowupCatalog(
|
||||
dossier.latestResult,
|
||||
dossier.evidence,
|
||||
);
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: dossier.evidence,
|
||||
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
||||
sessionOutcome: "discriminate_candidates",
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
askedProbeKeys: askedDiscriminatorKeys(
|
||||
dossier.latestResult?.decisionReceipt,
|
||||
dossier.evidence,
|
||||
),
|
||||
contrastPacket: contrastPacketFromDossier(dossier),
|
||||
...catalog,
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
const persisted = await persistServerOwnedFocus({
|
||||
|
||||
@@ -80,8 +80,11 @@ export type EngineContrastProbe = Readonly<{
|
||||
information_gain?: number;
|
||||
expected_outcomes?: readonly Readonly<{
|
||||
answer_class?: string;
|
||||
outcomeId?: string;
|
||||
supports?: readonly string[];
|
||||
supportsCandidateIds?: readonly string[];
|
||||
conflicts?: readonly string[];
|
||||
conflictsCandidateIds?: readonly string[];
|
||||
}>[];
|
||||
left_time?: string;
|
||||
right_time?: string;
|
||||
@@ -312,11 +315,12 @@ export function buildCandidateContrastPacket(input: {
|
||||
candidateTimes: input.candidateTimes ?? [],
|
||||
transitions: input.transitions ?? [],
|
||||
});
|
||||
const presentKeys = new Set(fromEngine.map((item) => item.semanticKey));
|
||||
const fromVarga = vargaProbe(
|
||||
remainingSplits,
|
||||
input.candidateSetVersion,
|
||||
input.calculationResultId ?? null,
|
||||
asked,
|
||||
new Set([...asked, ...presentKeys]),
|
||||
);
|
||||
const probes = [...fromEngine, ...(fromVarga ? [fromVarga] : [])]
|
||||
.sort((left, right) => right.informationGain - left.informationGain);
|
||||
@@ -515,15 +519,30 @@ export function conflictProbesFromContrast(
|
||||
});
|
||||
}
|
||||
|
||||
function inferredContrastChoiceKind(
|
||||
semanticKey: string,
|
||||
explicit?: ContrastChoiceKind,
|
||||
): ContrastChoiceKind {
|
||||
if (explicit === "varga_style" || explicit === "event_quality" || explicit === "existence") {
|
||||
return explicit;
|
||||
}
|
||||
const layer = semanticKey.match(/^varga\.(d\d+)/)?.[1];
|
||||
if (layer === "d9" || layer === "d10") return "varga_style";
|
||||
if (layer === "d24" || layer === "d5") return "event_quality";
|
||||
return "existence";
|
||||
}
|
||||
|
||||
function probeFromEngine(
|
||||
probe: EngineContrastProbe,
|
||||
candidateSetVersion: string,
|
||||
calculationResultId: string | null,
|
||||
): CandidateDiscriminatorProbe | null {
|
||||
const outcomes = (probe.expected_outcomes ?? []).flatMap((row) => {
|
||||
const outcomeId = typeof row.answer_class === "string" ? row.answer_class : "";
|
||||
const supports = row.supports ?? [];
|
||||
const conflicts = row.conflicts ?? [];
|
||||
const outcomeId = typeof row.answer_class === "string" && row.answer_class.trim()
|
||||
? row.answer_class
|
||||
: typeof row.outcomeId === "string" ? row.outcomeId : "";
|
||||
const supports = row.supports ?? row.supportsCandidateIds ?? [];
|
||||
const conflicts = row.conflicts ?? row.conflictsCandidateIds ?? [];
|
||||
if (!outcomeId) return [];
|
||||
return [{ outcomeId, supportsCandidateIds: supports, conflictsCandidateIds: conflicts }];
|
||||
});
|
||||
@@ -553,7 +572,7 @@ function probeFromEngine(
|
||||
domain: probe.domain ?? null,
|
||||
year: probe.year ?? null,
|
||||
semanticKey,
|
||||
choiceKind: probe.choice_kind,
|
||||
choiceKind: inferredContrastChoiceKind(semanticKey, probe.choice_kind),
|
||||
styleOptions: styleOptionsFromEngine(probe.style_options),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
selectDiscriminatorProbe,
|
||||
volunteeredDomainsFromEvidence,
|
||||
type CandidateContrastPacket,
|
||||
type EngineContrastProbe,
|
||||
} from "../core/candidate-contrast-packet.ts";
|
||||
import {
|
||||
decideRectification,
|
||||
@@ -84,6 +85,22 @@ export function candidateScoresFromDossier(latest: DecisionDossier["latestResult
|
||||
return authoritativeCandidateProjection(latest).scores;
|
||||
}
|
||||
|
||||
const CLOCK_TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
||||
|
||||
export function discriminatorCandidateTimes(
|
||||
latest: DecisionDossier["latestResult"],
|
||||
): string[] {
|
||||
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
||||
const fromInference = [...new Set(
|
||||
(inference?.candidates ?? [])
|
||||
.filter((item) => item.status !== "eliminated")
|
||||
.map((item) => item.time)
|
||||
.filter((time) => CLOCK_TIME.test(time)),
|
||||
)];
|
||||
if (fromInference.length >= 2) return fromInference;
|
||||
return candidateScoresFromDossier(latest).map((item) => item.time);
|
||||
}
|
||||
|
||||
function holdoutStatusFromInference(inference: ReturnType<typeof previousInferenceFromReceipt>) {
|
||||
if (!inference) return "unavailable" as const;
|
||||
const hasHoldout = inference.events.some((item) => item.usage === "holdout");
|
||||
@@ -105,15 +122,47 @@ function holdoutStatusFromState(state: InferenceState) {
|
||||
return "unavailable" as const;
|
||||
}
|
||||
|
||||
export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateContrastPacket {
|
||||
const windowScan = windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const inference = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const candidateScores = candidateScoresFromDossier(dossier.latestResult);
|
||||
export function contrastPacketFromLatestResult(
|
||||
latest: DecisionDossier["latestResult"],
|
||||
evidence: DecisionDossier["evidence"] = [],
|
||||
): CandidateContrastPacket {
|
||||
const windowScan = windowScanFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const refinement = refinementFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
||||
const answered = new Set((inference?.answered_probes ?? []).map((item) => item.id));
|
||||
const fromInference: EngineContrastProbe[] = (inference?.probes ?? []).flatMap((probe) => {
|
||||
if (answered.has(probe.id) || probe.information_gain <= 0) return [];
|
||||
if (probe.source === "known_event_quality") return [];
|
||||
return [{
|
||||
semantic_key: probe.semantic_key,
|
||||
candidate_split_hash: probe.candidate_split_hash,
|
||||
domain: probe.domain,
|
||||
year: probe.year > 0 ? probe.year : undefined,
|
||||
user_meaning: probe.question,
|
||||
information_gain: probe.information_gain,
|
||||
expected_outcomes: probe.expected_outcomes,
|
||||
candidate_ids: probe.candidate_ids,
|
||||
}];
|
||||
});
|
||||
const merged = mergeEngineProbes(
|
||||
fromInference,
|
||||
refinement.discriminating_event_probes.map((probe) => ({
|
||||
semantic_key: probe.semantic_key,
|
||||
candidate_split_hash: probe.candidate_split_hash,
|
||||
domain: probe.domain,
|
||||
year: probe.year,
|
||||
user_meaning: probe.user_meaning,
|
||||
information_gain: probe.information_gain,
|
||||
expected_outcomes: probe.expected_outcomes,
|
||||
candidate_ids: probe.candidate_ids,
|
||||
choice_kind: probe.choice_kind,
|
||||
style_options: probe.style_options,
|
||||
})),
|
||||
);
|
||||
return buildCandidateContrastPacket({
|
||||
candidateSetVersion: inference?.candidate_set_id ?? dossier.latestResult?.resultId ?? "none",
|
||||
calculationResultId: dossier.latestResult?.resultId ?? null,
|
||||
engineProbes: refinement.discriminating_event_probes,
|
||||
candidateSetVersion: inference?.candidate_set_id ?? latest?.resultId ?? "none",
|
||||
calculationResultId: latest?.resultId ?? null,
|
||||
engineProbes: merged,
|
||||
vargaDifferences: [
|
||||
...(windowScan?.d9_candidates_differ && windowScan.d9_sign_names.length >= 2
|
||||
? [{ layer: "d9", signs: windowScan.d9_sign_names }]
|
||||
@@ -122,16 +171,57 @@ export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateCo
|
||||
? [{ layer: "d10", signs: windowScan.d10_sign_names }]
|
||||
: []),
|
||||
],
|
||||
candidateTimes: candidateScores.map((item) => item.time),
|
||||
candidateTimes: discriminatorCandidateTimes(latest),
|
||||
transitions: windowScan?.transitions ?? [],
|
||||
askedKeys: askedDiscriminatorKeys(
|
||||
dossier.latestResult?.decisionReceipt,
|
||||
dossier.evidence,
|
||||
),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(dossier.evidence),
|
||||
askedKeys: askedDiscriminatorKeys(latest?.decisionReceipt, evidence),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(evidence),
|
||||
});
|
||||
}
|
||||
|
||||
function mergeEngineProbes(
|
||||
...groups: ReadonlyArray<readonly EngineContrastProbe[] | undefined>
|
||||
): EngineContrastProbe[] {
|
||||
const byKey = new Map<string, EngineContrastProbe>();
|
||||
for (const group of groups) {
|
||||
for (const probe of group ?? []) {
|
||||
const key = probe.semantic_key?.trim() ?? "";
|
||||
if (!key) continue;
|
||||
const current = byKey.get(key);
|
||||
if (!current || (probe.information_gain ?? 0) > (current.information_gain ?? 0)) {
|
||||
byKey.set(key, probe);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...byKey.values()];
|
||||
}
|
||||
|
||||
export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateContrastPacket {
|
||||
return contrastPacketFromLatestResult(dossier.latestResult, dossier.evidence);
|
||||
}
|
||||
|
||||
export function rectificationFollowupCatalog(
|
||||
latest: DecisionDossier["latestResult"],
|
||||
evidence: DecisionDossier["evidence"] = [],
|
||||
) {
|
||||
const receipt = latest?.decisionReceipt ?? null;
|
||||
const refinement = refinementFromDecisionReceipt(receipt);
|
||||
const inference = previousInferenceFromReceipt(receipt);
|
||||
return {
|
||||
contrastPacket: contrastPacketFromLatestResult(latest, evidence),
|
||||
topCandidateTimes: discriminatorCandidateTimes(latest),
|
||||
askedProbeKeys: askedDiscriminatorKeys(receipt, evidence),
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
precisionStage: refinement.precision_stage?.current ?? null,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
holdoutEvents: (inference?.events ?? [])
|
||||
.filter((item) => item.usage === "holdout")
|
||||
.map((item) => ({ domain: item.domain, year: item.year })),
|
||||
};
|
||||
}
|
||||
|
||||
function contrastPacketFromState(state: InferenceState): CandidateContrastPacket {
|
||||
const answered = new Set(state.answered_probes.map((item) => item.probe_id));
|
||||
return buildCandidateContrastPacket({
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
* Card identity is the persisted focus UUID plus the inference revision.
|
||||
*/
|
||||
|
||||
import { askedKeysFromLedgerEvidence } from "../core/candidate-contrast-packet.ts";
|
||||
import { askedProbeKeysFromReceipt, previousInferenceFromReceipt } from "./inference-adapter";
|
||||
import { previousInferenceFromReceipt } from "./inference-adapter";
|
||||
import {
|
||||
contrastPacketFromDossier,
|
||||
decideFromDossier,
|
||||
rectificationFollowupCatalog,
|
||||
} from "./decision-from-dossier";
|
||||
import { evidenceLedgerFingerprint } from "./tool-service";
|
||||
import { projectRectificationChoiceCard } from "./method-followup";
|
||||
import { refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import {
|
||||
internalObservationsFromWindowScan,
|
||||
windowScanFromDecisionReceipt,
|
||||
@@ -62,11 +60,11 @@ export function choiceCardFromCaseDossier(dossier: {
|
||||
};
|
||||
turns?: readonly Readonly<{ role: string; text: string | null }>[];
|
||||
}): RectificationChoiceCard | null {
|
||||
const windowScan = windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const observations = internalObservationsFromWindowScan(windowScan);
|
||||
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const catalog = rectificationFollowupCatalog(dossier.latestResult, dossier.evidence);
|
||||
const observations = internalObservationsFromWindowScan(
|
||||
windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null),
|
||||
);
|
||||
const inference = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
||||
const contrastPacket = contrastPacketFromDossier(dossier);
|
||||
const decision = decideFromDossier(dossier, {
|
||||
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence as never),
|
||||
});
|
||||
@@ -74,35 +72,23 @@ export function choiceCardFromCaseDossier(dossier: {
|
||||
.reverse()
|
||||
.find((turn) => turn.role === "assistant")
|
||||
?.text ?? null;
|
||||
const holdoutEvents = (inference?.events ?? [])
|
||||
.filter((item) => item.usage === "holdout")
|
||||
.map((item) => ({ domain: item.domain, year: item.year }));
|
||||
return projectRectificationChoiceCard({
|
||||
evidence: dossier.evidence,
|
||||
activeFocus: dossier.conversationSummary.activeFocus,
|
||||
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
||||
observations,
|
||||
sessionOutcome: decision.sessionOutcome,
|
||||
...catalog,
|
||||
precisionStage: decision.precisionStage === "collect_events"
|
||||
? "collect_events"
|
||||
: decision.precisionStage === "ready_to_adopt"
|
||||
? "ready_to_adopt"
|
||||
: refinement.precision_stage?.current,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
askedProbeKeys: [
|
||||
...askedProbeKeysFromReceipt(dossier.latestResult?.decisionReceipt),
|
||||
...askedKeysFromLedgerEvidence(dossier.evidence),
|
||||
],
|
||||
: catalog.precisionStage,
|
||||
accepted: Boolean(dossier.case.acceptedTime),
|
||||
selectionAllowed: decision.selectionAllowed,
|
||||
proposeAllowed: decision.proposeAllowed,
|
||||
confirmationAllowed: decision.canConfirmExactMinute,
|
||||
caseRevision: inference?.revision ?? 0,
|
||||
contrastPacket,
|
||||
candidateScores: decision.separation.ranked.map((item) => ({
|
||||
time: item.time,
|
||||
score: item.score,
|
||||
@@ -111,7 +97,6 @@ export function choiceCardFromCaseDossier(dossier: {
|
||||
latestAssistantText,
|
||||
candidatesSeparated: decision.separation.sufficient,
|
||||
holdoutValidation: decision.holdoutValidation,
|
||||
holdoutEvents,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
* Coverage complete never means adopt. Horary does not block cards.
|
||||
* A/B/C/D choice frames attach only when candidates already diverge
|
||||
* (event probes, precision stage, varga observation, nakshatra, or holdout).
|
||||
* An already-open distinguish card yields if the live catalog winner is a
|
||||
* different probe. Do not keep a low-gain Python event card over D24.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -499,9 +501,19 @@ function followupOwnedProbe(
|
||||
};
|
||||
}
|
||||
|
||||
function persistedFocusProbeKey(focus: MethodFollowupFocus | null | undefined): string {
|
||||
const key = focus?.expectedAnswerSchema?.semantic_key;
|
||||
return typeof key === "string" && key.trim() ? key.trim() : "";
|
||||
}
|
||||
|
||||
function rankedDiscriminatorKey(row: RankedDiscriminator | null): string {
|
||||
if (!row) return "";
|
||||
return row.eventProbe?.semantic_key ?? row.contrastProbe?.semanticKey ?? "";
|
||||
}
|
||||
|
||||
function rankRenderableDiscriminators(input: {
|
||||
eventProbes: readonly DiscriminatingEventProbe[];
|
||||
contrastProbe: CandidateDiscriminatorProbe | null;
|
||||
contrastProbes: readonly CandidateDiscriminatorProbe[];
|
||||
askedKeys: ReadonlySet<string>;
|
||||
topCandidateTimes?: readonly string[];
|
||||
}): RankedDiscriminator[] {
|
||||
@@ -520,7 +532,9 @@ function rankRenderableDiscriminators(input: {
|
||||
for (const probe of input.eventProbes) {
|
||||
push(renderableEventProbe(probe, input.askedKeys, top));
|
||||
}
|
||||
push(input.contrastProbe ? renderableContrastProbe(input.contrastProbe, input.askedKeys, top) : null);
|
||||
for (const probe of input.contrastProbes) {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -811,6 +825,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
accepted?: boolean;
|
||||
candidatesSeparated?: boolean;
|
||||
contrastPacket?: CandidateContrastPacket | null;
|
||||
topCandidateTimes?: readonly string[];
|
||||
holdoutValidation?: HoldoutValidationStatus;
|
||||
holdoutEvents?: readonly Readonly<{ domain: string; year: number | null }>[];
|
||||
}): MethodFollowupPlan {
|
||||
@@ -879,7 +894,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
|
||||
const sessionOutcome = input.sessionOutcome ?? "collect_evidence";
|
||||
const candidatesSeparated = input.candidatesSeparated === true;
|
||||
const contrastProbe = selectDiscriminatorProbe(input.contrastPacket ?? null);
|
||||
const contrastProbes = candidatesSeparated ? [] : [...(input.contrastPacket?.probes ?? [])];
|
||||
// Legacy known-event quality cards were never backed by an inference probe.
|
||||
// Ignore them so existing cases resume evidence collection instead of exposing a stale card.
|
||||
const focus = input.activeFocus?.intent === "clarify_event" ? null : input.activeFocus ?? null;
|
||||
@@ -887,6 +902,20 @@ export function buildMethodFollowupPlan(input: {
|
||||
focus && (focus.intent === "reverse_verify" || focus.intent === "out_of_sample_check"),
|
||||
);
|
||||
const coverageComplete = blockingMethodsCovered(methods);
|
||||
const askedKeys = new Set([
|
||||
...(input.askedProbeKeys ?? []),
|
||||
...askedKeysFromLedgerEvidence(input.evidence),
|
||||
]);
|
||||
const rankedDiscriminators = dashaCovered && meetsAcceptanceEventQuality(input.evidence)
|
||||
? rankRenderableDiscriminators({
|
||||
eventProbes: remainingConflictProbes(input.eventProbes, input.evidence, declined, askedKeys),
|
||||
contrastProbes,
|
||||
askedKeys,
|
||||
topCandidateTimes: input.topCandidateTimes,
|
||||
})
|
||||
: [];
|
||||
const bestDiscriminator = rankedDiscriminators[0] ?? null;
|
||||
const catalogWinnerKey = rankedDiscriminatorKey(bestDiscriminator);
|
||||
const staleCollectFocus = Boolean(
|
||||
focus
|
||||
&& focus.intent === "collect_method_evidence"
|
||||
@@ -898,9 +927,17 @@ export function buildMethodFollowupPlan(input: {
|
||||
|| (focus.targetDomain === "horary" && horaryStatus !== "uncovered")
|
||||
),
|
||||
);
|
||||
const staleDiscriminatorFocus = Boolean(
|
||||
focus
|
||||
&& focus.intent === "distinguish_candidates"
|
||||
&& catalogWinnerKey
|
||||
&& persistedFocusProbeKey(focus)
|
||||
&& persistedFocusProbeKey(focus) !== catalogWinnerKey
|
||||
);
|
||||
if (
|
||||
focus
|
||||
&& !staleCollectFocus
|
||||
&& !staleDiscriminatorFocus
|
||||
&& (sessionOutcome !== "adopt_representative"
|
||||
&& sessionOutcome !== "validated_range"
|
||||
&& sessionOutcome !== "exact_minute_confirmed"
|
||||
@@ -1018,18 +1055,6 @@ export function buildMethodFollowupPlan(input: {
|
||||
|
||||
let next: MethodFollowup | null = null;
|
||||
const stage = input.precisionStage ?? null;
|
||||
const askedKeys = new Set([
|
||||
...(input.askedProbeKeys ?? []),
|
||||
...askedKeysFromLedgerEvidence(input.evidence),
|
||||
]);
|
||||
const rankedDiscriminators = dashaCovered && meetsAcceptanceEventQuality(input.evidence)
|
||||
? rankRenderableDiscriminators({
|
||||
eventProbes: remainingConflictProbes(input.eventProbes, input.evidence, declined, askedKeys),
|
||||
contrastProbe: !candidatesSeparated ? contrastProbe : null,
|
||||
askedKeys,
|
||||
})
|
||||
: [];
|
||||
const bestDiscriminator = rankedDiscriminators[0] ?? null;
|
||||
const followupFromRanked = (ranked: RankedDiscriminator): MethodFollowup => {
|
||||
if (ranked.kind === "event" && ranked.eventProbe) {
|
||||
const conflictProbe = ranked.eventProbe;
|
||||
|
||||
@@ -87,6 +87,10 @@ import {
|
||||
resolveEvidenceQuote,
|
||||
} from "@/lib/rectification-agentic/v9/evidence-quote";
|
||||
import { projectTurnDecision } from "@/lib/rectification-agentic/v9/turn-decision";
|
||||
import {
|
||||
contrastPacketFromLatestResult,
|
||||
rectificationFollowupCatalog,
|
||||
} from "@/lib/rectification-agentic/v9/decision-from-dossier";
|
||||
import { QUESTION_CONTRACT_VERSION } from "@/lib/rectification-agentic/v9/probe-question-contract";
|
||||
import {
|
||||
posteriorMap,
|
||||
@@ -174,43 +178,17 @@ function holdoutStatusFromLatest(latest: NonNullable<DossierForTools["latestResu
|
||||
const hasHoldout = inference.events.some((item) => item.usage === "holdout");
|
||||
if (!hasHoldout) return "unavailable";
|
||||
if (inference.holdout_passed === true) return "passed";
|
||||
if (inference.holdout_passed === false || inference.result_status === "validation_failed") return "failed";
|
||||
if (inference.holdout_passed === false || inference.result_status === "validation_failed") {
|
||||
return "failed";
|
||||
}
|
||||
return "not_started";
|
||||
}
|
||||
|
||||
function holdoutEventsFromLatest(latest: NonNullable<DossierForTools["latestResult"]> | null | undefined) {
|
||||
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
||||
return (inference?.events ?? [])
|
||||
.filter((item) => item.usage === "holdout")
|
||||
.map((item) => ({ domain: item.domain, year: item.year }));
|
||||
}
|
||||
|
||||
function contrastPacketFromLatest(
|
||||
latest: NonNullable<DossierForTools["latestResult"]> | null | undefined,
|
||||
evidence: DossierForTools["evidence"] = [],
|
||||
) {
|
||||
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
||||
const windowScan = windowScanFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const refinement = refinementFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const candidateTimes = candidateScoresFromLatest(latest).map((item) => item.time);
|
||||
const vargaDifferences = [
|
||||
...(windowScan?.d9_candidates_differ && windowScan.d9_sign_names.length >= 2
|
||||
? [{ layer: "d9", signs: windowScan.d9_sign_names }]
|
||||
: []),
|
||||
...(windowScan?.d10_candidates_differ && windowScan.d10_sign_names.length >= 2
|
||||
? [{ layer: "d10", signs: windowScan.d10_sign_names }]
|
||||
: []),
|
||||
];
|
||||
return buildCandidateContrastPacket({
|
||||
candidateSetVersion: inference?.candidate_set_id ?? latest?.resultId ?? "none",
|
||||
calculationResultId: latest?.resultId ?? null,
|
||||
engineProbes: refinement.discriminating_event_probes,
|
||||
vargaDifferences,
|
||||
candidateTimes,
|
||||
transitions: windowScan?.transitions ?? [],
|
||||
askedKeys: askedDiscriminatorKeys(latest?.decisionReceipt, evidence),
|
||||
volunteeredDomains: volunteeredDomainsFromEvidence(evidence),
|
||||
});
|
||||
return contrastPacketFromLatestResult(latest, evidence);
|
||||
}
|
||||
|
||||
function snapshotSourceFromDossier(
|
||||
@@ -251,9 +229,9 @@ function safeCaseProjection(
|
||||
const latest = dossier.latestResult;
|
||||
const windowScan = windowScanFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const observations = internalObservationsFromWindowScan(windowScan);
|
||||
const refinement = refinementFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
||||
const catalog = rectificationFollowupCatalog(latest, dossier.evidence);
|
||||
const contrastPacket = catalog.contrastPacket;
|
||||
const accepted = Boolean(caseRow.acceptedTime);
|
||||
const contrastPacket = contrastPacketFromLatest(latest, dossier.evidence);
|
||||
const candidateScores = candidateScoresFromLatest(latest);
|
||||
const holdoutValidation = holdoutStatusFromLatest(latest);
|
||||
const separation = evaluateCandidateSeparation(candidateScores);
|
||||
@@ -268,19 +246,11 @@ function safeCaseProjection(
|
||||
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
||||
observations,
|
||||
sessionOutcome: "collect_evidence",
|
||||
precisionStage: refinement.precision_stage?.current,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
askedProbeKeys: askedDiscriminatorKeys(latest?.decisionReceipt, dossier.evidence),
|
||||
...catalog,
|
||||
birthDate: String(compute.baselineBirthSnapshot.birth_date ?? "") || null,
|
||||
accepted,
|
||||
candidatesSeparated: separation.sufficient,
|
||||
contrastPacket,
|
||||
holdoutValidation,
|
||||
holdoutEvents: holdoutEventsFromLatest(latest),
|
||||
});
|
||||
const userStopped = dossier.case.status === "paused";
|
||||
const confirmationGate = buildConfirmationGate({
|
||||
@@ -324,17 +294,10 @@ function safeCaseProjection(
|
||||
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
||||
observations,
|
||||
sessionOutcome,
|
||||
precisionStage: refinement.precision_stage?.current,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
askedProbeKeys: askedDiscriminatorKeys(latest?.decisionReceipt, dossier.evidence),
|
||||
...catalog,
|
||||
birthDate: String(compute.baselineBirthSnapshot.birth_date ?? "") || null,
|
||||
accepted,
|
||||
candidatesSeparated: separation.sufficient,
|
||||
contrastPacket,
|
||||
holdoutValidation,
|
||||
});
|
||||
const birthContext = safeBirthContext(compute);
|
||||
@@ -630,8 +593,7 @@ function collectingFollowupForParsed(
|
||||
) {
|
||||
const windowScan = windowScanFromDecisionReceipt(latest.decisionReceipt ?? null);
|
||||
const observations = internalObservationsFromWindowScan(windowScan);
|
||||
const refinement = refinementFromDecisionReceipt(latest.decisionReceipt ?? null);
|
||||
const contrastPacket = contrastPacketFromLatest(latest, parsed.evidence);
|
||||
const catalog = rectificationFollowupCatalog(latest, parsed.evidence);
|
||||
const separation = evaluateCandidateSeparation(candidateScoresFromLatest(latest));
|
||||
return buildMethodFollowupPlan({
|
||||
evidence: parsed.evidence,
|
||||
@@ -639,18 +601,10 @@ function collectingFollowupForParsed(
|
||||
declinedTopics: parsed.conversationSummary.declinedSkippedTopics,
|
||||
observations,
|
||||
sessionOutcome: "collect_evidence",
|
||||
precisionStage: refinement.precision_stage?.current,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
askedProbeKeys: askedDiscriminatorKeys(latest.decisionReceipt, parsed.evidence),
|
||||
...catalog,
|
||||
accepted: Boolean(parsed.case.acceptedTime),
|
||||
candidatesSeparated: separation.sufficient,
|
||||
contrastPacket,
|
||||
holdoutValidation: holdoutStatusFromLatest(latest),
|
||||
holdoutEvents: holdoutEventsFromLatest(latest),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -660,7 +614,8 @@ function sessionAwareFollowupForParsed(
|
||||
options?: { birthDate?: string | null; snapshotCurrent?: boolean },
|
||||
) {
|
||||
const collectingPlan = collectingFollowupForParsed(parsed, latest);
|
||||
const contrastPacket = contrastPacketFromLatest(latest, parsed.evidence);
|
||||
const catalog = rectificationFollowupCatalog(latest, parsed.evidence);
|
||||
const contrastPacket = catalog.contrastPacket;
|
||||
const candidateScores = candidateScoresFromLatest(latest);
|
||||
const holdoutValidation = holdoutStatusFromLatest(latest);
|
||||
const sessionOutcome = conversationalSessionOutcome({
|
||||
@@ -690,7 +645,6 @@ function sessionAwareFollowupForParsed(
|
||||
}
|
||||
const windowScan = windowScanFromDecisionReceipt(latest.decisionReceipt ?? null);
|
||||
const observations = internalObservationsFromWindowScan(windowScan);
|
||||
const refinement = refinementFromDecisionReceipt(latest.decisionReceipt ?? null);
|
||||
const separation = evaluateCandidateSeparation(candidateScores);
|
||||
return {
|
||||
plan: buildMethodFollowupPlan({
|
||||
@@ -699,19 +653,11 @@ function sessionAwareFollowupForParsed(
|
||||
declinedTopics: parsed.conversationSummary.declinedSkippedTopics,
|
||||
observations,
|
||||
sessionOutcome,
|
||||
precisionStage: refinement.precision_stage?.current,
|
||||
nakshatraBoundary: refinement.nakshatra_boundary,
|
||||
oosBlindPrompts: refinement.oos_blind_prompts,
|
||||
eventProbes: refinement.discriminating_event_probes,
|
||||
eventClarificationProbes: refinement.event_clarification_probes,
|
||||
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
||||
askedProbeKeys: askedDiscriminatorKeys(latest.decisionReceipt, parsed.evidence),
|
||||
...catalog,
|
||||
birthDate: options?.birthDate ?? null,
|
||||
accepted: Boolean(parsed.case.acceptedTime),
|
||||
candidatesSeparated: separation.sufficient,
|
||||
contrastPacket,
|
||||
holdoutValidation,
|
||||
holdoutEvents: holdoutEventsFromLatest(latest),
|
||||
}),
|
||||
contrastPacket,
|
||||
sessionOutcome,
|
||||
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
publicDecisionFields,
|
||||
} from "../src/lib/rectification-agentic/core/rectification-decision.ts";
|
||||
import { decideNextAction } from "../src/lib/rectification-agentic/core/decide-next-action.ts";
|
||||
import { selectDiscriminatorProbe } from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
||||
import { contrastPacketFromDossier, overlayPublicDecision } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts";
|
||||
import { conversationalSessionOutcome } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
||||
import { buildMethodFollowupPlan, conversationalSessionOutcome } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
||||
|
||||
const SEPARATED = [
|
||||
{ time: "04:48", score: 58 },
|
||||
@@ -145,6 +146,158 @@ test("recorded education evidence does not suppress an unasked D24 discriminator
|
||||
assert.equal(packet.probes[0]?.expectedOutcomes.at(-1)?.outcomeId, "unsure");
|
||||
});
|
||||
|
||||
test("scored inference catalog outranks a low-gain Python career probe when snapshot candidates are empty", () => {
|
||||
const careerOutcomes = [
|
||||
{ answer_class: "yes", supports: ["04:45", "05:00", "05:14"], conflicts: ["05:15"] },
|
||||
{ answer_class: "weak_yes", supports: ["04:45", "05:00", "05:14"], conflicts: ["05:15"] },
|
||||
{ answer_class: "no", supports: ["05:15"], conflicts: ["04:45", "05:00", "05:14"] },
|
||||
{ answer_class: "unsure", supports: [], conflicts: [] },
|
||||
];
|
||||
const d24Outcomes = [
|
||||
{ answer_class: "yes", supports: ["04:47"], conflicts: ["04:51", "04:53", "04:59", "05:00", "05:07", "05:12", "05:14", "05:15"] },
|
||||
{ answer_class: "weak_yes", supports: ["04:51", "04:53"], conflicts: ["04:47", "04:59", "05:00", "05:07", "05:12", "05:14", "05:15"] },
|
||||
{ answer_class: "no", supports: ["04:59"], conflicts: ["04:47", "04:51", "04:53", "05:00", "05:07", "05:12", "05:14", "05:15"] },
|
||||
{ answer_class: "unsure", supports: [], conflicts: [] },
|
||||
];
|
||||
const inferenceCandidates = [
|
||||
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 22, posterior_score: 22, probability: 0.51, status: "active", rank: 1, strong_conflict_count: 0 },
|
||||
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 17, posterior_score: 17, probability: 0.15, status: "active", rank: 2, strong_conflict_count: 0 },
|
||||
{ id: "05:12", time: "05:12", cluster_range: ["05:12", "05:14"], prior_score: 17, posterior_score: 17, probability: 0.15, status: "equivalent", rank: 3, strong_conflict_count: 0 },
|
||||
{ id: "05:14", time: "05:14", cluster_range: ["05:12", "05:14"], prior_score: 17, posterior_score: 17, probability: 0.15, status: "equivalent", rank: 4, strong_conflict_count: 0 },
|
||||
{ id: "04:47", time: "04:47", cluster_range: ["04:47", "04:47"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 5, strong_conflict_count: 0 },
|
||||
{ id: "04:51", time: "04:51", cluster_range: ["04:51", "04:51"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 6, strong_conflict_count: 0 },
|
||||
{ id: "04:53", time: "04:53", cluster_range: ["04:53", "04:53"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 7, strong_conflict_count: 0 },
|
||||
{ id: "04:59", time: "04:59", cluster_range: ["04:59", "04:59"], prior_score: 6, posterior_score: 6, probability: 0.01, status: "active", rank: 8, strong_conflict_count: 0 },
|
||||
{ id: "05:15", time: "05:15", cluster_range: ["05:15", "05:15"], prior_score: 3, posterior_score: 3, probability: 0.004, status: "active", rank: 9, strong_conflict_count: 0 },
|
||||
];
|
||||
const evidence = [
|
||||
{ status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2016-09-01", occurredTo: null, eventKind: "education_start" },
|
||||
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: null, eventKind: "relationship_end" },
|
||||
{ status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: null, eventKind: "career_entry" },
|
||||
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null, eventKind: "family_event" },
|
||||
];
|
||||
const dossier = {
|
||||
evidence,
|
||||
conversationSummary: { activeFocus: null, declinedSkippedTopics: [] },
|
||||
latestResult: {
|
||||
resultId: "result-empty-snapshot",
|
||||
candidates: [],
|
||||
decisionReceipt: {
|
||||
discriminating_event_probes: [{
|
||||
role: "distinguish",
|
||||
phase: "candidate_discriminator",
|
||||
year: 2023,
|
||||
year_label: "2023 年前后",
|
||||
domain: "career",
|
||||
event_family: "入职、升职或职责明显加重",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: false,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "时间范围锁定 2023 年前后;领域锁定 career。",
|
||||
choice_kind: "existence",
|
||||
information_gain: 0.56,
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
candidate_split_hash: "500ce694938305201fbab9ba",
|
||||
candidate_ids: ["04:45", "05:00", "05:14", "05:15"],
|
||||
expected_outcomes: careerOutcomes,
|
||||
style_options: [
|
||||
{ label: "明确发生且时间吻合", answer_class: "yes" },
|
||||
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
|
||||
{ label: "明确没有发生", answer_class: "no" },
|
||||
{ label: "这段记不清楚", answer_class: "unsure" },
|
||||
],
|
||||
}],
|
||||
inference_state: {
|
||||
algorithm_version: "rectification-inference-v1",
|
||||
candidate_set_id: "04:45-05:15:04:47,04:51,04:53,04:59,05:00,05:07,05:12,05:14,05:15",
|
||||
revision: 1,
|
||||
phase: "discrimination",
|
||||
result_status: "discriminating",
|
||||
range_start: "04:45",
|
||||
range_end: "05:15",
|
||||
candidates: inferenceCandidates,
|
||||
events: [],
|
||||
probes: [{
|
||||
id: "probe:career.2023.dasha_activation:500ce694938305201fbab9ba",
|
||||
year: 2023,
|
||||
domain: "career",
|
||||
source: "dasha_activation",
|
||||
question: "时间范围锁定 2023 年前后;领域锁定 career。",
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
candidate_ids: ["04:45", "05:00", "05:14", "05:15"],
|
||||
information_gain: 0.56,
|
||||
expected_outcomes: careerOutcomes,
|
||||
candidate_split_hash: "500ce694938305201fbab9ba",
|
||||
}, {
|
||||
id: "contrast:varga.d24.04:47/04:51|04:53/04:59/05:00/05:07|05:12/05:14|05:15",
|
||||
year: 0,
|
||||
domain: "education",
|
||||
source: "varga_contrast",
|
||||
question: "引擎给出的区分机会绑定 D24。",
|
||||
semantic_key: "varga.d24.04:47/04:51|04:53/04:59/05:00/05:07|05:12/05:14|05:15",
|
||||
candidate_ids: ["04:47", "04:51", "04:53", "04:59", "05:00", "05:07", "05:12", "05:14", "05:15"],
|
||||
information_gain: 2.503258334775646,
|
||||
expected_outcomes: d24Outcomes,
|
||||
candidate_split_hash: "04:45-05:15:varga.d24",
|
||||
}],
|
||||
answered_probes: [],
|
||||
rounds: [],
|
||||
entropy: 2.0,
|
||||
representative_time: "05:00",
|
||||
credible_range: ["05:00", "05:14"],
|
||||
},
|
||||
window_scan: {
|
||||
scanned: true,
|
||||
d24_lagna_count: 6,
|
||||
d24_candidates_differ: true,
|
||||
transitions: [
|
||||
{ layer: "d24", at: "04:48", from_sign: "白羊座", to_sign: "金牛座" },
|
||||
{ layer: "d24", at: "04:54", from_sign: "金牛座", to_sign: "双子座" },
|
||||
{ layer: "d24", at: "05:00", from_sign: "双子座", to_sign: "巨蟹座" },
|
||||
{ layer: "d24", at: "05:06", from_sign: "巨蟹座", to_sign: "狮子座" },
|
||||
{ layer: "d24", at: "05:13", from_sign: "狮子座", to_sign: "处女座" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
case: { acceptedTime: null },
|
||||
};
|
||||
const packet = contrastPacketFromDossier(dossier);
|
||||
const selected = selectDiscriminatorProbe(packet);
|
||||
assert.match(selected?.semanticKey ?? "", /^varga\.d24\./);
|
||||
assert.ok((selected?.informationGain ?? 0) > 2);
|
||||
assert.doesNotMatch(selected?.semanticKey ?? "", /career\.2023/);
|
||||
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
eventProbes: packet.probes.flatMap((probe) => probe.semanticKey.startsWith("career.")
|
||||
? [{
|
||||
year: 2023,
|
||||
year_label: "2023 年前后",
|
||||
domain: "career",
|
||||
event_family: "入职、升职或职责明显加重",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: false,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "时间范围锁定 2023 年前后。",
|
||||
role: "distinguish",
|
||||
phase: "candidate_discriminator",
|
||||
information_gain: 0.56,
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
candidate_split_hash: "500ce694938305201fbab9ba",
|
||||
candidate_ids: ["04:45", "05:00", "05:14", "05:15"],
|
||||
expected_outcomes: careerOutcomes,
|
||||
}]
|
||||
: []),
|
||||
contrastPacket: packet,
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
assert.match(plan.next_followup?.semantic_key ?? "", /^varga\.d24\./);
|
||||
assert.doesNotMatch(plan.next_followup?.semantic_key ?? "", /career\.2023/);
|
||||
});
|
||||
|
||||
test("public candidate cards follow the inference ranking and hide an inconsistent state", () => {
|
||||
const decision = decideRectification({
|
||||
methodCoverageAll: true,
|
||||
|
||||
@@ -1825,6 +1825,52 @@ test("low-gain career event probe does not outrank a renderable high-gain D24 co
|
||||
assert.ok((plan.next_followup?.selection_score ?? 0) > 0.56);
|
||||
});
|
||||
|
||||
test("already-open low-gain career card yields to the high-gain D24 catalog winner", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: CLASSIC_COVERAGE.filter((item) => item.domain !== "horary"),
|
||||
eventProbes: [{
|
||||
...CAREER_CONFLICT_PROBE,
|
||||
year: 2023,
|
||||
year_label: "2023 年前后",
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
information_gain: 0.56,
|
||||
candidate_split_hash: "set-test:career:2023",
|
||||
}],
|
||||
contrastPacket: {
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
vargaDifferences: [],
|
||||
probes: [{
|
||||
probeId: "contrast:varga.d24.05:00/05:07|05:10|05:14",
|
||||
candidateSetVersion: "05:00-05:14",
|
||||
question: "当前几个候选在学业盘上还分得开。",
|
||||
expectedOutcomes: [
|
||||
{ outcomeId: "yes", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:07", "05:10", "05:14"] },
|
||||
{ outcomeId: "no", supportsCandidateIds: ["05:07", "05:10", "05:14"], conflictsCandidateIds: ["05:00"] },
|
||||
],
|
||||
candidateSplitHash: "varga.d24.05:00/05:07|05:10|05:14",
|
||||
informationGain: 2.503258,
|
||||
sourceFeatures: [{ technique: "D24", calculationResultId: RESULT_ID }],
|
||||
domain: "education",
|
||||
year: null,
|
||||
semanticKey: "varga.d24.05:00/05:07|05:10|05:14",
|
||||
choiceKind: "event_quality",
|
||||
}],
|
||||
},
|
||||
candidatesSeparated: false,
|
||||
activeFocus: {
|
||||
intent: "distinguish_candidates",
|
||||
targetDomain: "career",
|
||||
targetKind: "career_entry",
|
||||
expectedAnswerSchema: {
|
||||
semantic_key: "career.2023.dasha_activation",
|
||||
candidate_split_hash: "set-test:career:2023",
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(plan.next_followup?.semantic_key, "varga.d24.05:00/05:07|05:10|05:14");
|
||||
assert.doesNotMatch(plan.next_followup?.semantic_key ?? "", /career\.2023/);
|
||||
});
|
||||
|
||||
const DUMP_COVERAGE = [
|
||||
{
|
||||
status: "confirmed" as const,
|
||||
|
||||
Reference in New Issue
Block a user