Files
graphs/src/productParsing.ts
Cauê Faleiros 9af5f296a7
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m25s
Improve planning data normalization
2026-07-20 11:24:47 -03:00

136 lines
4.7 KiB
TypeScript

const sizeOrder = ['2', '4', '6', '8', '10', '12', '14', '16', 'PP', 'P', 'M', 'G', 'GG', 'XG', 'G1', 'G2', 'G3', 'G4', 'G5'];
const sizeSet = new Set(sizeOrder);
const knownColors = [
'BRANCO + PRETO',
'CINZA/PRETO',
'VERDE BANDEIRA',
'VERDE MILITAR',
'AZUL MARINHO',
'CINZA GRAFITE',
'AMARELO',
'BRANCO',
'GRAFITE',
'MARINHO',
'VERMELHO',
'BORDO',
'CAFE',
'CAQUI',
'CHUMBO',
'CINZA',
'MARROM',
'PEROLA',
'PRETO',
'ROYAL',
'ROSA',
'ROXO',
'VERDE',
'AZUL',
'BEGE'
].sort((a, b) => b.length - a.length);
export const normalizeProductText = (value: string) => value.replace(/\s+/g, ' ').trim();
const normalizeForMatch = (value: string) => normalizeProductText(value)
.normalize('NFD')
.replace(/\p{Diacritic}/gu, '')
.toUpperCase();
const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const toFlexiblePattern = (value: string) => escapeRegex(value).replace(/\s+/g, '\\s+');
const findLeadingColor = (value: string) => {
const normalizedValue = normalizeForMatch(value);
return knownColors.find(color => normalizedValue === color || normalizedValue.startsWith(`${color} `)) || '';
};
const findTrailingColor = (value: string) => {
const normalizedValue = normalizeForMatch(value);
return knownColors.find(color => normalizedValue === color || normalizedValue.endsWith(` ${color}`) || normalizedValue.endsWith(`- ${color}`)) || '';
};
export const encodeProductGroupKey = (value: string) => {
const normalizedValue = normalizeProductText(value);
const bytes = new TextEncoder().encode(normalizedValue);
const binary = Array.from(bytes, byte => String.fromCharCode(byte)).join('');
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
};
export const decodeProductGroupKey = (value: string) => {
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
const paddedBase64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
const binary = atob(paddedBase64);
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
return normalizeProductText(new TextDecoder().decode(bytes));
};
export const sortProductSizes = (sizes: string[]) => [...sizes].sort((a, b) => {
const indexA = sizeOrder.indexOf(a);
const indexB = sizeOrder.indexOf(b);
if (indexA !== -1 || indexB !== -1) {
return (indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA) - (indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB);
}
return a.localeCompare(b, 'pt-BR');
});
export const parseProductName = (name: string) => {
const cleanName = normalizeProductText(name);
const explicitSizeMatch = cleanName.match(/\bTAMANHO\s*-?\s*([A-Z0-9]+)\b/i);
const trailingTokenMatch = cleanName.match(/(?:\s+-\s+|\s)([A-Z0-9]+)$/i);
const trailingToken = trailingTokenMatch?.[1]?.toUpperCase() || '';
let size = (explicitSizeMatch?.[1] || (sizeSet.has(trailingToken) ? trailingToken : '')).toUpperCase();
const colorMatch = cleanName.match(/\bCOR\s+(.+?)(?:\s+TAMANHO|\s+-\s+[A-Z0-9]+$|$)/i);
let color = normalizeProductText(findLeadingColor(colorMatch?.[1] || '') || colorMatch?.[1] || '');
if (!color && size) {
const nameBeforeSize = cleanName.replace(new RegExp(`(?:\\s+-\\s+|\\s)${escapeRegex(size)}$`, 'i'), '');
color = findTrailingColor(nameBeforeSize);
}
if (!color) {
const trailingColor = findTrailingColor(cleanName);
if (trailingColor) {
const normalizedName = normalizeForMatch(cleanName);
const normalizedPrefix = normalizeProductText(normalizedName.replace(new RegExp(`${escapeRegex(trailingColor)}$`), ''));
const tokenBeforeColor = normalizedPrefix.match(/([A-Z0-9]+)$/)?.[1] || '';
if (sizeSet.has(tokenBeforeColor)) {
color = trailingColor;
size = tokenBeforeColor;
}
}
}
let baseName = cleanName
.replace(/\bCOR\s+.+?(?:\s+TAMANHO\s*-?\s*[A-Z0-9]+|\s+-\s+[A-Z0-9]+$|$)/i, '')
.replace(/\bTAMANHO\s*-?\s*[A-Z0-9]+\b/i, '')
.replace(/\s+-\s*[A-Z0-9]+$/i, '');
if (color && size) {
const colorPattern = toFlexiblePattern(color);
const sizePattern = escapeRegex(size);
baseName = baseName
.replace(new RegExp(`\\s+-\\s*${colorPattern}\\s+${sizePattern}$`, 'i'), '')
.replace(new RegExp(`\\s+${colorPattern}\\s+-\\s*${sizePattern}$`, 'i'), '')
.replace(new RegExp(`\\s+${sizePattern}\\s+${colorPattern}$`, 'i'), '')
.replace(new RegExp(`\\s+${colorPattern}\\s+${sizePattern}$`, 'i'), '');
}
if (color) {
const colorPattern = toFlexiblePattern(color);
baseName = baseName
.replace(new RegExp(`\\s+-\\s*${colorPattern}$`, 'i'), '')
.replace(new RegExp(`\\s+${colorPattern}$`, 'i'), '');
}
baseName = normalizeProductText(baseName.replace(/\s+-\s*$/g, ''));
return {
baseName: baseName || cleanName,
color,
size
};
};