CEP 10: Lab Management Module
Motive
The goal of this enhancement proposal is to create a comprehensive Lab Management module in CARE by extending the current functionality of sample tests. The primary changes involve renaming sample tests to Lab Orders and building a robust system to manage lab orders, samples, investigations, and results. The Lab Orders will be integrated within the existing consultation flow and can span multiple consultations, improving the handling of follow-up investigations and lab processes.
Requirements
1. Lab Orders
- A Lab Order will contain multiple investigations.
- Each Lab Order may require various sample types (e.g., blood, urine).
- Templates of investigation groups (e.g., Biochemistry Test) should be available.
2. Sample Collection and Association
- Multiple sample types (e.g., urine, blood) can be associated with a single Lab Order.
- Samples collected may be associated with several investigations (e.g., a blood sample for CBC and Blood Sugar).
3. Investigation Results
- Results will include:
- Standard Clinical Lab Results with values measured against reference ranges.
- Positive/Negative tests for certain conditions (e.g., pregnancy).
- Reports in plain text for certain tests.
- Microbiology test results with classifications like Sensitive (S), Resistant (R), and others.
- The system should allow the clinician to overwrite the Min-Max range for tests when necessary.
4. Lab Order Workflow
- Stages of Lab Order:
- Creation during or after a consultation.
- Sample collection.
- Sample processing.
- Results entry and review.
- Lab Order closure.
5. Sample Stages
- Sample stages will be modified to include:
- Sample Collected, Sample in Transit, Sample Received, Sample Processing, Sample Tested, and Result Available.
6. User Interface
- Lab Order view to track lab orders, samples, investigations, and results.
- Sample and Investigation views to show multiple investigations per sample.
- Results view to highlight values outside reference ranges.
7. Data Management
- A master data structure will manage sample types and investigations.
- Customizable reference ranges for each investigation, with the ability to override based on test methods.
8. Integration with Existing Modules
- Lab Orders will link to consultations and extend across multiple consultations for follow-up.
- Notifications for pending or overdue Lab Orders and investigations.
9. Extensibility: Integration with External LIMS
- Initially, the system will operate manually within CARE, with Lab Orders, samples, and results managed directly through the UI.
- A plugin will later be developed to integrate CARE with external Laboratory Information Management Systems (LIMS).
- The plugin will handle CRUD events for Lab Orders and automatically update external LIMS.
- Webhooks will be used to receive test results from LIMS and update CARE in real-time.
10. Future Considerations
- Support for custom lab investigations.
- Advanced analytics and reporting on lab trends.
User Stories
1. A Doctor (usertype) must be able to create and view Lab orders and view Lab results
- A doctor must be able to generate a Lab Order. This will be a component within the Computerised Physician Order Entry (CPOE) within Treatment Plan
- Doctor must be able to choose from a list of tests offered by the associated Lab/Labs or order tests outside of it (which patient may have to avail at an external Lab ). Therefore the list of tests offered by all associated labs showed be labelled for identification)
- Lab orders may be marked as to be done once at a specific time or to be performed repeatedly at specific intervals
- Once the lab order is placed, the Doctor must be able to track the status of the Lab order. Such status could be Order placed/ Sample collected/ Sample sent to Lab/ Sample received at Lab/ Test ongoing/ Sample rejected/ Result under review (before the pathologist)/ Invalid result/ Completed/ Cancelled.
- The doctor must be able to see “time to completion” based on the list maintained by the Lab for time required to complete tests.
- The doctor must be able to “Add on ” additional tests to existing Samples before the testing on the sample actually commences at the Lab.
- The doctor must be able to Cancel tests before the testing on the sample actually commences at the Lab.
- Once the Testing is done and the result is dispatched by the Lab, the doctor must get a notification. Additional alerts may be added if the lab results are outside of normal values.
- The doctor may request the Pathologist for clarification or advice based on the result.
2. A user (Nurse/Doctor/Phlebotomist) with the role of “Sample collection agents” will collect and send Samples as mentioned in the Lab order
- The collection agents for various kinds of Lab tests may be different users. For example a Nurse may be the one drawing blood for simple blood analysis while a doctor may be the one collecting a tissue sample for Biopsy. The Lab order will have the “Sample collection agent” tagged to the order.
- The relevant user must get a notification when a Lab order is created.
- The user must be able to see the list of samples to be collected from the patient
- The user must be able to view and print labels with bar codes to affix on each Sample. Sample ID autogenerated by system should be clearly visible on the label.
- The user must be able to mark status of a Lab order as “Sample Collected” and either scan the Bar code on Sample or type in Sample ID to validate.
- Sometime, more than 1 test within the Lab order may be done using the same Sample. In those cases, the tests within the Lab order must be automatically linked into a group, and the status of “Sample Collected” must be applied on the group.
- The Samples from various patients would be grouped together based on Type of Sample/ Lab where testing is done etc. This group is marked on the software as a “Sample Batch”. A Sample batch ID is generated and printed on a Label to be affixed on the packaging contain all the samples within the batch.
- The Samples are then dispatched to a collection centre and finally to the Lab. At each of these points, the Sample Batch ID (if not grouped, then Sample ID) is scanned/ typed in to track the movement of the Sample.
3. A Lab Technician will receive the Samples, conduct the tests and dispatch the results
-
Once the Sample is received at the Lab, the Lab technical will scan/type in the Sample Batch ID/Sample ID to mark the status as “Samples received at Lab”.
-
In case the samples are damaged or contaminated, the Lab technician will mark status as “Sample rejected”
-
Labs will have 3 types of analysers- Manuel, Semi-automatic and Fully Automatic.
Manuel Analyser: The Lab technician will scan/type in the Sample ID to view all the tests to be performed on the Sample. When the testing commences, he/she will manually update the status on the Lab order to “Testing ongoing”. When the result is derived from the analyser, the Lab technician will upload the same against the relevant lab order.
Semi-Automatic Analyser: The Lab technician will scan/type in the Sample ID to view all the tests to be performed on the Sample. When the testing commences, he/she will manually update the status on the Lab order to “Testing ongoing”. Once the test is complete, the Lab analyser automatically transmits the results to the Software and logs in against the relevant Lab Order
Fully Automatic Analyser: The Lab technician only places the samples in the analyser. The Analyser automatically fetches the list of tests to be performed from the software, marks the status as “Test Ongoing” and performs the tests. Once the test is complete, the Lab analyser automatically transmits the results to the Software and logs in against the relevant Lab Order.
-
The Lab technician then reviews all the results and either marks them for
- review by the Pathologist,
- orders for re-test,
- marks result as “Invalid”
- dispatches the results.
The status on Lab order updates accordingly. Once the result is dispatched, the test is Complete and the Lab Order is closed as complete.
4. Pathologist to review results and provide remarks
- Pathologist will see the list of Lab results marked for review by the Lab technician. There may be notes added by the Lab technicians with remarks or questions
- The Pathologist must be able to contact the Doctor (who ordered the test) to discuss the case.
- Pathologist can do the following: a) Dispatch result with a Note/Remake b) Order for re-test c) Mark result as “Invalid”
- Pathologist must be able to view all Lab orders relevant to that Lab
5. Billing ( user) may need to approve Lab orders before Sample is collected
- Once the doctor orders a Lab test, the patient may need to settle the bill for it before it is approved for further processing. An additional step of the Billing manager/ staff deployed to billing counter receiving the amount and marking the bill as paid may be needed. This must connect to the billing module.
- A hospital may choose to configure this flow as needed. This may be different for IP and OP patients. For OP patients, the bill may have to be settled before the lab order is processed, while for IP patients, the bill is simply added to the patients billing account or the amount may be deductable against a deposit pre-paid by the patient.
- Govt. hospitals may have free Lab service where this step is not necessary. This is a configurable feature at the hospital level.
Proposed Solution
1. Models
from django.db import models
from care.user.models import User
from care.facility.models import BaseModel, Schedule, PatientConsultation
class SampleType(BaseModel):
"""
Model to store the type of sample that can be collected for an investigation. For example, Blood, Urine, etc.
The data in this model is expected to be static and should be populated via fixtures or migrations.
"""
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class InvestigationGroup(BaseModel):
"""
Model to store the group of investigations. For example, Hematology, Biochemistry, etc.
The data in this model is expected to be static and should be populated via fixtures or migrations.
"""
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class Investigation(BaseModel):
"""
Model to store the investigations that can be ordered for a patient. For example, Hemoglobin Count, Blood Sugar Level, etc. This model also stores the sample type required for the investigation, the unit of measurement, the result type, the default min and max values for the result (if the result type is NUMERIC), and the choices for the result (if the result type is CHOICE).
The data in this model is expected to be static and should be populated via fixtures or migrations.
"""
name = models.CharField(max_length=200, unique=True)
group = models.ForeignKey(InvestigationGroup, on_delete=models.PROTECT)
sample_type = models.ForeignKey(SampleType, on_delete=models.PROTECT)
unit = models.CharField(max_length=50, blank=True, null=True)
result_type = models.CharField(
max_length=20,
choices=[
("NUMERIC", "Numeric"),
("TEXT", "Text"),
("CHOICE", "Choice"),
],
)
default_min_value = models.FloatField(blank=True, null=True)
default_max_value = models.FloatField(blank=True, null=True)
choices = models.TextField(blank=True, null=True)
def __str__(self):
return f"{self.name} ({self.group.name})"
class LabOrder(BaseModel):
"""
Model to store the lab orders ordered by the doctor for the patient. The lab order can be for one or more investigations. The lab order can be for a single consultation or can span across multiple consultations. The lab order can be a one-time order or a repeating order.
"""
# if spanning across multiple consultation is linear, consultations in b/w are not skipped
starting_consultation = models.ForeignKey(
PatientConsultation, on_delete=models.PROTECT, related_name="started_lab_orders"
)
ending_consultation = models.ForeignKey(
PatientConsultation,
on_delete=models.PROTECT,
related_name="ended_lab_orders",
null=True,
blank=True,
)
# else
# one to many relation with LabOrderConsultation
status = models.CharField(
max_length=20,
choices=[
("CREATED", "Order Placed"),
("PROCESSING", "Processing"),
("COMPLETED", "Completed"),
("CANCELLED", "Cancelled"),
],
default="CREATED",
)
ordering_doctor = models.ForeignKey(
User, on_delete=models.PROTECT, related_name="ordered_lab_tests"
)
# Schedule for repeating the lab order
schedule = models.ForeignKey(
Schedule, on_delete=models.PROTECT, null=True, blank=True
)
# or
is_repeat = models.BooleanField(default=False)
repeat_interval = models.DurationField(null=True, blank=True)
next_repeat_date = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"Lab Order {self.external_id} for {self.patient}"
class LabOrderConsultation(BaseModel):
"""
Model to store the consultations for which the lab order is applicable. This model enables the lab order to span across multiple consultations.
"""
lab_order = models.ForeignKey(
LabOrder, on_delete=models.CASCADE, related_name="consultations"
)
consultation = models.ForeignKey(PatientConsultation, on_delete=models.PROTECT)
is_originating = models.BooleanField(default=False)
class Meta:
unique_together = ("lab_order", "consultation")
def __str__(self):
return f"Lab Order {self.lab_order.external_id} - Consultation {self.consultation.id}"
class LabOrderSample(BaseModel):
"""
Model to store the samples needed for the lab order. The sample can be in different states like Pending, Collected, In Transit, Received, Rejected, Processing, Under Review, Invalid, Completed, Cancelled.
The LabOrderSample instances can be created automatically based on the investigations ordered in the LabOrder, all the unique SampleType instances in the LabOrder.investigations can be created as LabOrderSample instances.
"""
lab_order = models.ForeignKey(
LabOrder, on_delete=models.CASCADE, related_name="samples"
)
sample_type = models.ForeignKey(SampleType, on_delete=models.PROTECT)
status = models.CharField(
max_length=20,
choices=[
("PENDING", "Pending"),
("COLLECTED", "Sample Collected"),
("IN_TRANSIT", "Sample sent to Lab"),
("RECEIVED", "Sample received at Lab"),
("REJECTED", "Sample Rejected"),
("PROCESSING", "Test Ongoing"),
("UNDER_REVIEW", "Result under review"),
("INVALID", "Invalid Result"),
("COMPLETED", "Test Completed"),
("CANCELLED", "Test Cancelled"),
],
default="PENDING",
)
collected_by = models.ForeignKey(
User, on_delete=models.PROTECT, null=True, blank=True
)
collected_date = models.DateTimeField(null=True, blank=True)
barcode = models.CharField(max_length=100, unique=True, null=True, blank=True)
def __str__(self):
return f"{self.sample_type} for {self.lab_order}"
class LabOrderInvestigation(BaseModel):
"""
Model to store the investigations ordered in the lab order. The investigations can be in different states like Pending, In Progress, Completed, Cancelled.
"""
lab_order = models.ForeignKey(
LabOrder, on_delete=models.CASCADE, related_name="investigations"
)
investigation = models.ForeignKey(Investigation, on_delete=models.CASCADE)
lab_order_sample = models.ForeignKey(
LabOrderSample, on_delete=models.CASCADE, related_name="investigations"
)
status = models.CharField(
max_length=20,
choices=[
("PENDING", "Pending"),
("IN_PROGRESS", "In Progress"),
("COMPLETED", "Completed"),
("CANCELLED", "Cancelled"),
],
default="PENDING",
)
notes = models.TextField(blank=True, null=True)
class Meta:
unique_together = ("lab_order", "investigation")
def __str__(self):
return f"{self.investigation.name} for {self.lab_order}"
class InvestigationResult(BaseModel):
"""
Model to store the results for the investigations. The results can be in different states like Received, Under Review, Invalid, Accepted.
"""
lab_order_investigation = models.ForeignKey(
LabOrderInvestigation, on_delete=models.PROTECT, related_name="results"
)
result_value = models.TextField()
is_abnormal = models.BooleanField(default=False)
status = models.CharField(
max_length=20,
choices=[
("RECEIVED", "Result Received"),
("UNDER_REVIEW", "Result under review"),
("INVALID", "Invalid Result"),
("ACCEPTED", "Result Accepted"),
],
default="PENDING",
)
performed_by = models.ForeignKey(
User, on_delete=models.PROTECT, related_name="performed_tests"
)
verified_by = models.ForeignKey(
User,
on_delete=models.PROTECT,
related_name="verified_tests",
null=True,
blank=True,
)
notes = models.TextField(blank=True, null=True)
custom_min_value = models.FloatField(blank=True, null=True)
custom_max_value = models.FloatField(blank=True, null=True)
def __str__(self):
return f"Result for {self.lab_order_investigation.investigation.name} in {self.lab_order_investigation.lab_order}"
# ? Can we create a new instance of LabOrder / LabOrderInvestigation for a retesting a sample?