Prep for pull request

This commit is contained in:
Nile Walker 2020-12-18 10:12:15 -05:00
parent 4cb3fec40b
commit 7b414b1f15
6 changed files with 169 additions and 89 deletions

View File

@ -1,3 +1,4 @@
import csv
import io
import json
@ -5,6 +6,7 @@ import json
import logging
import os
from datetime import datetime
from datetime import date
from functools import wraps
import connexion
@ -23,8 +25,6 @@ from webassets import Bundle
from flask_executor import Executor
import numpy as np
import matplotlib.pyplot as plt
logging.basicConfig(level=logging.INFO)
# API, fully defined in api.yml
@ -49,6 +49,7 @@ else:
def render_errors(exception):
return Response(json.dumps({"error": str(exception)}), status=500, mimetype="application/json")
connexion_app.add_error_handler(Exception, render_errors)
@ -80,12 +81,10 @@ scss = Bundle(
output='argon.css'
)
assets.register('app_scss', scss)
from communicator import models
from communicator import api
from communicator import forms
import random
# from communicator import scheduler
from communicator import forms
from communicator import api
from communicator import models
connexion_app.add_api('api.yml', base_path='/v1.0')
@ -105,7 +104,6 @@ if app.config['ENABLE_SENTRY']:
# HTML Pages
BASE_HREF = app.config['APPLICATION_ROOT'].strip('/')
def superuser(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@ -118,6 +116,11 @@ def superuser(f):
return f(*args, **kwargs)
return decorated_function
@app.errorhandler(404)
@superuser
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('pages/404.html')
@app.route('/', methods=['GET', 'POST'])
@superuser
@ -138,9 +141,6 @@ def index():
session["index_filter"] = {}
if form.startDate.data:
session["index_filter"]["start_date"] = form.startDate.data
else:
from datetime import date
session["index_filter"]["start_date"] = date.today()
if form.endDate.data:
session["index_filter"]["end_date"] = form.endDate.data
if form.studentId.data:
@ -151,29 +151,47 @@ def index():
session["index_filter"]["email"] = form.email.data
if form.download.data:
download = True
# # Store previous form submission settings in the session, so they are preseved through pagination.
filtered_samples = samples
if "index_filter" in session:
filters = session["index_filter"]
try:
if "start_date" in filters:
samples = samples.filter(Sample.date >= filters["start_date"])
filtered_samples = filtered_samples.filter(Sample.date >= filters["start_date"])
else:
filtered_samples = filtered_samples.filter(Sample.date >= date.today())
if "end_date" in filters:
samples = samples.filter(Sample.date <= filters["end_date"])
filtered_samples = filtered_samples.filter(Sample.date <= filters["end_date"])
if "student_id" in filters:
samples = samples.filter(
filtered_samples = filtered_samples.filter(
Sample.student_id.in_(filters["student_id"].split()))
if "location" in filters:
samples = samples.filter(
filtered_samples = filtered_samples.filter(
Sample.location.in_(filters["location"].split()))
if "email" in filters:
samples = samples.filter(
filtered_samples = filtered_samples.filter(
Sample.email.ilike(filters["email"] + "%"))
except Exception as e:
logging.error(
"Encountered an error building filters, so clearing. " + e)
logging.error("Encountered an error building filters, so clearing. " + str(e))
session["index_filter"] = {}
else:
# Default to Todays Results
filtered_samples = filtered_samples.filter(Sample.date >= date.today())
############# Daily Total #######################
from datetime import date, timedelta
stats = dict()
stats["today"] = samples.filter(Sample.date >= date.today()).count()
############# Last 2 Week Average ###############
today = date.today()
counts = [] # ! Could be calculated in a single pass since data is sorted
for i in range(14):
days_back_start = timedelta(i)
days_back_stop = timedelta(i + 1)
temp = samples.filter(Sample.date <= today - days_back_start)
temp = temp.filter(Sample.date >= today - days_back_stop)
counts.append(temp.count())
stats["weeks"] = sum(counts)/len(counts)
#################################################
# display results
if download:
csv = __make_csv(samples)
@ -186,60 +204,86 @@ def index():
table = SampleTable(samples.paginate(page, 10, error_out=False).items)
chart_data = {"datasets": []}
# Get Active Locations Info
active_stations = ["10", "20", "30", "40", "50", "60"]
# https://stackoverflow.com/questions/19442224/getting-information-for-bins-in-matplotlib-histogram-function
# Seperate Data
# Seperate Data by location and station
location_data = dict()
sample_times = dict()
active_stations = set()
for entry in samples:
for entry in filtered_samples:
loc_code = str(entry.location)[:2]
stat_code= str(entry.location)[2:]
active_stations.add(stat_code)
if loc_code not in location_data:
location_data[loc_code] = [entry]
sample_times[loc_code] = [entry.date.timestamp()]
logging.info(entry.date)
else:
location_data[loc_code].append(entry)
sample_times[loc_code].append(entry.date.timestamp())
# Analysis
i = 0
station_charts = []
location_chart = {"datasets": []}
for loc_code in location_data.keys():
data_dict = dict({
#################################################
############# Build histogram ###################
color = [hash(loc_code), 128, (hash(loc_code) % 256 + 128) % 256]
single_hist = dict({
"label": loc_code,
"borderColor": f'rgba(255,{i*50},{i*20},.7)',
"pointBorderColor": f'rgba(255,{i*50},{i*20},1)',
"borderWidth": 10,
"borderColor": f'rgba({color[0]},{color[1]},{color[2]},.7)',
"pointBorderColor": f'rgba({color[0]},{color[1]},{color[2]},1)',
"borderWidth": 8,
"data": [],
})
hist, bin_edges = np.histogram(np.array(sample_times[loc_code]))#, dtype = np.int64))
#bin_edges = [datetime.fromtimestamp(date) for date in bin_edges]
bins = [bin_edges[i]+(bin_edges[i+1]-bin_edges[i])/2 for i in range(len(bin_edges)-1)]
for cnt, date in zip(hist,bins):
data_dict["data"].append({
"x": datetime.utcfromtimestamp(date), "y": int(cnt)
# https://stackoverflow.com/questions/19442224/getting-information-for-bins-in-matplotlib-histogram-function
hist, bin_edges = np.histogram(np.array(sample_times[loc_code]))
bins = [bin_edges[i]+(bin_edges[i+1]-bin_edges[i]) /
2 for i in range(len(bin_edges)-1)]
for cnt, time in zip(hist, bins):
single_hist["data"].append({
"x": datetime.utcfromtimestamp(time), "y": int(cnt)
})
location_chart["datasets"].append(single_hist)
###### Build Rolling Averaging Graph ##############
chart_data["datasets"].append(data_dict)
i += 1
# Check for Unresponsive
for loc_code in active_stations:
if loc_code not in location_data:
chart_data["datasets"].append({
"label": loc_code,
"borderColor": f'rgba(128,128,128,.7)',
"pointBorderColor": f'rgba(128,128,128,1)',
#################################################
############## Build Station Graph ##############
station_lines = []
# Read Data by station
i = 0
for stat_code in active_stations:
filtered_entries = [_entry for _entry in location_data[loc_code] if str(_entry.location)[2:] == stat_code] # ! Inefficient but works for rn
if len(filtered_entries) == 0: continue
station_line = {"label": stat_code,
"borderColor": f'rgba(50,255,255,.7)',
"pointBorderColor": f'rgba(50,255,255,1)',
"borderWidth": 10,
"data": [{
"x": session["index_filter"]["start_date"], "y": i
}, ],
})
"data": [
{"x": filtered_entries[0].date, "y": i}, {"x": filtered_entries[-1].date, "y": i},
],
}
i += 1
station_lines.append(station_line)
station_charts.append({"datasets": station_lines, "labels" : []})
#################################################
# # Check for Unresponsive
# for loc_code in active_stations:
# if loc_code not in location_data:
# location_dict["datasets"].append({
# "label": loc_code,
# "borderColor": f'rgba(128,128,128,.7)',
# "pointBorderColor": f'rgba(128,128,128,1)',
# "borderWidth": 10,
# "data": [{
# "x": session["index_filter"]["start_date"], "y": i
# }, ],
# })
# i += 1
return render_template('layouts/default.html',
base_href=BASE_HREF,
content=render_template(
@ -248,8 +292,9 @@ def index():
table=table,
action=action,
pagination=pagination,
description_map={},
chart_data=chart_data
location_data=location_chart,
station_data=station_charts,
stats = stats
))

View File

@ -1,15 +1,12 @@
<!-- Core -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!-- Optional JS -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.bundle.js"></script>
<!-- Core -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!-- Optional JS -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.bundle.js"></script>
<!-- Bootstrap Date-Picker Plugin -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css"/>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css" />

View File

@ -80,7 +80,7 @@
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">
<i class="ni ni-tv-2 text-primary"></i> Daily
<i class="ni ni-tv-2 text-primary"></i> Dashboard
</a>
</li>
<li class="nav-item">
@ -107,11 +107,11 @@
<ul class="navbar-nav mb-md-3">
<li class="nav-item">
<a class="nav-link" href="https://github.com/app-generator/flask-argon-dashboard">
<a class="nav-link" href="https://github.com/sartography/uva-covid19-testing-communicator">
<i class="ni ni-ui-04"></i> Source Code
</a>
</li>
<!--
<li class="nav-item">
<a class="nav-link" href="https://appseed.us/admin-dashboards/flask-dashboard-argon">
<i class="ni ni-book-bookmark"></i> App Information
@ -134,7 +134,7 @@
<a class="nav-link" href="https://demos.creative-tim.com/argon-dashboard/docs/getting-started/overview.html">
<i class="ni ni-books"></i> Design Docs
</a>
</li>
</li> -->
</ul>
</div>

View File

@ -11,7 +11,7 @@
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">Total Samples Today</h5>
<span id="stats_traffic" class="h2 font-weight-bold mb-0">N?A</span>
<span id="stats_traffic" class="h2 font-weight-bold mb-0">{{stats.today}}</span>
</div>
<div class="col-auto">
@ -22,7 +22,7 @@
</div>
<p class="mt-3 mb-0 text-muted text-sm">
<span class="text-success mr-2"><i class="fa fa-arrow-up"></i> 3.48%</span>
<span class="text-nowrap">Since last month</span>
<span class="text-nowrap">Since Yesterday</span>
</p>
</div>
</div>
@ -32,9 +32,9 @@
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">New users</h5>
<h5 class="card-title text-uppercase text-muted mb-0">2 Week Average</h5>
<span id="stats_users" class="h2 font-weight-bold mb-0">N/A</span>
<span id="stats_users" class="h2 font-weight-bold mb-0">{{stats.weeks}}</span>
</div>
<div class="col-auto">

View File

@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Be SAFE Notification System</title>
<base href="/">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

View File

@ -9,7 +9,7 @@
<a class="h4 mb-0 text-white text-uppercase d-none d-lg-inline-block" href="./index.html">UVA Communicator
Dashboard</a>
<!-- Form -->
<form class="navbar-search navbar-search-dark form-inline mr-3 d-none d-md-flex ml-lg-auto">
<!-- <form class="navbar-search navbar-search-dark form-inline mr-3 d-none d-md-flex ml-lg-auto">
<div class="form-group mb-0">
<div class="input-group input-group-alternative">
<div class="input-group-prepend">
@ -18,7 +18,7 @@
<input class="form-control" placeholder="Search" type="text">
</div>
</div>
</form>
</form> -->
</div>
</nav>
@ -35,7 +35,7 @@
<div class="row align-items-center">
<div class="col">
<h6 class="text-uppercase text-light ls-1 mb-1">Overview</h6>
<h2 class="text-white mb-0">Location Activity</h2>
<h2 id="chart-title" class="text-white mb-0">Location Activity</h2>
</div>
</div>
</div>
@ -51,13 +51,16 @@
<div class="col-xl-4">
<div class="card shadow">
<form action="{{ action }}" method="post">
<form action="{{ action }}" method="post" id="pageForm">
{{ form.csrf_token() }}
<div class="card-header border-0">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0">Search</h3>
</div>
<div class="col text-right">
<button class="btn btn-sm btn-primary">Search Today</button>
</div>
<div class="col text-right">
<button type="submit" class="btn btn-sm btn-primary">Run Search</button>
</div>
@ -81,7 +84,7 @@
<input name="{{field.name}}" />
{%else%}
<div class="form-field-input">{{ field }}</div>
<div class="form-field-help">{{ description_map[field.name] }}</div>
{% for error in field.errors %}
<div class="form-field-error">{{ error }}</div>
{% endfor %}
@ -137,14 +140,17 @@
</div>
</div>
<script>
var data = JSON.parse('{{ chart_data | tojson | safe}}');
var location_data = JSON.parse('{{ location_data | tojson | safe}}');
var station_data = JSON.parse('{{ station_data | tojson | safe}}');
var ctx = document.getElementById('chart-sales').getContext('2d');
var timeFormat = 'YYYY-MM-DD h:mm:ss.SSS';
//////////////////
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'line',
data: data,
data: location_data,
// Configuration options go here
options: {
@ -156,6 +162,7 @@
legend: {
display: true,
position: "right",
onClick: location_legend,
labels: {
usePointStyle: true,
fontColor: '#FFFFFF',
@ -185,4 +192,34 @@
}
}
);
function station_legend(e, legendItem) {
chart.config.data = location_data;
chart.config.options.legend.onClick= location_legend;
$('#chart-title').text("Location Activity");
chart.update();
};
function location_legend(e, legendItem) {
$('#chart-title').text("Station Activity @ " + chart.data.datasets[legendItem.datasetIndex].label);
chart.config.data = station_data[legendItem.datasetIndex];
chart.config.options.legend.onClick = station_legend;
chart.update();
};
</script>
</form>
<script>
var auto_refresh = setInterval(
function()
{
submitform();
}, 30000);
function submitform()
{
$("#pageForm").submit();
}
</script>