fix: keep unconfirmed candidates in rectification

This commit is contained in:
732642856
2026-07-21 15:52:01 +08:00
parent d82218e70a
commit 9154ec5ef1
6 changed files with 8 additions and 65 deletions
+1 -17
View File
@@ -747,7 +747,6 @@ export default function Home() {
preview: process.env.NODE_ENV === "development" && uiPreview.current,
onJourney: setBirthTimeJourney,
onReady: completeGuidedBirthTime,
onCandidateComplete: completeCandidateBirthTime,
onEditBirthTimeDetails: editDeclaredBirthTimeDetails,
});
@@ -1019,7 +1018,7 @@ export default function Home() {
setSessions(nextSessions);
setActiveSessionId(nextSessions[0].id);
if ((nextProfile.birthTimeStatus === "rectifying"
|| (nextProfile.birthTimeStatus === "candidate" && !nextProfile.time))
|| nextProfile.birthTimeStatus === "candidate")
&& nextProfile.rectificationCaseId) {
try {
const resumed = await resumeBirthTimeJourney(nextProfile.rectificationCaseId);
@@ -1657,21 +1656,6 @@ export default function Home() {
setOnboardingJustCompleted(true);
}
function completeCandidateBirthTime(result: JourneyClientResponse, time: string) {
const candidateProfile: Profile = {
...profileDraft,
time,
birthTimeStatus: "candidate",
rectificationCaseId: result.caseId,
};
setProfile(candidateProfile);
setProfileDraft(candidateProfile);
setBirthTimeJourney(null);
setPresetMessageLength(0);
setStartGreeting(createStartGreeting(candidateProfile.name));
setOnboardingJustCompleted(true);
}
async function retryBirthTimeAssessment() {
if (!account || profileSaving) return;
setProfileSaving(true);
@@ -14,7 +14,6 @@ import {
} from "@/lib/birth-time-journey-client";
import type { JourneyClientResponse } from "@/lib/birth-time-journey-client";
import {
completeGuidedBirthTimeCandidate,
confirmGuidedBirthTimeCandidate,
reviseBirthTimeEvidenceDraft,
saveGuidedBirthTimeCandidate,
@@ -37,7 +36,6 @@ type GuidedJourneyInput = {
readonly preview: boolean;
readonly onJourney: (journey: JourneyClientResponse) => void;
readonly onReady: (journey: JourneyClientResponse) => void;
readonly onCandidateComplete: (journey: JourneyClientResponse, time: string) => void;
readonly onEditBirthTimeDetails: () => void;
};
@@ -53,7 +51,6 @@ export type BirthTimeGuidedController = {
readonly resume: () => void;
readonly editBirthTimeDetails: () => void;
readonly acknowledgeReady: () => void;
readonly completeCandidate: (time: string) => void;
readonly retryScoring: () => void;
readonly saveCandidate: (resultId: string) => void;
readonly confirmCandidate: (resultId: string, time: string) => void;
@@ -70,7 +67,7 @@ function previewAction(turn: JourneyClientResponse, command: DynamicPreviewComma
}
export function useBirthTimeGuidedJourney(input: GuidedJourneyInput): BirthTimeGuidedController {
const { journey, onJourney, onReady, onCandidateComplete, onEditBirthTimeDetails, preview } = input;
const { journey, onJourney, onReady, onEditBirthTimeDetails, preview } = input;
const latest = useRef(journey);
const busy = useRef(false);
const [actionRegistry] = useState(() => createStableActionIdentityRegistry());
@@ -185,26 +182,6 @@ export function useBirthTimeGuidedJourney(input: GuidedJourneyInput): BirthTimeG
const acknowledgeReady = () => {
if (journey?.nextAction.kind === "ready") onReady(journey);
};
const completeCandidate = (time: string) => {
const turn = journey;
const resultId = turn?.candidateResult?.resultId;
const winner = turn?.candidateResult?.winningSegment;
if (!turn || !resultId || winner?.representativeTime !== time) return;
const release = claimMutation(busy);
if (release === null) return;
setPending(true);
setError("");
const completion = preview
? Promise.resolve()
: completeGuidedBirthTimeCandidate({ caseId: turn.caseId, resultId, time });
void completion
.then(() => onCandidateComplete(turn, time))
.catch((caught) => setError(birthTimeUserError(caught)))
.finally(() => {
release();
setPending(false);
});
};
const retryScoring = () => {
const turn = journey;
if (preview && turn?.journeyProtocol === "dynamic-choice-v2"
@@ -277,7 +254,6 @@ export function useBirthTimeGuidedJourney(input: GuidedJourneyInput): BirthTimeG
resume,
editBirthTimeDetails: onEditBirthTimeDetails,
acknowledgeReady,
completeCandidate,
retryScoring,
saveCandidate,
confirmCandidate,
@@ -16,7 +16,6 @@ type DraftRevision = GuidedMutation & {
};
type CandidateSave = GuidedMutation & { readonly resultId: string };
type CandidateConfirmation = CandidateSave & { readonly time: string };
type CandidateCompletion = Pick<CandidateSave, "caseId" | "resultId"> & { readonly time: string };
const errorPayloadSchema = z.object({
message: z.string().optional(),
@@ -69,22 +68,3 @@ export function confirmGuidedBirthTimeCandidate(
) {
return send({ type: "confirm_guided_candidate", ...input });
}
export async function completeGuidedBirthTimeCandidate(
input: CandidateCompletion,
) {
const { response, payload } = await postJson({
url: "/api/birth-time-candidate-completion",
body: JSON.stringify(input),
retryLostResponse: false,
});
if (!response.ok) {
const parsed = errorPayloadSchema.safeParse(payload);
throw new GuidedBirthTimeRequestError(
response.status,
parsed.success
? parsed.data.message ?? parsed.data.error ?? "候选时间暂时无法保存"
: "候选时间暂时无法保存",
);
}
}
+1 -1
View File
@@ -154,7 +154,7 @@ export function isBirthTimeDraftReady(draft: BirthTimeDraft) {
export function isBirthTimeReadyForConsultation(draft: BirthTimeDraft) {
return Boolean(draft.time)
&& (draft.birthTimeStatus === "candidate" || draft.birthTimeStatus === "confirmed");
&& draft.birthTimeStatus === "confirmed";
}
export function birthTimePersistenceValues(draft: BirthTimeDraft) {
@@ -118,6 +118,8 @@ test("ready completion is explicit and terminal low has no finish mutation", ()
assert.doesNotMatch(hookSource, /turn\.nextAction\.kind === "ready"\) onReady/);
assert.match(candidateSource, /acknowledgeReady/);
assert.doesNotMatch(candidateSource, /controller\.finish/);
assert.doesNotMatch(hookSource, /completeGuidedBirthTimeCandidate/);
assert.doesNotMatch(hookSource, /completeCandidate:/);
});
test("unconfirmed terminal candidates preserve the range without offering direct adoption", () => {
+3 -2
View File
@@ -47,16 +47,17 @@ test("birth time intake requires only the fields selected by the source", () =>
assert.equal(isBirthTimeDraftReady({ ...emptyDraft, birthTimeSource: "unknown" }), true);
});
test("a persisted candidate working time can leave rectification onboarding", () => {
test("an unconfirmed candidate working time remains in rectification onboarding", () => {
const candidate = {
...emptyDraft,
time: "04:53",
birthTimeStatus: "candidate",
} satisfies BirthTimeDraft;
assert.equal(isBirthTimeReadyForConsultation(candidate), true);
assert.equal(isBirthTimeReadyForConsultation(candidate), false);
assert.equal(isBirthTimeReadyForConsultation({ ...candidate, time: "" }), false);
assert.equal(isBirthTimeReadyForConsultation({ ...candidate, birthTimeStatus: "rectifying" }), false);
assert.equal(isBirthTimeReadyForConsultation({ ...candidate, birthTimeStatus: "confirmed" }), true);
});
test("a persisted candidate working time takes precedence over the reported range", () => {