Le temps travaillé, l'écart du jour et le solde de la semaine ne
restituent plus que les heures pointées. Le forfait des jours AS reste
suivi à part : ligne du jour et récapitulatif d'astreinte, libellés et
documentation ajustés en conséquence.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
pointeuse-optimisator
Saisie des horaires de travail avec calcul en temps réel de l'heure de sortie optimale pour finir la semaine à exactement 0h d'heures supplémentaires.
Démarrage rapide
Avant le premier lancement, copie et adapte le fichier de config (voir Configuration plus bas) :
cp data/config.example.json data/config.json
Pour surcharger compose.yml localement (ports, variables d'env, volumes...) sans
modifier le fichier versionné, crée un compose.override.yml — il est chargé
automatiquement par docker compose et déjà ignoré par git.
Principe
La semaine cible est 39h (7h48 / jour). Les directives imposent des plages de présence minimum :
| Demi-journée | Plage minimale |
|---|---|
| Matin | 09:00 → 12:00 |
| Après-midi | 14:00 → 17:00 |
Ces horaires sont réglables par utilisateur depuis la page Réglages de l'app (voir Configuration).
Le matin n'est jamais touché : sa durée nominale est fixe, et si elle ne rentre pas avant la pause déjeuner, le surplus est reporté sur l'après-midi du même jour. Quand un écart hebdomadaire apparaît, il est réparti uniquement sur les heures de départ du soir des demi-journées restantes (répartition égale, recalculée à chaque fois qu'un départ atteint sa limite) :
- un retard repousse les départs — d'abord dans la marge de confort
depart_vise, puis au-delà si besoin (jusqu'à minuit) ; - une avance avance les départs, mais jamais en dessous du minimum de présence de l'après-midi.
Si le rythme normal suffit déjà à rentrer dans les clous, aucune préconisation n'est affichée.
Ce que tu vois dans le tableau
| Colonne | Ce qu'elle dit |
|---|---|
| Travaillé | Heures effectivement saisies ce jour |
| Δ jour | Écart par rapport aux 7h48 dus (affiché dès qu'une heure est enregistrée) |
| Δ sem. | Solde cumulé depuis lundi — visible uniquement quand la journée est complète et sans jour manquant |
| Fin cible | Préconisation de sortie M (matin) et/ou A (après-midi) pour cette demi-journée — vide si le rythme normal suffit |
Les plages qui ne respectent pas les minimums sont signalées en rouge dès la saisie.
Le bandeau au-dessus du tableau ajoute, quand il reste du retard à rattraper, une ligne Reste à faire : le total encore dû, le nombre de demi-journées restantes, et la part supplémentaire moyenne à ajouter sur chacune.
Pages
| Page | Contenu |
|---|---|
Semaine courante (/) |
Le tableau de saisie ci-dessus, pour la semaine en cours ou une autre semaine choisie via le calendrier. |
Statistiques (/stats) |
Conformité aux plages obligatoires, ponctualité moyenne par demi-journée, distribution des horaires, graphique des soldes hebdomadaires. |
Journaux (/logs) |
Historique par utilisateur : changements de réglages, notifications ntfy envoyées, pointages de présence reçus. |
Réglages (/settings) |
Plages horaires personnelles, notifications ntfy, URLs des webhooks de présence — voir Configuration et Présence & notifications. |
Aide (/aide) |
Guide de configuration de ntfy et de l'app de présence (Automation/Tasker). |
| Export | Boutons ⇩ Exporter (semaine, dans l'en-tête) et ⇩ {année} (dans l'historique) : téléchargent un fichier .ods. |
Comptes & connexion
Chaque personne a son propre compte, ses propres pointages, congés, réglages et jetons de
présence — tout est stocké séparément dans data/users/<identifiant>/.
Connexion par email + mot de passe, restreinte au domaine allowed_email_domain (voir
Configuration). Pour un compte qui n'a pas encore de mot de passe, se
connecter avec son adresse envoie un lien (valable 24h) pour en définir un — c'est aussi
le seul moyen d'en obtenir un : il n'y a pas de "mot de passe oublié" en libre-service une
fois un mot de passe défini. La session est un cookie signé (HMAC), sans base de données
de sessions côté serveur.
Lancement
docker compose up -d
Ouvre http://localhost:8000.
Les données (pointages, congés, comptes) sont stockées dans data/ et ignorées par git.
Seul data/config.example.json est versionné.
Configuration
data/config.json (voir data/config.example.json) :
{
"heures_semaine": 39.0,
"jours_par_semaine": 5,
"log_level": "DEBUG",
"allowed_email_domain": "exemple.fr",
"base_url": "https://pointeuse.exemple.fr",
"mail_from": "no-reply@exemple.fr",
"smtp": {
"host": "smtp.exemple.fr",
"port": 587,
"user": "no-reply@exemple.fr",
"password": "change-moi",
"use_tls": true
}
}
heures_semaine— heures hebdomadaires visées (ex:39.0).jours_par_semaine— nombre de jours travaillés dans la semaine (ex:5). Le quota quotidien est calculé :heures_semaine ÷ jours_par_semaine(39h / 5 = 7h48/jour).log_level—DEBUG,INFO,WARNINGouERROR. Contrôle la verbosité des logs (docker logs).allowed_email_domain— seules les adresses de ce domaine peuvent se connecter.base_url— schéma + domaine (ex:https://pointeuse.exemple.fr, sans/final) utilisés pour construire les liens envoyés par email (magic link) et les URLs Tasker/Automation affichées dans Réglages. Si vide, ces URLs sont dérivées de la requête entrante (Host header) quand celui-ci correspond àlocalhostou àallowed_email_domain; sinon l'envoi d'email est refusé par sécurité (anti open-redirect). Toujours renseignerbase_urlen production.mail_from— adresse expéditrice des emails de connexion.smtp— serveur relais utilisé pour l'envoi (host, port, identifiants, TLS).force_secure_cookies(optionnel,falsepar défaut) — force le flagSecuresur le cookie de session même si la requête entrante n'est pas vue comme HTTPS par l'app (utile derrière un reverse proxy qui termine le TLS).
Les rappels ntfy (topic, serveur, plage horaire) et les plages horaires (bornes de
présence obligatoire matin/après-midi, voir Principe, et heure de reprise
par défaut de l'après-midi) se règlent par utilisateur depuis la page Réglages
de l'app, pas dans config.json. Modifier ses plages change, pour ce compte uniquement,
les avertissements de saisie, les stats de conformité et les préconisations de rattrapage.
Présence & notifications
La page Réglages fournit, par utilisateur, deux URLs à jeton unique
(/presence/{token}/arrivee et /presence/{token}/depart) — sans autre authentification,
la sécurité vient de l'imprévisibilité du jeton. Une app comme Automation (alternative
libre à Tasker) les appelle depuis le téléphone en entrant/sortant d'une zone (wifi,
géofence...) pour signaler l'arrivée ou le départ.
Une fois configuré, l'app peut envoyer un rappel ntfy si quelqu'un est détecté présent sans avoir pointé son arrivée, ou parti sans avoir pointé son départ, uniquement pendant les jours/heures de travail. Guide complet sur la page Aide.
Tests
docker compose run --rm pointeuse pytest -v
Congés & jours fériés
Quatre boutons par ligne : MA (congé matin), AM (congé après-midi), J (journée entière) et F (jour férié). Les trois premiers réduisent les heures dues et recalculent les préconisations Fin cible pour les demi-journées restantes. F marque un jour férié (fête locale non calculée, pont...) : heures dues nulles, aucune présence obligatoire, ligne grisée avec badge « férié » — sans consommer de congé. Un jour marqué F compte aussi comme férié pour les ratios d'astreinte (×2 en journée par défaut) et pour la fenêtre d'astreinte ci-dessous.
Astreinte & repos de 11h
Le bouton AS marque un jour d'astreinte : la période couverte va de 18:00 ce jour-là à 18:00 le lendemain (deux astreintes consécutives se recollent sans trou) — sauf samedi, dimanche et jour férié, qui comptent de 0h00 à 0h00 (journée entière ; un jour AS déclaré sam/dim/férié garde sa borne du lendemain 18:00 pour ne pas tronquer les interventions nocturnes). Ce jour et son lendemain, une sous-ligne dédiée apparaît sous la ligne du jour, pleine largeur : un menu dépliant fermé par défaut (seuls l'heure de retour et le total restent visibles). Déplié, il offre autant de lignes que nécessaire (+ intervention) sur trois colonnes : début, fin et description libre. Une fin antérieure au début (fin < début) désigne une intervention à cheval sur minuit. Une durée forfaitaire peut être attribuée à chaque jour d'astreinte déclaré (page Réglages, « Forfait par jour d'astreinte ») : elle ne compte jamais dans les heures travaillées, elle est suivie à part dans le récapitulatif d'astreinte.
La semaine affiche aussi samedi et dimanche (heures dues nulles, lignes
grisées) : on peut y déclarer des astreintes, y saisir les interventions et les
pointer si besoin — les exports .ods suivent.
L'app en déduit l'heure de retour au bureau : fin du dernier événement + 11h de repos consécutif (exigées par période de 24h), affichée sous la liste (« retour ≥ 13:00 »). Tout quart d'heure entamé est travaillé : la fin est arrondie au quart d'heure supérieur (fin 23:37 → comptée 23:45) ; les interventions hors fenêtre 18:00→18:00 ne sont pas de l'astreinte. Fin 23:30 → retour 10:30 le lendemain ; fin 02:00 → retour 13:00 le jour même. Un repos qui se termine avant l'ouverture ne contraint rien.
À partir de cette heure de retour, tout ce qui la précède sur la journée quitte le décompte de la semaine :
- la présence obligatoire avant l'heure de retour est retirée des heures dues (ex: retour 13:00 → la plage 09:00-12:00 ne compte plus) ;
- une présence pointée avant l'heure de retour ne compte pas dans le travaillé ;
- les préconisations Fin cible démarrent à l'heure de retour (arrivée du matin décalée d'autant, sans avertissement de retard) ;
- une demi-journée entièrement couverte par le repos est considérée comme remplie.
Les heures d'astreinte ne comptent jamais dans les heures de semaine : elles sont décomptées à part, par quarts d'heure de l'horloge (:00, :15, :30, :45) — toute tranche entamée compte en totalité, et un quart partagé par deux interventions qui se suivent ou se chevauchent n'est compté qu'une fois (attribué à la première, les lignes d'événements somment au total du jour) — dans des compteurs pondérés selon le jour et l'heure. Sous le bandeau de la semaine s'affiche le détail par événement, les totaux par compteur et le total pondéré.
Par défaut : Nuit (20:00→08:00, tous types de jours) ×1,5 ; Journée (semaine Lun-Ven, 08:00→20:00) ×1 ; Samedi (08:00→20:00) ×1,5 ; Dimanche / férié (08:00→20:00) ×2. Les jours fériés français (fixes + Pâques, Ascension, Pentecôte) sont calculés automatiquement, et s'ajoutent les jours marqués F.
Ces règles sont éditables à la main sur la page Réglages (section « Ratios
des heures d'astreinte ») : nom, types de jours, fenêtre horaire et ratio de chaque
règle, ajout et suppression de lignes. La première règle qui couvre un quart
gagne — place les fenêtres spécifiques avant les générales. Elles restent aussi
ajustables dans data/config.json, clé astreinte_facteurs : chaque règle porte
un nom, les jours concernés (semaine, sam, dim, ferie), un debut,
une fin et un facteur. Une tranche qui ne correspond à aucune règle est rangée
dans « Autre » avec un facteur ×1.
Les événements ne s'appliquent que les jours qualifiés (astreinte ou lendemain) :
partout ailleurs ils sont ignorés. Les colonnes Astreinte (brut), Astreinte
pondérée et Retour (calculé) apparaissent dans les exports .ods.
Stack
FastAPI · HTMX · Jinja2 · fichiers JSON · Docker
