Co-authored-by: OpenAI/GPT-5.6-Terra <vibecoder@antoineve.me> Co-authored-by: DeepSeek/DeepSeek-v4-Pro <vibecoder@antoineve.me> Co-authored-by: DeepSeek/DeepSeek-v4-Flash <vibecoder@antoineve.me>
110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
import statistics as _stats
|
|
|
|
|
|
def minutes_to_str(minutes: int) -> str:
|
|
"""
|
|
Convertit une durée en minutes en une chaîne formatée lisible (ex: "7h45" ou "-1h15").
|
|
|
|
Paramètres :
|
|
minutes (int) : Le nombre de minutes à convertir (peut être négatif).
|
|
|
|
Retour :
|
|
str : La chaîne formatée au format "[signe]HhMM".
|
|
"""
|
|
sign = "-" if minutes < 0 else ""
|
|
minutes = abs(minutes)
|
|
return f"{sign}{minutes // 60}h{minutes % 60:02d}"
|
|
|
|
|
|
_REFERENCE_MINUTES = {
|
|
"WORK": 465,
|
|
"TT": 465,
|
|
"FORMATION": 465,
|
|
"GARDE": 600,
|
|
"ASTREINTE": 0,
|
|
"RTT": 0,
|
|
"CONGE": 0,
|
|
"MALADE": 0,
|
|
"FERIE": 0,
|
|
}
|
|
|
|
|
|
def work_minutes_reference(day_type: str) -> int:
|
|
"""
|
|
Retourne la durée de travail de référence en minutes pour un type de journée donné.
|
|
|
|
Les durées de référence sont :
|
|
- WORK, TT, FORMATION : 7h45 (465 minutes)
|
|
- GARDE : 10h00 (600 minutes)
|
|
- ASTREINTE, RTT, CONGE, MALADE, FERIE : 0 minute
|
|
|
|
Paramètres :
|
|
day_type (str) : Le type de journée (ex: "WORK", "TT", "CONGE").
|
|
|
|
Retour :
|
|
int : La durée de référence en minutes. Par défaut 465 minutes si le type est inconnu.
|
|
"""
|
|
return _REFERENCE_MINUTES.get(day_type, 465)
|
|
|
|
|
|
def week_balance_minutes(actual_minutes: int, reference_minutes: int) -> int:
|
|
"""
|
|
Calcule l'écart (solde) entre les minutes réellement travaillées et les minutes de référence.
|
|
|
|
Paramètres :
|
|
actual_minutes (int) : Le total des minutes travaillées.
|
|
reference_minutes (int) : Le total des minutes de référence.
|
|
|
|
Retour :
|
|
int : L'écart en minutes (positif si heures supplémentaires, négatif si déficit).
|
|
"""
|
|
return actual_minutes - reference_minutes
|
|
|
|
|
|
def monthly_stats(entries: list) -> dict:
|
|
"""
|
|
Calcule la médiane journalière et la médiane hebdomadaire (semaines ISO) pour un groupe d'entrées.
|
|
|
|
Les absences (durée de travail de 0 minute) sont incluses dans les calculs.
|
|
Les semaines sont regroupées selon le calendrier ISO (année, numéro de semaine).
|
|
|
|
Paramètres :
|
|
entries (list) : Une liste d'objets `WorkEntry`.
|
|
|
|
Retour :
|
|
dict : Un dictionnaire contenant :
|
|
- "median_daily_min" (int) : La médiane des minutes travaillées par jour.
|
|
- "median_weekly_min" (int) : La médiane des minutes travaillées par semaine ISO.
|
|
"""
|
|
if not entries:
|
|
return {"median_daily_min": 0, "median_weekly_min": 0}
|
|
|
|
daily = [e.total_minutes() for e in entries]
|
|
median_daily = int(_stats.median(daily))
|
|
|
|
weekly: dict[tuple, int] = {}
|
|
for e in entries:
|
|
key = e.date.isocalendar()[:2] # (year, isoweek)
|
|
weekly[key] = weekly.get(key, 0) + e.total_minutes()
|
|
|
|
median_weekly = int(_stats.median(weekly.values()))
|
|
|
|
return {"median_daily_min": median_daily, "median_weekly_min": median_weekly}
|
|
|
|
|
|
def count_day_types(entries: list) -> dict[str, int]:
|
|
"""
|
|
Comptabilise le nombre d'occurrences de chaque type de journée dans une liste d'entrées.
|
|
|
|
Paramètres :
|
|
entries (list) : Une liste d'objets `WorkEntry`.
|
|
|
|
Retour :
|
|
dict[str, int] : Un dictionnaire associant chaque type de journée présent à son nombre d'occurrences.
|
|
Les types de journées non représentés ne figurent pas dans le dictionnaire.
|
|
"""
|
|
counts: dict[str, int] = {}
|
|
for entry in entries:
|
|
counts[entry.day_type] = counts.get(entry.day_type, 0) + 1
|
|
return counts
|