Skip to content

Commit

Permalink
Dropped age column from patient registration and annotated age in p…
Browse files Browse the repository at this point in the history
…atient 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 9964b85.

* drop patient registration's age from dummy data

* select only `last_consultation` in custom migration

Co-authored-by: Aakash Singh <mail@singhaakash.dev>

* format code

* rebase migrations

* rebase migrations

---------

Co-authored-by: Aakash Singh <mail@singhaakash.dev>
  • Loading branch information
rithviknishad and sainak authored Apr 2, 2024
1 parent e97177b commit 1115a8f
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 89 deletions.
2 changes: 1 addition & 1 deletion care/facility/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
23 changes: 14 additions & 9 deletions care/facility/api/serializers/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand All @@ -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"):
Expand Down
87 changes: 67 additions & 20 deletions care/facility/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
),
]
29 changes: 6 additions & 23 deletions care/facility/models/patient.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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="")
Expand All @@ -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"
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand Down
14 changes: 14 additions & 0 deletions care/facility/models/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
27 changes: 10 additions & 17 deletions care/facility/models/patient_icmr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime

from dateutil.relativedelta import relativedelta
from django.utils import timezone

from care.facility.models import (
DISEASE_CHOICES_MAP,
Expand Down Expand Up @@ -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):
Expand Down
1 change: 0 additions & 1 deletion care/utils/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 1115a8f

Please sign in to comment.