2023-02-14 10:57:02 +01:00
"""
Script de migration des données de la base " absences " - > " assiduites " / " justificatifs "
Ecrit par Matthias HARTMANN
"""
from datetime import date , datetime , time , timedelta
from json import dump , dumps
from sqlalchemy import not_
2023-02-07 18:49:51 +01:00
from app import db
from app . models import (
Absence ,
2023-02-14 10:57:02 +01:00
Assiduite ,
Departement ,
2023-02-07 18:49:51 +01:00
Identite ,
2023-02-14 10:57:02 +01:00
Justificatif ,
2023-02-09 21:04:53 +01:00
ModuleImplInscription ,
2023-02-07 18:49:51 +01:00
)
2023-02-14 10:57:02 +01:00
from app . profiler import Profiler
2023-02-09 21:04:53 +01:00
from app . scodoc . sco_utils import (
EtatAssiduite ,
EtatJustificatif ,
2023-02-14 10:57:02 +01:00
TerminalColor ,
2023-02-09 21:04:53 +01:00
localize_datetime ,
2023-02-14 10:57:02 +01:00
print_progress_bar ,
2023-02-09 21:04:53 +01:00
)
2023-02-11 14:26:08 +01:00
class _Merger :
2023-02-14 10:57:02 +01:00
""" pour typage """
2023-02-11 14:26:08 +01:00
2023-02-07 18:49:51 +01:00
2023-02-09 21:04:53 +01:00
class _glob :
2023-02-14 10:57:02 +01:00
""" variables globales du script """
2023-02-09 21:04:53 +01:00
PROBLEMS : dict [ int , list [ str ] ] = { }
CURRENT_ETU : list = [ ]
MODULES : list [ tuple [ int , int ] ] = [ ]
COMPTE : list [ int , int ] = [ ]
2023-02-10 14:08:31 +01:00
ERR_ETU : list [ int ] = [ ]
2023-02-13 17:50:58 +01:00
MERGER_ASSI : _Merger = None
MERGER_JUST : _Merger = None
2023-02-11 14:26:08 +01:00
MORNING : time = None
NOON : time = None
EVENING : time = None
class _Merger :
2023-02-14 10:57:02 +01:00
def __init__ ( self , abs_ : Absence , est_abs : bool ) - > None :
self . deb = ( abs_ . jour , abs_ . matin )
self . fin = ( abs_ . jour , abs_ . matin )
self . moduleimpl = abs_ . moduleimpl_id
self . etudid = abs_ . etudid
2023-02-13 17:50:58 +01:00
self . est_abs = est_abs
2023-02-14 10:57:02 +01:00
self . raison = abs_ . description
2023-02-11 14:26:08 +01:00
2023-02-14 10:57:02 +01:00
def merge ( self , abs_ : Absence ) - > bool :
""" Fusionne les absences """
2023-02-11 14:26:08 +01:00
2023-02-14 10:57:02 +01:00
if self . etudid != abs_ . etudid :
2023-02-11 14:26:08 +01:00
return False
# Cas d'une même absence enregistrée plusieurs fois
2023-02-14 10:57:02 +01:00
if self . fin == ( abs_ . jour , abs_ . matin ) :
2023-02-11 14:26:08 +01:00
self . moduleimpl = None
else :
if self . fin [ 1 ] :
2023-02-14 10:57:02 +01:00
if abs_ . jour != self . fin [ 0 ] :
2023-02-11 14:26:08 +01:00
return False
else :
2023-02-14 10:57:02 +01:00
day_after : date = abs_ . jour - timedelta ( days = 1 ) == self . fin [ 0 ]
if not ( day_after and abs_ . matin ) :
2023-02-11 14:26:08 +01:00
return False
2023-02-14 10:57:02 +01:00
self . fin = ( abs_ . jour , abs_ . matin )
2023-02-11 14:26:08 +01:00
return True
@staticmethod
2023-02-14 10:57:02 +01:00
def _tuple_to_date ( couple : tuple [ date , bool ] , end = False ) :
if couple [ 1 ] :
2023-02-11 14:26:08 +01:00
time_ = _glob . NOON if end else _glob . MORNING
2023-02-14 10:57:02 +01:00
date_ = datetime . combine ( couple [ 0 ] , time_ )
2023-02-11 14:26:08 +01:00
else :
time_ = _glob . EVENING if end else _glob . NOON
2023-02-14 10:57:02 +01:00
date_ = datetime . combine ( couple [ 0 ] , time_ )
2023-02-11 14:26:08 +01:00
d = localize_datetime ( date_ )
return d
def _to_justif ( self ) :
date_deb = _Merger . _tuple_to_date ( self . deb )
date_fin = _Merger . _tuple_to_date ( self . fin , end = True )
retour = Justificatif . fast_create_justificatif (
etudid = self . etudid ,
date_debut = date_deb ,
date_fin = date_fin ,
etat = EtatJustificatif . VALIDE ,
raison = self . raison ,
)
return retour
def _to_assi ( self ) :
date_deb = _Merger . _tuple_to_date ( self . deb )
date_fin = _Merger . _tuple_to_date ( self . fin , end = True )
retour = Assiduite . fast_create_assiduite (
etudid = self . etudid ,
date_debut = date_deb ,
date_fin = date_fin ,
etat = EtatAssiduite . ABSENT ,
moduleimpl_id = self . moduleimpl ,
description = self . raison ,
)
return retour
def export ( self ) :
2023-02-14 10:57:02 +01:00
""" Génère un nouvel objet Assiduité ou Justificatif """
obj : Assiduite or Justificatif = None
2023-02-11 14:26:08 +01:00
if self . est_abs :
_glob . COMPTE [ 0 ] + = 1
2023-02-14 10:57:02 +01:00
obj = self . _to_assi ( )
2023-02-13 17:50:58 +01:00
else :
2023-02-11 14:26:08 +01:00
_glob . COMPTE [ 1 ] + = 1
2023-02-14 10:57:02 +01:00
obj = self . _to_justif ( )
2023-02-11 14:26:08 +01:00
2023-02-14 10:57:02 +01:00
db . session . add ( obj )
2023-02-10 14:08:31 +01:00
class _Statistics :
def __init__ ( self ) - > None :
2023-02-14 10:57:02 +01:00
self . object : dict [ str , dict or int ] = { " total " : 0 }
2023-02-10 14:08:31 +01:00
self . year : int = None
def __set_year ( self , year : int ) :
if year not in self . object :
self . object [ year ] = {
" etuds_inexistant " : [ ] ,
" abs_invalide " : { } ,
}
self . year = year
return self
def __add_etud ( self , etudid : int ) :
if etudid not in self . object [ self . year ] [ " etuds_inexistant " ] :
self . object [ self . year ] [ " etuds_inexistant " ] . append ( etudid )
return self
2023-02-14 10:57:02 +01:00
def __add_abs ( self , abs_ : int , err : str ) :
if abs_ not in self . object [ self . year ] [ " abs_invalide " ] :
self . object [ self . year ] [ " abs_invalide " ] [ abs_ ] = [ err ]
2023-02-10 14:08:31 +01:00
else :
2023-02-14 10:57:02 +01:00
self . object [ self . year ] [ " abs_invalide " ] [ abs_ ] . append ( err )
2023-02-10 14:08:31 +01:00
return self
2023-02-14 10:57:02 +01:00
def add_problem ( self , abs_ : Absence , err : str ) :
""" Ajoute un nouveau problème dans les statistiques """
abs_ . jour : date
pivot : date = date ( abs_ . jour . year , 9 , 15 )
year : int = abs_ . jour . year
if pivot < abs_ . jour :
2023-02-10 14:08:31 +01:00
year + = 1
self . __set_year ( year )
if err == " Etudiant inexistant " :
2023-02-14 10:57:02 +01:00
self . __add_etud ( abs_ . etudid )
2023-02-10 14:08:31 +01:00
else :
2023-02-14 10:57:02 +01:00
self . __add_abs ( abs_ . id , err )
2023-02-10 14:08:31 +01:00
self . object [ " total " ] + = 1
def compute_stats ( self ) - > dict :
2023-02-14 10:57:02 +01:00
""" Comptage des statistiques """
2023-02-10 14:08:31 +01:00
stats : dict = { " total " : self . object [ " total " ] }
2023-02-14 10:57:02 +01:00
for year , item in self . object . items ( ) :
2023-02-10 14:08:31 +01:00
if year == " total " :
continue
2023-02-14 10:57:02 +01:00
2023-02-10 14:08:31 +01:00
stats [ year ] = { }
2023-02-14 10:57:02 +01:00
stats [ year ] [ " etuds_inexistant " ] = len ( item [ " etuds_inexistant " ] )
stats [ year ] [ " abs_invalide " ] = len ( item [ " abs_invalide " ] )
2023-02-10 14:08:31 +01:00
return stats
def export ( self , file ) :
2023-02-14 10:57:02 +01:00
""" Sérialise les statistiques dans un fichier """
2023-02-10 14:08:31 +01:00
dump ( self . object , file , indent = 2 )
2023-02-08 19:48:34 +01:00
2023-02-07 18:49:51 +01:00
def migrate_abs_to_assiduites (
dept : str = " " , morning : str = None , noon : str = None , evening : str = None
) :
"""
une absence à 3 états :
| . estabs | . estjust |
| 1 | 0 | - > absence non justifiée
| 1 | 1 | - > absence justifiée
| 0 | 1 | - > justifié
dualité des temps :
. matin : bool ( 0 : 00 - > time_pref | time_pref - > 23 : 59 : 59 )
. jour : date ( jour de l ' absence/justificatif)
. moduleimpl_id : relation - > moduleimpl_id
description : str - > motif abs / raision justif
. entry_date : datetime - > timestamp d ' entrée de l ' abs
. etudid : relation - > Identite
"""
2023-02-09 21:04:53 +01:00
Profiler . clear ( )
2023-02-10 14:08:31 +01:00
stats : _Statistics = _Statistics ( )
2023-02-09 21:04:53 +01:00
time_elapsed : Profiler = Profiler ( " migration " )
time_elapsed . start ( )
2023-02-08 19:48:34 +01:00
if morning is None :
2023-02-11 14:26:08 +01:00
_glob . MORNING = time ( 8 , 0 )
2023-02-07 18:49:51 +01:00
else :
morning : list [ str ] = morning . split ( " h " )
2023-02-11 14:26:08 +01:00
_glob . MORNING = time ( int ( morning [ 0 ] ) , int ( morning [ 1 ] ) )
2023-02-07 18:49:51 +01:00
2023-02-08 19:48:34 +01:00
if noon is None :
2023-02-11 14:26:08 +01:00
_glob . NOON = time ( 12 , 0 )
2023-02-07 18:49:51 +01:00
else :
noon : list [ str ] = noon . split ( " h " )
2023-02-11 14:26:08 +01:00
_glob . NOON = time ( int ( noon [ 0 ] ) , int ( noon [ 1 ] ) )
2023-02-07 18:49:51 +01:00
2023-02-08 19:48:34 +01:00
if evening is None :
2023-02-11 14:26:08 +01:00
_glob . EVENING = time ( 18 , 0 )
2023-02-07 18:49:51 +01:00
else :
evening : list [ str ] = evening . split ( " h " )
2023-02-11 14:26:08 +01:00
_glob . EVENING = time ( int ( evening [ 0 ] ) , int ( evening [ 1 ] ) )
2023-02-07 18:49:51 +01:00
absences_query = Absence . query
2023-02-08 19:48:34 +01:00
if dept is not None :
2023-02-07 18:49:51 +01:00
2023-02-09 21:04:53 +01:00
dept : Departement = Departement . query . filter_by ( acronym = dept ) . first ( )
if dept is not None :
etuds_id : list [ int ] = [ etud . id for etud in dept . etudiants ]
absences_query = absences_query . filter ( Absence . etudid . in_ ( etuds_id ) )
2023-02-11 14:26:08 +01:00
absences : Absence = absences_query . order_by (
Absence . etudid , Absence . jour , not_ ( Absence . matin )
)
2023-02-07 18:49:51 +01:00
2023-02-09 21:04:53 +01:00
_glob . CURRENT_ETU = [ ]
_glob . MODULES = [ ]
_glob . COMPTE = [ 0 , 0 ]
2023-02-10 14:08:31 +01:00
_glob . ERR_ETU = [ ]
2023-02-13 17:50:58 +01:00
_glob . MERGER_ASSI = None
_glob . MERGER_JUST = None
2023-02-09 21:04:53 +01:00
absences_len : int = absences . count ( )
print (
2023-02-14 10:57:02 +01:00
f " { TerminalColor . BLUE } { absences_len } absences vont être migrées { TerminalColor . RESET } "
2023-02-09 21:04:53 +01:00
)
2023-02-08 19:48:34 +01:00
2023-02-14 10:57:02 +01:00
print_progress_bar ( 0 , absences_len , " Progression " , " effectué " , autosize = True )
2023-02-08 19:48:34 +01:00
2023-02-14 10:57:02 +01:00
for i , abs_ in enumerate ( absences ) :
2023-02-13 17:50:58 +01:00
2023-02-09 21:04:53 +01:00
try :
2023-02-14 10:57:02 +01:00
_from_abs_to_assiduite_justificatif ( abs_ )
except ValueError as e :
stats . add_problem ( abs_ , e . args [ 0 ] )
2023-02-09 21:04:53 +01:00
if i % 10 == 0 :
2023-02-14 10:57:02 +01:00
print_progress_bar (
2023-02-09 21:04:53 +01:00
i ,
absences_len ,
" Progression " ,
" effectué " ,
autosize = True ,
2023-02-07 18:49:51 +01:00
)
2023-02-08 19:48:34 +01:00
2023-02-09 21:04:53 +01:00
if i % 1000 == 0 :
2023-02-14 10:57:02 +01:00
print_progress_bar (
2023-02-09 21:04:53 +01:00
i ,
absences_len ,
" Progression " ,
" effectué " ,
autosize = True ,
)
db . session . commit ( )
2023-02-07 18:49:51 +01:00
2023-02-13 17:50:58 +01:00
_glob . MERGER_ASSI . export ( )
_glob . MERGER_JUST . export ( )
2023-02-07 18:49:51 +01:00
db . session . commit ( )
2023-02-14 10:57:02 +01:00
print_progress_bar (
2023-02-08 19:48:34 +01:00
absences_len ,
absences_len ,
" Progression " ,
" effectué " ,
autosize = True ,
2023-02-09 21:04:53 +01:00
)
time_elapsed . stop ( )
2023-02-10 14:08:31 +01:00
statistiques : dict = stats . compute_stats ( )
2023-02-09 21:04:53 +01:00
print (
2023-02-14 10:57:02 +01:00
f " { TerminalColor . GREEN } La migration a pris { time_elapsed . elapsed ( ) : .2f } secondes { TerminalColor . RESET } "
2023-02-09 21:04:53 +01:00
)
print (
2023-02-14 10:57:02 +01:00
f " { TerminalColor . RED } { statistiques [ ' total ' ] } absences qui n ' ont pas pu être migrées. "
2023-02-08 19:48:34 +01:00
)
2023-02-09 21:04:53 +01:00
print (
2023-02-14 10:57:02 +01:00
f " Vous retrouverez un fichier json { TerminalColor . GREEN } /opt/scodoc-data/log/scodoc_migration_abs.json { TerminalColor . RED } contenant les problèmes de migrations "
2023-02-09 21:04:53 +01:00
)
2023-02-11 14:26:08 +01:00
with open (
" /opt/scodoc-data/log/scodoc_migration_abs.json " , " w " , encoding = " utf-8 "
) as file :
2023-02-10 14:08:31 +01:00
stats . export ( file )
2023-02-09 21:04:53 +01:00
print (
2023-02-14 10:57:02 +01:00
f " { TerminalColor . CYAN } { _glob . COMPTE [ 0 ] } assiduités et { _glob . COMPTE [ 1 ] } justificatifs ont été générés. { TerminalColor . RESET } "
2023-02-09 21:04:53 +01:00
)
2023-02-10 14:08:31 +01:00
print ( dumps ( statistiques , indent = 2 ) )
2023-02-08 19:48:34 +01:00
2023-02-07 18:49:51 +01:00
2023-02-11 14:26:08 +01:00
def _from_abs_to_assiduite_justificatif ( _abs : Absence ) :
2023-02-07 18:49:51 +01:00
2023-02-09 21:04:53 +01:00
if _abs . etudid not in _glob . CURRENT_ETU :
etud : Identite = Identite . query . filter_by ( id = _abs . etudid ) . first ( )
if etud is None :
2023-02-14 10:57:02 +01:00
raise ValueError ( " Etudiant inexistant " )
2023-02-09 21:04:53 +01:00
_glob . CURRENT_ETU . append ( _abs . etudid )
2023-02-11 14:26:08 +01:00
if _abs . estabs :
moduleimpl_id : int = _abs . moduleimpl_id
if (
moduleimpl_id is not None
and ( _abs . etudid , _abs . moduleimpl_id ) not in _glob . MODULES
) :
moduleimpl_inscription : ModuleImplInscription = (
ModuleImplInscription . query . filter_by (
moduleimpl_id = _abs . moduleimpl_id , etudid = _abs . etudid
) . first ( )
)
if moduleimpl_inscription is None :
2023-02-14 10:57:02 +01:00
raise ValueError ( " Moduleimpl_id incorrect ou étudiant non inscrit " )
2023-02-09 21:04:53 +01:00
2023-02-13 17:50:58 +01:00
if _glob . MERGER_ASSI is None :
_glob . MERGER_ASSI = _Merger ( _abs , True )
return True
elif _glob . MERGER_ASSI . merge ( _abs ) :
return True
else :
_glob . MERGER_ASSI . export ( )
_glob . MERGER_ASSI = _Merger ( _abs , True )
return False
if _glob . MERGER_JUST is None :
_glob . MERGER_JUST = _Merger ( _abs , False )
return True
elif _glob . MERGER_JUST . merge ( _abs ) :
2023-02-11 14:26:08 +01:00
return True
2023-02-07 18:49:51 +01:00
else :
2023-02-13 17:50:58 +01:00
_glob . MERGER_JUST . export ( )
_glob . MERGER_JUST = _Merger ( _abs , False )
return False