/** * 卖价/买价重量因素:左开右闭区间 (start, end]。 * 界面与快照统一存为 (0-0.5] 形式,便于 0.312 等重量命中。 */ export function isWeightPricingFactor(factorKey: string): boolean { const k = String(factorKey ?? '').trim(); if (!k) return false; if (k === '重量' || k === 'weight') return true; return false; } /** 解析重量单元格或因素值为区间上下界;单值 0.5 视为 (0, 0.5]。 */ export function parseWeightIntervalBounds(text: string): { start: number; end: number } | null { const trimmed = String(text ?? '').trim(); if (!trimmed) return null; const stripped = trimmed.replace(/^\(/, '').replace(/\]$/, '').replace(/^\[/, ''); const parts = stripped .split(/[,,\-~~]/) .map((s) => s.trim()) .filter(Boolean); if (parts.length >= 2) { const start = Number.parseFloat(parts[0]); const end = Number.parseFloat(parts[1]); if (Number.isFinite(start) && Number.isFinite(end) && end > start) { return { start, end }; } } const single = Number.parseFloat(parts[0] ?? stripped); if (Number.isFinite(single) && single > 0) { return { start: 0, end: single }; } return null; } function formatWeightNumber(n: number): string { const s = n.toFixed(4).replace(/\.?0+$/, ''); return s === '' ? '0' : s; } /** 格式化为 (start-end] 展示与存储。 */ export function formatWeightIntervalLeftOpenRightClosed(start: number, end: number): string { return `(${formatWeightNumber(start)}-${formatWeightNumber(end)}]`; } /** 将单值或旧格式转为区间字符串 (start-end]。 */ export function weightValueToIntervalCell( rawValue: string, allWeightValuesInFactor: string[] ): string { const trimmed = String(rawValue ?? '').trim(); if (!trimmed) return ''; const parsed = parseWeightIntervalBounds(trimmed); if ( parsed && (trimmed.includes('-') || trimmed.includes(',') || trimmed.includes(',') || trimmed.includes(']')) ) { return formatWeightIntervalLeftOpenRightClosed(parsed.start, parsed.end); } const bounds = weightFactorValuesToSortedUpperBounds(allWeightValuesInFactor); const end = Number.parseFloat(trimmed); if (!Number.isFinite(end)) return trimmed; const idx = bounds.findIndex((n) => Math.abs(n - end) < 1e-9); const start = idx > 0 ? bounds[idx - 1]! : 0; const upper = idx >= 0 ? bounds[idx]! : end; return formatWeightIntervalLeftOpenRightClosed(start, upper); } /** 提取因素值列表中的重量上限并升序排列(用于区间左界)。 */ export function weightFactorValuesToSortedUpperBounds(values: string[]): number[] { const nums = (values ?? []) .map((v) => { const parsed = parseWeightIntervalBounds(v); if (parsed) return parsed.end; const n = Number.parseFloat(String(v).trim()); return Number.isFinite(n) ? n : null; }) .filter((n): n is number => n != null); return [...new Set(nums)].sort((a, b) => a - b); } /** 提交快照/API 时规范化重量单元格。 */ export function canonicalWeightFactorCellForApi( rawValue: string, allWeightValuesInFactor: string[] ): string { return weightValueToIntervalCell(rawValue, allWeightValuesInFactor); }