diff --git a/qmra/risk_assessment/exports.py b/qmra/risk_assessment/exports.py
new file mode 100644
index 0000000..1c68fb7
--- /dev/null
+++ b/qmra/risk_assessment/exports.py
@@ -0,0 +1,97 @@
+from zipfile import ZipFile
+import base64
+from django.db.models import QuerySet
+from django.template.loader import render_to_string
+
+from qmra.risk_assessment.models import RiskAssessment, RiskAssessmentResult, Inflow, Treatment
+import pandas as pd
+
+from qmra.risk_assessment.plots import risk_plots
+
+
+def inflows_as_df(inflows: QuerySet[Inflow]):
+ dfs = []
+ for inflow in inflows.all():
+ dfs += [pd.DataFrame({
+ "Pathogen": [inflow.pathogen],
+ "Minimum Concentration": [inflow.min],
+ "Maximum Concentration": [inflow.max],
+ })]
+ return pd.concat(dfs)
+
+
+def treatments_as_df(treatments: QuerySet[Treatment]) -> pd.DataFrame:
+ dfs = []
+ for t in treatments.all():
+ dfs += [pd.DataFrame({
+ "Treatment": [t.name] * 3,
+ "Pathogen group": ["Viruses", "Bacteria", "Protozoa"],
+ "Maximum LRV": [t.viruses_max, t.bacteria_max, t.protozoa_max],
+ "Minimum LRV": [t.viruses_min, t.bacteria_min, t.protozoa_min]
+ })]
+ return pd.concat(dfs)
+
+
+def risk_assessment_result_as_df(pathogen: str, r: RiskAssessmentResult) -> pd.DataFrame:
+ return pd.DataFrame({
+ ("", "pathogen"): [pathogen] * 2,
+ ("", "stat"): ["Maximum LRV", "Minimum LRV"],
+ ("Infection prob.", "min"): [
+ r.infection_maximum_lrv_min, r.infection_minimum_lrv_min
+ ],
+ ("Infection prob.", "25%"): [
+ r.infection_maximum_lrv_q1, r.infection_minimum_lrv_q1
+ ],
+ ("Infection prob.", "50%"): [
+ r.infection_maximum_lrv_median, r.infection_minimum_lrv_median
+ ],
+ ("Infection prob.", "75%"): [
+ r.infection_maximum_lrv_q3, r.infection_minimum_lrv_q3
+ ],
+ ("Infection prob.", "max"): [
+ r.infection_maximum_lrv_max, r.infection_minimum_lrv_max
+ ],
+ ("DALYs pppy", "min"): [
+ r.dalys_maximum_lrv_min, r.dalys_minimum_lrv_min
+ ],
+ ("DALYs pppy", "25%"): [
+ r.dalys_maximum_lrv_q1, r.dalys_minimum_lrv_q1
+ ],
+ ("DALYs pppy", "50%"): [
+ r.dalys_maximum_lrv_median, r.dalys_minimum_lrv_median
+ ],
+ ("DALYs pppy", "75%"): [
+ r.dalys_maximum_lrv_q3, r.dalys_minimum_lrv_q3
+ ],
+ ("DALYs pppy", "max"): [
+ r.dalys_maximum_lrv_max, r.dalys_minimum_lrv_max
+ ],
+ })
+
+
+def results_as_df(results: dict[str, RiskAssessmentResult]) -> pd.DataFrame:
+ dfs = []
+ for pathogen, r in results.items():
+ dfs += [risk_assessment_result_as_df(pathogen, r)]
+ return pd.concat(dfs)
+
+
+def risk_assessment_as_zip(buffer, risk_assessment: RiskAssessment):
+ inflows = inflows_as_df(risk_assessment.inflows)
+ treatments = treatments_as_df(risk_assessment.treatments)
+ results = results_as_df({r.pathogen: r for r in risk_assessment.results.all()})
+ plots = risk_plots(risk_assessment.results.all(), "png")
+ report = render_to_string("assessment-result-export.html",
+ context=dict(results=risk_assessment.results.all(),
+ infection_risk=risk_assessment.infection_risk,
+ risk_plot_data=base64.b64encode(plots[0]).decode("utf-8"),
+ daly_plot_data=base64.b64encode(plots[1]).decode("utf-8")))
+ with ZipFile(buffer, mode="w") as archive:
+ archive.mkdir("exposure-assessment")
+ archive.mkdir("results-plots")
+ archive.writestr("exposure-assessment/inflows.csv", inflows.to_csv(sep=",", decimal=".", index=False))
+ archive.writestr("exposure-assessment/treatments.csv", treatments.to_csv(sep=",", decimal=".", index=False))
+ archive.writestr(f"{risk_assessment.name}-result.csv", results.to_csv(sep=",", decimal=".", index=False))
+ archive.writestr(f"{risk_assessment.name}-report.html", report)
+ archive.writestr("results-plots/infection-probability.png", plots[0])
+ archive.writestr("results-plots/dalys-pppy.png", plots[1])
diff --git a/qmra/risk_assessment/forms.py b/qmra/risk_assessment/forms.py
index a37cfbe..2629180 100644
--- a/qmra/risk_assessment/forms.py
+++ b/qmra/risk_assessment/forms.py
@@ -34,7 +34,7 @@ def __init__(self, *args, **kwargs):
self.helper.form_tag = False
self.helper.label_class = "text-muted small"
self.helper.layout = Layout(
- Row(Column("name"), Column("description")),
+ Row(Column("name"), Column("description"), css_id="name-and-description"),
Row(Column("exposure_name"), Column("events_per_year"), Column("volume_per_event"), css_id="exposure-form-fieldset"),
# Row("source_name", css_id="source-form")
)
diff --git a/qmra/risk_assessment/plots.py b/qmra/risk_assessment/plots.py
index bc7b853..274fe4b 100644
--- a/qmra/risk_assessment/plots.py
+++ b/qmra/risk_assessment/plots.py
@@ -26,7 +26,7 @@
COLOR_SEQS = dict(min=MIN_COLOR_SEQ, max=MAX_COLOR_SEQ, none=NONE_COLOR_SEQ)
-def risk_plots(risk_assessment_results, risk_category="none"):
+def risk_plots(risk_assessment_results, output_type="div"):
infection_prob_fig = go.Figure()
dalys_fig = go.Figure()
for i, r in enumerate(risk_assessment_results):
@@ -111,7 +111,7 @@ def risk_plots(risk_assessment_results, risk_category="none"):
dalys_fig.update_traces(
marker_size=8
)
-
- return plot(infection_prob_fig, output_type="div", config={'displayModeBar': False}, include_plotlyjs=False), \
- plot(dalys_fig, output_type="div", config={'displayModeBar': False}, include_plotlyjs=False)
-
+ if output_type == "div":
+ return plot(infection_prob_fig, output_type="div", config={'displayModeBar': False}, include_plotlyjs=False), \
+ plot(dalys_fig, output_type="div", config={'displayModeBar': False}, include_plotlyjs=False)
+ return infection_prob_fig.to_image(format=output_type), dalys_fig.to_image(format=output_type)
diff --git a/qmra/risk_assessment/templates/assessment-configurator.html b/qmra/risk_assessment/templates/assessment-configurator.html
index 9ed6912..e804abd 100644
--- a/qmra/risk_assessment/templates/assessment-configurator.html
+++ b/qmra/risk_assessment/templates/assessment-configurator.html
@@ -11,7 +11,7 @@
Risk assessment parameters
{% include "risk-assessment-form-fieldset.html" with risk_assessment_form=risk_assessment_form %}
{% include "inflows-form-fieldset.html" with inflow_form=inflow_form source_name_field=risk_assessment_form.source_name %}
{% include "treatments-form-fieldset.html" with treatment_form=treatment_form add_treatment_form=add_treatment_form %}
-
+
@@ -20,19 +20,19 @@
Risk assessment parameters
-
Result
-
References
-
User guide
@@ -96,6 +96,7 @@ Treatments
{% endblock %}
{% block script %}
+ {% include "guided-tour.html" %}
\ No newline at end of file
diff --git a/qmra/risk_assessment/templates/inflows-form-js.html b/qmra/risk_assessment/templates/inflows-form-js.html
index 95058a2..2ed3db1 100644
--- a/qmra/risk_assessment/templates/inflows-form-js.html
+++ b/qmra/risk_assessment/templates/inflows-form-js.html
@@ -13,7 +13,7 @@
const y = parseFloat(x.toFixed(4));
var step = "any";
if (y < 1 && y > 0){
- step = "."+"0".repeat(-Math.floor(Math.log(y)/Math.log(10))-1)+"1";
+ step = "."+"0".repeat(-Math.floor(Math.log(y)/Math.log(10)))+"1";
}
elem.step = step;
}},
@@ -22,7 +22,7 @@
const y = parseFloat(x.toFixed(4));
var step = "any";
if (y < 1 && y > 0){
- step = "."+"0".repeat(-Math.floor(Math.log(y)/Math.log(10))-1)+"1";
+ step = "."+"0".repeat(-Math.floor(Math.log(y)/Math.log(10)))+"1";
}
elem.step = step;
}}
diff --git a/qmra/risk_assessment/templates/risk-assessment-list.html b/qmra/risk_assessment/templates/risk-assessment-list.html
index 7ec1c4a..0e60d19 100644
--- a/qmra/risk_assessment/templates/risk-assessment-list.html
+++ b/qmra/risk_assessment/templates/risk-assessment-list.html
@@ -77,6 +77,7 @@
+
ZIP
compare
diff --git a/qmra/risk_assessment/tests/test_export.py b/qmra/risk_assessment/tests/test_export.py
new file mode 100644
index 0000000..0a9eebd
--- /dev/null
+++ b/qmra/risk_assessment/tests/test_export.py
@@ -0,0 +1,55 @@
+import io
+
+from assertpy import assert_that
+from django.test import TestCase
+import pandas as pd
+
+from qmra.risk_assessment import exports
+from qmra.risk_assessment.models import RiskAssessment, Inflow, Treatment, DefaultTreatments
+from qmra.risk_assessment.risk import assess_risk
+from qmra.user.models import User
+
+
+class TestResultExport(TestCase):
+
+ def test_that(self):
+ given_user = User.objects.create_user("test-user2", "test-user@test.com", "password")
+ given_user.save()
+ given_ra = RiskAssessment.objects.create(
+ user=given_user,
+ events_per_year=1,
+ volume_per_event=2,
+ )
+ given_ra.save()
+ given_inflows = [
+ Inflow.objects.create(
+ risk_assessment=given_ra,
+ pathogen="Rotavirus",
+ min=0.1, max=0.2
+ ),
+ Inflow.objects.create(
+ risk_assessment=given_ra,
+ pathogen="Campylobacter jejuni",
+ min=0.1, max=0.2
+ ),
+ Inflow(
+ risk_assessment=given_ra,
+ pathogen="Cryptosporidium parvum",
+ min=0.1, max=0.2
+ ),
+ ]
+ given_treatments = [
+ Treatment.from_default(t, given_ra)
+ for t in list(DefaultTreatments.data.values())[:3]
+ ]
+ given_ra.inflows.set(given_inflows, bulk=False)
+ given_ra.treatments.set(given_treatments, bulk=False)
+
+ results = assess_risk(given_ra, given_inflows, given_treatments)
+ given_ra = RiskAssessment.objects.get(pk=given_ra.id)
+ with io.BytesIO() as buffer:
+ exports.risk_assessment_as_zip(buffer, given_ra)
+ buffer.seek(0)
+ with open("test.zip", "wb") as f:
+ f.write(buffer.getvalue())
+ assert_that(buffer).is_not_none()
diff --git a/qmra/risk_assessment/urls.py b/qmra/risk_assessment/urls.py
index ecc8c7a..1849d80 100644
--- a/qmra/risk_assessment/urls.py
+++ b/qmra/risk_assessment/urls.py
@@ -23,6 +23,11 @@
views.risk_assessment_result,
name="assessment-result",
),
+ path(
+ "assessment/
/export",
+ views.export_risk_assessment,
+ name="assessment-export",
+ ),
path(
"assessment/results",
views.risk_assessment_result,
diff --git a/qmra/risk_assessment/views.py b/qmra/risk_assessment/views.py
index f871b13..1fe4fd2 100644
--- a/qmra/risk_assessment/views.py
+++ b/qmra/risk_assessment/views.py
@@ -1,8 +1,11 @@
+import io
+
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render
from django.urls import reverse
+from qmra.risk_assessment import exports
from qmra.risk_assessment.forms import InflowFormSet, RiskAssessmentForm, TreatmentFormSet, AddTreatmentForm
from qmra.risk_assessment.models import Inflow, RiskAssessment, Treatment
from qmra.risk_assessment.plots import risk_plots
@@ -155,3 +158,18 @@ def risk_assessment_result(request):
context=dict(results=results,
infection_risk=risk_assessment.infection_risk,
risk_plot=plots[0], daly_plot=plots[1]))
+
+
+@login_required(login_url="/login")
+def export_risk_assessment(request, risk_assessment_id=None):
+ if risk_assessment_id is not None:
+ risk_assessment = RiskAssessment.objects.get(id=risk_assessment_id)
+ if not any(risk_assessment.results.all()):
+ risk_assessment = assess_and_save_results(risk_assessment)
+ response = HttpResponse(content_type="application/zip")
+ response["Content-Disposition"] = (
+ "attachment; filename=" + str(risk_assessment.name) + ".zip"
+ )
+ exports.risk_assessment_as_zip(response, risk_assessment)
+ return response
+ return HttpResponse(status=422)
\ No newline at end of file
diff --git a/qmra/templates/head.html b/qmra/templates/head.html
new file mode 100644
index 0000000..dbab03f
--- /dev/null
+++ b/qmra/templates/head.html
@@ -0,0 +1,23 @@
+{% load static %}
+
+
+
+
+ {% block title %} QMRA {% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block script%}
+ {%endblock%}
+
\ No newline at end of file
diff --git a/qmra/templates/layout.html b/qmra/templates/layout.html
index 11cada0..4a97408 100644
--- a/qmra/templates/layout.html
+++ b/qmra/templates/layout.html
@@ -1,27 +1,9 @@
{% load static %}
-
-
-
-
- {% block title %} QMRA {% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {% include "head.html" %}
{% block script%}
{%endblock%}
-