File size: 10,457 Bytes
7b53631
 
69d90a0
7b53631
 
69d90a0
7b53631
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69d90a0
7b53631
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69d90a0
7b53631
 
 
 
 
 
 
 
 
 
f7d27ea
 
 
 
7b53631
f7d27ea
7b53631
 
 
 
f7d27ea
7b53631
 
 
f7d27ea
7b53631
 
 
 
69d90a0
7b53631
 
 
 
69d90a0
7b53631
 
 
f7d27ea
56fccc4
7b53631
69d90a0
7b53631
 
69d90a0
7b53631
 
 
69d90a0
7b53631
 
69d90a0
7b53631
 
69d90a0
7b53631
 
 
69d90a0
56fccc4
7b53631
56fccc4
7b53631
56fccc4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69d90a0
56fccc4
 
 
 
 
 
 
 
 
 
 
 
 
 
f7d27ea
56fccc4
7b53631
56fccc4
 
 
 
f7d27ea
56fccc4
 
7b53631
56fccc4
 
 
7b53631
56fccc4
7b53631
 
 
69d90a0
56fccc4
7b53631
69d90a0
 
7b53631
 
 
69d90a0
f7d27ea
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// 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 ---