fix(rectification): narrate the credible range and stop scoring periods by length (BUG-569–570)

Choice copy was comparing the search window, so every answer said the range had not changed. Block-scan summed raw scores, so longer afternoon windows won before any evidence difference.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 10:03:28 +08:00
parent d6ee8cbb26
commit 517df002b5
17 changed files with 312 additions and 46 deletions
@@ -592,8 +592,8 @@ export async function applyRectificationChoice(
appliedInference: persistable && scoring,
answerClass,
deltasByCluster: clusterScoreDeltas(previous.candidates, appliedScoreDeltas),
rangeBefore: [previous.range_start, previous.range_end],
rangeAfter: [applied.state.range_start, applied.state.range_end],
credibleBefore: previous.credible_range,
credibleAfter: applied.state.credible_range,
});
const evidenceFp = dossier.latestResult?.evidenceLedgerFingerprint
?? evidenceLedgerFingerprint(dossier.evidence);
@@ -102,8 +102,8 @@ export function composeChoiceNarration(input: {
appliedInference: boolean;
answerClass?: AnswerClass | null;
deltasByCluster?: readonly ClusterScoreDelta[];
rangeBefore?: readonly [string, string] | null;
rangeAfter?: readonly [string, string] | null;
credibleBefore?: readonly [string, string] | null;
credibleAfter?: readonly [string, string] | null;
}): string {
if (input.optionId === "stop") {
return `已记录你的选择,并结束本次校正,交付当前可信区间和代表性工作时间。${RECTIFICATION_TERMINATION_COPY}`;
@@ -119,7 +119,7 @@ export function composeChoiceNarration(input: {
}
if (input.appliedInference) {
const movement = explainScoreMovement(input.deltasByCluster ?? []);
const range = explainRangeChange(input.rangeBefore, input.rangeAfter);
const range = explainRangeChange(input.credibleBefore, input.credibleAfter);
if (movement || range) {
return ["已记录你的选择", movement, range].filter(Boolean).join("。") + "。";
}
@@ -3,6 +3,7 @@
* The model writes the stem; this module writes "why" and "what answering does".
*/
import { publicRectificationMethodLabel } from "../../rectification-varga-sentence.ts";
import type { AnswerClass } from "../core/types.ts";
import type { DiscriminatingEventProbe, ProbeExpectedOutcome } from "./refinement-packet.ts";
@@ -31,11 +32,6 @@ export type ProbeUserExplain = Readonly<{
answer_impact: ProbeAnswerImpact;
}>;
const TRACK_LABEL: Readonly<Record<string, string>> = {
vimshottari: "毗湿奴多利",
narayana: "那罗延",
};
const CLOCK = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
export const PROBE_EXPLAIN_COPY = {
@@ -91,7 +87,10 @@ function rangesForTimes(
}
function trackLabels(tracks: readonly string[] | undefined): string {
const labels = [...new Set((tracks ?? []).map((track) => TRACK_LABEL[track]).filter(Boolean))];
const labels = [...new Set((tracks ?? []).flatMap((track) => {
const label = publicRectificationMethodLabel(track);
return label ? [label] : [];
}))];
return labels.join("、");
}
@@ -22,11 +22,17 @@ export const PUBLIC_RECTIFICATION_METHOD_LABELS: Readonly<Record<PublicRectifica
"functional-benefic-malefic": "本命功能吉凶星",
};
export function publicRectificationMethodLabel(methodOrTrack: string): string | null {
const direct = PUBLIC_RECTIFICATION_METHOD_LABELS[methodOrTrack as PublicRectificationMethod];
if (direct) return direct;
return PUBLIC_RECTIFICATION_METHOD_LABELS[`${methodOrTrack}-dasha` as PublicRectificationMethod] ?? null;
}
export function vargaSentenceFromMethods(
methods: readonly string[] | null | undefined,
): string | null {
const labels = [...new Set((methods ?? []).flatMap((method) => {
const label = PUBLIC_RECTIFICATION_METHOD_LABELS[method as PublicRectificationMethod];
const label = publicRectificationMethodLabel(method);
return label ? [label] : [];
}))];
if (labels.length === 0) return null;