feat: expose dynamic rectification actions

This commit is contained in:
Jesse_Chen
2026-07-19 09:45:32 +08:00
parent 5c5b60b3e4
commit 806d2babbf
15 changed files with 579 additions and 101 deletions
+52 -3
View File
@@ -6,6 +6,9 @@ import {
createBirthTimeGuideService,
} from "@/lib/birth-time-guide-service";
import { BirthTimeJourneyActionError, createJourneyTurnActions } from "@/lib/birth-time-journey-actions";
import { BirthTimeDynamicActionError } from "@/lib/birth-time-dynamic-actions";
import { BirthTimeJourneyEngineError, createJyotishBirthTimeJourneyEngine } from "@/lib/birth-time-journey-engine";
import { createBirthTimeJourneyService } from "@/lib/birth-time-journey-service";
import {
BirthTimeJourneyStoreError,
createSupabaseBirthTimeJourneyStore,
@@ -64,6 +67,10 @@ export async function POST(request: Request) {
const store = createSupabaseBirthTimeJourneyStore(createAdminSupabaseClient());
const actions = createJourneyTurnActions({ store });
const journey = createBirthTimeJourneyService({
store,
engine: createJyotishBirthTimeJourneyEngine(),
});
const model = defaultLanguageModel();
const generator = model
? {
@@ -79,6 +86,15 @@ export async function POST(request: Request) {
generator,
loadCase: store.loadCase,
proposeEvidenceDraft: actions.proposeEvidenceDraft,
loadDynamicQuestionBuild: journey.generateDynamicQuestion,
commitDynamicQuestion: async (userId, command, question) => {
const committed = await journey.commitDynamicQuestion(userId, command, question);
const action = committed.nextAction;
if (action.kind !== "ask_dynamic_choice" && action.kind !== "present_low_result") {
throw new BirthTimeDynamicActionError("invalid_turn");
}
return { nextAction: action };
},
});
try {
@@ -92,6 +108,34 @@ export async function POST(request: Request) {
recordJourneyTransitionMetric(response.turn, "turn_advanced");
return NextResponse.json(response);
}
case "generate_dynamic_question": {
const current = await store.loadCase(user.id, parsed.data.caseId);
const receipt = current?.journeyProtocol === "dynamic-choice-v2"
? current.dynamicControl.lastActionReceipt
: null;
if (receipt?.kind === "commit_question"
&& receipt.actionId === parsed.data.actionId.toLowerCase()
&& receipt.turnVersion === parsed.data.turnVersion) {
return NextResponse.json(await journey.resume(user.id, parsed.data.caseId));
}
await guide.generateQuestion(user.id, { ...parsed.data, unmatchedNote: null });
return NextResponse.json(await journey.resume(user.id, parsed.data.caseId));
}
case "reframe_unmatched": {
const current = await store.loadCase(user.id, parsed.data.caseId);
const receipt = current?.journeyProtocol === "dynamic-choice-v2"
? current.dynamicControl.lastActionReceipt
: null;
if (receipt?.kind === "unmatched_context"
&& receipt.actionId === parsed.data.actionId.toLowerCase()
&& receipt.turnVersion === parsed.data.turnVersion
&& receipt.questionId === parsed.data.questionId
&& receipt.note === parsed.data.note) {
return NextResponse.json(await journey.resume(user.id, parsed.data.caseId));
}
const response = await journey.submitUnmatchedContext(user.id, parsed.data);
return NextResponse.json(response);
}
default: {
const exhaustive: never = parsed.data;
return exhaustive;
@@ -104,20 +148,25 @@ export async function POST(request: Request) {
{ status: 409 },
);
}
if (error instanceof BirthTimeGuideActionError) {
if (error instanceof BirthTimeGuideActionError
|| (error instanceof BirthTimeDynamicActionError && error.reason !== "unavailable")) {
const status = error.reason === "case_not_found" ? 404 : 409;
return NextResponse.json(
{ error: "当前步骤不可用", message: "请刷新并使用最新的生时校正步骤。" },
{ status },
);
}
if (error instanceof StaleJourneyTurnError || error instanceof BirthTimeJourneyActionError) {
if (error instanceof StaleJourneyTurnError
|| error instanceof BirthTimeJourneyActionError
|| (error instanceof BirthTimeDynamicActionError && error.reason !== "unavailable")) {
return NextResponse.json(
{ error: "校正状态已更新", message: "请使用最新问题或草稿后重试。" },
{ status: 409 },
);
}
if (error instanceof BirthTimeJourneyStoreError) {
if (error instanceof BirthTimeJourneyStoreError
|| error instanceof BirthTimeJourneyEngineError
|| (error instanceof BirthTimeDynamicActionError && error.reason === "unavailable")) {
return NextResponse.json(
{ error: "生时引导暂时不可用", message: "当前资料已保留,请稍后重试。" },
{ status: 503 },
@@ -20,6 +20,7 @@ import { GuidedCandidateActionError } from "@/lib/birth-time-guided-candidate";
import { GuidedCandidateStoreConflictError } from "@/lib/birth-time-guided-candidate-store";
import { JourneyTurnInvariantError } from "@/lib/birth-time-journey-turn";
import { JourneyResponseInvariantError } from "@/lib/birth-time-journey-response-schema";
import { BirthTimeDynamicActionError } from "@/lib/birth-time-dynamic-actions";
import {
recordJourneyMetricEvent,
recordJourneyTransitionMetric,
@@ -114,6 +115,8 @@ export async function POST(request: Request) {
parsed.data.questionId,
parsed.data.answer,
), "turn_advanced");
case "answer_dynamic_choice":
return responseWithJourneyMetric(service.answerDynamicChoice(user.id, parsed.data), "turn_advanced");
case "resume": {
const response = await service.resume(user.id, parsed.data.caseId);
return NextResponse.json(response);
@@ -122,7 +125,12 @@ export async function POST(request: Request) {
{
const before = await service.resume(user.id, parsed.data.caseId);
if (before.journeyProtocol === "dynamic-choice-v2") {
throw new GuidedJourneyLegacyMutationError(parsed.data.caseId);
const response = await service.pollDynamicScoringJob(
user.id,
parsed.data.caseId,
parsed.data.jobId,
);
return NextResponse.json(response);
}
const response = await service.pollScoringJob(user.id, parsed.data.caseId, parsed.data.jobId);
recordScoringJourneyMetric(before, response);
@@ -138,10 +146,18 @@ export async function POST(request: Request) {
return responseWithJourneyMetric(service.confirmEvidenceDraft(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion, parsed.data.draftId), "turn_advanced");
case "skip_evidence_question":
return responseWithJourneyMetric(service.skipEvidenceQuestion(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion), "turn_advanced");
case "pause_rectification":
return responseWithJourneyMetric(service.pause(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion), "journey_paused");
case "finish_rectification":
return responseWithJourneyMetric(service.finishWithCurrentRange(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion), "turn_advanced");
case "pause_rectification": {
const current = await service.resume(user.id, parsed.data.caseId);
return responseWithJourneyMetric(current.journeyProtocol === "dynamic-choice-v2"
? service.pauseDynamic(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion)
: service.pause(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion), "journey_paused");
}
case "finish_rectification": {
const current = await service.resume(user.id, parsed.data.caseId);
return responseWithJourneyMetric(current.journeyProtocol === "dynamic-choice-v2"
? service.finishDynamic(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion)
: service.finishWithCurrentRange(user.id, parsed.data.caseId, parsed.data.actionId, parsed.data.turnVersion), "turn_advanced");
}
case "revise_evidence_draft":
return responseWithJourneyMetric(service.reviseEvidenceDraft({
userId: user.id,
@@ -189,6 +205,7 @@ export async function POST(request: Request) {
error instanceof RectificationCaseNotFoundError
|| error instanceof EvidenceRectificationCaseNotFoundError
|| (error instanceof BirthTimeJourneyActionError && error.reason === "case_not_found")
|| (error instanceof BirthTimeDynamicActionError && error.reason === "case_not_found")
|| (error instanceof GuidedCandidateActionError && error.reason === "case_not_found")
) {
return NextResponse.json(
@@ -205,6 +222,7 @@ export async function POST(request: Request) {
if (
error instanceof StaleJourneyTurnError
|| error instanceof BirthTimeJourneyActionError
|| (error instanceof BirthTimeDynamicActionError && error.reason !== "unavailable")
|| error instanceof GuidedJourneyLegacyMutationError
|| error instanceof BirthTimeScoringJobError
|| error instanceof GuidedCandidateActionError
@@ -225,7 +243,9 @@ export async function POST(request: Request) {
{ status: 409 },
);
}
if (error instanceof BirthTimeJourneyStoreError || error instanceof BirthTimeJourneyEngineError) {
if (error instanceof BirthTimeJourneyStoreError
|| error instanceof BirthTimeJourneyEngineError
|| (error instanceof BirthTimeDynamicActionError && error.reason === "unavailable")) {
return NextResponse.json(
{ error: "生时评估暂时不可用", message: "已保留当前资料,请稍后重试。" },
{ status: 503 },