import { ReportData, Standard, ComplianceItem } from '../types'; /** * Extracts a numeric value from a string like "0.22%", "415 MPa", etc. */ function extractNumber(val: string): number | null { if (!val) return null; const cleanVal = val.replace(/,/g, '.'); const match = cleanVal.match(/-?\d+(\.\d+)?/); return match ? parseFloat(match[0]) : null; } /** * Parses the product string to try and find a thickness in mm. * E.g., "CHAPA 50MM", "PLATE 25.4 MM" */ function parseThickness(productDesc: string): number { if (!productDesc) return 0; const cleanDesc = productDesc.replace(/,/g, '.'); const matchDimensions = cleanDesc.match(/(\d+(?:\.\d+)?)\s*(?:x|X|\*)\s*(\d+(?:\.\d+)?)\s*(?:x|X|\*)\s*(\d+(?:\.\d+)?)/); if (matchDimensions) { const dim1 = parseFloat(matchDimensions[1]); const dim2 = parseFloat(matchDimensions[2]); const dim3 = parseFloat(matchDimensions[3]); // The thickness is almost always the smallest dimension of a plate return Math.min(dim1, dim2, dim3); } const match = cleanDesc.match(/(\d+(\.\d+)?)\s*(mm)/i); if (match) return parseFloat(match[1]); return 0; // Default or unknown } function normalizeElementName(element: string): string { const map: Record = { 'c': 'carbono', 'carbon': 'carbono', 'carbono': 'carbono', 'mn': 'manganes', 'manganese': 'manganes', 'manganes': 'manganes', 'si': 'silicio', 'silicon': 'silicio', 'silicio': 'silicio', 'p': 'fosforo', 'phosphorus': 'fosforo', 'fosforo': 'fosforo', 's': 'enxofre', 'sulfur': 'enxofre', 'enxofre': 'enxofre', 'cu': 'cobre', 'copper': 'cobre', 'cobre': 'cobre', 'ni': 'niquel', 'nickel': 'niquel', 'niquel': 'niquel', 'cr': 'cromo', 'chromium': 'cromo', 'cromo': 'cromo', 'mo': 'molibdenio', 'molybdenum': 'molibdenio', 'molibdenio': 'molibdenio', 'v': 'vanadio', 'vanadium': 'vanadio', 'vanadio': 'vanadio', 'nb': 'niobio', 'columbium': 'niobio', 'niobium': 'niobio', 'niobio': 'niobio', 'ti': 'titanio', 'titanium': 'titanio', 'titanio': 'titanio', 'al': 'aluminio', 'aluminum': 'aluminio', 'aluminio': 'aluminio' }; const lower = element.toLowerCase().trim(); // Remove content in parentheses, e.g. "C (Carbon)" -> "c" const clean = lower.replace(/\(.*?\)/g, '').trim(); return map[clean] || clean; } function getTolerance(elementName: string, productVal: number, thickness: number, norm: Standard): number { if (!norm.tolerancias_analise_produto_astma6) return 0; const tolRules = norm.tolerancias_analise_produto_astma6[elementName]; if (!tolRules) return 0; if (tolRules.tabela && tolRules.tabela.length > 0) { for (const faixa of tolRules.tabela) { if (faixa.espessura_ate_mm && thickness <= faixa.espessura_ate_mm) { return faixa.tolerancia_mais || 0; } if (faixa.espessura_maior_que_mm && thickness > faixa.espessura_maior_que_mm) { return faixa.tolerancia_mais || 0; } if (faixa.limite_corrida_ate && productVal <= faixa.limite_corrida_ate) { return faixa.tolerancia_mais || 0; } } } return tolRules.tolerancia_mais || 0; } export function validateCertificateAgainstStandard(extractedReport: ReportData, standard: Standard): ReportData { const report = JSON.parse(JSON.stringify(extractedReport)) as ReportData; const thickness = parseThickness(report.identification.product); let allOk = true; // Encontrar limites baseados no produto (simplificado: pegamos o primeiro grupo que atende a espessura, ou o flat limits) let activeLimits: Record = {}; if (standard.limites_quimicos_corrida) { const grupos = Object.values(standard.limites_quimicos_corrida)[0] || []; for (const limite of grupos) { if (!limite.espessura_max_mm || thickness <= limite.espessura_max_mm) { // Encontrou a faixa certa for (const [key, val] of Object.entries(limite)) { if (typeof val === 'number') { activeLimits[key] = val; } } break; // Pega o primeiro correspondente } } } else if (standard.parametros_controle?.quimicos) { activeLimits = { ...standard.parametros_controle.quimicos }; } // Validate Chemical if (report.compliance.chemical) { for (const item of report.compliance.chemical) { if (!item.element) continue; const normElement = normalizeElementName(item.element); const val = extractNumber(item.certificate); if (val !== null) { // Look for max limit const maxLimit = activeLimits[`${normElement}_max`] || activeLimits[normElement]; const minLimit = activeLimits[`${normElement}_min`]; let status: 'OK' | 'FALHA' = 'OK'; let normStr = []; // Product tolerance const tolerance = getTolerance(normElement, maxLimit as number || 0, thickness, standard); if (minLimit !== undefined && minLimit !== null) { normStr.push(`Min: ${minLimit}`); if (val < (minLimit - tolerance)) status = 'FALHA'; } if (maxLimit !== undefined && maxLimit !== null) { normStr.push(`Max: ${maxLimit}`); if (val > (maxLimit + tolerance)) status = 'FALHA'; } if (normStr.length > 0) { item.norm = normStr.join(' - '); } else { item.norm = '-'; } item.status = status; if (status === 'FALHA') allOk = false; } } } // Validate Mechanical const mechLimits = standard.parametros_controle?.mecanicos || {}; if (report.compliance.mechanical) { for (const item of report.compliance.mechanical) { if (!item.property) continue; const val = extractNumber(item.certificate); if (val !== null) { const propLower = item.property.toLowerCase(); let maxLimit: number | null = null; let minLimit: number | null = null; if (propLower.includes('yield') || propLower.includes('escoamento')) { minLimit = mechLimits['limite_escoamento_min'] || mechLimits['yield_strength_min'] || null; maxLimit = mechLimits['limite_escoamento_max'] || mechLimits['yield_strength_max'] || null; } else if (propLower.includes('tensile') || propLower.includes('resistencia') || propLower.includes('tração') || propLower.includes('tracao')) { minLimit = mechLimits['limite_resistencia_min'] || mechLimits['resistencia_tracao_min'] || mechLimits['tensile_strength_min'] || null; maxLimit = mechLimits['limite_resistencia_max'] || mechLimits['resistencia_tracao_max'] || mechLimits['tensile_strength_max'] || null; } else if (propLower.includes('elongation') || propLower.includes('alongamento')) { minLimit = mechLimits['alongamento_min'] || mechLimits['alongamento_min_50mm'] || mechLimits['elongation_min'] || null; } let status: 'OK' | 'FALHA' = 'OK'; let normStr = []; if (minLimit !== null && maxLimit !== null) { normStr.push(`Min: ${minLimit} - Max: ${maxLimit}`); if (val < minLimit) { status = 'FALHA'; item.differencePercentage = `${(((val - minLimit) / minLimit) * 100).toFixed(1)}%`; } else if (val > maxLimit) { status = 'FALHA'; item.differencePercentage = `+${(((val - maxLimit) / maxLimit) * 100).toFixed(1)}%`; } else { item.differencePercentage = `+${(((val - minLimit) / minLimit) * 100).toFixed(1)}%`; } } else if (minLimit !== null) { normStr.push(`Min: ${minLimit}`); if (val < minLimit) status = 'FALHA'; item.differencePercentage = `${val >= minLimit ? '+' : ''}${(((val - minLimit) / minLimit) * 100).toFixed(1)}%`; } else if (maxLimit !== null) { normStr.push(`Max: ${maxLimit}`); if (val > maxLimit) status = 'FALHA'; item.differencePercentage = `${val <= maxLimit ? '-' : '+'}${Math.abs(((val - maxLimit) / maxLimit) * 100).toFixed(1)}%`; } if (normStr.length > 0) { item.norm = normStr.join(' | '); } else { item.norm = '-'; } item.status = status; if (status === 'FALHA') allOk = false; } } } report.compliance.status = allOk ? 'CONFORME' : 'NÃO CONFORME'; if (!allOk) { report.compliance.nonComplianceReason = 'Valores fora dos limites da norma técnica considerando bitola e tolerâncias de produto.'; } return report; }