fix(rectification): reject synthetic stale probes
This commit is contained in:
@@ -42,7 +42,7 @@ export function RectificationChoiceCard(props: RectificationChoiceCardProps) {
|
||||
aria-label={props.card.scoring ? "校正判断" : "盘外核对"}
|
||||
>
|
||||
<fieldset className="birth-time-choice-question" disabled={props.pending || props.disabled || Boolean(selectedKey)}>
|
||||
<legend>{props.card.prompt}</legend>
|
||||
<legend className="sr-only">{props.card.prompt}</legend>
|
||||
{props.card.why ? <p className="rectification-choice-why">{props.card.why}</p> : null}
|
||||
<div className="birth-time-primary-choices">
|
||||
{primary.map((option, index) => (
|
||||
|
||||
@@ -255,6 +255,13 @@ export function stampChoiceSchemaWithProbe(
|
||||
probe_id: preferredId,
|
||||
})) ?? null;
|
||||
if (preferredKey && !matched) {
|
||||
if (state) {
|
||||
const unstamped = { ...schema };
|
||||
delete unstamped.probe_id;
|
||||
delete unstamped.semantic_key;
|
||||
delete unstamped.candidate_split_hash;
|
||||
return { ...unstamped, scoring };
|
||||
}
|
||||
return {
|
||||
...schema,
|
||||
probe_id: preferredId ?? `probe:${preferredKey}`,
|
||||
|
||||
@@ -337,49 +337,6 @@ function remainingReverseVerifyProbes(
|
||||
return [...dasha, ...fallback].slice(0, MAX_REVERSE_VERIFY);
|
||||
}
|
||||
|
||||
const QUALITY_ENCODED_RE = /失利|失常|复读|没考好|考砸|发挥不好|发挥失常|发挥异常|压力很大/;
|
||||
|
||||
function qualityAlreadyEncoded(
|
||||
evidence: readonly MethodFollowupEvidence[],
|
||||
domain: string,
|
||||
year: number,
|
||||
): boolean {
|
||||
const nearby = existenceNearbyYears(domain);
|
||||
return evidence.some((item) => {
|
||||
if (item.status !== "confirmed" && item.status !== "draft" && item.status !== "pending_confirmation") {
|
||||
return false;
|
||||
}
|
||||
if (item.domain !== domain) return false;
|
||||
const itemYear = evidenceYear(item);
|
||||
if (itemYear === null) return false;
|
||||
if (Math.abs(itemYear - year) > nearby) return false;
|
||||
return QUALITY_ENCODED_RE.test(item.summary ?? "");
|
||||
});
|
||||
}
|
||||
|
||||
function remainingQualityProbes(
|
||||
probes: readonly DiscriminatingEventProbe[] | undefined,
|
||||
evidence: readonly MethodFollowupEvidence[],
|
||||
declined: ReadonlySet<string>,
|
||||
askedKeys: ReadonlySet<string> = new Set(),
|
||||
): DiscriminatingEventProbe[] {
|
||||
const rows: DiscriminatingEventProbe[] = [];
|
||||
for (const probe of probes ?? []) {
|
||||
if (probe.source !== "known_event_quality") continue;
|
||||
if (probe.domain !== "education") continue;
|
||||
if (declined.has(probe.domain)) continue;
|
||||
if (!probeYearAlreadyCovered(evidence, probe.domain, probe.year)) continue;
|
||||
if (qualityAlreadyEncoded(evidence, probe.domain, probe.year)) continue;
|
||||
const semantic = probe.semantic_key ?? `${probe.domain}.${probe.year}`;
|
||||
const split = probe.candidate_split_hash ?? "";
|
||||
if (askedKeys.has(semantic) || (split && askedKeys.has(split))) continue;
|
||||
rows.push(probe);
|
||||
}
|
||||
return rows
|
||||
.sort((left, right) => (right.information_gain ?? 0) - (left.information_gain ?? 0))
|
||||
.slice(0, MAX_REVERSE_VERIFY);
|
||||
}
|
||||
|
||||
function remainingConflictProbes(
|
||||
probes: readonly DiscriminatingEventProbe[] | undefined,
|
||||
evidence: readonly MethodFollowupEvidence[],
|
||||
@@ -767,7 +724,9 @@ export function buildMethodFollowupPlan(input: {
|
||||
const sessionOutcome = input.sessionOutcome ?? "collect_evidence";
|
||||
const candidatesSeparated = input.candidatesSeparated === true;
|
||||
const contrastProbe = selectDiscriminatorProbe(input.contrastPacket ?? null);
|
||||
const focus = input.activeFocus ?? null;
|
||||
// Legacy known-event quality cards were never backed by an inference probe.
|
||||
// Ignore them so existing cases resume evidence collection instead of exposing a stale card.
|
||||
const focus = input.activeFocus?.intent === "clarify_event" ? null : input.activeFocus ?? null;
|
||||
const keepAcceptedFocus = Boolean(
|
||||
focus && (focus.intent === "reverse_verify" || focus.intent === "out_of_sample_check"),
|
||||
);
|
||||
@@ -907,14 +866,6 @@ export function buildMethodFollowupPlan(input: {
|
||||
...(input.askedProbeKeys ?? []),
|
||||
...askedKeysFromLedgerEvidence(input.evidence),
|
||||
]);
|
||||
const qualityProbe = dashaCovered
|
||||
? remainingQualityProbes(
|
||||
input.eventClarificationProbes ?? input.eventProbes,
|
||||
input.evidence,
|
||||
declined,
|
||||
askedKeys,
|
||||
)[0] ?? null
|
||||
: null;
|
||||
const conflictProbe = dashaCovered && meetsAcceptanceEventQuality(input.evidence)
|
||||
? remainingConflictProbes(input.eventProbes, input.evidence, declined, askedKeys)[0] ?? null
|
||||
: null;
|
||||
@@ -932,25 +883,6 @@ export function buildMethodFollowupPlan(input: {
|
||||
),
|
||||
source: "method_coverage",
|
||||
});
|
||||
} else if (qualityProbe) {
|
||||
next = makeFollowup({
|
||||
method_id: PROBE_METHOD_ID[qualityProbe.domain],
|
||||
intent: "clarify_event",
|
||||
ask_theme: REVERSE_VERIFY_THEME[qualityProbe.domain],
|
||||
domain: qualityProbe.domain,
|
||||
kind_hint: REVERSE_VERIFY_KIND[qualityProbe.domain],
|
||||
user_prompt_hint: ask(
|
||||
`已记下 ${qualityProbe.year_label} 的经历。按 choice_frame 问那次是否${qualityProbe.event_family}。对得上写入账本并重算;对不上关闭该问。不要发明年份。`,
|
||||
REVERSE_VERIFY_VARGA[qualityProbe.domain],
|
||||
),
|
||||
source: "event_probe",
|
||||
information_gain: qualityProbe.information_gain ?? 0,
|
||||
semantic_key: qualityProbe.semantic_key ?? `${qualityProbe.domain}.${qualityProbe.year}`,
|
||||
candidate_split_hash: qualityProbe.candidate_split_hash,
|
||||
probe_year: qualityProbe.year,
|
||||
choice_kind: qualityProbe.choice_kind ?? "event_quality",
|
||||
style_options: qualityProbe.style_options,
|
||||
}, true, true);
|
||||
} else if (conflictProbe && (!coverageComplete || !candidatesSeparated || (conflictProbe.information_gain ?? 0) >= 0.08)) {
|
||||
next = makeFollowup({
|
||||
method_id: PROBE_METHOD_ID[conflictProbe.domain],
|
||||
|
||||
@@ -47,14 +47,12 @@ function schemaProbeId(schema: Readonly<Record<string, unknown>> | null | undefi
|
||||
export function shouldSkipDiscriminatorFollowup(followup: MethodFollowup): PersistServerFocusStatus | null {
|
||||
if (
|
||||
followup.source === "event_probe"
|
||||
&& followup.choice_kind !== "event_quality"
|
||||
&& (followup.information_gain ?? 0) <= 0
|
||||
) {
|
||||
return "zero_information_gain";
|
||||
}
|
||||
if (
|
||||
followup.intent === "distinguish_candidates"
|
||||
&& followup.choice_kind !== "event_quality"
|
||||
&& ((followup.candidate_ids?.length ?? 0) < 2 || (followup.expected_outcomes?.length ?? 0) < 2)
|
||||
) {
|
||||
return "zero_information_gain";
|
||||
@@ -82,15 +80,17 @@ function expectedAnswerSchemaFor(
|
||||
candidate_split_hash: followup.candidate_split_hash ?? null,
|
||||
choice_kind: frame.choice_kind ?? followup.choice_kind ?? "existence",
|
||||
};
|
||||
return stampChoiceSchemaWithProbe(
|
||||
const state = previousInferenceFromReceipt(decisionReceipt ?? null);
|
||||
const stamped = stampChoiceSchemaWithProbe(
|
||||
schema,
|
||||
previousInferenceFromReceipt(decisionReceipt ?? null),
|
||||
state,
|
||||
questionId,
|
||||
{
|
||||
semantic_key: followup.semantic_key,
|
||||
candidate_split_hash: followup.candidate_split_hash,
|
||||
},
|
||||
);
|
||||
return state && stamped.scoring !== false && !schemaProbeId(stamped) ? null : stamped;
|
||||
}
|
||||
|
||||
export function openQuestionFromPersistedFocus(result: PersistServerFocusResult): {
|
||||
|
||||
Reference in New Issue
Block a user