fix(rectification): separate adjacent minutes with transition proximity
Day-level events now score Vimshottari/Narayana transition closeness so nearby candidate minutes can diverge, with gated quality probes and answer-prior ranking so high-base-rate existence questions stay out. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -103,6 +103,7 @@ export type EngineContrastProbe = Readonly<{
|
||||
answer_class?: string;
|
||||
sign?: string;
|
||||
}>[];
|
||||
target_evidence_id?: string;
|
||||
}>;
|
||||
|
||||
const REMAINING_LAYERS = ["d24", "d5", "d10", "d9", "d4", "d7", "d12", "d2", "d11", "d30"] as const;
|
||||
|
||||
@@ -16,6 +16,7 @@ export type SnapshotStaleReason =
|
||||
| "scoreable_evidence_changed"
|
||||
| "candidate_set_superseded"
|
||||
| "inference_revision_changed"
|
||||
| "scoring_policy_changed"
|
||||
| "fingerprint_missing";
|
||||
|
||||
export const SNAPSHOT_STALE_COPY: Readonly<Record<SnapshotStaleReason, string>> = {
|
||||
@@ -23,6 +24,7 @@ export const SNAPSHOT_STALE_COPY: Readonly<Record<SnapshotStaleReason, string>>
|
||||
scoreable_evidence_changed: "可评分证据已变化,请重新比较候选",
|
||||
candidate_set_superseded: "已有更新的候选结果",
|
||||
inference_revision_changed: "候选后验已更新,请使用当前结果",
|
||||
scoring_policy_changed: "评分政策已更新,请重新比较候选",
|
||||
fingerprint_missing: "候选快照缺少证据指纹,请重新比较候选",
|
||||
};
|
||||
|
||||
@@ -50,6 +52,13 @@ export function classifySnapshotStaleReason(
|
||||
if (snapshot.inferenceRevision !== current.inferenceRevision) {
|
||||
return "inference_revision_changed";
|
||||
}
|
||||
if (
|
||||
snapshot.scoringPolicyVersion
|
||||
&& current.scoringPolicyVersion
|
||||
&& snapshot.scoringPolicyVersion !== current.scoringPolicyVersion
|
||||
) {
|
||||
return "scoring_policy_changed";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -62,7 +71,8 @@ export function scoreableSnapshotIsCurrent(
|
||||
if (!snapshot.scoreableEvidenceFingerprint || !current.scoreableEvidenceFingerprint) return false;
|
||||
return snapshot.scoreableEvidenceFingerprint === current.scoreableEvidenceFingerprint
|
||||
&& snapshot.candidateSetVersion === current.candidateSetVersion
|
||||
&& snapshot.inferenceRevision === current.inferenceRevision;
|
||||
&& snapshot.inferenceRevision === current.inferenceRevision
|
||||
&& snapshot.scoringPolicyVersion === current.scoringPolicyVersion;
|
||||
}
|
||||
|
||||
/** No stored snapshot is not stale; a stored snapshot with a missing fingerprint is. */
|
||||
|
||||
@@ -85,6 +85,7 @@ export type ConflictProbe = Readonly<{
|
||||
information_gain: number;
|
||||
source: string;
|
||||
choice_kind?: ProbeChoiceKind;
|
||||
target_evidence_id?: string;
|
||||
style_options?: readonly Readonly<{
|
||||
label: string;
|
||||
answer_class: AnswerClass;
|
||||
|
||||
@@ -50,9 +50,19 @@ import {
|
||||
spokenFollowupForUser,
|
||||
} from "./method-followup";
|
||||
import type { SessionOutcomeKind } from "./confirmation-gate";
|
||||
import { refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import { prospectiveWindowsNarration, refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import { projectCurrentQuestion } from "./turn-decision";
|
||||
|
||||
function withProspectiveWindows(
|
||||
base: string,
|
||||
receipt: Readonly<Record<string, unknown>> | null | undefined,
|
||||
): string {
|
||||
const extra = prospectiveWindowsNarration(
|
||||
refinementFromDecisionReceipt(receipt).prospective_probes,
|
||||
);
|
||||
return extra ? `${base} ${extra}` : base;
|
||||
}
|
||||
|
||||
export type ApplyChoiceCommand = Readonly<{
|
||||
userId: string;
|
||||
caseId: string;
|
||||
@@ -386,10 +396,10 @@ export async function persistNextInterviewAfterChoice(input: {
|
||||
});
|
||||
}
|
||||
return {
|
||||
hostNarration: nonConvergingRangeNarration({
|
||||
hostNarration: withProspectiveWindows(nonConvergingRangeNarration({
|
||||
credibleRange: input.nextAction.credible_range,
|
||||
representativeTime: input.nextAction.representative_time,
|
||||
}),
|
||||
}), latest.decisionReceipt),
|
||||
choiceReady: false,
|
||||
};
|
||||
}
|
||||
@@ -535,10 +545,10 @@ export async function persistNextInterviewIfIdle(input: {
|
||||
return {
|
||||
persisted: false,
|
||||
choiceReady: false,
|
||||
hostNarration: `${nonConvergingRangeNarration({
|
||||
hostNarration: withProspectiveWindows(`${nonConvergingRangeNarration({
|
||||
credibleRange: decision.credibleRange,
|
||||
representativeTime: decision.representativeTime,
|
||||
})} 可以从下面的时间里选一个采用。`,
|
||||
})} 可以从下面的时间里选一个采用。`, dossier.latestResult?.decisionReceipt),
|
||||
};
|
||||
}
|
||||
if (isNonConvergingRangeOffer(decision)) {
|
||||
@@ -615,7 +625,7 @@ async function persistExhaustionCollect(input: {
|
||||
return {
|
||||
persisted,
|
||||
choiceReady: false,
|
||||
hostNarration: range,
|
||||
hostNarration: withProspectiveWindows(range, input.decisionReceipt ?? input.dossier.latestResult?.decisionReceipt),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -702,19 +712,19 @@ ${nextInterview.hostNarration}`
|
||||
}
|
||||
}
|
||||
const adoptionNarration = nextAction.can_adopt
|
||||
? `${nonConvergingRangeNarration({
|
||||
? withProspectiveWindows(`${nonConvergingRangeNarration({
|
||||
credibleRange: nextAction.credible_range,
|
||||
representativeTime: nextAction.representative_time,
|
||||
})} 可以从下面的时间里选一个采用。`
|
||||
})} 可以从下面的时间里选一个采用。`, input.dossier.latestResult?.decisionReceipt)
|
||||
: null;
|
||||
const completedRangePrefix = input.narration.replace(RECTIFICATION_TERMINATION_COPY, "").trim();
|
||||
const completedRangeNarration = nextAction.type === "complete_with_range"
|
||||
? `${completedRangePrefix}
|
||||
? withProspectiveWindows(`${completedRangePrefix}
|
||||
|
||||
${nonConvergingRangeNarration({
|
||||
credibleRange: nextAction.credible_range,
|
||||
representativeTime: nextAction.representative_time,
|
||||
}, RECTIFICATION_TERMINATION_COPY)}`
|
||||
}, RECTIFICATION_TERMINATION_COPY)}`, input.dossier.latestResult?.decisionReceipt)
|
||||
: null;
|
||||
if (adoptionNarration || completedRangeNarration) {
|
||||
hostNarration = adoptionNarration ?? completedRangeNarration ?? hostNarration;
|
||||
|
||||
@@ -275,6 +275,7 @@ function eventQuestionPrompt(
|
||||
family: string,
|
||||
kind: EventProbeChoiceKind,
|
||||
domain?: string | null,
|
||||
probe?: DiscriminatingEventProbe | null,
|
||||
): string {
|
||||
const time = period.trim();
|
||||
const topic = family.replace(/[??。]+$/g, "").trim();
|
||||
@@ -283,6 +284,9 @@ function eventQuestionPrompt(
|
||||
? "亲密关系里,你更接近哪一种相处方式?"
|
||||
: "平时做事,你更接近下面哪一种?";
|
||||
}
|
||||
if (kind === "event_quality" && probe?.role === "distinguish" && probe.target_evidence_id && probe.user_meaning?.trim()) {
|
||||
return probe.user_meaning.trim();
|
||||
}
|
||||
if (!topic) return time;
|
||||
const dated = isConcreteChoicePeriod(period);
|
||||
if (kind === "event_quality") {
|
||||
@@ -342,7 +346,7 @@ function hypothesisFor(
|
||||
} else if (!isConcreteChoicePeriod(period)) {
|
||||
return null;
|
||||
}
|
||||
const prompt = eventQuestionPrompt(period, probe.event_family, kind, domain);
|
||||
const prompt = eventQuestionPrompt(period, probe.event_family, kind, domain, probe);
|
||||
const why = probe.user_meaning?.trim() || followup.user_prompt_hint.trim();
|
||||
if (!why) return null;
|
||||
return withStyleOptionLabels(prompt, why, null, styleOptions.options);
|
||||
|
||||
@@ -184,7 +184,7 @@ export function contrastPacketFromLatestResult(
|
||||
const answered = new Set((inference?.answered_probes ?? []).map((item) => item.probe_id));
|
||||
const fromInference: EngineContrastProbe[] = (inference?.probes ?? []).flatMap((probe) => {
|
||||
if (answered.has(probe.id) || probe.information_gain <= 0) return [];
|
||||
if (probe.source === "known_event_quality") return [];
|
||||
if (probe.source === "known_event_quality" && !probe.target_evidence_id) return [];
|
||||
if (probe.source === "nakshatra_boundary") return [];
|
||||
return [{
|
||||
semantic_key: probe.semantic_key,
|
||||
@@ -200,6 +200,7 @@ export function contrastPacketFromLatestResult(
|
||||
? { choice_kind: probe.choice_kind }
|
||||
: {}),
|
||||
...(probe.style_options?.length ? { style_options: probe.style_options } : {}),
|
||||
...(probe.target_evidence_id ? { target_evidence_id: probe.target_evidence_id } : {}),
|
||||
}];
|
||||
});
|
||||
const merged = mergeEngineProbes(
|
||||
@@ -214,6 +215,7 @@ export function contrastPacketFromLatestResult(
|
||||
expected_outcomes: probe.expected_outcomes,
|
||||
choice_kind: probe.choice_kind,
|
||||
style_options: probe.style_options,
|
||||
target_evidence_id: probe.target_evidence_id,
|
||||
})),
|
||||
);
|
||||
return buildCandidateContrastPacket({
|
||||
@@ -398,6 +400,7 @@ function completedProbeForSemanticKey(
|
||||
expected_outcomes: event.expected_outcomes,
|
||||
...(event.choice_kind ? { choice_kind: event.choice_kind } : {}),
|
||||
...(event.style_options?.length ? { style_options: event.style_options } : {}),
|
||||
...(event.target_evidence_id ? { target_evidence_id: event.target_evidence_id } : {}),
|
||||
}],
|
||||
candidateTimes: [...(event.candidate_ids ?? [])],
|
||||
});
|
||||
|
||||
@@ -508,10 +508,13 @@ function remainingReverseVerifyProbes(
|
||||
const dasha: DiscriminatingEventProbe[] = [];
|
||||
const fallback: DiscriminatingEventProbe[] = [];
|
||||
for (const probe of probes ?? []) {
|
||||
if (probe.source === "known_event_quality" || probe.role === "clarify" || probe.phase === "event_clarification") continue;
|
||||
const anchoredQuality = probe.source === "known_event_quality"
|
||||
&& Boolean(probe.target_evidence_id)
|
||||
&& probe.role === "distinguish";
|
||||
if ((probe.source === "known_event_quality" && !anchoredQuality) || probe.role === "clarify" || probe.phase === "event_clarification") continue;
|
||||
if (probe.role === "collect" || probe.phase === "evidence_collection") continue;
|
||||
if (declined.has(probe.domain)) continue;
|
||||
if (probeYearAlreadyCovered(evidence, probe.domain, probe.year)) continue;
|
||||
if (!anchoredQuality && probeYearAlreadyCovered(evidence, probe.domain, probe.year)) continue;
|
||||
if (probeBelowAdultFloor(probe, birthDate)) continue;
|
||||
if (CONFLICT_PROBE_SOURCES.has(probe.source)) {
|
||||
dasha.push(probe);
|
||||
@@ -1109,7 +1112,8 @@ export function isOfferBlockingFollowup(
|
||||
|
||||
function discriminatorFromFollowup(followup: MethodFollowup | null): CandidateDiscriminatorProbe | null {
|
||||
if (!followup) return null;
|
||||
if (followup.choice_kind === "event_quality" || followup.intent === "clarify_event") return null;
|
||||
if (followup.intent === "clarify_event") return null;
|
||||
if (followup.choice_kind === "event_quality" && followup.intent !== "distinguish_candidates") return null;
|
||||
const realProbe = followup.source === "event_probe"
|
||||
|| followup.source === "reverse_verify"
|
||||
|| (followup.source === "active_focus" && followup.intent === "distinguish_candidates");
|
||||
|
||||
@@ -226,6 +226,24 @@ export type DiscriminatingEventProbe = Readonly<{
|
||||
right_time?: string;
|
||||
choice_kind?: EventProbeChoiceKind;
|
||||
style_options?: readonly EventProbeStyleOption[];
|
||||
target_evidence_id?: string;
|
||||
display_date_label?: string;
|
||||
}>;
|
||||
|
||||
export type ProspectiveProbe = Readonly<{
|
||||
candidate_label: string;
|
||||
domain: string;
|
||||
window_label: string;
|
||||
user_meaning: string;
|
||||
used_for_scoring: false;
|
||||
}>;
|
||||
|
||||
export type DroppedPacketProbe = Readonly<{
|
||||
semantic_key?: string;
|
||||
reason: string;
|
||||
domain?: string;
|
||||
year?: number;
|
||||
source?: string;
|
||||
}>;
|
||||
|
||||
export type CandidateContrastOpportunity = Readonly<{
|
||||
@@ -492,6 +510,8 @@ function parseExpectedOutcomes(value: unknown): ProbeExpectedOutcome[] {
|
||||
}
|
||||
|
||||
function parseProbeRole(row: Readonly<Record<string, unknown>>, source: string): EventProbeRole {
|
||||
const anchored = typeof row.target_evidence_id === "string" && row.target_evidence_id.length > 0;
|
||||
if (source === "known_event_quality" && row.role === "distinguish" && anchored) return "distinguish";
|
||||
if (row.role === "clarify" || source === "known_event_quality") return "clarify";
|
||||
if (row.role === "collect" || source === "age_band") return "collect";
|
||||
if (row.role === "holdout" || source === "oos_blind") return "holdout";
|
||||
@@ -531,7 +551,13 @@ export function parseEventProbes(
|
||||
|| CLOCK_IN_COPY.test(family)
|
||||
|| row.unique_minute_claim === true
|
||||
) continue;
|
||||
if (!options.allowQuality && source === "known_event_quality") continue;
|
||||
const evidenceId = typeof row.target_evidence_id === "string" && UUID.test(row.target_evidence_id)
|
||||
? row.target_evidence_id
|
||||
: null;
|
||||
const anchoredQuality = source === "known_event_quality"
|
||||
&& Boolean(evidenceId)
|
||||
&& (row.role === "distinguish" || row.phase === "candidate_discriminator");
|
||||
if (!options.allowQuality && source === "known_event_quality" && !anchoredQuality) continue;
|
||||
const tracks = Array.isArray(row.tracks)
|
||||
? row.tracks.filter((track): track is "vimshottari" | "narayana" => track === "vimshottari" || track === "narayana")
|
||||
: [];
|
||||
@@ -578,6 +604,8 @@ export function parseEventProbes(
|
||||
}),
|
||||
} : {}),
|
||||
...(expectedOutcomes.length > 0 ? { expected_outcomes: expectedOutcomes } : {}),
|
||||
...(evidenceId ? { target_evidence_id: evidenceId } : {}),
|
||||
...(asText(row.display_date_label, 40) ? { display_date_label: asText(row.display_date_label, 40)! } : {}),
|
||||
};
|
||||
if (options.requireDistinguishContract && distinguishContractErrors({ ...probe, role: "distinguish" }).length > 0) continue;
|
||||
rows.push(probe);
|
||||
@@ -602,6 +630,65 @@ export function parseCollectionProbes(value: unknown): readonly DiscriminatingEv
|
||||
));
|
||||
}
|
||||
|
||||
export function parseDroppedPacketProbes(value: unknown): readonly DroppedPacketProbe[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const rows: DroppedPacketProbe[] = [];
|
||||
for (const item of value) {
|
||||
const row = asRecord(item);
|
||||
const reason = asText(row?.reason, 80);
|
||||
if (!row || !reason) continue;
|
||||
rows.push({
|
||||
reason,
|
||||
...(asText(row.semantic_key, 80) ? { semantic_key: asText(row.semantic_key, 80)! } : {}),
|
||||
...(typeof row.domain === "string" ? { domain: row.domain } : {}),
|
||||
...(typeof row.year === "number" && Number.isInteger(row.year) ? { year: row.year } : {}),
|
||||
...(typeof row.source === "string" ? { source: row.source } : {}),
|
||||
});
|
||||
if (rows.length === 16) break;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function parseProspectiveProbes(value: unknown): readonly ProspectiveProbe[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const rows: ProspectiveProbe[] = [];
|
||||
for (const item of value) {
|
||||
const row = asRecord(item);
|
||||
const label = asText(row?.candidate_label, 8);
|
||||
const domain = typeof row?.domain === "string" ? row.domain : "";
|
||||
const window = asText(row?.window_label, 40);
|
||||
const meaning = asText(row?.user_meaning, 200);
|
||||
if (
|
||||
!row
|
||||
|| !label
|
||||
|| !EVENT_PROBE_DOMAIN_SET.has(domain)
|
||||
|| !window
|
||||
|| !meaning
|
||||
|| row.used_for_scoring === true
|
||||
|| CLOCK_IN_COPY.test(meaning)
|
||||
|| CLOCK_IN_COPY.test(window)
|
||||
) continue;
|
||||
rows.push({
|
||||
candidate_label: label,
|
||||
domain,
|
||||
window_label: window,
|
||||
user_meaning: meaning,
|
||||
used_for_scoring: false,
|
||||
});
|
||||
if (rows.length === 3) break;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function prospectiveWindowsNarration(probes: readonly ProspectiveProbe[]): string | null {
|
||||
const meanings = probes
|
||||
.filter((item) => item.used_for_scoring === false)
|
||||
.map((item) => item.user_meaning.trim())
|
||||
.filter((item) => item.length > 0 && !CLOCK_IN_COPY.test(item));
|
||||
if (meanings.length === 0) return null;
|
||||
return meanings.join(" ");
|
||||
}
|
||||
|
||||
export function parseCandidateContrastOpportunities(value: unknown): readonly CandidateContrastOpportunity[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const rows: CandidateContrastOpportunity[] = [];
|
||||
@@ -711,6 +798,8 @@ export function refinementFromDecisionReceipt(
|
||||
event_clarification_probes: readonly DiscriminatingEventProbe[];
|
||||
evidence_collection_probes: readonly DiscriminatingEventProbe[];
|
||||
candidate_contrast_opportunities: readonly CandidateContrastOpportunity[];
|
||||
dropped_probes: readonly DroppedPacketProbe[];
|
||||
prospective_probes: readonly ProspectiveProbe[];
|
||||
} {
|
||||
return {
|
||||
event_dasha_ledger: parseEventDashaLedger(receipt?.event_dasha_ledger),
|
||||
@@ -728,5 +817,7 @@ export function refinementFromDecisionReceipt(
|
||||
receipt?.evidence_collection_probes ?? receipt?.discriminating_event_probes,
|
||||
),
|
||||
candidate_contrast_opportunities: parseCandidateContrastOpportunities(receipt?.candidate_contrast_opportunities),
|
||||
dropped_probes: parseDroppedPacketProbes(receipt?.dropped_probes),
|
||||
prospective_probes: parseProspectiveProbes(receipt?.prospective_probes),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user