AppConsulta360 / js /labAnalysisModule.js
aarnal80's picture
Update js/labAnalysisModule.js
56fccc4 verified
// js/labAnalysisModule.js
// Importaciones (sin cambios)
import { getIaConfig, llmProviders } from './iaConfigModule.js';
// Función buildLabAnalysisPrompt (sin cambios)
function buildLabAnalysisPrompt(rawLabText) {
const prompt = `Quiero que me ayudes a ordenar resultados de análisis médicos. Te proporcionaré datos de entrada desordenados y debes extraer los datos relevantes siguiendo el formato XLabs. El formato XLabs se define como seis categorías en este orden exacto, cada una en una línea continua:
Hematología
Coagulación
Bioquímica
Gasometría
Orina
Otras pruebas
La salida debe estar en español, formateada así:
Categoría: Parametro Resultado unidades | Parametro Resultado unidades | ...
Ejemplo de Entrada:
Glucosa * 171 mg/dL 74 - 109
Urea * 56 mg/dL 17 - 48
Creatinina 0.82 mg/dL 0.7
Ejemplo de Salida Esperada:
Bioquímica: Glucosa **171** mg/dL | Urea **56** mg/dL | Creatinina 0.82 mg/dL
Ajusta el formato con estas modificaciones específicas:
Hematología:
Incluye únicamente: Hematíes, Hemoglobina, Hematocrito, VCM, HCM.
Para elementos celulares (Leucocitos, Neutrófilos, Linfocitos, Monocitos, etc.): agrupa mostrando primero el porcentaje y, entre paréntesis, el valor absoluto. Ejemplo: Neutrofilos 49.1 % (4.03 10^9/L) | Linfocitos 39.8 % (3.27 10^9/L).
No incluyas Eosinófilos ni Basófilos si están dentro de los rangos normales.
Incluye Plaquetas, pero NO incluyas VPM.
Coagulación:
Resume los datos usando abreviaturas: "INR" para Ratio normalizado internacional, "PT" para Tiempo de protrombina (Quick), "PTT" para T. tromboplastina parcial activada.
Otras pruebas:
Si no hay elementos en una categoría (ej., "Otras pruebas: Ninguna"), OMITE completamente esa categoría en la salida. No escribas "Ninguna".
**MUY IMPORTANTE: Alerta de valores alterados:**
Si CUALQUIER parámetro está fuera del rango normal (alterado), **DEBES envolver el valor numérico resultado entre DOBLES ASTERISCOS**. Ejemplo: Glucosa **171** mg/dL. Asegúrate de aplicar esto consistentemente a TODOS y CADA UNO de los valores que identifiques como alterados según los rangos de referencia si están disponibles en la entrada, o si están marcados explícitamente como alterados (ej. con '*'). Si no puedes determinar si está alterado, no lo marques.
Reglas generales:
1. Usa normas gramaticales y mayúsculas correctas (ej., "Hematíes" no "HEMATIES").
2. Si un apartado (Categoría) no tiene ninguna prueba que mostrar después de aplicar las reglas, omite el apartado completo.
3. NO uses formato bold (negrita) ni cambios de tamaño de texto en tu respuesta, EXCEPTO los dobles asteriscos (**valor**) para marcar los valores alterados.
4. NO incluyas introducciones, conclusiones, ni frases extra (ej., evita decir "Aquí está el formato XLabs...", "Resultados formateados:", etc.).
5. Proporciona ÚNICAMENTE la respuesta directa con los resultados formateados como se especifica.
6. Sigue estrictamente TODAS estas instrucciones. No inventes ningún resultado.
Datos de laboratorio para analizar:
--- INICIO DATOS ---
${rawLabText}
--- FIN DATOS ---`;
return prompt;
}
// Función analyzeLabResults (sin cambios)
export async function analyzeLabResults(text) {
if (!text || !text.trim()) {
console.warn("[labAnalysisModule] analyzeLabResults llamada con texto vacío.");
return Promise.resolve("");
}
console.log("[labAnalysisModule] Iniciando análisis de exámenes...");
const config = getIaConfig();
if (!config || !config.llm || !config.llm.provider || !config.llm.model) {
throw new Error("Configuración del LLM incompleta. Por favor, revisa la Configuración IA.");
}
const provider = config.llm.provider;
const model = config.llm.model;
const apiKey = config.llm.apiKeys?.[provider];
if (!apiKey) {
throw new Error(`No se encontró API Key para el proveedor LLM '${provider}'. Revisa la Configuración IA.`);
}
const providerDetails = llmProviders.find(p => p.value === provider);
if (!providerDetails || !providerDetails.url) {
throw new Error(`Detalles (URL) no encontrados para el proveedor LLM '${provider}'.`);
}
let apiUrl;
if (provider === 'openai' || provider === 'deepseek') {
apiUrl = `${providerDetails.url}/v1/chat/completions`;
} else {
throw new Error(`Proveedor LLM '${provider}' no soportado actualmente por labAnalysisModule.`);
}
const systemMessage = "Eres un asistente experto en formatear resultados de análisis de laboratorio médicos siguiendo el formato XLabs y destacando valores alterados.";
const userPrompt = buildLabAnalysisPrompt(text);
let messages;
if (provider === 'openai') {
messages = [{ role: 'user', content: `${systemMessage}\n\n${userPrompt}` }];
} else {
messages = [{ role: 'system', content: systemMessage }, { role: 'user', content: userPrompt }];
}
const payload = { model: model, messages: messages, temperature: 0.2 };
console.log(`[labAnalysisModule] Enviando petición a ${apiUrl} con modelo ${model}`);
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Error ${response.status} de la API LLM: ${errorBody || response.statusText}`);
}
const data = await response.json();
const content = data.choices?.[0]?.message?.content;
if (!content) { throw new Error("La respuesta de la API no contiene el contenido esperado."); }
console.log("[labAnalysisModule] Análisis de exámenes completado exitosamente.");
return content.trim();
} catch (error) {
console.error("[labAnalysisModule] Fallo en la llamada API o procesamiento:", error);
throw error;
}
}
// --- INICIO: Función displayLabResults (MODIFICADA con filtro a NIVEL PARÁMETRO) ---
/**
* Muestra los resultados de laboratorio formateados en la UI.
* @param {string} resultsText - El texto formateado recibido de la IA.
* @param {HTMLElement} containerElement - El elemento del DOM donde mostrar los resultados.
* @param {boolean} filterAltered - `true` si se debe mostrar solo resultados alterados.
*/
export function displayLabResults(resultsText, containerElement, filterAltered) {
if (!containerElement) {
console.error("[labAnalysisModule] Contenedor de resultados no encontrado.");
return;
}
containerElement.innerHTML = '';
if (!resultsText || !resultsText.trim()) {
containerElement.textContent = 'No se generaron resultados.';
return;
}
const lines = resultsText.split('\n').filter(line => line.trim() !== '');
let linesToDisplay = lines; // Por defecto, mostrar todas
// Aplicar filtro A NIVEL DE PARÁMETRO si está activo
if (filterAltered) {
console.log("--- INICIO FILTRO ALTERADOS (Nivel Parámetro) ---");
const filteredOutputLines = []; // Aquí guardaremos las líneas reconstruidas
const alterationRegex = /\*\*.*?\*\*/; // Regex para detectar **...**
for (const line of lines) { // Iterar sobre las líneas originales de la IA
// Intentar separar la línea en "Cabecera: Contenido"
const headerMatch = line.match(/^([^:]+):\s*(.*)$/);
if (headerMatch) {
// Si coincide el formato "Cabecera: Contenido"
const headerName = headerMatch[1].trim(); // Ej: "Hematología"
const contentPart = headerMatch[2].trim(); // Ej: "Hematíes... | Hemoglobina..."
// Dividir el contenido en parámetros individuales usando "|"
const parameters = contentPart.split('|')
.map(p => p.trim()) // Quitar espacios extra de cada parámetro
.filter(p => p); // Quitar posibles parámetros vacíos
const alteredParametersInLine = []; // Guardar solo los parámetros alterados de ESTA línea
// Revisar cada parámetro de la línea actual
for (const param of parameters) {
if (alterationRegex.test(param)) { // Si el parámetro contiene **...**
alteredParametersInLine.push(param); // Añadirlo a la lista de alterados de esta línea
console.log(` -> Parámetro Alterado Encontrado: "${param}" bajo "${headerName}"`);
}
}
// Si encontramos algún parámetro alterado en esta línea...
if (alteredParametersInLine.length > 0) {
// ...reconstruir la línea solo con la cabecera y esos parámetros alterados.
const filteredLine = `${headerName}: ${alteredParametersInLine.join(' | ')}`;
filteredOutputLines.push(filteredLine); // Añadir la línea reconstruida al resultado final
console.log(` -> Línea Filtrada Construida: "${filteredLine}"`);
} else {
// Si no hubo alterados en esta línea, simplemente la ignoramos (no se añade a filteredOutputLines)
console.log(` -> Cabecera "${headerName}" sin parámetros alterados, omitiendo línea completa.`);
}
} else {
// Si una línea no tiene el formato "Cabecera: Contenido", la ignoramos al filtrar
console.log(` -> Línea ignorada en filtro (no coincide formato Cabecera: Contenido): "${line}"`);
}
} // Fin del bucle sobre las líneas originales
// Asignar el resultado del filtrado a linesToDisplay
if (filteredOutputLines.length === 0 && lines.length > 0) {
linesToDisplay = ["No se encontraron resultados marcados como alterados."];
console.log("--- FIN FILTRO ALTERADOS (Resultado: Vacío) ---");
} else {
linesToDisplay = filteredOutputLines; // Usar las líneas reconstruidas y filtradas
console.log("--- FIN FILTRO ALTERADOS (Resultado: Mostrando filtrados por parámetro) ---");
}
console.log("Líneas finales a mostrar (filtrado por parámetro):", linesToDisplay);
} // Fin if(filterAltered)
// Formateo y renderizado (sin cambios, opera sobre linesToDisplay)
const fragment = document.createDocumentFragment();
linesToDisplay.forEach(line => {
const p = document.createElement('p');
p.style.margin = '0 0 0.3em 0';
// Reemplazar **valor** por <strong> con estilo
p.innerHTML = line.replace(
/\*\*(.*?)\*\*/g,
'<strong class="text-red-600 font-bold">$1</strong>'
);
fragment.appendChild(p);
});
containerElement.appendChild(fragment);
}
// --- FIN: Función displayLabResults ---