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 testsLabTest: Individual tests within a lab order, tracking status from creation through processingLabReport: Contains the results returned from the lab, including all values and associated metadataLabReportRemark: Report-level remarks from lab personnel, accessible viaLabReport.remarksLabValue: Individual test results within a lab report, including values, units, and reference rangesLabReview: Tracks the clinical review process for lab results, including provider comments and patient communicationDiagnosticReport: TheDiagnosticReportlinked to aLabReport, accessible viaLabReport.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:LabTestrows created when aLabOrderis placed. These represent the tests that were requested and are not associated with anyLabValuerecords.result_tests:LabTestrows created for the results themselves. For FHIRDiagnosticReportand Health Gorilla ingested reports,LabValuerecords 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 Name | Type |
|---|---|
| id | UUID |
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| review_mode | DocumentReviewMode |
| junked | Boolean |
| requires_signature | Boolean |
| assigned_date | DateTime |
| patient | Patient |
| transmission_type | TransmissionType |
| for_test_only | Boolean |
| external_id | String |
| version | Integer |
| requisition_number | String |
| review | LabReview |
| original_date | DateTime |
| date_performed | DateTime |
| custom_document_name | String |
| originator | CanvasUser |
| committer | CanvasUser |
| entered_in_error | CanvasUser |
| deleted | Boolean |
| values | LabValue[] |
| tests | LabTest[] |
| remarks | LabReportRemark[] |
| diagnostic_reports | DiagnosticReport[] |
LabReportRemark #
| Field Name | Type |
|---|---|
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| report | LabReport |
| comment | String |
DiagnosticReport #
The DiagnosticReport linked to a LabReport. The id is the DiagnosticReport id.
| Field Name | Type |
|---|---|
| id | UUID |
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| status | DiagnosticReportStatus |
| subject | Patient |
| lab | LabReport |
LabReview #
| Field Name | Type |
|---|---|
| id | UUID |
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| originator | CanvasUser |
| deleted | Boolean |
| committer | CanvasUser |
| entered_in_error | CanvasUser |
| internal_comment | String |
| message_to_patient | String |
| status | String |
| note | Note |
| patient | Patient |
| patient_communication_method | String |
| reports | LabReport[] |
| tests | LabTest[] |
LabValue #
| Field Name | Type |
|---|---|
| id | UUID |
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| report | LabReport |
| value | String |
| units | String |
| abnormal_flag | String |
| reference_range | String |
| low_threshold | String |
| high_threshold | String |
| comment | String |
| observation_status | String |
| test | LabTest |
| codings | LabValueCoding[] |
LabValueCoding #
| Field Name | Type |
|---|---|
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| value | LabValue |
| code | String |
| name | String |
| system | String |
LabOrder #
| Field Name | Type |
|---|---|
| id | UUID |
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| originator | CanvasUser |
| deleted | Boolean |
| committer | CanvasUser |
| entered_in_error | CanvasUser |
| patient | Patient |
| note | Note |
| ontology_lab_partner | String |
| ordering_provider | Staff |
| comment | String |
| requisition_number | String |
| is_patient_bill | Boolean |
| date_ordered | DateTime |
| fasting_status | Boolean |
| specimen_collection_type | SpecimenCollectionType |
| transmission_type | TransmissionType |
| courtesy_copy_type | CourtesyCopyType |
| courtesy_copy_number | String |
| courtesy_copy_text | String |
| parent_order | LabOrder |
| healthgorilla_id | String |
| manual_processing_status | ManualProcessingStatus |
| manual_processing_comment | String |
| labcorp_abn_url | URL |
| reasons | LabOrderReason[] |
| tests | LabTest[] |
| reports | LabReport[] |
LabOrderReason #
| Field Name | Type |
|---|---|
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| originator | CanvasUser |
| deleted | Boolean |
| committer | CanvasUser |
| entered_in_error | CanvasUser |
| order | LabOrder |
| mode | LabReasonMode |
| reason_conditions | LabOrderReasonCondition[] |
LabOrderReasonCondition #
| Field Name | Type |
|---|---|
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| originator | CanvasUser |
| deleted | Boolean |
| committer | CanvasUser |
| entered_in_error | CanvasUser |
| reason | LabOrderReason |
| condition | Condition |
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 Name | Type |
|---|---|
| id | UUID |
| dbid | Integer |
| created | DateTime |
| modified | DateTime |
| ontology_test_name | String |
| ontology_test_code | String |
| status | LabTestOrderStatus |
| report | LabReport |
| specimen_type | String |
| specimen_source_code | String |
| specimen_source_description | String |
| specimen_source_coding_system | String |
| order | LabOrder |
| aoe_code | String |
| procedure_class | String |
| values | LabValue[] |
Enumeration types #
DiagnosticReportStatus #
| Value | Label |
|---|---|
REGISTERED | Registered |
PARTIAL | Partial |
PRELIMINARY | Preliminary |
FINAL | Final |
AMENDED | Amended |
CORRECTED | Corrected |
APPENDED | Appended |
CANCELLED | Cancelled |
ENTERED_IN_ERROR | Entered-in-error |
UNKNOWN | Unknown |
TransmissionType #
| Value | Label |
|---|---|
| F | fax |
| H | hl7 |
| M | manual |
SpecimenCollectionType #
| Value | Label |
|---|---|
| L | on location |
| P | patient service center |
| O | other |
CourtesyCopyType #
| Value | Label |
|---|---|
| A | account |
| F | fax |
| P | patient |
ManualProcessingStatus #
| Value | Label |
|---|---|
| NEEDS_REVIEW | Needs Review |
| IN_PROGRESS | In Progress |
| PROCESSED | Processed |
| FLAGGED | Flagged |
LabReasonMode #
| Value | Label |
|---|---|
| MO | monitor |
| IN | investigate |
| SF | screen for |
| UNK | unknown |
LabTestOrderStatus #
| Value | Label |
|---|---|
| NE | new |
| SR | staged for requisition |
| SE | sending |
| SF | sending failed |
| PR | processing |
| PF | processing failed |
| RE | received |
| RV | reviewed |
| IN | inactive |