import { Prisma } from "@prisma/client"; import { QUOTE_VALIDITY_MS } from "@/lib/constants/quote"; import { prisma } from "@/lib/prisma"; import { getMarkupRule } from "@/modules/pricing/engine"; import { getConfidenceScore } from "@/modules/quote/confidence"; import { handleRpaFailure } from "@/modules/quote/fallback-orchestrator"; import type { Priority1QuoteJobData } from "@/modules/priority1/rpa-queue"; import { buildPriority1StoredQuotes } from "@/modules/priority1/quote-storage"; import { RpaError } from "@/modules/rpa/errors"; import { recordPostDone, safeRecord } from "@/modules/metrics/collector"; import { closeCircuit, isCircuitOpen, recordRpaFailure, recordRpaSuccess, } from "@/modules/cache/circuit-breaker"; import { PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE } from "@/modules/priority1/constants"; import { PRIORITY1_MANUAL_FOLLOWUP_MESSAGE } from "@/workers/rpa/priority1/quote-extract"; import { runPriority1QuoteRpa } from "@/workers/rpa/priority1/run-quote"; import { heartbeat, isWorkerPaused } from "@/workers/rpa/worker-state"; async function completePriority1ManualFollowup( quoteId: string, message: string, ): Promise { await prisma.quoteRecord.update({ where: { quoteId }, data: { status: "done", sourceType: "rpa", isRealtime: true, confidenceScore: getConfidenceScore("rpa"), markupPercent: 0, quotesJson: Prisma.JsonNull, validUntil: null, errorCode: PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE, errorMessage: message, }, }); safeRecord(() => recordPostDone({ quoteId, sourceType: "rpa", isRealtime: true, }), ); } export async function processPriority1QuoteJob( data: Priority1QuoteJobData, workerId: string, options?: { isFinalAttempt?: boolean }, ): Promise { await heartbeat(workerId); if (await isCircuitOpen()) { throw new RpaError("PAGE_LOAD_TIMEOUT", "RPA 熔断中,暂缓消费", { retryable: true, }); } if (await isWorkerPaused(workerId)) { throw new RpaError("RPA_CAPTCHA", "Worker 已暂停", { retryable: true }); } try { const result = await runPriority1QuoteRpa(data.input); if (!result.ok) { throw new RpaError( "RPA_DATA_INVALID", result.errorMessage ?? "Priority1 报价提取失败", { retryable: true }, ); } if (result.quoteOutcome === "manual_followup") { await completePriority1ManualFollowup( data.quoteId, result.message ?? PRIORITY1_MANUAL_FOLLOWUP_MESSAGE, ); await recordRpaSuccess(); await closeCircuit(); return; } if (result.quotes.length === 0) { throw new RpaError( "RPA_DATA_INVALID", result.errorMessage ?? "Priority1 报价提取失败", { retryable: true }, ); } const markupRule = await getMarkupRule(data.customerId); const storedQuotes = buildPriority1StoredQuotes( result.quotes, markupRule, data.input.shipmentType, ); const validUntil = new Date(Date.now() + QUOTE_VALIDITY_MS); await prisma.quoteRecord.update({ where: { quoteId: data.quoteId }, data: { status: "done", sourceType: "rpa", isRealtime: true, confidenceScore: getConfidenceScore("rpa"), markupPercent: markupRule.type === "percent" ? markupRule.percent : 0, quotesJson: storedQuotes as unknown as Prisma.InputJsonValue, validUntil, errorCode: null, errorMessage: null, }, }); safeRecord(() => recordPostDone({ quoteId: data.quoteId, sourceType: "rpa", isRealtime: true, }), ); await recordRpaSuccess(); await closeCircuit(); } catch (error) { await recordRpaFailure(); const isFinalAttempt = options?.isFinalAttempt ?? true; const canRetry = error instanceof RpaError && error.retryable && !isFinalAttempt; if (canRetry) throw error; await handleRpaFailure( data.quoteId, data.cargoHash, data.customerId, error, { skipStaleFallback: true }, ); if (error instanceof RpaError && !error.retryable) return; throw error; } }