diff --git a/kfet/management/commands/graphs.py b/kfet/management/commands/graphs.py new file mode 100644 index 00000000..ba4424a4 --- /dev/null +++ b/kfet/management/commands/graphs.py @@ -0,0 +1,230 @@ +import numpy as np +import matplotlib.pyplot as plt + +from django.core.management.base import BaseCommand +from django.utils import timezone +import os + +from kfet.models import Operation + +# ne fonctionne pas, le mettre à la main dans l'env +os.environ['MPLBACKEND'] = "agg" + +# ce code est fait pour fonctionner, pas pour être beau, performant, +# et encore moins pour être lisible... + + +class Command(BaseCommand): + help = ("Génères de graphes utiles pour montrer quelques" + " résultats de la K-Fêt à l'administration") + + def add_arguments(self, parser): + parser.add_argument('--weeks', type=int, default=30, + help="Nombre de semaines de l'échantillonage") + + def handle(self, *args, **options): + days_echan = options['weeks']*7 # nombre de jour de l'échantillon + weeks_echan = options['weeks'] # nombre de semaines de l'échantillon + + time = {} + for i in range(24*2): + time[i] = {'min': (i%2)*30, + 'hour': i//2, + 'label': "{h}h{m}".format(h=i//2, m=(i%2)*30) \ + if i%2 != 0 else "{h}h".format(h=i//2), + 'nb': i, + } + + time_bis = {} + for i in range(9*4): + time_bis[i] = { + 'min': (i%4)*15, + 'hour': 12 + i//4, + 'label': "{h}h{m}".format(h=12+i//4, m=(i%4)*15) \ + if i%4 != 0 else "{h}h".format(h=12+i//4), + 'nb': i, + } + + self.stdout.write("Génération des graphes sur {days} jours..." + .format(days=days_echan)) + + def trie_type(q): + alcool = q.filter(article__category__name__in=["Autre Bieres", + "Pression", + "Vins"]) + soft = q.filter(article__category__name__in=["Softs", "Eaux"]) + eat = q.filter(article__category__name__in=["Gourmandises", + "Salé"]) + return (alcool, soft, eat) + + def trie_jour(q): + q_days = {} + for d in range(7): + q_days[d] = [ + op.article_nb for op in q if op.group.at.weekday() == d + ] + return q_days + + def get_jour(q, j): + q_day = [ + op for op in q if op.group.at.weekday() == j + ] + return q_day + + + def trie_hours(l): + res = {} + for i in time: + res[i] = list(map((lambda x: x.article_nb), + filter(lambda x: all((False if x is None else x.group.at.minute >= time[i]['min'], + False if x is None else x.group.at.minute < time[i]['min']+30, + False if x is None else x.group.at.hour >= time[i]['hour'], + False if x is None else x.group.at.hour < time[i]['hour']+1)), + l))) + return res + + def trie_hours_bis(l): + res = {} + for i in time_bis: + res[i] = list(map((lambda x: x.article_nb), + filter(lambda x: all((False if x is None else \ + x.group.at.minute >= time_bis[i]['min'], + False if x is None else \ + x.group.at.minute < time_bis[i]['min']+15, + False if x is None else \ + x.group.at.hour >= time_bis[i]['hour'], + False if x is None else \ + x.group.at.hour < time_bis[i]['hour']+1)), + l))) + return res + + def graphe_semaine(a, s, e): + self.stdout.write("** Tri selon les jours de la semaine.") + opa = trie_jour(a) # operation day alcool + ops = trie_jour(s) # operation day soft + ope = trie_jour(e) # operation day eat + + self.stdout.write("** Calcul des quantité des opérations") + opal = list(map(lambda x: x/weeks_echan, list(map(sum, opa.values())))) + opsl = list(map(lambda x: x/weeks_echan, list(map(sum, ops.values())))) + opel = list(map(lambda x: x/weeks_echan, list(map(sum, ope.values())))) + + self.stdout.write("** Génération des graphiques") + days = range(7) + days_name = ["lundi", "mardi", "mercredi", + "jeudi", "vendredi", "samedi", "dimanche"] + + plt.clf() + plt.xticks(days, days_name) + plt.stackplot(days, + [opal, opsl, opel], + labels=["Boissons alcoolisée", + "Boissons non-alcoolisée", + "Nourriture"]) + plt.ylabel("nombre d'item consomé") + plt.legend() + plt.suptitle("Consomation sur une semaine," + " ces {d} derniers jours" + .format(d=days_echan)) + plt.savefig('semaine.pdf', + figsize=(16,9)) + + def graphe_journee(a, s, e, days, label): + days_name = { + 0: "Lundi", + 1: "Mardi", + 2: "Mercredi", + 3: "Jeudi", + 4: "Vendredi", + 5: "Samedi", + 6: "Dimanche", + } + + hours = range(24*2) + hours_name = [ + d['label'] if d['nb']%2 == 0 else "" + for d in time.values()] + + opal, opsl, opel = {}, {}, {} + self.stdout.write("** Calcul des quantité des opérations") + for d in days: + opa = trie_hours(get_jour(a, d)) # operation alcool + ops = trie_hours(get_jour(s, d)) # operation soft + ope = trie_hours(get_jour(e, d)) # operation eat + + opal[d] = list(map(lambda x: x/weeks_echan, list(map(sum, opa.values())))) + opsl[d] = list(map(lambda x: x/weeks_echan, list(map(sum, ops.values())))) + opel[d] = list(map(lambda x: x/weeks_echan, list(map(sum, ope.values())))) + + # sanitize + for h in range(15*2, 18*2): + opal[d][h] *= 0.5 + + + self.stdout.write("** Génération des graphiques") + i = 1 + plt.clf() + for d in days: + ax = plt.subplot(210+i) + plt.xticks(hours, hours_name, fontsize=6) + plt.stackplot(hours, + [opal[d], opsl[d], opel[d]], + labels=["Boissons alcoolisée", + "Boissons non-alcoolisée", + "Nourriture"]) + plt.ylabel("nb item le {d}".format(d=days_name[d])) + if i == 1: + plt.legend() + i += 1 + + plt.suptitle("Consomation sur une journée," + " ces {j} derniers jours" + .format(j=days_echan)) + plt.savefig('journee_{l}.pdf'.format(l=label), + figsize=(16,9)) + + def premiere_conso(ai, last_date, days_echan): + date = [a.order_by('group__at') + .filter(group__at__gte=last_date - timezone.timedelta(days=i)) + .first() + for i in range(days_echan)] + #FIXME + # date = filter(lambda op: all((False if op is None else not op.group.at.hour <= 17, + # False if op is None else not op.group.at.hour >= 5)), date) + + hours = range(9*4) + hours_name = [ + d['label'] if d['nb']%2 == 0 else "" + for d in time_bis.values()] + + pc = trie_hours_bis(date) + pcl = list(map(sum, pc.values())) + + plt.clf() + plt.xticks(hours, hours_name, fontsize=6) + plt.bar(hours, pcl) + plt.ylabel("nb première conso sur la plage") + + plt.suptitle("Heure de la première consomation alcoolisée" + " ces {j} derniers jours" + .format(j=days_echan)) + plt.savefig('premiere_conso.pdf', + figsize=(16,9)) + + def sanitize(opes): + # return opes.extra(select={'hour': "EXTRACT(hour FROM kfet_operationgroup.at)"}, having=["`hour` >= 17 OR `hour` <= 5"]) + return opes.extra(where=["EXTRACT(HOUR FROM kfet_operationgroup.at) >= 17 OR EXTRACT(HOUR FROM kfet_operationgroup.at) <= 5"]) + + opes = Operation.objects.filter(type='PURCHASE', canceled_at=None) + last_date = opes.order_by('-group__at').first().group.at + min_date = last_date - timezone.timedelta(days=days_echan) + + opes = opes.filter(group__at__gte=min_date) + a, s, e = trie_type(opes) + # a = sanitize(a) + #print(a.query) + + graphe_semaine(a, s, e) + graphe_journee(a, s, e, [3,4], "soirée") + graphe_journee(a, s, e, [1,6], "calme") + premiere_conso(a, last_date, days_echan) diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh index 269e4f25..bae7219c 100644 --- a/provisioning/bootstrap.sh +++ b/provisioning/bootstrap.sh @@ -9,7 +9,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" # Installation de paquets utiles apt-get update && apt-get install -y python3-pip python3-dev python3-venv \ - libmysqlclient-dev libjpeg-dev git redis-server + libmysqlclient-dev libjpeg-dev git redis-server python3-tk pip install -U pip # Configuration et installation de mysql. Le mot de passe root est le même que diff --git a/requirements.txt b/requirements.txt index ce081588..6ba9957d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,5 @@ git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_customma ldap3 git+https://github.com/Aureplop/channels.git#egg=channels python-dateutil +numpy==1.12.1 +matplotlib==2.0.0