Labs

Introduction #

The Canvas SDK provides comprehensive models for working with laboratory data throughout the entire lab workflow—from ordering tests to reviewing results. The primary models include:

  • LabOrder: Represents a lab order placed for a patient, including order details, transmission type, and associated tests
  • LabTest: Individual tests within a lab order, tracking status from creation through processing
  • LabReport: Contains the results returned from the lab, including all values and associated metadata
  • LabReportRemark: Report-level remarks from lab personnel, accessible via LabReport.remarks
  • LabValue: Individual test results within a lab report, including values, units, and reference ranges
  • LabReview: Tracks the clinical review process for lab results, including provider comments and patient communication
  • DiagnosticReport: The DiagnosticReport linked to a LabReport, accessible via LabReport.diagnostic_reports

Basic Usage #

To retrieve a LabReport model by id, use the objects.get method on the model. For example:

from canvas_sdk.v1.data.lab import LabReport

lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")

Filtering #

To retrieve the LabValue instances that are associated with the LabReport, you can either call the values on the LabReport instance:

from canvas_sdk.v1.data.lab import LabReport

lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
lab_values = lab_report.values.all()

Or query the LabValue model and pass the report argument:

from canvas_sdk.v1.data.lab import LabReport, LabValue

lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
lab_values = LabValue.objects.filter(lab_report=lab_report)

Additionally, codings for lab values can be attained by querying the LabValueCoding model. To retrieve the codings associated with a LabValue, you can call codings on the LabValue instance:

from logger import log
from canvas_sdk.v1.data.lab import LabReport, LabValue

lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
lab_values = LabValue.objects.filter(lab_report=lab_report)
for value in lab_values:
    log.info(value.codings.all())

Or query the LabValueCoding model directly:

from logger import log
from canvas_sdk.v1.data.lab import LabReport, LabValue, LabValueCoding

lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
lab_values = LabValue.objects.filter(lab_report=lab_report)
for value in lab_values:
    lab_value_codings = LabValueCoding.objects.filter(value=value)
    log.info(lab_value_codings)

Ordered vs. result tests #

A LabReport references two kinds of LabTest rows, and LabReport exposes each as its own property:

  • ordered_tests: LabTest rows created when a LabOrder is placed. These represent the tests that were requested and are not associated with any LabValue records.
  • result_tests: LabTest rows created for the results themselves. For FHIR DiagnosticReport and Health Gorilla ingested reports, LabValue records are attached to these tests.
from canvas_sdk.v1.data.lab import LabReport

lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")

for test in lab_report.ordered_tests:
    print(f"Ordered: {test.ontology_test_name}")

for test in lab_report.result_tests:
    print(f"Result: {test.ontology_test_name}")
    for value in test.values.all():
        print(f"  {value.value} {value.units}")

When iterating many reports at once, the LabReport queryset exposes with_result_tests_and_values() to prefetch each report’s result tests (with their values) and the report’s full value list in bulk:

from canvas_sdk.v1.data.lab import LabReport

reports = (
    LabReport.objects
    .filter(patient__id="patient-id")
    .with_result_tests_and_values()
)

To query all lab reports for a particular patient, the patient argument can be used:

from logger import log
from canvas_sdk.v1.data.lab import LabReport
from canvas_sdk.v1.data.patient import Patient

patient = Patient.objects.get(id="6cbc40b408294a5f9b41f57ba1b2b487")
lab_report = LabReport.objects.filter(patient=patient)

Example #

The following plugin code will run every time a new Lab Report is created and log the patient it is for, along with the values and codings from the report’s results:

from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from logger import log

from canvas_sdk.v1.data.lab import LabReport


class MyHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.LAB_REPORT_CREATED)

    def compute(self):
        lab_report = LabReport.objects.select_related("patient").get(id=self.target)
        if lab_report.patient:
            log.info(f"{lab_report.patient.first_name} {lab_report.patient.last_name}")
        for value in lab_report.values.all():
            log.info(f"{value.value} {value.units}")
            for coding in value.codings.all():
                log.info(coding.system)
                log.info(coding.name)
                log.info(coding.code)
        return []

For complete field documentation on all lab models, see the Attributes section below.

Working with Lab Orders and Tests #

You can also work with lab orders and their associated tests. Here’s an example of querying a lab order and checking the status of its tests:

from canvas_sdk.v1.data.lab import LabOrder, LabTest

# Get a lab order by ID
lab_order = LabOrder.objects.get(id="abc123...")

# Access all tests in the order
for test in lab_order.tests.all():
    print(f"Test: {test.ontology_test_name}")
    print(f"Status: {test.status}")
    print(f"Code: {test.ontology_test_code}")

    # Check if results have been received
    if test.report:
        print(f"Report available with {test.report.values.count()} values")

Navigating Between Lab Orders and Reports #

Lab orders and lab reports are connected through the LabTest model. Here’s how to navigate between them:

Getting the LabOrder from a LabReport #

from canvas_sdk.v1.data.lab import LabReport

# Get a lab report
lab_report = LabReport.objects.get(id="report-id")

# Direct access to all orders via the reverse many-to-many relationship
for lab_order in lab_report.laborder_set.all():
    print(f"Order ID: {lab_order.id}")
    print(f"Ordered by: {lab_order.ordering_provider.full_name if lab_order.ordering_provider else 'N/A'}")
    print(f"Date ordered: {lab_order.date_ordered}")

# Alternatively, access the order through the tests
for test in lab_report.tests.all():
    lab_order = test.order
    print(f"Order ID: {lab_order.id}")
    break  # Usually all tests in a report share the same order

Getting LabReports from a LabOrder #

from canvas_sdk.v1.data.lab import LabOrder

# Get a lab order
lab_order = LabOrder.objects.get(id="order-id")

# Direct access to all reports via the many-to-many relationship
for report in lab_order.reports.all():
    print(f"Report ID: {report.id}")
    print(f"Date performed: {report.date_performed}")
    print(f"Number of values: {report.values.count()}")

# Alternatively, access reports through the tests if you need test-level details
for test in lab_order.tests.all():
    if test.report:
        print(f"Test: {test.ontology_test_name}")
        print(f"Report ID: {test.report.id}")

Working with Diagnostic Reports #

A LabReport may be linked to one or more DiagnosticReport records. The DiagnosticReport model exposes its id, status, the subject (Patient), and the lab foreign key back to the originating LabReport.

Getting the DiagnosticReport(s) from a LabReport #

from canvas_sdk.v1.data.lab import LabReport

lab_report = LabReport.objects.get(id="report-id")

for diagnostic_report in lab_report.diagnostic_reports.all():
    print(f"DiagnosticReport ID: {diagnostic_report.id}")
    print(f"Status: {diagnostic_report.status}")

Following a DiagnosticReport back to its LabReport #

from canvas_sdk.v1.data.diagnostic_report import DiagnosticReport

diagnostic_report = DiagnosticReport.objects.get(id="diagnostic-report-id")

# Follow the `lab` foreign key back to the originating LabReport
lab_report = diagnostic_report.lab
if lab_report:
    print(f"LabReport ID: {lab_report.id}")

Filtering DiagnosticReports by patient #

from canvas_sdk.v1.data.diagnostic_report import DiagnosticReport

diagnostic_reports = DiagnosticReport.objects.for_patient("patient-id")

Reconciling with FHIR #

A DiagnosticReport’s id is the same id used by the FHIR API, so you can start from a LabReport, grab its DiagnosticReport, and use the FHIR client to read the corresponding FHIR DiagnosticReport resource:

from canvas_sdk.clients.canvas_fhir import CanvasFhir
from canvas_sdk.v1.data.lab import LabReport

lab_report = LabReport.objects.get(id="report-id")
diagnostic_report = lab_report.diagnostic_reports.first()

