]>
git.immae.eu Git - perso/Denise/oms.git/blob - gestion_donnees.py
2 # -*- coding: utf-8 -*-
4 from configuration
import CONFIG
,DEFAUT
5 from gestion_erreurs
import debug
, warning
, erreur
, initialise_erreurs
6 from gestion_couleurs
import rgb_vers_tuple
, tuple_vers_rgb
7 from gestion_unites
import choix_unite
13 ### Les données "tournent" selon :
14 ### python -> json -> (export/import) -> formulaire HTML -> données POST -> python etc
16 ############ Fonctions de conversion
18 def convertit_jours_vers_python(chaine
,liste_err
):
19 """ convertit une chaine de type 1a 3m 1s 10j en jours
20 Renvoie un nombre de jours en float
21 Si un des caractères n'est ni un nombre, ni une lettre "autorisée" ni une espace,
22 on affiche un warning et on ignore ce caractère
24 # debug("conversion de "+chaine+" vers un nb de jours",liste_err)
29 chainenombre
+= lettre
31 if lettre
== 'a' or lettre
== 'A':
32 # On a trouvé l'année, on ajoute tout ce qui est trouvé jusque là
33 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_annee"]
35 elif lettre
== 'm' or lettre
== 'M':
37 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_mois"]
39 elif lettre
== 's' or lettre
== 'S':
41 agejours
+= int(chainenombre
)*CONFIG
["jours_dans_semaine"]
43 elif lettre
== 'j' or lettre
== 'J':
45 agejours
+= int(chainenombre
)
48 # autre caractère : bizarre ?
49 warning("problème à la conversion de "+chaine
+". Caractère invalide : "+lettre
,liste_err
)
50 # à la fin s'il reste qqch on le garde dans les jours
51 if chainenombre
!= "":
52 agejours
+= int(chainenombre
)
54 warning("L'âge est négatif !",liste_err
)
56 # debug("On a convertit ! Résultat : "+str(agejours),liste_err)
60 def convertit_age_vers_texte(nombre
):
61 """ convertit un nombre de jours en un truc plus lisible en mois, années, jours
62 et renvoie une chaîne sous la forme 3a2m1j par exemple"""
63 annees
= int(nombre
/ CONFIG
["jours_dans_annee"])
64 restant
= nombre
- annees
*CONFIG
["jours_dans_annee"]
65 mois
= int(restant
/CONFIG
["jours_dans_mois"])
66 jours
= round(nombre
- mois
*CONFIG
["jours_dans_mois"] - annees
*CONFIG
["jours_dans_annee"])
70 chaine
+= str(annees
)+"a"
72 chaine
+= str(mois
)+"m"
73 if jours
>0 or nombre
==0: # si c'est la naissance, faut beien écrire 0j quand même
74 chaine
+= str(jours
)+"j"
77 ##########################
79 # fonction qui calcule "auto" le maxi du graphique en fonction du max
80 def calcule_max_graphique(l_jours
):
81 """ calcule l'age maxi sur le graphique"""
83 return CONFIG
["jours_defaut_donneesvides"]
85 jour_maxi
= max(l_jours
)# pas la peine d'aller très au delà du jour max
86 jour_maxi
= int(jour_maxi
* 1.2)+3 # on rajoute un peu
90 def simplifie_nom(chaine
):
91 """ simplifie le nom chaine afin d'en faire une extension
92 pour le nom du fichier. Met tout en minuscules et vire les caractères spéciaux
93 et max 15 caractères"""
98 chaine2
= unidecode
.unidecode(chaine2
)
101 def convertit_donnee_vers_python(chaine
,typedonnee
,liste_err
):
102 """ convertit une chaine vers un float qui est le type donnee voulu.
103 La virgule peut être . ou , et on vire d'éventuels espaces.
104 Taille invalide : on renvoie 0 avec un warning.
105 Si la chaine est en fait déjà au format float, on laisse tel quel"""
106 if type(chaine
) == float:
108 chaine2
= chaine
.replace(",",".")
109 chaine2
= chaine2
.replace(" ","")
113 donnee
= float(chaine2
)
115 warning(typedonnee
+" impossible à lire : "+chaine
,liste_err
)
118 # Pour le poids, un cas particulier
119 if typedonnee
== "poids" and donnee
> CONFIG
["poids_maxi_conversion"]:
120 donnee
= donnee
/1000 # conversion en grammes
121 if not( 0<=donnee
<CONFIG
[typedonnee
+"_maxi"]):
122 warning(typedonnee
+" incohérent(e) : "+str(donnee
),liste_err
)
127 #########################
130 def convertit_date_vers_python(chaine
,liste_err
):
131 """ prend une chaine comme renvoyée par un champ de formulaire date
132 aaaa-mm-jj et en fait une date python
133 renvoie "" si ne marche pas"""
134 liste
= chaine
.split("-")
136 warning("La date : "+chaine
+" est invalide !",liste_err
)
139 debug("Conversion de la date "+chaine
+". Découpage : "+str(liste
),liste_err
)
141 date
= datetime
.date(int(liste
[0]),int(liste
[1]),int(liste
[2]))
144 warning("Impossible de lire la date "+chaine
+". Format accepté : aaaa-mm-jj",liste_err
)
148 def convertit_date_vers_texte(date
):
149 """ convertit une date python en format texte aaaa-mm-jj"""
153 #return (str(date.year)+"-"+str(date.month)+"-"+str(date.day))
156 def delta_date(date1
,datenaissance
):
157 """ renvoie le nombre de jours (entier) entre date1 et datenaissance format "datetime"
158 datenaissance est supposée antérieure. Erreur sinon."""
159 d
= date1
- datenaissance
162 warning("La différence entre les dates est négative... :/")
168 ################### On regroupe tout ce qui gère les données en une fonction
170 def web_vers_python(data
,liste_err
, court
=False):
171 """ prend en argument le dictionnaire de requête et renvoie la config, et les
173 court : si True est précisé, on ne met que le nom dans la config (enfant
176 # Régler la configuration
177 config
= gere_configuration(data
,liste_err
, court
)
179 # récupérer les données
182 for typed
in CONFIG
["liste_typedonnees"]:
183 listes_jours
[typed
],listes_donnees
[typed
] = gere_donnees(data
,config
["naissance"],typed
,liste_err
)
185 # Si on veut extrapoler au-delà du jour maxi, on adapte
187 # Si on a choisi la même échelle de données
188 if config
.get("memechelle") == "oui":
189 config
["non_sauve"]["maxi"] = calcule_max_graphique([j
for lj
in listes_jours
.values() for j
in lj
])
190 # En cas d'extrapolation, on prend le maxi
191 if config
["non_sauve"]["calculextradata_type"] !="" and config
["non_sauve"]["calculextradata_age"]>config
["non_sauve"]["maxi"]:
192 config
["non_sauve"]["maxi"] = int(config
["non_sauve"]["calculextradata_age"]) +1
193 config
["non_sauve"]["unite"] = choix_unite(config
["non_sauve"]["maxi"])
195 return (config
,listes_jours
,listes_donnees
)
199 ########### Fonctions qui gèretn les données web vers python
201 def gere_checkbox(chaine
):
202 """ prend en arg une chaine, et renvoie "oui" si c'est "on" (sortie de la checkbox)
203 et chaîne vide si n'importe quoi d'autre"""
209 def gere_symbole(chaine
):
210 """ prend en arg une chaîne genre "o", ">" et vérifie si c'est un symbole valide.
211 Renvoie ce symbole-là ou le défaut"""
212 if chaine
in CONFIG
["liste_symboles"]:
215 return DEFAUT
["symbole"]
217 def gere_configuration(data
,liste_err
, court
=False):
218 """ prend en argument le dictionnaire de requête (configuration imparfaite), et
219 construit le dictionnaire de configuration qui va bien.
220 Vérifie que chaque entrée est cohérente évidemment.
221 court : si mis à True, on ne met que le nom dans la configuraion,
222 ainsi que la date de naissance et le sexe"""
224 configuration
= {"non_sauve": {}
}
226 # Pour le nom, osef qu'il soit vide
227 nom
= data
.get("nom","")
228 # Par contre s'il est trop long on le tronque
229 configuration
["nom"] = nom
[:CONFIG
["longueur_max_nom_bebe"]]
231 naissance
= data
.get("naissance","")
233 naissance
= convertit_date_vers_python(naissance
,liste_err
)
234 configuration
["naissance"] = naissance
236 sexe
= data
.get("sexe","")
237 if not (sexe
in ["F","M","N"]):
238 warning("Le sexe de l'enfant est invalide ! "+sexe
,liste_err
)
240 configuration
["sexe"] = sexe
244 prematurite
= data
.get("prematurite","")
245 j
= convertit_jours_vers_python(prematurite
,liste_err
)
246 configuration
["prematurite"] = convertit_age_vers_texte(j
)
248 configuration
["agecorrige"] = gere_checkbox(data
.get("agecorrige",""))
250 # Type de courbe. Au pire on met P
251 tyc
= data
.get("typecourbe","")
252 if not (tyc
in ["P","Z"]):
254 configuration
["typecourbe"] = tyc
257 unite
= data
.get("unite","")
258 if not (unite
in CONFIG
["liste_unites"]):
260 #warning("L'unité "+unite+" n'est pas reconnue !",liste_err)
261 configuration
["unite"] = unite
264 configuration
["grille"] = gere_checkbox(data
.get("grille",""))
266 # tracer ou non les courbes vides
267 configuration
["tracevide"] = gere_checkbox(data
.get("tracevide",""))
269 # Même échelle sur tous les graphiques
270 configuration
["memechelle"] = gere_checkbox(data
.get("memechelle",""))
273 # maxi. 0 signifie qu'on veut pas de maxi
274 maxi
= data
.get("maxi","")
276 configuration
["maxi"] = 0
278 configuration
["maxi"] = int(convertit_jours_vers_python(maxi
,liste_err
))
280 # dimensions du graphique
281 largeur
= data
.get("largeur","")
283 largeur
= DEFAUT
["largeur_graphique"]
286 largeur
= int(largeur
)
288 warning("La largeur "+largeur
+"est invalide !",liste_err
)
289 largeur
= DEFAUT
["largeur_graphique"]
290 if largeur
> CONFIG
["largeur_graphique_max"]:
291 largeur
= CONFIG
["largeur_graphique_max"]
292 warning("Largeur du graphique trop grande !",liste_err
)
293 elif largeur
< CONFIG
["largeur_graphique_min"]:
294 largeur
= CONFIG
["largeur_graphique_min"]
295 warning("Largeur du graphique trop petite !",liste_err
)
296 configuration
["largeur"] = largeur
298 hauteur
= data
.get("hauteur","")
300 hauteur
= DEFAUT
["hauteur_graphique"]
303 hauteur
= int(hauteur
)
305 warning("La hauteur "+hauteur
+"est invalide !",liste_err
)
306 hauteur
= DEFAUT
["hauteur_graphique"]
307 if hauteur
> CONFIG
["hauteur_graphique_max"]:
308 hauteur
= CONFIG
["hauteur_graphique_max"]
309 warning("Hauteur du graphique trop grande !",liste_err
)
310 elif hauteur
< CONFIG
["hauteur_graphique_min"]:
311 hauteur
= CONFIG
["hauteur_graphique_min"]
312 warning("Hauteur du graphique trop petite !",liste_err
)
313 configuration
["hauteur"] = hauteur
315 # existence et position de la légende
316 configuration
["legende"] = gere_checkbox(data
.get("legende",""))
318 positionlegende
= data
.get("positionlegende","")
319 if not(positionlegende
in ['upper left','upper right','lower left','lower right']):
320 positionlegende
= "upper left"
321 configuration
["positionlegende"] = positionlegende
323 configuration
["couleurs"] = {}
325 for clecouleur
in DEFAUT
["couleurs"]:
326 coul
= rgb_vers_tuple(data
.get("couleur_"+clecouleur
,""),DEFAUT
["couleurs"].get(clecouleur
, ""),liste_err
)
327 configuration
["couleurs"][clecouleur
] = coul
330 configuration
["symbole"] = gere_symbole( data
.get("symbole", ""))
332 configuration
["non_sauve"]["grilleamelio"] = gere_checkbox(data
.get("grilleamelio",""))
335 #### La partie extrapolation n'a pas besoin d'être sauvée
336 configuration
["non_sauve"]["prolongercourbes"] = gere_checkbox(data
.get("prolongercourbes",""))
338 # Valeur par défaut : 1
339 debug(data
.get("nbextradata", "aaargh"), liste_err
)
340 nbextradata
= data
.get("nbextradata",1)
342 nbextradata
= int(nbextradata
)
344 warning("Le nombre de données sur lequel on extrapole est invalide : "+nbextradata
, liste_err
)
346 configuration
["non_sauve"]["nbextradata"] = nbextradata
348 if data
.get("calculextradata_type","") in CONFIG
["liste_typedonnees"]:
349 configuration
["non_sauve"]["calculextradata_type"] = data
.get("calculextradata_type","")
350 configuration
["non_sauve"]["calculextradata_age"] = convertit_jours_vers_python(data
.get("calculextradata_age","0j"),liste_err
)
352 configuration
["non_sauve"]["calculextradata_type"] = ""
353 # On ne met rien dans l'âge, pas la peine
355 ctyped
= data
.get("calculextratemps_type","")
356 if ctyped
in CONFIG
["liste_typedonnees"]:
357 configuration
["non_sauve"]["calculextratemps_type"] = ctyped
358 configuration
["non_sauve"]["calculextratemps_val"] = convertit_donnee_vers_python(data
.get("calculextratemps_val",""), ctyped
, liste_err
)
360 configuration
["non_sauve"]["calculextratemps_type"] = ""
362 # Tracer les calculs sur la grille
363 configuration
["non_sauve"]["calculextradata_trace"] = gere_checkbox(data
.get("calculextradata_trace"))
364 configuration
["non_sauve"]["calculextratemps_trace"] = gere_checkbox(data
.get("calculextratemps_trace"))
371 ## web vers python : données
372 def gere_donnees(data
,naissance
,typedonnee
,liste_err
):
373 """ prend en argument le dictionnaire de requête, et la date de
374 naissance (éventuellement vide), et construit deux listes :
375 l_jours et l_data correspondantes.
376 Il faut donner en argument le type de données : voir
377 CONFIG["liste_typedonnees"]"""
378 if typedonnee
not in CONFIG
["liste_typedonnees"]:
379 warning("gere_donnees : le type de données : "+typedonnee
+" est invalide !! Types acceptés : "+str(CONFIG
["liste_typedonnees"]),liste_err
)
382 # On construit une liste de couples d'abord
386 # On va chercher si y'a des données à donnee_i
387 while "age_"+str(i
) in data
.keys():
388 if data
.get(typedonnee
+"_"+str(i
), "") != "": # si la donne de ce type existe
389 donnee
= convertit_donnee_vers_python(data
[typedonnee
+"_"+str(i
)],typedonnee
,liste_err
)
390 age
= data
.get("age_"+str(i
),"")
392 age
= convertit_jours_vers_python(age
,liste_err
)
393 liste_donnees
.append((age
,donnee
))
395 date
= data
.get("date_"+str(i
),"")
396 datep
= convertit_date_vers_python(date
,liste_err
)
399 warning("La date de naissance n'a pas été précisée. Du coup on ne peut pas calculer l'âge de l'enfant le "+date
,liste_err
)
400 elif datep
!= "": # la date est valide et on a une date de naissance
401 age
= delta_date(datep
,naissance
)
402 liste_donnees
.append((age
,donnee
))
406 liste_donnees
.sort(key
=lambda x
: x
[0])
409 l_jours
= [x
[0] for x
in liste_donnees
]
410 l_donnee
= [x
[1] for x
in liste_donnees
]
412 return (l_jours
,l_donnee
)
416 #### export vers json
418 def donnees_vers_json(l_jours
,l_poids
,l_jourst
,l_taille
,config
):
419 """ retourne le json à renvoyer"""
420 gros_dico
= copy
.deepcopy(config
)
421 gros_dico
["version"] = CONFIG
["version"]
422 l_jours2
= [convertit_age_vers_texte(d
) for d
in l_jours
]
423 l_jourst2
= [convertit_age_vers_texte(d
) for d
in l_jourst
]
424 gros_dico
["data_j"] = l_jours2
425 gros_dico
["data_p"] = l_poids
426 gros_dico
["data_jours_taille"] = l_jourst2
427 gros_dico
["data_taille"] = l_taille
428 # gérer la date de naissance
429 if gros_dico
.get("naissance","") != "":
430 gros_dico
["naissance"] = convertit_date_vers_texte(gros_dico
["naissance"])
431 # Calcul de toutes les dates de données
432 l_dates_poids
= [convertit_date_vers_texte( config
["naissance"] + datetime
.timedelta(days
=jours
) ) for jours
in l_jours
]
433 l_dates_taille
= [convertit_date_vers_texte( config
["naissance"] + datetime
.timedelta(days
=jours
) ) for jours
in l_jourst
]
434 gros_dico
["data_dates_poids"]= l_dates_poids
435 gros_dico
["data_dates_taille"] = l_dates_taille
439 gros_dico
["maxi"] = convertit_age_vers_texte(gros_dico
["maxi"])
441 for clecouleur
in DEFAUT
["couleurs"]:
442 gros_dico
["couleurs"][clecouleur
] = tuple_vers_rgb(gros_dico
["couleurs"][clecouleur
])
444 # Enlever ce qui ne se sauvegarde pas si y'a
445 if "non_sauve" in gros_dico
:
446 del gros_dico
["non_sauve"]
448 return json
.dumps(gros_dico
, indent
=2,ensure_ascii
=False )
450 def fusionne_donnees(listes_jours
,listes_donnees
, listes_dates
):
451 """ prend en argument deux dicos de listes. Chaque liste de jours est associée à une liste
452 de données (par la même clé de type de données). Il faut les fusionner pour avoir une liste de dictionnaires, de type
453 {"age":truc, "donnee1":truc, "donnee2":truc, ...} triée par ordre de jours. Si jamais une des données est vide,
454 le champ du dictionnaire n'est pas rempli
455 Le troisième paquet de listes (les dates) peut être vide ou bien simiaire : même clés."""
458 """ teste si les listes sont toutes vides """
459 for l
in lj
.values():
465 """ renvoie la clé de la liste où il y a le min """
466 cle_mini
= CONFIG
["liste_typedonnees"][0]
467 for (cle
,liste
) in lj
.items():
468 if lj
[cle_mini
]== []:
471 if convertit_jours_vers_python(lj
[cle
][0],initialise_erreurs())<convertit_jours_vers_python(lj
[cle_mini
][0],initialise_erreurs()):
476 while not(fini(listes_jours
)):
477 typedonnee
= mini(listes_jours
)
478 # On extrait les données dans les deux listes (jours et données)
479 jour
= listes_jours
[typedonnee
].pop(0)
480 donnee
= listes_donnees
[typedonnee
].pop(0)
482 if liste_f
== [] or jour
!= liste_f
[-1]["age"]: # Si le jour est un "nouveau" jour
483 liste_f
.append({"age":jour}
)
484 # On met à jour l'élément (ou on l'ajoute)
485 liste_f
[-1][typedonnee
] = donnee
487 # Si il y a une date associée, on la met !
488 if listes_dates
[typedonnee
] != []:
489 date
= listes_dates
[typedonnee
].pop(0)
490 liste_f
[-1]["date"] = convertit_date_vers_texte(date
)
496 ### COnversion json vers formulaire
497 # Json -> formulaire HTML
498 def fichier_json_vers_configdonnees(chaine
,liste_err
):
499 """ prend le json importé (chaine) et l'exporte vers les valeurs du formulaire
500 Renvoyé sous forme de dictionnaire (mais adapté au formulaire web)"""
501 debug("json vers config : Prêt à interpréter le json",liste_err
)
503 valform
= json
.loads(chaine
)
505 erreur("Impossible de lire le fichier json !",liste_err
)
507 # Il faut maintenant récupérer les l_jours et l_poids puis les remettre
508 # sous forme de age_i et poids_i
513 for typed
in CONFIG
["liste_typedonnees"]:
514 if typed
== "poids": # pour la rétrocompatibilité
515 listes_jours
[typed
] = valform
.get("data_j",[])
516 listes_donnees
[typed
] = valform
.get("data_p",[])
517 listes_dates
[typed
] = valform
.get("data_dates_"+typed
,[])
519 listes_jours
[typed
] = valform
.get("data_jours_"+typed
,[])
520 listes_donnees
[typed
] = valform
.get("data_"+typed
,[])
521 listes_dates
[typed
] = valform
.get("data_dates_"+typed
,[])
524 debug("Avant fusion : listes jours "+str(listes_jours
),liste_err
)
525 liste_donnees
= fusionne_donnees(listes_jours
,listes_donnees
, listes_dates
)
526 debug("Fusion de listes ok. Liste moche : "+str(liste_donnees
),liste_err
)
527 for i
in range(len(liste_donnees
)):
528 for (cle
,val
) in liste_donnees
[i
].items():
529 valform
[cle
+"_"+str(i
)] = val
531 valform
["nb_data"] = max(len(liste_donnees
) +2,DEFAUT
["nb_data"])
537 #### Pour l'insertion d'une 2e (ou plus) courbe sur le graphique, ue fonction qui sépare tout ça
538 def eclate_donnees_additionnelles(conf
, ljours
, ldonnees
, symb
, couleur
):
539 """ conf est la config (on ne garde que le nom) pour un enfant additionnel,
540 ljours et ldonnees les dictionnaires de listes contenant les données.
541 symb est le symbole choisi pour cette courbe additionnelle (déjà vérifié)
542 On fabrique un joli dictionnaire typed -> (conf lj, ldonnee) avec le nom de l'enfant,
543 et les données pour chaque typed"""
544 print("test conf avant "+str(ldonnees
)+str(ljours
))
547 conf
["symbole"] = symb
# On ajoute le symbole additionnel
548 conf
["couleurcourbe"] = couleur
# la couleur
549 for typed
in CONFIG
["liste_typedonnees"]:
550 retour
[typed
] = (conf
, ljours
[typed
], ldonnees
[typed
])
552 print("test "+str(retour
))