module assiduités : fusion cache WIP

This commit is contained in:
iziram 2023-02-13 17:50:58 +01:00
parent aa956f4530
commit 0f3e1ea95e
4 changed files with 196 additions and 179 deletions

View File

@ -42,7 +42,7 @@ from app.scodoc import sco_cache
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.models import Assiduite from app.models import Assiduite, Justificatif
import app.scodoc.sco_assiduites as scass import app.scodoc.sco_assiduites as scass
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -1059,28 +1059,37 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
tuple (nb abs, nb abs justifiées) tuple (nb abs, nb abs justifiées)
Utilise un cache. Utilise un cache.
""" """
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso + "_assiduites"
r = sco_cache.AbsSemEtudCache.get(key) r = sco_cache.AbsSemEtudCache.get(key)
if not r: if not r:
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True) date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
date_fin: datetime.datetime = scu.is_iso_formated(date_debut_iso, True) date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid) assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
assiduites = scass.filter_assiduites_by_date(assiduites, date_debut, sup=True) assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin)
assiduites = scass.filter_assiduites_by_date(assiduites, date_fin, sup=False) justificatifs = scass.filter_by_date(
justificatifs, Justificatif, date_debut, date_fin
nb_abs = scass.get_count(assiduites)["demi"]
nb_abs_just = count_abs_just(
etudid=etudid,
debut=date_debut_iso,
fin=date_fin_iso,
) )
calculator: scass.CountCalculator = scass.CountCalculator()
calculator.compute_assiduites(assiduites)
nb_abs: dict = calculator.to_dict()["demi"]
abs_just: list[Assiduite] = scass.get_all_justified(
justificatifs, date_debut, date_fin
)
calculator.reset()
calculator.compute_assiduites(abs_just)
nb_abs_just: dict = calculator.to_dict()["demi"]
r = (nb_abs, nb_abs_just) r = (nb_abs, nb_abs_just)
ans = sco_cache.AbsSemEtudCache.set(key, r) ans = sco_cache.AbsSemEtudCache.set(key, r)
if not ans: if not ans:
log("warning: get_abs_count failed to cache") log("warning: get_assiduites_count failed to cache")
return r return r

View File

