Commit 514db865 authored by Nicolas Joyard's avatar Nicolas Joyard

Merge branch 'graphes'

parents 40e3dc0c edd9de77
# -*- coding: utf-8 -*-
from sqlalchemy.sql.expression import case, func
from .constants import (ETAPE_A_CONFIRMER, ETAPE_A_ENVOYER, ETAPE_ENVOYE,
ETAPE_NA, ETAPES)
from .database import db
from .parlementaire import Parlementaire
from .procedure import Action
def etat_courriers():
"""
Renvoie les données pour constituer un histogramme des états des courriers.
"""
_categories = [
{'etats': [''], 'label': 'Inconnu'},
{'etats': ['Pris en charge', 'En cours de traitement'],
'label': 'Pris en charge'},
{'etats': ['Pli présenté', 'En attente de seconde présentation'],
'label': 'Présenté'},
{'etats': ['Attend d\'être retiré au guichet'], 'label': 'Au guichet'},
{'etats': ['Distribué'], 'label': 'Distribué'}
]
# Extrait "Distribué" de "1X23456: Distribué (01/02/2017)"
expr = func.split_part(
func.split_part(
Action.suivi, ':', 2),
' (', 1
)
data = {item.etat: item.nb
for item in db.session.query(expr.label('etat'),
func.count(1).label('nb'))
.filter(Action.etape == ETAPE_ENVOYE)
.group_by(expr)
.all()}
return [(c['label'], sum([data.get(e, 0) for e in c['etats']]))
for c in _categories]
def par_departement():
"""
Renvoie chaque département avec le nombre total de parlementaires, le
nombre à chaque étape, le nombre >= pris en charge, le nombre >= envoyé
"""
# Comptage des parlementaires par département...
dept_qs = db.session \
.query(Parlementaire.num_deptmt,
func.count(Parlementaire.id).label('total'))
# ...et par étape
dept_qs = dept_qs.add_columns(*[
func.sum(case([(Parlementaire.etape == e['ordre'], 1)], else_=0))
.label('nb_etape_%s' % e['ordre'])
for e in ETAPES
])
# ...et qui sont dans une étape >= pris en charge
dept_qs = dept_qs.add_columns(
func.sum(case([(Parlementaire.etape >= ETAPE_A_CONFIRMER, 1)],
else_=0)).label('nb_prisencharge'),
func.sum(case([(Parlementaire.etape >= ETAPE_ENVOYE, 1)],
else_=0)).label('nb_envoyes')
)
return dept_qs.group_by(Parlementaire.num_deptmt) \
.order_by(Parlementaire.num_deptmt) \
.all()
def par_etape():
"""
Renvoie les étapes avec le nombre de parlementaires par étape
"""
count = func.count(Parlementaire.id)
return db.session.query(Parlementaire.etape, count.label('nb')) \
.filter(Parlementaire.etape > ETAPE_NA) \
.group_by(Parlementaire.etape) \
.order_by(Parlementaire.etape) \
.having(count > 0) \
.all()
def random_parl():
"""
Renvoie un parlementaire au hasard parmi ceux à l'état "À envoyer", ou si
aucun n'est disponible parmi ceux concernés par l'opération
"""
parl = Parlementaire.query \
.filter(Parlementaire.etape == ETAPE_A_ENVOYER) \
.order_by(func.random()) \
.first()
if not parl:
parl = Parlementaire.query \
.filter(Parlementaire.etape > ETAPE_NA) \
.order_by(func.random()) \
.first()
return parl
......@@ -2,73 +2,49 @@
from flask import render_template
from sqlalchemy.sql.expression import case, func
from ..models import Parlementaire, db
from ..models.constants import (ETAPES, ETAPES_BY_ORDRE, ETAPE_A_CONFIRMER,
ETAPE_A_ENVOYER, ETAPE_ENVOYE, ETAPE_NA)
from ..models.constants import ETAPES_BY_ORDRE
from ..models.queries import (etat_courriers, par_etape, par_departement,
random_parl)
def setup_routes(app):
@app.route('/', endpoint='home')
def home():
# Un parlementaire à l'étape "à envoyer" au hasard
parl = Parlementaire.query \
.filter(Parlementaire.etape == ETAPE_A_ENVOYER) \
.order_by(func.random()) \
.first()
# Toutes les étapes avec le nombre de parlementaires à cette étape
etapes_qs = db.session \
.query(Parlementaire.etape,
func.count(Parlementaire.id).label('nb')) \
.filter(Parlementaire.etape > ETAPE_NA) \
.group_by(Parlementaire.etape) \
.order_by(Parlementaire.etape) \
.all()
# Données camembert
# Comptage des parlementaires par département...
dept_qs = db.session \
.query(Parlementaire.num_deptmt,
func.count(Parlementaire.id).label('total'))
etapes_qs = par_etape()
# ...et par étape
dept_qs = dept_qs.add_columns(*[
func.sum(case([(Parlementaire.etape == e['ordre'], 1)], else_=0))
.label('nb_etape_%s' % e['ordre'])
for e in ETAPES
])
def each_etape(getter):
return [getter(e) for e in etapes_qs]
# ...et qui sont dans une étape >= pris en charge
dept_qs = dept_qs.add_columns(
func.sum(case([(Parlementaire.etape >= ETAPE_A_CONFIRMER, 1)],
else_=0)).label('nb_prisencharge'),
func.sum(case([(Parlementaire.etape >= ETAPE_ENVOYE, 1)],
else_=0)).label('nb_envoyes')
)
def key_each_etape(key):
return each_etape(lambda e: ETAPES_BY_ORDRE[e.etape][key])
dept_qs = dept_qs.group_by(Parlementaire.num_deptmt) \
.order_by(Parlementaire.num_deptmt) \
.all()
etapes_data = {
'labels': key_each_etape('label'),
'datasets': [{
'data': each_etape(lambda e: e.nb),
'backgroundColor': key_each_etape('couleur'),
'hoverBackgroundColor': key_each_etape('couleur'),
'borderWidth': 0
}]
}
def for_nz(getter):
return [getter(e) for e in etapes_qs if e.nb > 0]
# Données histogramme
def key_for_nz(key):
return for_nz(lambda e: ETAPES_BY_ORDRE[e.etape][key])
etats = etat_courriers()
histo_data = {
'labels': [etat for etat, nb in etats],
'datasets': [{
'data': [nb for etat, nb in etats]
}]
}
return render_template(
'index.html.j2',
parlementaire=parl,
etapes_data={
'labels': key_for_nz('label'),
'datasets': [{
'data': for_nz(lambda e: e.nb),
'backgroundColor': key_for_nz('couleur'),
'hoverBackgroundColor': key_for_nz('couleur'),
'borderWidth': for_nz(lambda e: 0)
}]
},
departements=dept_qs
parlementaire=random_parl(),
etapes_data=etapes_data,
histo_data=histo_data,
departements=par_departement()
)
......@@ -8,12 +8,13 @@ from flask import (flash, redirect, render_template, request, session, url_for)
from flask_mail import Mail, Message
from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import func
from ..models import Action, Parlementaire, User, db
from ..models.constants import (ETAPE_A_CONFIRMER, ETAPE_A_ENVOYER,
ETAPE_COM_A_MODERER, ETAPE_COM_PUBLIE,
ETAPE_ENVOYE, ETAPE_NA)
ETAPE_ENVOYE)
from ..models.queries import random_parl
from ..tools.files import generer_demande, handle_upload
from ..tools.routing import (can_login_from_token, not_found, redirect_back,
remote_addr, require_user)
......@@ -47,17 +48,7 @@ def setup_routes(app):
@app.route('/hasard', endpoint='hasard')
def hasard():
parl = Parlementaire.query \
.filter(Parlementaire.etape == ETAPE_A_ENVOYER) \
.order_by(func.random()) \
.first()
if not parl:
parl = Parlementaire.query \
.filter(Parlementaire.etape > ETAPE_NA) \
.order_by(func.random()) \
.first()
return redirect(url_for('parlementaire', id=parl.id))
return redirect(url_for('parlementaire', id=random_parl().id))
@app.route('/parlementaires', endpoint='parlementaires')
def parlementaires():
......
......@@ -123,24 +123,28 @@ th.col-nobreak {
#pie-container {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
#pie-container #pie,
#pie-container #pie-legend {
flex-grow: 0;
flex-shrink: 0;
}
#pie-container #pie {
text-align: center;
flex-basis: 70%;
flex-shrink: 1;
flex-basis: 65%;
padding-right: 5%;
position: relative;
}
#pie-container #pie canvas {
margin: 0 auto 0 auto;
width: 100%;
height: 60%;
}
#pie-container #pie-legend {
flex-shrink: 0;
flex-basis: 30%;
}
......
......@@ -50,7 +50,7 @@
</header>
<article class="panel-body" id="pie-container">
<div id="pie">
<canvas id="pie-canvas" height="200" width="300"></canvas>
<canvas id="pie-canvas" height="210" width="350"></canvas>
</div>
<div id="pie-legend">
{% for etape in etapes %}
......@@ -62,6 +62,9 @@
{% endfor %}
</div>
</article>
<article class="panel-body" id="bar-container">
<canvas id="bar-canvas" height="150" width="400"></canvas>
</article>
</section>
<section class="panel panel-default">
......@@ -122,7 +125,7 @@
/* Camembert */
var circleRatio = 0.52;
var chart = new Chart($('#pie-canvas'), {
new Chart($('#pie-canvas'), {
type: 'pie',
data: {{ etapes_data|tojson }},
options: {
......@@ -130,6 +133,7 @@
display: false
},
responsive: false,
maintainAspectRatio: true,
rotation: Math.PI * (1 - (circleRatio - 0.5)),
circumference: 2 * Math.PI * circleRatio,
cutoutPercentage: 25,
......@@ -139,6 +143,37 @@
}
});
/* Histo de suivi des courriers */
new Chart($('#bar-canvas'), {
type: 'horizontalBar',
data: {{ histo_data|tojson }},
options: {
animation: {
duration: 0
},
legend: {
display: false
},
title: {
display: true,
text: 'Suivi des courriers envoyés'
},
scales: {
xAxes: [{
ticks: {
min: 0
}
}],
yAxes: [{
ticks: {
fontSize: 10
}
}]
}
}
});
/* Carte */
var modesCarte = ['prisencharge', 'envoye'];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment