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>
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import type { DiscriminatingEventProbe } from "../v9/refinement-packet.ts";
|
|
import { candidateIdsFromProbe, distinguishContractErrors } from "./distinguish-contract.ts";
|
|
import type { AnswerClass, ConflictProbe, ProbeOutcome } from "./types.ts";
|
|
|
|
const ANSWER_CLASSES: ReadonlySet<string> = new Set(["yes", "weak_yes", "no", "unsure"]);
|
|
|
|
export type EngineProbeFields = DiscriminatingEventProbe;
|
|
|
|
export function probeFromEngine(probe: EngineProbeFields): ConflictProbe | null {
|
|
if (distinguishContractErrors({ ...probe, role: probe.role ?? "distinguish" }).length > 0) {
|
|
return null;
|
|
}
|
|
const semanticKey = probe.semantic_key ?? `${probe.domain}.${probe.year}`;
|
|
const splitHash = probe.candidate_split_hash
|
|
?? `${probe.candidate_set_version ?? ""}:${probe.domain}:${probe.year}`;
|
|
const outcomes = outcomesFromEngine(probe.expected_outcomes);
|
|
const candidateIds = probe.candidate_ids?.length
|
|
? [...probe.candidate_ids]
|
|
: candidateIdsFromProbe(probe);
|
|
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,
|
|
candidate_split_hash: splitHash,
|
|
domain: probe.domain,
|
|
year: probe.year,
|
|
question: probe.user_meaning,
|
|
candidate_ids: candidateIds,
|
|
expected_outcomes: outcomes,
|
|
information_gain: probe.information_gain ?? 0,
|
|
source: probe.source,
|
|
...(probe.choice_kind === "varga_style"
|
|
|| probe.choice_kind === "event_quality"
|
|
|| 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[] {
|
|
if (!rows) return [];
|
|
const parsed: ProbeOutcome[] = [];
|
|
for (const row of rows) {
|
|
if (!ANSWER_CLASSES.has(row.answer_class)) continue;
|
|
parsed.push({
|
|
answer_class: row.answer_class as AnswerClass,
|
|
supports: row.supports,
|
|
conflicts: row.conflicts,
|
|
});
|
|
}
|
|
return parsed;
|
|
}
|