Skip to content

Commit

Permalink
Merge pull request #20 from SocialFinanceDigitalLabs/16-split-datacon…
Browse files Browse the repository at this point in the history
…tainer-processes

16 split datacontainer processes, add bootstrap datepicker and implement historic view
  • Loading branch information
franciscobmacedo committed Mar 18, 2024
2 parents 024e672 + cedd126 commit dc1f837
Show file tree
Hide file tree
Showing 18 changed files with 399 additions and 77 deletions.
13 changes: 13 additions & 0 deletions dm_regional_app/charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import plotly.express as px
from demand_model.multinomial.predictor import Prediction


def prediction_chart(prediction: Prediction):
df_pp = prediction.population.unstack().reset_index()
df_pp.columns = ["from", "date", "value"]

# visualise prediction using unstacked dataframe
fig = px.line(df_pp, y="value", x="date", color="from")
fig.update_layout(title="Prediction")
fig_html = fig.to_html(full_html=False)
return fig_html
88 changes: 83 additions & 5 deletions dm_regional_app/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,92 @@
import pandas as pd
from bootstrap_datepicker_plus.widgets import DatePickerInput
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Layout, Row, Submit
from django import forms


class PredictFilter(forms.Form):
start_date = forms.DateField(
widget=forms.widgets.DateInput(attrs={'type': 'date'}),
label='Start Date',
widget=DatePickerInput(),
label="Start Date",
required=True,
)
end_date = forms.DateField(
widget=DatePickerInput(range_from="start_date"),
label="End Date",
required=True,
)


class HistoricDataFilter(forms.Form):
start_date = forms.DateField(
widget=DatePickerInput(),
label="Start Date",
required=True,
)
end_date = forms.DateField(
widget=forms.widgets.DateInput(attrs={'type': 'date'}),
label='End Date',
widget=DatePickerInput(range_from="start_date"),
label="End Date",
required=True,
)
la = forms.ChoiceField(label="Local Authority", required=False, choices=[])
placement_types = forms.MultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
label="Placement Type",
required=False,
choices=[],
)

def __init__(self, *args, **kwargs):
la_choices = kwargs.pop("la_choices")
placement_type_choices = kwargs.pop("placement_type_choices")