# Declare these secrets in the CANVAS_MANIFEST.json and set the values on the
# plugin configuration page.
client = CanvasFhir(
    self.secrets["CANVAS_FHIR_CLIENT_ID"],
    self.secrets["CANVAS_FHIR_CLIENT_SECRET"],
)

# Use the DiagnosticReport's id to read the corresponding FHIR DiagnosticReport resource.
fhir_diagnostic_report = client.read("DiagnosticReport", str(diagnostic_report.id))

Working with Lab Reviews #

Lab reviews track the clinical review process for lab results, including provider comments and patient communication. Here’s how to work with the LabReport and LabReview relationship:

Accessing the Review from a LabReport #

from canvas_sdk.v1.data.lab import LabReport

# Get a lab report
lab_report = LabReport.objects.get(id="report-id")

# Check if the report has been reviewed
if lab_report.review:
    lab_review = lab_report.review
    print(f"Review status: {lab_review.status}")
    print(f"Internal comment: {lab_review.internal_comment}")
    print(f"Message to patient: {lab_review.message_to_patient}")

    # Access the provider who reviewed it
    if lab_review.originator:
        print(f"Reviewed by: {lab_review.originator.full_name}")
else:
    print("Report has not been reviewed yet")

Accessing Reports from a LabReview #

from canvas_sdk.v1.data.lab import LabReview

# Get a lab review
lab_review = LabReview.objects.get(id="review-id")

# Access all reports in this review batch
for report in lab_review.reports.all():
    print(f"Report ID: {report.id}")
    print(f"Date performed: {report.date_performed}")
    print(f"Number of values: {report.values.count()}")

    # Check if this report requires signature
    if report.requires_signature:
        print("  ⚠️  Requires provider signature")

Finding Unreviewed Lab Reports #

from canvas_sdk.v1.data.lab import LabReport
from canvas_sdk.v1.data.patient import Patient

# Get all unreviewed lab reports for a patient
patient = Patient.objects.get(id="patient-id")
unreviewed_reports = LabReport.objects.filter(
    patient=patient,
    review__isnull=True,
    deleted=False
)

print(f"Found {unreviewed_reports.count()} unreviewed reports")
for report in unreviewed_reports:
    print(f"Report from {report.date_performed} - {report.values.count()} values")

Filtering Lab Results by Abnormal Values #

A common use case is to identify abnormal lab values that may require clinical attention:

from canvas_sdk.v1.data.lab import LabReport, LabValue
from canvas_sdk.v1.data.patient import Patient

# Get all lab reports for a patient
patient = Patient.objects.get(id="patient-id")
lab_reports = LabReport.objects.filter(patient=patient)

# Find all abnormal values
for report in lab_reports:
    abnormal_values = report.values.filter(abnormal_flag__isnull=False).exclude(abnormal_flag="")

    if abnormal_values.exists():
        print(f"Report from {report.date_performed}:")
        for value in abnormal_values:
            for coding in value.codings.all():
                print(f"  {coding.name}: {value.value} {value.units} (Flag: {value.abnormal_flag})")

Attributes #

LabReport #

Field NameType
idUUID
dbidInteger
createdDateTime
modifiedDateTime
review_modeDocumentReviewMode
junkedBoolean
requires_signatureBoolean
assigned_dateDateTime
patientPatient
transmission_typeTransmissionType
for_test_onlyBoolean
external_idString
versionInteger
requisition_numberString
reviewLabReview
original_dateDateTime
date_performedDateTime
custom_document_nameString
originatorCanvasUser
committerCanvasUser
entered_in_errorCanvasUser
deletedBoolean
valuesLabValue[]
testsLabTest[]
remarksLabReportRemark[]
diagnostic_reportsDiagnosticReport[]

LabReportRemark #

Field NameType
dbidInteger
createdDateTime
modifiedDateTime
reportLabReport
commentString

DiagnosticReport #

The DiagnosticReport linked to a LabReport. The id is the DiagnosticReport id.

