Astreinte : durée réelle arrondie au quart d'heure supérieur
Le décompte ne compte plus les quarts d'horloge (:00, :15, :30, :45)
entamés mais la durée réelle de chaque intervention arrondie au quart
d'heure supérieur (2, 5 ou 12 min → 15 ; 22 ou 28 → 30), même si
l'arrondi ne tombe pas juste sur l'horloge. Les minutes communes à deux
interventions restent comptées une seule fois, le repos de 11h s'appuie
sur la durée arrondie, et l'interface/README expliquent la nouvelle
méthode.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
"""Astreinte : événements, décompte par tranches de 15 min, repos de 11h et retour décalé."""
|
||||
"""Astreinte : événements, durée réelle arrondie au quart d'heure supérieur, repos de 11h et retour décalé."""
|
||||
from calcul import (
|
||||
compute_week, decompte_astreinte, heures_dues, heures_travaillees, hhmm_to_minutes,
|
||||
jour_astreinte_qualifie, exclusion_retour, nominaux_du_jour, fenetre_astreinte_jour,
|
||||
@ -117,7 +117,7 @@ def test_retour_fin_du_soir_sur_le_jour_d_astreinte():
|
||||
|
||||
|
||||
def test_retour_fin_hors_quart_arrondie_au_quart_entame():
|
||||
"""Event 21:00→23:37 : le quart d'heure entamé est travaillé, fin comptée
|
||||
"""Event 21:00→23:37 : durée réelle 157 min arrondie à 165, fin comptée
|
||||
23:45 → repos terminé 10:45 le lendemain."""
|
||||
p_by_date = {"2026-07-14": _pointage("2026-07-14",
|
||||
astreinte_events=[{"debut": "21:00", "fin": "23:37"}])}
|
||||
@ -125,7 +125,8 @@ def test_retour_fin_hors_quart_arrondie_au_quart_entame():
|
||||
|
||||
|
||||
def test_retour_fin_nocturne_hors_quart_arrondie():
|
||||
"""Event 23:00→01:07 (à cheval sur minuit) : fin comptée 01:15 → retour 12:15."""
|
||||
"""Event 23:00→01:07 (à cheval sur minuit) : durée 127 min arrondie à 135,
|
||||
fin comptée 01:15 → retour 12:15."""
|
||||
p_by_date = {"2026-07-14": _pointage("2026-07-14",
|
||||
astreinte_events=[{"debut": "23:00", "fin": "01:07"}])}
|
||||
assert retour_apres_repos("2026-07-15", p_by_date, PLAGES, ["2026-07-14"]) == 12 * 60 + 15
|
||||
@ -169,25 +170,36 @@ def test_retour_fin_tot_ne_contraint_rien():
|
||||
assert retour_apres_repos("2026-07-15", p_by_date, PLAGES, ["2026-07-14"]) is None
|
||||
|
||||
|
||||
# ── Décompte astreinte : tranches de 15 min entamées, par règle ───────────────
|
||||
# ── Décompte astreinte : durée réelle arrondie au quart supérieur, par règle ───
|
||||
|
||||
def test_decompte_tranche_entamee_comptee_entiere():
|
||||
"""22:07→22:20 touche les tranches alignées 22:00 et 22:15 : comptées entières."""
|
||||
def test_decompte_duree_reelle_arrondie_au_quart_superieur():
|
||||
"""22:07→22:20 = 13 min réelles → arrondies à 15 min (et non aux tranches
|
||||
d'horloge 22:00 et 22:15 qui auraient donné 30)."""
|
||||
d = decompte_astreinte("2026-07-13", [{"debut": "22:07", "fin": "22:20"}])
|
||||
assert d["tranches"] == 2 # tranches 22:00 + 22:15 entamées
|
||||
assert d["total_min"] == 2 * PAS_ASTREINTE_MIN # 30
|
||||
assert d["total_h"] == "0h30"
|
||||
assert d["tranches"] == 1 # 15 min = 1 quart d'heure
|
||||
assert d["total_min"] == PAS_ASTREINTE_MIN
|
||||
assert d["total_h"] == "0h15"
|
||||
assert d["categories"][0]["nom"] == "Nuit" # lundi 22:xx → Nuit ×1,5
|
||||
assert d["total_pondere_min"] == 45 # 30 × 1,5
|
||||
assert d["total_pondere_min"] == 23 # 15 × 1,5
|
||||
|
||||
|
||||
def test_decompte_arrondi_examples_minutes_reelles():
|
||||
"""2, 5 ou 12 minutes réelles → 15 min ; 22 ou 28 → 30 min — même si
|
||||
l'arrondi ne tombe pas juste sur un quart d'heure de l'horloge."""
|
||||
for deb, fin, attendu in [("22:00", "22:02", 15), ("22:00", "22:05", 15),
|
||||
("22:00", "22:12", 15), ("22:00", "22:22", 30),
|
||||
("22:00", "22:28", 30), ("22:39", "23:49", 75)]:
|
||||
d = decompte_astreinte("2026-07-13", [{"debut": deb, "fin": fin}])
|
||||
assert d["total_min"] == attendu, f"{deb} → {fin}"
|
||||
|
||||
|
||||
def test_decompte_arrondi_des_bornes():
|
||||
"""09:50→10:05 touche les tranches 09:45 et 10:00 : 2 tranches en Journée ×1."""
|
||||
"""09:50→10:05 = 15 min réelles → 15 min, en Journée ×1."""
|
||||
d = decompte_astreinte("2026-07-13", [{"debut": "09:50", "fin": "10:05"}])
|
||||
assert d["tranches"] == 2 # tranches 09:45 + 10:00
|
||||
assert d["total_min"] == 30
|
||||
assert d["tranches"] == 1
|
||||
assert d["total_min"] == 15
|
||||
assert d["categories"][0]["nom"] == "Journée" # lundi en journée ×1
|
||||
assert d["total_pondere_min"] == 30
|
||||
assert d["total_pondere_min"] == 15
|
||||
|
||||
|
||||
def test_decompte_evenement_a_cheval_sur_minuit():
|
||||
@ -218,36 +230,33 @@ def test_decompte_vendredi_vers_samedi_reste_en_nuit():
|
||||
|
||||
|
||||
def test_decompte_chevauchement_dedouble():
|
||||
"""10:00→10:20 et 10:10→10:30 se recouvrent : les tranches communes ne comptent
|
||||
qu'une fois, attribuées au premier événement ; les lignes somment au total."""
|
||||
"""10:00→10:20 et 10:10→10:30 se recouvrent : les minutes réelles communes
|
||||
ne comptent qu'une fois (attribuées au premier événement) ; chaque
|
||||
intervention est arrondie sur ses seules minutes non déjà comptées."""
|
||||
d = decompte_astreinte("2026-07-13", [
|
||||
{"debut": "10:00", "fin": "10:20"},
|
||||
{"debut": "10:10", "fin": "10:30"},
|
||||
])
|
||||
assert len(d["events"]) == 2
|
||||
assert d["events"][0]["tranches"] == 2 # tranches 10:00 + 10:15
|
||||
assert d["events"][0]["min"] == 30
|
||||
assert d["events"][1]["tranches"] == 0 # les deux tranches déjà comptées
|
||||
assert d["events"][1]["min"] == 0
|
||||
assert d["tranches"] == 2 # union dédoublonnée
|
||||
assert d["total_min"] == 30
|
||||
assert d["events"][0]["min"] == 30 # 20 min réelles → arrondies à 30
|
||||
assert d["events"][1]["min"] == 15 # 10 min nouvelles → arrondies à 15
|
||||
assert d["total_min"] == 45
|
||||
assert sum(e["min"] for e in d["events"]) == d["total_min"]
|
||||
assert d["categories"][0]["nom"] == "Journée"
|
||||
|
||||
|
||||
def test_decompte_quart_partage_entre_interventions():
|
||||
"""Deux interventions qui se suivent et finissent/débutent dans le même quart :
|
||||
le quart n'est compté qu'une fois (attribué à la première), les lignes
|
||||
d'événements somment au total du jour."""
|
||||
def test_decompte_minutes_partagees_entre_interventions():
|
||||
"""Deux interventions qui se chevauchent : les minutes réelles communes ne
|
||||
sont comptées qu'une fois (attribuées à la première) ; chaque ligne est
|
||||
arrondie au quart supérieur et les événements somment au total du jour."""
|
||||
d = decompte_astreinte("2026-07-13", [
|
||||
{"debut": "22:39", "fin": "23:05"},
|
||||
{"debut": "23:02", "fin": "23:49"},
|
||||
])
|
||||
assert d["events"][0]["tranches"] == 3 # 22:30 + 22:45 + 23:00
|
||||
assert d["events"][1]["tranches"] == 3 # 23:15 + 23:30 + 23:45 (23:00 déjà compté)
|
||||
assert sum(e["tranches"] for e in d["events"]) == d["tranches"] == 6
|
||||
assert d["total_min"] == 90
|
||||
assert d["total_pondere_min"] == 135 # 6 × 15 × 1,5 (Nuit)
|
||||
assert d["events"][0]["min"] == 30 # 26 min réelles → 30
|
||||
assert d["events"][1]["min"] == 45 # 44 min nouvelles → 45 (3 min communes)
|
||||
assert d["total_min"] == 75
|
||||
assert d["total_pondere_min"] == 113 # 75 × 1,5 (Nuit)
|
||||
|
||||
|
||||
def test_decompte_event_incomplet_ignore():
|
||||
|
||||
Reference in New Issue
Block a user