fix(rectification): share decideFromDossier across projections and treat a sole candidate as a close, not a tie
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* Candidate separation is not event-fit and not method coverage.
|
||||
* 34/33/33 is a tie. A 1-point engine lead is not a winner.
|
||||
* One remaining candidate is a sole candidate, not a parallel range.
|
||||
*/
|
||||
|
||||
export const MIN_SEPARATION_LEAD = 8;
|
||||
|
||||
export type SeparationStatus = "not_separated" | "weak_lead" | "separated";
|
||||
export type SeparationStatus = "not_separated" | "weak_lead" | "separated" | "sole_candidate";
|
||||
|
||||
export type CandidateScoreRow = Readonly<{
|
||||
id?: string;
|
||||
@@ -35,14 +36,36 @@ export function evaluateCandidateSeparation(
|
||||
const top = ranked[0] ?? null;
|
||||
const runnerUp = ranked[1] ?? null;
|
||||
const total = ranked.reduce((sum, item) => sum + Math.max(item.score, 0), 0);
|
||||
const lead = top && runnerUp ? top.score - runnerUp.score : (top ? MIN_SEPARATION_LEAD : 0);
|
||||
const topShare = top && total > 0 ? Math.max(top.score, 0) / total : 0;
|
||||
const status: SeparationStatus = !top || ranked.length < 2 || lead < MIN_SEPARATION_LEAD
|
||||
if (!top) {
|
||||
return {
|
||||
sufficient: false,
|
||||
status: "not_separated",
|
||||
lead: 0,
|
||||
topShare: 0,
|
||||
representativeTime: null,
|
||||
credibleRange: [],
|
||||
ranked,
|
||||
};
|
||||
}
|
||||
if (!runnerUp) {
|
||||
return {
|
||||
sufficient: true,
|
||||
status: "sole_candidate",
|
||||
lead: 0,
|
||||
topShare: 1,
|
||||
representativeTime: top.time,
|
||||
credibleRange: [top.time],
|
||||
ranked,
|
||||
};
|
||||
}
|
||||
const lead = top.score - runnerUp.score;
|
||||
const topShare = total > 0 ? Math.max(top.score, 0) / total : 0;
|
||||
const status: SeparationStatus = lead < MIN_SEPARATION_LEAD
|
||||
? "not_separated"
|
||||
: lead >= 20
|
||||
? "separated"
|
||||
: "weak_lead";
|
||||
const peak = top?.score ?? 0;
|
||||
const peak = top.score;
|
||||
const credibleRange = ranked
|
||||
.filter((item) => peak - item.score < MIN_SEPARATION_LEAD)
|
||||
.map((item) => item.time);
|
||||
@@ -51,8 +74,8 @@ export function evaluateCandidateSeparation(
|
||||
status,
|
||||
lead,
|
||||
topShare,
|
||||
representativeTime: top?.time ?? null,
|
||||
credibleRange: credibleRange.length > 0 ? credibleRange : (top ? [top.time] : []),
|
||||
representativeTime: top.time,
|
||||
credibleRange: credibleRange.length > 0 ? credibleRange : [top.time],
|
||||
ranked,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,6 +141,19 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
}
|
||||
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");
|
||||
}
|
||||
@@ -273,7 +286,7 @@ function completeWithRange(
|
||||
}
|
||||
|
||||
function finish(
|
||||
sessionOutcome: "adopt_representative" | "awaiting_confirmation" | "validated_range" | "exact_minute_confirmed",
|
||||
fallbackOutcome: "adopt_representative" | "awaiting_confirmation" | "validated_range" | "exact_minute_confirmed",
|
||||
input: {
|
||||
input: DecideRectificationInput;
|
||||
separation: CandidateSeparation;
|
||||
@@ -294,7 +307,7 @@ function finish(
|
||||
? (input.input.accepted ? "exact_minute_confirmed" : "awaiting_confirmation")
|
||||
: input.holdout === "passed"
|
||||
? "validated_range"
|
||||
: sessionOutcome;
|
||||
: fallbackOutcome;
|
||||
return {
|
||||
phase: "completed",
|
||||
nextAction: "ready_to_adopt",
|
||||
|
||||
@@ -64,7 +64,6 @@ import {
|
||||
datedDomainsFromEvidence,
|
||||
isStructuredDiscriminator,
|
||||
mentionedVargaKeysFromLedgerEvidence,
|
||||
selectDiscriminatorProbe,
|
||||
vargaLayerCovered,
|
||||
vargaLayerFromSemanticKey,
|
||||
type CandidateContrastPacket,
|
||||
@@ -1116,7 +1115,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
coverage("horary", horaryStatus),
|
||||
];
|
||||
|
||||
const sessionOutcome = input.sessionOutcome ?? "collect_evidence";
|
||||
const sessionOutcome = input.sessionOutcome;
|
||||
const candidatesSeparated = input.candidatesSeparated === true;
|
||||
const contrastProbes = candidatesSeparated ? [] : [...(input.contrastPacket?.probes ?? [])];
|
||||
// Legacy known-event quality cards were never backed by an inference probe.
|
||||
@@ -1221,7 +1220,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
methods,
|
||||
next_followup: keepNext,
|
||||
deferred_followup: null,
|
||||
session_outcome: sessionOutcome,
|
||||
session_outcome: sessionOutcome ?? "collect_evidence",
|
||||
stop_domain_rotation: true,
|
||||
do_not_poll: DO_NOT_POLL,
|
||||
not_in_rotation: NOT_IN_ROTATION,
|
||||
@@ -1257,7 +1256,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
methods,
|
||||
next_followup: holdoutNext,
|
||||
deferred_followup: null,
|
||||
session_outcome: sessionOutcome,
|
||||
session_outcome: sessionOutcome ?? "collect_evidence",
|
||||
stop_domain_rotation: true,
|
||||
do_not_poll: DO_NOT_POLL,
|
||||
not_in_rotation: NOT_IN_ROTATION,
|
||||
@@ -1285,7 +1284,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
methods,
|
||||
next_followup: next,
|
||||
deferred_followup: null,
|
||||
session_outcome: sessionOutcome,
|
||||
session_outcome: sessionOutcome ?? "collect_evidence",
|
||||
stop_domain_rotation: true,
|
||||
do_not_poll: DO_NOT_POLL,
|
||||
not_in_rotation: NOT_IN_ROTATION,
|
||||
@@ -1714,7 +1713,7 @@ export function buildMethodFollowupPlan(input: {
|
||||
methods,
|
||||
next_followup: deferAdoption ? null : next,
|
||||
deferred_followup: deferAdoption ? next : null,
|
||||
session_outcome: sessionOutcome,
|
||||
session_outcome: sessionOutcome ?? "collect_evidence",
|
||||
stop_domain_rotation: true,
|
||||
do_not_poll: DO_NOT_POLL,
|
||||
not_in_rotation: NOT_IN_ROTATION,
|
||||
@@ -1732,24 +1731,8 @@ export function projectRectificationChoiceCard(
|
||||
latestAssistantText?: string | null;
|
||||
},
|
||||
): RectificationChoiceCard | null {
|
||||
let plan = buildMethodFollowupPlan(input);
|
||||
const sessionOutcome = conversationalSessionOutcome({
|
||||
selectionAllowed: input.selectionAllowed === true,
|
||||
proposeAllowed: input.proposeAllowed === true,
|
||||
confirmationAllowed: input.confirmationAllowed === true,
|
||||
nextFollowup: plan.next_followup,
|
||||
methods: plan.methods,
|
||||
userStopped: input.userStopped,
|
||||
candidateScores: input.candidateScores,
|
||||
discriminatorProbe: selectDiscriminatorProbe(input.contrastPacket ?? null, {
|
||||
mentionedKeys: mentionedVargaKeysFromLedgerEvidence(input.evidence),
|
||||
}) ?? undefined,
|
||||
holdoutValidation: input.holdoutValidation,
|
||||
evidence: input.evidence,
|
||||
});
|
||||
if (sessionOutcome !== (input.sessionOutcome ?? "collect_evidence")) {
|
||||
plan = buildMethodFollowupPlan({ ...input, sessionOutcome });
|
||||
}
|
||||
const sessionOutcome = input.sessionOutcome;
|
||||
const plan = buildMethodFollowupPlan(input);
|
||||
if (
|
||||
!input.accepted
|
||||
&& (sessionOutcome === "adopt_representative"
|
||||
|
||||
Reference in New Issue
Block a user