/** * Single authoritative rectification decision. * * phase, nextAction, canOfferRange, canAdopt, and canConfirmExactMinute are * computed here. precision_stage, selection_allowed, and active_focus policy * are derived from this object and must not be judged independently. */ import type { CandidateDiscriminatorProbe } from "./candidate-contrast-packet.ts"; import { evaluateCandidateSeparation, type CandidateScoreRow, type CandidateSeparation, } from "./candidate-separation.ts"; import { rangeFromTimes } from "./credible-range.ts"; import type { DroppedProbe } from "../v9/probe-question-contract.ts"; import type { RectificationPhase, ResultStatus } from "./types.ts"; export type RectificationNextActionType = | "ask_fact_collection" | "ask_candidate_discriminator" | "ask_holdout_validation" | "offer_provisional_range" | "complete_with_range" | "ready_to_adopt"; export type HoldoutValidationStatus = "not_started" | "passed" | "failed" | "unavailable"; export type DecisionSessionOutcome = | "collect_evidence" | "discriminate_candidates" | "validate_holdout" | "provisional_range" | "provisional_range_user_stopped" | "completed_with_range" | "validated_range" | "exact_minute_confirmed" | "adopt_representative" | "awaiting_confirmation"; export type CompletionStatus = | "provisional_range_user_stopped" | "validated_range" | "exact_minute_confirmed"; export type DerivedPrecisionStage = "collect_events" | "theme_refine" | "ready_to_adopt"; export type RectificationDecision = Readonly<{ phase: RectificationPhase; nextAction: RectificationNextActionType; sessionOutcome: DecisionSessionOutcome; resultStatus: ResultStatus; canOfferRange: boolean; canAdopt: boolean; canConfirmExactMinute: boolean; selectionAllowed: boolean; proposeAllowed: boolean; precisionStage: DerivedPrecisionStage; activeFocusPolicy: "keep" | "close"; completionStatus: CompletionStatus | null; validated: boolean; credibleRange: readonly [string, string] | null; representativeTime: string | null; separation: CandidateSeparation; probe: CandidateDiscriminatorProbe | null; holdoutValidation: HoldoutValidationStatus; droppedProbes: readonly DroppedProbe[]; }>; export type DecideRectificationInput = Readonly<{ methodCoverageAll: boolean; confirmationAllowed?: boolean; userStopped?: boolean; snapshotCurrent?: boolean; trainingGateOpen?: boolean; candidateScores: readonly CandidateScoreRow[]; discriminatorProbe?: CandidateDiscriminatorProbe | null; holdoutValidation?: HoldoutValidationStatus; accepted?: boolean; inferenceCredibleRange?: readonly [string, string] | null; engineAcceptAllowed?: boolean; engineProposeAllowed?: boolean; datedMethodCollectOpen?: boolean; }>; export function decideRectification(input: DecideRectificationInput): RectificationDecision { const separation = evaluateCandidateSeparation(input.candidateScores); const probe = input.discriminatorProbe ?? null; const holdout = input.holdoutValidation ?? "unavailable"; const userStopped = input.userStopped === true; const confirmationAllowed = input.confirmationAllowed === true; const range = input.inferenceCredibleRange ?? rangeFromTimes(separation.credibleRange) ?? (separation.representativeTime ? [separation.representativeTime, separation.representativeTime] as const : null); if (input.accepted) { return finish(confirmationAllowed ? "awaiting_confirmation" : "adopt_representative", { input, separation, holdout, range, probe: null, canConfirmExactMinute: confirmationAllowed, }); } if (confirmationAllowed) { return finish("awaiting_confirmation", { input, separation, holdout, range, probe: null, canConfirmExactMinute: true, }); } const canDiscriminateDespiteCoverage = Boolean(probe) && !separation.sufficient && input.trainingGateOpen !== false; const coverageBlocks = (!input.methodCoverageAll && !canDiscriminateDespiteCoverage) || input.trainingGateOpen === false; if (coverageBlocks && !(userStopped && input.candidateScores.length > 0)) { const trainingOpen = input.trainingGateOpen !== false; const engineOffers = input.engineAcceptAllowed === true || input.engineProposeAllowed === true; if ( trainingOpen && input.candidateScores.length > 0 && !probe && engineOffers && input.datedMethodCollectOpen !== true ) { return offerRangeWithoutAdopt(separation, holdout, range); } return collect(separation, holdout, range, probe); } if (input.snapshotCurrent === false) { if (probe && !userStopped && input.trainingGateOpen !== false) { return discriminate(separation, holdout, range, probe); } if (!(userStopped && input.candidateScores.length > 0)) { return collect(separation, holdout, range, probe); } } if (!separation.sufficient) { if (probe && !userStopped) { return discriminate(separation, holdout, range, probe); } return completeWithRange(separation, holdout, range, userStopped ? "user_stopped" : "offer"); } if (holdout === "not_started" && !userStopped) { return holdoutValidation(separation, range); } if (holdout === "failed") { if (probe && !userStopped) { return discriminate(separation, holdout, range, probe); } return completeWithRange(separation, holdout, range, "exhausted"); } if (holdout === "unavailable") { if (userStopped) { return completeWithRange(separation, holdout, range, "user_stopped"); } return finish("adopt_representative", { input, separation, holdout, range, probe: null, canConfirmExactMinute: false, }); } if (userStopped && holdout !== "passed") { return completeWithRange(separation, holdout, range, "user_stopped"); } return finish("adopt_representative", { input, separation, holdout, range, probe: null, canConfirmExactMinute: false, }); } function collect( separation: CandidateSeparation, holdout: HoldoutValidationStatus, range: readonly [string, string] | null, probe: CandidateDiscriminatorProbe | null, ): RectificationDecision { return { phase: "event_collection", nextAction: "ask_fact_collection", sessionOutcome: "collect_evidence", resultStatus: "insufficient_evidence", canOfferRange: false, canAdopt: false, canConfirmExactMinute: false, selectionAllowed: false, proposeAllowed: false, precisionStage: "collect_events", activeFocusPolicy: "keep", completionStatus: null, validated: false, credibleRange: range, representativeTime: separation.representativeTime, separation, probe, holdoutValidation: holdout, droppedProbes: [], }; } function offerRangeWithoutAdopt( separation: CandidateSeparation, holdout: HoldoutValidationStatus, range: readonly [string, string] | null, ): RectificationDecision { return { phase: "discrimination", nextAction: "offer_provisional_range", sessionOutcome: "provisional_range", resultStatus: "insufficient_evidence", canOfferRange: true, canAdopt: false, canConfirmExactMinute: false, selectionAllowed: false, proposeAllowed: false, precisionStage: "theme_refine", activeFocusPolicy: "close", completionStatus: null, validated: false, credibleRange: range, representativeTime: separation.representativeTime, separation, probe: null, holdoutValidation: holdout, droppedProbes: [], }; } export function isNonConvergingRangeOffer(decision: Pick< RectificationDecision, "canOfferRange" | "canAdopt" | "canConfirmExactMinute" | "nextAction" >): boolean { return decision.canOfferRange && !decision.canAdopt && !decision.canConfirmExactMinute && decision.nextAction === "offer_provisional_range"; } export const REPRESENTATIVE_MINUTE_DISCLAIMER = "代表分钟只是代表性候选,不是已确认的唯一出生分钟。"; export function nonConvergingRangeNarration(input: { credibleRange?: readonly [string, string] | null; representativeTime?: string | null; } = {}): string { const range = input.credibleRange; const representative = input.representativeTime?.trim() || null; const rangeText = range?.[0] && range[1] ? range[0] === range[1] ? range[0] : `${range[0]}–${range[1]}` : null; if (rangeText && representative) { return `当前可信区间是 ${rangeText},代表分钟 ${representative}。${REPRESENTATIVE_MINUTE_DISCLAIMER}`; } if (rangeText) { return `当前可信区间是 ${rangeText}。${REPRESENTATIVE_MINUTE_DISCLAIMER}`; } if (representative) { return `当前代表分钟 ${representative}。${REPRESENTATIVE_MINUTE_DISCLAIMER}`; } return `当前几个候选还分不开。${REPRESENTATIVE_MINUTE_DISCLAIMER}`; } function discriminate( separation: CandidateSeparation, holdout: HoldoutValidationStatus, range: readonly [string, string] | null, probe: CandidateDiscriminatorProbe, ): RectificationDecision { return { phase: "discrimination", nextAction: "ask_candidate_discriminator", sessionOutcome: "discriminate_candidates", resultStatus: "discriminating", canOfferRange: false, canAdopt: false, canConfirmExactMinute: false, selectionAllowed: false, proposeAllowed: false, precisionStage: "theme_refine", activeFocusPolicy: "keep", completionStatus: null, validated: false, credibleRange: range, representativeTime: separation.representativeTime, separation, probe, holdoutValidation: holdout, droppedProbes: [], }; } function holdoutValidation( separation: CandidateSeparation, range: readonly [string, string] | null, ): RectificationDecision { return { phase: "holdout_validation", nextAction: "ask_holdout_validation", sessionOutcome: "validate_holdout", resultStatus: "discriminating", canOfferRange: false, canAdopt: false, canConfirmExactMinute: false, selectionAllowed: false, proposeAllowed: false, precisionStage: "theme_refine", activeFocusPolicy: "keep", completionStatus: null, validated: false, credibleRange: range, representativeTime: separation.representativeTime, separation, probe: null, holdoutValidation: "not_started", droppedProbes: [], }; } function completeWithRange( separation: CandidateSeparation, holdout: HoldoutValidationStatus, range: readonly [string, string] | null, kind: "user_stopped" | "offer" | "exhausted", ): RectificationDecision { const userStopped = kind === "user_stopped"; const terminal = kind !== "offer"; const nextAction = terminal ? "complete_with_range" : "offer_provisional_range"; const sessionOutcome = userStopped ? "provisional_range_user_stopped" : terminal ? "completed_with_range" : "provisional_range"; return { phase: terminal ? "completed" : "discrimination", nextAction, sessionOutcome, resultStatus: "completed_with_range", canOfferRange: true, canAdopt: true, canConfirmExactMinute: false, selectionAllowed: true, proposeAllowed: true, precisionStage: "ready_to_adopt", activeFocusPolicy: "close", completionStatus: userStopped ? "provisional_range_user_stopped" : null, validated: false, credibleRange: range, representativeTime: separation.representativeTime, separation, probe: null, holdoutValidation: holdout, droppedProbes: [], }; } function finish( fallbackOutcome: "adopt_representative" | "awaiting_confirmation" | "validated_range" | "exact_minute_confirmed", input: { input: DecideRectificationInput; separation: CandidateSeparation; holdout: HoldoutValidationStatus; range: readonly [string, string] | null; probe: CandidateDiscriminatorProbe | null; canConfirmExactMinute: boolean; }, ): RectificationDecision { const completionStatus: CompletionStatus | null = input.canConfirmExactMinute && input.input.accepted ? "exact_minute_confirmed" : input.holdout === "passed" ? "validated_range" : input.input.userStopped === true ? "provisional_range_user_stopped" : null; const kind = input.canConfirmExactMinute ? (input.input.accepted ? "exact_minute_confirmed" : "awaiting_confirmation") : input.holdout === "passed" ? "validated_range" : fallbackOutcome; return { phase: "completed", nextAction: "ready_to_adopt", sessionOutcome: kind, resultStatus: kind === "awaiting_confirmation" || kind === "exact_minute_confirmed" ? "converged" : "completed_with_range", canOfferRange: true, canAdopt: true, canConfirmExactMinute: input.canConfirmExactMinute, selectionAllowed: true, proposeAllowed: true, precisionStage: "ready_to_adopt", activeFocusPolicy: "close", completionStatus, validated: completionStatus === "validated_range" || completionStatus === "exact_minute_confirmed", credibleRange: input.range, representativeTime: input.separation.representativeTime, separation: input.separation, probe: input.probe, holdoutValidation: input.holdout, droppedProbes: [], }; } export function sessionKindFromNextAction( type: RectificationNextActionType, ): DecisionSessionOutcome { if (type === "ask_fact_collection") return "collect_evidence"; if (type === "ask_candidate_discriminator") return "discriminate_candidates"; if (type === "ask_holdout_validation") return "validate_holdout"; if (type === "offer_provisional_range") return "provisional_range"; if (type === "complete_with_range") return "completed_with_range"; return "adopt_representative"; } export function offerSessionKinds(): readonly string[] { return [ "adopt_representative", "awaiting_confirmation", "provisional_range", "provisional_range_user_stopped", "completed_with_range", "validated_range", "exact_minute_confirmed", ]; } export function publicNextAction(decision: RectificationDecision): Readonly<{ type: RectificationNextActionType; session_outcome: DecisionSessionOutcome; completion_status: CompletionStatus | null; validated: boolean; can_offer_range: boolean; can_adopt: boolean; can_confirm_exact_minute: boolean; selection_allowed: boolean; propose_allowed: boolean; precision_stage: DerivedPrecisionStage; representative_time: string | null; credible_range: readonly [string, string] | null; }> { return { type: decision.nextAction, session_outcome: decision.sessionOutcome, completion_status: decision.completionStatus, validated: decision.validated, can_offer_range: decision.canOfferRange, can_adopt: decision.canAdopt, can_confirm_exact_minute: decision.canConfirmExactMinute, selection_allowed: decision.selectionAllowed, propose_allowed: decision.proposeAllowed, precision_stage: decision.precisionStage, representative_time: decision.representativeTime, credible_range: decision.credibleRange, }; } export function publicDecisionFields( decision: RectificationDecision, ): ReturnType { return publicNextAction(decision); }