From 1115a8fe9a4de6be090b34fec42872abf9c89fa6 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Tue, 2 Apr 2024 22:37:20 +0530
Subject: [PATCH 1/9] Dropped `age` column from patient registration and
annotated age in patient viewset (#1966)
* drop field `age` and compute age on demand
* Update filters and related usages
* add min value validator for year of birth
* remove age from test utils
* remove unnecessary default
* Add field `death_datetime` in patient registration
* annotate age in days
* suggestion from code review
* revert unintended changes
* skip serializing age
* rebase migrations
* rebase migrations
* drop age from dummy data
* Revert "drop age from dummy data"
This reverts commit 9964b853e24e30ff332a8c38128cd484510f8c6f.
* drop patient registration's age from dummy data
* select only `last_consultation` in custom migration
Co-authored-by: Aakash Singh
* format code
* rebase migrations
* rebase migrations
---------
Co-authored-by: Aakash Singh
---
care/facility/admin.py | 2 +-
care/facility/api/serializers/patient.py | 23 +++--
care/facility/api/viewsets/patient.py | 87 ++++++++++++++-----
..._add_patientregistration_death_datetime.py | 65 ++++++++++++++
care/facility/models/patient.py | 29 ++-----
care/facility/models/patient_consultation.py | 14 +++
care/facility/models/patient_icmr.py | 27 +++---
care/utils/tests/test_utils.py | 1 -
data/dummy/facility.json | 18 ----
9 files changed, 177 insertions(+), 89 deletions(-)
create mode 100644 care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py
diff --git a/care/facility/admin.py b/care/facility/admin.py
index bc5b3fb650..db0ac6a173 100644
--- a/care/facility/admin.py
+++ b/care/facility/admin.py
@@ -147,7 +147,7 @@ class AmbulanceDriverAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
class PatientAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
- list_display = ("id", "name", "age", "gender")
+ list_display = ("id", "name", "year_of_birth", "gender")
djangoql_completion_enabled_by_default = True
diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py
index 28993af4ca..5ba675a734 100644
--- a/care/facility/api/serializers/patient.py
+++ b/care/facility/api/serializers/patient.py
@@ -121,13 +121,12 @@ class Meta:
"created_by",
"deleted",
"ongoing_medication",
- "year_of_birth",
"meta_info",
"countries_travelled_old",
"allergies",
"external_id",
)
- read_only = TIMESTAMP_FIELDS
+ read_only = TIMESTAMP_FIELDS + ("death_datetime",)
class PatientContactDetailsSerializer(serializers.ModelSerializer):
@@ -223,12 +222,16 @@ class Meta:
model = PatientRegistration
exclude = (
"deleted",
- "year_of_birth",
"countries_travelled_old",
"external_id",
)
include = ("contacted_patients",)
- read_only = TIMESTAMP_FIELDS + ("last_edited", "created_by", "is_active")
+ read_only = TIMESTAMP_FIELDS + (
+ "last_edited",
+ "created_by",
+ "is_active",
+ "death_datetime",
+ )
# def get_last_consultation(self, obj):
# last_consultation = PatientConsultation.objects.filter(patient=obj).last()
@@ -250,13 +253,15 @@ def validate_countries_travelled(self, value):
def validate(self, attrs):
validated = super().validate(attrs)
- if (
- not self.partial
- and not validated.get("age")
- and not validated.get("date_of_birth")
+ if not self.partial and not (
+ validated.get("year_of_birth") or validated.get("date_of_birth")
):
raise serializers.ValidationError(
- {"non_field_errors": ["Either age or date_of_birth should be passed"]}
+ {
+ "non_field_errors": [
+ "Either year_of_birth or date_of_birth should be passed"
+ ]
+ }
)
if validated.get("is_vaccinated"):
diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py
index 704dc98bbb..f55a60030a 100644
--- a/care/facility/api/viewsets/patient.py
+++ b/care/facility/api/viewsets/patient.py
@@ -5,7 +5,18 @@
from django.conf import settings
from django.contrib.postgres.search import TrigramSimilarity
from django.db import models
-from django.db.models import Case, OuterRef, Q, Subquery, When
+from django.db.models import (
+ Case,
+ ExpressionWrapper,
+ F,
+ Func,
+ OuterRef,
+ Q,
+ Subquery,
+ Value,
+ When,
+)
+from django.db.models.functions import Coalesce, ExtractDay, Now
from django.db.models.query import QuerySet
from django_filters import rest_framework as filters
from djqscsv import render_to_csv_response
@@ -333,25 +344,61 @@ class PatientViewSet(
]
permission_classes = (IsAuthenticated, DRYPermissions)
lookup_field = "external_id"
- queryset = PatientRegistration.objects.all().select_related(
- "local_body",
- "district",
- "state",
- "ward",
- "assigned_to",
- "facility",
- "facility__ward",
- "facility__local_body",
- "facility__district",
- "facility__state",
- # "nearest_facility",
- # "nearest_facility__local_body",
- # "nearest_facility__district",
- # "nearest_facility__state",
- "last_consultation",
- "last_consultation__assigned_to",
- "last_edited",
- "created_by",
+ queryset = (
+ PatientRegistration.objects.all()
+ .select_related(
+ "local_body",
+ "district",
+ "state",
+ "ward",
+ "assigned_to",
+ "facility",
+ "facility__ward",
+ "facility__local_body",
+ "facility__district",
+ "facility__state",
+ # "nearest_facility",
+ # "nearest_facility__local_body",
+ # "nearest_facility__district",
+ # "nearest_facility__state",
+ "last_consultation",
+ "last_consultation__assigned_to",
+ "last_edited",
+ "created_by",
+ )
+ .annotate(
+ coalesced_dob=Coalesce(
+ "date_of_birth",
+ Func(
+ F("year_of_birth"),
+ Value(1),
+ Value(1),
+ function="MAKE_DATE",
+ output_field=models.DateField(),
+ ),
+ output_field=models.DateField(),
+ ),
+ age_end=Case(
+ When(death_datetime__isnull=True, then=Now()),
+ default=F("death_datetime__date"),
+ ),
+ )
+ .annotate(
+ age=Func(
+ Value("year"),
+ Func(
+ F("age_end"),
+ F("coalesced_dob"),
+ function="age",
+ ),
+ function="date_part",
+ output_field=models.IntegerField(),
+ ),
+ age_days=ExpressionWrapper(
+ ExtractDay(F("age_end") - F("coalesced_dob")),
+ output_field=models.IntegerField(),
+ ),
+ )
)
ordering_fields = [
"facility__name",
diff --git a/care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py b/care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py
new file mode 100644
index 0000000000..e051559ba8
--- /dev/null
+++ b/care/facility/migrations/0424_remove_patientregistration_age_and_add_patientregistration_death_datetime.py
@@ -0,0 +1,65 @@
+# Generated by Django 4.2.8 on 2024-03-13 07:03
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("facility", "0423_patientconsultation_consent_records_and_more"),
+ ]
+
+ def populate_patientregistration_death_datetime(apps, schema_editor):
+ PatientRegistration = apps.get_model("facility", "PatientRegistration")
+
+ patients = (
+ PatientRegistration.objects.only("last_consultation")
+ .filter(last_consultation__death_datetime__isnull=False)
+ .annotate(new_death_datetime=models.F("last_consultation__death_datetime"))
+ )
+
+ for patient in patients:
+ patient.death_datetime = patient.new_death_datetime
+
+ PatientRegistration.objects.bulk_update(
+ patients, ["death_datetime"], batch_size=1000
+ )
+
+ operations = [
+ migrations.RemoveField(
+ model_name="historicalpatientregistration",
+ name="age",
+ ),
+ migrations.RemoveField(
+ model_name="patientregistration",
+ name="age",
+ ),
+ migrations.AddField(
+ model_name="historicalpatientregistration",
+ name="death_datetime",
+ field=models.DateTimeField(default=None, null=True),
+ ),
+ migrations.AddField(
+ model_name="patientregistration",
+ name="death_datetime",
+ field=models.DateTimeField(default=None, null=True),
+ ),
+ migrations.AlterField(
+ model_name="historicalpatientregistration",
+ name="year_of_birth",
+ field=models.IntegerField(
+ null=True, validators=[django.core.validators.MinValueValidator(1900)]
+ ),
+ ),
+ migrations.AlterField(
+ model_name="patientregistration",
+ name="year_of_birth",
+ field=models.IntegerField(
+ null=True, validators=[django.core.validators.MinValueValidator(1900)]
+ ),
+ ),
+ migrations.RunPython(
+ populate_patientregistration_death_datetime,
+ migrations.RunPython.noop,
+ ),
+ ]
diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py
index 1c5408bebe..090fee13fe 100644
--- a/care/facility/models/patient.py
+++ b/care/facility/models/patient.py
@@ -1,10 +1,10 @@
-import datetime
import enum
from django.contrib.postgres.aggregates import ArrayAgg
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import JSONField
+from django.utils import timezone
from simple_history.models import HistoricalRecords
from care.abdm.models import AbhaNumber
@@ -109,7 +109,6 @@ class TestTypeEnum(enum.Enum):
# name_old = EncryptedCharField(max_length=200, default="")
name = models.CharField(max_length=200, default="")
- age = models.PositiveIntegerField(null=True, blank=True)
gender = models.IntegerField(choices=GENDER_CHOICES, blank=False)
# phone_number_old = EncryptedCharField(max_length=14, validators=[phone_number_regex], default="")
@@ -128,7 +127,8 @@ class TestTypeEnum(enum.Enum):
pincode = models.IntegerField(default=0, blank=True, null=True)
date_of_birth = models.DateField(default=None, null=True)
- year_of_birth = models.IntegerField(default=0, null=True)
+ year_of_birth = models.IntegerField(validators=[MinValueValidator(1900)], null=True)
+ death_datetime = models.DateTimeField(default=None, null=True)
nationality = models.CharField(
max_length=255, default="", verbose_name="Nationality of Patient"
@@ -426,7 +426,7 @@ class TestTypeEnum(enum.Enum):
objects = BaseManager()
def __str__(self):
- return "{} - {} - {}".format(self.name, self.age, self.get_gender_display())
+ return f"{self.name} - {self.year_of_birth} - {self.get_gender_display()}"
@property
def tele_consultation_history(self):
@@ -458,30 +458,13 @@ def save(self, *args, **kwargs) -> None:
if self.district is not None:
self.state = self.district.state
- self.year_of_birth = (
- self.date_of_birth.year
- if self.date_of_birth is not None
- else datetime.datetime.now().year - self.age
- )
-
- today = datetime.date.today()
-
if self.date_of_birth:
- self.age = (
- today.year
- - self.date_of_birth.year
- - (
- (today.month, today.day)
- < (self.date_of_birth.month, self.date_of_birth.day)
- )
- )
- elif self.year_of_birth:
- self.age = today.year - self.year_of_birth
+ self.year_of_birth = self.date_of_birth.year
self.date_of_receipt_of_information = (
self.date_of_receipt_of_information
if self.date_of_receipt_of_information is not None
- else datetime.datetime.now()
+ else timezone.now()
)
self._alias_recovery_to_recovered()
diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py
index 8f6797f997..91dad91de4 100644
--- a/care/facility/models/patient_consultation.py
+++ b/care/facility/models/patient_consultation.py
@@ -286,6 +286,20 @@ def get_related_consultation(self):
def __str__(self):
return f"{self.patient.name}<>{self.facility.name}"
+ def save(self, *args, **kwargs):
+ """
+ # Removing Patient Hospital Change on Referral
+ if not self.pk or self.referred_to is not None:
+ # pk is None when the consultation is created
+ # referred to is not null when the person is being referred to a new facility
+ self.patient.facility = self.referred_to or self.facility
+ self.patient.save()
+ """
+ if self.death_datetime and self.patient.death_datetime != self.death_datetime:
+ self.patient.death_datetime = self.death_datetime
+ self.patient.save(update_fields=["death_datetime"])
+ super(PatientConsultation, self).save(*args, **kwargs)
+
class Meta:
constraints = [
models.CheckConstraint(
diff --git a/care/facility/models/patient_icmr.py b/care/facility/models/patient_icmr.py
index 5d33c25ed3..677b278322 100644
--- a/care/facility/models/patient_icmr.py
+++ b/care/facility/models/patient_icmr.py
@@ -1,6 +1,7 @@
import datetime
from dateutil.relativedelta import relativedelta
+from django.utils import timezone
from care.facility.models import (
DISEASE_CHOICES_MAP,
@@ -45,26 +46,18 @@ class Meta:
# instance.__class__ = PatientSampleICMR
# return instance
+ def get_age_delta(self):
+ start = self.date_of_birth or timezone.datetime(self.year_of_birth, 1, 1).date()
+ end = (self.death_datetime or timezone.now()).date()
+ return relativedelta(end, start)
+
@property
- def age_years(self):
- if self.date_of_birth is not None:
- age_years = relativedelta(datetime.datetime.now(), self.date_of_birth).years
- else:
- age_years = relativedelta(
- datetime.datetime.now(),
- datetime.datetime(year=self.year_of_birth, month=1, day=1),
- ).years
- return age_years
+ def age_years(self) -> int:
+ return self.get_age_delta().year
@property
- def age_months(self):
- if self.date_of_birth is None or self.year_of_birth is None:
- age_months = 0
- else:
- age_months = relativedelta(
- datetime.datetime.now(), self.date_of_birth
- ).months
- return age_months
+ def age_months(self) -> int:
+ return self.get_age_delta().months
@property
def email(self):
diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py
index 446249e949..fcc93584b4 100644
--- a/care/utils/tests/test_utils.py
+++ b/care/utils/tests/test_utils.py
@@ -238,7 +238,6 @@ def create_facility(
def get_patient_data(cls, district, state) -> dict:
return {
"name": "Foo",
- "age": 32,
"date_of_birth": date(1992, 4, 1),
"gender": 2,
"is_medical_worker": True,
diff --git a/data/dummy/facility.json b/data/dummy/facility.json
index 2104a23cd1..9976ca3646 100644
--- a/data/dummy/facility.json
+++ b/data/dummy/facility.json
@@ -4393,7 +4393,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient",
- "age": 18,
"gender": 1,
"phone_number": "+919987455444",
"emergency_phone_number": "+919898797775",
@@ -4474,7 +4473,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Test E2E User",
- "age": 22,
"gender": 1,
"phone_number": "+919765259927",
"emergency_phone_number": "+919228973557",
@@ -4555,7 +4553,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 1",
- "age": 22,
"gender": 1,
"phone_number": "+919192495353",
"emergency_phone_number": "+919460491040",
@@ -4636,7 +4633,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 2",
- "age": 22,
"gender": 1,
"phone_number": "+919112608904",
"emergency_phone_number": "+919110616234",
@@ -4717,7 +4713,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 3",
- "age": 22,
"gender": 1,
"phone_number": "+919640229897",
"emergency_phone_number": "+919135436547",
@@ -4798,7 +4793,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 4",
- "age": 22,
"gender": 1,
"phone_number": "+919762277015",
"emergency_phone_number": "+919342634016",
@@ -4879,7 +4873,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 5",
- "age": 22,
"gender": 1,
"phone_number": "+919303212282",
"emergency_phone_number": "+919229738916",
@@ -4960,7 +4953,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 6",
- "age": 22,
"gender": 1,
"phone_number": "+919740701377",
"emergency_phone_number": "+919321666516",
@@ -5041,7 +5033,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 7",
- "age": 22,
"gender": 1,
"phone_number": "+919148299129",
"emergency_phone_number": "+919267280161",
@@ -5122,7 +5113,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 8",
- "age": 22,
"gender": 1,
"phone_number": "+919490490290",
"emergency_phone_number": "+919828674710",
@@ -5203,7 +5193,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 9",
- "age": 22,
"gender": 1,
"phone_number": "+919983927490",
"emergency_phone_number": "+919781111140",
@@ -5284,7 +5273,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 10",
- "age": 22,
"gender": 1,
"phone_number": "+919849511866",
"emergency_phone_number": "+919622326248",
@@ -5365,7 +5353,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 11",
- "age": 22,
"gender": 1,
"phone_number": "+919343556704",
"emergency_phone_number": "+919967920474",
@@ -5446,7 +5433,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 12",
- "age": 22,
"gender": 1,
"phone_number": "+919320374643",
"emergency_phone_number": "+919493558024",
@@ -5527,7 +5513,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 13",
- "age": 22,
"gender": 1,
"phone_number": "+919292990239",
"emergency_phone_number": "+919992258784",
@@ -5608,7 +5593,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 14",
- "age": 22,
"gender": 1,
"phone_number": "+919650206292",
"emergency_phone_number": "+919596454242",
@@ -5689,7 +5673,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 15",
- "age": 22,
"gender": 1,
"phone_number": "+919266236581",
"emergency_phone_number": "+919835286558",
@@ -5770,7 +5753,6 @@
"nearest_facility": null,
"meta_info": null,
"name": "Dummy Patient 16",
- "age": 22,
"gender": 1,
"phone_number": "+919243083817",
"emergency_phone_number": "+919924971004",
From 182b43fcc15e1d61328d7b5284de39734f5c3296 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 3 Apr 2024 11:12:52 +0530
Subject: [PATCH 2/9] Bump the boto group with 2 updates (#2044)
Bumps the boto group with 2 updates: [boto3](https://github.com/boto/boto3) and [boto3-stubs](https://github.com/youtype/mypy_boto3_builder).
Updates `boto3` from 1.34.65 to 1.34.75
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.34.65...1.34.75)
Updates `boto3-stubs` from 1.34.65 to 1.34.75
- [Release notes](https://github.com/youtype/mypy_boto3_builder/releases)
- [Commits](https://github.com/youtype/mypy_boto3_builder/commits)
---
updated-dependencies:
- dependency-name: boto3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: boto
- dependency-name: boto3-stubs
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: boto
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vignesh Hari
---
Pipfile | 4 ++--
Pipfile.lock | 29 +++++++++++++++--------------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/Pipfile b/Pipfile
index 61b01767b0..d4ffa91468 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,7 +6,7 @@ name = "pypi"
[packages]
argon2-cffi = "==23.1.0"
authlib = "==1.2.1"
-boto3 = "==1.34.65"
+boto3 = "==1.34.75"
celery = "==5.3.6"
django = "==4.2.10"
django-environ = "==0.11.2"
@@ -48,7 +48,7 @@ redis-om = "==0.2.1"
[dev-packages]
black = "==23.9.1"
-boto3-stubs = {extras = ["s3", "boto3"], version = "==1.34.65"}
+boto3-stubs = {extras = ["s3", "boto3"], version = "==1.34.75"}
coverage = "==7.4.0"
debugpy = "==1.8.1"
django-coverage-plugin = "==3.1.0"
diff --git a/Pipfile.lock b/Pipfile.lock
index 90f6080148..8bbef9e4ff 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "ea4fe23094588fde2796a4107473d14dc5a47a9795a08582da5182dac2b5c4fb"
+ "sha256": "d3f8439435571930893eb20d0599cf4de93bfb4646965c3725af4fdd966b8138"
},
"pipfile-spec": 6,
"requires": {
@@ -94,20 +94,20 @@
},
"boto3": {
"hashes": [
- "sha256:b611de58ab28940a36c77d7ef9823427ebf25d5ee8277b802f9979b14e780534",
- "sha256:db97f9c29f1806cf9020679be0dd5ffa2aff2670e28e0e2046f98b979be498a4"
+ "sha256:ba5d2104bba4370766036d64ad9021eb6289d154265852a2a821ec6a5e816faa",
+ "sha256:eaec72fda124084105a31bcd67eafa1355b34df6da70cadae0c0f262d8a4294f"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
- "version": "==1.34.65"
+ "version": "==1.34.75"
},
"botocore": {
"hashes": [
- "sha256:92560f8fbdaa9dd221212a3d3a7609219ba0bbf308c13571674c0cda9d8f39e1",
- "sha256:fd7d8742007c220f897cb126b8916ca0cf3724a739d4d716aa5385d7f9d8aeb1"
+ "sha256:06113ee2587e6160211a6bd797e135efa6aa21b5bde97bf455c02f7dff40203c",
+ "sha256:1d7f683d99eba65076dfb9af3b42fa967c64f11111d9699b65757420902aa002"
],
"markers": "python_version >= '3.8'",
- "version": "==1.34.66"
+ "version": "==1.34.75"
},
"celery": {
"hashes": [
@@ -1309,7 +1309,7 @@
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
],
- "markers": "python_version >= '3.8'",
+ "markers": "python_version >= '3.6'",
"version": "==2.2.1"
},
"vine": {
@@ -1412,11 +1412,12 @@
"s3"
],
"hashes": [
- "sha256:105da4a04dcb5e4ddc90f21ab8b24a3423ecfacb4775b8ccd3879574e5dce358",
- "sha256:2afd696c8bb4daf8890ecd75a720e1733cd8b8556eaecc92c36f9b56fc6013bd"
+ "sha256:78093a0bf5a03bc66a79d6cddb9f0eb67b67ed6b008cba4cf394c0c9d11de2c1",
+ "sha256:bb55fe97f474ea800c762592d81369bb6c23a8e53a5b2d8497145f87c1d7640c"
],
+ "index": "pypi",
"markers": "python_version >= '3.8'",
- "version": "==1.34.65"
+ "version": "==1.34.75"
},
"botocore": {
"hashes": [
@@ -1428,11 +1429,11 @@
},
"botocore-stubs": {
"hashes": [
- "sha256:530ea7d66022ec6aa0ba0c5200a2aede5d30b839c632d00962f0cf4f806c6a51",
- "sha256:a5aa1240c3c8ccc62d43916395943896afa81399dc5d4203127cc0ffba20f999"
+ "sha256:0c3835c775db1387246c1ba8063b197604462fba8603d9b36b5dc60297197b2f",
+ "sha256:463248fd1d6e7b68a0c57bdd758d04c6bd0c5c2c3bfa81afdf9d64f0930b59bc"
],
"markers": "python_version >= '3.8' and python_version < '4.0'",
- "version": "==1.34.66"
+ "version": "==1.34.69"
},
"certifi": {
"hashes": [
From ce619ef580c596bbee7423e69b0c90bc78471569 Mon Sep 17 00:00:00 2001
From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com>
Date: Wed, 3 Apr 2024 14:05:27 +0530
Subject: [PATCH 3/9] Add validations for negative values in inventory and min
quanityt (#2005)
Add validations for negative values in inventory
Co-authored-by: Aakash Singh
---
..._facilityinventorylog_quantity_and_more.py | 27 +++++++++++++++++++
care/facility/models/inventory.py | 5 ++--
2 files changed, 30 insertions(+), 2 deletions(-)
create mode 100644 care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py
diff --git a/care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py b/care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py
new file mode 100644
index 0000000000..561a0dbf61
--- /dev/null
+++ b/care/facility/migrations/0422_alter_facilityinventorylog_quantity_and_more.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.10 on 2024-03-22 11:21
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("facility", "0421_merge_20240318_1434"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="facilityinventorylog",
+ name="quantity",
+ field=models.FloatField(
+ default=0, validators=[django.core.validators.MinValueValidator(0.0)]
+ ),
+ ),
+ migrations.AlterField(
+ model_name="facilityinventoryminquantity",
+ name="min_quantity",
+ field=models.FloatField(
+ default=0, validators=[django.core.validators.MinValueValidator(0.0)]
+ ),
+ ),
+ ]
diff --git a/care/facility/models/inventory.py b/care/facility/models/inventory.py
index 934b0128f4..e0eb056e49 100644
--- a/care/facility/models/inventory.py
+++ b/care/facility/models/inventory.py
@@ -1,4 +1,5 @@
from django.contrib.auth import get_user_model
+from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Index
@@ -108,7 +109,7 @@ class FacilityInventoryLog(FacilityBaseModel, FacilityRelatedPermissionMixin):
)
current_stock = models.FloatField(default=0)
quantity_in_default_unit = models.FloatField(default=0)
- quantity = models.FloatField(default=0)
+ quantity = models.FloatField(default=0, validators=[MinValueValidator(0.0)])
unit = models.ForeignKey(
FacilityInventoryUnit, on_delete=models.SET_NULL, null=True, blank=False
)
@@ -157,7 +158,7 @@ class FacilityInventoryMinQuantity(FacilityBaseModel, FacilityRelatedPermissionM
item = models.ForeignKey(
FacilityInventoryItem, on_delete=models.SET_NULL, null=True, blank=False
)
- min_quantity = models.FloatField(default=0)
+ min_quantity = models.FloatField(default=0, validators=[MinValueValidator(0.0)])
class Meta:
constraints = [
From 7aa73b9f5c624c4dca0e680b0b92bf71d9718bdb Mon Sep 17 00:00:00 2001
From: Khavin Shankar
Date: Wed, 3 Apr 2024 19:47:48 +0530
Subject: [PATCH 4/9] Added github workflow for automated release (#2045)
---
.github/workflows/release.yml | 56 +++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 .github/workflows/release.yml
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..1a418a889a
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,56 @@
+name: Create Release on Branch Push
+
+on:
+ push:
+ branches:
+ - production
+
+permissions:
+ contents: write
+
+jobs:
+ release:
+ name: Release on Push
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0 # Necessary to fetch all tags
+
+ - name: Calculate next tag
+ id: calc_tag
+ run: |
+ YEAR=$(date +"%y")
+ WEEK=$(date +"%V")
+ LAST_TAG=$(git tag -l "v$YEAR.$WEEK.*" | sort -V | tail -n1)
+ LAST_TAG=$(echo "$LAST_TAG" | tr -d '\r' | sed 's/[[:space:]]*$//')
+ echo "Last Tag: $LAST_TAG"
+ if [[ $LAST_TAG == "" ]]; then
+ MINOR=0
+ else
+ MINOR=$(echo $LAST_TAG | awk -F '.' '{print $NF}')
+ echo "Minor Version: $MINOR"
+ MINOR=$((MINOR + 1))
+ fi
+ TAG="v$YEAR.$WEEK.$MINOR"
+ echo "TAG=$TAG" >> $GITHUB_ENV
+ echo "Next Tag: $TAG"
+ - name: Configure git
+ run: |
+ git config user.name github-actions
+ git config user.email github-actions@github.com
+ - name: Create and push tag
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ git tag -a "$TAG" -m "Release $TAG"
+ git push origin "$TAG"
+ - name: Create release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh release create "$TAG" \
+ --repo="$GITHUB_REPOSITORY" \
+ --title="$TAG" \
+ --generate-notes \
+ --draft
From ef53bec2dd44ce78588980ef76767c4a41086d79 Mon Sep 17 00:00:00 2001
From: Aakash Singh
Date: Wed, 3 Apr 2024 21:48:10 +0530
Subject: [PATCH 5/9] Fixed race condition that caused service to be unhealthy
and merge migrations (#2048)
---
.../migrations/0425_merge_20240403_2055.py | 15 +++++++++++++++
docker-compose.local.yaml | 10 ++++++++--
docker-compose.pre-built.yaml | 19 +++++++++++++++----
docker-compose.yaml | 9 +++++----
docker/dev.Dockerfile | 2 +-
scripts/celery-dev.sh | 2 +-
scripts/celery_beat-ecs.sh | 2 +-
scripts/celery_beat.sh | 2 +-
scripts/celery_worker-ecs.sh | 2 +-
scripts/celery_worker.sh | 2 +-
scripts/start-dev.sh | 2 +-
scripts/start-ecs.sh | 2 +-
scripts/start.sh | 2 +-
13 files changed, 52 insertions(+), 19 deletions(-)
create mode 100644 care/facility/migrations/0425_merge_20240403_2055.py
diff --git a/care/facility/migrations/0425_merge_20240403_2055.py b/care/facility/migrations/0425_merge_20240403_2055.py
new file mode 100644
index 0000000000..6668820484
--- /dev/null
+++ b/care/facility/migrations/0425_merge_20240403_2055.py
@@ -0,0 +1,15 @@
+# Generated by Django 4.2.10 on 2024-04-03 15:25
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("facility", "0422_alter_facilityinventorylog_quantity_and_more"),
+ (
+ "facility",
+ "0424_remove_patientregistration_age_and_add_patientregistration_death_datetime",
+ ),
+ ]
+
+ operations = []
diff --git a/docker-compose.local.yaml b/docker-compose.local.yaml
index 27d2a2ff63..aeb971ccd6 100644
--- a/docker-compose.local.yaml
+++ b/docker-compose.local.yaml
@@ -14,15 +14,21 @@ services:
ports:
- "9000:9000"
- "9876:9876" #debugpy
+ restart: unless-stopped
depends_on:
- - db
- - redis
+ db:
+ condition: service_started
+ redis:
+ condition: service_started
+ celery:
+ condition: service_healthy
celery:
image: care_local
env_file:
- ./docker/.local.env
entrypoint: [ "bash", "scripts/celery-dev.sh" ]
+ restart: unless-stopped
depends_on:
- db
- redis
diff --git a/docker-compose.pre-built.yaml b/docker-compose.pre-built.yaml
index 2d614adfe8..19dbd14194 100644
--- a/docker-compose.pre-built.yaml
+++ b/docker-compose.pre-built.yaml
@@ -6,9 +6,14 @@ services:
env_file:
- ./docker/.prebuilt.env
entrypoint: [ "bash", "start-ecs.sh" ]
+ restart: unless-stopped
depends_on:
- - db
- - redis
+ db:
+ condition: service_started
+ redis:
+ condition: service_started
+ celery-beat:
+ condition: service_healthy
ports:
- "9000:9000"
@@ -17,15 +22,21 @@ services:
env_file:
- ./docker/.prebuilt.env
entrypoint: [ "bash", "celery_worker-ecs.sh" ]
+ restart: unless-stopped
depends_on:
- - db
- - redis
+ db:
+ condition: service_started
+ redis:
+ condition: service_started
+ celery-beat:
+ condition: service_healthy
celery-beat:
image: "ghcr.io/coronasafe/care:latest"
env_file:
- ./docker/.prebuilt.env
entrypoint: [ "bash", "celery_beat-ecs.sh" ]
+ restart: unless-stopped
depends_on:
- db
- redis
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 2972849920..7959c927d3 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -7,7 +7,7 @@ networks:
services:
db:
image: postgres:alpine
- restart: always
+ restart: unless-stopped
env_file:
- ./docker/.prebuilt.env
volumes:
@@ -15,12 +15,13 @@ services:
redis:
image: redis/redis-stack-server:6.2.6-v10
- restart: always
+ restart: unless-stopped
volumes:
- redis-data:/data
localstack:
image: localstack/localstack:latest
+ restart: unless-stopped
environment:
- AWS_DEFAULT_REGION=ap-south-1
- EDGE_PORT=4566
@@ -34,8 +35,8 @@ services:
- "4566:4566"
fidelius:
- image: khavinshankar/fidelius:v1.0
- restart: always
+ image: khavinshankar/fidelius:latest
+ restart: unless-stopped
volumes:
postgres-data:
diff --git a/docker/dev.Dockerfile b/docker/dev.Dockerfile
index f71f5f6464..3a916db775 100644
--- a/docker/dev.Dockerfile
+++ b/docker/dev.Dockerfile
@@ -25,7 +25,7 @@ HEALTHCHECK \
--interval=10s \
--timeout=5s \
--start-period=10s \
- --retries=12 \
+ --retries=24 \
CMD ["/app/scripts/healthcheck.sh"]
WORKDIR /app
diff --git a/scripts/celery-dev.sh b/scripts/celery-dev.sh
index 4e1c0ad22c..ac63afe1b3 100755
--- a/scripts/celery-dev.sh
+++ b/scripts/celery-dev.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-printf "celery" >> /tmp/container-role
+printf "celery" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
diff --git a/scripts/celery_beat-ecs.sh b/scripts/celery_beat-ecs.sh
index beeb0e1ca6..e664458132 100755
--- a/scripts/celery_beat-ecs.sh
+++ b/scripts/celery_beat-ecs.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-printf "celery-beat" >> /tmp/container-role
+printf "celery-beat" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
diff --git a/scripts/celery_beat.sh b/scripts/celery_beat.sh
index abdc97efba..e4aa5d083f 100755
--- a/scripts/celery_beat.sh
+++ b/scripts/celery_beat.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-printf "celery-beat" >> /tmp/container-role
+printf "celery-beat" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
diff --git a/scripts/celery_worker-ecs.sh b/scripts/celery_worker-ecs.sh
index 701378b461..840b9e73b5 100755
--- a/scripts/celery_worker-ecs.sh
+++ b/scripts/celery_worker-ecs.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-printf "celery-worker" >> /tmp/container-role
+printf "celery-worker" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
diff --git a/scripts/celery_worker.sh b/scripts/celery_worker.sh
index bc291f737e..9c9e4d739d 100755
--- a/scripts/celery_worker.sh
+++ b/scripts/celery_worker.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-printf "celery-worker" >> /tmp/container-role
+printf "celery-worker" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh
index f79ff45a20..b4996c3490 100755
--- a/scripts/start-dev.sh
+++ b/scripts/start-dev.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
-printf "api" >> /tmp/container-role
+printf "api" > /tmp/container-role
cd /app
diff --git a/scripts/start-ecs.sh b/scripts/start-ecs.sh
index ac9fb2c745..fb731bf198 100755
--- a/scripts/start-ecs.sh
+++ b/scripts/start-ecs.sh
@@ -4,7 +4,7 @@ set -o errexit
set -o pipefail
set -o nounset
-printf "api" >> /tmp/container-role
+printf "api" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
diff --git a/scripts/start.sh b/scripts/start.sh
index d660d8ec9c..bebefa9a66 100755
--- a/scripts/start.sh
+++ b/scripts/start.sh
@@ -4,7 +4,7 @@ set -o errexit
set -o pipefail
set -o nounset
-printf "api" >> /tmp/container-role
+printf "api" > /tmp/container-role
if [ -z "${DATABASE_URL}" ]; then
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
From 397695dd4faee85377071bac893204248b5c54e6 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Thu, 4 Apr 2024 21:15:16 +0530
Subject: [PATCH 6/9] Update discharge summary to reflect age and year of birth
(#2053)
* Update discharge summary to reflect age and year of birth
fixes #2052
* suggestions from codereview
---
care/facility/models/patient.py | 6 ++++++
.../reports/patient_discharge_summary_pdf.html | 14 ++++++++++----
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py
index 090fee13fe..d40733515e 100644
--- a/care/facility/models/patient.py
+++ b/care/facility/models/patient.py
@@ -1,5 +1,6 @@
import enum
+from dateutil.relativedelta import relativedelta
from django.contrib.postgres.aggregates import ArrayAgg
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
@@ -470,6 +471,11 @@ 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()
+ end = (self.death_datetime or timezone.now()).date()
+ return relativedelta(end, start).years
+
def annotate_diagnosis_ids(*args, **kwargs):
return ArrayAgg(
"last_consultation__diagnoses__diagnosis_id",
diff --git a/care/templates/reports/patient_discharge_summary_pdf.html b/care/templates/reports/patient_discharge_summary_pdf.html
index 8884e1d43a..48c05155b1 100644
--- a/care/templates/reports/patient_discharge_summary_pdf.html
+++ b/care/templates/reports/patient_discharge_summary_pdf.html
@@ -46,11 +46,17 @@
Gender: {{patient.get_gender_display}}
- Age: {{patient.age}}
-
-
- Date of Birth: {{patient.date_of_birth}}
+ 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}}
From cda2db978ded57d6ec3b0a8d73a177165e40c0c6 Mon Sep 17 00:00:00 2001
From: Aakash Singh
Date: Mon, 8 Apr 2024 16:46:40 +0530
Subject: [PATCH 7/9] fix custom user authentication (#2059)
---
config/authentication.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/config/authentication.py b/config/authentication.py
index ee86acab94..cfea6116ae 100644
--- a/config/authentication.py
+++ b/config/authentication.py
@@ -1,4 +1,5 @@
import json
+from datetime import datetime
import jwt
import requests
@@ -151,7 +152,7 @@ def get_user(self, validated_token, facility):
user_type=User.TYPE_VALUE_MAP["Nurse"],
verified=True,
asset=asset_obj,
- age=10,
+ date_of_birth=datetime.now().date(),
)
asset_user.save()
return asset_user
@@ -202,7 +203,7 @@ def get_user(self, validated_token):
phone_number="917777777777",
user_type=User.TYPE_VALUE_MAP["Volunteer"],
verified=True,
- age=10,
+ date_of_birth=datetime.now().date(),
)
user.save()
return user
From 8b9bd0b0a80b5f8c1c243784e3581a3c8b6e1e94 Mon Sep 17 00:00:00 2001
From: Aakash Singh
Date: Mon, 8 Apr 2024 17:18:35 +0530
Subject: [PATCH 8/9] Staging release 3 for v24.15.0 (#2060)
fix custom user authentication (#2059)
---
config/authentication.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/config/authentication.py b/config/authentication.py
index ee86acab94..cfea6116ae 100644
--- a/config/authentication.py
+++ b/config/authentication.py
@@ -1,4 +1,5 @@
import json
+from datetime import datetime
import jwt
import requests
@@ -151,7 +152,7 @@ def get_user(self, validated_token, facility):
user_type=User.TYPE_VALUE_MAP["Nurse"],
verified=True,
asset=asset_obj,
- age=10,
+ date_of_birth=datetime.now().date(),
)
asset_user.save()
return asset_user
@@ -202,7 +203,7 @@ def get_user(self, validated_token):
phone_number="917777777777",
user_type=User.TYPE_VALUE_MAP["Volunteer"],
verified=True,
- age=10,
+ date_of_birth=datetime.now().date(),
)
user.save()
return user
From 76a72d8df1ed1a6da29dcca5a5e6094896ec8765 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Mon, 8 Apr 2024 20:41:10 +0530
Subject: [PATCH 9/9] Switch to using year of birth for patient transfer
confirmation (#2061)
* Switch to using year of birth for patient transfer confirmation
* update tests
---
care/facility/api/serializers/patient.py | 8 ++++----
care/facility/tests/test_patient_api.py | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py
index 5ba675a734..e94fa0d25c 100644
--- a/care/facility/api/serializers/patient.py
+++ b/care/facility/api/serializers/patient.py
@@ -452,11 +452,11 @@ class PatientTransferSerializer(serializers.ModelSerializer):
class Meta:
model = PatientRegistration
- fields = ("facility", "date_of_birth", "patient", "facility_object")
+ fields = ("facility", "year_of_birth", "patient", "facility_object")
- def validate_date_of_birth(self, value):
- if self.instance and self.instance.date_of_birth != value:
- raise serializers.ValidationError("Date of birth does not match")
+ def validate_year_of_birth(self, value):
+ if self.instance and self.instance.year_of_birth != value:
+ raise serializers.ValidationError("Year of birth does not match")
return value
def create(self, validated_data):
diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py
index c3621e66eb..01b017c34d 100644
--- a/care/facility/tests/test_patient_api.py
+++ b/care/facility/tests/test_patient_api.py
@@ -448,7 +448,7 @@ def test_patient_transfer(self):
response = self.client.post(
f"/api/v1/patient/{self.patient.external_id}/transfer/",
{
- "date_of_birth": "1992-04-01",
+ "year_of_birth": 1992,
"facility": self.destination_facility.external_id,
},
)
@@ -477,7 +477,7 @@ def test_transfer_with_active_consultation_same_facility(self):
response = self.client.post(
f"/api/v1/patient/{self.patient.external_id}/transfer/",
{
- "date_of_birth": "1992-04-01",
+ "year_of_birth": 1992,
"facility": self.facility.external_id,
},
)
@@ -496,7 +496,7 @@ def test_transfer_disallowed_by_facility(self):
response = self.client.post(
f"/api/v1/patient/{self.patient.external_id}/transfer/",
{
- "date_of_birth": "1992-04-01",
+ "year_of_birth": 1992,
"facility": self.destination_facility.external_id,
},
)