This commit is contained in:
2026-01-14 18:49:33 +01:00
parent 558bcbd946
commit f29ceacb51
57 changed files with 237 additions and 66 deletions

View File

@@ -70,6 +70,18 @@ function parseVoltageSortKey(voltageLabel: string): number {
function compactNumericForLocale(value: string, locale: 'en' | 'de'): string {
const v = normalizeValue(value);
if (!v) return '';
// Compact common bending-radius style: "15xD (Single core); 12xD (Multi core)" -> "15/12xD".
// Keep semantics, reduce width. Never truncate with ellipses.
if (/\d+xD/i.test(v)) {
const nums = Array.from(v.matchAll(/(\d+)xD/gi)).map(m => m[1]).filter(Boolean);
const unique: string[] = [];
for (const n of nums) {
if (!unique.includes(n)) unique.push(n);
}
if (unique.length) return `${unique.join('/') }xD`;
}
const hasDigit = /\d/.test(v);
if (!hasDigit) return v;
const trimmed = v.replace(/\s+/g, ' ').trim();
@@ -242,6 +254,12 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
return args.locale === 'de' ? 'Leiter' : 'Cond.';
case 'shape':
return args.locale === 'de' ? 'Form' : 'Shape';
// Electrical
case 'cap':
// Capacitance. Use a clear label; lowercase "cap" looks like an internal key.
return `Cap${suffix}`;
case 'X':
return `X${suffix}`;
case 'test_volt':
return `U_test${suffix}`;
case 'rated_volt':
@@ -265,13 +283,14 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
}
}
function summarizeOptions(options: string[] | undefined, maxItems: number = 3): string {
function summarizeOptions(options: string[] | undefined): string {
const vals = (options || []).map(normalizeValue).filter(Boolean);
if (vals.length === 0) return '';
const uniq = Array.from(new Set(vals));
if (uniq.length === 1) return uniq[0];
if (uniq.length <= maxItems) return uniq.join(' / ');
return `${uniq.slice(0, maxItems).join(' / ')} / ...`;
// Never use ellipsis truncation in datasheets. Prefer full value list.
// (Long values should be handled by layout; if needed we can later add wrapping rules.)
return uniq.join(' / ');
}
function parseNumericOption(value: string): number | null {
@@ -294,10 +313,17 @@ function summarizeNumericRange(options: string[] | undefined): { ok: boolean; te
return { ok: true, text: `${fmt(min)}${fmt(max)}` };
}
function summarizeSmartOptions(label: string, options: string[] | undefined): string {
function summarizeSmartOptions(_label: string, options: string[] | undefined): string {
const range = summarizeNumericRange(options);
if (range.ok) return range.text;
return summarizeOptions(options, 3);
return summarizeOptions(options);
}
function normalizeDesignation(value: string): string {
return String(value || '')
.toUpperCase()
.replace(/-\d+$/g, '')
.replace(/[^A-Z0-9]+/g, '');
}
function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): BuildExcelModelResult {
@@ -361,6 +387,14 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
'cpr class': { header: 'CPR class', unit: '', key: 'cpr' },
'flame retardant': { header: 'Flame retardant', unit: '', key: 'flame' },
'self-extinguishing of single cable': { header: 'Flame retardant', unit: '', key: 'flame' },
// High-value electrical/screen columns
'capacitance (approx.)': { header: 'Capacitance', unit: 'uF/km', key: 'cap' },
'capacitance': { header: 'Capacitance', unit: 'uF/km', key: 'cap' },
'reactance': { header: 'Reactance', unit: 'Ohm/km', key: 'X' },
'diameter over screen': { header: 'Diameter over screen', unit: 'mm', key: 'D_screen' },
'metallic screen mm2': { header: 'Metallic screen', unit: 'mm2', key: 'S_screen' },
'metallic screen': { header: 'Metallic screen', unit: 'mm2', key: 'S_screen' },
};
const excelKeys = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units');
@@ -464,6 +498,10 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
const denseTableKeyOrder = [
'Cond',
'shape',
// Electrical properties (when present)
'cap',
'X',
// Dimensions and ratings
'DI',
'RI',
'Wi',
@@ -473,6 +511,9 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
'Wm',
'Rbv',
'Ø',
// Screen data (when present)
'D_screen',
'S_screen',
'Fzv',
'G',
] as const;
@@ -510,16 +551,33 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
if (!mappedByKey.has(c.mapping.key)) mappedByKey.set(c.mapping.key, c);
}
// If conductor material is missing in Excel, derive it from designation.
// NA... => Al, N... => Cu (common for this dataset).
if (!mappedByKey.has('Cond')) {
mappedByKey.set('Cond', {
excelKey: '',
mapping: { header: 'Cond.', unit: '', key: 'Cond' },
});
}
const orderedTableColumns = denseTableKeyOrder
.filter(k => mappedByKey.has(k))
.map(k => mappedByKey.get(k)!)
.map(({ excelKey, mapping }) => {
const unit = normalizeUnit(units[excelKey] || mapping.unit || '');
const unit = normalizeUnit((excelKey ? units[excelKey] : '') || mapping.unit || '');
return {
key: mapping.key,
label: denseAbbrevLabel({ key: mapping.key, locale: args.locale, unit }) || formatExcelHeaderLabel(excelKey, unit),
get: (rowIndex: number) => {
const srcRowIndex = indices[rowIndex];
if (mapping.key === 'Cond' && !excelKey) {
const pn = normalizeDesignation(args.product.name || args.product.slug || args.product.sku || '');
if (/^NA/.test(pn)) return 'Al';
if (/^N/.test(pn)) return 'Cu';
return '';
}
const raw = excelKey ? normalizeValue(String(compatibleRows[srcRowIndex]?.[excelKey] ?? '')) : '';
return compactCellForDenseTable(raw, unit, args.locale);
},