@ -4,6 +4,7 @@ import app.scodoc.sco_utils as scu
from app.models.assiduites import Assiduite, Justificatif from app.models.assiduites import Assiduite, Justificatif
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre, FormSemestreInscription from app.models.formsemestre import FormSemestre, FormSemestreInscription
from app.profiler import Profiler
class CountCalculator: class CountCalculator:
@ -37,6 +38,12 @@ class CountCalculator:
self.count: int = 0 self.count: int = 0
def reset(self):
self.days = []
self.half_days = []
self.hours = 0.0
self.count = 0
def add_half_day(self, day: date, is_morning: bool = True): def add_half_day(self, day: date, is_morning: bool = True):
key: tuple[date, bool] = (day, is_morning) key: tuple[date, bool] = (day, is_morning)
if key not in self.half_days: if key not in self.half_days:
@ -53,7 +60,9 @@ class CountCalculator:
scu.localize_datetime(datetime.combine(period[0].date(), self.noon)), scu.localize_datetime(datetime.combine(period[0].date(), self.noon)),
) )
in_morning: bool = scu.is_period_overlapping(period, interval_morning) in_morning: bool = scu.is_period_overlapping(
period, interval_morning, bornes=False
)
return in_morning return in_morning
def check_in_evening(self, period: tuple[datetime, datetime]) -> bool: def check_in_evening(self, period: tuple[datetime, datetime]) -> bool:
@ -113,7 +122,10 @@ class CountCalculator:
def compute_assiduites(self, assiduites: Assiduite): def compute_assiduites(self, assiduites: Assiduite):
assi: Assiduite assi: Assiduite
for assi in assiduites.all(): assiduites: list[Assiduite] = (
assiduites.all() if isinstance(assiduites, Assiduite) else assiduites
)
for assi in assiduites:
self.count += 1 self.count += 1
delta: timedelta = assi.date_fin - assi.date_debut delta: timedelta = assi.date_fin - assi.date_debut
@ -177,106 +189,6 @@ def get_assiduites_stats(
return output if output else count return output if output else count
# def big_counter(
# interval: tuple[datetime],
# pref_time: time = time(12, 0),
# ):
# curr_date: datetime
# if interval[0].time() >= pref_time:
# curr_date = scu.localize_datetime(
# datetime.combine(interval[0].date(), pref_time)
# )
# else:
# curr_date = scu.localize_datetime(
# datetime.combine(interval[0].date(), time(0, 0))
# )
# def next_(curr: datetime, journee):
# if curr.time() != pref_time:
# next_time = scu.localize_datetime(datetime.combine(curr.date(), pref_time))
# else:
# next_time = scu.localize_datetime(
# datetime.combine(curr.date() + timedelta(days=1), time(0, 0))
# )
# journee += 1
# return next_time, journee
# demi: int = 0
# j: int = 0
# while curr_date <= interval[1]:
# next_time: datetime
# next_time, j = next_(curr_date, j)
# if scu.is_period_overlapping((curr_date, next_time), interval, True):
# demi += 1
# curr_date = next_time
# delta: timedelta = interval[1] - interval[0]
# heures: float = delta.total_seconds() / 3600
# if delta.days >= 1:
# heures -= delta.days * 16
# return (demi, j, heures)
# def get_count(
# assiduites: Assiduite, noon: time = time(hour=12)
# ) -> dict[str, int or float]:
# """Fonction permettant de compter les assiduites
# -> seul "compte" est correcte lorsque les assiduites viennent de plusieurs étudiants
# """
# # TODO: Comptage demi journée / journée d'assiduité longue
# output: dict[str, int or float] = {}
# compte: int = assiduites.count()
# heure: float = 0.0
# journee: int = 0
# demi: int = 0
# all_assiduites: list[Assiduite] = assiduites.order_by(Assiduite.date_debut).all()
# current_day: date = None
# current_time: str = None
# midnight: time = time(hour=0)
# def time_check(dtime):
# return midnight <= dtime.time() <= noon
# for ass in all_assiduites:
# delta: timedelta = ass.date_fin - ass.date_debut
# if delta.days > 0:
# computed_values: tuple[int, int, float] = big_counter(
# (ass.date_debut, ass.date_fin), noon
# )
# demi += computed_values[0] - 1
# journee += computed_values[1] - 1
# heure += computed_values[2]
# current_day = ass.date_fin.date()
# continue
# heure += delta.total_seconds() / 3600
# ass_time: str = time_check(ass.date_debut)
# if current_day != ass.date_debut.date():
# current_day = ass.date_debut.date()
# current_time = ass_time
# demi += 1
# journee += 1
# if current_time != ass_time:
# current_time = ass_time
# demi += 1
# heure = round(heure, 2)
# return {"compte": compte, "journee": journee, "heure": heure, "demi": demi}
def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Assiduite: def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
""" """
Filtrage d'une collection d'assiduites en fonction de leur état Filtrage d'une collection d'assiduites en fonction de leur état
@ -323,27 +235,6 @@ def filter_justificatifs_by_etat(
return justificatifs.filter(Justificatif.etat.in_(etats)) return justificatifs.filter(Justificatif.etat.in_(etats))
def filter_justificatifs_by_date(
justificatifs: Justificatif, date_: datetime, sup: bool = True
) -> Assiduite:
"""
Filtrage d'une collection d'assiduites en fonction d'une date
Sup == True -> les assiduites doivent débuter après 'date'\n
Sup == False -> les assiduites doivent finir avant 'date'
"""
if date_.tzinfo is None:
first_justificatif: Justificatif = justificatifs.first()
if first_justificatif is not None:
date_: datetime = date_.replace(tzinfo=first_justificatif.date_debut.tzinfo)
if sup:
return justificatifs.filter(Justificatif.date_debut >= date_)
return justificatifs.filter(Justificatif.date_fin <= date_)
def filter_by_module_impl( def filter_by_module_impl(
assiduites: Assiduite, module_impl_id: int or None assiduites: Assiduite, module_impl_id: int or None
) -> Assiduite: ) -> Assiduite:
@ -376,26 +267,48 @@ def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemest
return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin) return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin)
def justifies(justi: Justificatif) -> list[int]: def justifies(justi: Justificatif, obj: bool = False) -> list[int]:
""" """
Retourne la liste des assiduite_id qui sont justifié par la justification Retourne la liste des assiduite_id qui sont justifié par la justification
Une assiduité est justifiée si elle est STRICTEMENT comprise dans la plage du justificatif Une assiduité est justifiée si elle est STRICTEMENT comprise dans la plage du justificatif
et que l'état du justificatif est "validé" et que l'état du justificatif est "validé"
renvoie des id si obj == False, sinon les Assiduités
""" """
justified: list[int] = []
if justi.etat != scu.EtatJustificatif.VALIDE: if justi.etat != scu.EtatJustificatif.VALIDE:
return justified return []
assiduites_query: Assiduite = Assiduite.query.join( assiduites_query: Assiduite = (
Justificatif, Assiduite.etudid == Justificatif.etudid Assiduite.query.join(Justificatif, Assiduite.etudid == Justificatif.etudid)
).filter(Assiduite.etat != scu.EtatAssiduite.PRESENT) .filter(Assiduite.etat != scu.EtatAssiduite.PRESENT)
.filter(
assiduites_query = filter_by_date( Assiduite.date_debut >= justi.date_debut,
assiduites_query, Assiduite, justi.date_debut, justi.date_fin Assiduite.date_debut <= justi.date_fin,
Assiduite.date_fin >= justi.date_debut,
Assiduite.date_fin <= justi.date_fin,
)
) )
justified = [assi.id for assi in assiduites_query.all()] if not obj:
return [assi.id for assi in assiduites_query.all()]
return justified return assiduites_query
def get_all_justified(
justificatifs: Justificatif, date_deb: datetime = None, date_fin: datetime = None
) -> list[Assiduite]:
if date_deb is None:
date_deb = datetime.min
if date_fin is None:
date_fin = datetime.max
date_deb = scu.localize_datetime(date_deb)
date_fin = scu.localize_datetime(date_fin)
assiduites: list[Assiduite] = []
for justi in justificatifs:
assis: list[Assiduite] = justifies(justi, obj=True)
assiduites.extend(assis)
return list(assiduites)

