Files
Jyotisha/frontend/src/lib/rectification-agentic/core/credible-range.ts
T
Jesse_ChenandCursor a0ce55f066
Independent Staging Quality Gate / validate (push) Successful in 11m17s
Independent Staging Quality Gate / publish (push) Failing after 9m28s
fix(web): drive rectification from candidate contrast and allow range close
Showing a choice card is no longer treated as completion. Distinguish probes
require real candidate groups, holdout stays out of scoring, and ordinary
sessions can finish with a credible range instead of an exact-minute gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:19:26 +08:00

53 lines
2.2 KiB
TypeScript

import { MIN_SEPARATION_LEAD } from "./candidate-separation.ts";
import type { InferenceCandidate } from "./types.ts";
function rankActive(candidates: readonly InferenceCandidate[]): InferenceCandidate[] {
return [...candidates]
.filter((item) => item.status !== "eliminated")
.sort((left, right) => {
if (right.probability !== left.probability) return right.probability - left.probability;
if (right.posterior_score !== left.posterior_score) return right.posterior_score - left.posterior_score;
return left.time.localeCompare(right.time);
});
}
function toMinutes(value: string): number | null {
if (!/^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value)) return null;
return Number(value.slice(0, 2)) * 60 + Number(value.slice(3, 5));
}
function fromMinutes(value: number): string {
const wrapped = ((value % 1440) + 1440) % 1440;
return `${String(Math.floor(wrapped / 60)).padStart(2, "0")}:${String(wrapped % 60).padStart(2, "0")}`;
}
/**
* Union every still-valid parallel candidate or cluster, not only rank=1.
* A time stays valid while it is within the separation lead of the peak.
*/
export function unionStillValidRange(
candidates: readonly InferenceCandidate[],
lead = MIN_SEPARATION_LEAD,
): readonly [string, string] | null {
const active = rankActive(candidates);
if (active.length === 0) return null;
const peak = active[0]!.posterior_score;
const stillValid = active.filter((item) => peak - item.posterior_score < lead);
const points = stillValid.flatMap((item) => [item.cluster_range[0], item.cluster_range[1], item.time]);
const minutes = points
.map(toMinutes)
.filter((value): value is number => value !== null)
.sort((left, right) => left - right);
if (minutes.length === 0) return null;
return [fromMinutes(minutes[0]!), fromMinutes(minutes[minutes.length - 1]!)];
}
export function rangeFromTimes(times: readonly string[]): readonly [string, string] | null {
const minutes = times
.map(toMinutes)
.filter((value): value is number => value !== null)
.sort((left, right) => left - right);
if (minutes.length === 0) return null;
return [fromMinutes(minutes[0]!), fromMinutes(minutes[minutes.length - 1]!)];
}