From 55ffe5e73d04e6021dcae6c2a1f1b2ff960e7ad0 Mon Sep 17 00:00:00 2001 From: Marcelo Arocha Date: Thu, 2 Jan 2025 09:35:19 -0300 Subject: [PATCH] allergies and dialysis cache --- .../clinical_notes_repository.py | 142 ++++++++++++++++-- services/clinical_notes_service.py | 89 ++++++++++- services/exams_service.py | 18 ++- services/prescription_service.py | 61 ++------ services/prescription_view_service.py | 37 ++--- 5 files changed, 266 insertions(+), 81 deletions(-) rename services/clinical_notes_queries_service.py => repository/clinical_notes_repository.py (61%) diff --git a/services/clinical_notes_queries_service.py b/repository/clinical_notes_repository.py similarity index 61% rename from services/clinical_notes_queries_service.py rename to repository/clinical_notes_repository.py index 2574c647..c9890e9a 100644 --- a/services/clinical_notes_queries_service.py +++ b/repository/clinical_notes_repository.py @@ -4,9 +4,40 @@ from models.main import db, User from models.notes import ClinicalNotes from services import cache_service, clinical_notes_service +from decorators.timed_decorator import timed +def get_dialysis_cache(admission_number: int): + return ( + db.session.query( + ClinicalNotes.id, ClinicalNotes.annotations, ClinicalNotes.date + ) + .filter(ClinicalNotes.admissionNumber == admission_number) + .filter(ClinicalNotes.dialysisText != "") + .filter(ClinicalNotes.dialysisText != None) + .filter(ClinicalNotes.date > func.current_date() - 3) + .order_by(desc(ClinicalNotes.date)) + .all() + ) + + +def get_allergies_cache(admission_number: int): + return ( + db.session.query( + ClinicalNotes.id, ClinicalNotes.annotations, ClinicalNotes.date + ) + .filter(ClinicalNotes.admissionNumber == admission_number) + .filter(ClinicalNotes.allergyText != "") + .filter(ClinicalNotes.allergyText != None) + .filter(ClinicalNotes.date > func.current_date() - 120) + .order_by(desc(ClinicalNotes.date)) + .all() + ) + + +@timed() def get_signs(admission_number: int, user_context: User, cache=True): + signs = {} if cache: result = cache_service.get_by_key( f"""{user_context.schema}:{admission_number}:sinais""" @@ -14,13 +45,15 @@ def get_signs(admission_number: int, user_context: User, cache=True): if result != None: result_list = result.get("lista", []) - return { + signs = { "id": str(result.get("fkevolucao", None)), "data": " ".join(result_list), "date": result.get("dtevolucao", None), "cache": True, } + return signs + result = ( db.session.query(ClinicalNotes.signsText, ClinicalNotes.date, ClinicalNotes.id) .select_from(ClinicalNotes) @@ -33,17 +66,19 @@ def get_signs(admission_number: int, user_context: User, cache=True): ) if result != None: - return { + signs = { "id": str(result[2]), "data": result[0], "date": result[1].isoformat(), "cache": False, } - return {} + return signs +@timed() def get_infos(admission_number, user_context: User, cache=True): + infos = {} if cache: result = cache_service.get_by_key( f"""{user_context.schema}:{admission_number}:dados""" @@ -51,13 +86,15 @@ def get_infos(admission_number, user_context: User, cache=True): if result != None: result_list = result.get("lista", []) - return { + infos = { "id": str(result.get("fkevolucao", None)), "data": " ".join(result_list), "date": result.get("dtevolucao", None), "cache": True, } + return infos + result = ( db.session.query(ClinicalNotes.infoText, ClinicalNotes.date, ClinicalNotes.id) .select_from(ClinicalNotes) @@ -70,24 +107,58 @@ def get_infos(admission_number, user_context: User, cache=True): ) if result != None: - return { + infos = { "id": str(result[2]), "data": result[0], "date": result[1].isoformat(), "cache": False, } - return {} + return infos + + +@timed() +def get_allergies( + admission_number, user_context: User, admission_date=None, cache=True +): + allergies = [] + + if cache: + results = cache_service.get_range( + key=f"""{user_context.schema}:{admission_number}:alergia""", days_ago=120 + ) + + if results: + texts = [] + for a in sorted( + results, + key=lambda d: d["dtevolucao"], + reverse=True, + ): + text = " ".join(a.get("lista", [])) + if text in texts: + continue + + texts.append(text) + allergies.append( + { + "id": str(a.get("fkevolucao", None)), + "text": text, + "date": a.get("dtevolucao", None), + "cache": True, + "source": "care", + } + ) + return allergies -def get_allergies(admission_number, admission_date=None): cutoff_date = ( datetime.today() - timedelta(days=120) if admission_date == None else admission_date ) - timedelta(days=1) - return ( + results = ( db.session.query( ClinicalNotes.allergyText, func.max(ClinicalNotes.date).label("maxdate"), @@ -104,9 +175,52 @@ def get_allergies(admission_number, admission_date=None): .all() ) + allergies = [] + for a in results: + allergies.append( + { + "date": a[1].isoformat(), + "text": a[0], + "source": "care", + "id": str(a[2]), + "cache": False, + } + ) + return allergies -def get_dialysis(admission_number): - return ( + +@timed() +def get_dialysis(admission_number: int, user_context: User, cache=True): + dialysis_data = [] + + if cache: + results = cache_service.get_range( + key=f"""{user_context.schema}:{admission_number}:dialise""", days_ago=3 + ) + + if results: + texts = [] + for d in sorted( + results, + key=lambda d: d["dtevolucao"] if d["dtevolucao"] != None else "", + reverse=True, + ): + text = " ".join(d.get("lista", [])) + if text in texts: + continue + + dialysis_data.append( + { + "id": str(d.get("fkevolucao", None)), + "text": " ".join(d.get("lista", [])), + "date": d.get("dtevolucao", None), + "cache": True, + } + ) + + return dialysis_data + + results = ( db.session.query( func.first_value(ClinicalNotes.dialysisText).over( partition_by=func.date(ClinicalNotes.date), @@ -131,7 +245,15 @@ def get_dialysis(admission_number): .all() ) + for d in results: + dialysis_data.append( + {"date": d[1].isoformat(), "text": d[0], "id": str(d[3]), "cache": False} + ) + + return dialysis_data + +@timed() def get_admission_stats(admission_number: int, user_context: User, cache=True): tags = clinical_notes_service.get_tags() stats = {} diff --git a/services/clinical_notes_service.py b/services/clinical_notes_service.py index 42a45ba1..e63e9e6b 100644 --- a/services/clinical_notes_service.py +++ b/services/clinical_notes_service.py @@ -1,12 +1,15 @@ +import json +import time from datetime import datetime, timedelta from sqlalchemy import text, Integer, func, desc, or_ from sqlalchemy.orm import undefer -from models.main import db, User +from models.main import db, User, redis_client from models.notes import ClinicalNotes from models.prescription import Prescription, Patient from models.enums import UserAuditTypeEnum from services import memory_service, exams_service, user_service +from repository import clinical_notes_repository from decorators.has_permission_decorator import has_permission, Permission from exception.validation_error import ValidationError from utils import status @@ -94,11 +97,19 @@ def remove_annotation(id_clinical_notes: int, annotation_type: str, user_context clinical_notes.allergy = 0 clinical_notes.allergyText = None db.session.flush() + + refresh_allergies_cache( + admission_number=clinical_notes.admissionNumber, user_context=user_context + ) elif annotation_type == "dialysis": old_note = clinical_notes.dialysisText clinical_notes.dialysis = 0 clinical_notes.dialysisText = None db.session.flush() + + refresh_dialysis_cache( + admission_number=clinical_notes.admissionNumber, user_context=user_context + ) else: raise ValidationError( "Tipo inválido", "errors.businessRules", status.HTTP_400_BAD_REQUEST @@ -377,3 +388,79 @@ def update_note_text(id: int, data: dict, user_context: User): n.form = data.get("form") return n + + +@has_permission(Permission.WRITE_PRESCRIPTION, Permission.MAINTAINER) +def refresh_clinical_notes_stats_cache(admission_number: int, user_context: User): + def _add_cache(key: str, data: dict, expire_in: int): + if data.get("data", None) != None: + cache_data = { + "dtevolucao": data.get("date", None), + "fkevolucao": data.get("id", None), + "lista": [data.get("data", None)], + } + + redis_client.json().set(key, "$", cache_data) + redis_client.expire(key, expire_in) + + # signs (expires in 30 days) + signs = clinical_notes_repository.get_signs( + admission_number=admission_number, user_context=user_context, cache=False + ) + key = f"{user_context.schema}:{admission_number}:sinais" + _add_cache(key=key, data=signs, expire_in=(30 * 24 * 60 * 60)) + + # infos (expires in 30 days) + infos = clinical_notes_repository.get_infos( + admission_number=admission_number, user_context=user_context, cache=False + ) + key = f"{user_context.schema}:{admission_number}:dados" + _add_cache(key=key, data=infos, expire_in=(30 * 24 * 60 * 60)) + + +@has_permission(Permission.WRITE_PRESCRIPTION, Permission.MAINTAINER) +def refresh_dialysis_cache(admission_number: int, user_context: User): + dialysis = clinical_notes_repository.get_dialysis_cache( + admission_number=admission_number + ) + key = f"{user_context.schema}:{admission_number}:dialise" + now = int(time.time()) + ten_days_ago = now - (10 * 24 * 60 * 60) + + redis_client.delete(key) + + for d in dialysis: + data = { + "dtevolucao": d.date.isoformat(), + "fkevolucao": d.id, + "lista": d.annotations.get("dialise", []), + } + data_json = json.dumps(data) + timestamp = int( + time.mktime(time.strptime(data["dtevolucao"], "%Y-%m-%dT%H:%M:%S")) + ) + redis_client.zadd(key, {data_json: timestamp}) + redis_client.zremrangebyscore(key, min=0, max=ten_days_ago) + redis_client.expire(key, (10 * 24 * 60 * 60)) # 10 days + + +@has_permission(Permission.WRITE_PRESCRIPTION, Permission.MAINTAINER) +def refresh_allergies_cache(admission_number: int, user_context: User): + allergies = clinical_notes_repository.get_allergies_cache( + admission_number=admission_number + ) + key = f"{user_context.schema}:{admission_number}:alergia" + redis_client.delete(key) + + for a in allergies: + data = { + "dtevolucao": a.date.isoformat(), + "fkevolucao": a.id, + "lista": a.annotations.get("allergiesComposed", []), + } + data_json = json.dumps(data) + timestamp = int( + time.mktime(time.strptime(data["dtevolucao"], "%Y-%m-%dT%H:%M:%S")) + ) + redis_client.zadd(key, {data_json: timestamp}) + redis_client.expire(key, (120 * 24 * 60 * 60)) # 120 days diff --git a/services/exams_service.py b/services/exams_service.py index fe8f0bb5..a06b8b15 100644 --- a/services/exams_service.py +++ b/services/exams_service.py @@ -1,8 +1,9 @@ import copy +import json from sqlalchemy import text, desc, and_ from datetime import datetime, timedelta, date -from models.main import db +from models.main import db, redis_client, User from models.prescription import Patient from models.segment import Exams, SegmentExam from models.notes import ClinicalNotes @@ -507,3 +508,18 @@ def find_latest_exams( ) return dict(exams, **examsExtra) + + +@has_permission(Permission.WRITE_PRESCRIPTION, Permission.MAINTAINER) +def refresh_exams_cache(id_patient: int, user_context: User): + exams = get_exams_current_results( + id_patient=id_patient, + add_previous_exams=True, + cache=False, + schema=user_context.schema, + lower_key=False, + ) + + key = f"{user_context.schema}:{id_patient}:exames" + for type_exam, exam in exams.items(): + redis_client.hset(key, type_exam, json.dumps(exam)) diff --git a/services/prescription_service.py b/services/prescription_service.py index 22329664..2b2ba75b 100644 --- a/services/prescription_service.py +++ b/services/prescription_service.py @@ -1,8 +1,7 @@ -import json from sqlalchemy import desc, nullsfirst, func, and_, or_, text from datetime import date, datetime -from models.main import db, redis_client, User +from models.main import db, User from models.prescription import ( Prescription, Patient, @@ -22,9 +21,9 @@ memory_service, feature_service, data_authorization_service, - clinical_notes_queries_service, exams_service, patient_service, + clinical_notes_service, ) from decorators.has_permission_decorator import has_permission, Permission from utils import status, prescriptionutils, dateutils @@ -228,7 +227,7 @@ def recalculate_prescription(id_prescription: int, user_context: User): pm.* FROM {user_context.schema}.presmed pm - WHERE + WHERE fkprescricao = ANY(:prescriptionIds) """ ) @@ -280,10 +279,19 @@ def recalculate_prescription(id_prescription: int, user_context: User): # refresh cache if feature_service.has_feature_flag(flag=AppFeatureFlagEnum.REDIS_CACHE): - _refresh_clinical_notes_stats( + clinical_notes_service.refresh_clinical_notes_stats_cache( admission_number=p.admissionNumber, user_context=user_context ) - _refresh_exams(id_patient=p.idPatient, user_context=user_context) + + clinical_notes_service.refresh_dialysis_cache( + admission_number=p.admissionNumber, user_context=user_context + ) + clinical_notes_service.refresh_allergies_cache( + admission_number=p.admissionNumber, user_context=user_context + ) + exams_service.refresh_exams_cache( + id_patient=p.idPatient, user_context=user_context + ) def get_query_prescriptions_by_agg( @@ -373,47 +381,6 @@ def update_prescription_data(id_prescription: int, data: dict, user_context: Use return p -def _refresh_clinical_notes_stats(admission_number: int, user_context: User): - def _add_cache(key: str, data: dict, expire_in: int): - if data.get("data", None) != None: - cache_data = { - "dtevolucao": data.get("date", None), - "fkevolucao": data.get("id", None), - "lista": [data.get("data", None)], - } - - redis_client.json().set(key, "$", cache_data) - redis_client.expire(key, expire_in) - - # signs (expires in 60 days) - signs = clinical_notes_queries_service.get_signs( - admission_number=admission_number, user_context=user_context, cache=False - ) - key = f"{user_context.schema}:{admission_number}:sinais" - _add_cache(key=key, data=signs, expire_in=5184000) - - # infos (expires in 60 days) - infos = clinical_notes_queries_service.get_infos( - admission_number=admission_number, user_context=user_context, cache=False - ) - key = f"{user_context.schema}:{admission_number}:dados" - _add_cache(key=key, data=infos, expire_in=5184000) - - -def _refresh_exams(id_patient: int, user_context: User): - exams = exams_service.get_exams_current_results( - id_patient=id_patient, - add_previous_exams=True, - cache=False, - schema=user_context.schema, - lower_key=False, - ) - - key = f"{user_context.schema}:{id_patient}:exames" - for type_exam, exam in exams.items(): - redis_client.hset(key, type_exam, json.dumps(exam)) - - def _update_patient_weight(admission_number: int, user_context: User): patient = ( db.session.query(Patient) diff --git a/services/prescription_view_service.py b/services/prescription_view_service.py index 3eca3f21..88e2bda8 100644 --- a/services/prescription_view_service.py +++ b/services/prescription_view_service.py @@ -29,12 +29,12 @@ patient_service, intervention_service, clinical_notes_service, - clinical_notes_queries_service, alert_interaction_service, alert_service, feature_service, exams_service, ) +from repository import clinical_notes_repository from utils import prescriptionutils, dateutils, status @@ -378,7 +378,7 @@ def _get_clinical_notes_stats( flag=AppFeatureFlagEnum.REDIS_CACHE ) - cn_stats = clinical_notes_queries_service.get_admission_stats( + cn_stats = clinical_notes_repository.get_admission_stats( admission_number=prescription.admissionNumber, user_context=user_context, cache=is_cache_active, @@ -398,45 +398,38 @@ def _get_clinical_notes_stats( if is_complete: if cn_stats.get("signs", 0) != 0: - signs_data = clinical_notes_queries_service.get_signs( + signs_data = clinical_notes_repository.get_signs( admission_number=prescription.admissionNumber, user_context=user_context, cache=is_cache_active, ) - infos_data = clinical_notes_queries_service.get_infos( + infos_data = clinical_notes_repository.get_infos( admission_number=prescription.admissionNumber, user_context=user_context, cache=is_cache_active, ) - allergies = clinical_notes_queries_service.get_allergies( + allergies = clinical_notes_repository.get_allergies( admission_number=prescription.admissionNumber, admission_date=patient.admissionDate, + user_context=user_context, + cache=is_cache_active, ) db_allergies = patient_service.get_patient_allergies(patient.idPatient) - dialysis = clinical_notes_queries_service.get_dialysis( - admission_number=prescription.admissionNumber - ) + allergies_data += allergies - for a in allergies: + for a in db_allergies: allergies_data.append( - { - "date": a[1].isoformat(), - "text": a[0], - "source": "care", - "id": str(a[2]), - } + {"date": dateutils.to_iso(a[0]), "text": a[1], "source": "pep"} ) - for a in db_allergies: - allergies_data.append({"date": a[0], "text": a[1], "source": "pep"}) - - for a in dialysis: - dialysis_data.append( - {"date": a[1].isoformat(), "text": a[0], "id": str(a[3])} - ) + dialysis_data = clinical_notes_repository.get_dialysis( + admission_number=prescription.admissionNumber, + user_context=user_context, + cache=is_cache_active, + ) return { "cn_stats": cn_stats,