Files
SteelCheck/services/validationEngine.ts
T

231 lines
7.7 KiB
TypeScript

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<string, string> = {
'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<string, number | null> = {};
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')) {
minLimit = mechLimits['limite_resistencia_min'] || mechLimits['tensile_strength_min'] || null;
maxLimit = mechLimits['limite_resistencia_max'] || mechLimits['tensile_strength_max'] || null;
} else if (propLower.includes('elongation') || propLower.includes('alongamento')) {
minLimit = mechLimits['alongamento_min'] || mechLimits['elongation_min'] || null;
}
let status: 'OK' | 'FALHA' = 'OK';
let normStr = [];
if (minLimit !== null) {
normStr.push(`Min: ${minLimit}`);
if (val < minLimit) status = 'FALHA';
item.differencePercentage = `${val >= minLimit ? '+' : ''}${(((val - minLimit) / minLimit) * 100).toFixed(1)}%`;
}
if (maxLimit !== null) {
normStr.push(`Max: ${maxLimit}`);
if (val > maxLimit) status = 'FALHA';
}
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;
}