async def patient_update_demographics( demographics: DemographicUpdateSchema, patient_record: PatientRecord = Depends(_get_patientrecord), mirth: MirthAPI = Depends(get_mirth), redis: Redis = Depends(get_redis), audit: Auditer = Depends(get_auditer), ): """ Update the demographic data of a given patient record Args: demographics (DemographicUpdateSchema): [description] patient_record (PatientRecord, optional): [description]. Defaults to Depends(_get_patientrecord). mirth (MirthAPI, optional): [description]. Defaults to Depends(get_mirth). redis (Redis, optional): [description]. Defaults to Depends(get_redis). audit (Auditer, optional): [description]. Defaults to Depends(get_auditer). Returns: [type]: [description] """ response = await update_patient_demographics( patient_record, demographics.name, demographics.birth_time, demographics.gender, demographics.address, mirth, redis, ) audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.UPDATE) return response
async def error_masterrecords( message_id: str, user: UKRDCUser = Security(auth.get_user()), errorsdb: Session = Depends(get_errorsdb), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive MasterRecords associated with a specific error message""" message = get_message(errorsdb, message_id, user) # Get masterrecords directly referenced by the error records = (jtrace.query(MasterRecord).filter( MasterRecord.nationalid == message.ni).all()) message_audit = audit.add_event(Resource.MESSAGE, message.id, MessageOperation.READ) for record in records: audit.add_event( Resource.MASTER_RECORD, record.id, AuditOperation.READ, parent=message_audit, ) return records
def patient_delete( pid: str, user: UKRDCUser = Security(auth.get_user()), ukrdc3: Session = Depends(get_ukrdc3), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), args: Optional[DeletePIDRequestSchema] = None, ): """Delete a specific patient record and all its associated data""" summary: DeletePIDResponseSchema audit_op: AuditOperation if args and args.hash: summary = delete_pid(ukrdc3, jtrace, pid, args.hash, user) audit_op = AuditOperation.DELETE else: summary = summarise_delete_pid(ukrdc3, jtrace, pid, user) audit_op = AuditOperation.READ record_audit = audit.add_event(Resource.PATIENT_RECORD, pid, audit_op) if summary.empi: for person in summary.empi.persons: audit.add_event(Resource.PERSON, person.id, audit_op, parent=record_audit) for master_record in summary.empi.master_records: audit.add_event(Resource.MASTER_RECORD, master_record.id, audit_op, parent=record_audit) return summary
def resultitem_delete( resultitem_id: str, patient_record: PatientRecord = Depends(_get_patientrecord), ukrdc3: Session = Depends(get_ukrdc3), audit: Auditer = Depends(get_auditer), ) -> Response: """Mark a particular lab result for deletion""" item = patient_record.result_items.filter( ResultItem.id == resultitem_id).first() if not item: raise HTTPException(404, detail="Result item not found") order: Optional[LabOrder] = item.order ukrdc3.delete(item) ukrdc3.commit() if order and order.result_items.count() == 0: ukrdc3.delete(order) ukrdc3.commit() audit.add_event( Resource.RESULTITEM, resultitem_id, RecordOperation.DELETE, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.UPDATE), ) return Response(status_code=204)
def master_record_messages( record_id: int, facility: Optional[str] = None, since: Optional[datetime.datetime] = None, until: Optional[datetime.datetime] = None, status: Optional[list[str]] = QueryParam(None), user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), errorsdb: Session = Depends(get_errorsdb), sorter: SQLASorter = Depends(ERROR_SORTER), audit: Auditer = Depends(get_auditer), ): """ Retreive a list of errors related to a particular master record. By default returns message created within the last 365 days. """ audit.add_event( Resource.MESSAGES, None, AuditOperation.READ, parent=audit.add_event(Resource.MASTER_RECORD, record_id, AuditOperation.READ), ) return paginate( sorter.sort( get_messages_related_to_masterrecord( errorsdb, jtrace, record_id, user, status, facility, since, until ) ) )
def workitems_list( since: Optional[datetime.datetime] = None, until: Optional[datetime.datetime] = None, status: Optional[list[int]] = Query([1]), facility: Optional[str] = None, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), sorter: SQLASorter = Depends( make_sqla_sorter( [WorkItem.id, WorkItem.last_updated], default_sort_by=WorkItem.last_updated, )), audit: Auditer = Depends(get_auditer), ): """Retreive a list of open work items from the EMPI""" query = get_workitems(jtrace, user, statuses=status, facility=facility, since=since, until=until) page = paginate(sorter.sort(query)) for item in page.items: # type: ignore audit.add_workitem(item) return page
def patient_resultitems( patient_record: PatientRecord = Depends(_get_patientrecord), service_id: Optional[list[str]] = QueryParam([]), order_id: Optional[list[str]] = QueryParam([]), since: Optional[datetime.datetime] = None, until: Optional[datetime.datetime] = None, sorter: SQLASorter = Depends( make_sqla_sorter( [ResultItem.observation_time, ResultItem.entered_on], default_sort_by=ResultItem.observation_time, )), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's lab orders""" query = patient_record.result_items if service_id: query = query.filter(ResultItem.service_id.in_(service_id)) if order_id: query = query.filter(ResultItem.order_id.in_(order_id)) if since: query = query.filter(ResultItem.observation_time >= since) if until: query = query.filter(ResultItem.observation_time <= until) audit.add_event( Resource.RESULTITEMS, None, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return paginate(sorter.sort(query))
def patient_observations( patient_record: PatientRecord = Depends(_get_patientrecord), code: Optional[list[str]] = QueryParam([]), sorter: SQLASorter = Depends( make_sqla_sorter( [Observation.observation_time, Observation.updated_on], default_sort_by=Observation.observation_time, )), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's lab orders""" observations = patient_record.observations if code: observations = observations.filter( Observation.observation_code.in_(code)) audit.add_event( Resource.OBSERVATIONS, None, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return paginate(sorter.sort(observations))
def error_messages( facility: Optional[str] = None, since: Optional[datetime.datetime] = None, until: Optional[datetime.datetime] = None, status: Optional[list[str]] = QueryParam(None), ni: Optional[list[str]] = QueryParam([]), user: UKRDCUser = Security(auth.get_user()), errorsdb: Session = Depends(get_errorsdb), sorter: SQLASorter = Depends(ERROR_SORTER), audit: Auditer = Depends(get_auditer), ): """ Retreive a list of error messages, optionally filtered by NI, facility, or date. By default returns message created within the last 365 days. """ audit.add_event(Resource.MESSAGES, None, MessageOperation.READ) return paginate( sorter.sort( get_messages( errorsdb, user, statuses=status, nis=ni, facility=facility, since=since, until=until, )))
def master_record_related( record_id: int, exclude_self: bool = True, nationalid_type: Optional[str] = None, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a list of other master records related to a particular master record""" records = get_masterrecords_related_to_masterrecord( jtrace, record_id, user, nationalid_type=nationalid_type, exclude_self=exclude_self, ).all() record_audit = audit.add_event( Resource.MASTER_RECORD, record_id, AuditOperation.READ ) for record in records: audit.add_event( Resource.MASTER_RECORD, record.id, AuditOperation.READ, parent=record_audit, ) return records
def document_download( document_id: str, patient_record: PatientRecord = Depends(_get_patientrecord), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's document file""" document: Optional[Document] = patient_record.documents.filter( Document.id == document_id).first() if not document: raise HTTPException(404, detail="Document not found") audit.add_event( Resource.DOCUMENT, document_id, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) media_type: str stream: bytes filename: str if not document.filetype: media_type = "text/csv" stream = (document.notetext or "").encode() filename = f"{document.documentname}.txt" else: media_type = document.filetype stream = document.stream or b"" filename = document.filename or document.documentname or "NoFileName" response = Response(content=stream, media_type=media_type) response.headers[ "Content-Disposition"] = f"attachment; filename={filename}" return response
def person_detail( person_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a particular Person record from the EMPI""" person = get_person(jtrace, person_id, user) audit.add_event(Resource.PERSON, person.id, AuditOperation.READ) return person
def workitem_detail( workitem_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a particular work item from the EMPI""" workitem = get_extended_workitem(jtrace, workitem_id, user) audit.add_workitem(workitem) return workitem
def master_record_detail( record_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a particular master record from the EMPI""" record = get_masterrecord(jtrace, record_id, user) audit.add_event(Resource.MASTER_RECORD, record.id, AuditOperation.READ) return record
async def patient_export_radar( pid: str, user: UKRDCUser = Security(auth.get_user()), ukrdc3: Session = Depends(get_ukrdc3), mirth: MirthAPI = Depends(get_mirth), redis: Redis = Depends(get_redis), audit: Auditer = Depends(get_auditer), ): """Export a specific patient's data to RaDaR""" response = await export_all_to_radar(pid, user, ukrdc3, mirth, redis) audit.add_event(Resource.PATIENT_RECORD, pid, RecordOperation.EXPORT_RADAR) return response
def patient_treatments( patient_record: PatientRecord = Depends(_get_patientrecord), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's treatments""" audit.add_event( Resource.TREATMENTS, None, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return patient_record.treatments.all()
def patient_surveys( patient_record: PatientRecord = Depends(_get_patientrecord), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's surveys""" audit.add_event( Resource.SURVEYS, None, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return patient_record.surveys.all()
def error_detail( message_id: str, user: UKRDCUser = Security(auth.get_user()), errorsdb: Session = Depends(get_errorsdb), audit: Auditer = Depends(get_auditer), ): """Retreive detailed information about a specific error message""" # For some reason the fastAPI response_model doesn't call our channel_name # validator, meaning we don't get a populated channel name unless we explicitly # call it here. message = get_message(errorsdb, message_id, user) audit.add_event(Resource.MESSAGE, message.id, MessageOperation.READ) return MessageSchema.from_orm(message)
def patient_medications( patient_record: PatientRecord = Depends(_get_patientrecord), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's medications""" audit.add_event( Resource.MEDICATIONS, None, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return patient_record.medications.all()
def workitem_collection( workitem_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a list of other work items related to a particular work item""" collection = get_workitem_collection(jtrace, workitem_id, user).all() for workitem in collection: audit.add_workitem(workitem) return collection
def patient_get( patient_record: PatientRecord = Depends(_get_patientrecord), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient record""" # For some reason the fastAPI response_model doesn't call our master_record_compute # validator, meaning we don't get a populated master record unless we explicitly # call it here. record: PatientRecordSchema = PatientRecordSchema.from_orm_with_master_record( patient_record, jtrace) audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ) return record
def patient_laborders( patient_record: PatientRecord = Depends(_get_patientrecord), audit: Auditer = Depends(get_auditer), ): """Retreive a specific patient's lab orders""" audit.add_event( Resource.LABORDERS, None, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return paginate( patient_record.lab_orders.order_by( LabOrder.specimen_collected_time.desc()))
def person_masterrecords( person_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive MasterRecords directly linked to a particular Person record""" person: Person = get_person(jtrace, person_id, user) related_master_ids = [link.master_id for link in person.link_records] records = (get_masterrecords(jtrace, user).filter( MasterRecord.id.in_(related_master_ids)).all()) for record in records: audit.add_event(Resource.MASTER_RECORD, record.id, AuditOperation.READ) return records
def search_masterrecords( pid: list[str] = QueryParam([]), mrn_number: list[str] = QueryParam([]), ukrdc_number: list[str] = QueryParam([]), full_name: list[str] = QueryParam([]), dob: list[str] = QueryParam([]), facility: list[str] = QueryParam([]), search: list[str] = QueryParam([]), number_type: list[str] = QueryParam([]), include_ukrdc: bool = False, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), ukrdc3: Session = Depends(get_ukrdc3), audit: Auditer = Depends(get_auditer), ): """Search the EMPI for a particular master record""" matched_ukrdc_ids = search_masterrecord_ids(mrn_number, ukrdc_number, full_name, pid, dob, facility, search, ukrdc3) # Matched UKRDC IDs will only give us UKRDC-type Master Records, # but we also want the associated NHS/CHI/HSC master records. # So, we do a single pass of the link records to expand our selection. person_ids = (jtrace.query(LinkRecord.person_id).join(MasterRecord).filter( MasterRecord.nationalid.in_(matched_ukrdc_ids))) master_ids = (jtrace.query(MasterRecord.id).join(LinkRecord).filter( LinkRecord.person_id.in_(person_ids))) matched_records = get_masterrecords(jtrace, user).filter( MasterRecord.id.in_(master_ids)) if number_type: matched_records = matched_records.filter( MasterRecord.nationalid_type.in_(number_type)) if not include_ukrdc: matched_records = matched_records.filter( MasterRecord.nationalid_type != "UKRDC") page: Page = paginate(matched_records) # type: ignore for record in page.items: audit.add_event(Resource.MASTER_RECORD, record.id, AuditOperation.READ) return page
def master_record_persons( record_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive a list of person records related to a particular master record.""" persons = get_persons_related_to_masterrecord(jtrace, record_id, user).all() record_audit = audit.add_event( Resource.MASTER_RECORD, record_id, AuditOperation.READ ) for person in persons: audit.add_event( Resource.PERSON, person.id, AuditOperation.READ, parent=record_audit ) return persons
def master_record_statistics( record_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), errorsdb: Session = Depends(get_errorsdb), audit: Auditer = Depends(get_auditer), ): """Retreive a particular master record from the EMPI""" record: MasterRecord = get_masterrecord(jtrace, record_id, user) errors = get_messages_related_to_masterrecord( errorsdb, jtrace, record.id, user, statuses=["ERROR"] ) related_records = get_masterrecords_related_to_masterrecord(jtrace, record.id, user) related_ukrdc_records = related_records.filter( MasterRecord.nationalid_type == "UKRDC" ) workitems = get_workitems( jtrace, user, master_id=[record.id for record in related_records.all()] ) audit.add_event( Resource.STATISTICS, None, AuditOperation.READ, parent=audit.add_event(Resource.MASTER_RECORD, record.id, AuditOperation.READ), ) return MasterRecordStatisticsSchema( workitems=workitems.count(), errors=errors.count(), # Workaround for https://jira.ukrdc.org/browse/UI-56 # For some reason, if you log in as a non-admin user, # related_ukrdc_records.count() returns the wrong value # sometimes, despite the query returning the right data. # I truly, deeply do not understand why this would happen, # so I've had to implement this slightly slower workaround. # Assuming the patient doesn't somehow have hundreds of # UKRDC records, the speed decrease should be negligable. ukrdcids=len(related_ukrdc_records.all()), )
def laborder_get( order_id: str, patient_record: PatientRecord = Depends(_get_patientrecord), audit: Auditer = Depends(get_auditer), ) -> LabOrder: """Retreive a particular lab order""" order = patient_record.lab_orders.filter(LabOrder.id == order_id).first() if not order: raise HTTPException(404, detail="Lab Order not found") audit.add_event( Resource.LABORDER, order_id, RecordOperation.READ, parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid, RecordOperation.READ), ) return order
async def error_workitems( message_id: str, user: UKRDCUser = Security(auth.get_user()), errorsdb: Session = Depends(get_errorsdb), jtrace: Session = Depends(get_jtrace), audit: Auditer = Depends(get_auditer), ): """Retreive WorkItems associated with a specific error message""" message = get_message(errorsdb, message_id, user) workitems = get_workitems_related_to_message(jtrace, errorsdb, str(message.id), user).all() message_audit = audit.add_event(Resource.MESSAGE, message.id, MessageOperation.READ) for item in workitems: audit.add_workitem(item, parent=message_audit) return workitems
async def master_record_memberships_create_pkb( record_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), mirth: MirthAPI = Depends(get_mirth), audit: Auditer = Depends(get_auditer), redis: Redis = Depends(get_redis), ): """ Create a new PKB membership for a master record. """ record = get_masterrecord(jtrace, record_id, user) # If the request was not triggered from a UKRDC MasterRecord if record.nationalid_type != "UKRDC": # Find all linked UKRDC MasterRecords records = get_masterrecords_related_to_masterrecord( jtrace, record_id, user, nationalid_type="UKRDC", ).all() if len(records) > 1: raise HTTPException( 500, "Cannot create PKB membership for a patient with multiple UKRDC IDs", ) if len(records) == 0: raise HTTPException( 500, "Cannot create PKB membership for a patient with no UKRDC ID", ) # Use the UKRDC MasterRecord to create the PKB membership record = records[0] audit.add_event( Resource.MEMBERSHIP, "PKB", AuditOperation.CREATE, parent=audit.add_event(Resource.MASTER_RECORD, record_id, AuditOperation.READ), ) return await create_pkb_membership(record, mirth, redis)
def facility_patients_latest_errors( code: str, ukrdc3: Session = Depends(get_ukrdc3), errorsdb: Session = Depends(get_errorsdb), statsdb: Session = Depends(get_statsdb), user: UKRDCUser = Security(auth.get_user()), sorter: SQLASorter = Depends(ERROR_SORTER), audit: Auditer = Depends(get_auditer), ): """Retreive time-series new error counts for the last year for a particular facility""" query = get_patients_latest_errors(ukrdc3, errorsdb, statsdb, code, user) audit.add_event( Resource.MESSAGES, None, AuditOperation.READ, parent=audit.add_event(Resource.FACILITY, code, AuditOperation.READ), ) return paginate(sorter.sort(query))