"""Utilitaires de gestion des identifiants uniques (UID) des événements. Ce module fournit la normalisation des UIDs Pronote (suppression des suffixes temporels) et la génération d'UIDs déterministes par hachage des champs clés d'un événement, garantissant l'idempotence de la synchronisation. """ from __future__ import annotations import hashlib import re from datetime import datetime from zoneinfo import ZoneInfo _TEMPORAL_SUFFIX_PATTERN = re.compile(r"-\d{8}T\d{6}Z-Index-Education$") _EDUCATION_SUFFIX_PATTERN = re.compile(r"-Index-Education$") def normalize_pronote_uid(uid: str) -> str: """Normalise un UID Pronote en supprimant ses suffixes temporels. Les suffixes de type ``-AAAAMMJJTHHMMSSZ-Index-Education`` puis ``-Index-Education`` sont retirés. La fonction est idempotente : appliquée à un UID déjà normalisé, elle retourne la même valeur. :param uid: UID brut provenant de Pronote (ex: ``L-1234-20250901T080000Z-Index-Education``). :return: UID normalisé, sans suffixe temporel ni marque ``Index-Education``. :rtype: str """ normalized = _TEMPORAL_SUFFIX_PATTERN.sub("", uid) return _EDUCATION_SUFFIX_PATTERN.sub("", normalized) def normalize_datetime_to_utc(dt: datetime) -> datetime: """Normalise une datetime vers UTC pour les signatures et hachages. Les datetimes naïves sont interprétées comme Europe/Paris puis converties vers UTC. Les datetimes conscientes sont converties vers UTC. :param dt: Datetime à normaliser (naïve ou consciente). :return: Datetime en UTC. :rtype: datetime """ if dt.tzinfo is None: return dt.replace(tzinfo=ZoneInfo("Europe/Paris")).astimezone(ZoneInfo("UTC")) return dt.astimezone(ZoneInfo("UTC")) def generate_deterministic_uid( start: datetime, end: datetime, subject: str, teachers: list[str], rooms: list[str], group: str | None = None, ) -> str: """Génère un UID déterministe par hachage des champs clés d'un événement. Utilisé lorsqu'aucun UID exploitable n'est disponible : deux appels avec des champs identiques produisent le même identifiant, ce qui garantit l'idempotence de la synchronisation. :param start: Début de l'événement. :param end: Fin de l'événement. :param subject: Intitulé de la matière. :param teachers: Liste des enseignants (triée avant hachage). :param rooms: Liste des salles (triée avant hachage). :param group: Groupe éventuel ; traité comme chaîne vide si absent. :return: Identifiant déterministe : 12 premiers caractères hexadécimaux du SHA-1 des champs clés joints par ``|``. :rtype: str """ parts = [ normalize_datetime_to_utc(start).isoformat(), normalize_datetime_to_utc(end).isoformat(), subject, ",".join(sorted(teachers)), ",".join(sorted(rooms)), group or "", ] payload = "|".join(parts).encode("utf-8") return hashlib.sha1(payload, usedforsecurity=False).hexdigest()[:12]