
- Cambiar 'tree' por 'list' en view_mode de todas las acciones de dashboard - Corregir sintaxis de filtros de fecha usando context_today() y relativedelta - Eliminar campo booleano is_out_of_range como medida en gráfico - Corregir referencia a sample.state en lugar de sample.sample_state - Reemplazar sample.test_ids por búsqueda de tests asociados - Eliminar consulta SQL directa a columna logo inexistente - Corregir método invalidate_cache() por _invalidate_cache() - Agregar sección de notificaciones en CLAUDE.md Los dashboards ahora funcionan correctamente sin errores de JavaScript. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
416 lines
18 KiB
Python
416 lines
18 KiB
Python
# -*- 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', # Campo no existe aún
|
|
'note': '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) > 2 and len(analyses) >= 5:
|
|
# Usar María González (índice 2) que es femenina
|
|
patients[2].is_pregnant = True
|
|
|
|
order2 = env['sale.order'].create({
|
|
'partner_id': patients[2].id,
|
|
'doctor_id': doctors[-1].id if doctors else False,
|
|
'is_lab_request': True,
|
|
# 'lab_request_priority': 'high', # Campo no existe aún
|
|
'note': '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) > 1 and len(analyses) >= 3:
|
|
order3 = env['sale.order'].create({
|
|
'partner_id': patients[1].id,
|
|
'doctor_id': doctors[0].id if doctors else False,
|
|
'is_lab_request': True,
|
|
# 'lab_request_priority': 'urgent', # Campo no existe aún
|
|
'note': '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', # Campo no existe aún
|
|
'note': '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.generated_sample_ids:
|
|
order.action_generate_samples()
|
|
print(f" ✓ Muestras generadas: {len(order.generated_sample_ids)}")
|
|
|
|
# Procesar cada muestra
|
|
for sample in order.generated_sample_ids:
|
|
# Marcar como recolectada
|
|
if sample.state == 'pending_collection':
|
|
sample.action_collect()
|
|
print(f" ✓ Muestra {sample.name} recolectada")
|
|
|
|
# Procesar pruebas de esta muestra
|
|
tests = env['lims.test'].search([('sample_id', '=', sample.id)])
|
|
for test in tests:
|
|
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 == tests[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() |