예제 #1
0
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
예제 #2
0
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
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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
예제 #6
0
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
            )
        )
    )
예제 #7
0
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,
            )))
예제 #8
0
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)
예제 #9
0
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)
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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)
예제 #13
0
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
예제 #14
0
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
예제 #15
0
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)
예제 #16
0
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
예제 #17
0
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
예제 #18
0
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)
예제 #19
0
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
예제 #20
0
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,
    )
예제 #21
0
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
예제 #22
0
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
예제 #23
0
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()),
    )
예제 #24
0
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
예제 #25
0
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)
예제 #26
0
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))
예제 #27
0
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,
    )
예제 #28
0
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()
예제 #29
0
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,
    )
예제 #30
0
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),
    )