|
|
/**
|
|
|
* axel/quote 响应契约解析(阶段0 录证专用,与 RPA normalize 解耦)
|
|
|
*
|
|
|
* 真实结构(2026-06 录证):
|
|
|
* body.data.rates.standard.{ lowest, fastest, bestValue }
|
|
|
* body.data.rates.guaranteed.{ lowest, fastest, bestValue? }
|
|
|
* body.data.availableRates.{ rate_<id>: RateObject }
|
|
|
*/
|
|
|
|
|
|
export type AxelRateTier = {
|
|
|
id?: string;
|
|
|
price?: number;
|
|
|
finalPrice?: number;
|
|
|
baseQuotePrice?: number;
|
|
|
days?: number;
|
|
|
serviceLevel?: string;
|
|
|
serviceType?: string;
|
|
|
quoteType?: string;
|
|
|
serviceCommitment?: string;
|
|
|
serviceCommitmentDescription?: string;
|
|
|
};
|
|
|
|
|
|
export type AxelQuoteContract = {
|
|
|
sourcePath: string;
|
|
|
dataRates: {
|
|
|
standard: Record<string, AxelRateTier>;
|
|
|
guaranteed: Record<string, AxelRateTier>;
|
|
|
} | null;
|
|
|
availableRateCount: number;
|
|
|
/** 四档映射:standard/guaranteed × lowest/fastest */
|
|
|
fourTierCoverage: {
|
|
|
standardLowest: boolean;
|
|
|
standardFastest: boolean;
|
|
|
guaranteedLowest: boolean;
|
|
|
guaranteedFastest: boolean;
|
|
|
};
|
|
|
pricedLeafCount: number;
|
|
|
};
|
|
|
|
|
|
function readData(body: unknown): Record<string, unknown> | null {
|
|
|
if (body == null || typeof body !== "object" || Array.isArray(body)) {
|
|
|
return null;
|
|
|
}
|
|
|
const data = (body as Record<string, unknown>).data;
|
|
|
if (data == null || typeof data !== "object" || Array.isArray(data)) {
|
|
|
return null;
|
|
|
}
|
|
|
return data as Record<string, unknown>;
|
|
|
}
|
|
|
|
|
|
function asTierMap(value: unknown): Record<string, AxelRateTier> | null {
|
|
|
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
|
return null;
|
|
|
}
|
|
|
return value as Record<string, AxelRateTier>;
|
|
|
}
|
|
|
|
|
|
function hasPositivePrice(tier: AxelRateTier): boolean {
|
|
|
for (const key of ["price", "finalPrice", "baseQuotePrice"] as const) {
|
|
|
const val = tier[key];
|
|
|
if (typeof val === "number" && val > 0) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
function countPricedLeavesInAvailableRates(
|
|
|
availableRates: unknown,
|
|
|
): number {
|
|
|
if (
|
|
|
availableRates == null ||
|
|
|
typeof availableRates !== "object" ||
|
|
|
Array.isArray(availableRates)
|
|
|
) {
|
|
|
return 0;
|
|
|
}
|
|
|
return Object.values(availableRates as Record<string, AxelRateTier>).filter(
|
|
|
(r) => hasPositivePrice(r),
|
|
|
).length;
|
|
|
}
|
|
|
|
|
|
function pickTier(
|
|
|
branch: Record<string, AxelRateTier> | null | undefined,
|
|
|
name: string,
|
|
|
): AxelRateTier | null {
|
|
|
if (!branch) {
|
|
|
return null;
|
|
|
}
|
|
|
const tier = branch[name];
|
|
|
return tier && hasPositivePrice(tier) ? tier : null;
|
|
|
}
|
|
|
|
|
|
/** 从 axel/quote body 提取契约摘要 */
|
|
|
export function extractAxelQuoteContract(body: unknown): AxelQuoteContract {
|
|
|
const data = readData(body);
|
|
|
const dataRatesRaw = data?.rates;
|
|
|
let dataRates: AxelQuoteContract["dataRates"] = null;
|
|
|
|
|
|
if (dataRatesRaw && typeof dataRatesRaw === "object" && !Array.isArray(dataRatesRaw)) {
|
|
|
const dr = dataRatesRaw as Record<string, unknown>;
|
|
|
dataRates = {
|
|
|
standard: asTierMap(dr.standard) ?? {},
|
|
|
guaranteed: asTierMap(dr.guaranteed) ?? {},
|
|
|
};
|
|
|
}
|
|
|
|
|
|
const availableRateCount = countPricedLeavesInAvailableRates(
|
|
|
data?.availableRates,
|
|
|
);
|
|
|
|
|
|
const std = dataRates?.standard;
|
|
|
const guar = dataRates?.guaranteed;
|
|
|
|
|
|
const fourTierCoverage = {
|
|
|
standardLowest: pickTier(std, "lowest") != null,
|
|
|
standardFastest: pickTier(std, "fastest") != null,
|
|
|
guaranteedLowest:
|
|
|
pickTier(guar, "lowest") != null || pickTier(guar, "bestValue") != null,
|
|
|
guaranteedFastest:
|
|
|
pickTier(guar, "fastest") != null || pickTier(guar, "bestValue") != null,
|
|
|
};
|
|
|
|
|
|
let pricedLeafCount = 0;
|
|
|
if (std) {
|
|
|
pricedLeafCount += Object.values(std).filter(hasPositivePrice).length;
|
|
|
}
|
|
|
if (guar) {
|
|
|
pricedLeafCount += Object.values(guar).filter(hasPositivePrice).length;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
sourcePath: "data.rates",
|
|
|
dataRates,
|
|
|
availableRateCount,
|
|
|
fourTierCoverage,
|
|
|
pricedLeafCount,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/** 阶段0 A3 验收:四档齐全或 data.rates 定价叶子 ≥ 4 */
|
|
|
export function countAxelQuoteTiers(body: unknown): {
|
|
|
pricedLeafCount: number;
|
|
|
fourTierOk: boolean;
|
|
|
availableRateCount: number;
|
|
|
} {
|
|
|
const c = extractAxelQuoteContract(body);
|
|
|
const fourTierOk =
|
|
|
c.fourTierCoverage.standardLowest &&
|
|
|
c.fourTierCoverage.standardFastest &&
|
|
|
c.fourTierCoverage.guaranteedLowest &&
|
|
|
c.fourTierCoverage.guaranteedFastest;
|
|
|
|
|
|
return {
|
|
|
pricedLeafCount: Math.max(c.pricedLeafCount, c.availableRateCount),
|
|
|
fourTierOk,
|
|
|
availableRateCount: c.availableRateCount,
|
|
|
};
|
|
|
}
|