You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.8 KiB
207 lines
5.8 KiB
import type { QuoteRecord } from "@prisma/client";
|
|
import { filterQuotesForMotherShipUiDisplay } from "@/lib/constants/mothership-ui-tiers";
|
|
import { PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE } from "@/modules/priority1/constants";
|
|
import {
|
|
readPriority1ShipmentMeta,
|
|
resolvePriority1StoredQuotes,
|
|
type Priority1DisplayLine,
|
|
} from "@/modules/priority1/quote-storage";
|
|
import { getConfidenceScore } from "@/modules/quote/confidence";
|
|
import { formatQuoteErrorMessage } from "@/modules/quote/quote-error-messages";
|
|
|
|
type QuoteTierKey = { service_level: string; rate_option: string };
|
|
|
|
export type Priority1QuoteDetailPayload = {
|
|
display_mode: "ltl_cards" | "ftl_calendar";
|
|
lines: Priority1DisplayLine[];
|
|
shipment_meta?: {
|
|
shipment_type?: string;
|
|
pickup_date?: string;
|
|
commodity?: string;
|
|
origin_zip?: string;
|
|
destination_zip?: string;
|
|
weight_lb?: number;
|
|
};
|
|
};
|
|
|
|
export type QuoteDetailResponse = {
|
|
quote_id: string;
|
|
request_id: string;
|
|
status: "processing" | "done" | "failed" | "expired";
|
|
valid_until?: string;
|
|
source_type?: "rpa" | "cache" | "stale";
|
|
is_realtime?: boolean;
|
|
confidence_score?: number;
|
|
currency: string;
|
|
error_code?: string;
|
|
error_message?: string;
|
|
quotes?: unknown[];
|
|
priority1?: Priority1QuoteDetailPayload;
|
|
};
|
|
|
|
export type QuoteSummaryItem = {
|
|
quote_id: string;
|
|
request_id: string;
|
|
status: string;
|
|
source_type: string | null;
|
|
is_realtime: boolean;
|
|
created_at: string;
|
|
valid_until: string | null;
|
|
pallet_count: number;
|
|
cargo_type: string;
|
|
error_code: string | null;
|
|
};
|
|
|
|
function resolveStatus(record: QuoteRecord): QuoteDetailResponse["status"] {
|
|
if (
|
|
record.status === "done" &&
|
|
record.validUntil &&
|
|
record.validUntil.getTime() < Date.now()
|
|
) {
|
|
return "expired";
|
|
}
|
|
return record.status as QuoteDetailResponse["status"];
|
|
}
|
|
|
|
/** 将 QuoteRecord 格式化为 GET /quotes/{id} 响应 */
|
|
export function serializeQuoteDetail(record: QuoteRecord): QuoteDetailResponse {
|
|
const status = resolveStatus(record);
|
|
const sourceType = record.sourceType as
|
|
| "rpa"
|
|
| "cache"
|
|
| "stale"
|
|
| null
|
|
| undefined;
|
|
|
|
const base: QuoteDetailResponse = {
|
|
quote_id: record.quoteId,
|
|
request_id: record.requestId,
|
|
status,
|
|
currency: record.currency,
|
|
};
|
|
|
|
if (status === "processing") {
|
|
return base;
|
|
}
|
|
|
|
if (status === "failed") {
|
|
const errorCode = record.errorCode ?? "QUOTE_UNAVAILABLE";
|
|
return {
|
|
...base,
|
|
error_code: errorCode,
|
|
error_message: formatQuoteErrorMessage(errorCode, record.errorMessage),
|
|
is_realtime: record.isRealtime,
|
|
};
|
|
}
|
|
|
|
if (status === "expired") {
|
|
return {
|
|
...base,
|
|
valid_until: record.validUntil?.toISOString(),
|
|
source_type: sourceType ?? undefined,
|
|
is_realtime: record.isRealtime,
|
|
};
|
|
}
|
|
|
|
if (
|
|
record.errorCode === PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE
|
|
) {
|
|
const confidence =
|
|
record.confidenceScore != null
|
|
? Number(record.confidenceScore)
|
|
: getConfidenceScore("rpa");
|
|
return {
|
|
...base,
|
|
source_type: sourceType ?? "rpa",
|
|
is_realtime: record.isRealtime,
|
|
confidence_score: confidence,
|
|
error_code: PRIORITY1_MANUAL_FOLLOWUP_ERROR_CODE,
|
|
error_message: formatQuoteErrorMessage(
|
|
record.errorCode,
|
|
record.errorMessage,
|
|
),
|
|
quotes: [],
|
|
};
|
|
}
|
|
|
|
const confidence =
|
|
record.confidenceScore != null
|
|
? Number(record.confidenceScore)
|
|
: sourceType
|
|
? getConfidenceScore(sourceType)
|
|
: 0.95;
|
|
|
|
const priority1Stored = resolvePriority1StoredQuotes(
|
|
record.quotesJson,
|
|
record.pickupJson,
|
|
);
|
|
const priority1Meta = readPriority1ShipmentMeta(record.pickupJson);
|
|
if (priority1Stored) {
|
|
const pickup = record.pickupJson as Record<string, unknown> | null;
|
|
const delivery = record.deliveryJson as Record<string, unknown> | null;
|
|
const meta = readPriority1ShipmentMeta(record.pickupJson);
|
|
return {
|
|
...base,
|
|
valid_until: record.validUntil?.toISOString(),
|
|
source_type: sourceType ?? undefined,
|
|
is_realtime: record.isRealtime,
|
|
confidence_score: confidence,
|
|
priority1: {
|
|
display_mode: priority1Stored.display_mode,
|
|
lines: priority1Stored.lines,
|
|
shipment_meta: {
|
|
shipment_type: meta.shipmentType,
|
|
pickup_date: meta.pickupDate,
|
|
commodity: meta.commodity,
|
|
origin_zip:
|
|
typeof pickup?.zip === "string" ? pickup.zip : undefined,
|
|
destination_zip:
|
|
typeof delivery?.zip === "string" ? delivery.zip : undefined,
|
|
weight_lb:
|
|
record.weightLb != null ? Number(record.weightLb) : undefined,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
if (priority1Meta.shipmentType) {
|
|
const errorCode = record.errorCode ?? "QUOTE_UNAVAILABLE";
|
|
return {
|
|
...base,
|
|
status: "failed",
|
|
error_code: errorCode,
|
|
error_message: formatQuoteErrorMessage(errorCode, record.errorMessage),
|
|
is_realtime: record.isRealtime,
|
|
confidence_score: confidence,
|
|
};
|
|
}
|
|
|
|
return {
|
|
...base,
|
|
valid_until: record.validUntil?.toISOString(),
|
|
source_type: sourceType ?? undefined,
|
|
is_realtime: record.isRealtime,
|
|
confidence_score: confidence,
|
|
quotes: filterQuotesForMotherShipUiDisplay(
|
|
((record.quotesJson as QuoteTierKey[]) ?? []) as QuoteTierKey[],
|
|
),
|
|
};
|
|
}
|
|
|
|
/** 历史列表摘要 */
|
|
export function serializeQuoteSummary(record: QuoteRecord): QuoteSummaryItem {
|
|
const status = resolveStatus(record);
|
|
return {
|
|
quote_id: record.quoteId,
|
|
request_id: record.requestId,
|
|
status,
|
|
source_type: record.sourceType,
|
|
is_realtime: record.isRealtime,
|
|
created_at: record.createdAt.toISOString(),
|
|
valid_until: record.validUntil?.toISOString() ?? null,
|
|
pallet_count: record.palletCount,
|
|
cargo_type: record.cargoType,
|
|
error_code: record.errorCode,
|
|
};
|
|
}
|