Spaces:
Running
Running
| // 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 --- |