From 7ad0e406754e49db0e89c4921666429a2684c7cc Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Tue, 21 Jul 2026 22:12:06 +0800 Subject: [PATCH] fix: prevent repeated birth-time assessment loops --- frontend/src/app/page.tsx | 21 +++++++++++++++++++ .../birth-time-candidate-result.tsx | 4 ++-- ...se-birth-time-automatic-journey-effects.ts | 5 +++-- frontend/tests/birth-time-user-errors.test.ts | 2 ++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index aeba5cb7..11bf6aa7 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -86,6 +86,19 @@ type Profile = BirthTimeDraft & { districtCode: string; rectificationCaseId: string; }; + +function birthTimeAssessmentSignature(value: BirthTimeDraft) { + return [ + value.date, + value.time, + value.reportedTime, + value.birthTimeSource, + value.birthTimePeriod, + value.birthTimeClue, + value.uncertaintyBeforeMinutes, + value.uncertaintyAfterMinutes, + ].join("|"); +} type ChartLibraryRecord = { id: string; role: "self" | "other"; @@ -741,6 +754,7 @@ export default function Home() { const uiPreview = useRef(false); const uiPreviewMode = useRef(null); const birthTimeRevisionPending = useRef(false); + const lastBirthTimeAssessmentSignature = useRef(""); const birthTimeGuided = useBirthTimeGuidedJourney({ journey: birthTimeJourney, preview: process.env.NODE_ENV === "development" && uiPreview.current, @@ -1542,6 +1556,7 @@ export default function Home() { rectificationCaseId: result.caseId, }; setBirthTimeJourney(result); + lastBirthTimeAssessmentSignature.current = birthTimeAssessmentSignature(nextProfile); setBirthTimeError(""); setProfile(assessedProfile); setProfileDraft(assessedProfile); @@ -1603,6 +1618,12 @@ export default function Home() { try { await persistProfile(profileDraft); if (birthTimeRevisionPending.current) { + if (lastBirthTimeAssessmentSignature.current === birthTimeAssessmentSignature(profileDraft)) { + birthTimeRevisionPending.current = false; + setBirthTimeError("出生资料未变化,重新评估会重复同一结果。请补充不同领域且能注明年月的关键经历,再继续校时。"); + setOnboardingStep("rectification"); + return; + } setBirthTimeAssessmentPhase("assessing"); const assessedProfile = await assessSavedBirthTime(profileDraft); birthTimeRevisionPending.current = false; diff --git a/frontend/src/components/birth-time-candidate-result.tsx b/frontend/src/components/birth-time-candidate-result.tsx index 50708638..e6912e9a 100644 --- a/frontend/src/components/birth-time-candidate-result.tsx +++ b/frontend/src/components/birth-time-candidate-result.tsx @@ -124,8 +124,8 @@ function TerminalAction({ controller, error, path }: { return (
尚未达到采用条件 -

候选范围已保留,但当前证据不足以将具体分钟写入当前排盘时间。补充经历后可重新评估。

- +

候选范围已保留,但当前证据不足以将具体分钟写入当前排盘时间。请补充不同领域、可注明年月的关键经历;仅重复原出生资料不会生成新结果。

+ {error ?

{error}

: null} 会建立新的记录,当前结果仍会保留。
diff --git a/frontend/src/hooks/use-birth-time-automatic-journey-effects.ts b/frontend/src/hooks/use-birth-time-automatic-journey-effects.ts index 6cb311b4..35b30b07 100644 --- a/frontend/src/hooks/use-birth-time-automatic-journey-effects.ts +++ b/frontend/src/hooks/use-birth-time-automatic-journey-effects.ts @@ -16,6 +16,7 @@ import { } from "@/lib/birth-time-guided-effect-coordinator"; import type { StableActionIdentityRegistry } from "@/lib/birth-time-guided-effect-coordinator"; import { runBirthTimeScoringPoll, scoringPollDelay } from "@/lib/birth-time-guided-polling"; +import { birthTimeUserError } from "@/lib/birth-time-user-error"; type AutomaticEffectsInput = { readonly journey: JourneyClientResponse | null; @@ -82,7 +83,7 @@ export function useBirthTimeAutomaticJourneyEffects(input: AutomaticEffectsInput if (publishCurrentJourney({ expected, current: latest.current, next, publish: onJourney })) latest.current = next; }).catch((caught: unknown) => { if (latest.current?.caseId === expected.caseId && latest.current.turnVersion === expected.turnVersion) { - setError(caught instanceof Error ? caught.message : "暂时无法生成下一题,请重试。"); + setError(birthTimeUserError(caught)); } }); }, [actionRegistry, generationIdentity, generationRequests, generationRun, latest, onJourney, preview, setError]); @@ -114,7 +115,7 @@ export function useBirthTimeAutomaticJourneyEffects(input: AutomaticEffectsInput latest.current = result.turn; if (result.kind === "exhausted") setError("评分仍在进行。你可以稍后继续,或重新检查状态。"); }).catch((caught: unknown) => { - if (!controller.signal.aborted) setError(caught instanceof Error ? caught.message : "暂时无法读取评分进度,请稍后重试。"); + if (!controller.signal.aborted) setError(birthTimeUserError(caught)); }); }); return () => { cancelStart(); controller.abort(); }; diff --git a/frontend/tests/birth-time-user-errors.test.ts b/frontend/tests/birth-time-user-errors.test.ts index 445a3d44..7f2a0190 100644 --- a/frontend/tests/birth-time-user-errors.test.ts +++ b/frontend/tests/birth-time-user-errors.test.ts @@ -18,4 +18,6 @@ test("all guided journey mutations normalize implementation errors", () => { const source = readFileSync(new URL("../src/hooks/use-birth-time-guided-journey.ts", import.meta.url), "utf8"); assert.ok((source.match(/setError\(birthTimeUserError\(caught\)\)/g) ?? []).length >= 1); assert.equal(source.includes("setError(caught.message"), false); + const automatic = readFileSync(new URL("../src/hooks/use-birth-time-automatic-journey-effects.ts", import.meta.url), "utf8"); + assert.equal((automatic.match(/setError\(birthTimeUserError\(caught\)\)/g) ?? []).length, 2); });