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 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 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
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 master_record_audit( record_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), ukrdc3: Session = Depends(get_ukrdc3), auditdb: Session = Depends(get_auditdb), since: Optional[datetime.datetime] = None, until: Optional[datetime.datetime] = None, sorter: SQLASorter = Depends( make_sqla_sorter( [AuditEvent.id, AccessEvent.time], default_sort_by=AuditEvent.id, ) ), ): """ Retreive a page of audit events related to a particular master record. """ page = paginate( sorter.sort( get_auditevents_related_to_masterrecord( auditdb, ukrdc3, jtrace, record_id, user, since=since, until=until ) ) ) for item in page.items: # type: ignore item.populate_identifiers(jtrace, ukrdc3) return page
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 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 _get_patientrecord( pid: str, user: UKRDCUser = Security(auth.get_user()), ukrdc3: Session = Depends(get_ukrdc3), ) -> PatientRecord: """Simple dependency to turn pid query param and User object into a PatientRecord object.""" return get_patientrecord(ukrdc3, pid, user)
def facility( code: str, ukrdc3: Session = Depends(get_ukrdc3), statsdb: Session = Depends(get_statsdb), user: UKRDCUser = Security(auth.get_user()), ): """Retreive information and current status of a particular facility""" return get_facility(ukrdc3, statsdb, code, user)
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 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
async def empi_merge( args: MergeRequestSchema, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), mirth: MirthAPI = Depends(get_mirth), redis: Redis = Depends(get_redis), ): """Merge a pair of MasterRecords""" return await merge_master_records(args.superseding, args.superseded, user, jtrace, mirth, redis)
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 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 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 master_record_latest_message( record_id: int, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), errorsdb: Session = Depends(get_errorsdb), ): """ Retreive a minimal representation of the latest file received for the patient, if received within the last year.""" latest = get_last_message_on_masterrecord(jtrace, errorsdb, record_id, user) if not latest: return Response(status_code=HTTP_204_NO_CONTENT) return latest
def facility_errrors_history( code: str, since: Optional[datetime.date] = None, until: Optional[datetime.date] = None, ukrdc3: Session = Depends(get_ukrdc3), statsdb: Session = Depends(get_statsdb), user: UKRDCUser = Security(auth.get_user()), ): """Retreive time-series new error counts for the last year for a particular facility""" return get_errors_history(ukrdc3, statsdb, code, user, since=since, until=until)
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
async def empi_unlink( args: UnlinkRequestSchema, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), mirth: MirthAPI = Depends(get_mirth), redis: Redis = Depends(get_redis), ): """Unlink a Person from a specified MasterRecord""" return await unlink_person_from_master_record( args.person_id, args.master_id, args.comment, user, jtrace, mirth, redis, )
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()), )
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))
def admin_counts( jtrace: Session = Depends(get_jtrace), statsdb: Session = Depends(get_statsdb), user: UKRDCUser = Security(auth.get_user()), ): """Retreive basic counts across the UKRDC""" open_workitems_count = get_workitems(jtrace, user, [1]).count() ukrdc_records_count = ( jtrace.query(MasterRecord) .filter(MasterRecord.nationalid_type == "UKRDC") .count() ) patients_receiving_errors_count = ( statsdb.query(PatientsLatestErrors.ni).distinct().count() ) return AdminCountsSchema( open_workitems=open_workitems_count, UKRDC_records=ukrdc_records_count, patients_receiving_errors=patients_receiving_errors_count, )
async def patient_export_pkb( pid: str, background_tasks: BackgroundTasks, 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), tracker: TaskTracker = Depends(get_task_tracker), ): """ Export a specific patient's data to PKB. This export runs as a background task since the split sending can take a while. """ task = tracker.http_create(export_all_to_pkb, lock=f"task-export-pkb-{pid}") background_tasks.add_task(task.tracked, pid, user, ukrdc3, mirth, redis) audit.add_event(Resource.PATIENT_RECORD, pid, RecordOperation.EXPORT_PKB) return task.response()
async def workitem_update( workitem_id: int, args: UpdateWorkItemRequestSchema, user: UKRDCUser = Security(auth.get_user()), jtrace: Session = Depends(get_jtrace), mirth: MirthAPI = Depends(get_mirth), redis: Redis = Depends(get_redis), audit: Auditer = Depends(get_auditer), ): """Update a particular work item in the EMPI""" audit.add_event(Resource.WORKITEM, workitem_id, AuditOperation.UPDATE) return await update_workitem( jtrace, workitem_id, user, mirth, redis, status=args.status, comment=args.comment, )
async def workitem_close( workitem_id: int, args: Optional[CloseWorkItemRequestSchema], jtrace: Session = Depends(get_jtrace), user: UKRDCUser = Security(auth.get_user()), mirth: MirthAPI = Depends(get_mirth), redis: Redis = Depends(get_redis), audit: Auditer = Depends(get_auditer), ): """Update and close a particular work item""" workitem = get_workitem(jtrace, workitem_id, user) audit.add_event(Resource.WORKITEM, workitem.id, AuditOperation.UPDATE) return await close_workitem( jtrace, workitem.id, user, mirth, redis, comment=(args.comment if args else None), )