Skip to content
Snippets Groups Projects
Commit 8533dd4d authored by Tobias Dussa's avatar Tobias Dussa
Browse files

Cleanup.

parent c51c41d4
No related branches found
No related tags found
No related merge requests found
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# reaction
# Copyright (C) 2021 EGI-IRTF
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from datetime import datetime, timedelta
try:
from jinja2 import Template
jinja2 = True
except:
jinja2 = False
import glob
import io
import os
try:
import matplotlib.pyplot as plot
matplotlib = True
except:
matplotlib = False
BASEDIR = 'stats/'
if jinja2:
LATEXTEMPLATE = Template(r'''
<pre>
\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{width=\textwidth,compat=1.16}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
title={Communication Challenge -- {{title}}},
xlabel={Time [hours]},
ylabel={Reactions [number of participants]},
xmin=0, xmax={{xmax}},
ymin=0, ymax={{ymax}},
legend pos=south east,
ymajorgrids=true,
grid style=dashed,
]
\addplot[color=blue, mark=square]
coordinates { {{coordinates}} };
\addlegendentry{ {{responses}} participants ({{percentageResponses}}\,\%) have reacted}
\addplot[color=red]
coordinates { (0, {{sitesNumber}})({{xmax}}, {{sitesNumber}}) };
\addlegendentry{ {{sitesNumber}} participants have been challenged}
\addplot[color=green]
coordinates { (24, 0)(24, {{ymax}}) };
\addlegendentry{ {{quickSitesNumber}} participants ({{percentageQuick}}\,\%) have reacted within 24 h}
\end{axis}
\end{tikzpicture}
\end{document}
</pre>
''')
def floatPart(string):
return(float(string.split('/')[-1].split('-', 1)[0]))
def hashtagPart(string):
return(string.split('/')[-1].split('-', 1)[-1])
class ReactionApp(object):
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def format(self, code, msg):
self.start(code, [('Content-type', 'text/html; charset=UTF-8')])
return '<html><body><p>{msg}</p></body></html>'.format(msg=msg).encode('UTF-8')
def image(self, code, type, image):
self.start(code, [('Content-type', 'image/{}'.format(type))])
return image
def updatefile(self, folder, hashtag):
with open(folder + hashtag, 'a') as tagfile:
tagfile.write(str((datetime.now()-datetime(1970, 1, 1)).total_seconds()) + '\n')
def checkresults(self, folder, hashtag):
if os.path.isfile(folder + '.LOCKED'):
return self.format('200 OK', 'This campaign has been closed!')
with open(folder + hashtag, 'r') as tagfile:
lines = tagfile.read().splitlines()
if len(lines) < 2:
return self.format('500 Internal Server Error', 'Unfortunately, this campaign is broken!')
elapsedtime = datetime.fromtimestamp(float(lines[1])) - datetime.fromtimestamp(float(lines[0]))
if not os.path.isfile(folder + str(elapsedtime.total_seconds()) + '-' + hashtag):
open(folder + str(elapsedtime.total_seconds()) + '-' + hashtag, 'a').close()
responses = glob.glob(folder + '*-*')
responses.sort(key=floatPart)
responses = list(map(hashtagPart, responses))
message = '<p>Reaction time: ' + str(elapsedtime) + '</p>'
return self.format('200 OK', message)
def gatherdata(self, folder):
if not os.path.isdir(folder):
return False
responseFiles = glob.glob(folder + '*-*')
data = {}
data['title'] = folder.split('/')[-2]
data['sites'] = set([hashtagPart(x) for x in glob.glob(folder + '*')])
data['sitesNumber'] = len(data['sites'])
data['responses'] = len(responseFiles)
data['responseTimes'] = {hashtagPart(x): floatPart(x) for x in responseFiles}
data['responsePoints'] = {hashtagPart(x): os.path.getctime(x) for x in responseFiles}
data['missingSites'] = list(filter(lambda x: x not in data['responseTimes'], data['sites']))
data['missingSitesNumber'] = len(data['missingSites'])
data['quickSites'] = list(filter(lambda x: x<(24*3600), data['responseTimes'].values()))
data['quickSitesNumber'] = len(data['quickSites'])
data['percentageResponses'] = round(100*data['responses']/len(data['sites']))
data['percentageQuick'] = round(100*len(data['quickSites'])/len(data['sites']))
return data
def getresults(self, folder):
ReactionApp.getresults.rank = 0
def steprank():
ReactionApp.getresults.rank += 1
return(ReactionApp.getresults.rank)
if not os.path.isdir(folder):
return self.format('404 Not Found', 'This campaign does not exist!')
data = self.gatherdata(folder)
message = '<h1>Communication Challenge — {title}'.format_map(data)
message += ' {status} Results</h1>'
message += '{responses} participants ({percentageResponses} %) have reacted<br>'.format_map(data)
message += '{sitesNumber} participants have been challenged<br>'.format_map(data)
message += '{quickSitesNumber} participants ({percentageQuick} %) have reacted within 24 h<br>'.format_map(data)
message += '<h2>Reaction Times</h2>'
message += '<table cellspacing="2" cellpadding="5" bgcolor="#000000"><tr bgcolor="#ffffff"><th align="left">Sequence #</th><th align="left">Timestamp</th><th align="left">Participant</th><th align="left">Reaction Time</th></tr>'
message += '<tr bgcolor="#ffffff">' + '</tr><tr bgcolor="#ffffff">'.join(map(lambda x: '<td align="right">'+str(steprank())+'</td><td>'+datetime.fromtimestamp(data['responsePoints'][x], tz=datetime.now().astimezone().tzinfo).isoformat()+'</td><td><code>'+x+'</code></td><td align="right">'+str(timedelta(seconds=data['responseTimes'][x]))+'</td>', sorted(data['responseTimes'].keys(), key=lambda x: data['responseTimes'][x]))) + '</tr></table>'
if (data['missingSitesNumber'] > 0):
message += '<h2>Missing Reactions</h2>'
message += '<ul><li><code>' + '...</code></li><li><code>'.join(map(lambda x: x[0:48], sorted(data['missingSites']))) + '...</code></li></ul>'
status = 'Preliminary'
else:
status = 'Final'
if os.path.isfile(folder + '.LOCKED'):
status = 'Final'
return self.format('200 OK', message.format(status=status))
usa def getlatex(self, folder):
if not os.path.isdir(folder):
return self.format('404 Not Found', 'This campaign does not exist!')
data = self.gatherdata(folder)
responseTimes = [round(x/3600, 3) for x in data['responseTimes'].values()]
responseTimes.sort()
data['coordinates'] = '(0, 0)'
data['coordinates'] += ''.join(['('+str(responseTimes[x])+', '+str(x+1)+')' for x in range(data['responses'])])
data['xmax'] = round(max(responseTimes) * 1.2, -1)
data['ymax'] = round(data['sitesNumber'] * 1.2, -1)
return self.format('200 OK', LATEXTEMPLATE.render(data))
def getgraph(self, folder, format):
if not os.path.isdir(folder):
return self.format('404 Not Found', 'This campaign does not exist!')
data = self.gatherdata(folder)
responseTimes = [round(x/3600, 3) for x in data['responseTimes'].values()]
responseTimes.append(0)
responseTimes.sort()
xmax = round(max(responseTimes) * 1.2, -1)
ymax = round(data['sitesNumber'] * 1.2, -1)
plot.figure()
plot.plot(responseTimes, range(data['responses']+1), marker='.', color='blue', label='{responses} participants ({percentageResponses} %) have reacted'.format_map(data), zorder=3)
plot.axhline(y=data['sitesNumber'], color='red', label='{sitesNumber} participants have been challenged'.format_map(data), zorder=2)
plot.axvline(x=24, color='lightgreen', label='{quickSitesNumber} participants ({percentageQuick} %) have reacted within 24 h'.format_map(data), zorder=1)
plot.title('Communication Challenge — {title}'.format_map(data))
plot.xlabel('Time [hours]')
plot.ylabel('Reactions [number of participants]')
plot.legend(loc='lower right')
plot.plot
with io.BytesIO() as buffer:
plot.savefig(buffer, format=format)
buffer.seek(0)
return self.image('200 OK', format, buffer.read())
def process(self):
path = self.environ['PATH_INFO'].split('/')[1:]
try:
prefix, hashtag = path[0].rsplit('-', 1)
except ValueError:
prefix = ''
hashtag = path[0]
try:
int(hashtag, 16)
except (KeyError, ValueError):
return self.format('400 Bad Request', 'This is not a valid link!')
if prefix:
folder = os.path.abspath(BASEDIR + prefix) + '/'
if not folder.startswith(BASEDIR):
self.format('400 Bad Request', 'Good try!')
else:
folder = BASEDIR
if not os.path.isfile(folder + hashtag):
if len(path) == 2 and path[1] == 'create':
if not os.path.isdir(folder):
os.makedirs(folder)
self.updatefile(folder, hashtag)
return self.format('200 OK', 'File {tag} created'.format(tag=hashtag))
elif len(path) != 1:
return self.format('400 Bad Request', 'What are you trying to do?')
elif not os.path.isdir(folder):
return self.format('404 Not Found', 'This campaign does not exist!')
else:
return self.format('404 Not Found', 'This id does not exist!')
if 'HTTP_REFERER' in self.environ:
referer = self.environ['HTTP_REFERER']
else:
referer = ''
if 'HTTPS' in self.environ and self.environ['HTTPS']:
url = 'https://'
else:
url = 'http://'
url += '{host}{URI}'.format(host=self.environ['HTTP_HOST'],
URI=self.environ['REQUEST_URI'])
if referer == url:
self.updatefile(folder, hashtag)
return self.checkresults(folder, hashtag)
elif len(path) == 2:
if path[1] == 'results':
return self.getresults(folder)
elif path[1] == 'graph' and matplotlib:
return self.getgraph(folder, 'png')
elif path[1] == 'latex' and jinja2:
return self.getlatex(folder)
else:
return self.format('400 Bad Request', 'Nice try!')
elif len(path) == 3 and path[1] == 'graph' and matplotlib:
if path[2] == 'jpg':
return self.getgraph(folder, 'jpg')
elif path[2] == 'pdf':
return self.getgraph(folder, 'pdf')
elif path[2] == 'png':
return self.getgraph(folder, 'png')
elif path[2] == 'svg':
return self.getgraph(folder, 'svg')
else:
return self.format('400 Bad Request', 'Nice try!')
elif os.path.isfile(folder + '.LOCKED'):
return self.format('200 OK', 'This campaign has been closed!')
else:
return self.format('200 OK', 'Please click <a href="{URL}">here</a> to confirm your submission!'.format(URL=url))
def __iter__(self):
yield self.process()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment