diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ee793ed..8142a24 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -28,7 +28,8 @@ "Bash(git cherry-pick:*)", "Bash(del comment_issue_15.txt)", "Bash(cat:*)", - "Bash(powershell.exe:*)" + "Bash(powershell.exe:*)", + "Bash(gh pr create:*)" ], "deny": [] } diff --git a/lims_management/models/lims_result.py b/lims_management/models/lims_result.py index dfaca77..7470794 100644 --- a/lims_management/models/lims_result.py +++ b/lims_management/models/lims_result.py @@ -365,6 +365,101 @@ class LimsResult(models.Model): if len(matches) == 1: self.value_selection = matches[0] + @api.onchange('value_numeric', 'is_critical') + def _onchange_critical_value(self): + """Autocompleta las notas cuando el valor es crítico.""" + if self.is_critical and self.parameter_value_type == 'numeric' and self.value_numeric: + # Diccionario de notas médicas para parámetros críticos + CRITICAL_NOTES = { + 'glucosa': { + 'high': 'Valor elevado de glucosa. Posible prediabetes o diabetes. Se recomienda repetir la prueba en ayunas y consultar con endocrinología.', + 'low': 'Hipoglucemia detectada. Riesgo de síntomas neuroglucogénicos. Evaluar causas: medicamentos, insuficiencia hepática o endocrinopatías.' + }, + 'hemoglobina': { + 'high': 'Policitemia. Evaluar posibles causas: deshidratación, tabaquismo, cardiopatía o policitemia vera.', + 'low': 'Anemia severa. Investigar origen: deficiencia de hierro, pérdida sanguínea, hemólisis o enfermedad crónica.' + }, + 'hematocrito': { + 'high': 'Hemoconcentración. Correlacionar con hemoglobina. Descartar deshidratación o policitemia.', + 'low': 'Valor compatible con anemia. Evaluar junto con hemoglobina e índices eritrocitarios.' + }, + 'leucocitos': { + 'high': 'Leucocitosis marcada. Descartar proceso infeccioso, inflamatorio o hematológico.', + 'low': 'Leucopenia severa. Riesgo de infecciones. Evaluar causas: viral, medicamentosa o hematológica.' + }, + 'plaquetas': { + 'high': 'Trombocitosis. Riesgo trombótico. Descartar causa primaria vs reactiva.', + 'low': 'Trombocitopenia severa. Riesgo de sangrado. Evaluar PTI, hiperesplenismo o supresión medular.' + }, + 'neutrofilos': { + 'high': 'Neutrofilia. Sugiere infección bacteriana o proceso inflamatorio agudo.', + 'low': 'Neutropenia. Alto riesgo de infección bacteriana. Evaluar urgentemente.' + }, + 'linfocitos': { + 'high': 'Linfocitosis. Considerar infección viral o proceso linfoproliferativo.', + 'low': 'Linfopenia. Evaluar inmunodeficiencia o efecto de corticoides.' + }, + 'colesterol total': { + 'high': 'Hipercolesterolemia. Riesgo cardiovascular elevado. Iniciar medidas dietéticas y evaluar tratamiento con estatinas.', + 'low': 'Hipocolesterolemia. Evaluar malnutrición, hipertiroidismo o enfermedad hepática.' + }, + 'trigliceridos': { + 'high': 'Hipertrigliceridemia severa. Riesgo de pancreatitis aguda. Considerar tratamiento farmacológico urgente.', + 'low': 'Valor bajo, generalmente sin significado patológico.' + }, + 'hdl': { + 'high': 'HDL elevado, factor protector cardiovascular.', + 'low': 'HDL bajo. Factor de riesgo cardiovascular. Recomendar ejercicio y cambios en estilo de vida.' + }, + 'ldl': { + 'high': 'LDL elevado. Alto riesgo aterogénico. Evaluar inicio de estatinas según riesgo global.', + 'low': 'LDL bajo, generalmente favorable.' + }, + 'glucosa en sangre': { + 'high': 'Hiperglucemia. Si en ayunas >126 mg/dL sugiere diabetes. Confirmar con segunda muestra.', + 'low': 'Hipoglucemia. Evaluar síntomas y causas. Riesgo neurológico si <50 mg/dL.' + } + } + + # Solo autocompletar si no hay notas previas o están vacías + if not self.notes or self.notes.strip() == '': + note = self._get_critical_note(CRITICAL_NOTES) + if note: + self.notes = note + + def _get_critical_note(self, critical_notes_dict): + """Obtiene la nota apropiada para un resultado crítico.""" + if not self.parameter_id or not self.parameter_name: + return False + + param_lower = self.parameter_name.lower() + + # Buscar el parámetro en el diccionario + for key in critical_notes_dict: + if key in param_lower: + # Obtener rangos del rango aplicable si existe + normal_min = normal_max = None + if self.applicable_range_id: + normal_min = self.applicable_range_id.normal_min + normal_max = self.applicable_range_id.normal_max + + if normal_max and self.value_numeric > normal_max: + return critical_notes_dict[key].get('high', f'Valor crítico alto para {self.parameter_name}. Requiere evaluación médica inmediata.') + elif normal_min and self.value_numeric < normal_min: + return critical_notes_dict[key].get('low', f'Valor crítico bajo para {self.parameter_name}. Requiere evaluación médica inmediata.') + + # Nota genérica si no se encuentra el parámetro + if self.applicable_range_id: + normal_min = self.applicable_range_id.normal_min + normal_max = self.applicable_range_id.normal_max + + if normal_max and self.value_numeric > normal_max: + return f'Valor significativamente elevado. Rango normal: {normal_min}-{normal_max}. Se recomienda evaluación médica.' + elif normal_min and self.value_numeric < normal_min: + return f'Valor significativamente bajo. Rango normal: {normal_min}-{normal_max}. Se recomienda evaluación médica.' + + return 'Valor fuera de rango normal. Requiere interpretación clínica.' + def _validate_and_autocomplete_selection(self, value): """Valida y autocompleta el valor de selección. diff --git a/test/test_critical_notes_autocomplete.py b/test/test_critical_notes_autocomplete.py new file mode 100644 index 0000000..7c4891d --- /dev/null +++ b/test/test_critical_notes_autocomplete.py @@ -0,0 +1,85 @@ +import odoo +import json + +def test_critical_notes_autocomplete(cr): + """Prueba el autocompletado de notas críticas en resultados de laboratorio""" + env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + + print("\n=== PRUEBA DE AUTOCOMPLETADO DE NOTAS CRÍTICAS ===\n") + + # Buscar algunas pruebas con resultados + tests = env['lims.test'].search([('state', 'in', ['result_entered', 'validated'])], limit=5) + + if not tests: + print("No se encontraron pruebas con resultados para probar.") + return + + for test in tests: + print(f"\nPrueba: {test.name} - {test.product_id.name}") + print(f"Paciente: {test.patient_id.name}") + + for result in test.result_ids: + if result.parameter_value_type == 'numeric': + print(f"\n Parámetro: {result.parameter_name}") + print(f" Valor: {result.value_numeric} {result.parameter_unit or ''}") + print(f" ¿Es crítico?: {'SÍ' if result.is_critical else 'NO'}") + + if result.is_critical: + # Limpiar las notas para probar el autocompletado + result.notes = '' + + # Simular cambio en el valor para activar el onchange + with env.cr.savepoint(): + # Trigger the onchange by updating the value + result.with_context(force_onchange=True)._onchange_critical_value() + + print(f" Nota autocompletada: {result.notes}") + + # No guardar los cambios, solo mostrar + env.cr.rollback() + + # Probar con valores específicos + print("\n\n=== PRUEBA CON VALORES ESPECÍFICOS ===\n") + + # Buscar parámetros específicos + test_params = [ + ('Glucosa', 200.0, 'high'), + ('Glucosa', 50.0, 'low'), + ('Hemoglobina', 20.0, 'high'), + ('Hemoglobina', 7.0, 'low'), + ('Plaquetas', 600000, 'high'), + ('Plaquetas', 50000, 'low') + ] + + for param_name, test_value, expected_type in test_params: + # Buscar un resultado con este parámetro + result = env['lims.result'].search([ + ('parameter_name', 'ilike', param_name), + ('parameter_value_type', '=', 'numeric') + ], limit=1) + + if result: + print(f"\nProbando {param_name} con valor {test_value} (esperado: {expected_type})") + + with env.cr.savepoint(): + # Establecer el valor de prueba + result.value_numeric = test_value + result.notes = '' + + # Forzar recálculo de is_critical + result._compute_is_out_of_range() + + # Trigger el onchange + result._onchange_critical_value() + + print(f" ¿Es crítico?: {'SÍ' if result.is_critical else 'NO'}") + print(f" Nota generada: {result.notes[:100]}...") + + # No guardar + env.cr.rollback() + +if __name__ == '__main__': + db_name = 'lims_demo' + registry = odoo.registry(db_name) + with registry.cursor() as cr: + test_critical_notes_autocomplete(cr) \ No newline at end of file