On rajoute un lien symbolique de l'ancien stats_cableurs vers le nouveau

This commit is contained in:
Raphaël-David Lasseri 2014-01-12 23:31:44 +01:00
parent cb9dda8c7a
commit 0d56d356ad
2 changed files with 432 additions and 431 deletions

431
archive/utils/stats_cableurs.py Executable file
View file

@ -0,0 +1,431 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# stats_cableurs.py
# -----------------
#
# Copyright (C) 2008, 2009 François Bobot <bobot@crans.org>,
# Jeremie Dimino <jeremie@dimino.org>,
# Michel Blockelet <blockelet@crans.org>,
# Antoine Durand-Gasselin <adg@crans.org>
#
# This file 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 2 of the License, or
# (at your option) any later version.
#
# This file 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, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
import sys, re, datetime
# hack temporaire pour pallier avoir la version 2.5 de optparse
#from optparse import OptionParser
sys.path.append('/usr/scripts/lib')
from optparse import OptionParser, OptionGroup
sys.path.append("/usr/scripts/gestion/")
from ldap_crans import CransLdap, Adherent
from config import ann_scol
db = CransLdap()
__DAYS_IN_PREVIOUS_MONTH = [datetime.timedelta(days) for days in
[ 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 ] ]
def parse_ligne_histo(ligne):
u"""Parse une ligne d'historique et renvoie (['dd', 'mm', 'yyyy'],
['date', 'heure', 'who', 'what'])"""
champ = ligne.replace(',', '').replace(': ', '').split(' ')
sdate = champ[0].split('/')
date = datetime.date(int(sdate[2]), int(sdate[1]), int(sdate[0]))
return date, champ
def parse_historique(truc):
u"""Parse l'historique et renvoie la liste des actions sous la forme
(['dd', 'mm', 'yyyy'], ['date', 'heure', 'who', 'what'])"""
return [ parse_ligne_histo(ligne) for ligne in truc.historique() ]
def dec_date(date):
u"""retranche un mois à la date."""
return (date - __DAYS_IN_PREVIOUS_MONTH[date.month -1])
class StatsCableursBase:
u"""Classe prototype pour afficher des stats de câbleurs."""
colonnes = []
nbmois = 4
stats = {}
score = {}
kwargs = {}
def __init__(self, **kwargs):
self.kwargs.update(kwargs)
self.affiche (self.kwargs, 'd')
# On regarde s'il y a un mois à partir duquel on veuille partir.
mois = kwargs.get("mois", -4)
# On compte ensuite combien de mois ça fait
if mois > 0:
# si on demande depuis 09 (septembre) , on remonte
# jusqu'à septembre
self.nbmois = (datetime.date.today().month + 12 - mois) % 12
else:
# si on demande les stats sur 4 mois, on remonte sur 4
# mois
self.nbmois = - mois
# On regarde si on avait pas déjà précisé sur combien de mois on
# voulait partir.
self.nbmois = kwargs.get("nbmois", self.nbmois)
# bout de code à mon avis explicite
self.def_columns()
self.affiche("Génération des stats")
self.get_stats()
self.calc_score()
cableurs = self.sort_cableurs()
self.print_stats(cableurs)
def def_columns(self):
u"""Fonction qui sera appelée pour éventuellement mettre à jour
self.colonnes"""
pass
def get_stats(self):
u"""Fonction censée récupérer les stats des différents câbleurs,
et les fouttre dans self.stats"""
raise NotImplementedError
def calc_score(self):
u"""Fonction censée calculer le score en fonction des stats, et
le mettre dans self.score"""
raise NotImplementedError
def sort_cableurs(self):
u"""Renvoie la liste des câbleurs triés, en fonction de leur score"""
cableurs = self.stats.keys()
cableurs.sort(lambda x, y : cmp(self.score[x], self.score[y]), reverse=True)
if self.kwargs.get('top', 10) != 0:
cableurs = cableurs[:self.kwargs.get('top', 10)]
return cableurs
def print_stats(self, cableurs):
u"""Affiche pour chaque cableur les valeurs de ses différentes colonnes
définies dans self.colonnes."""
# On génère la liste des noms sous lesquels on va afficher nos câbleurs
noms = {}
for cableur in cableurs:
try:
ldap_cableur = db.search('login=%s' % cableur)['adherent'][0]
ids = { 'nom' : ldap_cableur.nom(),
'prenom' : ldap_cableur.prenom(),
'droits' : reduce(unicode.__add__,
[d[0] for d in ldap_cableur.droits()], u''),
'tel' : ldap_cableur.tel(),
'login' : cableur }
noms[cableur] = self.kwargs.get("fqn", "%(prenom)s %(nom)s") % ids
except IndexError:
self.affiche ("Unknown cableur %s!" % cableur, 'd')
noms[cableur] = cableur+'*'
# On récupère la liste des câbleurs, triés en fonction de leur score
long_max = reduce(lambda m, c : max(m, len(c)), noms.values(), len('cableur'))
# Titres des colonnes
ligne = u"%-*s" % (long_max, u'Câbleur')
ligne += u" | Score"
for col in self.colonnes:
ligne += u" | %s" % col
print(ligne)
# Ligne pour délimiter
ligne = u'-' * long_max
ligne += u"-+-%s" % (u'-' * len ("score"))
for col in self.colonnes:
ligne += u"-+-%s" % (u'-' * len(col))
print(ligne)
# Statiqtiques par câbleur
for cableur in cableurs:
acts = self.stats[cableur]
ligne = u"%-*s" % (long_max, noms[cableur])
ligne += u" | %*d" % (len ("score"), self.score[cableur])
for i in range (len (self.colonnes)):
ligne += u" | %*d" % (len(self.colonnes[i]), acts[i])
print(ligne)
def sort_events(self, events):
u"""Split une liste d'évènements selon les mois"""
sorted_events = []
since = datetime.date.replace(datetime.date.today(), day = 1)
backwards = self.nbmois
while backwards > 0:
sorted_events.append([ ev for ev in events if ev[0] >= since ])
events = [ ev for ev in events if ev[0] < since ]
backwards -= 1
since = dec_date(since)
sorted_events.append(events)
return sorted_events
def affiche(self, msg, reqs= 'v'):
u"""Fonction de print qui n'affiche que si on est dans un mode
kikoolol, debug ou verbose suffisamment avancé."""
try:
pool = list (self.kwargs.get('flags', ''))
for i in reqs:
pool.remove(i)
print msg
except ValueError:
return
class StatsCableursMachines(StatsCableursBase):
u"""Cette classe s'occupe de recenser l'activité des différents
câbleurs sur les machines, ce qui représente le travail typique du
câbleur en milieu d'année"""
kwargs = { 'fqn' : '%(login)s' , 'top' : 0 }
def ajoute_actions(self, machine, hist):
u"""Ajoute dans hist la liste des actions effectués par
chacuns des câbleurs sur la machine pendant les mois."""
for date, champ in parse_historique(machine):
# on ne va pas non plus compter les actions qu'on fait sur
# ses propres machines.
proprio = machine.proprietaire()
if isinstance(proprio, Adherent) and proprio.compte() != champ[2]:
# Il se peut qu'une personne sans les droits câbleur ait
# effectué une action.
try:
hist[champ[2]].append( (date, champ[3]) )
except KeyError:
hist[champ[2]] = [ (date, champ[3]) ]
def def_columns(self):
u"""Simplement une par mois, plus une pour toutes les actions
d'avant."""
month = datetime.date.replace(datetime.date.today(), day=1)
for i in range(self.nbmois):
self.colonnes.append("%d/%02d" % (month.month, month.year - 2000))
month = dec_date(month)
self.colonnes.append("< " + self.colonnes[-1])
def get_stats(self):
u"""Récupère les statistiques des différents câbleurs sur les
différentes machines"""
# La liste des câbleurs en tant qu'entité ldap, histoire de bien
# faire apparaître ceux qui n'ont rien fait
ldap_cableurs = db.search("droits=cableur")['adherent']
# On récupère la liste des machines
all_machines = db.search('mid=*')
machines = all_machines['machineFixe'] + all_machines['machineWifi']
# hists permet de répartir toutes les actions sur les différents
# câbleurs.
hists = {}
for i in ldap_cableurs:
hists[i.compte()] = []
# on récupère l'historique des machines que l'on met dans hists
for becane in machines:
self.ajoute_actions(becane, hists)
split_hist = {}
for cableur in hists.keys():
split_hist[cableur] = self.sort_events(hists[cableur])
self.stats[cableur] = [ len(i) for i in split_hist[cableur] ]
def calc_score(self):
u"""Le calcul se fait simplement comme la somme des actions
(indépendamment de leur type pour l'instant) sur les mois
concernés."""
for cableur in self.stats.keys():
self.score[cableur] = sum (self.stats[cableur][:-1])
class StatsCableursAdherents(StatsCableursBase):
u"""Cette classe s'occupe de recenser les actions que les câbleurs
font sur les adhérents (en fait les {,re}adhésions)."""
colonnes = [ u'Inscriptions', u'Réadhésions' ]
debut_ann_scol = datetime.date(ann_scol, 8, 1)
paiement_ann_scol = "paiement+%d" % ann_scol
def donne_cableur(self, adherent):
u""" Cherche le cableur qui a inscrit ou réinscrit un adhérent. """
# Est-ce qu'on recherche une inscription ou une reinscription?
date_inscr = datetime.date.fromtimestamp(adherent.dateInscription())
if date_inscr < self.debut_ann_scol:
action = 1 # 'reinscription'
action_filtre = self.paiement_ann_scol
else:
action = 0 # 'inscription'
action_filtre = 'inscription'
for date, champ in parse_historique(adherent):
if date >= self.debut_ann_scol:
# Maintenant on regarde si l'action recherchee est ici
if action_filtre in champ[3:]:
return action, champ[2]
return action, None
def get_stats(self):
u"""Calcule le nombre d'inscriptions et réinscription pour tous
les cableurs ayant inscrit ou réinscrit quelqu'un pour l'année
en cours."""
liste = db.search("paiement=%s" % ann_scol)['adherent']
for adherent in liste:
action, cableur = self.donne_cableur(adherent)
if cableur:
if not self.stats.has_key(cableur):
self.stats[cableur] = [0, 0]
self.stats[cableur][action] += 1
def calc_score(self):
u"""Calcule le score d'un câbleur, en fonction du nombre
d'adhésions qu'il a effectué"""
# On calcule le score total pour chaque cableur
for cableur in self.stats.keys():
self.score[cableur] = 2 * self.stats[cableur][0] + self.stats[cableur][1]
def update_fqn(option, opt_str, value, this, fmt):
u"""Utilisée dans le parser, cette fonction permet de mettre à jour
correctement la chaîne de format"""
try:
if opt_str not in ['-d', '--droits']:
if re.match ('^ \(%\(droits\)s\)', this.values.fqn):
this.values.fqn = fmt + this.values.fqn
else:
this.values.fqn = this.values.fqn + (' (%s)' % fmt)
else:
this.values.fqn = this.values.fqn + fmt
except TypeError:
this.values.fqn = fmt
def notimplerr(option, opt_str, value, parseur):
u"""Un callback pour le parser pour renvoyer un NotImplementedError"""
raise NotImplementedError
if __name__ == "__main__":
usage = "usage: %prog [options]\n %prog [options] [--efficiency -e]"
parser = OptionParser(usage=usage)
# options pour décider le nombre de câbleurs à afficher
group = OptionGroup(parser, "Options de filtrage",
u"Option pour filtrer les câbleurs à afficher")
group.add_option('-a', '--all', help= u"Affiche tous les câbleurs",
action='store_const', const=0, dest='top')
group.add_option('-t', '--top', metavar= "NB", type= 'int', dest='top',
help= u"N'affiche que les NB meilleurs câbleurs")
parser.add_option_group(group)
# options pour le format d'affichage des câbleurs
group = OptionGroup(parser, u"Format d'affichage",
u"Pour définir le format d'affichage des câbleurs")
group.add_option('-d', '--droits', help= u"Affiche les droits du câbleur",
action='callback', callback= update_fqn,
callback_kwargs= { 'fmt': ' (%(droits)s)'})
group.add_option('-F', '--full-name',
help=u"Affiche Prenom Nom des câbleurs",
action='callback', callback= update_fqn,
callback_kwargs= { 'fmt': '%(prenom)s %(nom)s'})
group.add_option('-l', '--login', help= u"Affiche le login des câbleurs",
action='callback', callback= update_fqn,
callback_kwargs= { 'fmt': '%(login)s'})
parser.add_option('-T', '--tel', help= u"Affiche le n° de téléphone",
action='callback', callback= update_fqn,
callback_kwargs= { 'fmt': '%(tel)s'})
group.add_option('--fqn', dest='fqn',
help= u"Définit le format d'affichage du nom du câbleur, les champs possibles sont %(droits)s, %(nom)s, %(prenom)s, %(login)s, %(tel)s")
parser.add_option_group(group)
# options de verbosité
group = OptionGroup(parser, u"Commander la verbosité",
u"Pour définir ce que le script doit afficher en plus")
group.add_option('-D', '--debug',
help= u"Affiche des informations de debuggage",
action='store_const', const='d', dest= 'debug')
group.add_option('-k', '--kikoolol', help="Affiche des trucs kikoolol",
action='callback', callback= notimplerr)
group.add_option('-v', '--verbose', help= u"Augmente la verbosité",
action='store_const', const= 'v', dest= 'verbose')
parser.add_option_group(group)
# options sur la durée étudiée
group = OptionGroup(parser, u"Durée étudiée",
u"Pour définir la durée sur laquelle on travaille")
group.add_option('-f', '--for', metavar= 'N',
help= u"Affiche les statistiqes depuis N mois",
type= 'int', dest='nbmois')
group.add_option('-s', '--since', metavar= 'MM',
help= u'Affiche les stats depuis MOIS',
type= 'int', dest='mois')
parser.add_option_group(group)
# Devrait plutôt être un argument qu'une option
parser.add_option('-e', '--efficiency',
help= u"Compte les actions effectuées sur les machines",
action= 'store_const',
const= StatsCableursMachines,
dest= 'default_stats',
default= StatsCableursAdherents)
# on parse, enfin, on laisse optparse le faire pour nous
(options, args) = parser.parse_args()
# Parce que les clefs ont par défaut la valeur None
kwargs = {}
for lbl, field in options.__dict__.items():
if field is not None and lbl != 'default_stats':
kwargs[lbl] = field
# XXX: l'action append_const dans optparse est ajoutée en Python 2.5
flaglist = []
if options.verbose:
flaglist.append("v")
if options.debug:
flaglist.append("D")
flags = "".join(flaglist)
kwargs["flags"] = flags
# On apelle les stats que l'on veut calculer
options.default_stats(**kwargs)