Skip to content

Commit

Permalink
Merge pull request #4 from KWB-R/experimental/plotly-js
Browse files Browse the repository at this point in the history
presentation version
  • Loading branch information
adaurat authored Oct 22, 2024
2 parents b917126 + 0b4e787 commit 6905ace
Show file tree
Hide file tree
Showing 49 changed files with 1,254 additions and 1,056 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/static
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ WORKDIR qmra
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

RUN apt update && apt upgrade -y
RUN apt-get update -y && apt upgrade -y && apt-get clean && rm -rf /var/lib/apt/lists/*
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt
RUN pip install gunicorn
RUN pip install --upgrade pip &&\
pip install --no-cache-dir -r requirements.txt &&\
pip install --no-cache-dir gunicorn

# copy project
COPY ./qmra ./qmra
Expand Down
2 changes: 2 additions & 0 deletions qmra/management/commands/collect_static_default_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

def get_default_pathogens():
pathogen = pd.read_csv("raw_public_data/tbl_pathogen.csv", encoding="windows-1251")
pathogen = pathogen[pathogen.id.isin((3, 32, 34))]
health = pd.read_csv("raw_public_data/tbl_health.csv", encoding="windows-1251")
# NOTE: HEALTH has been modified 'pathogen_id' is now 'pathogen_group'!!
# i.e. Rotavirus -> Viruses, jejuni -> Bacteria, parvum -> Protozoa
Expand All @@ -27,6 +28,7 @@ def get_default_inflows():
sources = pd.read_csv("raw_public_data/tbl_waterSource.csv", encoding="windows-1251")
inflows = pd.merge(inflows, sources, left_on="source_id", right_on="id", how="left").rename(columns={"name": "source_name"})
inflows = pd.merge(inflows, pathogens, left_on="pathogen_id", right_on="id", how="left").rename(columns={"name": "pathogen_name"})
inflows = inflows[inflows.pathogen_id.isin((3, 32, 34))]
return inflows.loc[:, ["source_name", "pathogen_name", "min", "max"]]


Expand Down
62 changes: 38 additions & 24 deletions qmra/risk_assessment/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from django import forms
from django.core.exceptions import ValidationError
from django.forms import modelformset_factory, HiddenInput
from crispy_forms.bootstrap import AppendedText, Modal
from django.forms import modelformset_factory
from crispy_forms.bootstrap import AppendedText
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Row, Column, Div, HTML, Button
from crispy_forms.layout import Layout, Field, Row, Column, HTML

from qmra.risk_assessment.models import Inflow, DefaultPathogens, DefaultTreatments, Treatment, \
from qmra.risk_assessment.models import Inflow, DefaultTreatments, Treatment, \
RiskAssessment


def _zero_if_none(x): return x if x is not None else 0


class RiskAssessmentForm(forms.ModelForm):
class Meta:
model = RiskAssessment
Expand All @@ -23,7 +26,7 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["source_name"].label = "Select a source type to add inflows"
self.fields["source_name"].label = "Select a source water type to add pathogen concentrations"
self.fields['exposure_name'].widget.attrs['min'] = 0
self.fields['volume_per_event'].widget.attrs['min'] = 0
self.helper = FormHelper(self)
Expand Down Expand Up @@ -53,14 +56,15 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial.update(kwargs.get("initial", {}))
self.helper = FormHelper(self)
self.helper.render_hidden_fields = False
self.helper.render_unmentioned_fields = False
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.label_class = "text-muted small"
self.fields['pathogen'].choices = DefaultPathogens.choices()
self.fields['pathogen'].label = "Pathogen"
self.fields['pathogen'].disabled = True
self.fields['pathogen'].label = "Reference Pathogen"
self.fields['min'].widget.attrs['min'] = 0
self.fields['max'].widget.attrs['min'] = 0
self.fields['min'].label = "Minimum concentration"
Expand All @@ -75,11 +79,12 @@ def __init__(self, *args, **kwargs):

def clean(self):
cleaned_data = super().clean()
if cleaned_data["min"] < 0:
mn, mx = cleaned_data.get("min", 0), cleaned_data.get("max", 0)
if mn < 0:
self.add_error("min", "this field must be positive or 0")
if cleaned_data["max"] < 0:
if mx < 0:
self.add_error("max", "this field must be positive or 0")
if cleaned_data.get("min", 0) > cleaned_data.get("max", 0):
if mn > mx:
msg = "minimum concentration must be less than maximum concentration"
self.add_error("min", msg)
self.add_error("max", msg)
Expand All @@ -88,16 +93,19 @@ def clean(self):

InflowFormSetBase = modelformset_factory(
Inflow, form=InflowForm,
extra=0, max_num=30, min_num=0,
can_delete=True, can_delete_extra=True
extra=0, max_num=3, min_num=3,
can_delete=False, can_delete_extra=False
)


class InflowFormSet(InflowFormSetBase):
def get_deletion_widget(self):
return forms.CheckboxInput(attrs=dict(label="remove"))

def __init__(self, *args, **kwargs):
kwargs["initial"] = [
{"pathogen": "Rotavirus"},
{"pathogen": 'Campylobacter jejuni'},
{"pathogen": "Cryptosporidium parvum"},
]
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
Expand Down Expand Up @@ -146,7 +154,7 @@ def __init__(self, *args, **kwargs):
self.fields['protozoa_max'].widget.attrs['min'] = 0
label_style = "class='text-muted text-center w-100' style='margin-top: .4em;'"
self.helper.layout = Layout(
Field("name", css_class="disabled-input text-center"),
Field("name", css_class="disabled-input d-none"),
Row(Column(HTML(f"<div></div>")),
Column(HTML(f"<label class='text-muted text-center w-100'>Minimum</label>")),
Column(HTML(f"<label class='text-muted text-center w-100'>Maximum</label>"))),
Expand All @@ -161,24 +169,30 @@ def __init__(self, *args, **kwargs):

def clean(self):
cleaned_data = super().clean()
if cleaned_data["bacteria_min"] < 0:
b_min = _zero_if_none(cleaned_data.get("bacteria_min", 0))
b_max = _zero_if_none(cleaned_data.get("bacteria_max", 0))
v_min = _zero_if_none(cleaned_data.get("viruses_min", 0))
v_max = _zero_if_none(cleaned_data.get("viruses_max", 0))
p_min = _zero_if_none(cleaned_data.get("protozoa_min", 0))
p_max = _zero_if_none(cleaned_data.get("protozoa_max", 0))
if b_min < 0:
self.add_error("bacteria_min", "this field must be positive or 0")
if cleaned_data["bacteria_max"] < 0:
if b_max < 0:
self.add_error("bacteria_max", "this field must be positive or 0")
if cleaned_data["viruses_min"] < 0:
if v_min < 0:
self.add_error("viruses_min", "this field must be positive or 0")
if cleaned_data["viruses_max"] < 0:
if v_max < 0:
self.add_error("viruses_max", "this field must be positive or 0")
if cleaned_data["protozoa_min"] < 0:
if p_min < 0:
self.add_error("protozoa_min", "this field must be positive or 0")
if cleaned_data["protozoa_max"] < 0:
if p_max < 0:
self.add_error("protozoa_max", "this field must be positive or 0")
msg = "min. must be less than max"
if cleaned_data.get("bacteria_min", 0) > cleaned_data.get("bacteria_max", 0):
if b_min > b_max:
self.add_error("bacteria_min", msg)
if cleaned_data.get("viruses_min", 0) > cleaned_data.get("viruses_max", 0):
if v_min > v_max:
self.add_error("viruses_min", msg)
if cleaned_data.get("protozoa_min", 0) > cleaned_data.get("protozoa_max", 0):
if p_min > p_max:
self.add_error("protozoa_min", msg)
return cleaned_data

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.0.6 on 2024-10-18 14:52

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('risk_assessment', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='inflow',
name='pathogen',
field=models.CharField(choices=[('', '---------'), ('Bacteria', [('Campylobacter jejuni', 'Campylobacter jejuni')]), ('Viruses', [('Rotavirus', 'Rotavirus')]), ('Protozoa', [('Cryptosporidium parvum', 'Cryptosporidium parvum')])], max_length=256),
),
migrations.AlterField(
model_name='riskassessmentresult',
name='dalys_risk',
field=models.CharField(choices=[('min', 'min'), ('max', 'max'), ('none', 'none')], max_length=4),
),
migrations.AlterField(
model_name='riskassessmentresult',
name='infection_risk',
field=models.CharField(choices=[('min', 'min'), ('max', 'max'), ('none', 'none')], max_length=4),
),
migrations.AlterField(
model_name='riskassessmentresult',
name='pathogen',
field=models.CharField(choices=[('', '---------'), ('Bacteria', [('Campylobacter jejuni', 'Campylobacter jejuni')]), ('Viruses', [('Rotavirus', 'Rotavirus')]), ('Protozoa', [('Cryptosporidium parvum', 'Cryptosporidium parvum')])], max_length=256),
),
]
47 changes: 39 additions & 8 deletions qmra/risk_assessment/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ class DefaultSources(StaticEntity):

@classmethod
def choices(cls):
grouped = {grp: list(v) for grp, v in groupby(sorted(cls.data.values(), key=lambda x: x.name), key=lambda x: x.name.split(",")[0])}
grouped = {grp: list(v) for grp, v in
groupby(sorted(cls.data.values(), key=lambda x: x.name), key=lambda x: x.name.split(",")[0])}
return [
("", "---------"),
*[(k, [(x.name, x.name) for x in v]) for k, v in grouped.items()],
Expand Down Expand Up @@ -294,7 +295,8 @@ class DefaultExposures(StaticEntity):

@classmethod
def choices(cls):
grouped = {grp: list(v) for grp, v in groupby(sorted(cls.data.values(), key=lambda x: x.name), key=lambda x: x.name.split(",")[0])}
grouped = {grp: list(v) for grp, v in
groupby(sorted(cls.data.values(), key=lambda x: x.name), key=lambda x: x.name.split(",")[0])}
return [
("", "---------"),
*[(k, [(x.name, x.name) for x in sorted(v, key=lambda x: x.name)]) for k, v in grouped.items()],
Expand Down Expand Up @@ -322,7 +324,8 @@ class RiskAssessment(models.Model):

@property
def infection_risk(self):
return any(r.infection_risk for r in self.results.all())
risks = {r.infection_risk for r in self.results.all()}
return 'max' if 'max' in risks else ("min" if 'min' in risks else 'none')

@property
def dalys_risk(self):
Expand All @@ -336,6 +339,9 @@ def pathogens_labels(self):
def treatments_labels(self):
return ", ".join([treatment.name for treatment in self.treatments.all()])

def results_list(self):
return [r.as_dict() for r in self.results.all()]

def __str__(self):
return self.name

Expand All @@ -344,10 +350,8 @@ class RiskAssessmentResult(models.Model):
risk_assessment = models.ForeignKey(RiskAssessment, on_delete=models.CASCADE, related_name="results")

pathogen = models.CharField(choices=DefaultPathogens.choices(), max_length=256)

infection_risk = models.BooleanField()
dalys_risk = models.BooleanField()

infection_risk = models.CharField(choices=[("min", "min"), ("max", "max"), ("none", "none")], max_length=4)
dalys_risk = models.CharField(choices=[("min", "min"), ("max", "max"), ("none", "none")], max_length=4)
infection_minimum_lrv_min = models.FloatField()
infection_minimum_lrv_max = models.FloatField()
infection_minimum_lrv_q1 = models.FloatField()
Expand All @@ -358,7 +362,6 @@ class RiskAssessmentResult(models.Model):
infection_maximum_lrv_q1 = models.FloatField()
infection_maximum_lrv_q3 = models.FloatField()
infection_maximum_lrv_median = models.FloatField()

dalys_minimum_lrv_min = models.FloatField()
dalys_minimum_lrv_max = models.FloatField()
dalys_minimum_lrv_q1 = models.FloatField()
Expand All @@ -369,3 +372,31 @@ class RiskAssessmentResult(models.Model):
dalys_maximum_lrv_q1 = models.FloatField()
dalys_maximum_lrv_q3 = models.FloatField()
dalys_maximum_lrv_median = models.FloatField()

def as_dict(self):
return dict(
ra_name=self.risk_assessment.name,
pathogen=self.pathogen,
infection_risk=self.infection_risk,
dalys_risk=self.dalys_risk,
infection_minimum_lrv_min=self.infection_minimum_lrv_min,
infection_minimum_lrv_max=self.infection_minimum_lrv_max,
infection_minimum_lrv_q1=self.infection_minimum_lrv_q1,
infection_minimum_lrv_q3=self.infection_minimum_lrv_q3,
infection_minimum_lrv_median=self.infection_minimum_lrv_median,
infection_maximum_lrv_min=self.infection_maximum_lrv_min,
infection_maximum_lrv_max=self.infection_maximum_lrv_max,
infection_maximum_lrv_q1=self.infection_maximum_lrv_q1,
infection_maximum_lrv_q3=self.infection_maximum_lrv_q3,
infection_maximum_lrv_median=self.infection_maximum_lrv_median,
dalys_minimum_lrv_min=self.dalys_minimum_lrv_min,
dalys_minimum_lrv_max=self.dalys_minimum_lrv_max,
dalys_minimum_lrv_q1=self.dalys_minimum_lrv_q1,
dalys_minimum_lrv_q3=self.dalys_minimum_lrv_q3,
dalys_minimum_lrv_median=self.dalys_minimum_lrv_median,
dalys_maximum_lrv_min=self.dalys_maximum_lrv_min,
dalys_maximum_lrv_max=self.dalys_maximum_lrv_max,
dalys_maximum_lrv_q1=self.dalys_maximum_lrv_q1,
dalys_maximum_lrv_q3=self.dalys_maximum_lrv_q3,
dalys_maximum_lrv_median=self.dalys_maximum_lrv_median
)
Loading

0 comments on commit 6905ace

Please sign in to comment.