diff --git a/mobile.py b/mobile.py index fda6e94d..573a3a2a 100644 --- a/mobile.py +++ b/mobile.py @@ -111,11 +111,12 @@ if Config.ENV != NoHarmENV.PRODUCTION.value: logging.basicConfig() logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) + logging.getLogger("noharm.backend").setLevel(logging.DEBUG) @app.route("/version", methods=["GET"]) def getVersion(): - return {"status": "success", "data": "v3.12-beta"}, status.HTTP_200_OK + return {"status": "success", "data": "v3.13-beta"}, status.HTTP_200_OK @app.route("/exc", methods=["GET"]) diff --git a/models/enums.py b/models/enums.py index 860decd6..9984e5ed 100644 --- a/models/enums.py +++ b/models/enums.py @@ -71,6 +71,7 @@ class PrescriptionAuditTypeEnum(Enum): UNDO_REVISION = 4 INTEGRATION_CLINICAL_NOTES = 5 INTEGRATION_PRESCRIPTION_RELEASE = 6 + UPSERT_CLINICAL_NOTES = 7 class PrescriptionDrugAuditTypeEnum(Enum): @@ -132,3 +133,23 @@ class NifiQueueActionTypeEnum(Enum): CLEAR_STATE = "CLEAR_STATE" TERMINATE_PROCESS = "TERMINATE_PROCESS" CUSTOM_CALLBACK = "CUSTOM_CALLBACK" + + +class DrugAlertTypeEnum(Enum): + KIDNEY = "kidney" + LIVER = "liver" + PLATELETS = "platelets" + ELDERLY = "elderly" + TUBE = "tube" + ALLERGY = "allergy" + MAX_TIME = "maxTime" + MAX_DOSE = "maxDose" + IRA = "ira" + PREGNANT = "pregnant" + LACTATING = "lactating" + + +class DrugAlertLevelEnum(Enum): + LOW = "low" + MEDIUM = "medium" + HIGH = "high" diff --git a/models/main.py b/models/main.py index 9b3cbbd7..339308af 100644 --- a/models/main.py +++ b/models/main.py @@ -113,6 +113,7 @@ class Relation(db.Model): sctidb = db.Column("sctidb", db.BigInteger, primary_key=True) kind = db.Column("tprelacao", db.String(2), primary_key=True) text = db.Column("texto", db.String, nullable=True) + level = db.Column("nivel", db.String, nullable=True) active = db.Column("ativo", db.Boolean, nullable=True) update = db.Column("update_at", db.DateTime, nullable=True) user = db.Column("update_by", db.BigInteger, nullable=True) @@ -146,6 +147,7 @@ def findBySctid(sctid, user): "type": r[0].kind, "text": r[0].text, "active": r[0].active, + "level": r[0].level, "editable": bool(r[0].creator == user.id) or (not User.permission(user)), } diff --git a/models/prescription.py b/models/prescription.py index 98ff29bf..a42b13a6 100644 --- a/models/prescription.py +++ b/models/prescription.py @@ -398,7 +398,18 @@ def getPatients( if len(indicators) > 0: for i in indicators: - q = q.filter(Prescription.features["alertStats"][i].as_integer() > 0) + interactions = ["it", "dt", "dm", "iy", "sl", "rx"] + if i in interactions: + q = q.filter( + Prescription.features["alertStats"]["interactions"][ + i + ].as_integer() + > 0 + ) + else: + q = q.filter( + Prescription.features["alertStats"][i].as_integer() > 0 + ) if len(drugAttributes) > 0: for a in drugAttributes: diff --git a/routes/drugList.py b/routes/drugList.py index 95abc8db..26a0b029 100644 --- a/routes/drugList.py +++ b/routes/drugList.py @@ -19,11 +19,20 @@ def _get_legacy_alert(kind): class DrugList: def __init__( - self, drugList, interventions, relations, exams, agg, dialysis, is_cpoe=False + self, + drugList, + interventions, + relations, + exams, + agg, + dialysis, + alerts, + is_cpoe=False, ): self.drugList = drugList self.interventions = interventions self.relations = relations + self.alerts = alerts self.exams = exams self.agg = agg self.dialysis = dialysis @@ -45,6 +54,8 @@ def __init__( "exams": 0, # kidney + liver + platelets "allergy": 0, # allergy + rea, "interactions": {}, + "total": 0, + "level": "low", } def sumAlerts(self): @@ -53,6 +64,13 @@ def sumAlerts(self): for k, v in self.relations["stats"].items(): self.alertStats[_get_legacy_alert(k)] = v self.alertStats["interactions"][k] = v + self.alertStats["total"] += v + + # alerts stats + if self.alerts["stats"]: + for k, v in self.alerts["stats"].items(): + self.alertStats[k] = v + self.alertStats["total"] += v # keep legacy data if ( @@ -70,7 +88,23 @@ def sumAlerts(self): + self.alertStats["liver"] + self.alertStats["platelets"] ) - self.alertStats["allergy"] += self.alertStats["rea"] + + levels = [] + if self.relations["alerts"]: + for k, v in self.relations["alerts"].items(): + for alert in v: + levels.append(alert["level"]) + + if self.alerts["alerts"]: + for k, v in self.alerts["alerts"].items(): + for alert in v: + levels.append(alert["level"]) + + if "medium" in levels: + self.alertStats["level"] = "medium" + + if "high" in levels: + self.alertStats["level"] = "high" @staticmethod def sortDrugs(d): @@ -110,306 +144,65 @@ def getDrugType(self, pDrugs, source): if pd[0].source not in source: continue - pdFrequency = ( - 1 if pd[0].frequency in [33, 44, 55, 66, 99] else pd[0].frequency - ) - - if pd[2] != None and pd[6] != None and pd[6].division != None: - measureUnitFactor = 1 - - if pd.measure_unit_convert_factor != None: - measureUnitFactor = pd.measure_unit_convert_factor - - pdDoseconv = ( - none2zero(pd[0].dose) * measureUnitFactor * none2zero(pdFrequency) - ) - else: - pdDoseconv = none2zero(pd[0].doseconv) * none2zero(pdFrequency) - pdUnit = strNone(pd[2].id) if pd[2] else "" pdWhiteList = bool(pd[6].whiteList) if pd[6] is not None else False doseWeightStr = None doseBodySurfaceStr = None - expireDay = pd[10].day if pd[10] else 0 - - idDrugAgg = str(pd[0].idDrug) + "_" + str(expireDay) - if idDrugAgg not in self.maxDoseAgg: - self.maxDoseAgg[idDrugAgg] = {"value": 0, "count": 0} tubeAlert = False alerts = [] - - if self.relations["alerts"] and pd[0].id in self.relations["alerts"]: - for a in self.relations["alerts"][pd[0].id]: - # self.alertStats[a[:3].lower()] += 1 - alerts.append(a) - - if not bool(pd[0].suspendedDate): - self.maxDoseAgg[idDrugAgg]["value"] += pdDoseconv - self.maxDoseAgg[idDrugAgg]["count"] += 1 - - if self.exams and pd[6]: - if pd[6].kidney: - if self.dialysis == "c": - alerts.append( - "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está diálise contínua." - ) - self.alertStats["kidney"] += 1 - elif self.dialysis == "x": - alerts.append( - "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está diálise estendida, também conhecida como SLED." - ) - self.alertStats["kidney"] += 1 - elif self.dialysis == "v": - alerts.append( - "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está em diálise intermitente." - ) - self.alertStats["kidney"] += 1 - elif self.dialysis == "p": - alerts.append( - "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está em diálise peritoneal." - ) - self.alertStats["kidney"] += 1 - elif ( - "ckd" in self.exams - and self.exams["ckd"]["value"] - and pd[6].kidney > self.exams["ckd"]["value"] - and self.exams["age"] > 17 - ): - alerts.append( - "Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função renal do paciente (" - + str(self.exams["ckd"]["value"]) - + " mL/min) está abaixo de " - + str(pd[6].kidney) - + " mL/min." - ) - self.alertStats["kidney"] += 1 - elif ( - "swrtz2" in self.exams - and self.exams["swrtz2"]["value"] - and pd[6].kidney > self.exams["swrtz2"]["value"] - and self.exams["age"] <= 17 - ): - alerts.append( - "Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função renal do paciente (" - + str(self.exams["swrtz2"]["value"]) - + " mL/min/1.73m²) está abaixo de " - + str(pd[6].kidney) - + " mL/min. (Schwartz 2)" - ) - self.alertStats["kidney"] += 1 - elif ( - "swrtz1" in self.exams - and self.exams["swrtz1"]["value"] - and pd[6].kidney > self.exams["swrtz1"]["value"] - and self.exams["age"] <= 17 - ): - alerts.append( - "Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função renal do paciente (" - + str(self.exams["swrtz1"]["value"]) - + " mL/min/1.73m²) está abaixo de " - + str(pd[6].kidney) - + " mL/min. (Schwartz 1)" - ) - self.alertStats["kidney"] += 1 - - if pd[6].liver: - if ( - "tgp" in self.exams - and self.exams["tgp"]["value"] - and float(self.exams["tgp"]["value"]) > pd[6].liver - ) or ( - "tgo" in self.exams - and self.exams["tgo"]["value"] - and float(self.exams["tgo"]["value"]) > pd[6].liver - ): - alerts.append( - "Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função hepática do paciente está reduzida (acima de " - + str(pd[6].liver) - + " U/L)." - ) - self.alertStats["liver"] += 1 + alerts_complete = [] + + if self.relations["alerts"] and str(pd[0].id) in self.relations["alerts"]: + for a in self.relations["alerts"][str(pd[0].id)]: + alerts.append(a["text"]) + alerts_complete.append(a) + + if self.alerts["alerts"] and str(pd[0].id) in self.alerts["alerts"]: + for a in self.alerts["alerts"][str(pd[0].id)]: + alerts.append(a["text"]) + alerts_complete.append(a) + + if self.exams and pd[6]: + if pd[6].chemo and pd[0].dose: + bs_weight = none2zero(self.exams["weight"]) + bs_height = none2zero(self.exams["height"]) + + if bs_weight != 0 and bs_height != 0: + body_surface = math.sqrt((bs_weight * bs_height) / 3600) + doseBodySurfaceStr = f"""{strFormatBR(round(pd[0].dose / body_surface, 2))} {pdUnit}/m²""" + + if pd[6].useWeight and pd[0].dose: + weight = none2zero(self.exams["weight"]) + weight = weight if weight > 0 else 1 + + doseWeightStr = ( + strFormatBR(round(pd[0].dose / float(weight), 2)) + + " " + + pdUnit + + "/Kg" + ) if ( - pd[6].platelets - and "plqt" in self.exams - and self.exams["plqt"]["value"] - and pd[6].platelets > self.exams["plqt"]["value"] + pd[6].idMeasureUnit != None + and pd[6].idMeasureUnit != pdUnit + and pd[0].doseconv != None ): - alerts.append( - "Medicamento contraindicado para paciente com plaquetas (" - + str(self.exams["plqt"]["value"]) - + " plaquetas/µL) abaixo de " - + str(pd[6].platelets) - + " plaquetas/µL." - ) - self.alertStats["platelets"] += 1 - - if pd[6].elderly and self.exams["age"] > 60: - alerts.append( - "Medicamento potencialmente inapropriado para idosos, independente das comorbidades do paciente." - ) - self.alertStats["elderly"] += 1 - - if pd[6].chemo and pd[0].dose: - bs_weight = none2zero(self.exams["weight"]) - bs_height = none2zero(self.exams["height"]) - - if bs_weight != 0 and bs_height != 0: - body_surface = math.sqrt((bs_weight * bs_height) / 3600) - doseBodySurfaceStr = f"""{strFormatBR(round(pd[0].dose / body_surface, 2))} {pdUnit}/m²""" - - if pd[6].useWeight and pd[0].dose: - weight = none2zero(self.exams["weight"]) - weight = weight if weight > 0 else 1 - - doseWeight = round(pdDoseconv / float(weight), 2) - doseWeightStr = ( - strFormatBR(round(pd[0].dose / float(weight), 2)) + doseWeightStr += ( + " ou " + + strFormatBR(pd[0].doseconv) + " " - + pdUnit - + "/Kg" + + str(pd[6].idMeasureUnit) + + "/Kg (faixa arredondada)" ) - if ( - pd[6].idMeasureUnit != None - and pd[6].idMeasureUnit != pdUnit - and pd[0].doseconv != None - ): - doseWeightStr += ( - " ou " - + strFormatBR(pd[0].doseconv) - + " " - + str(pd[6].idMeasureUnit) - + "/Kg (faixa arredondada)" - ) - - keyDrugKg = str(idDrugAgg) + "kg" - if keyDrugKg not in self.maxDoseAgg: - self.maxDoseAgg[keyDrugKg] = {"value": 0, "count": 0} - - self.maxDoseAgg[keyDrugKg]["value"] += doseWeight - self.maxDoseAgg[keyDrugKg]["count"] += 1 - - if pd[6].maxDose and pd[6].maxDose < doseWeight: - alerts.append( - "Dose diária prescrita (" - + strFormatBR(doseWeight) - + " " - + str(pd[6].idMeasureUnit) - + "/Kg) maior que a dose de alerta (" - + strFormatBR(pd[6].maxDose) - + " " - + str(pd[6].idMeasureUnit) - + "/Kg) usualmente recomendada (considerada a dose diária independente da indicação)." - ) - self.alertStats["maxDose"] += 1 - - if ( - pd[6].maxDose - and self.maxDoseAgg[keyDrugKg]["count"] > 1 - and pd[6].maxDose - < none2zero(self.maxDoseAgg[keyDrugKg]["value"]) - ): - alerts.append( - "Dose diária prescrita SOMADA (" - + str(self.maxDoseAgg[keyDrugKg]["value"]) - + " " - + str(pd[6].idMeasureUnit) - + "/Kg) maior que a dose de alerta (" - + str(pd[6].maxDose) - + " " - + str(pd[6].idMeasureUnit) - + "/Kg) usualmente recomendada (considerada a dose diária independente da indicação)." - ) - self.alertStats["maxDose"] += 1 - - else: - if pd[6].maxDose and pd[6].maxDose < pdDoseconv: - alerts.append( - "Dose diária prescrita (" - + str(pdDoseconv) - + " " - + str(pd[6].idMeasureUnit) - + ") maior que a dose de alerta (" - + str(pd[6].maxDose) - + " " - + str(pd[6].idMeasureUnit) - + ") usualmente recomendada (considerada a dose diária independente da indicação)." - ) - self.alertStats["maxDose"] += 1 - - if ( - pd[6].maxDose - and self.maxDoseAgg[idDrugAgg]["count"] > 1 - and pd[6].maxDose - < none2zero(self.maxDoseAgg[idDrugAgg]["value"]) - ): - alerts.append( - "Dose diária prescrita SOMADA (" - + str(self.maxDoseAgg[idDrugAgg]["value"]) - + " " - + str(pd[6].idMeasureUnit) - + ") maior que a dose de alerta (" - + str(pd[6].maxDose) - + " " - + str(pd[6].idMeasureUnit) - + ") usualmente recomendada (considerada a dose diária independente da indicação)." - ) - self.alertStats["maxDose"] += 1 - - if pd[6] and pd[6].tube and pd[0].tube: - alerts.append( - "Medicamento contraindicado via sonda (" - + strNone(pd[0].route) - + ")" - ) - self.alertStats["tube"] += 1 - tubeAlert = True - - if pd[0].allergy == "S": - alerts.append("Paciente alérgico a este medicamento.") - self.alertStats["allergy"] += 1 - if ( - pd[6] - and pd[6].maxTime - and pd[0].period - and pd[0].period > pd[6].maxTime + not bool(pd[0].suspendedDate) + and pd[6] + and pd[6].tube + and pd[0].tube ): - alerts.append( - "Tempo de tratamento atual (" - + str(pd[0].period) - + " dias) maior que o tempo máximo de tratamento (" - + str(pd[6].maxTime) - + " dias) usualmente recomendado." - ) - self.alertStats["maxTime"] += 1 - - if pd[1] and "vanco" in pd[1].name.lower(): - maxdose = self.maxDoseAgg[idDrugAgg]["value"] - ckd = ( - self.exams["ckd"]["value"] - if "ckd" in self.exams and self.exams["ckd"]["value"] - else None - ) - weight = self.exams["weight"] - - if ( - maxdose != None - and ckd != None - and weight != None - and ckd > 0 - and weight > 0 - ): - ira = maxdose / ckd / weight - maxira = 0.6219 - - if ira > maxira and self.dialysis is None: - self.alertStats["exams"] += 1 - alerts.append( - 'Risco de desenvolvimento de Insuficiência Renal Aguda (IRA), já que o resultado do cálculo [dose diária de VANCOMICINA/TFG/peso] é superior a 0,6219. Caso o paciente esteja em diálise, desconsiderar. Referência: CaEPS' - ) + tubeAlert = True total_period = 0 if self.is_cpoe: @@ -518,9 +311,9 @@ def getDrugType(self, pDrugs, source): "existIntervention": self.getExistIntervention( pd[0].idDrug, pd[0].idPrescription ), - # remove intervention attribute after transition (new attribute = interventionList) - "intervention": self.getIntervention(pd[0].id), - "alerts": alerts, + # remove alerts attribute after migration to alertsComplete + # "alerts": alerts, + "alertsComplete": alerts_complete, "tubeAlert": tubeAlert, "notes": pd[7], "prevNotes": prevNotes, @@ -532,11 +325,6 @@ def getDrugType(self, pDrugs, source): "infusionKey": self.getInfusionKey(pd), "formValues": pd[0].form, "drugAttributes": self.getDrugAttributes(pd), - "relations": ( - self.relations["list"][pd[0].id] - if pd[0].id in self.relations["list"] - else [] - ), } ) diff --git a/routes/prescription.py b/routes/prescription.py index 2302c9af..c606cdc2 100644 --- a/routes/prescription.py +++ b/routes/prescription.py @@ -1,5 +1,6 @@ import os import random +import logging from utils import status from models.main import * from models.appendix import * @@ -20,12 +21,13 @@ from datetime import date, datetime from .drugList import DrugList from services import ( + alert_service, + alert_interaction_service, memory_service, prescription_service, prescription_drug_service, intervention_service, patient_service, - alert_service, data_authorization_service, ) from converter import prescription_converter @@ -189,7 +191,7 @@ def getPrescriptions(): id_segment=p[0].idSegment, pdate=p[0].date, ), - } + }, ) ) @@ -306,6 +308,14 @@ def getExistIntervention(interventions, dtPrescription): return result +def _log_perf(start_date, section): + end_date = datetime.now() + logging.basicConfig() + logger = logging.getLogger("noharm.backend") + + logger.debug(f"PERF {section}: {(end_date-start_date).total_seconds()}") + + def getPrescription( idPrescription=None, admissionNumber=None, @@ -315,6 +325,8 @@ def getPrescription( is_pmc=False, is_complete=False, ): + start_date = datetime.now() + if idPrescription: prescription = Prescription.getPrescription(idPrescription) else: @@ -356,33 +368,36 @@ def getPrescription( ref_date=aggDate if aggDate != None else prescription[0].date, ) + _log_perf(start_date, "GET PRESCRIPTION") + + start_date = datetime.now() drugs = PrescriptionDrug.findByPrescription( prescription[0].id, patient.admissionNumber, aggDate, idSegment, is_cpoe, is_pmc ) interventions = intervention_service.get_interventions( admissionNumber=patient.admissionNumber ) - - relations = alert_service.find_relations( - drug_list=drugs, is_cpoe=is_cpoe, id_patient=patient.idPatient - ) headers = ( Prescription.getHeaders(admissionNumber, aggDate, idSegment, is_pmc, is_cpoe) if aggDate else [] ) + _log_perf(start_date, "GET DRUGS AND INTERVENTIONS") + formTemplate = memory_service.get_memory(MemoryEnum.PRESMED_FORM.value) admission_reports = memory_service.get_memory(MemoryEnum.ADMISSION_REPORTS.value) admission_reports_internal = memory_service.get_memory( MemoryEnum.ADMISSION_REPORTS_INTERNAL.value ) + start_date = datetime.now() clinicalNotesCount = ClinicalNotes.getCountIfExists( prescription[0].admissionNumber, is_pmc ) notesTotal = ClinicalNotes.getTotalIfExists( prescription[0].admissionNumber, admission_date=patient.admissionDate ) + notesSigns = None notesInfo = None notesAllergies = [] @@ -406,6 +421,9 @@ def getPrescription( for a in dialysis: notesDialysis.append({"date": a[1].isoformat(), "text": a[0], "id": a[3]}) + _log_perf(start_date, "GET CLINICAL NOTES") + + start_date = datetime.now() registeredAllergies = patient_service.get_patient_allergies(patient.idPatient) for a in registeredAllergies: notesAllergies.append({"date": a[0], "text": a[1], "source": "pep"}) @@ -426,6 +444,19 @@ def getPrescription( exams = dict( exams, **{"age": age, "weight": patientWeight, "height": patientHeight} ) + _log_perf(start_date, "ALLERGIES AND EXAMS") + + start_date = datetime.now() + relations = alert_interaction_service.find_relations( + drug_list=drugs, is_cpoe=is_cpoe, id_patient=patient.idPatient + ) + alerts = alert_service.find_alerts( + drug_list=drugs, + exams=exams, + dialisys=patient.dialysis, + pregnant=patient.pregnant, + lactating=patient.lactating, + ) drugList = DrugList( drugs, @@ -434,9 +465,12 @@ def getPrescription( exams, aggDate is not None, patient.dialysis, + alerts, is_cpoe, ) + _log_perf(start_date, "ALERTS") + start_date = datetime.now() disable_solution_tab = memory_service.has_feature( FeatureEnum.DISABLE_SOLUTION_TAB.value ) @@ -511,6 +545,8 @@ def getPrescription( if int(i["id"]) == 0 and int(i["idPrescription"]) == prescription[0].id ] + _log_perf(start_date, "ADDITIONAL QUERIES") + return { "status": "success", "data": { @@ -651,6 +687,22 @@ def setPrescriptionData(idPrescription): p.notes = data.get("notes", None) p.notes_at = datetime.today() + audit = PrescriptionAudit() + audit.auditType = PrescriptionAuditTypeEnum.UPSERT_CLINICAL_NOTES.value + audit.admissionNumber = p.admissionNumber + audit.idPrescription = p.id + audit.prescriptionDate = p.date + audit.idDepartment = p.idDepartment + audit.idSegment = p.idSegment + audit.totalItens = 0 + audit.agg = p.agg + audit.concilia = p.concilia + audit.bed = p.bed + audit.extra = {"text": data.get("notes", None)} + audit.createdAt = datetime.today() + audit.createdBy = user.id + db.session.add(audit) + if "concilia" in data.keys(): concilia = data.get("concilia", "s") p.concilia = str(concilia)[:1] @@ -675,6 +727,7 @@ def setPrescriptionStatus(): else None ) evaluation_time = data.get("evaluationTime", None) + alerts = data.get("alerts", []) try: result = prescription_service.check_prescription( @@ -682,6 +735,7 @@ def setPrescriptionStatus(): p_status=p_status, user=user, evaluation_time=evaluation_time, + alerts=alerts, ) except ValidationError as e: return { diff --git a/routes/substance.py b/routes/substance.py index f748154c..286ebef3 100644 --- a/routes/substance.py +++ b/routes/substance.py @@ -178,6 +178,9 @@ def setRelation(sctidA, sctidB, kind): if "active" in data.keys(): relation.active = bool(data.get("active", False)) + if "level" in data.keys(): + relation.level = data.get("level", None) + relation.update = datetime.today() relation.user = user.id diff --git a/routes/utils.py b/routes/utils.py index 0dbcb847..3d3645bc 100644 --- a/routes/utils.py +++ b/routes/utils.py @@ -465,13 +465,15 @@ def getFeatures(result): drugList.extend(result["data"]["solution"]) drugList.extend(result["data"]["procedures"]) - allergy = alerts = pScore = score1 = score2 = score3 = 0 + allergy = alerts = alerts_prescription = pScore = score1 = score2 = score3 = 0 am = av = control = np = tube = diff = 0 drugIDs = [] substanceIDs = [] substanceClassIDs = [] frequencies = [] drug_attributes = {} + alert_levels = [] + alert_level = "low" for attr in get_bool_drug_attributes_list(): drug_attributes[attr] = 0 @@ -492,7 +494,7 @@ def getFeatures(result): continue allergy += int(d["allergy"]) - alerts += len(d["alerts"]) + alerts_prescription += len(d["alertsComplete"]) pScore += int(d["score"]) score1 += int(d["score"] == "1") score2 += int(d["score"] == "2") @@ -507,6 +509,9 @@ def getFeatures(result): if d["frequency"]["value"] != "": frequencies.append(d["frequency"]["value"]) + for a in d["alertsComplete"]: + alert_levels.append(a["level"]) + interventions = 0 for i in result["data"]["interventions"]: interventions += int(i["status"] == "s") @@ -514,10 +519,25 @@ def getFeatures(result): exams = result["data"]["alertExams"] complicationCount = result["data"]["complication"] + if "alertStats" in result["data"]: + # entire prescription (agg features) + alerts = result["data"]["alertStats"]["total"] + alert_level = result["data"]["alertStats"].get("level", "low") + else: + # headers + alerts = alerts_prescription + + if "medium" in alert_levels: + alert_level = "medium" + + if "high" in alert_levels: + alert_level = "high" + return { "alergy": allergy, "allergy": allergy, "alerts": alerts, + "alertLevel": alert_level, "prescriptionScore": pScore, "scoreOne": score1, "scoreTwo": score2, diff --git a/services/alert_interaction_service.py b/services/alert_interaction_service.py new file mode 100644 index 00000000..31039534 --- /dev/null +++ b/services/alert_interaction_service.py @@ -0,0 +1,302 @@ +from datetime import datetime +from sqlalchemy import text + +from models.prescription import PrescriptionDrug, Allergy +from models.main import db, Drug, Substance +from models.enums import DrugTypeEnum, DrugAlertLevelEnum +from routes.utils import typeRelations, strNone + + +# analyze interactions between drugs. +# drug_list (PrescriptionDrug.findByPrescription) +def find_relations(drug_list, id_patient: int, is_cpoe: bool): + filtered_list = _filter_drug_list(drug_list=drug_list) + allergies = _get_allergies(id_patient=id_patient) + overlap_drugs = [] + + for item in filtered_list: + prescription_drug: PrescriptionDrug = item[0] + drug: Drug = item[1] + prescription_date = item[13] + prescription_expire_date = item[10] if item[10] != None else datetime.today() + + for compare_item in filtered_list: + cp_prescription_drug: PrescriptionDrug = compare_item[0] + cp_drug: Drug = compare_item[1] + cp_prescription_date = compare_item[13] + cp_prescription_expire_date = ( + compare_item[10] if compare_item[10] != None else datetime.today() + ) + + if prescription_drug.id == cp_prescription_drug.id: + continue + + if is_cpoe: + # period overlap + if not ( + (prescription_date.date() <= cp_prescription_expire_date.date()) + and (cp_prescription_date.date() <= prescription_expire_date.date()) + ): + continue + else: + # same expire date + if not ( + prescription_expire_date.date() + == cp_prescription_expire_date.date() + ): + continue + + overlap_drugs.append( + { + "from": { + "id": str(prescription_drug.id), + "drug": drug.name, + "sctid": drug.sctid, + "intravenous": ( + prescription_drug.intravenous + if prescription_drug.intravenous != None + else False + ), + "group": _get_solution_group_key( + pd=prescription_drug, is_cpoe=is_cpoe + ), + "expireDate": prescription_expire_date.isoformat(), + "rx": False, + }, + "to": { + "id": str(cp_prescription_drug.id), + "drug": cp_drug.name, + "sctid": cp_drug.sctid, + "intravenous": ( + cp_prescription_drug.intravenous + if cp_prescription_drug.intravenous != None + else False + ), + "group": _get_solution_group_key( + pd=cp_prescription_drug, is_cpoe=is_cpoe + ), + "expireDate": cp_prescription_expire_date.isoformat(), + "rx": False, + }, + } + ) + + for a in allergies: + overlap_drugs.append( + { + "from": { + "id": str(prescription_drug.id), + "drug": drug.name, + "sctid": drug.sctid, + "intravenous": ( + prescription_drug.intravenous + if prescription_drug.intravenous != None + else False + ), + "group": _get_solution_group_key( + pd=prescription_drug, is_cpoe=is_cpoe + ), + "expireDate": prescription_expire_date.isoformat(), + "rx": True, + }, + "to": a, + } + ) + + if len(overlap_drugs) == 0: + return {"alerts": {}, "list": {}, "stats": {}} + + uniq_overlap_keys = [] + for d in overlap_drugs: + key = f"""({d["from"]["sctid"]},{d["to"]["sctid"]})""" + if key not in uniq_overlap_keys: + uniq_overlap_keys.append(key) + + query = text( + f""" + with cruzamento as ( + select * from (values {",".join(uniq_overlap_keys)}) AS t (sctida, sctidb) + ) + select + r.sctida, + r.sctidb, + r.tprelacao as "kind", + r.texto as "text", + r.nivel as "level" + from + public.relacao r + inner join cruzamento c on (r.sctida = c.sctida and r.sctidb = c.sctidb) + where + r.ativo = true + """ + ) + + active_relations = {} + + for item in db.session.execute(query).all(): + key = f"{item.sctida}-{item.sctidb}-{item.kind}" + active_relations[key] = { + "sctida": item.sctida, + "sctidb": item.sctidb, + "kind": item.kind, + "text": item.text, + "level": item.level, + } + + alerts = {} + stats = {} + unique_relations = {} + kinds = ["it", "dt", "dm", "iy", "sl", "rx"] + + for kind in kinds: + stats[kind] = 0 + + for drug in overlap_drugs: + drug_from = drug["from"] + drug_to = drug["to"] + + for kind in kinds: + key = f"""{drug_from["sctid"]}-{drug_to["sctid"]}-{kind}""" + invert_key = f"""{drug_to["sctid"]}-{drug_from["sctid"]}-{kind}""" + + # iy must have intravenous route + if kind == "iy" and ( + not drug_from["intravenous"] or not drug_to["intravenous"] + ): + continue + + # sl must be in the same group + if kind == "sl" and ( + drug_from["group"] != drug_to["group"] or drug_from["group"] == None + ): + continue + + # rx rules + if kind == "rx": + if not drug_from["rx"]: + continue + else: + if drug_from["rx"]: + continue + + if key in active_relations: + if is_cpoe: + uniq_key = key + uniq_invert_key = invert_key + else: + uniq_key = f"""{key}-{drug_from["expireDate"]}""" + uniq_invert_key = f"""{invert_key}-{drug_from["expireDate"]}""" + + if ( + not uniq_key in unique_relations + and not uniq_invert_key in unique_relations + ): + stats[kind] += 1 + unique_relations[uniq_key] = 1 + unique_relations[uniq_invert_key] = 1 + + alert_text = typeRelations[kind] + ": " + alert_text += ( + strNone(active_relations[key]["text"]) + + " (" + + strNone(drug_from["drug"]) + + " e " + + strNone(drug_to["drug"]) + + ")" + ) + + if kind == "dm": + # one way + ids = [drug_from["id"]] + else: + # both ways + ids = [drug_from["id"], drug_to["id"]] + + for id in ids: + alert_obj = { + "idPrescriptionDrug": id, + "key": key, + "type": kind, + "level": ( + active_relations[key]["level"] + if active_relations[key]["level"] != None + else DrugAlertLevelEnum.LOW + ), + "relation": drug_to["id"], + "text": alert_text, + } + + if id in alerts: + # avoid alert repetition + text_array = [a["text"] for a in alerts[id]] + if alert_text not in text_array: + alerts[id].append(alert_obj) + else: + alerts[id] = [alert_obj] + + return {"alerts": alerts, "stats": stats} + + +def _filter_drug_list(drug_list): + filtered_list = [] + valid_sources = [ + DrugTypeEnum.DRUG.value, + DrugTypeEnum.SOLUTION.value, + DrugTypeEnum.PROCEDURE.value, + ] + + for item in drug_list: + prescription_drug: PrescriptionDrug = item[0] + drug: Drug = item[1] + if prescription_drug.source not in valid_sources: + continue + + if prescription_drug.suspendedDate != None: + continue + + if drug == None or drug.sctid == None: + continue + + filtered_list.append(item) + + return filtered_list + + +def _get_solution_group_key(pd: PrescriptionDrug, is_cpoe: bool): + if is_cpoe: + if pd.cpoe_group: + return f"{pd.idPrescription}-{pd.cpoe_group}" + else: + if pd.solutionGroup: + return f"{pd.idPrescription}-{pd.solutionGroup}" + + return None + + +def _get_allergies(id_patient: int): + allergies = ( + db.session.query(Substance.id, Substance.name) + .select_from(Allergy) + .join(Drug, Allergy.idDrug == Drug.id) + .join(Substance, Substance.id == Drug.sctid) + .filter(Allergy.idPatient == id_patient) + .filter(Allergy.active == True) + .group_by(Substance.id, Substance.name) + .all() + ) + + results = [] + for a in allergies: + if a.id != None: + results.append( + { + "id": None, + "drug": a.name, + "sctid": a.id, + "intravenous": False, + "group": None, + "rx": True, + } + ) + + return results diff --git a/services/alert_service.py b/services/alert_service.py index f993e945..93dab53f 100644 --- a/services/alert_service.py +++ b/services/alert_service.py @@ -1,225 +1,721 @@ -from datetime import datetime -from sqlalchemy import text +import re -from models.prescription import PrescriptionDrug, Allergy -from models.main import db, Drug, Relation, Substance -from models.enums import DrugTypeEnum -from routes.utils import typeRelations, strNone +from routes.utils import none2zero, strNone, strFormatBR +from models.enums import DrugTypeEnum, DrugAlertTypeEnum, DrugAlertLevelEnum +from models.prescription import ( + PrescriptionDrug, + Drug, + DrugAttributes, +) -def find_relations(drug_list, id_patient: int, is_cpoe: bool): +# analyze alerts +# drug_list (PrescriptionDrug.findByPrescription) +def find_alerts(drug_list, exams: dict, dialisys: str, pregnant: bool, lactating: bool): filtered_list = _filter_drug_list(drug_list=drug_list) - allergies = _get_allergies(id_patient=id_patient) - overlap_drugs = [] + dose_total = _get_dose_total(drug_list=filtered_list, exams=exams) + alerts = {} + stats = _get_empty_stats() + + def add_alert(a): + if a != None: + key = a["idPrescriptionDrug"] + stats[a["type"]] += 1 + a["text"] = re.sub(" {2,}", "", a["text"]) + a["text"] = re.sub("\n", "", a["text"]) + + if key not in alerts: + alerts[key] = [a] + else: + alerts[key].append(a) for item in filtered_list: prescription_drug: PrescriptionDrug = item[0] drug: Drug = item[1] - prescription_date = item[13] - prescription_expire_date = item[10] if item[10] != None else datetime.today() - - for compare_item in filtered_list: - cp_prescription_drug: PrescriptionDrug = compare_item[0] - cp_drug: Drug = compare_item[1] - cp_prescription_date = compare_item[13] - cp_prescription_expire_date = ( - compare_item[10] if compare_item[10] != None else datetime.today() + measure_unit_convert_factor = ( + item.measure_unit_convert_factor + if item.measure_unit_convert_factor != None + else 1 + ) + drug_attributes: DrugAttributes = item[6] + prescription_expire_date = item[10] + + # kidney alert + add_alert( + _alert_kidney( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + exams=exams, + dialysis=dialisys, ) + ) - if prescription_drug.id == cp_prescription_drug.id: - continue + # liver alert + add_alert( + _alert_liver( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + exams=exams, + ) + ) - if is_cpoe: - # period overlap - if not ( - (prescription_date.date() <= cp_prescription_expire_date.date()) - and (cp_prescription_date.date() <= prescription_expire_date.date()) - ): - continue - else: - # same expire date - if not (prescription_expire_date == cp_prescription_expire_date): - continue - - overlap_drugs.append( - { - "from": { - "id": prescription_drug.id, - "drug": drug.name, - "sctid": drug.sctid, - "intravenous": ( - prescription_drug.intravenous - if prescription_drug.intravenous != None - else False - ), - "group": _get_solution_group_key( - pd=prescription_drug, is_cpoe=is_cpoe - ), - "expireDate": prescription_expire_date.isoformat(), - "rx": False, - }, - "to": { - "id": cp_prescription_drug.id, - "drug": cp_drug.name, - "sctid": cp_drug.sctid, - "intravenous": ( - cp_prescription_drug.intravenous - if cp_prescription_drug.intravenous != None - else False - ), - "group": _get_solution_group_key( - pd=cp_prescription_drug, is_cpoe=is_cpoe - ), - "expireDate": cp_prescription_expire_date.isoformat(), - "rx": False, - }, - } + # platelets + add_alert( + _alert_platelets( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + exams=exams, ) + ) - for a in allergies: - overlap_drugs.append( - { - "from": { - "id": prescription_drug.id, - "drug": drug.name, - "sctid": drug.sctid, - "intravenous": ( - prescription_drug.intravenous - if prescription_drug.intravenous != None - else False - ), - "group": _get_solution_group_key( - pd=prescription_drug, is_cpoe=is_cpoe - ), - "expireDate": prescription_expire_date.isoformat(), - "rx": True, - }, - "to": a, - } + # elderly + add_alert( + _alert_elderly( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + exams=exams, ) + ) - if len(overlap_drugs) == 0: - return {"alerts": {}, "list": {}, "stats": {}} + # tube + add_alert( + _alert_tube( + prescription_drug=prescription_drug, drug_attributes=drug_attributes + ) + ) - uniq_overlap_keys = [] - for d in overlap_drugs: - key = f"""({d["from"]["sctid"]},{d["to"]["sctid"]})""" - if key not in uniq_overlap_keys: - uniq_overlap_keys.append(key) + # allergy + add_alert(_alert_allergy(prescription_drug=prescription_drug)) - query = text( - f""" - with cruzamento as ( - select * from (values {",".join(uniq_overlap_keys)}) AS t (sctida, sctidb) + # maximum treatment period + add_alert( + _alert_max_time( + prescription_drug=prescription_drug, drug_attributes=drug_attributes + ) ) - select - r.sctida, - r.sctidb, - r.tprelacao as "kind", - r.texto as "text" - from - public.relacao r - inner join cruzamento c on (r.sctida = c.sctida and r.sctidb = c.sctidb) - where - r.ativo = true - """ - ) - active_relations = {} + # max dose + add_alert( + _alert_max_dose( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + exams=exams, + measure_unit_convert_factor=measure_unit_convert_factor, + ) + ) - for item in db.session.execute(query).all(): - key = f"{item.sctida}-{item.sctidb}-{item.kind}" - active_relations[key] = { - "sctida": item.sctida, - "sctidb": item.sctidb, - "kind": item.kind, - "text": item.text, - } + # max dose total + add_alert( + _alert_max_dose_total( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + exams=exams, + prescription_expire_date=prescription_expire_date, + dose_total=dose_total, + ) + ) - alerts = {} - relations = {} + # IRA + add_alert( + _alert_ira( + prescription_drug=prescription_drug, + drug=drug, + exams=exams, + prescription_expire_date=prescription_expire_date, + dose_total=dose_total, + dialysis=dialisys, + ) + ) + + # pregnant + add_alert( + _alert_pregnant( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + pregnant=pregnant, + ) + ) + + # lactating + add_alert( + _alert_lactating( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + lactating=lactating, + ) + ) + + return {"alerts": alerts, "stats": stats} + + +def _get_empty_stats(): stats = {} - unique_relations = {} - kinds = ["it", "dt", "dm", "iy", "sl", "rx"] - - for kind in kinds: - stats[kind] = 0 - - for drug in overlap_drugs: - drug_from = drug["from"] - drug_to = drug["to"] - - for kind in kinds: - key = f"""{drug_from["sctid"]}-{drug_to["sctid"]}-{kind}""" - invert_key = f"""{drug_to["sctid"]}-{drug_from["sctid"]}-{kind}""" - - # iy must have intravenous route - if kind == "iy" and ( - not drug_from["intravenous"] or not drug_to["intravenous"] - ): - continue - - # sl must be in the same group - if kind == "sl" and (drug_from["group"] != drug_to["group"]): - continue - - # rx rules - if kind == "rx": - if not drug_from["rx"]: - continue - else: - if drug_from["rx"]: - continue - - if key in active_relations: - if is_cpoe: - uniq_key = key - uniq_invert_key = invert_key - else: - uniq_key = f"""{key}-{drug_from["expireDate"]}""" - uniq_invert_key = f"""{invert_key}-{drug_from["expireDate"]}""" - - if ( - not uniq_key in unique_relations - and not uniq_invert_key in unique_relations - ): - stats[kind] += 1 - unique_relations[uniq_key] = 1 - unique_relations[uniq_invert_key] = 1 - - alert = typeRelations[kind] + ": " - alert += ( - strNone(active_relations[key]["text"]) - + " (" - + strNone(drug_from["drug"]) - + " e " - + strNone(drug_to["drug"]) - + ")" - ) - - relation = { - "key": key, - "kind": kind, - "to": drug_to["id"], - } - - if kind == "dm": - # one way - ids = [drug_from["id"]] - else: - # both ways - ids = [drug_from["id"], drug_to["id"]] - - for id in ids: - if id in alerts: - relations[id].append(relation) - if alert not in alerts[id]: - alerts[id].append(alert) - else: - alerts[id] = [alert] - relations[id] = [relation] - - return {"alerts": alerts, "list": relations, "stats": stats} + for t in DrugAlertTypeEnum: + stats[t.value] = 0 + + return stats + + +def _create_alert( + id_prescription_drug: str, + key: str, + alert_type: DrugAlertTypeEnum, + alert_level: DrugAlertLevelEnum, + text: str, +): + return { + "idPrescriptionDrug": id_prescription_drug, + "key": key, + "type": alert_type.value, + "level": alert_level.value, + "text": text, + } + + +def _get_dose_conv( + prescription_drug: PrescriptionDrug, + drug_attributes: DrugAttributes, + measure_unit_convert_factor: float, +): + pd_frequency = ( + 1 + if prescription_drug.frequency in [33, 44, 55, 66, 99] + else prescription_drug.frequency + ) + + if drug_attributes != None and drug_attributes.division != None: + return ( + none2zero(prescription_drug.dose) + * ( + measure_unit_convert_factor + if measure_unit_convert_factor != None + else 1 + ) + * none2zero(pd_frequency) + ) + + return none2zero(prescription_drug.doseconv) * none2zero(pd_frequency) + + +def _get_dose_total(drug_list, exams: dict): + dose_total = {} + for item in drug_list: + prescription_drug: PrescriptionDrug = item[0] + drug_attributes: DrugAttributes = item[6] + prescription_expire_date = item[10] + expireDay = prescription_expire_date.day if prescription_expire_date else 0 + measure_unit_convert_factor = ( + item.measure_unit_convert_factor + if item.measure_unit_convert_factor != None + else 1 + ) + pd_dose_conv = _get_dose_conv( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + measure_unit_convert_factor=measure_unit_convert_factor, + ) + + if prescription_drug.frequency in [66]: + # do not sum some types of frequency + continue + + idDrugAgg = str(prescription_drug.idDrug) + "_" + str(expireDay) + idDrugAggWeight = str(idDrugAgg) + "kg" + + # dose + if idDrugAgg not in dose_total: + dose_total[idDrugAgg] = {"value": pd_dose_conv, "count": 1} + else: + dose_total[idDrugAgg]["value"] += pd_dose_conv + dose_total[idDrugAgg]["count"] += 1 + + # dose / kg + weight = none2zero(exams["weight"]) + weight = weight if weight > 0 else 1 + doseWeight = round(pd_dose_conv / float(weight), 2) + + if idDrugAggWeight not in dose_total: + dose_total[idDrugAggWeight] = {"value": doseWeight, "count": 1} + else: + dose_total[idDrugAggWeight]["value"] += doseWeight + dose_total[idDrugAggWeight]["count"] += 1 + + return dose_total + + +def _alert_ira( + prescription_drug: PrescriptionDrug, + drug: Drug, + exams: dict, + prescription_expire_date, + dose_total: dict, + dialysis: str, +): + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.IRA, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if drug and "vanco" in drug.name.lower(): + expireDay = prescription_expire_date.day if prescription_expire_date else 0 + idDrugAgg = str(prescription_drug.idDrug) + "_" + str(expireDay) + maxdose = dose_total[idDrugAgg]["value"] + ckd = ( + exams["ckd"]["value"] if "ckd" in exams and exams["ckd"]["value"] else None + ) + weight = exams["weight"] + + if ( + maxdose != None + and ckd != None + and weight != None + and ckd > 0 + and weight > 0 + ): + ira = maxdose / ckd / weight + maxira = 0.6219 + + if ira > maxira and dialysis is None: + alert[ + "text" + ] = f""" + Risco de desenvolvimento de Insuficiência Renal Aguda (IRA), já que o resultado do cálculo + [dose diária de VANCOMICINA/TFG/peso] é superior a 0,6219. Caso o paciente esteja em diálise, + desconsiderar. Referência: CaEPS + """ + + return alert + + return None + + +def _alert_max_dose( + prescription_drug: PrescriptionDrug, + drug_attributes: DrugAttributes, + exams: dict, + measure_unit_convert_factor: float, +): + if not drug_attributes: + return None + + pd_dose_conv = _get_dose_conv( + prescription_drug=prescription_drug, + drug_attributes=drug_attributes, + measure_unit_convert_factor=measure_unit_convert_factor, + ) + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.MAX_DOSE, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if drug_attributes.useWeight and prescription_drug.dose: + weight = none2zero(exams["weight"]) + weight = weight if weight > 0 else 1 + doseWeight = round(pd_dose_conv / float(weight), 2) + + if drug_attributes.maxDose and drug_attributes.maxDose < doseWeight: + alert[ + "text" + ] = f""" + Dose diária prescrita ({strFormatBR(doseWeight)} {str(drug_attributes.idMeasureUnit)}/Kg) + maior que a dose de alerta + ({strFormatBR(drug_attributes.maxDose)} {str(drug_attributes.idMeasureUnit)}/Kg) + usualmente recomendada (considerada a dose diária independente da indicação). + """ + + return alert + + else: + if drug_attributes.maxDose and drug_attributes.maxDose < pd_dose_conv: + alert[ + "text" + ] = f""" + Dose diária prescrita ({str(pd_dose_conv)} {str(drug_attributes.idMeasureUnit)}) + maior que a dose de alerta ({str(drug_attributes.maxDose)} {str(drug_attributes.idMeasureUnit)}) + usualmente recomendada (considerada a dose diária independente da indicação). + """ + + return alert + + return None + + +def _alert_max_dose_total( + prescription_drug: PrescriptionDrug, + drug_attributes: DrugAttributes, + exams: dict, + prescription_expire_date, + dose_total, +): + if not drug_attributes: + return None + + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.MAX_DOSE, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + expireDay = prescription_expire_date.day if prescription_expire_date else 0 + idDrugAgg = str(prescription_drug.idDrug) + "_" + str(expireDay) + idDrugAggWeight = str(idDrugAgg) + "kg" + + if drug_attributes.useWeight and prescription_drug.dose: + weight = none2zero(exams["weight"]) + weight = weight if weight > 0 else 1 + + if ( + drug_attributes.maxDose + and idDrugAggWeight in dose_total + and dose_total[idDrugAggWeight]["count"] > 1 + and drug_attributes.maxDose + < none2zero(dose_total[idDrugAggWeight]["value"]) + ): + alert[ + "text" + ] = f""" + Dose diária prescrita SOMADA ( + {str(dose_total[idDrugAggWeight]["value"])} {str(drug_attributes.idMeasureUnit)}/Kg) maior + que a dose de alerta ( + {str(drug_attributes.maxDose)} {str(drug_attributes.idMeasureUnit)}/Kg) + usualmente recomendada (Frequência "AGORA" não é considerada no cálculo)." + """ + + return alert + + else: + if ( + drug_attributes.maxDose + and idDrugAgg in dose_total + and dose_total[idDrugAgg]["count"] > 1 + and drug_attributes.maxDose < none2zero(dose_total[idDrugAgg]["value"]) + ): + alert[ + "text" + ] = f""" + Dose diária prescrita SOMADA ( + {str(dose_total[idDrugAgg]["value"])} {str(drug_attributes.idMeasureUnit)}) maior que a + dose de alerta ({str(drug_attributes.maxDose)} {str(drug_attributes.idMeasureUnit)}) + usualmente recomendada (Frequência "AGORA" não é considerada no cálculo). + """ + + return alert + + return None + + +def _alert_max_time( + prescription_drug: PrescriptionDrug, drug_attributes: DrugAttributes +): + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.MAX_TIME, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if ( + drug_attributes + and drug_attributes.maxTime + and prescription_drug.period + and prescription_drug.period > drug_attributes.maxTime + ): + alert[ + "text" + ] = f""" + Tempo de tratamento atual ({str(prescription_drug.period)} dias) maior que o tempo máximo de tratamento ( + {str(drug_attributes.maxTime)} dias) usualmente recomendado. + """ + + return alert + + return None + + +def _alert_pregnant( + prescription_drug: PrescriptionDrug, drug_attributes: DrugAttributes, pregnant: bool +): + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.PREGNANT, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if pregnant and drug_attributes != None: + if drug_attributes.pregnant == "D" or drug_attributes.pregnant == "X": + alert["text"] = ( + f"Paciente gestante com medicamento classificado como {drug_attributes.pregnant} prescrito. Avaliar manutenção deste medicamento com a equipe médica." + ) + alert["level"] = ( + DrugAlertLevelEnum.HIGH.value + if drug_attributes.pregnant == "X" + else DrugAlertLevelEnum.MEDIUM.value + ) + + return alert + + return None + + +def _alert_lactating( + prescription_drug: PrescriptionDrug, + drug_attributes: DrugAttributes, + lactating: bool, +): + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.LACTATING, + alert_level=DrugAlertLevelEnum.MEDIUM, + text="", + ) + + if lactating and drug_attributes != None and drug_attributes.lactating == "3": + alert["text"] = ( + f"""Paciente amamentando com medicamento classificado como Alto risco prescrito. + Avaliar manutenção deste medicamento com a equipe médica ou cessação da amamentação.""" + ) + + return alert + + return None + + +def _alert_allergy(prescription_drug: PrescriptionDrug): + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.ALLERGY, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if prescription_drug.allergy == "S": + alert["text"] = "Paciente alérgico a este medicamento." + + return alert + + return None + + +def _alert_tube(prescription_drug: PrescriptionDrug, drug_attributes: DrugAttributes): + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.TUBE, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if drug_attributes and drug_attributes.tube and prescription_drug.tube: + alert["text"] = ( + f"""Medicamento contraindicado via sonda ({strNone(prescription_drug.route)})""" + ) + + return alert + + return None + + +def _alert_elderly( + prescription_drug: PrescriptionDrug, drug_attributes: DrugAttributes, exams: dict +): + if not drug_attributes or not exams: + return None + + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.ELDERLY, + alert_level=DrugAlertLevelEnum.LOW, + text="", + ) + + if drug_attributes.elderly and exams["age"] > 60: + alert[ + "text" + ] = f""" + Medicamento potencialmente inapropriado para idosos, independente das comorbidades do paciente. + """ + + return alert + + return None + + +def _alert_platelets( + prescription_drug: PrescriptionDrug, drug_attributes: DrugAttributes, exams: dict +): + if not drug_attributes or not exams: + return None + + if not drug_attributes.platelets: + return None + + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.PLATELETS, + alert_level=DrugAlertLevelEnum.HIGH, + text="", + ) + + if ( + "plqt" in exams + and exams["plqt"]["value"] + and drug_attributes.platelets > exams["plqt"]["value"] + ): + alert[ + "text" + ] = f""" + Medicamento contraindicado para paciente com plaquetas ({str(exams["plqt"]["value"])} plaquetas/µL) + abaixo de {str(drug_attributes.platelets)} plaquetas/µL. + """ + + return alert + + return None + + +def _alert_liver( + prescription_drug: PrescriptionDrug, drug_attributes: DrugAttributes, exams: dict +): + if not drug_attributes or not exams: + return None + + if not drug_attributes.liver: + return None + + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.LIVER, + alert_level=DrugAlertLevelEnum.MEDIUM, + text="", + ) + + if ( + "tgp" in exams + and exams["tgp"]["value"] + and float(exams["tgp"]["value"]) > drug_attributes.liver + ) or ( + "tgo" in exams + and exams["tgo"]["value"] + and float(exams["tgo"]["value"]) > drug_attributes.liver + ): + alert[ + "text" + ] = f""" + Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função hepática do paciente + está reduzida (acima de {str(drug_attributes.liver)} U/L). + """ + + return alert + + return None + + +def _alert_kidney( + prescription_drug: PrescriptionDrug, + drug_attributes: DrugAttributes, + exams: dict, + dialysis: str, +): + if not drug_attributes or not exams: + return None + + if not drug_attributes.kidney: + return None + + alert = _create_alert( + id_prescription_drug=str(prescription_drug.id), + key="", + alert_type=DrugAlertTypeEnum.KIDNEY, + alert_level=DrugAlertLevelEnum.MEDIUM, + text="", + ) + + if dialysis == "c": + alert["text"] = ( + "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está em diálise contínua." + ) + + return alert + + if dialysis == "x": + alert["text"] = ( + "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está em diálise estendida, também conhecida como SLED." + ) + return alert + + if dialysis == "v": + alert["text"] = ( + "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está em diálise intermitente." + ) + return alert + + if dialysis == "p": + alert["text"] = ( + "Medicamento é contraindicado ou deve sofrer ajuste de posologia, já que o paciente está em diálise peritoneal." + ) + return alert + + if ( + "ckd" in exams + and exams["ckd"]["value"] + and drug_attributes.kidney > exams["ckd"]["value"] + and exams["age"] > 17 + ): + alert[ + "text" + ] = f""" + Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função renal do paciente ( + {str(exams["ckd"]["value"])} mL/min) está abaixo de {str(drug_attributes.kidney)} mL/min. + """ + return alert + + if ( + "swrtz2" in exams + and exams["swrtz2"]["value"] + and drug_attributes.kidney > exams["swrtz2"]["value"] + and exams["age"] <= 17 + ): + alert[ + "text" + ] = f""" + Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função renal do paciente + ({str(exams["swrtz2"]["value"])} mL/min/1.73m²) está abaixo de + {str(drug_attributes.kidney)} mL/min. (Schwartz 2) + """ + return alert + + if ( + "swrtz1" in exams + and exams["swrtz1"]["value"] + and drug_attributes.kidney > exams["swrtz1"]["value"] + and exams["age"] <= 17 + ): + alert[ + "text" + ] = f""" + Medicamento deve sofrer ajuste de posologia ou contraindicado, já que a função renal do paciente + ({str(exams["swrtz1"]["value"])} mL/min/1.73m²) está abaixo de {str(drug_attributes.kidney)} + mL/min. (Schwartz 1) + """ + return alert + + return None def _filter_drug_list(drug_list): @@ -232,52 +728,13 @@ def _filter_drug_list(drug_list): for item in drug_list: prescription_drug: PrescriptionDrug = item[0] - drug: Drug = item[1] + if prescription_drug.source not in valid_sources: continue if prescription_drug.suspendedDate != None: continue - if drug == None or drug.sctid == None: - continue - filtered_list.append(item) return filtered_list - - -def _get_solution_group_key(pd: PrescriptionDrug, is_cpoe: bool): - if is_cpoe: - return f"{pd.idPrescription}-{pd.cpoe_group}" - else: - return f"{pd.idPrescription}-{pd.solutionGroup}" - - -def _get_allergies(id_patient: int): - allergies = ( - db.session.query(Substance.id, Substance.name) - .select_from(Allergy) - .join(Drug, Allergy.idDrug == Drug.id) - .join(Substance, Substance.id == Drug.sctid) - .filter(Allergy.idPatient == id_patient) - .filter(Allergy.active == True) - .group_by(Substance.id, Substance.name) - .all() - ) - - results = [] - for a in allergies: - if a.id != None: - results.append( - { - "id": None, - "drug": a.name, - "sctid": a.id, - "intravenous": False, - "group": None, - "rx": True, - } - ) - - return results diff --git a/services/prescription_service.py b/services/prescription_service.py index e9766405..ce345263 100644 --- a/services/prescription_service.py +++ b/services/prescription_service.py @@ -87,7 +87,7 @@ def search(search_key): ) -def check_prescription(idPrescription, p_status, user, evaluation_time): +def check_prescription(idPrescription, p_status, user, evaluation_time, alerts): roles = user.config["roles"] if user.config and "roles" in user.config else [] if not permission_service.is_pharma(user): raise ValidationError( @@ -136,6 +136,7 @@ def check_prescription(idPrescription, p_status, user, evaluation_time): else None ), "evaluationTime": evaluation_time or 0, + "alerts": alerts, } results = []