107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
/**
|
|
* Structured choice / stop commands for birth-time rectification.
|
|
*
|
|
* A/B/C/D taps and “先这样” are server-owned state transitions. They are not
|
|
* chat messages and must not be interpreted by the language model.
|
|
*/
|
|
|
|
import type { AnswerClass } from "../core/types";
|
|
import { classifyChoiceAnswer } from "../core/build-state";
|
|
import type { ChoiceKey, RectificationChoiceCard } from "./choice-card";
|
|
|
|
export const CHOICE_ACTION = "answer_choice" as const;
|
|
export const STOP_ACTION = "stop_and_review" as const;
|
|
export const DETERMINISTIC_CHOICE_MODEL = "deterministic:choice";
|
|
|
|
export const OPTION_ANSWER_CLASS: Readonly<Record<ChoiceKey, AnswerClass>> = {
|
|
A: "yes",
|
|
B: "weak_yes",
|
|
C: "no",
|
|
D: "unsure",
|
|
};
|
|
|
|
export type ChoiceActionKind = typeof CHOICE_ACTION | typeof STOP_ACTION;
|
|
export type ChoiceOptionId = ChoiceKey | "stop";
|
|
export type ChoiceActionStatus = "received" | "applied" | "narrated";
|
|
|
|
export type StructuredProbeDerivedContext = Readonly<{
|
|
sourceType: "structured_probe_answer";
|
|
derivedFromProbeId: string | null;
|
|
referencedDateRange: Readonly<{ start: string; end: string }> | null;
|
|
answerClass: AnswerClass | null;
|
|
}>;
|
|
|
|
export function outcomeIdForOption(optionId: ChoiceKey, schema?: unknown): AnswerClass {
|
|
return classifyChoiceAnswer(optionId, schema);
|
|
}
|
|
|
|
export function focusStatusForOption(optionId: ChoiceOptionId): "resolved" | "declined" | "skipped" {
|
|
if (optionId === "C") return "declined";
|
|
if (optionId === "D" || optionId === "stop") return "skipped";
|
|
return "resolved";
|
|
}
|
|
|
|
export function choiceOptionQuote(card: RectificationChoiceCard, key: ChoiceKey): string {
|
|
return (card.options.find((item) => item.key === key)?.label ?? "").trim();
|
|
}
|
|
|
|
export function userVisibleChoiceLine(card: RectificationChoiceCard, key: ChoiceKey): string {
|
|
const label = choiceOptionQuote(card, key);
|
|
return `${key}. ${label}`.trim();
|
|
}
|
|
|
|
export function derivedProbeDateRange(year: number | null | undefined): { start: string; end: string } | null {
|
|
if (!year || year < 1900 || year > 2100) return null;
|
|
return { start: `${year}-01-01`, end: `${year}-12-31` };
|
|
}
|
|
|
|
export function structuredProbeContext(input: {
|
|
probeId: string | null;
|
|
year?: number | null;
|
|
answerClass: AnswerClass | null;
|
|
}): StructuredProbeDerivedContext {
|
|
return {
|
|
sourceType: "structured_probe_answer",
|
|
derivedFromProbeId: input.probeId,
|
|
referencedDateRange: derivedProbeDateRange(input.year),
|
|
answerClass: input.answerClass,
|
|
};
|
|
}
|
|
|
|
export function quoteIsFromAssistantQuestion(quote: string, question: string): boolean {
|
|
const normalizedQuote = quote.replace(/\s+/g, "");
|
|
const normalizedQuestion = question.replace(/\s+/g, "");
|
|
if (!normalizedQuote || normalizedQuote.length < 2) return false;
|
|
if (normalizedQuote === normalizedQuestion) return true;
|
|
return /(?:19|20)\d{2}年/.test(normalizedQuote) && normalizedQuestion.includes(normalizedQuote);
|
|
}
|
|
|
|
export function composeChoiceNarration(input: {
|
|
optionId: ChoiceOptionId;
|
|
scoring: boolean;
|
|
appliedInference: boolean;
|
|
}): string {
|
|
if (input.optionId === "stop") {
|
|
return "已按你的选择先看到当前范围。独立核对尚未完成,这不是最终校正结果。";
|
|
}
|
|
if (!input.scoring) {
|
|
return "已记录你的选择。这是盘外核对,不会改候选分数。";
|
|
}
|
|
if (input.appliedInference) {
|
|
return "已记录你的选择,并更新了候选比较。";
|
|
}
|
|
return "已记录你的选择。";
|
|
}
|
|
|
|
export function shouldContinueAfterStructuredChoice(nextAction: unknown): boolean {
|
|
if (!nextAction || typeof nextAction !== "object") return false;
|
|
const type = (nextAction as { type?: unknown }).type;
|
|
return type === "ask_fact_collection"
|
|
|| type === "ask_candidate_discriminator"
|
|
|| type === "ask_holdout_validation";
|
|
}
|
|
|
|
export function stableChoiceActionKey(focusId: string, optionId: ChoiceOptionId): string {
|
|
return `${focusId}:${optionId}`;
|
|
}
|