From 999896f89e02012d3d0ac019ac8e1d82a100792e Mon Sep 17 00:00:00 2001 From: Luis Ernesto Portillo Zaldivar Date: Tue, 15 Jul 2025 13:56:09 -0600 Subject: [PATCH] =?UTF-8?q?feat(#51):=20Task=2011=20completada=20-=20Datos?= =?UTF-8?q?=20de=20demostraci=C3=B3n=20con=20cat=C3=A1logo=20de=20par?= =?UTF-8?q?=C3=A1metros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Creados 36 parámetros de análisis en parameter_demo.xml - Creados 31 rangos de referencia en parameter_range_demo.xml - Creadas 40 configuraciones parámetro-análisis en analysis_parameter_config_demo.xml - Consolidado script de creación de datos demo en test/create_demo_data.py - Actualizado init_odoo.py para usar script consolidado - Eliminados scripts obsoletos (04_demo_lab_orders.sh, create_test_demo_data.py) - Verificada carga exitosa de todos los datos demo --- .claude/settings.local.json | 4 +- init_odoo.py | 49 ++- lims_management/__manifest__.py | 3 + .../demo/analysis_parameter_config_demo.xml | 363 +++++++++++++++ lims_management/demo/parameter_demo.xml | 339 ++++++++++++++ lims_management/demo/parameter_range_demo.xml | 374 ++++++++++++++++ test/create_demo_data.py | 415 ++++++++++++++++++ test/create_test_demo_data.py | 225 ---------- test/verify_demo_data.py | 159 +++++++ 9 files changed, 1695 insertions(+), 236 deletions(-) create mode 100644 lims_management/demo/analysis_parameter_config_demo.xml create mode 100644 lims_management/demo/parameter_demo.xml create mode 100644 lims_management/demo/parameter_range_demo.xml create mode 100644 test/create_demo_data.py delete mode 100644 test/create_test_demo_data.py create mode 100644 test/verify_demo_data.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f9f6276..f15c95c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -21,7 +21,9 @@ "WebFetch(domain:apps.odoo.com)", "Bash(dir:*)", "Bash(find:*)", - "Bash(true)" + "Bash(true)", + "Bash(bash:*)", + "Bash(grep:*)" ], "deny": [] } diff --git a/init_odoo.py b/init_odoo.py index fdff0c5..46e197f 100644 --- a/init_odoo.py +++ b/init_odoo.py @@ -36,6 +36,7 @@ odoo_command = [ "-d", DB_NAME, "-i", MODULES_TO_INSTALL, "--load-language", "es_ES", + "--without-demo=", # Forzar carga de datos demo "--stop-after-init" ] @@ -99,34 +100,62 @@ EOF print("\nCreando datos de demostración de pruebas de laboratorio...") sys.stdout.flush() - if os.path.exists("/app/test/create_test_demo_data.py"): - with open("/app/test/create_test_demo_data.py", "r") as f: - test_script_content = f.read() + # Usar el nuevo script consolidado de datos demo + demo_script_path = "/app/test/create_demo_data.py" + if os.path.exists(demo_script_path): + with open(demo_script_path, "r") as f: + demo_script_content = f.read() - create_tests_command = f""" + create_demo_command = f""" odoo shell -c {ODOO_CONF} -d {DB_NAME} <<'EOF' -{test_script_content} +{demo_script_content} EOF """ result = subprocess.run( - create_tests_command, + create_demo_command, shell=True, capture_output=True, text=True, check=False ) - print("--- Create Test Demo Data stdout ---") + print("--- Create Demo Data stdout ---") print(result.stdout) - print("--- Create Test Demo Data stderr ---") + print("--- Create Demo Data stderr ---") print(result.stderr) sys.stdout.flush() if result.returncode == 0: - print("Datos de demostración de pruebas creados exitosamente.") + print("Datos de demostración creados exitosamente.") else: - print(f"Advertencia: Fallo al crear datos de demostración de pruebas (código {result.returncode})") + print(f"Advertencia: Fallo al crear datos de demostración (código {result.returncode})") + else: + # Fallback al script anterior si existe + old_script_path = "/app/test/create_test_demo_data.py" + if os.path.exists(old_script_path): + print("Usando script de demostración anterior...") + with open(old_script_path, "r") as f: + test_script_content = f.read() + + create_tests_command = f""" + odoo shell -c {ODOO_CONF} -d {DB_NAME} <<'EOF' +{test_script_content} +EOF + """ + + result = subprocess.run( + create_tests_command, + shell=True, + capture_output=True, + text=True, + check=False + ) + + if result.returncode == 0: + print("Datos de demostración de pruebas creados exitosamente.") + else: + print(f"Advertencia: Fallo al crear datos de demostración de pruebas (código {result.returncode})") # --- Actualizar logo de la empresa --- print("\nActualizando logo de la empresa...") diff --git a/lims_management/__manifest__.py b/lims_management/__manifest__.py index 27aafab..c7bf774 100644 --- a/lims_management/__manifest__.py +++ b/lims_management/__manifest__.py @@ -48,6 +48,9 @@ 'demo/z_lims_demo.xml', 'demo/z_analysis_demo.xml', 'demo/z_sample_demo.xml', + 'demo/parameter_demo.xml', + 'demo/parameter_range_demo.xml', + 'demo/analysis_parameter_config_demo.xml', 'demo/z_automatic_generation_demo.xml', ], 'installable': True, diff --git a/lims_management/demo/analysis_parameter_config_demo.xml b/lims_management/demo/analysis_parameter_config_demo.xml new file mode 100644 index 0000000..b61b03a --- /dev/null +++ b/lims_management/demo/analysis_parameter_config_demo.xml @@ -0,0 +1,363 @@ + + + + + + + + 10 + True + + + + + + 20 + True + + + + + + 30 + True + + + + + + 40 + True + + + + + + 50 + True + + + + + + 60 + True + + + + + + 70 + True + + + + + + + 10 + True + + + + + + 20 + True + + + + + + 30 + True + + + + + + 40 + True + + + + + + + 10 + True + + + + + + + 10 + True + + + + + + 20 + False + Completar solo si el cultivo es positivo + + + + + + 30 + False + Completar solo si el cultivo es positivo. Formato: >100,000 UFC/mL + + + + + + + 10 + True + + + + + + 20 + True + + + + + + + 10 + True + + + + + + 20 + False + + + + + + + 10 + True + + + + + + 20 + False + + + + + + + Química Sanguínea Básica + True + chemistry + + service + + + + 3.0 + + Panel básico de química sanguínea que incluye glucosa, creatinina, urea, ALT y AST. + + + + + + + + 10 + True + + + + + + 20 + True + + + + + + 30 + True + + + + + + 40 + True + + + + + + 50 + True + + + + + Urianálisis Completo + True + other + + service + + + + 10.0 + + Examen completo de orina que incluye examen físico, químico y microscópico del sedimento. + + + + + + + + 10 + True + + + + + + 20 + True + + + + + + 30 + True + + + + + + 40 + True + + + + + + 50 + True + + + + + + 60 + True + + + + + + 70 + True + + + + + + 80 + True + + + + + + 90 + True + + + + + Panel de Serología Básica + True + immunology + + service + + + + 5.0 + + Panel serológico que incluye HIV, Hepatitis B, Hepatitis C y VDRL. + + + + + + + + 10 + True + + + + + + 20 + True + + + + + + 30 + True + + + + + + 40 + True + + + + + Prueba de Embarazo en Sangre + True + immunology + + service + + + + 1.0 + + Detección cualitativa de Beta-HCG en sangre. + + + + + + + 10 + True + + + + \ No newline at end of file diff --git a/lims_management/demo/parameter_demo.xml b/lims_management/demo/parameter_demo.xml new file mode 100644 index 0000000..585a565 --- /dev/null +++ b/lims_management/demo/parameter_demo.xml @@ -0,0 +1,339 @@ + + + + + + + + HGB + Hemoglobina + numeric + g/dL + Concentración de hemoglobina en sangre + + + + + HCT + Hematocrito + numeric + % + Porcentaje del volumen de glóbulos rojos + + + + + RBC + Glóbulos Rojos + numeric + millones/µL + Recuento de eritrocitos + + + + + WBC + Glóbulos Blancos + numeric + mil/µL + Recuento de leucocitos + + + + + PLT + Plaquetas + numeric + mil/µL + Recuento de plaquetas + + + + + NEUT + Neutrófilos + numeric + % + Porcentaje de neutrófilos + + + + + LYMPH + Linfocitos + numeric + % + Porcentaje de linfocitos + + + + + + + GLU + Glucosa + numeric + mg/dL + Nivel de glucosa en sangre + + + + + CREA + Creatinina + numeric + mg/dL + Nivel de creatinina sérica + + + + + UREA + Urea + numeric + mg/dL + Nivel de urea en sangre + + + + + CHOL + Colesterol Total + numeric + mg/dL + Nivel de colesterol total + + + + + HDL + Colesterol HDL + numeric + mg/dL + Colesterol de alta densidad + + + + + LDL + Colesterol LDL + numeric + mg/dL + Colesterol de baja densidad + + + + + TRIG + Triglicéridos + numeric + mg/dL + Nivel de triglicéridos + + + + + ALT + Alanina Aminotransferasa (ALT) + numeric + U/L + Enzima hepática ALT + + + + + AST + Aspartato Aminotransferasa (AST) + numeric + U/L + Enzima hepática AST + + + + + + + U-COLOR + Color + selection + Amarillo claro,Amarillo,Amarillo oscuro,Ámbar,Rojizo,Marrón,Turbio + Color de la muestra de orina + + + + + U-ASP + Aspecto + selection + Transparente,Ligeramente turbio,Turbio,Muy turbio + Aspecto de la muestra de orina + + + + + U-PH + pH + numeric + unidades + pH de la orina + + + + + U-DENS + Densidad + numeric + g/mL + Densidad específica de la orina + + + + + U-PROT + Proteínas + selection + Negativo,Trazas,+,++,+++,++++ + Presencia de proteínas en orina + + + + + U-GLU + Glucosa + selection + Negativo,Trazas,+,++,+++,++++ + Presencia de glucosa en orina + + + + + U-SANG + Sangre + selection + Negativo,Trazas,+,++,+++ + Presencia de sangre en orina + + + + + U-LEU + Leucocitos + numeric + por campo + Leucocitos en sedimento urinario + + + + + U-BACT + Bacterias + selection + Escasas,Moderadas,Abundantes + Presencia de bacterias en orina + + + + + + + CULT + Resultado del Cultivo + selection + Negativo,Positivo + Resultado del cultivo microbiológico + + + + + MICRO + Microorganismo Aislado + text + Identificación del microorganismo + + + + + UFC + Recuento de Colonias + text + UFC/mL (Unidades Formadoras de Colonias) + + + + + + + TP + Tiempo de Protrombina + numeric + segundos + Tiempo de coagulación PT + + + + + INR + INR + numeric + ratio + Índice Internacional Normalizado + + + + + TTP + Tiempo de Tromboplastina Parcial + numeric + segundos + Tiempo de coagulación PTT + + + + + + + HIV + HIV 1/2 + selection + No Reactivo,Reactivo,Indeterminado + Anticuerpos anti-HIV + + + + + HBsAg + Antígeno de Superficie Hepatitis B + selection + No Reactivo,Reactivo,Indeterminado + HBsAg + + + + + HCV + Anticuerpos Hepatitis C + selection + No Reactivo,Reactivo,Indeterminado + Anti-HCV + + + + + VDRL + VDRL + selection + No Reactivo,Reactivo + Prueba de sífilis VDRL + + + + + HCG + Prueba de Embarazo + selection + Negativo,Positivo + Beta-HCG cualitativa + + + + \ No newline at end of file diff --git a/lims_management/demo/parameter_range_demo.xml b/lims_management/demo/parameter_range_demo.xml new file mode 100644 index 0000000..816d9c5 --- /dev/null +++ b/lims_management/demo/parameter_range_demo.xml @@ -0,0 +1,374 @@ + + + + + + + Hombre adulto + male + 18 + 99 + 13.5 + 17.5 + 7.0 + 20.0 + + + + + Mujer adulta + female + 18 + 99 + False + 12.0 + 15.5 + 7.0 + 20.0 + + + + + Mujer embarazada + female + 15 + 50 + True + 11.0 + 14.0 + 7.0 + 20.0 + + + + + Niños 2-12 años + both + 2 + 12 + 11.5 + 14.5 + 7.0 + 20.0 + + + + + + Hombre adulto + male + 18 + 99 + 41 + 53 + 20 + 60 + + + + + Mujer adulta + female + 18 + 99 + 36 + 46 + 20 + 60 + + + + + + Hombre adulto + male + 18 + 99 + 4.5 + 5.9 + + + + + Mujer adulta + female + 18 + 99 + 4.1 + 5.1 + + + + + + Adulto + both + 18 + 99 + 4.5 + 11.0 + 2.0 + 30.0 + + + + + Niño + both + 2 + 17 + 5.0 + 15.0 + 2.0 + 30.0 + + + + + + Todos + both + 0 + 99 + 150 + 400 + 50 + 1000 + + + + + + Adulto + both + 18 + 99 + 45 + 70 + + + + + + Adulto + both + 18 + 99 + 20 + 45 + + + + + + Ayunas + both + 0 + 99 + 70 + 100 + 40 + 500 + Valores normales en ayunas. Prediabetes: 100-125 mg/dL. Diabetes: ≥126 mg/dL + + + + + + Hombre adulto + male + 18 + 99 + 0.7 + 1.3 + 6.0 + + + + + Mujer adulta + female + 18 + 99 + 0.6 + 1.1 + 6.0 + + + + + + Adulto + both + 18 + 99 + 15 + 45 + 100 + + + + + + Adulto + both + 18 + 99 + 0 + 200 + Deseable: <200 mg/dL. Límite alto: 200-239 mg/dL. Alto: ≥240 mg/dL + + + + + + Hombre + male + 18 + 99 + 40 + 100 + + + + + Mujer + female + 18 + 99 + 50 + 100 + + + + + + Adulto + both + 18 + 99 + 0 + 100 + Óptimo: <100 mg/dL. Casi óptimo: 100-129 mg/dL. Límite alto: 130-159 mg/dL. Alto: 160-189 mg/dL. Muy alto: ≥190 mg/dL + + + + + + Adulto + both + 18 + 99 + 0 + 150 + 500 + Normal: <150 mg/dL. Límite alto: 150-199 mg/dL. Alto: 200-499 mg/dL. Muy alto: ≥500 mg/dL + + + + + + Hombre + male + 18 + 99 + 10 + 40 + 1000 + + + + + Mujer + female + 18 + 99 + 10 + 35 + 1000 + + + + + + Adulto + both + 18 + 99 + 10 + 40 + 1000 + + + + + + Normal + both + 0 + 99 + 4.5 + 8.0 + + + + + + Normal + both + 0 + 99 + 1.003 + 1.030 + + + + + + Normal + both + 0 + 99 + 0 + 5 + + + + + + Normal + both + 0 + 99 + 11 + 13.5 + 9 + 30 + + + + + + Sin anticoagulación + both + 0 + 99 + 0.8 + 1.2 + + + + + + Normal + both + 0 + 99 + 25 + 35 + 20 + 70 + + + + \ No newline at end of file diff --git a/test/create_demo_data.py b/test/create_demo_data.py new file mode 100644 index 0000000..5c47913 --- /dev/null +++ b/test/create_demo_data.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- +""" +Script para crear datos de demostración completos para el módulo LIMS. +Incluye órdenes de laboratorio, muestras, pruebas y resultados. +""" + +import odoo +from datetime import datetime, timedelta +import random +import logging + +_logger = logging.getLogger(__name__) + +def create_demo_lab_data(cr): + """Crea datos completos de demostración para laboratorio""" + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + + print("\n=== INICIANDO CREACIÓN DE DATOS DE DEMOSTRACIÓN ===") + + # Verificar que los parámetros y rangos se cargaron correctamente + param_count = env['lims.analysis.parameter'].search_count([]) + range_count = env['lims.parameter.range'].search_count([]) + + print(f"Parámetros encontrados: {param_count}") + print(f"Rangos de referencia encontrados: {range_count}") + + if param_count == 0 or range_count == 0: + print("⚠️ No se encontraron parámetros o rangos. Asegúrese de que los datos XML se cargaron.") + return + + # Obtener pacientes de demostración + patients = [] + patient_refs = [ + 'lims_management.demo_patient_1', + 'lims_management.demo_patient_2', + 'lims_management.demo_patient_3', + 'lims_management.demo_patient_4' + ] + + for ref in patient_refs: + patient = env.ref(ref, raise_if_not_found=False) + if patient: + patients.append(patient) + + if not patients: + print("⚠️ No se encontraron pacientes de demostración") + return + + print(f"Pacientes encontrados: {len(patients)}") + + # Obtener doctores + doctors = [] + doctor_refs = ['lims_management.demo_doctor_1', 'lims_management.demo_doctor_2'] + for ref in doctor_refs: + doctor = env.ref(ref, raise_if_not_found=False) + if doctor: + doctors.append(doctor) + + if not doctors: + # Crear un doctor de demo si no existe + doctors = [env['res.partner'].create({ + 'name': 'Dr. Demo', + 'is_doctor': True + })] + + # Obtener análisis disponibles + analyses = [] + analysis_refs = [ + 'lims_management.analysis_hemograma', + 'lims_management.analysis_perfil_lipidico', + 'lims_management.analysis_glucosa', + 'lims_management.analysis_quimica_sanguinea', + 'lims_management.analysis_urianalisis', + 'lims_management.analysis_serologia', + 'lims_management.analysis_urocultivo', + 'lims_management.analysis_tp', + 'lims_management.analysis_prueba_embarazo' + ] + + for ref in analysis_refs: + analysis = env.ref(ref, raise_if_not_found=False) + if analysis: + analyses.append(analysis) + + print(f"Análisis encontrados: {len(analyses)}") + + if not analyses: + print("⚠️ No se encontraron análisis de demostración") + return + + # Crear órdenes de laboratorio + orders_created = [] + + # Orden 1: Chequeo general para paciente adulto masculino + if len(patients) > 0 and len(analyses) >= 4: + order1 = env['sale.order'].create({ + 'partner_id': patients[0].id, + 'doctor_id': doctors[0].id if doctors else False, + 'is_lab_request': True, + 'lab_request_priority': 'normal', + 'observations': 'Chequeo general anual - Control de salud preventivo', + 'order_line': [ + (0, 0, { + 'product_id': analyses[0].product_variant_id.id, # Hemograma + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[1].product_variant_id.id, # Perfil Lipídico + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[2].product_variant_id.id, # Glucosa + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[3].product_variant_id.id, # Química Sanguínea + 'product_uom_qty': 1 + }) + ] + }) + order1.action_confirm() + orders_created.append(order1) + print(f"✓ Orden {order1.name} creada para {order1.partner_id.name}") + + # Orden 2: Control prenatal para paciente embarazada + if len(patients) > 1 and len(analyses) >= 5: + # Asegurarse de que la paciente esté marcada como embarazada + patients[1].is_pregnant = True + + order2 = env['sale.order'].create({ + 'partner_id': patients[1].id, + 'doctor_id': doctors[-1].id if doctors else False, + 'is_lab_request': True, + 'lab_request_priority': 'high', + 'observations': 'Control prenatal - 20 semanas de gestación', + 'order_line': [ + (0, 0, { + 'product_id': analyses[0].product_variant_id.id, # Hemograma + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[2].product_variant_id.id, # Glucosa + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[4].product_variant_id.id if len(analyses) > 4 else analyses[0].product_variant_id.id, # Urianálisis + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[5].product_variant_id.id if len(analyses) > 5 else analyses[1].product_variant_id.id, # Serología + 'product_uom_qty': 1 + }) + ] + }) + order2.action_confirm() + orders_created.append(order2) + print(f"✓ Orden {order2.name} creada para {order2.partner_id.name} (embarazada)") + + # Orden 3: Urgencia - Sospecha de infección + if len(patients) > 2 and len(analyses) >= 3: + order3 = env['sale.order'].create({ + 'partner_id': patients[2].id, + 'doctor_id': doctors[0].id if doctors else False, + 'is_lab_request': True, + 'lab_request_priority': 'urgent', + 'observations': 'Urgencia - Fiebre de 39°C, dolor lumbar, sospecha de infección urinaria', + 'order_line': [ + (0, 0, { + 'product_id': analyses[4].product_variant_id.id if len(analyses) > 4 else analyses[0].product_variant_id.id, # Urianálisis + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[6].product_variant_id.id if len(analyses) > 6 else analyses[1].product_variant_id.id, # Urocultivo + 'product_uom_qty': 1 + }), + (0, 0, { + 'product_id': analyses[0].product_variant_id.id, # Hemograma (para ver leucocitos) + 'product_uom_qty': 1 + }) + ] + }) + order3.action_confirm() + orders_created.append(order3) + print(f"✓ Orden urgente {order3.name} creada para {order3.partner_id.name}") + + # Orden 4: Control pediátrico + if len(patients) > 3: + order4 = env['sale.order'].create({ + 'partner_id': patients[3].id, + 'doctor_id': doctors[-1].id if doctors else False, + 'is_lab_request': True, + 'lab_request_priority': 'normal', + 'observations': 'Control pediátrico - Evaluación de anemia, niña con palidez', + 'order_line': [ + (0, 0, { + 'product_id': analyses[0].product_variant_id.id, # Hemograma completo + 'product_uom_qty': 1 + }) + ] + }) + order4.action_confirm() + orders_created.append(order4) + print(f"✓ Orden pediátrica {order4.name} creada para {order4.partner_id.name}") + + print(f"\n📋 Total de órdenes creadas: {len(orders_created)}") + + # Procesar muestras y generar resultados + for idx, order in enumerate(orders_created): + print(f"\n--- Procesando orden {idx + 1}/{len(orders_created)}: {order.name} ---") + + # Generar muestras si no existen + if not order.lab_sample_ids: + order.action_generate_samples() + print(f" ✓ Muestras generadas: {len(order.lab_sample_ids)}") + + # Procesar cada muestra + for sample in order.lab_sample_ids: + # Marcar como recolectada + if sample.sample_state == 'pending_collection': + sample.action_collect() + print(f" ✓ Muestra {sample.name} recolectada") + + # Procesar pruebas de esta muestra + for test in sample.test_ids: + print(f" - Procesando prueba: {test.product_id.name}") + + # Iniciar proceso si está en borrador + if test.state == 'draft': + test.action_start_process() + + # La generación automática de resultados ya debería haberse ejecutado + if test.result_ids: + print(f" ✓ Resultados generados automáticamente: {len(test.result_ids)}") + + # Simular ingreso de valores en los resultados + simulate_test_results(env, test) + + # Marcar como resultados ingresados + if test.state == 'in_process': + test.action_enter_results() + print(f" ✓ Resultados ingresados") + + # Validar las primeras 2 órdenes completas y algunas pruebas de la tercera + should_validate = (idx < 2) or (idx == 2 and test == sample.test_ids[0]) + + if should_validate and test.state == 'result_entered': + test.action_validate() + print(f" ✓ Prueba validada") + else: + print(f" ⚠️ No se generaron resultados automáticamente") + + # Resumen final + print("\n" + "="*60) + print("RESUMEN DE DATOS CREADOS") + print("="*60) + + # Contar registros creados + total_samples = env['stock.lot'].search_count([('is_lab_sample', '=', True)]) + total_tests = env['lims.test'].search_count([]) + total_results = env['lims.result'].search_count([]) + + tests_by_state = {} + for state in ['draft', 'in_process', 'result_entered', 'validated', 'cancelled']: + count = env['lims.test'].search_count([('state', '=', state)]) + if count > 0: + tests_by_state[state] = count + + print(f"\n📊 Estadísticas:") + print(f" - Órdenes de laboratorio: {len(orders_created)}") + print(f" - Muestras totales: {total_samples}") + print(f" - Pruebas totales: {total_tests}") + print(f" - Resultados totales: {total_results}") + print(f"\n📈 Pruebas por estado:") + for state, count in tests_by_state.items(): + print(f" - {state}: {count}") + + # Verificar algunos resultados fuera de rango + out_of_range = env['lims.result'].search_count([('is_out_of_range', '=', True)]) + critical = env['lims.result'].search_count([('is_critical', '=', True)]) + + if out_of_range or critical: + print(f"\n⚠️ Valores anormales:") + print(f" - Fuera de rango: {out_of_range}") + print(f" - Críticos: {critical}") + + print("\n✅ Datos de demostración creados exitosamente") + + +def simulate_test_results(env, test): + """Simular el ingreso de resultados realistas para una prueba""" + + for result in test.result_ids: + param = result.parameter_id + + if param.value_type == 'numeric': + # Generar valor numérico considerando el rango normal + if result.applicable_range_id: + range_obj = result.applicable_range_id + + # Probabilidades: 75% normal, 20% anormal, 5% crítico + rand = random.random() + + if rand < 0.75: # Valor normal + # Generar valor dentro del rango normal + value = random.uniform(range_obj.normal_min, range_obj.normal_max) + + elif rand < 0.95: # Valor anormal pero no crítico + # Decidir si va por arriba o por abajo + if random.random() < 0.5 and range_obj.normal_min > 0: + # Por debajo del normal + value = random.uniform(range_obj.normal_min * 0.7, range_obj.normal_min * 0.95) + else: + # Por encima del normal + value = random.uniform(range_obj.normal_max * 1.05, range_obj.normal_max * 1.3) + + else: # Valor crítico (5%) + if range_obj.critical_min and random.random() < 0.5: + # Crítico bajo + value = random.uniform(range_obj.critical_min * 0.5, range_obj.critical_min * 0.9) + elif range_obj.critical_max: + # Crítico alto + value = random.uniform(range_obj.critical_max * 1.1, range_obj.critical_max * 1.5) + else: + # Si no hay valores críticos definidos, usar un valor muy anormal + value = range_obj.normal_max * 2.0 + + # Redondear según el tipo de parámetro + if param.code in ['HGB', 'CREA', 'GLU', 'CHOL', 'HDL', 'LDL', 'TRIG']: + result.value_numeric = round(value, 1) + elif param.code in ['U-PH', 'U-DENS']: + result.value_numeric = round(value, 3) + else: + result.value_numeric = round(value, 2) + + # Agregar notas para valores anormales en algunos casos + if result.is_out_of_range and random.random() < 0.3: + if param.code == 'GLU' and result.value_numeric > 126: + result.notes = "Hiperglucemia - Sugerir control de diabetes" + elif param.code == 'WBC' and result.value_numeric > 11: + result.notes = "Leucocitosis - Posible proceso infeccioso" + elif param.code == 'HGB' and result.value_numeric < range_obj.normal_min: + result.notes = "Anemia - Evaluar causa" + + else: + # Sin rango definido, usar valores típicos + result.value_numeric = round(random.uniform(10, 100), 2) + + elif param.value_type == 'selection': + # Seleccionar una opción con pesos realistas + if param.selection_values: + options = [opt.strip() for opt in param.selection_values.split(',')] + + # Para cultivos, 70% negativo, 30% positivo + if param.code in ['CULT', 'HIV', 'HBsAg', 'HCV', 'VDRL']: + if 'Negativo' in options or 'No Reactivo' in options: + negative_option = 'Negativo' if 'Negativo' in options else 'No Reactivo' + positive_option = 'Positivo' if 'Positivo' in options else 'Reactivo' + result.value_selection = negative_option if random.random() < 0.7 else positive_option + else: + result.value_selection = random.choice(options) + + # Para orina, distribución más realista + elif param.code == 'U-COLOR': + weights = [0.1, 0.6, 0.2, 0.05, 0.02, 0.02, 0.01] # Amarillo más común + result.value_selection = random.choices(options, weights=weights[:len(options)])[0] + + elif param.code == 'U-ASP': + weights = [0.7, 0.2, 0.08, 0.02] # Transparente más común + result.value_selection = random.choices(options, weights=weights[:len(options)])[0] + + else: + # Primera opción más probable (generalmente es la normal) + weights = [0.7] + [0.3/(len(options)-1)]*(len(options)-1) + result.value_selection = random.choices(options, weights=weights)[0] + + elif param.value_type == 'boolean': + # Para pruebas de embarazo, considerar el género del paciente + if param.code == 'HCG' and test.patient_id.gender == 'female' and test.patient_id.is_pregnant: + result.value_boolean = True + else: + # 85% probabilidad de False (negativo) para la mayoría de pruebas + result.value_boolean = random.random() > 0.85 + + elif param.value_type == 'text': + # Generar texto según el parámetro + if param.code == 'MICRO': + # Solo si el cultivo es positivo + culture_result = test.result_ids.filtered( + lambda r: r.parameter_id.code == 'CULT' + ) + if culture_result and culture_result.value_selection == 'Positivo': + organisms = ['E. coli', 'Klebsiella pneumoniae', 'Proteus mirabilis', + 'Enterococcus faecalis', 'Staphylococcus aureus', + 'Pseudomonas aeruginosa', 'Streptococcus agalactiae'] + result.value_text = random.choice(organisms) + else: + result.value_text = "No se aisló microorganismo" + + elif param.code == 'UFC': + # Solo si hay microorganismo + micro_result = test.result_ids.filtered( + lambda r: r.parameter_id.code == 'MICRO' + ) + if micro_result and micro_result.value_text and micro_result.value_text != "No se aisló microorganismo": + counts = ['>100,000', '>50,000', '>10,000', '<10,000'] + weights = [0.5, 0.3, 0.15, 0.05] + result.value_text = random.choices(counts, weights=weights)[0] + " UFC/mL" + + +if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + create_demo_lab_data(cr) + cr.commit() \ No newline at end of file diff --git a/test/create_test_demo_data.py b/test/create_test_demo_data.py deleted file mode 100644 index 4952c24..0000000 --- a/test/create_test_demo_data.py +++ /dev/null @@ -1,225 +0,0 @@ -# -*- coding: utf-8 -*- -import odoo -from datetime import datetime, timedelta - -def create_test_demo_data(cr): - """Crea datos de demostración para lims.test y lims.result""" - env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) - - # Buscar algunos pacientes y análisis existentes - patients = env['res.partner'].search([('is_patient', '=', True)], limit=3) - if not patients: - print("No se encontraron pacientes para crear pruebas de demostración") - return - - # Buscar análisis disponibles - hemograma = env.ref('lims_management.analysis_hemograma', raise_if_not_found=False) - glucosa = env.ref('lims_management.analysis_glucosa', raise_if_not_found=False) - - if not hemograma or not glucosa: - print("No se encontraron análisis de demostración") - return - - # Buscar o crear una orden de laboratorio simple - lab_order = env['sale.order'].search([ - ('is_lab_request', '=', True), - ('state', '=', 'sale') - ], limit=1) - - if not lab_order: - # Crear una orden básica si no existe - lab_order = env['sale.order'].create({ - 'partner_id': patients[0].id, - 'is_lab_request': True, - 'order_line': [(0, 0, { - 'product_id': hemograma.product_variant_id.id, - 'product_uom_qty': 1 - }), (0, 0, { - 'product_id': glucosa.product_variant_id.id, - 'product_uom_qty': 1 - })] - }) - lab_order.action_confirm() - - # Obtener las líneas de orden - order_lines = lab_order.order_line - if not order_lines: - print("No se encontraron líneas de orden") - return - - # Buscar muestras existentes - samples = env['stock.lot'].search([ - ('is_lab_sample', '=', True), - ('patient_id', '=', lab_order.partner_id.id) - ], limit=2) - - if not samples: - print("No se encontraron muestras de laboratorio") - return - - # Crear prueba 1: Hemograma en proceso - test1 = env['lims.test'].create({ - 'sale_order_line_id': order_lines[0].id, - 'sample_id': samples[0].id, - 'state': 'draft' - }) - - # Iniciar proceso - test1.action_start_process() - - # Crear resultados para hemograma - results_data = [ - { - 'test_id': test1.id, - 'parameter_name': 'Glóbulos Rojos', - 'sequence': 10, - 'value_numeric': 4.5, - 'unit': '10^6/µL', - 'normal_min': 4.2, - 'normal_max': 5.4 - }, - { - 'test_id': test1.id, - 'parameter_name': 'Glóbulos Blancos', - 'sequence': 20, - 'value_numeric': 12.5, # Fuera de rango - 'unit': '10^3/µL', - 'normal_min': 4.5, - 'normal_max': 11.0, - 'notes': 'Valor elevado - posible infección' - }, - { - 'test_id': test1.id, - 'parameter_name': 'Hemoglobina', - 'sequence': 30, - 'value_numeric': 14.2, - 'unit': 'g/dL', - 'normal_min': 12.0, - 'normal_max': 16.0 - }, - { - 'test_id': test1.id, - 'parameter_name': 'Plaquetas', - 'sequence': 40, - 'value_numeric': 250, - 'unit': '10^3/µL', - 'normal_min': 150, - 'normal_max': 400 - } - ] - - for result_data in results_data: - env['lims.result'].create(result_data) - - print(f"Creada prueba {test1.name} con 4 resultados") - - # Crear prueba 2: Glucosa con resultado ingresado - if len(order_lines) > 1: - test2 = env['lims.test'].create({ - 'sale_order_line_id': order_lines[1].id, - 'sample_id': samples[0].id, - 'state': 'draft' - }) - - test2.action_start_process() - - # Crear resultado de glucosa - env['lims.result'].create({ - 'test_id': test2.id, - 'parameter_name': 'Glucosa en Ayunas', - 'sequence': 10, - 'value_numeric': 125, # Fuera de rango - 'unit': 'mg/dL', - 'normal_min': 70, - 'normal_max': 110, - 'notes': 'Valor elevado - prediabetes' - }) - - # Marcar resultados como ingresados - test2.action_enter_results() - - print(f"Creada prueba {test2.name} con resultado ingresado") - - # Crear prueba 3: Uroanálisis con valores mixtos (si hay más pacientes) - if len(patients) > 1 and len(samples) > 1: - # Crear una orden adicional - urine_analysis = env['product.template'].search([ - ('is_analysis', '=', True), - ('name', 'ilike', 'orina') - ], limit=1) - - if urine_analysis: - lab_order2 = env['sale.order'].create({ - 'partner_id': patients[1].id, - 'is_lab_request': True, - 'order_line': [(0, 0, { - 'product_id': urine_analysis.product_variant_id.id, - 'product_uom_qty': 1 - })] - }) - lab_order2.action_confirm() - - test3 = env['lims.test'].create({ - 'sale_order_line_id': lab_order2.order_line[0].id, - 'sample_id': samples[1].id, - 'state': 'draft' - }) - - test3.action_start_process() - - # Crear resultados mixtos - urine_results = [ - { - 'test_id': test3.id, - 'parameter_name': 'Color', - 'sequence': 10, - 'value_text': 'Amarillo claro' - }, - { - 'test_id': test3.id, - 'parameter_name': 'pH', - 'sequence': 20, - 'value_numeric': 6.5, - 'normal_min': 4.6, - 'normal_max': 8.0 - }, - { - 'test_id': test3.id, - 'parameter_name': 'Densidad', - 'sequence': 30, - 'value_numeric': 1.020, - 'normal_min': 1.005, - 'normal_max': 1.030 - }, - { - 'test_id': test3.id, - 'parameter_name': 'Proteínas', - 'sequence': 40, - 'value_text': 'Negativo' - }, - { - 'test_id': test3.id, - 'parameter_name': 'Glucosa', - 'sequence': 50, - 'value_text': 'Negativo' - } - ] - - for result_data in urine_results: - env['lims.result'].create(result_data) - - # Ingresar y validar resultados - test3.action_enter_results() - if test3.state == 'result_entered': - test3.action_validate() - - print(f"Creada prueba {test3.name} validada con resultados mixtos") - - print("\nDatos de demostración de pruebas creados exitosamente") - -if __name__ == '__main__': - db_name = 'lims_demo' - registry = odoo.registry(db_name) - with registry.cursor() as cr: - create_test_demo_data(cr) - cr.commit() \ No newline at end of file diff --git a/test/verify_demo_data.py b/test/verify_demo_data.py new file mode 100644 index 0000000..1fc8779 --- /dev/null +++ b/test/verify_demo_data.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Script para verificar los datos de demostración cargados. +""" + +import odoo +import json + +def verify_demo_data(cr): + """Verificar datos de demostración""" + + # Verificar parámetros + cr.execute(""" + SELECT COUNT(*) as total, + COUNT(DISTINCT value_type) as tipos + FROM lims_analysis_parameter + """) + params = cr.fetchone() + + # Verificar rangos + cr.execute(""" + SELECT COUNT(*) as total, + COUNT(DISTINCT parameter_id) as parametros_con_rangos + FROM lims_parameter_range + """) + ranges = cr.fetchone() + + # Verificar configuración de parámetros en análisis + cr.execute(""" + SELECT pt.name as analisis, + COUNT(ptp.id) as parametros_configurados + FROM product_template pt + LEFT JOIN product_template_parameter ptp ON ptp.product_tmpl_id = pt.id + WHERE pt.is_analysis = true + GROUP BY pt.id, pt.name + ORDER BY pt.name + """) + analysis_config = cr.fetchall() + + # Verificar órdenes de laboratorio + cr.execute(""" + SELECT COUNT(*) as total_ordenes, + COUNT(DISTINCT partner_id) as pacientes_distintos, + COUNT(CASE WHEN state = 'sale' THEN 1 END) as confirmadas + FROM sale_order + WHERE is_lab_request = true + """) + orders = cr.fetchone() + + # Verificar muestras + cr.execute(""" + SELECT COUNT(*) as total_muestras, + COUNT(DISTINCT sample_state) as estados_distintos + FROM stock_lot + WHERE is_lab_sample = true + """) + samples = cr.fetchone() + + # Verificar pruebas + cr.execute(""" + SELECT COUNT(*) as total_pruebas, + COUNT(CASE WHEN state = 'validated' THEN 1 END) as validadas, + COUNT(CASE WHEN state = 'result_entered' THEN 1 END) as con_resultados + FROM lims_test + """) + tests = cr.fetchone() + + # Verificar resultados + cr.execute(""" + SELECT COUNT(*) as total_resultados, + COUNT(CASE WHEN is_out_of_range = true THEN 1 END) as fuera_rango, + COUNT(CASE WHEN is_critical = true THEN 1 END) as criticos + FROM lims_result + """) + results = cr.fetchone() + + return { + 'parametros': { + 'total': params[0], + 'tipos_distintos': params[1] + }, + 'rangos': { + 'total': ranges[0], + 'parametros_con_rangos': ranges[1] + }, + 'analisis_configurados': [ + {'analisis': row[0], 'parametros': row[1]} + for row in analysis_config + ], + 'ordenes': { + 'total': orders[0], + 'pacientes_distintos': orders[1], + 'confirmadas': orders[2] + }, + 'muestras': { + 'total': samples[0], + 'estados_distintos': samples[1] + }, + 'pruebas': { + 'total': tests[0], + 'validadas': tests[1], + 'con_resultados': tests[2] + }, + 'resultados': { + 'total': results[0], + 'fuera_rango': results[1], + 'criticos': results[2] + } + } + + +if __name__ == '__main__': + import sys + sys.path.insert(0, '/usr/lib/python3/dist-packages') + + db_name = 'lims_demo' + registry = odoo.registry(db_name) + + with registry.cursor() as cr: + data = verify_demo_data(cr) + + print("\n" + "="*60) + print("VERIFICACIÓN DE DATOS DE DEMOSTRACIÓN") + print("="*60) + + print(f"\n📊 PARÁMETROS DE ANÁLISIS:") + print(f" - Total: {data['parametros']['total']}") + print(f" - Tipos distintos: {data['parametros']['tipos_distintos']}") + + print(f"\n📏 RANGOS DE REFERENCIA:") + print(f" - Total: {data['rangos']['total']}") + print(f" - Parámetros con rangos: {data['rangos']['parametros_con_rangos']}") + + print(f"\n🧪 ANÁLISIS CONFIGURADOS:") + for item in data['analisis_configurados']: + if item['parametros'] > 0: + print(f" - {item['analisis']}: {item['parametros']} parámetros") + + print(f"\n📋 ÓRDENES DE LABORATORIO:") + print(f" - Total: {data['ordenes']['total']}") + print(f" - Pacientes distintos: {data['ordenes']['pacientes_distintos']}") + print(f" - Confirmadas: {data['ordenes']['confirmadas']}") + + print(f"\n🧪 MUESTRAS:") + print(f" - Total: {data['muestras']['total']}") + print(f" - Estados distintos: {data['muestras']['estados_distintos']}") + + print(f"\n🔬 PRUEBAS:") + print(f" - Total: {data['pruebas']['total']}") + print(f" - Validadas: {data['pruebas']['validadas']}") + print(f" - Con resultados: {data['pruebas']['con_resultados']}") + + print(f"\n📊 RESULTADOS:") + print(f" - Total: {data['resultados']['total']}") + print(f" - Fuera de rango: {data['resultados']['fuera_rango']}") + print(f" - Críticos: {data['resultados']['criticos']}") + + print("\n" + "="*60) \ No newline at end of file