super().__init__(*args, **kwargs)
self.fields["la"].choices = [("all", "All")] + [(la, la) for la in la_choices]
self.fields["la"].initial = "all"
self.fields["placement_types"].choices = [
(placement_type, placement_type)
for placement_type in placement_type_choices
]
self.helper = FormHelper()
self.helper.layout = Layout(
Row(
Column("start_date", css_class="form-group col-md-6 mb-0"),
Column("end_date", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
Row(
Column("la", css_class="form-group col-md-6 mb-0"),
Column("placement_types", css_class="form-group col-md-4 mb-0"),
css_class="form-row",
),
Submit("submit", "Filter"),
)

def filter_by_start_date(self, data: pd.DataFrame):
data = data.loc[data.DECOM.dt.date >= self.cleaned_data["start_date"]]
return data

def filter_by_end_date(self, data: pd.DataFrame):
data = data.loc[data.DEC.dt.date <= self.cleaned_data["end_date"]]
return data

def filter_by_la(self, data: pd.DataFrame):
if self.cleaned_data["la"] != "all":
data = data.loc[data.LA == self.cleaned_data["la"]]
return data

def filter_by_placement_type(self, data: pd.DataFrame):
if self.cleaned_data["placement_types"] != []:
loc = data.placement_type.astype(str).isin(
self.cleaned_data["placement_types"]
)
data = data.loc[loc]
return data

def apply_filters(self, data: pd.DataFrame):
data = self.filter_by_start_date(data)
data = self.filter_by_end_date(data)
data = self.filter_by_la(data)
data = self.filter_by_placement_type(data)
return data
1 change: 1 addition & 0 deletions dm_regional_app/templates/dm_regional_app/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
rel="stylesheet"
href="{% static 'css/bootstrap.min.css' %}" />
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
</head>
<body>
{% block navbar %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% if user.is_authenticated %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='dashboard' name='Dashboard' url_names="['dashboard']" %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='chart_view' name='Prediction' url_names="['chart_view']" %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='prediction' name='Prediction' url_names="['prediction']" %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='scenarios' name='Scenarios' url_names="['scenarios', 'scenario_detail']" %}
{% else %}
{% include 'dm_regional_app/includes/navbar-item.html' with url='home' name='Home' url_names="['home']" %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<h1>Regional Demand Model</h1>
<article class="mb-3">
<h4>Welcome {{ request.user.first_name }}</h4>
<a class="btn btn-primary" href="{% url 'chart_view' %}">go to Prediction</a>
<a class="btn btn-primary" href="{% url 'prediction' %}">go to Prediction</a>
</article>
<article class="mb-3">
{% include 'dm_regional_app/includes/scenarios_list.html' with scenarios=scenarios %}
Expand Down
23 changes: 23 additions & 0 deletions dm_regional_app/templates/dm_regional_app/views/historic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends "dm_regional_app/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
{{ form.media }}
<form method="POST">
{% csrf_token %}
{% crispy form %}
</form>
<div class="row">
<div class="col">
<h1>Total # of entry into care</h1>
<p>{{ entry_into_care_count }}</p>
</div>
<div class="col">
<h1>Total # of exiting care</h1>
<p>{{ exiting_care_count }}</p>
</div>
</div>
<div class="alert alert-primary" role="alert">
TODO - add the relevant charts here!

</div>
{% endblock %}
2 changes: 1 addition & 1 deletion dm_regional_app/templates/dm_regional_app/views/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ <h1>Welcome to the Regional Demand Model</h1>
<a class="btn btn-primary" href="{% url 'login' %}">login</a>
{% endif %}
<a class="btn btn-primary" href="{% url 'dashboard' %}">go to Dashboard</a>
<a class="btn btn-primary" href="{% url 'chart_view' %}">go to Prediction</a>
<a class="btn btn-primary" href="{% url 'prediction' %}">go to Prediction</a>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@


{% block content %}
{{ form.media }}
<form method="POST">
{% csrf_token %}
{{ form|crispy }}

<button class="btn btn-primary" type="submit">Go</button>
{% crispy form %}
</form>

{{chart|safe}}
Expand Down
2 changes: 1 addition & 1 deletion dm_regional_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
urlpatterns = [
path("", views.home, name="home"),
path("dashboard/", views.dashboard, name="dashboard"),
path("predict/", views.chart_view, name="chart_view"),
path("predict/", views.prediction, name="prediction"),
path("scenario/<int:pk>", views.scenario_detail, name="scenario_detail"),
path("scenarios", views.scenarios, name="scenarios"),
]
129 changes: 90 additions & 39 deletions dm_regional_app/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import plotly.express as px
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render

from dm_regional_app.forms import PredictFilter
from dm_regional_app.charts import prediction_chart
from dm_regional_app.forms import HistoricDataFilter, PredictFilter
from dm_regional_app.models import Scenario
from dm_regional_app.predictor import predict
from ssda903.predictor import predict
from ssda903.reader import read_data


def home(request):
Expand All @@ -21,50 +24,98 @@ def dashboard(request):


@login_required
def chart_view(request):
def prediction(request):
# read data
datacontainer = read_data(source=settings.DATA_SOURCE)

if request.method == "POST":
# initialize form with data
form = PredictFilter(request.POST)

if form.is_valid():
start_date = form.cleaned_data["start_date"]
end_date = form.cleaned_data["end_date"]
# Call predict function with the provided dates
prediction = predict(
source="sample://v1.zip", # in the future, this will probably point to some S3 bucket with regiomal 903 files.
start=start_date,
end=end_date,
)
# Render the result along with the form
# unstack predicted dataframe
df_pp = prediction.population.unstack().reset_index()
df_pp.columns = ["from", "date", "value"]

# visualise prediction using unstacked dataframe
fig = px.line(df_pp, y="value", x="date", color="from")
fig.update_layout(title="Prediction")
fig_html = fig.to_html(full_html=False)

return render(
request,
"dm_regional_app/views/chart.html",
context={"form": form, "chart": fig_html},
)

else:
start_date = datacontainer.end_date - relativedelta(months=6)
end_date = datacontainer.end_date

else:
# set default dates
start_date = datacontainer.end_date - relativedelta(months=6)
end_date = datacontainer.end_date

# initialize form with default dates
form = PredictFilter(
initial={
"start_date": start_date,
"end_date": end_date,
}
)

# Call predict function with default dates
prediction = predict(
data=datacontainer.enriched_view,
start=start_date,
end=end_date,
)

# build chart
chart = prediction_chart(prediction)
return render(
request, "dm_regional_app/views/prediction.html", {"form": form, "chart": chart}
)


@login_required
def historic_data(request):
if request.method == "POST":
# read data
datacontainer = read_data(source=settings.DATA_SOURCE)

# initialize form with data
form = HistoricDataFilter(
request.POST,
la_choices=datacontainer.unique_las,
placement_type_choices=datacontainer.unique_placement_types,
)
if form.is_valid():
data = form.apply_filters(datacontainer.enriched_view)
else:
data = datacontainer.enriched_view
else:
form = PredictFilter()
# Call predict function with the provided dates
prediction = predict(
source="sample://v1.zip", # in the future, this will probably point to some S3 bucket with regiomal 903 files.
# read data
datacontainer = read_data(source=settings.DATA_SOURCE)

# initialize form with default dates
form = HistoricDataFilter(
initial={
"start_date": datacontainer.start_date,
"end_date": datacontainer.end_date,
},
la_choices=datacontainer.unique_las,
placement_type_choices=datacontainer.unique_placement_types,
)
# Render the result along with the form
# unstack predicted dataframe
df_pp = prediction.population.unstack().reset_index()
df_pp.columns = ["from", "date", "value"]

# visualise prediction using unstacked dataframe
fig = px.line(df_pp, y="value", x="date", color="from")
fig.update_layout(title="Prediction")
fig_html = fig.to_html(full_html=False)
data = datacontainer.enriched_view

# TODO AMY - create the relevant charts from this dataset, according to the designs
entry_into_care_count = data.loc[
data.placement_type_before
== datacontainer.config.PlacementCategories.NOT_IN_CARE
]["CHILD"].nunique()
exiting_care_count = data.loc[
data.placement_type_after
== datacontainer.config.PlacementCategories.NOT_IN_CARE
]["CHILD"].nunique()

return render(
request, "dm_regional_app/views/chart.html", {"form": form, "chart": fig_html}
request,
"dm_regional_app/views/historic.html",
{
"form": form,
"entry_into_care_count": entry_into_care_count,
"exiting_care_count": exiting_care_count,
},
)


Expand Down
5 changes: 5 additions & 0 deletions dm_regional_site/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"dm_regional_app.apps.DmRegionalAppConfig",
"crispy_forms",
"crispy_bootstrap5",
"bootstrap_datepicker_plus",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -140,3 +141,7 @@
# crispy forms
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"


# data source
DATA_SOURCE = "sample://v1.zip"
Loading

0 comments on commit dc1f837

Please sign in to comment.