fix: calibracao das quantidades e dimensoes do conversor tecnometal

This commit is contained in:
2026-09-11 11:33:26 +00:00
parent 6a7d761690
commit e77912785f
2 changed files with 313 additions and 130 deletions
+148 -56
View File
@@ -475,92 +475,183 @@
return bestCol; return bestCol;
}; };
const KNOWN_MATERIALS = [
'A572GR50', 'A572-GR 50', 'A572-GR50', 'A572GR5', 'A572',
'A36', 'ASTM A36', '10.9', '8.8', 'SAE 1020'
];
const assemblies = []; const assemblies = [];
let currentAssembly = null; let currentAssembly = null;
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
const lineText = line.map(it => it.text).join(' '); let rawLineText = line.map(it => it.text).join(' ').trim();
if (!rawLineText) continue;
let text = rawLineText.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3');
text = text.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2');
if ( if (
lineText.includes('LISTA DE CONJUNTOS') || text.includes('LISTA DE CONJUNTOS') ||
lineText.includes('Marca Pos. Descrição') || text.includes('Marca Pos. Descrição') ||
lineText.includes('P.Un. P.Tot. S.Tot.') || text.includes('P.Un. P.Tot.') ||
lineText.includes('B121-FASE') text.includes('FASE4-LISTA') ||
text === '21806'
) { ) {
continue; continue;
} }
const rowByCol = {}; // 1. Monopeça: Marca repete na coluna Pos
for (const it of line) { const monoMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+\1\s+(.+)$/);
const col = getNearestCol(it.x); if (monoMatch) {
if (col === 'superficieTotal') continue; // Ignora S.Tot. const fullMark = monoMatch[1];
const pieceNum = monoMatch[2];
const rest = monoMatch[3].trim();
const parts = fullMark.split('-');
if (!rowByCol[col]) { let mat = '';
rowByCol[col] = it.text.trim(); let matIdx = -1;
} else { let matLen = 0;
rowByCol[col] += ' ' + it.text.trim(); for (const m of KNOWN_MATERIALS) {
const idx = rest.indexOf(m);
if (idx !== -1 && idx > matIdx) {
mat = m;
matIdx = idx;
matLen = m.length;
} }
} }
const rawMarca = (rowByCol.marca || '').trim(); if (matIdx !== -1) {
const rawPos = (rowByCol.pos || '').trim(); const beforeMat = rest.substring(0, matIdx).trim();
const afterMat = rest.substring(matIdx + matLen).trim();
const markMatch = rawMarca.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || rawMarca.match(/^([A-Za-z0-9]+)-(\d+)$/); const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i);
let desc = 'ESTRUTURA';
let quant = 1;
let comp = 0;
if (markMatch) { if (mDescQtd) {
const parts = rawMarca.split('-'); desc = mDescQtd[1];
const ofCode = parts.length === 3 ? parts[0] : defaultOf; quant = parseInt(mDescQtd[2], 10) || 1;
const phaseCode = parts.length === 3 ? parts[1] : defaultFase; const dimTokens = mDescQtd[3].trim().split(/\s+/).filter(t => /^[0-9.,]+$/.test(t));
const pieceNumber = parts.length === 3 ? parts[2] : parts[1]; if (dimTokens.length > 0) {
comp = parseNumeric(dimTokens[dimTokens.length - 1]);
}
}
const qVal = parseInt(rowByCol.qtde || '1', 10); const postNums = afterMat.split(/\s+/).map(parseNumeric);
const mainQuant = isNaN(qVal) || qVal <= 0 ? 1 : qVal; const pUn = postNums[0] || 0;
const pTot = postNums[1] || 0;
const pUnVal = parseNumeric(rowByCol.pesoUnit || 0); assemblies.push({
const pTotVal = parseNumeric(rowByCol.pesoTotal || 0); rawMark: fullMark,
const compVal = parseNumeric(rowByCol.comprimento || 0); of: parts[0] || defaultOf,
const matVal = rowByCol.material ? normalizeMaterial(rowByCol.material) : ''; fase: parts[1] || defaultFase,
marca: pieceNum,
descricao: desc,
isComposed: 'NÃO',
quantidade: quant,
material: normalizeMaterial(mat),
perfilPrincipal: desc,
comprimento: comp,
pesoUnitario: pUn,
pesoTotal: pTot,
components: []
});
currentAssembly = null;
continue;
}
}
const isSimple = rawPos === rawMarca || (compVal > 0 && !!matVal); // 2. Linha Mestre de Conjunto (Peça Composta)
const masterMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+([A-Za-z0-9./-]+)\s+(\d+)\s+([0-9.,]+)\s+([0-9.,]+)$/);
if (masterMatch) {
const fullMark = masterMatch[1];
const pieceNum = masterMatch[2];
const pieceNumInt = parseInt(pieceNum, 10);
if (pieceNumInt < 1000) {
const desc = masterMatch[3].trim();
const quant = parseInt(masterMatch[4], 10) || 1;
const pUn = parseNumeric(masterMatch[5]);
const pTot = parseNumeric(masterMatch[6]);
const parts = fullMark.split('-');
currentAssembly = { currentAssembly = {
rawMark: rawMarca, rawMark: fullMark,
of: ofCode || defaultOf, of: parts[0] || defaultOf,
fase: phaseCode || defaultFase, fase: parts[1] || defaultFase,
marca: pieceNumber, marca: pieceNum,
descricao: rowByCol.descricao || 'ESTRUTURA', descricao: desc,
quantidade: mainQuant, isComposed: 'SIM',
material: matVal, quantidade: quant,
perfilPrincipal: rowByCol.descricao || 'ESTRUTURA', material: '',
comprimento: compVal, perfilPrincipal: desc,
pesoUnitario: pUnVal, comprimento: 0,
pesoTotal: pTotVal, pesoUnitario: pUn,
isComposed: isSimple ? 'NÃO' : 'NÃO', pesoTotal: pTot,
components: [] components: []
}; };
assemblies.push(currentAssembly); assemblies.push(currentAssembly);
} else if (rawPos && currentAssembly) { continue;
currentAssembly.isComposed = 'SIM'; }
}
const compQuant = parseInt(rowByCol.qtde || '1', 10) || 1; // 3. Componente Subordinado
const compLength = parseNumeric(rowByCol.comprimento || 0); const compMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+(.+)$/);
const compMaterial = rowByCol.material ? normalizeMaterial(rowByCol.material) : 'A36'; if (compMatch) {
const compPUn = parseNumeric(rowByCol.pesoUnit || 0); const pos = compMatch[1];
const compPTot = parseNumeric(rowByCol.pesoTotal || 0); const pieceNumInt = parseInt(compMatch[2], 10);
const rest = compMatch[3].trim();
if (pieceNumInt >= 1000 && currentAssembly) {
let mat = '';
let matIdx = -1;
let matLen = 0;
for (const m of KNOWN_MATERIALS) {
const idx = rest.indexOf(m);
if (idx !== -1 && idx > matIdx) {
mat = m;
matIdx = idx;
matLen = m.length;
}
}
if (matIdx !== -1) {
const beforeMat = rest.substring(0, matIdx).trim();
const afterMat = rest.substring(matIdx + matLen).trim();
const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i);
let desc = 'COMPONENTE';
let quant = 1;
let comp = 0;
if (mDescQtd) {
desc = mDescQtd[1];
quant = parseInt(mDescQtd[2], 10) || 1;
const dimTokens = mDescQtd[3].trim().split(/\s+/).filter(t => /^[0-9.,]+$/.test(t));
if (dimTokens.length > 0) {
comp = parseNumeric(dimTokens[dimTokens.length - 1]);
}
}
const postNums = afterMat.split(/\s+/).map(parseNumeric);
const pUn = postNums[0] || 0;
const pTot = postNums[1] || 0;
currentAssembly.components.push({ currentAssembly.components.push({
pos: rawPos, pos: pos,
descricao: rowByCol.descricao || '', descricao: desc,
quantidade: compQuant, quantidade: quant,
comprimento: compLength, comprimento: comp,
material: compMaterial, material: normalizeMaterial(mat),
pesoUnitario: compPUn, pesoUnitario: pUn,
pesoTotal: compPTot pesoTotal: pTot
}); });
} }
} }
}
}
addLog(`Total de Peças extraídas: ${assemblies.length}`); addLog(`Total de Peças extraídas: ${assemblies.length}`);
@@ -574,7 +665,7 @@
if (isComposed) { if (isComposed) {
let bestComp = asm.components[0]; let bestComp = asm.components[0];
maxComp = bestComp.comprimento; maxComp = 0;
for (const comp of asm.components) { for (const comp of asm.components) {
if (comp.comprimento > maxComp) { if (comp.comprimento > maxComp) {
@@ -585,6 +676,7 @@
if (bestComp) { if (bestComp) {
materialPrincipal = bestComp.material || 'A36'; materialPrincipal = bestComp.material || 'A36';
maxComp = bestComp.comprimento;
perfilPrincipal = asm.descricao; perfilPrincipal = asm.descricao;
} }
@@ -287,96 +287,188 @@ const TecnometalConverterContent: React.FC = () => {
return bestCol; return bestCol;
}; };
const KNOWN_MATERIALS = [
'A572GR50', 'A572-GR 50', 'A572-GR50', 'A572GR5', 'A572',
'A36', 'ASTM A36', '10.9', '8.8', 'SAE 1020'
];
const assemblies: AssemblyItem[] = []; const assemblies: AssemblyItem[] = [];
let currentAssembly: AssemblyItem | null = null; let currentAssembly: AssemblyItem | null = null;
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
const lineText = line.map((it) => it.text).join(' '); let rawLineText = line.map((it) => it.text).join(' ').trim();
if (!rawLineText) continue;
// Normalizar hífens e materiais
let text = rawLineText.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3');
text = text.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2');
// Descartar cabeçalhos conhecidos
if ( if (
lineText.includes('LISTA DE CONJUNTOS') || text.includes('LISTA DE CONJUNTOS') ||
lineText.includes('Marca Pos. Descrição') || text.includes('Marca Pos. Descrição') ||
lineText.includes('P.Un. P.Tot. S.Tot.') || text.includes('P.Un. P.Tot.') ||
lineText.includes('B121-FASE') text.includes('FASE4-LISTA') ||
text === '21806'
) { ) {
continue; continue;
} }
// Mapear elementos da linha nas colunas correspondentes (descartando S.Tot) // 1. Monopeça: Marca repete na coluna Pos (ex: "B121-4-1 B121-4-1 Lam. 9.5 22 160 10 270 A36 3,2 71")
const rowByCol: Record<string, string> = {}; const monoMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+\1\s+(.+)$/);
for (const it of line) { if (monoMatch) {
const col = getNearestCol(it.x); const fullMark = monoMatch[1];
if (col === 'superficieTotal') continue; // S.Tot riscado / ignorado const pieceNum = monoMatch[2];
const rest = monoMatch[3].trim();
const parts = fullMark.split('-');
if (!rowByCol[col]) { let mat = '';
rowByCol[col] = it.text.trim(); let matIdx = -1;
} else { let matLen = 0;
rowByCol[col] += ' ' + it.text.trim(); for (const m of KNOWN_MATERIALS) {
const idx = rest.indexOf(m);
if (idx !== -1 && idx > matIdx) {
mat = m;
matIdx = idx;
matLen = m.length;
} }
} }
const rawMarca = (rowByCol.marca || '').trim(); if (matIdx !== -1) {
const rawPos = (rowByCol.pos || '').trim(); const beforeMat = rest.substring(0, matIdx).trim();
const afterMat = rest.substring(matIdx + matLen).trim();
// Testa se rawMarca contém uma marca principal (ex: B121-4-1, B121-4-10, B121-4-11) const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i);
const markMatch = rawMarca.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || rawMarca.match(/^([A-Za-z0-9]+)-(\d+)$/); let desc = 'ESTRUTURA';
let quant = 1;
let comp = 0;
if (markMatch) { if (mDescQtd) {
const parts = rawMarca.split('-'); desc = mDescQtd[1];
const ofCode = parts.length === 3 ? parts[0] : defaultOf; quant = parseInt(mDescQtd[2], 10) || 1;
const phaseCode = parts.length === 3 ? parts[1] : defaultFase; const dimTokens = mDescQtd[3].trim().split(/\s+/).filter((t) => /^[0-9.,]+$/.test(t));
const pieceNumber = parts.length === 3 ? parts[2] : parts[1]; if (dimTokens.length > 0) {
comp = parseNumeric(dimTokens[dimTokens.length - 1]);
}
}
const qVal = parseInt(rowByCol.qtde || '1', 10); const postNums = afterMat.split(/\s+/).map(parseNumeric);
const mainQuant = isNaN(qVal) || qVal <= 0 ? 1 : qVal; const pUn = postNums[0] || 0;
const pTot = postNums[1] || 0;
const pUnVal = parseNumeric(rowByCol.pesoUnit || 0); assemblies.push({
const pTotVal = parseNumeric(rowByCol.pesoTotal || 0); rawMark: fullMark,
const compVal = parseNumeric(rowByCol.comprimento || 0); of: parts[0] || defaultOf,
const matVal = rowByCol.material ? normalizeMaterial(rowByCol.material) : ''; fase: parts[1] || defaultFase,
marca: pieceNum,
descricao: desc,
isComposed: 'NÃO',
quantidade: quant,
material: normalizeMaterial(mat),
perfilPrincipal: desc,
comprimento: comp,
pesoUnitario: pUn,
pesoTotal: pTot,
components: []
});
currentAssembly = null;
continue;
}
}
// Se a coluna Pos é igual à Marca ou se tem Comprimento e Material preenchidos na linha: // 2. Linha Mestre de Conjunto (Peça Composta)
const isSimple = rawPos === rawMarca || (compVal > 0 && !!matVal); // Ex: "B121-4-11 W410X46.1 2 60,8 122" ou "B121-4-18 W410X38.8 2 393,7 787"
const masterMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+([A-Za-z0-9./-]+)\s+(\d+)\s+([0-9.,]+)\s+([0-9.,]+)$/);
if (masterMatch) {
const fullMark = masterMatch[1];
const pieceNum = masterMatch[2];
const pieceNumInt = parseInt(pieceNum, 10);
if (pieceNumInt < 1000) {
const desc = masterMatch[3].trim();
const quant = parseInt(masterMatch[4], 10) || 1;
const pUn = parseNumeric(masterMatch[5]);
const pTot = parseNumeric(masterMatch[6]);
const parts = fullMark.split('-');
currentAssembly = { currentAssembly = {
rawMark: rawMarca, rawMark: fullMark,
of: ofCode || defaultOf, of: parts[0] || defaultOf,
fase: phaseCode || defaultFase, fase: parts[1] || defaultFase,
marca: pieceNumber, marca: pieceNum,
descricao: rowByCol.descricao || 'ESTRUTURA', descricao: desc,
quantidade: mainQuant, isComposed: 'SIM',
material: matVal, quantidade: quant,
perfilPrincipal: rowByCol.descricao || 'ESTRUTURA', material: '',
comprimento: compVal, perfilPrincipal: desc,
pesoUnitario: pUnVal, comprimento: 0,
pesoTotal: pTotVal, pesoUnitario: pUn,
isComposed: isSimple ? 'NÃO' : 'NÃO', pesoTotal: pTot,
components: [] components: []
}; };
assemblies.push(currentAssembly); assemblies.push(currentAssembly);
} else if (rawPos && currentAssembly) { continue;
// Linha de componente subordinado (Marca vazia, Pos preenchida com marca filha ex: B121-4-1037) }
currentAssembly.isComposed = 'SIM'; }
const compQuant = parseInt(rowByCol.qtde || '1', 10) || 1; // 3. Componente Subordinado
const compLength = parseNumeric(rowByCol.comprimento || 0); // Ex: "B121-4-1047 W410X46.1 1 1101 A572GR5 51,2 51"
const compMaterial = rowByCol.material ? normalizeMaterial(rowByCol.material) : 'A36'; // Ex: "B121-4-1038 Lam. 8 4 66 8 120 A36 0,5 2"
const compPUn = parseNumeric(rowByCol.pesoUnit || 0); const compMatch = text.match(/^([A-Za-z0-9]+-\d+-(\d+))\s+(.+)$/);
const compPTot = parseNumeric(rowByCol.pesoTotal || 0); if (compMatch) {
const pos = compMatch[1];
const pieceNumInt = parseInt(compMatch[2], 10);
const rest = compMatch[3].trim();
if (pieceNumInt >= 1000 && currentAssembly) {
let mat = '';
let matIdx = -1;
let matLen = 0;
for (const m of KNOWN_MATERIALS) {
const idx = rest.indexOf(m);
if (idx !== -1 && idx > matIdx) {
mat = m;
matIdx = idx;
matLen = m.length;
}
}
if (matIdx !== -1) {
const beforeMat = rest.substring(0, matIdx).trim();
const afterMat = rest.substring(matIdx + matLen).trim();
const mDescQtd = beforeMat.match(/^(Lam\.\s*[0-9.]+|W[0-9]+X[0-9.]+|L[0-9./X]+|TB[0-9.X]+|[A-Za-z0-9./-]+)\s+(\d+)\s*(.*)$/i);
let desc = 'COMPONENTE';
let quant = 1;
let comp = 0;
if (mDescQtd) {
desc = mDescQtd[1];
quant = parseInt(mDescQtd[2], 10) || 1;
const dimTokens = mDescQtd[3].trim().split(/\s+/).filter((t) => /^[0-9.,]+$/.test(t));
if (dimTokens.length > 0) {
comp = parseNumeric(dimTokens[dimTokens.length - 1]);
}
}
const postNums = afterMat.split(/\s+/).map(parseNumeric);
const pUn = postNums[0] || 0;
const pTot = postNums[1] || 0;
currentAssembly.components.push({ currentAssembly.components.push({
pos: rawPos, pos: pos,
descricao: rowByCol.descricao || '', descricao: desc,
quantidade: compQuant, quantidade: quant,
comprimento: compLength, comprimento: comp,
material: compMaterial, material: normalizeMaterial(mat),
pesoUnitario: compPUn, pesoUnitario: pUn,
pesoTotal: compPTot pesoTotal: pTot
}); });
} }
} }
}
}
addLog(`Total de Peças Principais Tecnometal extraídas: ${assemblies.length}`); addLog(`Total de Peças Principais Tecnometal extraídas: ${assemblies.length}`);
@@ -391,11 +483,9 @@ const TecnometalConverterContent: React.FC = () => {
if (isComposed) { if (isComposed) {
// REGRA DE OURO TECNOMETAL: // REGRA DE OURO TECNOMETAL:
// "para peças que tem varios componentes, quantidade, descricao (mesmo que perfil principal), // Selecionar o componente subordinado com maior comprimento e extrair comprimento e material correspondente
// peso unitario e peso total estarao na mesma linha. Para o comprimento e o material é necessario
// ver na lista de componentes aquele com maior comprimento e respectivamente na mesma linha obter tambem o material."
let bestComp = asm.components[0]; let bestComp = asm.components[0];
maxComp = bestComp.comprimento; maxComp = 0;
for (const comp of asm.components) { for (const comp of asm.components) {
if (comp.comprimento > maxComp) { if (comp.comprimento > maxComp) {
@@ -406,7 +496,8 @@ const TecnometalConverterContent: React.FC = () => {
if (bestComp) { if (bestComp) {
materialPrincipal = bestComp.material || 'A36'; materialPrincipal = bestComp.material || 'A36';
perfilPrincipal = asm.descricao; // perfil principal da linha do conjunto maxComp = bestComp.comprimento;
perfilPrincipal = asm.descricao; // perfil principal da linha mestre do conjunto
} }
if (pesoTot === 0 && asm.components.length > 0) { if (pesoTot === 0 && asm.components.length > 0) {