File size: 9,481 Bytes
7630b67
 
 
 
 
 
 
 
 
a0857c5
 
83c610d
 
 
 
7630b67
bdafc7b
7630b67
 
 
 
 
 
 
 
83c610d
 
 
7630b67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83c610d
 
7630b67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4e0d3a6
bdafc7b
7630b67
4e0d3a6
7630b67
 
bdafc7b
7630b67
 
 
 
 
 
 
 
4e0d3a6
773ceb2
 
 
 
bdafc7b
 
 
 
 
 
 
 
 
 
 
773ceb2
 
 
 
 
 
 
 
 
bdafc7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4e0d3a6
7630b67
8d82e61
7630b67
8d82e61
bdafc7b
8d82e61
7630b67
32fea89
7630b67
4e0d3a6
773ceb2
4e0d3a6
bdafc7b
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import gradio as gr
from google.adk.agents import Agent
import os
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.genai import types
from typing import Dict, Any
from google.adk.models.lite_llm import LiteLlm
import uuid
import asyncio
from datetime import datetime

def obtener_fecha_ddmmaaaa():
    return datetime.today().strftime("%d/%m/%Y")


#download_files_from_drive_tool = FunctionTool(func=download_files_from_drive)
# --- Definición de Mod

# Definir función para el Agente Revisor

# Envolver funciones como herramientas de ADK
# Es importante que el nombre de la herramienta sea el mismo que el nombre de la función para el ADK

instruction = f"""Eres un auditor médico especializado en la revisión administrativa de órdenes médicas. Tu función es verificar el cumplimiento de requisitos documentales según normativas vigentes. Fecha actual de referencia: **26 de junio de 2025**.

Hoy es {obtener_fecha_ddmmaaaa()}

## Instrucciones de Procesamiento
**IMPORTANTE**: Responde SIEMPRE en español. Analiza únicamente documentos que contengan órdenes médicas. Si el usuario te envia texto, respondele amablemente que te envie las imagenes.

## Criterios de Verificación Obligatorios

### 0. Validación del Plan del Afiliado (OBLIGATORIO)
Siempre debes invocar la herramienta `validar_plan_de_afiliado`. Esta verificación es **obligatoria** y se realiza **aunque todos los demás requisitos estén correctos**.

- El valor de entrada debe ser siempre: `"Plata"`, **independientemente del texto en el documento**.
- Solo puedes invocar esta herramienta **una única vez**
- Recibiras una unica orden.

⚠️ **Si no ejecutas esta herramienta, el análisis se considerará incompleto.**

---

### 1. Datos del Paciente
- **Nombre y apellido completos**: deben estar claramente legibles y sin ambigüedades o abreviaturas confusas.

### 2. Información Temporal
- **Fecha de emisión**: debe estar presente, legible y dentro de los **últimos 60 días** desde la fecha actual.
- Formatos válidos: DD/MM/AAAA, DD-MM-AAAA o fecha escrita completa.
- Si la fecha está fuera del rango la orden debe ser rechazada.
- Si la fecha es futura la orden debe ser rechazada.

### 3. Identificación Profesional
- **Número de credencial o matrícula médica**: debe estar presente y ser legible. Validar que corresponda a una credencial médica.

### 4. Diagnóstico
- **Obligatorio**. Debe estar explícitamente mencionado. Si **falta**, la orden debe ser rechazada sin excepciones.

### 5. Práctica solicitada
- **Obligatoria**. Debe estar claramente especificada. Si **falta**, la orden debe ser rechazada sin excepciones.

### 6. Indicaciones médicas
- **Este es un criterio obligatorio y excluyente**.
- Debe haber un **detalle clínico que justifique la práctica solicitada**.
- ⚠️ **NO se debe asumir, inferir o deducir información si las indicaciones no están presentes explícitamente**.
- Si **no hay indicaciones escritas o son insuficientes**, **la orden debe ser RECHAZADA automáticamente**, sin importar el resto del contenido.
- Si no se menciona la palabra 'Indicaciones' **la orden debe ser RECHAZADA automáticamente**, sin importar el resto del contenido.

✅ Ejemplo válido: “Dolor lumbar irradiado a miembro inferior izquierdo desde hace 2 meses. No responde a medicación. Se solicita resonancia lumbar.”
❌ Ejemplo inválido: “Resonancia de columna lumbar”, sin ningún detalle clínico.

---

### 7. Firma / Validación Legal
- Debe incluir la **firma del profesional**, y esta debe ser legible.
- La firma debe permitir identificar al profesional responsable.

---

## Formato de Respuesta

### Si la orden es ACEPTADA:
**"Orden aceptada. Se revisará con auditoría médica y se notificarán los resultados."**

**VERIFICACIÓN COMPLETADA:**
- ✅ Plan de afiliado: Plan **Plata** tiene cobertura para [insertar práctica solicitada]
- ✅ Nombre y apellido: Presente
- ✅ Fecha: Presente y dentro del rango válido
- ✅ Número de credencial: Presente
- ✅ Diagnóstico: Presente
- ✅ Práctica solicitada: Presente
- ✅ Indicaciones: Presentes y justificadas clínicamente
- ✅ Firma: Presente

---

### Si la orden es RECHAZADA:
**"Orden rechazada por los siguientes motivos:"**

**VERIFICACIÓN COMPLETADA:**
- [Lista detallada con el estado de cada criterio]
- **ELEMENTOS FALTANTES O DEFICIENTES:**
  - [Descripción clara del problema encontrado]
  - [Por qué este elemento es obligatorio]

---

## Criterios de Calidad

- **Legibilidad**: Todo debe ser legible y comprensible.
- **Coherencia**: Fechas realistas. Datos consistentes.
- **Completitud**: No se aceptan elementos vacíos, incompletos o poco claros.
- **No se debe completar con supuestos o inferencias. Todo dato debe estar explícito.**

---

## Protocolo de Error

En caso de documentos no procesables (archivo corrupto, ilegible o no es una orden médica):
**"No se puede procesar el documento. Verifique que sea un archivo válido que contenga una orden médica."**
"""
def validar_plan_de_afiliado(plan: str, practica: str) -> Dict[str, Any]:
    """
    Verifica si un plan de afiliado específico cubre una práctica médica determinada.

    Parámetros:
    - plan (str): El nombre del plan del afiliado (por ejemplo, "Plata").
    - practica (str): El nombre de la práctica médica a validar (por ejemplo, "Resonancia de Columna Lumbar").

    Retorna:
    - Dict[str, Any]: Un diccionario con el resultado de la validación. Contiene:
        - "status" (str): Estado de la validación ("success" o "error").
        - "comment" (str): Comentario descriptivo sobre el resultado de la validación.
    """
    return {
        "status": "success",
        "comment": f"Validación correcta. El Plan Plata tiene cobertura para la práctica Resonancia de Columna Lumbar."
    }



