Atualização automática - 2026-06-24 10:23:23
This commit is contained in:
+72
-68
@@ -1,6 +1,7 @@
|
||||
import { GoogleGenAI, Type } from "@google/genai";
|
||||
import type { AIProvider } from '../types/providers';
|
||||
import type { ReportData } from '../types';
|
||||
import type { ReportData, Standard } from '../types';
|
||||
import { validateCertificateAgainstStandard } from './validationEngine';
|
||||
|
||||
interface AnalyzeOptions {
|
||||
provider: AIProvider;
|
||||
@@ -73,7 +74,7 @@ const reportSchema = {
|
||||
compliance: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
status: { type: Type.STRING, description: "'CONFORME' ou 'NÃO CONFORME'" },
|
||||
status: { type: Type.STRING, description: "'CONFORME' ou 'NÃO CONFORME' (deixe vazio se não souber)" },
|
||||
nonComplianceReason: { type: Type.STRING },
|
||||
mechanical: {
|
||||
type: Type.ARRAY,
|
||||
@@ -81,12 +82,9 @@ const reportSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
property: { type: Type.STRING },
|
||||
norm: { type: Type.STRING },
|
||||
certificate: { type: Type.STRING },
|
||||
status: { type: Type.STRING, description: "'OK' ou 'REPROVADO'" },
|
||||
differencePercentage: { type: Type.STRING }
|
||||
certificate: { type: Type.STRING }
|
||||
},
|
||||
required: ["property", "norm", "certificate", "status", "differencePercentage"]
|
||||
required: ["property", "certificate"]
|
||||
}
|
||||
},
|
||||
chemical: {
|
||||
@@ -95,11 +93,9 @@ const reportSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
element: { type: Type.STRING },
|
||||
norm: { type: Type.STRING },
|
||||
certificate: { type: Type.STRING },
|
||||
status: { type: Type.STRING, description: "'OK' ou 'REPROVADO'" }
|
||||
certificate: { type: Type.STRING }
|
||||
},
|
||||
required: ["element", "norm", "certificate", "status"]
|
||||
required: ["element", "certificate"]
|
||||
}
|
||||
},
|
||||
otherTests: {
|
||||
@@ -108,14 +104,13 @@ const reportSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
test: { type: Type.STRING },
|
||||
result: { type: Type.STRING },
|
||||
status: { type: Type.STRING }
|
||||
result: { type: Type.STRING }
|
||||
},
|
||||
required: ["test", "result", "status"]
|
||||
required: ["test", "result"]
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ["status", "nonComplianceReason", "mechanical", "chemical", "otherTests"]
|
||||
required: ["mechanical", "chemical", "otherTests"]
|
||||
},
|
||||
overPerformance: {
|
||||
type: Type.ARRAY,
|
||||
@@ -150,39 +145,20 @@ const arraySchema = {
|
||||
items: reportSchema
|
||||
};
|
||||
|
||||
export const buildAnalysisPrompt = (standardsContext?: any[]) => {
|
||||
let prompt = `Você é o "SteelCheck", um especialista sênior em engenharia de materiais, metalurgia, calibração e controle de qualidade industrial.
|
||||
export const buildAnalysisPrompt = () => {
|
||||
let prompt = `Você é o "SteelCheck", um especialista sênior em extração de dados de Certificados de Qualidade.
|
||||
|
||||
Sua tarefa principal é transcrever com precisão matemática e zero alucinação os dados de um Certificado de Qualidade (Mill Test Report) e gerar um "Relatório de Análise Técnica" em formato JSON.
|
||||
Sua tarefa principal é transcrever com precisão matemática e zero alucinação os dados de um Certificado de Qualidade (Mill Test Report) e gerar um JSON estruturado.
|
||||
|
||||
### DIRETRIZES RÍGIDAS DE TRANSCRIÇÃO (OCR DE ALTA PRECISÃO):
|
||||
1. **Zero Alucinação de Normas:** No campo "standards", extraia APENAS e EXATAMENTE o que estiver escrito na imagem.
|
||||
2. **Extração de Identificação:** Transcreva números de certificado, lotes, corridas e quantidades exatamente como aparecem.
|
||||
3. **Mapeamento Preciso de Colunas:** Mapeie composição química e propriedades mecânicas com precisão absoluta.
|
||||
2. **Extração de Identificação:** Transcreva o produto (incluindo dimensões/bitola, ex: "CHAPA 50MM" ou "PERFIL W360x44.6"), números de certificado, lotes, corridas e quantidades exatamente como aparecem.
|
||||
3. **Mapeamento Preciso de Colunas:** Mapeie composição química e propriedades mecânicas com precisão absoluta para a chave "certificate". Não preencha "norm" nem "status".
|
||||
4. **Múltiplas Linhas:** Se houver múltiplas linhas de teste, gere um objeto para cada uma.
|
||||
5. **Orientação:** Rotacione mentalmente a imagem para ler corretamente.
|
||||
|
||||
### ANÁLISE DE CONFORMIDADE:
|
||||
- Compare os valores reais extraídos com os requisitos mínimos/máximos da norma técnica declarada no certificado.
|
||||
- FORMATO DO CAMPO "NORM": Sempre que a norma (ou o Banco Local) estabelecer uma FAIXA (um mínimo e um máximo) para um elemento, você DEVE transcrever AMBOS os limites no campo "norm" do JSON (Exemplo: "Min: 0.005% - Max: 0.05%"). Nunca oculte o limite máximo ou mínimo se ambos existirem.
|
||||
- LAUDOS COM DUPLA ESPECIFICAÇÃO (MÚLTIPLAS NORMAS): Se o certificado citar mais de uma norma (ex: "ASTM A572 GR50 / NBR 7007 AR350"), você DEVE adotar sempre o critério MAIS RÍGIDO (o mais conservador e restritivo) entre as normas citadas para garantir que o material atenda a ambas. Exemplo: se a ASTM não possui limite para Carbono Equivalente (CEQ), mas a NBR exige Máx: 0.47%, você DEVE usar Máx: 0.47% como parâmetro de reprovação.
|
||||
- REGRA CRÍTICA DE STATUS GERAL: Se QUALQUER item (mecânico, químico ou outro) for avaliado como "REPROVADO", o status geral do laudo ("compliance.status") DEVE ser OBRIGATORIAMENTE "NÃO CONFORME". Só atribua "CONFORME" se ABSOLUTAMENTE TODOS os itens individuais forem "OK".`;
|
||||
- No campo "analysisSource" do JSON, preencha EXATAMENTE com "Rede Neural".
|
||||
|
||||
if (standardsContext && standardsContext.length > 0) {
|
||||
prompt += `\n\n=== BANCO DE NORMAS LOCAL (USO OBRIGATÓRIO) ===\n`;
|
||||
prompt += `Você DEVE utilizar as regras rígidas do seguinte banco de dados local para confrontar e validar os resultados do certificado. SE a norma citada no laudo estiver presente na lista abaixo, USE ESTRITAMENTE os valores desta lista para verificar o "status" (CONFORME / REPROVADO):\n\n`;
|
||||
prompt += JSON.stringify(standardsContext, null, 2);
|
||||
prompt += `\n\n- No campo "analysisSource" do JSON, preencha EXATAMENTE com "Banco Local" se você usou as regras deste banco.`;
|
||||
prompt += `\n=================================================\n`;
|
||||
} else {
|
||||
prompt += `\n- Utilize o seu conhecimento paramétrico interno de engenharia de materiais para aplicar os limites corretos da norma encontrada no laudo.\n`;
|
||||
prompt += `- No campo "analysisSource" do JSON, preencha EXATAMENTE com "Rede Neural".\n`;
|
||||
}
|
||||
|
||||
prompt += `
|
||||
- **Fórmula de Variação (differencePercentage):** Calcule a variação percentual do valor do certificado em relação ao mínimo exigido pela norma.
|
||||
USE ESTRITAMENTE A FÓRMULA: ((Valor_Certificado - Valor_Norma) / Valor_Norma) * 100.
|
||||
Exemplo: Se a Norma exige Mínimo 345, e o Certificado acusa 415, o resultado é ((415-345)/345)*100 = +20.3%.
|
||||
|
||||
Retorne APENAS o JSON no formato do array especificado, sem explicações ou markdown.
|
||||
|
||||
@@ -202,13 +178,13 @@ FORMATO DO RETORNO:
|
||||
"quantity": "8,560 T"
|
||||
},
|
||||
"compliance": {
|
||||
"status": "CONFORME",
|
||||
"status": "",
|
||||
"nonComplianceReason": "",
|
||||
"mechanical": [
|
||||
{ "property": "Yield Strength (Limite de Escoamento)", "norm": "Min: 345 MPa", "certificate": "415 MPa", "status": "OK", "differencePercentage": "+20.3%" }
|
||||
{ "property": "Yield Strength (Limite de Escoamento)", "certificate": "415 MPa" }
|
||||
],
|
||||
"chemical": [
|
||||
{ "element": "C (Carbon)", "norm": "Max: 0.23%", "certificate": "0.22%", "status": "OK" }
|
||||
{ "element": "C (Carbon)", "certificate": "0.22%" }
|
||||
],
|
||||
"otherTests": []
|
||||
},
|
||||
@@ -227,7 +203,7 @@ export const analyzeWithGemini = async (file: File, apiKey: string, model: strin
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey: apiKey });
|
||||
const imagePart = await fileToGenerativePart(file);
|
||||
const prompt = buildAnalysisPrompt(standardsContext);
|
||||
const prompt = buildAnalysisPrompt();
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
@@ -241,8 +217,14 @@ export const analyzeWithGemini = async (file: File, apiKey: string, model: strin
|
||||
|
||||
try {
|
||||
const text = response.text;
|
||||
const data = cleanAndParseJson(text);
|
||||
return data as ReportData[];
|
||||
const data = cleanAndParseJson(text) as ReportData[];
|
||||
|
||||
// Apply programmatic validation
|
||||
if (standardsContext && standardsContext.length > 0) {
|
||||
// Assume the first standard matched is the main one for now
|
||||
return data.map(report => validateCertificateAgainstStandard(report, standardsContext[0] as Standard));
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw e;
|
||||
@@ -262,7 +244,7 @@ export const analyzeWithOpenAI = async (file: File, apiKey: string, model: strin
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
const prompt = buildAnalysisPrompt(standardsContext);
|
||||
const prompt = buildAnalysisPrompt();
|
||||
|
||||
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
@@ -302,7 +284,11 @@ export const analyzeWithOpenAI = async (file: File, apiKey: string, model: strin
|
||||
throw new Error("Resposta vazia da OpenAI");
|
||||
}
|
||||
|
||||
return cleanAndParseJson(content) as ReportData[];
|
||||
const dataJson = cleanAndParseJson(content) as ReportData[];
|
||||
if (standardsContext && standardsContext.length > 0) {
|
||||
return dataJson.map(report => validateCertificateAgainstStandard(report, standardsContext[0] as Standard));
|
||||
}
|
||||
return dataJson;
|
||||
};
|
||||
|
||||
export const analyzeCertificate = async (options: AnalyzeOptions): Promise<ReportData[]> => {
|
||||
@@ -334,7 +320,7 @@ export const analyzeWithOllama = async (file: File, endpoint: string, model: str
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
const prompt = buildAnalysisPrompt(standardsContext);
|
||||
const prompt = buildAnalysisPrompt();
|
||||
|
||||
let url = `${endpoint}/api/chat`;
|
||||
|
||||
@@ -392,7 +378,11 @@ export const analyzeWithOllama = async (file: File, endpoint: string, model: str
|
||||
throw new Error("Resposta vazia do Ollama/OpenWebUI");
|
||||
}
|
||||
|
||||
return cleanAndParseJson(content) as ReportData[];
|
||||
const dataJson = cleanAndParseJson(content) as ReportData[];
|
||||
if (standardsContext && standardsContext.length > 0) {
|
||||
return dataJson.map(report => validateCertificateAgainstStandard(report, standardsContext[0] as Standard));
|
||||
}
|
||||
return dataJson;
|
||||
};
|
||||
|
||||
export const analyzeWithOpenRouter = async (file: File, apiKey: string, model: string = 'openai/gpt-4o', standardsContext?: any[]): Promise<ReportData[]> => {
|
||||
@@ -406,7 +396,7 @@ export const analyzeWithOpenRouter = async (file: File, apiKey: string, model: s
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
const prompt = buildAnalysisPrompt(standardsContext);
|
||||
const prompt = buildAnalysisPrompt();
|
||||
|
||||
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
@@ -452,7 +442,11 @@ export const analyzeWithOpenRouter = async (file: File, apiKey: string, model: s
|
||||
throw new Error("Resposta vazia do OpenRouter");
|
||||
}
|
||||
|
||||
return cleanAndParseJson(content) as ReportData[];
|
||||
const dataJson = cleanAndParseJson(content) as ReportData[];
|
||||
if (standardsContext && standardsContext.length > 0) {
|
||||
return dataJson.map(report => validateCertificateAgainstStandard(report, standardsContext[0] as Standard));
|
||||
}
|
||||
return dataJson;
|
||||
};
|
||||
|
||||
export const buildStandardWithAI = async (
|
||||
@@ -463,32 +457,42 @@ export const buildStandardWithAI = async (
|
||||
const prompt = `Você é um engenheiro de materiais sênior. O usuário solicitou a criação de uma tabela de limites para a norma técnica: "${standardName}".
|
||||
A categoria deste material é descrita como: "${categoryDescription}".
|
||||
|
||||
Sua tarefa é acessar seu conhecimento sobre esta norma e gerar os limites estritos (mínimos e máximos) exigidos.
|
||||
Sua tarefa é acessar seu conhecimento sobre esta norma e gerar os limites estritos, incluindo as variações por faixa de espessura (bitola), grupos de perfil e tolerâncias de análise de produto.
|
||||
|
||||
RESPONDA APENAS UM JSON VÁLIDO no seguinte formato EXATO:
|
||||
{
|
||||
"codigo": "Nome oficial da norma (ex: ASTM A36/A36M-19)",
|
||||
"produto_especifico": "Descreva o tipo de produto que essa norma cobre",
|
||||
"id_norma": "id_unico_da_norma_sem_espacos",
|
||||
"codigo": "Nome oficial da norma (ex: ASTM A572/A572M)",
|
||||
"grau": "Grau se aplicável (ex: 50)",
|
||||
"produto_especifico": "Descreva o tipo de produto",
|
||||
"limites_quimicos_corrida": {
|
||||
"produtos_gerais": [
|
||||
{
|
||||
"espessura_max_mm": valor_numerico_ou_null,
|
||||
"carbono_max": valor_numerico,
|
||||
"manganes_max": valor_numerico
|
||||
}
|
||||
]
|
||||
},
|
||||
"tolerancias_analise_produto_astma6": {
|
||||
"carbono": {
|
||||
"tabela": [
|
||||
{ "limite_corrida_ate": 0.25, "tolerancia_mais": 0.03 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"parametros_controle": {
|
||||
"mecanicos": {
|
||||
"nome_do_parametro_1": valor_numerico,
|
||||
"nome_do_parametro_2": valor_numerico
|
||||
},
|
||||
"quimicos": {
|
||||
"nome_do_parametro": valor_numerico
|
||||
},
|
||||
"fisicos": {
|
||||
"nome_do_parametro": valor_numerico
|
||||
"limite_escoamento_min": valor_numerico
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Regras Cruciais:
|
||||
1. USE APENAS NÚMEROS (floats/ints) nos valores. NUNCA use strings como "0.25" ou "250". Use 0.25 e 250.
|
||||
2. Se a norma não especifica um limite máximo ou mínimo para um elemento, coloque null (sem aspas).
|
||||
3. O nome da chave dentro de mecânicos/químicos/físicos deve ser em minúsculo, sem acentos, com underscores, e DEVE conter a palavra "_min_" ou "_max_" (ex: "limite_escoamento_min_mpa", "carbono_max_percentual"). Se a norma define uma FAIXA (um mínimo e um máximo) para o mesmo elemento, você DEVE criar DUAS chaves separadas (ex: "columbio_min_percentual" e "columbio_max_percentual"). NUNCA combine min e max na mesma chave.
|
||||
4. Crie APENAS os grupos (mecanicos, quimicos, fisicos) que fazem sentido para a norma solicitada.
|
||||
5. DEVOLVA APENAS O JSON, sem nenhuma formatação ou texto explicativo ao redor.`;
|
||||
1. USE APENAS NÚMEROS (floats/ints) nos valores. NUNCA use strings como "0.25".
|
||||
2. Estruture as matrizes de limite químico considerando as faixas de espessura de acordo com a norma.
|
||||
3. Inclua a chave tolerancias_analise_produto_astma6 se aplicável.
|
||||
4. DEVOLVA APENAS O JSON, sem nenhuma formatação ou texto explicativo ao redor.`;
|
||||
|
||||
try {
|
||||
let resultJson = '';
|
||||
|
||||
Reference in New Issue
Block a user