fix(rectification): stop discriminator followup from dropping user evidence
Decision and question ranking now share contrast option completion, so a missing style card cannot deadlock the interview with a dead-end reply. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,13 @@
|
||||
*/
|
||||
|
||||
import type { AnswerClass, ConflictProbe } from "./types.ts";
|
||||
import { d9StyleLabel, d10StyleLabel } from "../v9/varga-type-tables.ts";
|
||||
import {
|
||||
D9_TYPE_TABLE,
|
||||
D10_TYPE_TABLE,
|
||||
d9StyleLabel,
|
||||
d10StyleLabel,
|
||||
signKey,
|
||||
} from "../v9/varga-type-tables.ts";
|
||||
import {
|
||||
completeStyleOptions,
|
||||
isRenderableProbe,
|
||||
@@ -469,7 +475,7 @@ export function selectDiscriminatorProbe(
|
||||
return inspectDiscriminatorProbes(packet, options).selected;
|
||||
}
|
||||
|
||||
function withCompletedContrastOptions(
|
||||
export function withCompletedContrastOptions(
|
||||
probe: CandidateDiscriminatorProbe,
|
||||
): { ok: true; probe: CandidateDiscriminatorProbe } | { ok: false; reason: DroppedProbe["reason"] } {
|
||||
const mapped = probe.styleOptions?.map((item) => ({
|
||||
@@ -522,22 +528,37 @@ function inferredVargaStyleIncoming(
|
||||
});
|
||||
}
|
||||
|
||||
function knownVargaSigns(layer: "d9" | "d10", tokens: readonly string[]): string[] {
|
||||
const table = layer === "d9" ? D9_TYPE_TABLE : D10_TYPE_TABLE;
|
||||
return tokens.flatMap((token) => {
|
||||
const key = signKey(token);
|
||||
return table[key] ? [key] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function signsFromVargaProbe(
|
||||
probe: CandidateDiscriminatorProbe,
|
||||
): { layer: "d9" | "d10"; signs: string[] } | null {
|
||||
const match = probe.semanticKey.match(/^varga\.(d9|d10)\.(.+)$/);
|
||||
const layer = match?.[1] === "d9" || match?.[1] === "d10" ? match[1] : null;
|
||||
const fromKey = match?.[2]
|
||||
?.split(/[|/]/)
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item && !/^\d{1,2}:\d{2}$/.test(item))
|
||||
?? [];
|
||||
const fromOutcomes = probe.expectedOutcomes.flatMap((row) => {
|
||||
const token = row.outcomeId.replace(/^supports_/, "").trim();
|
||||
return token && !/^\d{1,2}:\d{2}$/.test(token) ? [token] : [];
|
||||
});
|
||||
if (!layer) return null;
|
||||
const fromKey = knownVargaSigns(
|
||||
layer,
|
||||
match?.[2]
|
||||
?.split(/[|/]/)
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item && !/^\d{1,2}:\d{2}$/.test(item))
|
||||
?? [],
|
||||
);
|
||||
const fromOutcomes = knownVargaSigns(
|
||||
layer,
|
||||
probe.expectedOutcomes.flatMap((row) => {
|
||||
const token = row.outcomeId.replace(/^supports_/, "").trim();
|
||||
return token && !ANSWER_CLASSES.has(token) && !/^\d{1,2}:\d{2}$/.test(token) ? [token] : [];
|
||||
}),
|
||||
);
|
||||
const signs = (fromKey.length >= 2 ? fromKey : fromOutcomes).slice(0, 3);
|
||||
if (!layer || signs.length < 2) return null;
|
||||
if (signs.length < 2) return null;
|
||||
return { layer, signs };
|
||||
}
|
||||
|
||||
@@ -568,12 +589,29 @@ function withUnsureOutcome(
|
||||
return rows;
|
||||
}
|
||||
|
||||
function conflictStyleOptions(
|
||||
probe: CandidateDiscriminatorProbe,
|
||||
): ConflictProbe["style_options"] {
|
||||
const rows = probe.styleOptions?.flatMap((item) => {
|
||||
const label = item.label.trim();
|
||||
if (!label) return [];
|
||||
return [{
|
||||
label,
|
||||
answer_class: item.answerClass,
|
||||
...(item.sign ? { sign: item.sign } : {}),
|
||||
}];
|
||||
}) ?? [];
|
||||
return rows.length > 0 ? rows : undefined;
|
||||
}
|
||||
|
||||
export function conflictProbesFromContrast(
|
||||
packet: CandidateContrastPacket | null | undefined,
|
||||
): ConflictProbe[] {
|
||||
return (packet?.probes ?? []).flatMap((probe) => {
|
||||
if (!probe.semanticKey.startsWith("varga.")) return [];
|
||||
const outcomes = probe.expectedOutcomes.flatMap((row, index) => {
|
||||
const completed = withCompletedContrastOptions(probe);
|
||||
const working = completed.ok ? completed.probe : probe;
|
||||
const outcomes = working.expectedOutcomes.flatMap((row, index) => {
|
||||
const answer = ANSWER_CLASSES.has(row.outcomeId)
|
||||
? row.outcomeId as AnswerClass
|
||||
: (["yes", "weak_yes", "no"][index] as AnswerClass | undefined);
|
||||
@@ -585,29 +623,33 @@ export function conflictProbesFromContrast(
|
||||
}];
|
||||
});
|
||||
if (outcomes.length < 2) return [];
|
||||
if (probe.informationGain <= 0) return [];
|
||||
const candidateIds = [...new Set(probe.expectedOutcomes.flatMap((row) => [
|
||||
if (working.informationGain <= 0) return [];
|
||||
const candidateIds = [...new Set(working.expectedOutcomes.flatMap((row) => [
|
||||
...row.supportsCandidateIds,
|
||||
...row.conflictsCandidateIds,
|
||||
]))];
|
||||
if (candidateIds.length < 2) return [];
|
||||
const choiceKind = effectiveContrastChoiceKind(probe);
|
||||
const choiceKind = completed.ok
|
||||
? (working.choiceKind ?? effectiveContrastChoiceKind(working))
|
||||
: effectiveContrastChoiceKind(probe);
|
||||
const styleOptions = conflictStyleOptions(working);
|
||||
return [{
|
||||
id: probe.probeId,
|
||||
semantic_key: probe.semanticKey,
|
||||
candidate_split_hash: probe.candidateSplitHash,
|
||||
domain: probe.domain ?? "career",
|
||||
year: probe.year ?? 0,
|
||||
question: probe.question,
|
||||
id: working.probeId,
|
||||
semantic_key: working.semanticKey,
|
||||
candidate_split_hash: working.candidateSplitHash,
|
||||
domain: working.domain ?? "career",
|
||||
year: working.year ?? 0,
|
||||
question: working.question,
|
||||
candidate_ids: candidateIds,
|
||||
expected_outcomes: outcomes,
|
||||
information_gain: probe.informationGain,
|
||||
information_gain: working.informationGain,
|
||||
source: "varga_contrast",
|
||||
...(choiceKind === "varga_style"
|
||||
|| choiceKind === "event_quality"
|
||||
|| choiceKind === "existence"
|
||||
? { choice_kind: choiceKind }
|
||||
: {}),
|
||||
...(styleOptions ? { style_options: styleOptions } : {}),
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export function probeFromEngine(probe: EngineProbeFields): ConflictProbe | null
|
||||
if (outcomes.length < 2 || candidateIds.length < 2 || (probe.information_gain ?? 0) <= 0) {
|
||||
return null;
|
||||
}
|
||||
const styleOptions = styleOptionsFromEngine(probe.style_options);
|
||||
return {
|
||||
id: `probe:${semanticKey}:${splitHash}`,
|
||||
semantic_key: semanticKey,
|
||||
@@ -36,9 +37,23 @@ export function probeFromEngine(probe: EngineProbeFields): ConflictProbe | null
|
||||
|| probe.choice_kind === "existence"
|
||||
? { choice_kind: probe.choice_kind }
|
||||
: {}),
|
||||
...(styleOptions.length > 0 ? { style_options: styleOptions } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function styleOptionsFromEngine(
|
||||
rows: DiscriminatingEventProbe["style_options"],
|
||||
): NonNullable<ConflictProbe["style_options"]> {
|
||||
return (rows ?? []).flatMap((row) => {
|
||||
if (!ANSWER_CLASSES.has(row.answer_class)) return [];
|
||||
return [{
|
||||
label: row.label,
|
||||
answer_class: row.answer_class as AnswerClass,
|
||||
...(row.sign ? { sign: row.sign } : {}),
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
function outcomesFromEngine(
|
||||
rows: DiscriminatingEventProbe["expected_outcomes"],
|
||||
): ProbeOutcome[] {
|
||||
|
||||
@@ -85,6 +85,11 @@ export type ConflictProbe = Readonly<{
|
||||
information_gain: number;
|
||||
source: string;
|
||||
choice_kind?: ProbeChoiceKind;
|
||||
style_options?: readonly Readonly<{
|
||||
label: string;
|
||||
answer_class: AnswerClass;
|
||||
sign?: string;
|
||||
}>[];
|
||||
}>;
|
||||
|
||||
export type ProbeAnswer = Readonly<{
|
||||
|
||||
Reference in New Issue
Block a user