Example #1
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
Example #2
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,
            )))
Example #3
0
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
Example #4
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
            )
        )
    )
Example #5
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
Example #6
0
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
Example #7
0
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)
Example #8
0
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))
Example #9
0
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))
Example #10
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
Example #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
Example #12
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
Example #13
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
Example #14
0
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()
Example #15
0
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()
Example #16
0
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()
Example #17
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)
Example #18
0
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
Example #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
Example #20
0
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()))
Example #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
Example #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
Example #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()),
    )
Example #24
0
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
Example #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)
Example #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))
Example #27
0
def resultitem_get(
        resultitem_id: str,
        patient_record: PatientRecord = Depends(_get_patientrecord),
        audit: Auditer = Depends(get_auditer),
) -> ResultItem:
    """Retreive a particular lab result"""
    item = patient_record.result_items.filter(
        ResultItem.id == resultitem_id).first()
    if not item:
        raise HTTPException(404, detail="Result item not found")

    audit.add_event(
        Resource.RESULTITEM,
        resultitem_id,
        RecordOperation.READ,
        parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid,
                               RecordOperation.READ),
    )

    return item
Example #28
0
def document_get(
        document_id: str,
        patient_record: PatientRecord = Depends(_get_patientrecord),
        audit: Auditer = Depends(get_auditer),
):
    """Retreive a specific patient's document information"""
    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),
    )

    return document
Example #29
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()
Example #30
0
def patient_documents(
        patient_record: PatientRecord = Depends(_get_patientrecord),
        sorter: SQLASorter = Depends(
            make_sqla_sorter(
                [Document.documenttime, Document.updatedon],
                default_sort_by=Document.documenttime,
            )),
        audit: Auditer = Depends(get_auditer),
):
    """Retreive a specific patient's documents"""
    audit.add_event(
        Resource.DOCUMENTS,
        None,
        RecordOperation.READ,
        parent=audit.add_event(Resource.PATIENT_RECORD, patient_record.pid,
                               RecordOperation.READ),
    )
    # NOTE: We defer the 'stream' column to avoid sending the full PDF file content
    # when we're just querying the list of documents.
    return paginate(
        sorter.sort(patient_record.documents.options(defer("stream"))))