import { compareUiTierSortOrder } from "@/lib/constants/quote"; import type { QuoteItem } from "@/modules/providers/quote-provider"; import { RpaError } from "@/modules/rpa/errors"; function tierKey(serviceLevel: string, rateOption: string): string { return `${serviceLevel}/${rateOption}`; } /** 保留 MotherShip 实际返回的全部 UI 档位(2+1 / 2+2 / 3+3 等),仅去重 + 排序 */ export function normalizeQuoteItems(items: QuoteItem[]): QuoteItem[] { const map = new Map(); for (const item of items) { map.set(tierKey(item.serviceLevel, item.rateOption), item); } if (!map.has("standard/lowest")) { throw new RpaError( "RPA_DATA_INVALID", "缺少必选报价档位:standard/lowest", { retryable: false }, ); } const normalized = [...map.values()].sort((a, b) => compareUiTierSortOrder( a.serviceLevel, a.rateOption, b.serviceLevel, b.rateOption, ), ); if (normalized.length === 0) { throw new RpaError( "RPA_DATA_INVALID", "未解析到有效报价档位", { retryable: false }, ); } return normalized; } /** 网络层报价严格校验(唯一验收入口) */ export function validateQuoteSchema(items: QuoteItem[]): QuoteItem[] { const normalized = normalizeQuoteItems(items); for (const tier of normalized) { if (tier.rawFreight <= 0) { throw new RpaError( "RPA_DATA_INVALID", `档位 ${tier.serviceLevel}/${tier.rateOption} 运费无效`, { retryable: false }, ); } if (!tier.transitDays?.trim() || tier.transitDays === "—") { throw new RpaError( "RPA_DATA_INVALID", `档位 ${tier.serviceLevel}/${tier.rateOption} 时效为空`, { retryable: false }, ); } } return normalized; }