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

WIP adding database storage for symptom report setup #64

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
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
157 changes: 154 additions & 3 deletions reports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import secrets

from django.core.exceptions import ValidationError
from django.core.validators import validate_comma_separated_integer_list
from django.db import models
from django.db.models import F
from django.forms.widgets import CheckboxSelectMultiple
from django.utils.timezone import now

Expand Down Expand Up @@ -100,14 +102,132 @@ def valid_member(self):

class Symptom(models.Model):
label = models.CharField(max_length=20, choices=SYMPTOM_CHOICES, unique=True)
available = models.BooleanField(default=False)
verbose = models.CharField(max_length=40, blank=True)

def __str__(self):
return self.label

def __unicode__(self):
return self.label

def save(self, *args, **kwargs):
if not self.verbose:
self.verbose = self.label
super().save(*args, **kwargs)


class ReportDisplayMixin(object):
"""
Structure report symptoms and categories for display purposes.
"""

@staticmethod
def order_by_symptoms(symptomitem_queryset):
return (
symptomitem_queryset.annotate(verbose=F("symptom__verbose"))
.extra(select={"ci_verbose": "lower(verbose)"})
.order_by("ci_verbose")
)

@staticmethod
def sort_category_items(categoryitem_queryset, category_ordering):
ordering = [int(i) for i in category_ordering.split(",") if i]
categories = OrderedDict(
[(c.id, c) for c in categoryitem_queryset.order_by("name")]
)
sorted_categories = []
for item in ordering:
try:
sorted_categories.append(categories.pop(item))
except KeyError:
continue
for key in categories.keys():
sorted_categories.append(categories[key])
return sorted_categories

def display_format(self):
formatted = []
symptom_items = self.get_symptom_items()
sorted_categories = self.sort_category_items(
categoryitem_queryset=self.get_category_items(),
category_ordering=self.category_ordering,
)

for category in sorted_categories:
formatted.append(
{
"category": category,
"symptoms": self.order_by_symptoms(
symptom_items.filter(category=category)
),
}
)

formatted.append(
{
"category": None,
"symptoms": self.order_by_symptoms(symptom_items.filter(category=None)),
}
)

return formatted


class ReportSetup(ReportDisplayMixin, models.Model):
"""
The template set of symptoms and categories used for symptom reporting.

These are arranged as:
1. a set of categories (ReportSetupCategoryItems)
2. a set of symptoms (ReportSetupSymptomItems)

Symptoms may be re-used in other ReportSetups (Symptom objects).

Categories are specific to the report setup (there's no generic "Category"
object.) Categories exist for display purposes: they can be ordered, and
symptoms within a category are typically ordered alphabetically according
to their verbose label, e.g. as done in display_format().

A category (ReportSetupCategoryItem) can have symptoms (ReportSetupSymptomItems)
corresponding to it. Or it could have none (an "empty" category).

A symptom in the setup (ReportSetupSymptomItem) may have a category, or may be
unassigned (e.g. displayed later as "Uncategorized symptoms").
"""

title = models.CharField(max_length=30)
category_ordering = models.TextField(
validators=[validate_comma_separated_integer_list], blank=True
)

def __str__(self):
return "{} ({})".format(self.title, self.id)

def get_category_items(self):
return self.reportsetupcategoryitem_set

def get_symptom_items(self):
return self.reportsetupsymptomitem_set


class ReportSetupCategoryItem(models.Model):
report_setup = models.ForeignKey(ReportSetup, on_delete=models.CASCADE)
name = models.CharField(max_length=20)

def __str__(self):
return "{} ({})".format(self.name, self.id)


class ReportSetupSymptomItem(models.Model):
report_setup = models.ForeignKey(ReportSetup, on_delete=models.CASCADE)
category = models.ForeignKey(
ReportSetupCategoryItem, on_delete=models.SET_NULL, null=True
)
symptom = models.ForeignKey(Symptom, on_delete=models.PROTECT)

def __str__(self):
return "{} ({})".format(self.symptom.label, self.id)


"""
# TODO: Implement reporting of diagnostic testing.
Expand All @@ -125,9 +245,12 @@ def __unicode__(self):
"""


class SymptomReport(models.Model):
class SymptomReport(ReportDisplayMixin, models.Model):
member = models.ForeignKey(OpenHumansMember, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
category_ordering = models.TextField(
validators=[validate_comma_separated_integer_list], blank=True
)
fever_guess = models.CharField(
max_length=20, choices=FEVER_CHOICES, null=True, blank=True
)
Expand Down Expand Up @@ -160,6 +283,12 @@ def get_symptom_values(self):
for s in self.symptomreportsymptomitem_set.all()
}

def get_category_items(self):
return self.symptomreportcategoryitem_set

def get_symptom_items(self):
return self.symptomreportsymptomitem_set

@property
def severity(self):
"""Rough attempt to assess "severity" for a report, for display purposes"""
Expand Down Expand Up @@ -195,10 +324,32 @@ def as_json(self):
return json.dumps(data)


class SymptomReportCategoryItem(models.Model):
"""
The ReportSetupCategoryItem information at the time of reporting.
"""

name = models.CharField(max_length=20)
report = models.ForeignKey(SymptomReport, on_delete=models.CASCADE)

def __str__(self):
return "{} ({})".format(self.name, self.id)


class SymptomReportSymptomItem(models.Model):
symptom = models.ForeignKey(Symptom, on_delete=models.CASCADE)
"""
A specific Symptom recorded in a given report.
"""

symptom = models.ForeignKey(Symptom, on_delete=models.PROTECT)
report = models.ForeignKey(SymptomReport, on_delete=models.CASCADE)
intensity = models.IntegerField(choices=SYMPTOM_INTENSITY_CHOICES, default=0)
category = models.ForeignKey(
SymptomReportCategoryItem, on_delete=models.SET_NULL, null=True
)

def __str__(self):
return "{} ({})".format(self.symptom.label, self.id)


class SymptomReportPhysiology(models.Model):
Expand Down