Field NameType
idUUID
dbidInteger
createdDateTime
modifiedDateTime
statusDiagnosticReportStatus
subjectPatient
labLabReport

LabReview #

Field NameType
idUUID
dbidInteger
createdDateTime
modifiedDateTime
originatorCanvasUser
deletedBoolean
committerCanvasUser
entered_in_errorCanvasUser
internal_commentString
message_to_patientString
statusString
noteNote
patientPatient
patient_communication_methodString
reportsLabReport[]
testsLabTest[]

LabValue #

Field NameType
idUUID
dbidInteger
createdDateTime
modifiedDateTime
reportLabReport
valueString
unitsString
abnormal_flagString
reference_rangeString
low_thresholdString
high_thresholdString
commentString
observation_statusString
testLabTest
codingsLabValueCoding[]

LabValueCoding #

Field NameType
dbidInteger
createdDateTime
modifiedDateTime
valueLabValue
codeString
nameString
systemString

LabOrder #

Field NameType
idUUID
dbidInteger
createdDateTime
modifiedDateTime
originatorCanvasUser
deletedBoolean
committerCanvasUser
entered_in_errorCanvasUser
patientPatient
noteNote
ontology_lab_partnerString
ordering_providerStaff
commentString
requisition_numberString
is_patient_billBoolean
date_orderedDateTime
fasting_statusBoolean
specimen_collection_typeSpecimenCollectionType
transmission_typeTransmissionType
courtesy_copy_typeCourtesyCopyType
courtesy_copy_numberString
courtesy_copy_textString
parent_orderLabOrder
healthgorilla_idString
manual_processing_statusManualProcessingStatus
manual_processing_commentString
labcorp_abn_urlURL
reasonsLabOrderReason[]
testsLabTest[]
reportsLabReport[]

LabOrderReason #

Field NameType
dbidInteger
createdDateTime
modifiedDateTime
originatorCanvasUser
deletedBoolean
committerCanvasUser
entered_in_errorCanvasUser
orderLabOrder
modeLabReasonMode
reason_conditionsLabOrderReasonCondition[]

LabOrderReasonCondition #

Field NameType
dbidInteger
createdDateTime
modifiedDateTime
originatorCanvasUser
deletedBoolean
committerCanvasUser
entered_in_errorCanvasUser
reasonLabOrderReason
conditionCondition

LabTest #

Represents an individual test within a lab order. Each LabTest tracks the lifecycle of a specific test from order creation through processing and result receipt.

Field NameType
idUUID
dbidInteger
createdDateTime
modifiedDateTime
ontology_test_nameString
ontology_test_codeString
statusLabTestOrderStatus
reportLabReport
specimen_typeString
specimen_source_codeString
specimen_source_descriptionString
specimen_source_coding_systemString
orderLabOrder
aoe_codeString
procedure_classString
valuesLabValue[]

Enumeration types #

DiagnosticReportStatus #

ValueLabel
REGISTEREDRegistered
PARTIALPartial
PRELIMINARYPreliminary
FINALFinal
AMENDEDAmended
CORRECTEDCorrected
APPENDEDAppended
CANCELLEDCancelled
ENTERED_IN_ERROREntered-in-error
UNKNOWNUnknown

TransmissionType #

ValueLabel
Ffax
Hhl7
Mmanual

SpecimenCollectionType #

ValueLabel
Lon location
Ppatient service center
Oother

CourtesyCopyType #

ValueLabel
Aaccount
Ffax
Ppatient

ManualProcessingStatus #

ValueLabel
NEEDS_REVIEWNeeds Review
IN_PROGRESSIn Progress
PROCESSEDProcessed
FLAGGEDFlagged

LabReasonMode #

ValueLabel
MOmonitor
INinvestigate
SFscreen for
UNKunknown

LabTestOrderStatus #

ValueLabel
NEnew
SRstaged for requisition
SEsending
SFsending failed
PRprocessing
PFprocessing failed
REreceived
RVreviewed
INinactive