1472 lines
54 KiB
Python
1472 lines
54 KiB
Python
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
|
|
|
import django.db.models.deletion
|
|
import django.utils.timezone
|
|
import uuid
|
|
from django.conf import settings
|
|
from django.db import migrations, models
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
initial = True
|
|
|
|
dependencies = [
|
|
("core", "0001_initial"),
|
|
("emr", "0001_initial"),
|
|
("patients", "0001_initial"),
|
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
]
|
|
|
|
operations = [
|
|
migrations.CreateModel(
|
|
name="LabOrder",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"order_id",
|
|
models.UUIDField(
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
help_text="Unique order identifier",
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"order_number",
|
|
models.CharField(
|
|
help_text="Lab order number", max_length=20, unique=True
|
|
),
|
|
),
|
|
(
|
|
"order_datetime",
|
|
models.DateTimeField(
|
|
default=django.utils.timezone.now,
|
|
help_text="Date and time order was placed",
|
|
),
|
|
),
|
|
(
|
|
"priority",
|
|
models.CharField(
|
|
choices=[
|
|
("ROUTINE", "Routine"),
|
|
("URGENT", "Urgent"),
|
|
("STAT", "STAT"),
|
|
("ASAP", "ASAP"),
|
|
("TIMED", "Timed"),
|
|
],
|
|
default="ROUTINE",
|
|
help_text="Order priority",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"clinical_indication",
|
|
models.TextField(
|
|
blank=True, help_text="Clinical indication for tests", null=True
|
|
),
|
|
),
|
|
(
|
|
"diagnosis_code",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="ICD-10 diagnosis code",
|
|
max_length=20,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"clinical_notes",
|
|
models.TextField(blank=True, help_text="Clinical notes", null=True),
|
|
),
|
|
(
|
|
"collection_datetime",
|
|
models.DateTimeField(
|
|
blank=True,
|
|
help_text="Requested collection date and time",
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"collection_location",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Collection location",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"fasting_status",
|
|
models.CharField(
|
|
choices=[
|
|
("FASTING", "Fasting"),
|
|
("NON_FASTING", "Non-Fasting"),
|
|
("UNKNOWN", "Unknown"),
|
|
],
|
|
default="UNKNOWN",
|
|
help_text="Patient fasting status",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"status",
|
|
models.CharField(
|
|
choices=[
|
|
("PENDING", "Pending"),
|
|
("SCHEDULED", "Scheduled"),
|
|
("COLLECTED", "Collected"),
|
|
("IN_PROGRESS", "In Progress"),
|
|
("COMPLETED", "Completed"),
|
|
("CANCELLED", "Cancelled"),
|
|
("ON_HOLD", "On Hold"),
|
|
],
|
|
default="PENDING",
|
|
help_text="Order status",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"special_instructions",
|
|
models.TextField(
|
|
blank=True,
|
|
help_text="Special instructions for collection or processing",
|
|
null=True,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
(
|
|
"encounter",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="Related encounter",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="lab_orders",
|
|
to="emr.encounter",
|
|
),
|
|
),
|
|
(
|
|
"ordering_provider",
|
|
models.ForeignKey(
|
|
help_text="Ordering provider",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="ordered_lab_tests",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"patient",
|
|
models.ForeignKey(
|
|
help_text="Patient",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="lab_orders",
|
|
to="patients.patientprofile",
|
|
),
|
|
),
|
|
(
|
|
"tenant",
|
|
models.ForeignKey(
|
|
help_text="Organization tenant",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="lab_orders",
|
|
to="core.tenant",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Lab Order",
|
|
"verbose_name_plural": "Lab Orders",
|
|
"db_table": "laboratory_lab_order",
|
|
"ordering": ["-order_datetime"],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name="LabTest",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"test_id",
|
|
models.UUIDField(
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
help_text="Unique test identifier",
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"test_code",
|
|
models.CharField(help_text="Laboratory test code", max_length=20),
|
|
),
|
|
("test_name", models.CharField(help_text="Test name", max_length=200)),
|
|
(
|
|
"test_description",
|
|
models.TextField(
|
|
blank=True, help_text="Detailed test description", null=True
|
|
),
|
|
),
|
|
(
|
|
"loinc_code",
|
|
models.CharField(
|
|
blank=True, help_text="LOINC code", max_length=20, null=True
|
|
),
|
|
),
|
|
(
|
|
"cpt_code",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="CPT code for billing",
|
|
max_length=10,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"snomed_code",
|
|
models.CharField(
|
|
blank=True, help_text="SNOMED CT code", max_length=20, null=True
|
|
),
|
|
),
|
|
(
|
|
"test_category",
|
|
models.CharField(
|
|
choices=[
|
|
("CHEMISTRY", "Chemistry"),
|
|
("HEMATOLOGY", "Hematology"),
|
|
("MICROBIOLOGY", "Microbiology"),
|
|
("IMMUNOLOGY", "Immunology"),
|
|
("MOLECULAR", "Molecular"),
|
|
("PATHOLOGY", "Pathology"),
|
|
("TOXICOLOGY", "Toxicology"),
|
|
("ENDOCRINOLOGY", "Endocrinology"),
|
|
("CARDIOLOGY", "Cardiology"),
|
|
("ONCOLOGY", "Oncology"),
|
|
("GENETICS", "Genetics"),
|
|
("COAGULATION", "Coagulation"),
|
|
("URINALYSIS", "Urinalysis"),
|
|
("OTHER", "Other"),
|
|
],
|
|
help_text="Test category",
|
|
max_length=50,
|
|
),
|
|
),
|
|
(
|
|
"test_type",
|
|
models.CharField(
|
|
choices=[
|
|
("QUANTITATIVE", "Quantitative"),
|
|
("QUALITATIVE", "Qualitative"),
|
|
("SEMI_QUANTITATIVE", "Semi-Quantitative"),
|
|
("CULTURE", "Culture"),
|
|
("MICROSCOPY", "Microscopy"),
|
|
("MOLECULAR", "Molecular"),
|
|
("IMMUNOASSAY", "Immunoassay"),
|
|
("OTHER", "Other"),
|
|
],
|
|
help_text="Type of test",
|
|
max_length=30,
|
|
),
|
|
),
|
|
(
|
|
"specimen_type",
|
|
models.CharField(
|
|
choices=[
|
|
("BLOOD", "Blood"),
|
|
("SERUM", "Serum"),
|
|
("PLASMA", "Plasma"),
|
|
("URINE", "Urine"),
|
|
("STOOL", "Stool"),
|
|
("CSF", "Cerebrospinal Fluid"),
|
|
("SPUTUM", "Sputum"),
|
|
("SWAB", "Swab"),
|
|
("TISSUE", "Tissue"),
|
|
("FLUID", "Body Fluid"),
|
|
("SALIVA", "Saliva"),
|
|
("HAIR", "Hair"),
|
|
("NAIL", "Nail"),
|
|
("OTHER", "Other"),
|
|
],
|
|
help_text="Required specimen type",
|
|
max_length=30,
|
|
),
|
|
),
|
|
(
|
|
"specimen_volume",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Required specimen volume",
|
|
max_length=50,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"collection_container",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Collection container type",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"collection_instructions",
|
|
models.TextField(
|
|
blank=True,
|
|
help_text="Specimen collection instructions",
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"processing_time",
|
|
models.PositiveIntegerField(help_text="Processing time in minutes"),
|
|
),
|
|
(
|
|
"turnaround_time",
|
|
models.PositiveIntegerField(
|
|
help_text="Expected turnaround time in hours"
|
|
),
|
|
),
|
|
(
|
|
"stat_available",
|
|
models.BooleanField(
|
|
default=False, help_text="STAT processing available"
|
|
),
|
|
),
|
|
(
|
|
"stat_turnaround_time",
|
|
models.PositiveIntegerField(
|
|
blank=True, help_text="STAT turnaround time in hours", null=True
|
|
),
|
|
),
|
|
(
|
|
"storage_temperature",
|
|
models.CharField(
|
|
choices=[
|
|
("ROOM_TEMP", "Room Temperature"),
|
|
("REFRIGERATED", "Refrigerated (2-8°C)"),
|
|
("FROZEN", "Frozen (-20°C)"),
|
|
("DEEP_FROZEN", "Deep Frozen (-80°C)"),
|
|
("ICE", "On Ice"),
|
|
("AMBIENT", "Ambient"),
|
|
],
|
|
default="ROOM_TEMP",
|
|
help_text="Storage temperature requirement",
|
|
max_length=30,
|
|
),
|
|
),
|
|
(
|
|
"transport_requirements",
|
|
models.TextField(
|
|
blank=True, help_text="Transport requirements", null=True
|
|
),
|
|
),
|
|
(
|
|
"stability_time",
|
|
models.PositiveIntegerField(
|
|
blank=True,
|
|
help_text="Specimen stability time in hours",
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"clinical_significance",
|
|
models.TextField(
|
|
blank=True, help_text="Clinical significance of test", null=True
|
|
),
|
|
),
|
|
(
|
|
"indications",
|
|
models.TextField(
|
|
blank=True, help_text="Clinical indications", null=True
|
|
),
|
|
),
|
|
(
|
|
"contraindications",
|
|
models.TextField(
|
|
blank=True, help_text="Contraindications", null=True
|
|
),
|
|
),
|
|
(
|
|
"patient_preparation",
|
|
models.TextField(
|
|
blank=True,
|
|
help_text="Patient preparation instructions",
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"fasting_required",
|
|
models.BooleanField(default=False, help_text="Fasting required"),
|
|
),
|
|
(
|
|
"fasting_hours",
|
|
models.PositiveIntegerField(
|
|
blank=True, help_text="Required fasting hours", null=True
|
|
),
|
|
),
|
|
(
|
|
"methodology",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Test methodology",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"analyzer",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Analyzer/instrument used",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"qc_frequency",
|
|
models.CharField(
|
|
choices=[
|
|
("DAILY", "Daily"),
|
|
("WEEKLY", "Weekly"),
|
|
("MONTHLY", "Monthly"),
|
|
("PER_BATCH", "Per Batch"),
|
|
("CONTINUOUS", "Continuous"),
|
|
],
|
|
default="DAILY",
|
|
help_text="Quality control frequency",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"cost",
|
|
models.DecimalField(
|
|
blank=True,
|
|
decimal_places=2,
|
|
help_text="Test cost",
|
|
max_digits=10,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"is_active",
|
|
models.BooleanField(
|
|
default=True, help_text="Test is active and available"
|
|
),
|
|
),
|
|
(
|
|
"is_orderable",
|
|
models.BooleanField(default=True, help_text="Test can be ordered"),
|
|
),
|
|
(
|
|
"department",
|
|
models.CharField(
|
|
choices=[
|
|
("CHEMISTRY", "Chemistry"),
|
|
("HEMATOLOGY", "Hematology"),
|
|
("MICROBIOLOGY", "Microbiology"),
|
|
("IMMUNOLOGY", "Immunology"),
|
|
("MOLECULAR", "Molecular"),
|
|
("PATHOLOGY", "Pathology"),
|
|
("BLOOD_BANK", "Blood Bank"),
|
|
("CYTOLOGY", "Cytology"),
|
|
("HISTOLOGY", "Histology"),
|
|
("TOXICOLOGY", "Toxicology"),
|
|
],
|
|
help_text="Laboratory department",
|
|
max_length=50,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
(
|
|
"created_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="User who created the test",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="created_lab_tests",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"tenant",
|
|
models.ForeignKey(
|
|
help_text="Organization tenant",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="lab_tests",
|
|
to="core.tenant",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Lab Test",
|
|
"verbose_name_plural": "Lab Tests",
|
|
"db_table": "laboratory_lab_test",
|
|
"ordering": ["test_name"],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name="LabResult",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"result_id",
|
|
models.UUIDField(
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
help_text="Unique result identifier",
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"result_value",
|
|
models.TextField(
|
|
blank=True, help_text="Test result value", null=True
|
|
),
|
|
),
|
|
(
|
|
"result_unit",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Result unit of measure",
|
|
max_length=20,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"result_type",
|
|
models.CharField(
|
|
choices=[
|
|
("NUMERIC", "Numeric"),
|
|
("TEXT", "Text"),
|
|
("CODED", "Coded"),
|
|
("NARRATIVE", "Narrative"),
|
|
],
|
|
default="NUMERIC",
|
|
help_text="Type of result",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"reference_range",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Reference range",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"abnormal_flag",
|
|
models.CharField(
|
|
blank=True,
|
|
choices=[
|
|
("N", "Normal"),
|
|
("H", "High"),
|
|
("L", "Low"),
|
|
("HH", "Critical High"),
|
|
("LL", "Critical Low"),
|
|
("A", "Abnormal"),
|
|
],
|
|
help_text="Abnormal flag",
|
|
max_length=10,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"is_critical",
|
|
models.BooleanField(
|
|
default=False, help_text="Result is critical value"
|
|
),
|
|
),
|
|
(
|
|
"critical_called",
|
|
models.BooleanField(
|
|
default=False, help_text="Critical value was called to provider"
|
|
),
|
|
),
|
|
(
|
|
"critical_called_datetime",
|
|
models.DateTimeField(
|
|
blank=True,
|
|
help_text="Date and time critical value was called",
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"critical_called_to",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Person critical value was called to",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"analyzed_datetime",
|
|
models.DateTimeField(
|
|
blank=True, help_text="Date and time analyzed", null=True
|
|
),
|
|
),
|
|
(
|
|
"analyzer",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Analyzer/instrument used",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"verified",
|
|
models.BooleanField(
|
|
default=False, help_text="Result has been verified"
|
|
),
|
|
),
|
|
(
|
|
"verified_datetime",
|
|
models.DateTimeField(
|
|
blank=True, help_text="Date and time of verification", null=True
|
|
),
|
|
),
|
|
(
|
|
"status",
|
|
models.CharField(
|
|
choices=[
|
|
("PENDING", "Pending"),
|
|
("IN_PROGRESS", "In Progress"),
|
|
("COMPLETED", "Completed"),
|
|
("VERIFIED", "Verified"),
|
|
("AMENDED", "Amended"),
|
|
("CANCELLED", "Cancelled"),
|
|
],
|
|
default="PENDING",
|
|
help_text="Result status",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"technician_comments",
|
|
models.TextField(
|
|
blank=True, help_text="Technician comments", null=True
|
|
),
|
|
),
|
|
(
|
|
"pathologist_comments",
|
|
models.TextField(
|
|
blank=True, help_text="Pathologist comments", null=True
|
|
),
|
|
),
|
|
(
|
|
"qc_passed",
|
|
models.BooleanField(
|
|
default=True, help_text="Quality control passed"
|
|
),
|
|
),
|
|
(
|
|
"qc_notes",
|
|
models.TextField(
|
|
blank=True, help_text="Quality control notes", null=True
|
|
),
|
|
),
|
|
(
|
|
"reported_datetime",
|
|
models.DateTimeField(
|
|
blank=True,
|
|
help_text="Date and time result was reported",
|
|
null=True,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
(
|
|
"analyzed_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="Lab technician who analyzed",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="analyzed_results",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"order",
|
|
models.ForeignKey(
|
|
help_text="Related lab order",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="results",
|
|
to="laboratory.laborder",
|
|
),
|
|
),
|
|
(
|
|
"verified_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="Lab professional who verified result",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="verified_results",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"test",
|
|
models.ForeignKey(
|
|
help_text="Lab test",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="results",
|
|
to="laboratory.labtest",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Lab Result",
|
|
"verbose_name_plural": "Lab Results",
|
|
"db_table": "laboratory_lab_result",
|
|
"ordering": ["-analyzed_datetime"],
|
|
},
|
|
),
|
|
migrations.AddField(
|
|
model_name="laborder",
|
|
name="tests",
|
|
field=models.ManyToManyField(
|
|
help_text="Ordered tests",
|
|
related_name="orders",
|
|
to="laboratory.labtest",
|
|
),
|
|
),
|
|
migrations.CreateModel(
|
|
name="QualityControl",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"qc_id",
|
|
models.UUIDField(
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
help_text="Unique QC identifier",
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"control_material",
|
|
models.CharField(help_text="Control material name", max_length=100),
|
|
),
|
|
(
|
|
"control_lot",
|
|
models.CharField(help_text="Control lot number", max_length=50),
|
|
),
|
|
(
|
|
"control_level",
|
|
models.CharField(
|
|
choices=[
|
|
("NORMAL", "Normal"),
|
|
("LOW", "Low"),
|
|
("HIGH", "High"),
|
|
("CRITICAL", "Critical"),
|
|
],
|
|
help_text="Control level",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"target_value",
|
|
models.DecimalField(
|
|
decimal_places=6, help_text="Target value", max_digits=15
|
|
),
|
|
),
|
|
(
|
|
"acceptable_range_low",
|
|
models.DecimalField(
|
|
decimal_places=6,
|
|
help_text="Acceptable range low",
|
|
max_digits=15,
|
|
),
|
|
),
|
|
(
|
|
"acceptable_range_high",
|
|
models.DecimalField(
|
|
decimal_places=6,
|
|
help_text="Acceptable range high",
|
|
max_digits=15,
|
|
),
|
|
),
|
|
(
|
|
"run_datetime",
|
|
models.DateTimeField(help_text="Date and time of QC run"),
|
|
),
|
|
(
|
|
"observed_value",
|
|
models.DecimalField(
|
|
decimal_places=6, help_text="Observed value", max_digits=15
|
|
),
|
|
),
|
|
(
|
|
"status",
|
|
models.CharField(
|
|
choices=[
|
|
("PASSED", "Passed"),
|
|
("FAILED", "Failed"),
|
|
("WARNING", "Warning"),
|
|
("PENDING", "Pending"),
|
|
],
|
|
help_text="QC status",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"analyzer",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Analyzer/instrument used",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"comments",
|
|
models.TextField(blank=True, help_text="QC comments", null=True),
|
|
),
|
|
(
|
|
"corrective_action",
|
|
models.TextField(
|
|
blank=True, help_text="Corrective action taken", null=True
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
(
|
|
"performed_by",
|
|
models.ForeignKey(
|
|
help_text="Staff who performed QC",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="performed_qc",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"result",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="quality_controls",
|
|
to="laboratory.labresult",
|
|
),
|
|
),
|
|
(
|
|
"reviewed_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="Staff who reviewed QC",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="reviewed_qc",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"tenant",
|
|
models.ForeignKey(
|
|
help_text="Organization tenant",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="lab_quality_controls",
|
|
to="core.tenant",
|
|
),
|
|
),
|
|
(
|
|
"test",
|
|
models.ForeignKey(
|
|
help_text="Lab test",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="quality_controls",
|
|
to="laboratory.labtest",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Quality Control",
|
|
"verbose_name_plural": "Quality Controls",
|
|
"db_table": "laboratory_quality_control",
|
|
"ordering": ["-run_datetime"],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name="ReferenceRange",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"range_id",
|
|
models.UUIDField(
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
help_text="Unique range identifier",
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"gender",
|
|
models.CharField(
|
|
choices=[("M", "Male"), ("F", "Female"), ("ALL", "All")],
|
|
default="ALL",
|
|
help_text="Gender",
|
|
max_length=10,
|
|
),
|
|
),
|
|
(
|
|
"age_min",
|
|
models.PositiveIntegerField(
|
|
blank=True, help_text="Minimum age in years", null=True
|
|
),
|
|
),
|
|
(
|
|
"age_max",
|
|
models.PositiveIntegerField(
|
|
blank=True, help_text="Maximum age in years", null=True
|
|
),
|
|
),
|
|
(
|
|
"range_low",
|
|
models.DecimalField(
|
|
blank=True,
|
|
decimal_places=6,
|
|
help_text="Range low value",
|
|
max_digits=15,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"range_high",
|
|
models.DecimalField(
|
|
blank=True,
|
|
decimal_places=6,
|
|
help_text="Range high value",
|
|
max_digits=15,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"range_text",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Text description of range",
|
|
max_length=200,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"critical_low",
|
|
models.DecimalField(
|
|
blank=True,
|
|
decimal_places=6,
|
|
help_text="Critical low value",
|
|
max_digits=15,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"critical_high",
|
|
models.DecimalField(
|
|
blank=True,
|
|
decimal_places=6,
|
|
help_text="Critical high value",
|
|
max_digits=15,
|
|
null=True,
|
|
),
|
|
),
|
|
("unit", models.CharField(help_text="Unit of measure", max_length=20)),
|
|
(
|
|
"is_active",
|
|
models.BooleanField(
|
|
default=True, help_text="Reference range is active"
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
(
|
|
"created_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="User who created the reference range",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="created_reference_ranges",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"test",
|
|
models.ForeignKey(
|
|
help_text="Lab test",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="reference_ranges",
|
|
to="laboratory.labtest",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Reference Range",
|
|
"verbose_name_plural": "Reference Ranges",
|
|
"db_table": "laboratory_reference_range",
|
|
"ordering": ["test", "gender", "age_min"],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name="Specimen",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"specimen_id",
|
|
models.UUIDField(
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
help_text="Unique specimen identifier",
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"specimen_number",
|
|
models.CharField(
|
|
help_text="Specimen accession number",
|
|
max_length=20,
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"specimen_type",
|
|
models.CharField(
|
|
choices=[
|
|
("BLOOD", "Blood"),
|
|
("SERUM", "Serum"),
|
|
("PLASMA", "Plasma"),
|
|
("URINE", "Urine"),
|
|
("STOOL", "Stool"),
|
|
("CSF", "Cerebrospinal Fluid"),
|
|
("SPUTUM", "Sputum"),
|
|
("SWAB", "Swab"),
|
|
("TISSUE", "Tissue"),
|
|
("FLUID", "Body Fluid"),
|
|
("SALIVA", "Saliva"),
|
|
("HAIR", "Hair"),
|
|
("NAIL", "Nail"),
|
|
("OTHER", "Other"),
|
|
],
|
|
help_text="Specimen type",
|
|
max_length=30,
|
|
),
|
|
),
|
|
(
|
|
"container_type",
|
|
models.CharField(
|
|
blank=True, help_text="Container type", max_length=50, null=True
|
|
),
|
|
),
|
|
(
|
|
"volume",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Specimen volume",
|
|
max_length=20,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"collected_datetime",
|
|
models.DateTimeField(help_text="Date and time collected"),
|
|
),
|
|
(
|
|
"collection_site",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Collection site/location",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"collection_method",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Collection method",
|
|
max_length=50,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"quality",
|
|
models.CharField(
|
|
choices=[
|
|
("ACCEPTABLE", "Acceptable"),
|
|
("SUBOPTIMAL", "Suboptimal"),
|
|
("REJECTED", "Rejected"),
|
|
],
|
|
default="ACCEPTABLE",
|
|
help_text="Specimen quality",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"rejection_reason",
|
|
models.CharField(
|
|
blank=True,
|
|
choices=[
|
|
("HEMOLYZED", "Hemolyzed"),
|
|
("CLOTTED", "Clotted"),
|
|
("INSUFFICIENT_VOLUME", "Insufficient Volume"),
|
|
("CONTAMINATED", "Contaminated"),
|
|
("MISLABELED", "Mislabeled"),
|
|
("EXPIRED", "Expired"),
|
|
("IMPROPER_STORAGE", "Improper Storage"),
|
|
("DAMAGED_CONTAINER", "Damaged Container"),
|
|
("OTHER", "Other"),
|
|
],
|
|
help_text="Reason for rejection",
|
|
max_length=100,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"quality_notes",
|
|
models.TextField(
|
|
blank=True, help_text="Quality assessment notes", null=True
|
|
),
|
|
),
|
|
(
|
|
"received_datetime",
|
|
models.DateTimeField(
|
|
blank=True, help_text="Date and time received in lab", null=True
|
|
),
|
|
),
|
|
(
|
|
"storage_location",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Storage location",
|
|
max_length=50,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"storage_temperature",
|
|
models.CharField(
|
|
choices=[
|
|
("ROOM_TEMP", "Room Temperature"),
|
|
("REFRIGERATED", "Refrigerated (2-8°C)"),
|
|
("FROZEN", "Frozen (-20°C)"),
|
|
("DEEP_FROZEN", "Deep Frozen (-80°C)"),
|
|
],
|
|
default="ROOM_TEMP",
|
|
help_text="Storage temperature",
|
|
max_length=30,
|
|
),
|
|
),
|
|
(
|
|
"status",
|
|
models.CharField(
|
|
choices=[
|
|
("COLLECTED", "Collected"),
|
|
("IN_TRANSIT", "In Transit"),
|
|
("RECEIVED", "Received"),
|
|
("PROCESSING", "Processing"),
|
|
("COMPLETED", "Completed"),
|
|
("REJECTED", "Rejected"),
|
|
("DISPOSED", "Disposed"),
|
|
],
|
|
default="COLLECTED",
|
|
help_text="Specimen status",
|
|
max_length=20,
|
|
),
|
|
),
|
|
(
|
|
"chain_of_custody",
|
|
models.JSONField(
|
|
default=list, help_text="Chain of custody tracking"
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
(
|
|
"collected_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="Person who collected specimen",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="collected_specimens",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
(
|
|
"order",
|
|
models.ForeignKey(
|
|
help_text="Related lab order",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="specimens",
|
|
to="laboratory.laborder",
|
|
),
|
|
),
|
|
(
|
|
"received_by",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
help_text="Lab staff who received specimen",
|
|
null=True,
|
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
related_name="received_specimens",
|
|
to=settings.AUTH_USER_MODEL,
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Specimen",
|
|
"verbose_name_plural": "Specimens",
|
|
"db_table": "laboratory_specimen",
|
|
"ordering": ["-collected_datetime"],
|
|
},
|
|
),
|
|
migrations.AddField(
|
|
model_name="labresult",
|
|
name="specimen",
|
|
field=models.ForeignKey(
|
|
help_text="Specimen used for test",
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="results",
|
|
to="laboratory.specimen",
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labtest",
|
|
index=models.Index(
|
|
fields=["tenant", "is_active"], name="laboratory__tenant__dc3ed6_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labtest",
|
|
index=models.Index(
|
|
fields=["test_code"], name="laboratory__test_co_22f8cc_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labtest",
|
|
index=models.Index(
|
|
fields=["test_category"], name="laboratory__test_ca_7c9bb2_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labtest",
|
|
index=models.Index(
|
|
fields=["department"], name="laboratory__departm_5f2280_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labtest",
|
|
index=models.Index(
|
|
fields=["loinc_code"], name="laboratory__loinc_c_e5eab5_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labtest",
|
|
index=models.Index(
|
|
fields=["cpt_code"], name="laboratory__cpt_cod_f1046e_idx"
|
|
),
|
|
),
|
|
migrations.AlterUniqueTogether(
|
|
name="labtest",
|
|
unique_together={("tenant", "test_code")},
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="laborder",
|
|
index=models.Index(
|
|
fields=["tenant", "status"], name="laboratory__tenant__2120e0_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="laborder",
|
|
index=models.Index(
|
|
fields=["patient", "status"], name="laboratory__patient_6175e0_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="laborder",
|
|
index=models.Index(
|
|
fields=["ordering_provider"], name="laboratory__orderin_489873_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="laborder",
|
|
index=models.Index(
|
|
fields=["order_datetime"], name="laboratory__order_d_8b2665_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="laborder",
|
|
index=models.Index(
|
|
fields=["order_number"], name="laboratory__order_n_e3b8bb_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="laborder",
|
|
index=models.Index(
|
|
fields=["priority"], name="laboratory__priorit_30c56c_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="qualitycontrol",
|
|
index=models.Index(
|
|
fields=["tenant", "test"], name="laboratory__tenant__8d678d_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="qualitycontrol",
|
|
index=models.Index(
|
|
fields=["test", "run_datetime"], name="laboratory__test_id_9b16e1_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="qualitycontrol",
|
|
index=models.Index(fields=["status"], name="laboratory__status_798b34_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="qualitycontrol",
|
|
index=models.Index(
|
|
fields=["control_level"], name="laboratory__control_0b5167_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="referencerange",
|
|
index=models.Index(
|
|
fields=["test", "is_active"], name="laboratory__test_id_1156ed_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="referencerange",
|
|
index=models.Index(fields=["gender"], name="laboratory__gender_c64bc7_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="referencerange",
|
|
index=models.Index(
|
|
fields=["age_min", "age_max"], name="laboratory__age_min_999058_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="specimen",
|
|
index=models.Index(fields=["order"], name="laboratory__order_i_d2ffe7_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="specimen",
|
|
index=models.Index(
|
|
fields=["specimen_number"], name="laboratory__specime_43940d_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="specimen",
|
|
index=models.Index(
|
|
fields=["specimen_type"], name="laboratory__specime_455f2e_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="specimen",
|
|
index=models.Index(fields=["status"], name="laboratory__status_4fed28_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="specimen",
|
|
index=models.Index(
|
|
fields=["collected_datetime"], name="laboratory__collect_99eee1_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="specimen",
|
|
index=models.Index(
|
|
fields=["quality"], name="laboratory__quality_6f2bda_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(fields=["order"], name="laboratory__order_i_53ed8e_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(fields=["test"], name="laboratory__test_id_afcdaa_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(
|
|
fields=["specimen"], name="laboratory__specime_e34006_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(fields=["status"], name="laboratory__status_cf9bfd_idx"),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(
|
|
fields=["is_critical"], name="laboratory__is_crit_991343_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(
|
|
fields=["verified"], name="laboratory__verifie_12cf87_idx"
|
|
),
|
|
),
|
|
migrations.AddIndex(
|
|
model_name="labresult",
|
|
index=models.Index(
|
|
fields=["analyzed_datetime"], name="laboratory__analyze_e44c83_idx"
|
|
),
|
|
),
|
|
migrations.AlterUniqueTogether(
|
|
name="labresult",
|
|
unique_together={("order", "test", "specimen")},
|
|
),
|
|
]
|