Automatiser les feuilles de temps mensuelles depuis Toggl avec Claude Code
4 mar 2026
Auteur : Nicolas Rouanne
Date : 4 mars 2026
Je voulais générer une feuille de temps mensuelle depuis Toggl — une table montrant, jour par jour, combien de demi-journées j'ai travaillé pour chaque client. Simple en apparence. La partie délicate s'est révélée être l'arrondi.
L'objectif
Le rendu voulu : un tableau où chaque ligne est un jour ouvré, chaque colonne est un client, et chaque cellule est une valeur en multiples de 0.5 jour. Une journée de travail vaut 5h30. Donc 4h chez un client et 2h chez un autre devrait donner 0.5 + 0.5 = 1.0 jour, pas 1.0 + 0.5 = 1.5 jours.
Les données viennent de l'API Toggl. Claude Code s'occupe de la récupération, de la correspondance entre les IDs de projets et les clients, et de l'exécution du script de calcul.
Première tentative : report par client
L'approche naïve — arrondir les heures de chaque client indépendamment au 0.5 le plus proche — fait perdre des données. Un client avec 1h10m de suivi arrondit à 0 ce jour-là, et on ne récupère jamais ce temps.
La première correction essayée était un report par client : plutôt que d'arrondir chaque jour indépendamment, arrondir le total cumulé par client. La fraction restante se reporte au jour suivant.
def round_half(x):
return math.floor(x * 2 + 0.5) / 2
for day in all_days:
for client in all_clients:
client_running[c] += raw_today[c]
new_total = round_half(client_running[c])
day_alloc[c] = new_total - client_assigned[c]
client_assigned[c] = new_totalÇa fonctionne bien pour les totaux mensuels. Mais il y a un bug subtil quand plusieurs clients partagent une journée.
Le problème : les totaux journaliers s'enflent
Le 5 février, j'ai suivi 7h42 réparties sur trois clients : Episto (4h34), SAMM (2h21) et Qraft (44m). Soit 1.40 vraie journée de travail.
Avec le report par client, chacun avait accumulé une dette des jours précédents, et tous les trois ont reçu un arrondi à la hausse le même jour :
7h42 qui s'affiche comme 2 journées de travail, c'est faux. Les reports par client sont indépendants — ils ne se connaissent pas.
La correction : report à deux niveaux
La solution consiste à faire tourner deux reports en parallèle.
1. Report au niveau journée — détermine combien de créneaux de 0.5 la journée mérite au total, en fonction des vraies heures travaillées :
day_running += total_day_secs / DAY_SECS
day_value = round_half(day_running) - day_assigned # ex. 1.5 pour 7h42
slots = int(day_value * 2) # ex. 3 créneaux2. Suivi de la dette client — décide quels clients reçoivent les créneaux. Les clients avec le plus d'heures non compensées (total brut accumulé moins heures déjà créditées) ont la priorité :
client_running[c] += raw_today[c] # jamais arrondi
for _ in range(slots):
best = max(active_clients, key=lambda c: client_running[c] - client_assigned[c])
day_alloc[best] += 0.5
client_assigned[best] += 0.5Pour le 5 février avec 3 créneaux :
Ce qui fonctionne
Les totaux journaliers sont maintenant bornés par les vraies heures travaillées. Une journée à 7h42 produit au maximum 1.5 jour. Les totaux mensuels convergent correctement — le suivi de la dette garantit qu'aucune heure n'est définitivement perdue, elles se décalent simplement au prochain jour où ce client a la dette la plus élevée.
Le compromis
Les deux reports ne peuvent pas être parfaits simultanément. Si trois clients partagent une journée mais que celle-ci ne vaut que 1.0 créneau, un client ne reçoit rien ce jour-là même s'il a une dette significative. Ses heures se reportent au prochain jour où il apparaît.
En pratique sur un mois complet, l'erreur par client reste dans une plage de ±0.5 jour. C'est acceptable pour la facturation — c'est la même précision que des estimations manuelles en demi-journées.
Ce qu'on en retire
Le script est un fichier Python autonome invoqué par une compétence Claude Code :
python3 ~/dev/claude/scripts/toggl_calendar.py 2026-01
python3 ~/dev/claude/scripts/toggl_calendar.py 2026-01 2026-02La compétence gère aussi la classification des entrées sans tag, le plafonnement des timers lancés la nuit et la mise à jour en masse des projets via l'API Toggl — des choses que je faisais auparavant manuellement dans l'interface web de Toggl.
L'algorithme d'arrondi en lui-même fait une vingtaine de lignes. La partie intéressante, c'est de réaliser que l'arrondi est un problème à 2 dimensions quand on a plusieurs clients par jour, et non à 1 dimension.