fix(rectification): render adoption carrier on deterministic choice path
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

This commit is contained in:
Jesse_Chen
2026-08-31 02:15:12 +08:00
parent 79bfa73da3
commit ff5998f184
12 changed files with 298 additions and 17 deletions
@@ -729,6 +729,14 @@ ${nextInterview.hostNarration}`
nextInterviewPersisted = true;
}
}
const adoptionNarration = nextAction.type === "offer_provisional_range" && nextAction.can_adopt
? `${nonConvergingRangeNarration({
credibleRange: nextAction.credible_range,
representativeTime: nextAction.representative_time,
})} 可以从下面的时间里选一个采用。`
: null;
if (adoptionNarration) hostNarration = adoptionNarration;
if (
command.deferFollowup !== true
&& (nextInterviewPersisted || !shouldContinueAfterStructuredChoice(nextAction, { nextInterviewPersisted }))
@@ -745,7 +753,7 @@ ${nextInterview.hostNarration}`
}
}
const narration = (nextInterviewPersisted ? hostNarration : null)
const narration = (adoptionNarration || nextInterviewPersisted ? hostNarration : null)
|| (persisted.narration && persisted.narration.trim())
|| hostNarration
|| composeChoiceNarration({
@@ -783,7 +791,6 @@ export async function ensureNonTerminalTurnExit(input: {
accounting: AccountingClient;
userId: string;
caseId: string;
adoptCarrierReady: boolean;
}): Promise<{ persisted: boolean; choiceReady: boolean; hostNarration: string | null }> {
const dossier = await loadV9CaseDossier(input.accounting, input.userId, input.caseId);
if (projectCurrentQuestion(dossier.conversationSummary.activeFocus)) {
@@ -801,7 +808,7 @@ export async function ensureNonTerminalTurnExit(input: {
dossier.case.acceptedTime
|| dossier.case.confirmedTime
|| decision.completionStatus === "provisional_range_user_stopped"
|| (decision.canAdopt && input.adoptCarrierReady)
|| decision.canAdopt
) {
return { persisted: false, choiceReady: false, hostNarration: null };
}
@@ -0,0 +1,53 @@
import { previousInferenceFromReceipt } from "./inference-adapter";
import type { V9CaseDossier } from "./tool-service";
export function slimDecisionReceipt(
receipt: Readonly<Record<string, unknown>>,
dossier: Pick<V9CaseDossier, "case" | "conversationSummary" | "latestResult">,
): Readonly<Record<string, unknown>> {
const activeTimes = new Set<string>();
const inference = previousInferenceFromReceipt(receipt);
const activeInferenceCandidates = inference?.candidates.filter((candidate) => candidate.status === "active") ?? [];
for (const candidate of activeInferenceCandidates.length > 0
? activeInferenceCandidates
: dossier.latestResult?.candidates ?? []) {
activeTimes.add(candidate.time);
}
for (const value of [inference?.representative_time, dossier.latestResult?.representativeTime, dossier.latestResult?.selectedTime]) {
if (typeof value === "string" && value.trim()) activeTimes.add(value.slice(0, 5));
}
const focusSchema = dossier.conversationSummary.activeFocus?.expectedAnswerSchema ?? {};
const currentProbeIds = new Set(
[focusSchema.probe_id, focusSchema.semantic_key, focusSchema.candidate_split_hash]
.filter((value): value is string => typeof value === "string" && value.trim().length > 0),
);
const result = { ...receipt };
const tables = receipt.house_tables_by_time;
if (tables && typeof tables === "object" && !Array.isArray(tables)) {
result.house_tables_by_time = Object.fromEntries(
Object.entries(tables as Record<string, unknown>)
.filter(([time]) => activeTimes.has(time.slice(0, 5))),
);
}
if (receipt.house_table && typeof receipt.house_table === "object" && !Array.isArray(receipt.house_table)) {
const table = receipt.house_table as Record<string, unknown>;
if (typeof table.time === "string" && !activeTimes.has(table.time.slice(0, 5))) {
delete result.house_table;
}
}
for (const key of [
"discriminating_event_probes",
"event_clarification_probes",
"evidence_collection_probes",
]) {
const probes = result[key];
if (!Array.isArray(probes)) continue;
result[key] = probes.filter((probe) => {
if (!probe || typeof probe !== "object" || Array.isArray(probe)) return false;
const row = probe as Record<string, unknown>;
return [row.id, row.probe_id, row.semantic_key, row.candidate_split_hash]
.some((value) => typeof value === "string" && currentProbeIds.has(value));
});
}
return result;
}
@@ -53,6 +53,7 @@ export type RectificationCandidateResult = Readonly<{
candidates: readonly RectificationCandidate[];
overallConfidence: "low" | "medium" | "high";
selectionAllowed: boolean;
canAdopt: boolean;
confirmationAllowed: boolean;
representativeTime: string | null;
selectedTime: string | null;
@@ -267,6 +268,7 @@ export function parseRectificationCandidateResult(value: unknown): Rectification
? snapshot.overallConfidence
: "low",
selectionAllowed: snapshot.selectionAllowed === true || snapshot.selection_allowed === true,
canAdopt: snapshot.canAdopt === true || snapshot.can_adopt === true,
confirmationAllowed: snapshot.confirmationAllowed === true,
representativeTime,
selectedTime,