Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropped age column from patient registration and annotated age in patient viewset #1966

Merged
merged 24 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
335f66a
drop field `age` and compute age on demand
rithviknishad Mar 12, 2024
9ce7e6a
Update filters and related usages
rithviknishad Mar 12, 2024
4f9b806
add min value validator for year of birth
rithviknishad Mar 12, 2024
8f8a67f
remove age from test utils
rithviknishad Mar 12, 2024
7c4fbb3
remove unnecessary default
rithviknishad Mar 12, 2024
0315723
Add field `death_datetime` in patient registration
rithviknishad Mar 13, 2024
4819851
annotate age in days
rithviknishad Mar 13, 2024
110a708
suggestion from code review
rithviknishad Mar 13, 2024
b2390de
revert unintended changes
rithviknishad Mar 13, 2024
2a40095
Merge branch 'develop' into fix-patient-age
rithviknishad Mar 13, 2024
ced7877
skip serializing age
rithviknishad Mar 14, 2024
24b84c5
Merge branch 'develop' into fix-patient-age
rithviknishad Mar 14, 2024
4c72575
rebase migrations
rithviknishad Mar 14, 2024
f53138a
Merge branch 'develop' into fix-patient-age
rithviknishad Mar 18, 2024
24c813e
rebase migrations
rithviknishad Mar 18, 2024
9964b85
drop age from dummy data
rithviknishad Mar 18, 2024
0391880
Revert "drop age from dummy data"
rithviknishad Mar 18, 2024
2d4b2ea
drop patient registration's age from dummy data
rithviknishad Mar 18, 2024
acca03b
select only `last_consultation` in custom migration
rithviknishad Mar 19, 2024
0b258ff
format code
rithviknishad Mar 19, 2024
1fd7996
Merge branch 'develop' into fix-patient-age
rithviknishad Mar 25, 2024
a0f5fe1
rebase migrations
rithviknishad Mar 25, 2024
b8f18c4
Merge branch 'develop' into fix-patient-age
sainak Mar 30, 2024
5bd404a
rebase migrations
rithviknishad Mar 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
27 changes: 16 additions & 11 deletions care/facility/api/serializers/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,12 @@
"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 @@
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(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 Expand Up @@ -361,8 +366,8 @@

if meta_info:
if patient.meta_info is None:
meta_info_obj = PatientMetaInfo.objects.create(**meta_info)
patient.meta_info = meta_info_obj
meta_info_obj = PatientMetaInfo.objects.create(**meta_info)
patient.meta_info = meta_info_obj

Check warning on line 370 in care/facility/api/serializers/patient.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/serializers/patient.py#L369-L370

Added lines #L369 - L370 were not covered by tests
else:
for key, value in meta_info.items():
setattr(patient.meta_info, key, value)
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", "0421_merge_20240318_1434"),
]

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 @@
# 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 @@
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 @@
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()}"

Check warning on line 429 in care/facility/models/patient.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/patient.py#L429

Added line #L429 was not covered by tests

@property
def tele_consultation_history(self):
Expand Down Expand Up @@ -458,30 +458,13 @@
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
3 changes: 3 additions & 0 deletions care/facility/models/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ def save(self, *args, **kwargs):
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:
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 @@
# instance.__class__ = PatientSampleICMR
# return instance

def get_age_delta(self):
rithviknishad marked this conversation as resolved.
Show resolved Hide resolved
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)

Check warning on line 52 in care/facility/models/patient_icmr.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/patient_icmr.py#L50-L52

Added lines #L50 - L52 were not covered by tests

@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

Check warning on line 56 in care/facility/models/patient_icmr.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/patient_icmr.py#L56

Added line #L56 was not covered by tests

@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

Check warning on line 60 in care/facility/models/patient_icmr.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/patient_icmr.py#L60

Added line #L60 was not covered by tests

@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
Loading