from datetime import date, datetime, time, timedelta import app.scodoc.sco_utils as scu from app.models.assiduites import Assiduite, Justificatif from app.models.etudiants import Identite from app.models.formsemestre import FormSemestre, FormSemestreInscription # TOTALK: Réfléchir sur le fractionnement d'une assiduite prolongée def get_assiduites_stats( assiduites: Assiduite, metric: str = "all", filtered: dict[str, object] = None ) -> Assiduite: if filtered is not None: deb, fin = None, None for key in filtered: if key == "etat": assiduites = filter_assiduites_by_etat(assiduites, filtered[key]) elif key == "date_fin": fin = filtered[key] elif key == "date_debut": deb = filtered[key] elif key == "moduleimpl_id": assiduites = filter_by_module_impl(assiduites, filtered[key]) elif key == "formsemestre": assiduites = filter_by_formsemestre(assiduites, filtered[key]) if (deb, fin) != (None, None): assiduites = filter_by_date(assiduites, Assiduite, deb, fin) count: dict = get_count(assiduites) metrics: list[str] = metric.split(",") output: dict = {} for key, val in count.items(): if key in metrics: output[key] = val 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: """ Filtrage d'une collection d'assiduites en fonction de leur état """ etats: list[str] = list(etat.split(",")) etats = [scu.EtatAssiduite.get(e, -1) for e in etats] return assiduites.filter(Assiduite.etat.in_(etats)) def filter_by_date( collection: Assiduite or Justificatif, collection_cls: Assiduite or Justificatif, date_deb: datetime = None, date_fin: datetime = None, strict: bool = False, ): """ Filtrage d'une collection d'assiduites en fonction d'une date """ 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) if not strict: return collection.filter( collection_cls.date_debut <= date_fin, collection_cls.date_fin >= date_deb ) return collection.filter( collection_cls.date_debut < date_fin, collection_cls.date_fin > date_deb ) def filter_justificatifs_by_etat( justificatifs: Justificatif, etat: str ) -> Justificatif: """ Filtrage d'une collection de justificatifs en fonction de leur état """ etats: list[str] = list(etat.split(",")) etats = [scu.EtatJustificatif.get(e, -1) for e 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( assiduites: Assiduite, module_impl_id: int or None ) -> Assiduite: """ Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl """ return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id) def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemestre): """ Filtrage d'une collection d'assiduites en fonction d'un formsemestre """ if formsemestre is None: return assiduites_query.filter(False) assiduites_query = ( assiduites_query.join(Identite, Assiduite.etudid == Identite.id) .join( FormSemestreInscription, Identite.id == FormSemestreInscription.etudid, ) .filter(FormSemestreInscription.formsemestre_id == formsemestre.id) ) assiduites_query = assiduites_query.filter( Assiduite.date_debut >= formsemestre.date_debut ) return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin) def justifies(justi: Justificatif) -> list[int]: """ 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 et que l'état du justificatif est "validé" """ justified: list[int] = [] if justi.etat != scu.EtatJustificatif.VALIDE: return justified assiduites_query: Assiduite = Assiduite.query.join( Justificatif, Assiduite.etudid == Justificatif.etudid ).filter(Assiduite.etat != scu.EtatAssiduite.PRESENT) assiduites_query = filter_by_date( assiduites_query, Assiduite, justi.date_debut, justi.date_fin ) justified = [assi.id for assi in assiduites_query.all()] return justified