Files
Jyotisha/frontend/src/lib/rectification-agentic/v9/interview-state.ts
T
Jesse_Chen b1173f7245 fix(web): count only training events for discrimination and split user-stop from validated range
Three collected events with a reserved holdout were stalling because the discriminator door counted holdout. Public selection_allowed still had snapshot fallbacks, and health only proved the image SHA.

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

110 lines
4.4 KiB
TypeScript

/**
* Public interview projection for the Case GET payload.
*
* The tap card is shown only when the follow-up is a discriminator
* (candidates already diverge or holdout) and the Agent wrote choice copy.
* Card identity is the persisted focus UUID plus the inference revision.
*/
import { askedKeysFromLedgerEvidence } from "../core/candidate-contrast-packet.ts";
import { askedProbeKeysFromReceipt, previousInferenceFromReceipt } from "./inference-adapter";
import {
contrastPacketFromDossier,
decideFromDossier,
} from "./decision-from-dossier";
import { evidenceLedgerFingerprint } from "./tool-service";
import { latestUserStoppedCollecting, projectRectificationChoiceCard } from "./method-followup";
import { refinementFromDecisionReceipt } from "./refinement-packet";
import {
internalObservationsFromWindowScan,
windowScanFromDecisionReceipt,
} from "./varga-observations";
import type { RectificationChoiceCard } from "./choice-card";
export function choiceCardFromCaseDossier(dossier: {
evidence: readonly Readonly<{
id?: string;
status: string;
domain: string;
datePrecision: string;
occurredFrom: string | null;
occurredTo: string | null;
eventKind?: string | null;
summary?: string | null;
}>[];
conversationSummary: {
activeFocus: {
id?: string;
intent: string;
targetDomain: string | null;
targetKind: string | null;
expectedAnswerSchema?: Readonly<Record<string, unknown>> | null;
} | null;
declinedSkippedTopics: readonly Readonly<Record<string, unknown>>[];
};
latestResult: {
resultId?: string;
decisionReceipt: Readonly<Record<string, unknown>> | null;
selectionAllowed?: boolean;
candidates?: readonly Readonly<{ time: string; relativeSupport?: number }>[];
evidenceLedgerFingerprint?: string | null;
} | null;
case: {
acceptedTime: string | null;
};
turns?: readonly Readonly<{ role: string; text: string | null }>[];
}): RectificationChoiceCard | null {
const windowScan = windowScanFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
const observations = internalObservationsFromWindowScan(windowScan);
const refinement = refinementFromDecisionReceipt(dossier.latestResult?.decisionReceipt ?? null);
const inference = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
const contrastPacket = contrastPacketFromDossier(dossier);
const decision = decideFromDossier(dossier, {
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence as never),
});
const latestAssistantText = [...(dossier.turns ?? [])]
.reverse()
.find((turn) => turn.role === "assistant")
?.text ?? null;
const holdoutEvents = (inference?.events ?? [])
.filter((item) => item.usage === "holdout")
.map((item) => ({ domain: item.domain, year: item.year }));
return projectRectificationChoiceCard({
evidence: dossier.evidence,
activeFocus: dossier.conversationSummary.activeFocus,
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
observations,
sessionOutcome: decision.sessionOutcome,
precisionStage: decision.precisionStage === "collect_events"
? "collect_events"
: decision.precisionStage === "ready_to_adopt"
? "ready_to_adopt"
: refinement.precision_stage?.current,
nakshatraBoundary: refinement.nakshatra_boundary,
oosBlindPrompts: refinement.oos_blind_prompts,
eventProbes: refinement.discriminating_event_probes,
eventClarificationProbes: refinement.event_clarification_probes,
evidenceCollectionProbes: refinement.evidence_collection_probes,
askedProbeKeys: [
...askedProbeKeysFromReceipt(dossier.latestResult?.decisionReceipt),
...askedKeysFromLedgerEvidence(dossier.evidence),
],
accepted: Boolean(dossier.case.acceptedTime),
selectionAllowed: decision.selectionAllowed,
proposeAllowed: decision.proposeAllowed,
caseRevision: inference?.revision ?? 0,
contrastPacket,
candidateScores: decision.separation.ranked.map((item) => ({
time: item.time,
score: item.score,
})),
userStopped: latestUserStoppedCollecting(dossier.turns ?? []),
latestAssistantText,
candidatesSeparated: decision.separation.sufficient,
holdoutValidation: decision.holdoutValidation,
holdoutEvents,
});
}
export { decideFromDossier, overlayPublicDecision } from "./decision-from-dossier";