diff options
author | Devin Matte <devinmatte@gmail.com> | 2020-08-19 10:29:51 -0400 |
---|---|---|
committer | Devin Matte <devinmatte@gmail.com> | 2020-08-19 10:29:51 -0400 |
commit | e1ab5a398f3b3f89ed0a8fd4e89fd929f111186c (patch) | |
tree | e17c93d54a3b45fccae6647c1aa109c21cf4469d /packet | |
parent | e32a90448b322020a3fa0be5423797e668e8116f (diff) | |
parent | b9c3a6dec3f392b81f0156117b5674cb1f4a2bf5 (diff) |
Merge remote-tracking branch 'upstream/develop' into admin_ui
Diffstat (limited to 'packet')
-rw-r--r-- | packet/routes/api.py | 71 | ||||
-rw-r--r-- | packet/routes/upperclassmen.py | 43 | ||||
-rw-r--r-- | packet/stats.py | 113 | ||||
-rw-r--r-- | packet/templates/packet.html | 7 | ||||
-rw-r--r-- | packet/templates/packet_stats.html | 84 |
5 files changed, 250 insertions, 68 deletions
diff --git a/packet/routes/api.py b/packet/routes/api.py index a3eaa86..6a80478 100644 --- a/packet/routes/api.py +++ b/packet/routes/api.py @@ -18,6 +18,7 @@ from packet.utils import before_request, packet_auth, notify_slack, sync_freshma from packet.models import Packet, MiscSignature, NotificationSubscription, Freshman, FreshSignature, UpperSignature from packet.notifications import packet_signed_notification, packet_100_percent_notification, \ packet_starting_notification, packets_starting_notification +import packet.stats as stats class POSTFreshman: @@ -231,79 +232,13 @@ def report(info): @app.route('/api/v1/stats/packet/<packet_id>') @packet_auth def packet_stats(packet_id): - packet = Packet.by_id(packet_id) - - dates = [packet.start.date() + timedelta(days=x) for x in range(0, (packet.end - packet.start).days + 1)] - - print(dates) - - upper_stats = {date: list() for date in dates} - for uid, date in map(lambda sig: (sig.member, sig.updated), - filter(lambda sig: sig.signed, packet.upper_signatures)): - upper_stats[date.date()].append(uid) - - fresh_stats = {date: list() for date in dates} - for username, date in map(lambda sig: (sig.freshman_username, sig.updated), - filter(lambda sig: sig.signed, packet.fresh_signatures)): - fresh_stats[date.date()].append(username) - - misc_stats = {date: list() for date in dates} - for uid, date in map(lambda sig: (sig.member, sig.updated), packet.misc_signatures): - misc_stats[date.date()].append(uid) - - total_stats = dict() - for date in dates: - total_stats[date.isoformat()] = { - 'upper': upper_stats[date], - 'fresh': fresh_stats[date], - 'misc': misc_stats[date], - } - - return { - 'packet_id': packet_id, - 'dates': total_stats, - } - - -def sig2dict(sig): - """ - A utility function for upperclassman stats. - Converts an UpperSignature to a dictionary with the date and the packet. - """ - packet = Packet.by_id(sig.packet_id) - return { - 'date': sig.updated.date(), - 'packet': { - 'id': packet.id, - 'freshman_username': packet.freshman_username, - }, - } + return stats.packet_stats(packet_id) @app.route('/api/v1/stats/upperclassman/<uid>') @packet_auth def upperclassman_stats(uid): - sigs = UpperSignature.query.filter( - UpperSignature.signed, - UpperSignature.member == uid - ).all() + MiscSignature.query.filter(MiscSignature.member == uid).all() - - sig_dicts = list(map(sig2dict, sigs)) - - dates = set(map(lambda sd: sd['date'], sig_dicts)) - - return { - 'member': uid, - 'signatures': { - date.isoformat(): list( - map(lambda sd: sd['packet'], - filter(lambda sig, d=date: sig['date'] == d, - sig_dicts - ) - ) - ) for date in dates - } - } + return stats.upperclassman_stats(uid) def commit_sig(packet, was_100, uid): diff --git a/packet/routes/upperclassmen.py b/packet/routes/upperclassmen.py index c8cdd7c..11bc2b1 100644 --- a/packet/routes/upperclassmen.py +++ b/packet/routes/upperclassmen.py @@ -1,6 +1,7 @@ """ Routes available to CSH users only """ +import json from itertools import chain from operator import itemgetter @@ -10,6 +11,7 @@ from packet import app from packet.models import Packet, MiscSignature from packet.utils import before_request, packet_auth from packet.log_utils import log_cache, log_time +from packet.stats import packet_stats @app.route('/') @@ -61,3 +63,44 @@ def upperclassmen_total(info=None): return render_template('upperclassmen_totals.html', info=info, num_open_packets=len(open_packets), upperclassmen=sorted(upperclassmen.items(), key=itemgetter(1), reverse=True)) + + +@app.route('/stats/packet/<packet_id>') +@packet_auth +@before_request +def packet_graphs(packet_id, info=None): + stats = packet_stats(packet_id) + fresh = [] + misc = [] + upper = [] + + + # Make a rolling sum of signatures over time + agg = lambda l, attr, date: l.append((l[-1] if l else 0) + len(stats['dates'][date][attr])) + dates = list(stats['dates'].keys()) + for date in dates: + agg(fresh, 'fresh', date) + agg(misc, 'misc', date) + agg(upper, 'upper', date) + + # Stack misc and upper on top of fresh for a nice stacked line graph + for i in range(len(dates)): + misc[i] = misc[i] + fresh[i] + upper[i] = upper[i] + misc[i] + + return render_template('packet_stats.html', + info=info, + data=json.dumps({ + 'dates':dates, + 'accum': { + 'fresh':fresh, + 'misc':misc, + 'upper':upper, + }, + 'daily': { + + } + }), + fresh=stats['freshman'], + packet=Packet.by_id(packet_id), + ) diff --git a/packet/stats.py b/packet/stats.py new file mode 100644 index 0000000..2ac5fb2 --- /dev/null +++ b/packet/stats.py @@ -0,0 +1,113 @@ +from datetime import timedelta + +from packet.models import Packet, MiscSignature, UpperSignature + + +def packet_stats(packet_id): + """ + Gather statistics for a packet in the form of number of signatures per day + + Return format: { + packet_id, + freshman: { + name, + rit_username, + }, + dates: { + <date>: { + upper: [ uid ], + misc: [ uid ], + fresh: [ freshman_username ], + }, + }, + } + """ + packet = Packet.by_id(packet_id) + + dates = [packet.start.date() + timedelta(days=x) for x in range(0, (packet.end-packet.start).days + 1)] + + print(dates) + + upper_stats = {date: list() for date in dates} + for uid, date in map(lambda sig: (sig.member, sig.updated), + filter(lambda sig: sig.signed, packet.upper_signatures)): + upper_stats[date.date()].append(uid) + + fresh_stats = {date: list() for date in dates} + for username, date in map(lambda sig: (sig.freshman_username, sig.updated), + filter(lambda sig: sig.signed, packet.fresh_signatures)): + fresh_stats[date.date()].append(username) + + misc_stats = {date: list() for date in dates} + for uid, date in map(lambda sig: (sig.member, sig.updated), packet.misc_signatures): + misc_stats[date.date()].append(uid) + + total_stats = dict() + for date in dates: + total_stats[date.isoformat()] = { + 'upper': upper_stats[date], + 'fresh': fresh_stats[date], + 'misc': misc_stats[date], + } + + return { + 'packet_id': packet_id, + 'freshman': { + 'name': packet.freshman.name, + 'rit_username': packet.freshman.rit_username, + }, + 'dates': total_stats, + } + + +def sig2dict(sig): + """ + A utility function for upperclassman stats. + Converts an UpperSignature to a dictionary with the date and the packet. + """ + packet = Packet.by_id(sig.packet_id) + return { + 'date': sig.updated.date(), + 'packet': { + 'id': packet.id, + 'freshman_username': packet.freshman_username, + }, + } + + +def upperclassman_stats(uid): + """ + Gather statistics for an upperclassman's signature habits + + Return format: { + member: <uid>, + signautes: { + <date>: [{ + id: <packet_id>, + freshman_username, + }], + }, + } + """ + + sigs = UpperSignature.query.filter( + UpperSignature.signed, + UpperSignature.member == uid + ).all() + MiscSignature.query.filter(MiscSignature.member == uid).all() + + sig_dicts = list(map(sig2dict, sigs)) + + dates = set(map(lambda sd: sd['date'], sig_dicts)) + + return { + 'member': uid, + 'signatures': { + date.isoformat() : list( + map(lambda sd: sd['packet'], + filter(lambda sig, d=date: sig['date'] == d, + sig_dicts + ) + ) + ) for date in dates + } + } diff --git a/packet/templates/packet.html b/packet/templates/packet.html index 62708a7..d15f3ad 100644 --- a/packet/templates/packet.html +++ b/packet/templates/packet.html @@ -22,6 +22,13 @@ {% endif %} </div> </div> + <div class="row w-100 mb-1"> + {% if info.realm == "csh" %} + <div class="col"> + <a class="btn btn-primary" style="float: right" href="{{ url_for('packet_graphs', packet_id=packet.id) }}">Graphs</a> + </div> + {% endif %} + </div> <div class="row"> <div class="col ml-1 mb-1"> <h6>Signatures: <span class="badge badge-secondary">{{ received.total }}/{{ required.total }}</span></h6> diff --git a/packet/templates/packet_stats.html b/packet/templates/packet_stats.html new file mode 100644 index 0000000..7742397 --- /dev/null +++ b/packet/templates/packet_stats.html @@ -0,0 +1,84 @@ +{% extends "extend/base.html" %} + +{% block head %} +{{ super() }} +<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script> +{% endblock %} + +{% block body %} +<div class="container main"> + <div class="card"> + <h5 class="card-header bg-primary text-white">Cumulative Signatures Over Time for + <a class="text-white" href="{{ url_for('freshman_packet', packet_id=packet.id) }}"> + <img class="eval-user-img" + alt="{{ get_rit_name(packet.freshman_username) }}" + src="{{ get_rit_image(packet.freshman_username) }}" + width="25" + height="25"/> {{ get_rit_name(packet.freshman_username) }} + </a> +</h5> + <tr> + <td data-priority="1"> + </td> + </tr> + <div class="card-body"> + <canvas id="myChart" width="400" height="400"></canvas> + <script> +var data = {{ data|safe }}; +// Stack the lines +var ctx = document.getElementById('myChart'); +var myChart = new Chart(ctx, { + type: 'line', + data: { + labels: data.dates, + datasets: [ + { + fill: 'origin', + label: 'Fresh Sigs', + data: data.accum.fresh, + backgroundColor: '#b0197e80', + borderColor: '#b0197e', + borderWidth: 1, + lineTension: 0 + }, + { + fill: '-1', + label: 'Misc Sigs', + data: data.accum.misc, + backgroundColor: '#0000ff80', + borderColor: 'blue', + borderWidth: 1, + lineTension: 0 + }, + { + fill: '-1', + label: 'Upper Sigs', + data: data.accum.upper, + backgroundColor: '#00ff0080', + borderColor: 'green', + borderWidth: 1, + lineTension: 0 + } + ] + }, + options: { + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'day', + }, + }], + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } +}); + </script> + </div> + </div> +</div> +{% endblock %} |