diff --git a/Pipfile b/Pipfile index b7e6136903..6beddc1590 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,6 @@ django = "==4.2.15" django-environ = "==0.11.2" django-cors-headers = "==4.3.1" django-filter = "==24.2" -django-hardcopy = "==0.1.4" django-maintenance-mode = "==0.21.1" django-model-utils = "==4.5.1" django-multiselectfield = "==0.1.12" diff --git a/Pipfile.lock b/Pipfile.lock index 51069ba3bd..6cfb4f13d5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "17b769e6a740f13f82e44bce08575af72844ffab86d295d27ba7fca2e06f6a72" + "sha256": "0f478643ec74971469522c09c3e635a7ca9346c16a687ae7c14c7f638335cb1e" }, "pipfile-spec": 6, "requires": { @@ -509,14 +509,6 @@ "markers": "python_version >= '3.8'", "version": "==24.2" }, - "django-hardcopy": { - "hashes": [ - "sha256:1ceda5fb262c9787fbd2554b4b92db9c8feebdd6da4ef6c1746e1aa8c8e55377", - "sha256:8bf962245d95918bd5c4b4e1b809a3d029b19d4f331e1c7c5f65768fb8366ed8" - ], - "index": "pypi", - "version": "==0.1.4" - }, "django-maintenance-mode": { "hashes": [ "sha256:b79afddb671c59972ae542e4fafbc99117d2d37991843eaaa837e328eed12b1b", diff --git a/care/facility/api/viewsets/patient_consultation.py b/care/facility/api/viewsets/patient_consultation.py index 95aa138331..b166971dc9 100644 --- a/care/facility/api/viewsets/patient_consultation.py +++ b/care/facility/api/viewsets/patient_consultation.py @@ -1,7 +1,11 @@ +import tempfile + from django.db import transaction from django.db.models import Prefetch from django.db.models.query_utils import Q -from django.shortcuts import get_object_or_404, render +from django.http import HttpResponse +from django.shortcuts import get_object_or_404 +from django.utils import timezone from django_filters import rest_framework as filters from drf_spectacular.utils import extend_schema from dry_rest_permissions.generics import DRYPermissions @@ -296,7 +300,18 @@ def dev_preview_discharge_summary(request, consultation_id): if not consultation: raise NotFound({"detail": "Consultation not found"}) data = discharge_summary.get_discharge_summary_data(consultation) - return render(request, "reports/patient_discharge_summary_pdf.html", data) + data["date"] = timezone.now() + + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file: + discharge_summary.generate_discharge_summary_pdf(data, tmp_file) + + with open(tmp_file.name, "rb") as pdf_file: + pdf_content = pdf_file.read() + + response = HttpResponse(pdf_content, content_type="application/pdf") + response["Content-Disposition"] = 'inline; filename="discharge_summary.pdf"' + + return response class PatientConsentViewSet( diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index b3d42bef01..8a5aeb6e4c 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -1,4 +1,5 @@ import enum +from datetime import date from dateutil.relativedelta import relativedelta from django.contrib.postgres.aggregates import ArrayAgg @@ -6,6 +7,7 @@ from django.db import models from django.db.models import Case, F, Func, JSONField, Value, When from django.db.models.functions import Coalesce, Now +from django.template.defaultfilters import pluralize from django.utils import timezone from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords @@ -481,10 +483,29 @@ def save(self, *args, **kwargs) -> None: self._alias_recovery_to_recovered() super().save(*args, **kwargs) - def get_age(self) -> int: - start = self.date_of_birth or timezone.datetime(self.year_of_birth, 1, 1).date() + def get_age(self) -> str: + start = self.date_of_birth or date(self.year_of_birth, 1, 1) end = (self.death_datetime or timezone.now()).date() - return relativedelta(end, start).years + + delta = relativedelta(end, start) + + if delta.years > 0: + year_str = f"{delta.years} year{pluralize(delta.years)}" + return f"{year_str}" + + elif delta.months > 0: + month_str = f"{delta.months} month{pluralize(delta.months)}" + day_str = ( + f" {delta.days} day{pluralize(delta.days)}" if delta.days > 0 else "" + ) + return f"{month_str}{day_str}" + + elif delta.days > 0: + day_str = f"{delta.days} day{pluralize(delta.days)}" + return day_str + + else: + return "0 days" def annotate_diagnosis_ids(*args, **kwargs): return ArrayAgg( diff --git a/care/facility/templatetags/data_formatting_tags.py b/care/facility/templatetags/data_formatting_tags.py new file mode 100644 index 0000000000..18cc43c545 --- /dev/null +++ b/care/facility/templatetags/data_formatting_tags.py @@ -0,0 +1,35 @@ +from django import template + +register = template.Library() + + +@register.filter(name="format_empty_data") +def format_empty_data(data): + if data is None or data == "" or data == 0.0 or data == []: + return "N/A" + + return data + + +@register.filter(name="format_to_sentence_case") +def format_to_sentence_case(data): + if data is None: + return + + def convert_to_sentence_case(s): + if s == "ICU": + return "ICU" + s = s.lower() + s = s.replace("_", " ") + return s.capitalize() + + if isinstance(data, str): + items = data.split(", ") + converted_items = [convert_to_sentence_case(item) for item in items] + return ", ".join(converted_items) + + elif isinstance(data, (list, tuple)): + converted_items = [convert_to_sentence_case(item) for item in data] + return ", ".join(converted_items) + + return data diff --git a/care/facility/templatetags/prescription_tags.py b/care/facility/templatetags/prescription_tags.py new file mode 100644 index 0000000000..2f1b2ecee8 --- /dev/null +++ b/care/facility/templatetags/prescription_tags.py @@ -0,0 +1,13 @@ +from django import template + +register = template.Library() + + +@register.filter(name="format_prescription") +def format_prescription(prescription): + if prescription.dosage_type == "TITRATED": + return f"{prescription.medicine_name}, titration from {prescription.base_dosage} to {prescription.target_dosage}, {prescription.route}, {prescription.frequency} for {prescription.days} days." + if prescription.dosage_type == "PRN": + return f"{prescription.medicine_name}, {prescription.base_dosage}, {prescription.route}" + else: + return f"{prescription.medicine_name}, {prescription.base_dosage}, {prescription.route}, {prescription.frequency} for {prescription.days} days." diff --git a/care/facility/tests/sample_reports/sample1.png b/care/facility/tests/sample_reports/sample1.png new file mode 100644 index 0000000000..7cb6ce14b9 Binary files /dev/null and b/care/facility/tests/sample_reports/sample1.png differ diff --git a/care/facility/tests/sample_reports/sample2.png b/care/facility/tests/sample_reports/sample2.png new file mode 100644 index 0000000000..e46ee40001 Binary files /dev/null and b/care/facility/tests/sample_reports/sample2.png differ diff --git a/care/facility/tests/test_pdf_generation.py b/care/facility/tests/test_pdf_generation.py new file mode 100644 index 0000000000..6754c0c43a --- /dev/null +++ b/care/facility/tests/test_pdf_generation.py @@ -0,0 +1,230 @@ +import os +import subprocess +import tempfile +from datetime import date +from pathlib import Path + +from django.conf import settings +from django.template.loader import render_to_string +from django.test import TestCase +from PIL import Image +from rest_framework.test import APIClient + +from care.facility.models import ( + ConditionVerificationStatus, + ICD11Diagnosis, + PrescriptionDosageType, + PrescriptionType, +) +from care.facility.utils.reports import discharge_summary +from care.facility.utils.reports.discharge_summary import compile_typ +from care.utils.tests.test_utils import TestUtils + + +def compare_pngs(png_path1, png_path2): + with Image.open(png_path1) as img1, Image.open(png_path2) as img2: + if img1.mode != img2.mode: + return False + + if img1.size != img2.size: + return False + + img1_data = list(img1.getdata()) + img2_data = list(img2.getdata()) + + if img1_data == img2_data: + return True + else: + return False + + +def test_compile_typ(data): + sample_file_path = os.path.join( + os.getcwd(), "care", "facility", "tests", "sample_reports", "sample{n}.png" + ) + test_output_file_path = os.path.join( + os.getcwd(), "care", "facility", "tests", "sample_reports", "test_output{n}.png" + ) + try: + logo_path = ( + Path(settings.BASE_DIR) + / "staticfiles" + / "images" + / "logos" + / "black-logo.svg" + ) + data["logo_path"] = str(logo_path) + content = render_to_string( + "reports/patient_discharge_summary_pdf_template.typ", context=data + ) + subprocess.run( + ["typst", "compile", "-", test_output_file_path, "--format", "png"], + input=content.encode("utf-8"), + capture_output=True, + check=True, + cwd="/", + ) + + number_of_pngs_generated = 2 + # To be updated only if the number of sample png increase in future + + for i in range(1, number_of_pngs_generated + 1): + current_sample_file_path = sample_file_path + current_sample_file_path = str(current_sample_file_path).replace( + "{n}", str(i) + ) + + current_test_output_file_path = test_output_file_path + current_test_output_file_path = str(current_test_output_file_path).replace( + "{n}", str(i) + ) + + if not compare_pngs( + Path(current_sample_file_path), Path(current_test_output_file_path) + ): + return False + return True + except Exception: + return False + finally: + count = 1 + while True: + current_test_output_file_path = test_output_file_path + current_test_output_file_path = current_test_output_file_path.replace( + "{n}", str(count) + ) + if Path(current_test_output_file_path).exists(): + os.remove(Path(current_test_output_file_path)) + else: + break + count += 1 + + +class TestTypstInstallation(TestCase): + def test_typst_installed(self): + try: + subprocess.run(["typst", "--version"], check=True) + typst_installed = True + except subprocess.CalledProcessError: + typst_installed = False + + self.assertTrue(typst_installed, "Typst is not installed or not accessible") + + +class TestGenerateDischargeSummaryPDF(TestCase, TestUtils): + @classmethod + def setUpTestData(cls) -> None: + cls.state = cls.create_state(name="sample_state") + cls.district = cls.create_district(cls.state, name="sample_district") + cls.local_body = cls.create_local_body(cls.district, name="sample_local_body") + cls.super_user = cls.create_super_user("su", cls.district) + cls.facility = cls.create_facility( + cls.super_user, cls.district, cls.local_body, name="_Sample_Facility" + ) + cls.user = cls.create_user("staff1", cls.district, home_facility=cls.facility) + cls.treating_physician = cls.create_user( + "test Doctor", + cls.district, + home_facility=cls.facility, + first_name="Doctor", + last_name="Tester", + user_type=15, + ) + cls.patient = cls.create_patient( + cls.district, cls.facility, local_body=cls.local_body + ) + cls.consultation = cls.create_consultation( + cls.patient, + cls.facility, + patient_no="123456", + doctor=cls.treating_physician, + height=178, + weight=80, + suggestion="A", + ) + cls.create_patient_sample(cls.patient, cls.consultation, cls.facility, cls.user) + cls.create_policy(patient=cls.patient, user=cls.user) + cls.create_encounter_symptom(cls.consultation, cls.user) + cls.patient_investigation_group = cls.create_patient_investigation_group() + cls.patient_investigation = cls.create_patient_investigation( + cls.patient_investigation_group + ) + cls.patient_investigation_session = cls.create_patient_investigation_session( + cls.user + ) + cls.create_investigation_value( + cls.patient_investigation, + cls.consultation, + cls.patient_investigation_session, + cls.patient_investigation_group, + ) + cls.create_disease(cls.patient) + cls.create_prescription(cls.consultation, cls.user) + cls.create_prescription( + cls.consultation, cls.user, dosage_type=PrescriptionDosageType.TITRATED + ) + cls.create_prescription( + cls.consultation, cls.user, dosage_type=PrescriptionDosageType.PRN + ) + cls.create_prescription( + cls.consultation, cls.user, prescription_type=PrescriptionType.DISCHARGE + ) + cls.create_prescription( + cls.consultation, + cls.user, + prescription_type=PrescriptionType.DISCHARGE, + dosage_type=PrescriptionDosageType.TITRATED, + ) + cls.create_prescription( + cls.consultation, + cls.user, + prescription_type=PrescriptionType.DISCHARGE, + dosage_type=PrescriptionDosageType.PRN, + ) + cls.create_consultation_diagnosis( + cls.consultation, + ICD11Diagnosis.objects.filter( + label="SG31 Conception vessel pattern (TM1)" + ).first(), + verification_status=ConditionVerificationStatus.CONFIRMED, + ) + cls.create_consultation_diagnosis( + cls.consultation, + ICD11Diagnosis.objects.filter( + label="SG2B Liver meridian pattern (TM1)" + ).first(), + verification_status=ConditionVerificationStatus.DIFFERENTIAL, + ) + cls.create_consultation_diagnosis( + cls.consultation, + ICD11Diagnosis.objects.filter( + label="SG29 Triple energizer meridian pattern (TM1)" + ).first(), + verification_status=ConditionVerificationStatus.PROVISIONAL, + ) + cls.create_consultation_diagnosis( + cls.consultation, + ICD11Diagnosis.objects.filter( + label="SG60 Early yang stage pattern (TM1)" + ).first(), + verification_status=ConditionVerificationStatus.UNCONFIRMED, + ) + + def setUp(self) -> None: + self.client = APIClient() + + def test_pdf_generation_success(self): + test_data = {"consultation": self.consultation} + + with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as file: + compile_typ(file.name, test_data) + + self.assertTrue(os.path.exists(file.name)) + self.assertGreater(os.path.getsize(file.name), 0) + + def test_pdf_generation(self): + data = discharge_summary.get_discharge_summary_data(self.consultation) + data["date"] = date(2020, 1, 1) + + # This sorting is test's specific and done in order to keep the values in order + self.assertTrue(test_compile_typ(data)) diff --git a/care/facility/utils/reports/discharge_summary.py b/care/facility/utils/reports/discharge_summary.py index acec25ea20..b9fb47d077 100644 --- a/care/facility/utils/reports/discharge_summary.py +++ b/care/facility/utils/reports/discharge_summary.py @@ -1,18 +1,20 @@ import logging +import subprocess import tempfile from collections.abc import Iterable +from pathlib import Path from uuid import uuid4 from django.conf import settings from django.core.cache import cache from django.core.mail import EmailMessage -from django.db.models import Q +from django.db.models import Case, IntegerField, Q, Value, When from django.template.loader import render_to_string from django.utils import timezone -from hardcopy import bytestring_to_pdf from care.facility.models import ( - DailyRound, + BedType, + ConsultationBed, Disease, EncounterSymptom, InvestigationValue, @@ -28,7 +30,7 @@ ACTIVE_CONDITION_VERIFICATION_STATUSES, ConditionVerificationStatus, ) -from care.facility.static_data.icd11 import get_icd11_diagnoses_objects_by_ids +from care.facility.static_data.icd11 import get_icd11_diagnosis_object_by_id from care.hcx.models.policy import Policy logger = logging.getLogger(__name__) @@ -65,13 +67,19 @@ def get_diagnoses_data(consultation: PatientConsultation): ) ) - # retrieve diagnosis objects from in-memory table - diagnoses = get_icd11_diagnoses_objects_by_ids([entry[0] for entry in entries]) - + # retrieve diagnosis objects + diagnoses = [] + for entry in entries: + diagnose = get_icd11_diagnosis_object_by_id(entry[0]) + if diagnose: + diagnoses.append(diagnose) principal, unconfirmed, provisional, differential, confirmed = [], [], [], [], [] for diagnosis, record in zip(diagnoses, entries): _, verification_status, is_principal = record + + diagnosis.verification_status = verification_status + if is_principal: principal.append(diagnosis) if verification_status == ConditionVerificationStatus.UNCONFIRMED: @@ -92,40 +100,61 @@ def get_diagnoses_data(consultation: PatientConsultation): } +def format_duration(duration): + if not duration: + return "" + + days = duration.days + if days > 0: + return f"{days} days" + hours, remainder = divmod(duration.seconds, 3600) + minutes, _ = divmod(remainder, 60) + return f"{hours:02}:{minutes:02}" + + def get_discharge_summary_data(consultation: PatientConsultation): logger.info(f"fetching discharge summary data for {consultation.external_id}") samples = PatientSample.objects.filter( patient=consultation.patient, consultation=consultation ) hcx = Policy.objects.filter(patient=consultation.patient) - daily_rounds = DailyRound.objects.filter(consultation=consultation) - symptoms = EncounterSymptom.objects.filter(consultation=consultation).exclude( - clinical_impression_status=ClinicalImpressionStatus.ENTERED_IN_ERROR - ) + symptoms = EncounterSymptom.objects.filter( + consultation=consultation, onset_date__lt=consultation.encounter_date + ).exclude(clinical_impression_status=ClinicalImpressionStatus.ENTERED_IN_ERROR) diagnoses = get_diagnoses_data(consultation) investigations = InvestigationValue.objects.filter( Q(consultation=consultation.id) & (Q(value__isnull=False) | Q(notes__isnull=False)) ) medical_history = Disease.objects.filter(patient=consultation.patient) - prescriptions = Prescription.objects.filter( - consultation=consultation, - prescription_type=PrescriptionType.REGULAR.value, - ).exclude(dosage_type=PrescriptionDosageType.PRN.value) - prn_prescriptions = Prescription.objects.filter( - consultation=consultation, - prescription_type=PrescriptionType.REGULAR.value, - dosage_type=PrescriptionDosageType.PRN.value, + prescriptions = ( + Prescription.objects.filter( + consultation=consultation, prescription_type=PrescriptionType.REGULAR.value + ) + .annotate( + order_priority=Case( + When(dosage_type=PrescriptionDosageType.PRN.value, then=Value(2)), + When(dosage_type=PrescriptionDosageType.TITRATED.value, then=Value(1)), + default=Value(0), + output_field=IntegerField(), + ) + ) + .order_by("order_priority", "id") ) - discharge_prescriptions = Prescription.objects.filter( - consultation=consultation, - prescription_type=PrescriptionType.DISCHARGE.value, - ).exclude(dosage_type=PrescriptionDosageType.PRN.value) - - discharge_prn_prescriptions = Prescription.objects.filter( - consultation=consultation, - prescription_type=PrescriptionType.DISCHARGE.value, - dosage_type=PrescriptionDosageType.PRN.value, + discharge_prescriptions = ( + Prescription.objects.filter( + consultation=consultation, + prescription_type=PrescriptionType.DISCHARGE.value, + ) + .annotate( + order_priority=Case( + When(dosage_type=PrescriptionDosageType.PRN.value, then=Value(2)), + When(dosage_type=PrescriptionDosageType.TITRATED.value, then=Value(1)), + default=Value(0), + output_field=IntegerField(), + ) + ) + .order_by("order_priority", "id") ) files = FileUpload.objects.filter( associating_id=consultation.id, @@ -133,47 +162,91 @@ def get_discharge_summary_data(consultation: PatientConsultation): upload_completed=True, is_archived=False, ) + admitted_to = set() + if ConsultationBed.objects.filter(consultation=consultation).exists(): + for bed in ConsultationBed.objects.filter(consultation=consultation).order_by( + "-created_date" + ): + admitted_to.add(BedType(bed.bed.bed_type).name) + admitted_to = list(admitted_to) + if not admitted_to: + admitted_to = None + + admission_duration = ( + format_duration(consultation.discharge_date - consultation.encounter_date) + if consultation.discharge_date + else None + ) return { "patient": consultation.patient, "samples": samples, "hcx": hcx, "symptoms": symptoms, - "principal_diagnoses": diagnoses["principal"], - "unconfirmed_diagnoses": diagnoses["unconfirmed"], - "provisional_diagnoses": diagnoses["provisional"], - "differential_diagnoses": diagnoses["differential"], - "confirmed_diagnoses": diagnoses["confirmed"], + "admitted_to": admitted_to, + "admission_duration": admission_duration, + "diagnoses": diagnoses["confirmed"] + + diagnoses["provisional"] + + diagnoses["unconfirmed"] + + diagnoses["differential"], + "primary_diagnoses": diagnoses["principal"], "consultation": consultation, "prescriptions": prescriptions, - "prn_prescriptions": prn_prescriptions, "discharge_prescriptions": discharge_prescriptions, - "discharge_prn_prescriptions": discharge_prn_prescriptions, - "dailyrounds": daily_rounds, "medical_history": medical_history, "investigations": investigations, "files": files, } -def generate_discharge_summary_pdf(data, file): - logger.info( - f"Generating Discharge Summary html for {data['consultation'].external_id}" - ) - html_string = render_to_string("reports/patient_discharge_summary_pdf.html", data) +def compile_typ(output_file, data): + try: + logo_path = ( + Path(settings.BASE_DIR) + / "staticfiles" + / "images" + / "logos" + / "black-logo.svg" + ) + + data["logo_path"] = str(logo_path) + + content = render_to_string( + "reports/patient_discharge_summary_pdf_template.typ", context=data + ) + + subprocess.run( + [ + "typst", + "compile", + "-", + str(output_file), + ], + input=content.encode("utf-8"), + capture_output=True, + check=True, + cwd="/", + ) + logging.info( + f"Successfully Compiled Summary pdf for {data['consultation'].external_id}" + ) + return True + + except subprocess.CalledProcessError as e: + logging.error( + f"Error compiling summary pdf for {data['consultation'].external_id}: {e.stderr.decode('utf-8')}" + ) + return False + + +def generate_discharge_summary_pdf(data, file): logger.info( f"Generating Discharge Summary pdf for {data['consultation'].external_id}" ) - bytestring_to_pdf( - html_string.encode(), - file, - **{ - "no-margins": None, - "disable-gpu": None, - "disable-dev-shm-usage": False, - "window-size": "2480,3508", - }, + compile_typ(output_file=file.name, data=data) + logger.info( + f"Successfully Generated Discharge Summary pdf for {data['consultation'].external_id}" ) diff --git a/care/templates/reports/patient_discharge_summary_pdf.html b/care/templates/reports/patient_discharge_summary_pdf.html deleted file mode 100644 index 2a2125c788..0000000000 --- a/care/templates/reports/patient_discharge_summary_pdf.html +++ /dev/null @@ -1,1065 +0,0 @@ -{% load filters static %} - - -
- - - - - - - - - -- Created on {{date}} -
-- Full name: {{patient.name}} -
-- Gender: {{patient.get_gender_display}} -
-- Age: {{patient.get_age}} -
- {% if patient.date_of_birth %} -- Date of Birth: {{patient.date_of_birth}} -
- {% else %} -- Year of Birth: {{patient.year_of_birth}} -
- {% endif %} -- Blood Group: {{patient.blood_group}} -
-- Phone Number: {{patient.phone_number}} -
-- Address: {{patient.address}} -
-- Ration Card Category: {{patient.get_ration_card_category_display}} -
-- Route to Facility: - {{consultation.get_route_to_facility_display|field_name_to_label}} -
-- Decision after consultation: - {{consultation.get_suggestion_display|field_name_to_label}} -
- {% if consultation.icu_admission_date %} -- ICU Admission Date & Time: - {{consultation.icu_admission_date}} -
- {% endif %} - {% if consultation.suggestion == 'A' %} -- Date of addmission: - {{consultation.encounter_date}} -
- {% elif consultation.suggestion == 'R' %} -- Referred to: - {{consultation.referred_to.name}} -
- {% elif consultation.suggestion == 'DD' %} -- Cause of death: - {{consultation.discharge_notes}} -
-- Date and time of death: - {{consultation.death_datetime}} -
-- Death Confirmed by: - {{consultation.death_confirmed_by}} -
- {% endif %} -- - {% if consultation.suggestion == 'A' %} - IP No: - {% else %} - OP No: - {% endif %} - {{consultation.patient_no}} -
-- Weight: - {{consultation.weight}} kg -
-- Height: - {{consultation.height}} cm -
-- Insurer Name - | -- Issuer ID - | -- Member ID - | -- Policy ID - | -
---|---|---|---|
- {{policy.insurer_name}} - | -- {{policy.insurer_id}} - | -- {{policy.subscriber_id}} - | -- {{policy.policy_id}} - | -
- Name - | -- Onset Date - | -- Cure Date - | -
---|---|---|
- {% if symptom.symptom == 9 %} - {{symptom.other_symptom}} - {% else %} - {{symptom.get_symptom_display}} - {% endif %} - | -- {{symptom.onset_date.date}} - | -- {{symptom.cure_date.date}} - | -
- ID - | -- Name - | -
---|---|
- {{disease.id}} - | -- {{disease.label}} - | -
- ID - | -- Name - | -
---|---|
- {{disease.id}} - | -- {{disease.label}} - | -
- ID - | -- Name - | -
---|---|
- {{disease.id}} - | -- {{disease.label}} - | -
- ID - | -- Name - | -
---|---|
- {{disease.id}} - | -- {{disease.label}} - | -
- ID - | -- Name - | -
---|---|
- {{disease.id}} - | -- {{disease.label}} - | -
- Comorbidity - | -- Details - | -
---|---|
- {{disease.get_disease_display}} - | -- {{disease.details}} - | -
- Present health condition: - {{patient.present_health}} -
-- Ongoing Medication: - {{patient.ongoing_medication}} -
- {% if consultation.route_to_facility %} -- History of present illness: - {{consultation.history_of_present_illness}} -
- {% endif %} -- Examination details and Clinical conditions: - {{consultation.examination_details}} -
-- Allergies: - {{patient.allergies}} -
-- COVID Disease Status: - Positive -
- {% if patient.date_of_result %} -- Test Result Date: - {{patient.date_of_result.date}} -
- {% endif %} -- Vaccinated against COVID: - {% if patient.is_vaccinated %} - Yes - {% else %} - No - {% endif %} -
-- Medicine - | -- Dosage - | -- Route - | -- Frequency - | -- Days - | -- Notes - | -
---|---|---|---|---|---|
- {{ prescription.medicine_name }} - | -
- {{ prescription.base_dosage }}
- {% if prescription.dosage_type == 'TITRATED' %}
- to {{ prescription.target_dosage }}
- Instruction on titration: {{ prescription.instruction_on_titration }} - {% endif %} - |
- - {{ prescription.route }} - | -- {{ prescription.frequency }} - | -- {{ prescription.days }} - | -- {{ prescription.notes }} - | -
- Medicine - | -- Dosage - | -- Max Dosage - | -- Min Time btwn. 2 doses - | -- Route - | -- Indicator - | -
---|---|---|---|---|---|
- {{ prescription.medicine_name }} - | -- {{ prescription.base_dosage }} - | -- {{ prescription.max_dosage }} - | -- {{ prescription.min_hours_between_doses }} Hrs. - | -- {{ prescription.route }} - | -- {{ prescription.indicator }} - | -
- Type - | -- Time - | -- Notes - | -
---|---|---|
- {{investigation.type|join:", "}} - | -- {% if investigation.repetitive %} - every {{investigation.frequency}} - {% else %} - {{investigation.time|parse_datetime}} - {% endif %} - | -- {{investigation.notes}} - | -
- Procedure - | -- Time - | -- Notes - | -
---|---|---|
- {{procedure.procedure}} - | -- {% if procedure.repetitive %} - every {{procedure.frequency}} - {% else %} - {{procedure.time|parse_datetime}} - {% endif %} - | -- {{procedure.notes}} - | -
- {{ consultation.treatment_plan }} -
- {% endif %} - - {% if consultation.consultation_notes %} -- {{consultation.consultation_notes}} -
- {% endif %} - - {% if consultation.special_instruction %} -- {{consultation.special_instruction}} -
- {% endif %} - - - {% if samples %} -- Requested on - | -- Sample type - | -- Label - | -- Result - | -
---|---|---|---|
- {{sample.created_date}} - | -- {{sample.get_sample_type_display}} - | -- {{sample.icmr_label}} - | -- {{sample.get_result_display}} - | -
- Group - | -- Name - | -- Result - | -- Range - | -- Date - | -
---|---|---|---|---|
- {{investigation.investigation.groups.first}} - | -- {{investigation.investigation.name}} - | -- {% if investigation.value%} - {{investigation.value}} - {% else %} - {{investigation.notes}} - {% endif %} - | -- {% if investigation.investigation.min_value and investigation.investigation.max_value%} - {{investigation.investigation.min_value}} - {{investigation.investigation.max_value}} - {% else %} - - - {% endif %} - {% if investigation.investigation.unit %} - {{investigation.investigation.unit}} - {% endif %} - | -- {{investigation.created_date}} - | -
- Discharge Date: {{consultation.discharge_date}} -
-- Discharge Reason: {{consultation.get_discharge_reason_display}} -
-- Medicine - | -- Dosage - | -- Route - | -- Frequency - | -- Days - | -- Notes - | -
---|---|---|---|---|---|
- {{ prescription.medicine_name }} - | -- {{ prescription.base_dosage }} - | -- {{ prescription.route }} - | -- {{ prescription.frequency }} - | -- {{ prescription.days }} - | -- {{ prescription.notes }} - | -
- Medicine - | -- Dosage - | -- Max Dosage - | -- Min Time btwn. 2 doses - | -- Route - | -- Indicator - | -
---|---|---|---|---|---|
- {{ prescription.medicine_name }} - | -- {{ prescription.base_dosage }} - | -- {{ prescription.max_dosage }} - | -- {{ prescription.min_hours_between_doses }} Hrs. - | -- {{ prescription.route }} - | -- {{ prescription.indicator }} - | -
- Discharge Notes: {{consultation.discharge_notes}} -
-- Uploaded at - | -- Name - | -
---|---|
- {{file.modified_date}} - | -- {{file.name}} - | -