Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 3 additions & 69 deletions packet/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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


@app.route('/api/v1/freshmen', methods=['POST'])
Expand Down Expand Up @@ -255,80 +256,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):
packet_signed_notification(packet, uid)
Expand Down
43 changes: 43 additions & 0 deletions packet/routes/upperclassmen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Routes available to CSH users only
"""
import json

from itertools import chain
from operator import itemgetter
Expand All @@ -10,6 +11,7 @@
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('/')
Expand Down Expand Up @@ -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),
)
113 changes: 113 additions & 0 deletions packet/stats.py
Original file line number Diff line number Diff line change
@@ -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
}
}
7 changes: 7 additions & 0 deletions packet/templates/packet.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ <h3>{{ get_rit_name(packet.freshman_username) }}</h3>
{% 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>
Expand Down
84 changes: 84 additions & 0 deletions packet/templates/packet_stats.html
Original file line number Diff line number Diff line change
@@ -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 %}