View File

@ -17,6 +17,12 @@ import app.scodoc.sco_assiduites as scass
from app.models import Assiduite, Justificatif, Identite, FormSemestre, ModuleImpl from app.models import Assiduite, Justificatif, Identite, FormSemestre, ModuleImpl
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_abs import (
get_abs_count_in_interval,
get_assiduites_count_in_interval,
)
from app.scodoc import sco_abs_views
from tools import migrate_abs_to_assiduites, downgrade_module
class BiInt(int, scu.BiDirectionalEnum): class BiInt(int, scu.BiDirectionalEnum):
@ -130,17 +136,100 @@ def test_general(test_client):
etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux") etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first() etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
verif_migration_abs_assiduites(g_fake)
ajouter_assiduites(etuds, moduleimpls, etud_faux) ajouter_assiduites(etuds, moduleimpls, etud_faux)
justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0]) justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
verifier_comptage_et_filtrage_assiduites( verifier_comptage_et_filtrage_assiduites(
etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3) etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3)
) )
verifier_filtrage_justificatifs(etuds[0], justificatifs) verifier_filtrage_justificatifs(etuds[0], justificatifs)
editer_supprimer_assiduites(etuds, moduleimpls) editer_supprimer_assiduites(etuds, moduleimpls)
editer_supprimer_justificatif(etuds[0]) editer_supprimer_justificatif(etuds[0])
def verif_migration_abs_assiduites(g_fake):
downgrade_module(assiduites=True, justificatifs=True)
etudid: int = 1
for debut, fin, demijournee in [
(
"02/01/2023",
"10/01/2023",
2,
), # 2 assiduités 02/01: 08h -> 06/01: 18h & assiduités 09/01: 08h -> 10/01: 18h | 14dj
("16/01/2023", "16/01/2023", 1), # 1 assiduité 16/01: 08h -> 16/01: 12h | 1dj
("19/01/2023", "19/01/2023", 0), # 1 assiduité 19/01: 12h -> 19/01: 18h | 1dj
("18/01/2023", "18/01/2023", 2), # 1 assiduité 18/01: 08h -> 18/01: 18h | 2dj
("23/01/2023", "23/01/2023", 0), # 1 assiduité 23/01: 12h -> 24/01: 18h | 3dj
("24/01/2023", "24/01/2023", 2),
]:
sco_abs_views.doSignaleAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
# --- Justification de certaines absences
for debut, fin, demijournee in [
(
"02/01/2023",
"10/01/2023",
2,
), # 2 justificatif 02/01: 08h -> 06/01: 18h & justificatif 09/01: 08h -> 10/01: 18h | 14dj
(
"19/01/2023",
"19/01/2023",
0,
), # 1 justificatif 19/01: 12h -> 19/01: 18h | 1dj
(
"18/01/2023",
"18/01/2023",
2,
), # 1 justificatif 18/01: 08h -> 18/01: 18h | 2dj
]:
sco_abs_views.doJustifAbsence(
datedebut=debut,
datefin=fin,
demijournee=demijournee,
etudid=etudid,
)
migrate_abs_to_assiduites()
assert Assiduite.query.count() == 6, "Erreur migration assiduites"
assert Justificatif.query.count() == 4, "Erreur migration justificatifs"
essais_cache(etudid, g_fake)
downgrade_module(assiduites=True, justificatifs=True)
def essais_cache(etudid, g_fake):
date_deb: str = "2023-01-01T07:00"
date_fin: str = "2023-03-31T19:00"
abs_count_no_cache: int = get_abs_count_in_interval(etudid, date_deb, date_fin)
abs_count_cache = get_abs_count_in_interval(etudid, date_deb, date_fin)
assiduites_count_no_cache = get_assiduites_count_in_interval(
etudid, date_deb, date_fin
)
assiduites_count_cache = get_assiduites_count_in_interval(
etudid, date_deb, date_fin
)
assert (
abs_count_cache
== abs_count_no_cache
== assiduites_count_cache
== assiduites_count_no_cache
== (21, 17)
), "Erreur cache"
def ajouter_justificatifs(etud): def ajouter_justificatifs(etud):
obj_justificatifs = [ obj_justificatifs = [
@ -329,10 +418,10 @@ def editer_supprimer_justificatif(etud: Identite):
# Modification de l'état # Modification de l'état
justi.etat = scu.EtatJustificatif.MODIFIE justi.etat = scu.EtatJustificatif.MODIFIE
db.session.add(justi)
# Modification du moduleimpl # Modification du moduleimpl
justi.date_debut = scu.localize_datetime("2023-02-03T11:00:01+01:00") justi.date_debut = scu.localize_datetime("2023-02-03T11:00:01+01:00")
justi.fin = scu.localize_datetime("2023-02-03T12:00:01+01:00") justi.date_fin = scu.localize_datetime("2023-02-03T12:00:01+01:00")
db.session.add(justi) db.session.add(justi)
db.session.commit() db.session.commit()
@ -340,12 +429,15 @@ def editer_supprimer_justificatif(etud: Identite):
assert ( assert (
scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 2 scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 2
), "Edition de justificatif mauvais" ), "Edition de justificatif mauvais"
assert ( assert (
scass.filter_justificatifs_by_date( scass.filter_by_date(
etud.justificatifs, scu.localize_datetime("2023-02-03T11:00:00+01:00") etud.justificatifs,
Justificatif,
date_deb=scu.localize_datetime("2023-02-01T11:00:00+01:00"),
).count() ).count()
== 1 == 1
), "Edition de justificatif mauvais" ), "Edition de justificatif mauvais 2"
# Supression d'une assiduité # Supression d'une assiduité

View File

@ -27,14 +27,13 @@ class _Merger:
class _glob: class _glob:
DUPLICATIONS_ASSIDUITES: dict[tuple[date, bool, int], Assiduite] = {}
DUPLICATIONS_JUSTIFICATIFS: dict[tuple[date, bool, int], Justificatif] = {}
PROBLEMS: dict[int, list[str]] = {} PROBLEMS: dict[int, list[str]] = {}
CURRENT_ETU: list = [] CURRENT_ETU: list = []
MODULES: list[tuple[int, int]] = [] MODULES: list[tuple[int, int]] = []
COMPTE: list[int, int] = [] COMPTE: list[int, int] = []
ERR_ETU: list[int] = [] ERR_ETU: list[int] = []
MERGER: _Merger = None MERGER_ASSI: _Merger = None
MERGER_JUST: _Merger = None
MORNING: time = None MORNING: time = None
NOON: time = None NOON: time = None
@ -42,13 +41,12 @@ class _glob:
class _Merger: class _Merger:
def __init__(self, abs: Absence) -> None: def __init__(self, abs: Absence, est_abs: bool) -> None:
self.deb = (abs.jour, abs.matin) self.deb = (abs.jour, abs.matin)
self.fin = (abs.jour, abs.matin) self.fin = (abs.jour, abs.matin)
self.moduleimpl = abs.moduleimpl_id self.moduleimpl = abs.moduleimpl_id
self.etudid = abs.etudid self.etudid = abs.etudid
self.est_abs = abs.estabs self.est_abs = est_abs
self.est_just = abs.estjust
self.raison = abs.description self.raison = abs.description
def merge(self, abs: Absence) -> bool: def merge(self, abs: Absence) -> bool:
@ -58,18 +56,14 @@ class _Merger:
# Cas d'une même absence enregistrée plusieurs fois # Cas d'une même absence enregistrée plusieurs fois
if self.fin == (abs.jour, abs.matin): if self.fin == (abs.jour, abs.matin):
self.est_abs |= abs.estabs
self.est_just |= abs.estjust
self.moduleimpl = None self.moduleimpl = None
else: else:
if self.est_abs != abs.estabs or self.est_just != abs.estjust:
return False
if self.fin[1]: if self.fin[1]:
if abs.jour != self.fin[0]: if abs.jour != self.fin[0]:
return False return False
else: else:
if abs.jour - timedelta(days=1) != self.fin[0]: day_after: date = abs.jour - timedelta(days=1) == self.fin[0]
if not (day_after and abs.matin):
return False return False
self.fin = (abs.jour, abs.matin) self.fin = (abs.jour, abs.matin)
@ -118,7 +112,7 @@ class _Merger:
if self.est_abs: if self.est_abs:
_glob.COMPTE[0] += 1 _glob.COMPTE[0] += 1
objects.append(self._to_assi()) objects.append(self._to_assi())
if self.est_just: else:
_glob.COMPTE[1] += 1 _glob.COMPTE[1] += 1
objects.append(self._to_justif()) objects.append(self._to_justif())
@ -238,13 +232,12 @@ def migrate_abs_to_assiduites(
Absence.etudid, Absence.jour, not_(Absence.matin) Absence.etudid, Absence.jour, not_(Absence.matin)
) )
_glob.DUPLICATED = []
_glob.DUPLICATIONS_ASSIDUITES = {}
_glob.DUPLICATIONS_JUSTIFICATIFS = {}
_glob.CURRENT_ETU = [] _glob.CURRENT_ETU = []
_glob.MODULES = [] _glob.MODULES = []
_glob.COMPTE = [0, 0] _glob.COMPTE = [0, 0]
_glob.ERR_ETU = [] _glob.ERR_ETU = []
_glob.MERGER_ASSI = None
_glob.MERGER_JUST = None
absences_len: int = absences.count() absences_len: int = absences.count()
@ -255,6 +248,7 @@ def migrate_abs_to_assiduites(
printProgressBar(0, absences_len, "Progression", "effectué", autosize=True) printProgressBar(0, absences_len, "Progression", "effectué", autosize=True)
for i, abs in enumerate(absences): for i, abs in enumerate(absences):
try: try:
_from_abs_to_assiduite_justificatif(abs) _from_abs_to_assiduite_justificatif(abs)
except Exception as e: except Exception as e:
@ -279,11 +273,8 @@ def migrate_abs_to_assiduites(
) )
db.session.commit() db.session.commit()
dup_assi = _glob.DUPLICATED _glob.MERGER_ASSI.export()
assi: Assiduite _glob.MERGER_JUST.export()
for assi in dup_assi:
assi.moduleimpl_id = None
db.session.add(assi)
db.session.commit() db.session.commit()
@ -342,10 +333,22 @@ def _from_abs_to_assiduite_justificatif(_abs: Absence):
if moduleimpl_inscription is None: if moduleimpl_inscription is None:
raise Exception("Moduleimpl_id incorrect ou étudiant non inscrit") raise Exception("Moduleimpl_id incorrect ou étudiant non inscrit")
if _glob.MERGER is None: if _glob.MERGER_ASSI is None:
_glob.MERGER = _Merger(_abs) _glob.MERGER_ASSI = _Merger(_abs, True)
elif _glob.MERGER.merge(_abs): 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):
return True return True
else: else:
_glob.MERGER.export() _glob.MERGER_JUST.export()
_glob.MERGER = _Merger(_abs) _glob.MERGER_JUST = _Merger(_abs, False)
return False