APP_NAME = "predoc_app"

# Definir agente raíz
root_agent = LlmAgent(
    model=LiteLlm(model="openai/gpt-4.1"),
    generate_content_config=types.GenerateContentConfig(temperature=0.0),
    name="PreDoc",
    instruction=instruction,
    description="Asistente médico para renovación de recetas",
    tools=[validar_plan_de_afiliado]
)

session_service = InMemorySessionService()

# Esta función se conecta a ChatInterface
def respond(message, history):
    # Detectar si es inicio de conversación (history vacío o con 0 mensajes)
    print("HISTORY",history)
    if history is None or len(history) == 0:
        user_id = str(uuid.uuid4())
        session_id = str(uuid.uuid4())
        print(f"🔄 Nueva sesión: {session_id}")

        async def create_session():
            await session_service.create_session(
                app_name=APP_NAME,
                user_id=user_id,
                session_id=session_id
            )
        asyncio.run(create_session())
        # Guardar en algún lado user_id y session_id para usar en las próximas llamadas
        # Por simplicidad acá lo guardamos en variables globales
        global CURRENT_USER_ID, CURRENT_SESSION_ID
        CURRENT_USER_ID = user_id
        CURRENT_SESSION_ID = session_id
    else:
        # Usar IDs existentes
        user_id = CURRENT_USER_ID
        session_id = CURRENT_SESSION_ID

    runner = Runner(agent=root_agent, app_name=APP_NAME, session_service=session_service)

    def call_agent_text(query):
        content = types.Content(role='user', parts=[types.Part(text=query)])
        events = runner.run(user_id=user_id, session_id=session_id, new_message=content)
        for event in events:
            if event.is_final_response():
                return event.content.parts[0].text
        return "No se obtuvo respuesta."

    def call_agent_image(query):
        images = []
        for q in query:
            with open(q, 'rb') as f:
                image_bytes = f.read()
            images.append(types.Part.from_bytes(data=image_bytes, mime_type='image/jpeg'))

        content = types.Content(role='user', parts=images)
        events = runner.run(user_id=user_id, session_id=session_id, new_message=content)
        for event in events:
            if event.is_final_response():
                return event.content.parts[0].text
        return "No se obtuvo respuesta."

    def call_agent_both(image, text):
        with open(image[0], 'rb') as f:
            image_bytes = f.read()
        content = types.Content(
            role='user',
            parts=[
                types.Part.from_bytes(data=image_bytes, mime_type='image/jpeg'),
                types.Part(text=text)
            ]
        )
        events = runner.run(user_id=user_id, session_id=session_id, new_message=content)
        for event in events:
            if event.is_final_response():
                return event.content.parts[0].text
        return "No se obtuvo respuesta."

    # Dispatcher
    if message['text'] != '' and len(message['files']) > 0:
        return call_agent_both(message['files'], message['text'])
    elif message['text'] == '' and len(message['files']) > 0:
        return call_agent_image(message['files'])
    elif message['text'] != '' and len(message['files']) == 0:
        return call_agent_text(message['text'])
    else:
        return "Escribe algo para que pueda contestarte."

# Inicializamos demo sin el argumento state
demo = gr.ChatInterface(fn=respond, title="Agente Revisor", multimodal=True)

demo.launch(debug=True)