import { evaluateCandidateSeparation, type CandidateScoreRow, type CandidateSeparation, } from "./candidate-separation.ts"; import type { CandidateDiscriminatorProbe } from "./candidate-contrast-packet.ts"; export type RectificationNextActionType = | "ask_fact_collection" | "ask_candidate_discriminator" | "ask_holdout_validation" | "offer_provisional_range" | "ready_to_adopt"; export type HoldoutValidationStatus = "not_started" | "passed" | "failed" | "unavailable"; export type DecideNextActionInput = Readonly<{ methodCoverageAll: boolean; proposeAllowed: boolean; confirmationAllowed?: boolean; userStopped?: boolean; selectionAllowed?: boolean; snapshotCurrent?: boolean; candidateScores: readonly CandidateScoreRow[]; discriminatorProbe?: CandidateDiscriminatorProbe | null; holdoutValidation?: HoldoutValidationStatus; }>; export type RectificationNextAction = Readonly<{ type: RectificationNextActionType; separation: CandidateSeparation; probe: CandidateDiscriminatorProbe | null; }>; /** * Coverage complete only unlocks discrimination. It never grants adoption. */ export function decideNextAction(input: DecideNextActionInput): RectificationNextAction { const separation = evaluateCandidateSeparation(input.candidateScores); const probe = input.discriminatorProbe ?? null; const holdout = input.holdoutValidation ?? "unavailable"; const userStopped = input.userStopped === true; if (input.confirmationAllowed === true) { return { type: "ready_to_adopt", separation, probe: null }; } if (userStopped && input.selectionAllowed === true) { if (!separation.sufficient) { return { type: "offer_provisional_range", separation, probe: null }; } return { type: "ready_to_adopt", separation, probe: null }; } if (!input.methodCoverageAll || input.snapshotCurrent === false) { return { type: "ask_fact_collection", separation, probe: null }; } if (!separation.sufficient) { if (probe) { return { type: "ask_candidate_discriminator", separation, probe }; } return { type: "offer_provisional_range", separation, probe: null }; } if (holdout === "not_started") { return { type: "ask_holdout_validation", separation, probe: null }; } if (holdout === "failed") { if (probe) { return { type: "ask_candidate_discriminator", separation, probe }; } return { type: "offer_provisional_range", separation, probe: null }; } if (input.proposeAllowed !== true) { return { type: "ask_fact_collection", separation, probe: null }; } return { type: "ready_to_adopt", separation, probe: null }; } export function sessionKindFromNextAction( type: RectificationNextActionType, ): "collect_evidence" | "discriminate_candidates" | "validate_holdout" | "provisional_range" | "adopt_representative" { 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"; return "adopt_representative"; } export function offerSessionKinds(): readonly string[] { return ["adopt_representative", "awaiting_confirmation", "provisional_range"]; }