import { RpaError } from "@/modules/rpa/errors"; import { resolveGuaranteedLowestTier, } from "@/workers/rpa/quote-capture/axel-contract"; import { normalizeQuote, type CanonicalQuote, } from "@/workers/rpa/quote-capture/quote-normalize"; export type QuoteContractRates = Record; export type QuoteContractViolation = { path: string; reason: string; }; const PRICE_KEYS = [ "price", "freight", "amount", "cost", "total", "rawfreight", "rate", "basequoteprice", "finalprice", ] as const; function readPrice(tier: unknown): number | null { if (tier == null || typeof tier !== "object" || Array.isArray(tier)) { return null; } const obj = tier as Record; for (const key of Object.keys(obj)) { const nk = key.replace(/[^a-z0-9]/gi, "").toLowerCase(); if (!PRICE_KEYS.includes(nk as (typeof PRICE_KEYS)[number])) { continue; } const val = obj[key]; if (typeof val === "number" && val > 0) { return val; } if (typeof val === "string") { const m = val.replace(/,/g, "").match(/(\d+(?:\.\d+)?)/); if (m) { const n = Number(m[1]); if (n > 0) { return n; } } } } return null; } function getAt(obj: unknown, ...keys: string[]): unknown { let cur = obj; for (const key of keys) { if (cur == null || typeof cur !== "object" || Array.isArray(cur)) { return undefined; } cur = (cur as Record)[key]; } return cur; } function isPricedTier(tier: unknown): boolean { return readPrice(tier) !== null; } /** 契约校验仅作用于 normalized rates */ export function inspectQuoteContractOnRates( rates: QuoteContractRates, ): { ok: true; rates: QuoteContractRates } | { ok: false; violations: QuoteContractViolation[] } { const violations: QuoteContractViolation[] = []; if (!isPricedTier(getAt(rates, "standard", "lowest"))) { violations.push({ path: "rates.standard.lowest", reason: "缺失或无效价格", }); } const fastestTop = getAt(rates, "fastest"); const fastestStd = getAt(rates, "standard", "fastest"); if (!isPricedTier(fastestTop) && !isPricedTier(fastestStd)) { violations.push({ path: "rates.fastest", reason: "rates.fastest 与 rates.standard.fastest 均缺失或无效价格", }); } const guaranteed = rates.guaranteed; if ( guaranteed == null || typeof guaranteed !== "object" || Array.isArray(guaranteed) ) { violations.push({ path: "rates.guaranteed", reason: "缺失 guaranteed 分支", }); } else if (!isPricedTier(resolveGuaranteedLowestTier(guaranteed))) { violations.push({ path: "rates.guaranteed.lowest", reason: "缺失 lowest/bestValue 或无效价格", }); } if (violations.length > 0) { return { ok: false, violations }; } return { ok: true, rates }; } export function inspectQuoteContract( body: unknown, ): | { ok: true; rates: QuoteContractRates; canonical: CanonicalQuote } | { ok: false; violations: QuoteContractViolation[] } { const canonical = normalizeQuote(body); if (!canonical) { return { ok: false, violations: [{ path: "rates", reason: "normalizeQuote 未解析到 rates" }], }; } const checked = inspectQuoteContractOnRates(canonical.rates); if (!checked.ok) { return checked; } return { ok: true, rates: checked.rates, canonical }; } export function satisfiesQuoteContract(body: unknown): boolean { return inspectQuoteContract(body).ok; } export function assertQuoteContract( body: unknown, context?: string, ): QuoteContractRates { const result = inspectQuoteContract(body); if (!result.ok) { const detail = result.violations .map((v) => `${v.path}: ${v.reason}`) .join("; "); throw new RpaError( "RPA_DATA_INVALID", `报价契约校验失败${context ? ` (${context})` : ""}: ${detail}`, { retryable: false }, ); } return result.rates; }