Planificateur d'équipe pour congés et couverture des vacances
De Wikiprompt, l’encyclopédie libre de prompts
Planificateur d'équipe pour congés et couverture des vacances Une compétence IA complète pour planifier les congés d'équipe en fonction des effectifs minimaux, avec des règles de couverture, une priorité équitable, des suggestions alternatives et un script de planification en Python.
Contenu du PromptEnregistrer
🌐
---
name: team-leave-and-holiday-coverage-planner
description: Planifie les congés d'équipe en respectant les effectifs minimums, surtout pendant les fêtes - vérifie chaque jour ouvré par rapport aux règles de couverture par rôle (une personne remplit un créneau), décide quelles demandes peuvent être approuvées selon un ordre de priorité équitable, propose des dates alternatives sans trou de couverture, respecte les périodes d'interdiction, les jours à temps partiel et les demi-journées, et suit les soldes de congés, les reports et les pics de jours de congé. Inclut un planificateur Python stdlib hors ligne avec --self-test.
---
# Planificateur de congés d'équipe et de couverture pendant les fêtes
Vous aidez les responsables d'équipe, les managers support et opérations, les gérants de clinique et de magasin à répondre à la question de décembre : qui peut être absent, quand, sans laisser le poste vide, et est-ce équitable ? Les feuilles de calcul des congés montrent qui est absent mais pas si les personnes restantes peuvent réellement couvrir les rôles (un lead qui est aussi le seul ingénieur niveau 2 ne peut pas être les deux le même jour). Vous transformez l'équipe, les règles de couverture et les demandes de congé en un contrôle jour par jour, décidez les demandes dans un ordre de priorité annoncé, proposez des alternatives au lieu d'un simple refus, et signalez les problèmes de solde et d'équité avant qu'ils ne deviennent des conversations RH.
## Fichiers dans cette compétence
- `scripts/leave_cover.py` - lit un JSON d'équipe (personnes, rôles, jours à temps partiel, soldes, minimums de couverture, jours fériés, dérogations, jours d'interdiction et jours de pointe) et un CSV de congés ; vérifie chaque jour ouvré avec une correspondance une-personne-un-créneau, trouve les trous causés par les congés approuvés, décide des demandes ouvertes (ceux qui ont travaillé le pic l'année dernière d'abord, puis premier arrivé premier servi), suggère les dates sans trou les plus proches ou un voyage plus court, et rapporte une grille calendaire, un tableau des soldes et des constats LV01-LV07 (bibliothèque standard Python 3.8+ uniquement)
- `references/coverage-rules.md` - comment définir les minimums par rôle, les dérogations pour les jours calmes, les temps partiels, les demi-journées, l'astreinte et ce que fait la correspondance
- `references/fair-leave-policy.md` - règles de priorité, périodes d'interdiction, solde et report, comment bien refuser, et ce que signifie chaque code de constat
- `templates/leave-plan-brief.md` - le plan à envoyer à l'équipe
- `examples/support-team.json` - une équipe de support client de 7 personnes pour Noël et le Nouvel An : leads, niveau 2, astreinte, un temps partiel, jours fériés, couverture allégée les 24 et 31 décembre, une interdiction pour la clôture de facturation et des limites de report
- `examples/support-leave.csv` - 3 congés approuvés et 7 demandés, y compris une demi-journée
- `examples/support-plan.md` - la sortie réelle du planificateur sur ces fichiers
## Flux de travail
### 1. Collecter les entrées
Demandez : la période (couvrez toute la période des fêtes plus une semaine de chaque côté), l'équipe avec les rôles et les jours de travail de chaque personne, les soldes et les jours déjà pris cette année de congés, les jours fériés où l'entreprise est fermée (ou la couverture réduite si elle est ouverte), la couverture minimale par rôle et au total, les jours plus calmes avec des minimums plus bas, les périodes d'interdiction (lancements, inventaires, clôtures de facturation, audits) et les jours de pointe que les gens se disputent. Ensuite les congés : approuvés, demandés (avec date de soumission), formations, congés parentaux ou médicaux connus. Demandez ce que l'équipe a convenu concernant la priorité ; si rien, suggérez "ceux qui ont travaillé le pic l'année dernière d'abord, puis premier arrivé premier servi".
### 2. Exécuter le planificateur
```
python3 scripts/leave_cover.py examples/support-team.json examples/support-leave.csv
python3 scripts/leave_cover.py team.json leave.csv --json
python3 scripts/leave_cover.py --self-test
```
Code de sortie 0 = les congés approuvés maintiennent la couverture et personne ne dépasse son solde, 1 = les congés approuvés cassent déjà la couverture ou dépassent un solde, 2 = mauvaise entrée (le message nomme la ligne). Sans shell, construisez le calendrier à la main : une ligne par personne, une colonne par jour ouvré, puis pour chaque jour assignez les personnes qui travaillent aux créneaux de rôle une personne par créneau et marquez les manques.
### 3. Lire le résultat
- Les trous dus aux congés déjà approuvés (LV01) viennent en premier : ils nécessitent une correction maintenant (couverture temporaire, un échange, une agence ou un arrangement d'astreinte), pas une nouvelle règle.
- Problèmes de solde (LV02) : les congés approuvés au-delà du solde nécessitent que les RH confirment un report ou des jours sans solde.
- Demandes : approuvez celles marquées approuver ; pour les conflits et les interdictions, proposez l'alternative à la personne, pas un refus (LV04).
- Équité (LV05) et jours de pointe : vérifiez que personne n'obtient les meilleurs jours deux années de suite pendant que quelqu'un qui les a travaillés est refusé.
- Fragilité : les rôles sans remplaçant (LV03) et les jours avec zéro marge (LV07) nécessitent un remplaçant nommé.
- À utiliser ou à perdre (LV06) : les personnes qui perdront des jours devraient les réserver pendant les semaines calmes.
### 4. Ajuster et relancer
Essayez les corrections évidentes avec les mêmes fichiers : un remplaçant polyvalent (ajoutez un rôle à quelqu'un), un minimum plus bas un jour calme (dérogation), un échange entre deux personnes, ou la division d'un long voyage. Relancez jusqu'à ce que les congés approuvés n'aient aucun trou et que chaque demande ouverte ait une décision ou une alternative concrète.
### 5. Livrer
Remplissez `templates/leave-plan-brief.md` : la règle utilisée, la décision par demande avec la raison et l'alternative, le calendrier, les arrangements de couverture pour les jours maigres et les notes sur les soldes. Parlez aux personnes dont les demandes ne sont pas approuvées avant de publier le plan.
## Exemple de résultat
Sur l'exemple de l'équipe support, le voyage approuvé d'Omar le dépasse déjà de 3 jours sur son solde de 25 jours (LV02, élevé : les RH doivent confirmer un report ou des jours sans solde). Sam a travaillé le dernier Noël, donc sa demande du 24 déc au 4 janv est décidée en premier et approuvée ; Jonas (21-23 déc) et la demi-journée de Priya et ses 21-23 déc sont aussi approuvés. Les 29-31 déc d'Ade laisseraient le niveau 2 et la couverture totale en manque les 29 et 30 déc ; le planificateur propose les 16-18 déc à la place. Les 30 déc-8 janv de Lena traversent l'interdiction de clôture de facturation des 5-6 janv, et seul le 31 déc peut être approuvé sans trou. Le deuxième voyage de Jonas nécessite 5 jours avec 4 restants. Lena perdrait 10 jours à cause de la limite de report de 5 jours, et les 21-23 et 29 déc n'ont aucune marge du tout.
## Règles
- Vérifiez la couverture par rôle avec une personne par créneau, pas seulement par effectif.
- Appliquez une règle de priorité annoncée à tout le monde et montrez l'ordre ; ne décidez jamais selon qui a crié le plus fort.
- Associez toujours un "pas cette fois" à une date alternative, un voyage plus court ou un échange.
- Traitez les congés maladie, parentaux et autres congés protégés comme fixes : ne demandez jamais aux gens de les déplacer, et gardez les détails médicaux hors du plan.
- Les soldes, reports et congés sans solde suivent le contrat et la loi locale ; signalez-les aux RH plutôt que de décider.
- Partagez le plan uniquement avec l'équipe et les managers qui en ont besoin.
## Règles de couverture
### Minimums
- Définissez un minimum par **rôle** (ce qui doit être couvert : lead/approbateur, niveau 2, astreinte, pharmacien, détenteur de clés, permis chariot élévateur) et un total **quelconque** (assez de mains pour le volume).
- Basez les chiffres sur la demande, pas sur l'habitude : tickets/appels/commandes par jour divisé par ce qu'une personne gère, plus les rôles qui doivent être présents par loi ou contrat.
- Les jours calmes (24 et 31 décembre, la semaine entre les fêtes dans beaucoup d'équipes B2B) justifient généralement un minimum plus bas : utilisez `coverage_overrides` pour ces dates plutôt que de baisser toute la période.
- Les jours où l'entreprise est fermée vont dans `holidays` : pas de couverture nécessaire, pas de solde débité.
### Une personne, un créneau
Une personne qui a plusieurs rôles ne remplit toujours qu'un seul créneau par jour. Le planificateur assigne les personnes aux créneaux avec une correspondance bipartite, donc il trouve l'arrangement qui fonctionne s'il en existe un :
- Maya (lead, niveau 2) + Lena (niveau 1, niveau 2) peuvent couvrir lead 1 + niveau 2 1, mais pas lead 1 + niveau 2 2.
- Les manques sont rapportés par rôle ; "quelconque" en manque signifie trop peu de personnes au total.
### Personnes
- `days` : jours de semaine de travail des temps partiels ; les congés sur leurs jours non travaillés ne coûtent pas de solde et n'affectent pas la couverture.
- Demi-journées (`half_day=yes`) coûtent 0,5 jour de solde ; le planificateur ne compte pas la personne pour la couverture ce jour-là (conservateur). Si une absence le matin seulement couvre toujours le service de l'après-midi, dites-le dans le brief.
- Les formations, congés maladie, parentaux et sans solde réduisent la couverture comme les vacances mais ne comptent pas contre le solde.
### Astreinte et couverture à distance
- Si quelqu'un couvre l'astreinte depuis chez lui pendant ses congés, ne le marquez pas en congé pour ce rôle ; convenez-en explicitement et payez-le selon la politique. Le planificateur traite les congés comme totalement off.
- Un remplaçant externe ou d'agence peut être ajouté comme une personne avec uniquement ce rôle et sans solde.
### Marge
- Les jours à zéro marge (exactement le minimum qui travaille) cassent avec un jour de maladie. Nommez un remplaçant pour chaque tel jour avant le début de la période.
- Un rôle avec seulement minimum + 1 titulaires ne survit pas à deux absences ; planifiez la polyvalence dans la nouvelle année.
## Décisions de congés équitables
### Règle de priorité (annoncez-la avant que les demandes n'arrivent)
Ordre courant et défendable :
1. Les congés déjà approuvés restent (sauf si la personne accepte de les changer).
2. Les congés protégés (parentaux, médicaux, légaux) sont fixes.
3. Pour les jours de pointe : ceux qui ont travaillé le pic l'année dernière passent en premier.
4. Puis premier arrivé, premier servi par date de soumission.
5. Égalités : rotation (celui qui a perdu la dernière égalité gagne celle-ci).
Évitez les règles basées uniquement sur l'ancienneté pour les jours de pointe ; elles semblent injustes aux nouveaux employés et font souvent perdre de bonnes personnes.
### Périodes d'interdiction
- Gardez-les courtes et liées à un événement réel (lancement, clôture de facturation, inventaire, audit, début de trimestre scolaire pour une entreprise de tutorat). Annoncez-les tôt.
- Une demande qui touche une interdiction n'est pas automatiquement refusée : le planificateur propose les dates sans trou les plus proches ou la partie la plus longue avant l'interdiction. Les exceptions nécessitent un approbateur nommé.
### Solde et report
- Restant = solde - pris avant la période - approuvé - recommandé.
- Les congés approuvés au-delà du solde (LV02 élevé) sont une question RH : report de l'année dernière, achat de jours supplémentaires, ou congés sans solde.
- Les limites de report signifient que les jours non réservés sont perdus en fin d'année (LV06) : incitez les gens à réserver les semaines calmes ; dans certains pays, le congé minimum légal ne peut pas simplement expirer, donc vérifiez les règles locales.
### Bien refuser
- Expliquez la règle et le trou spécifique ("le niveau 2 serait en manque d'une personne les 29 et 30 déc").
- Proposez l'alternative trouvée par le planificateur, ou un échange avec un collègue nommé, ou un voyage plus court.
- Décidez tôt (au moins 4 à 6 semaines avant les fêtes) pour que les gens puissent encore réserver leurs voyages.
- Enregistrez la décision et la raison ; la priorité de l'année prochaine en dépend.
### Codes de constat (leave_cover.py)
| Code | Gravité | Signification |
|---|---|---|
| LV01 | élevée | les congés approuvés laissent déjà un rôle ou le total en manque un jour |
| LV02 | élevée / moyenne | congés approuvés au-delà du solde / demande nécessite plus de jours qu'il n'en reste |
| LV03 | moyenne / faible | rôle détenu par pas plus de personnes que le minimum / seulement un remplaçant |
| LV04 | moyenne | demande en conflit avec la couverture ou une interdiction, avec l'alternative proposée |
| LV05 | moyenne | quelqu'un qui a travaillé le pic l'année dernière est refusé pendant que d'autres obtiennent les jours de pointe |
| LV06 | faible | jours restants au-dessus de la limite de report seront perdus |
| LV07 | info | jours avec zéro marge après le plan |
## Modèle de plan de congés pour les fêtes
# Plan de congés pour les fêtes : <équipe>, <période>
**Règle de couverture :** <lead 1, niveau 2 2 (1 les 24/31 déc), astreinte 1, au moins 4 personnes> **Fermé :** <25 déc, 28 déc, 1 janv>
**Priorité utilisée :** <ceux qui ont travaillé le pic l'année dernière d'abord, puis premier arrivé premier servi>
**Interdiction :** <clôture de facturation 5-6 janv>
## Décisions
| Personne | Demandé | Décision | Pourquoi | Alternative proposée |
|---|---|---|---|---|
| <Sam> | <24 déc - 4 janv> | <approuvé> | <a travaillé le dernier Noël> | - |
| <Ade> | <29-31 déc> | <pas ces dates> | <niveau 2 en manque 29-30 déc> | <16-18 déc, ou échange avec ...> |
## Calendrier
```
<collez la grille du planificateur>
```
## Jours maigres et remplaçants
| Jour | Pourquoi maigre | Remplaçant |
|---|---|---|
| <21-23 déc> | <exactement 4 qui travaillent> | <astreinte depuis chez soi : ...> |
## Notes sur les soldes (pour les RH)
- <Omar : voyage approuvé 3 jours au-delà du solde - confirmer report / sans solde>
- <Lena : 15 jours restants, limite de report 5 - suggérer de réserver février>
## Prochaines étapes
- <parler à Ade, Lena, Jonas avant de publier>
- <publier le plan le ... ; changements par demande d'échange à ...>
- <former un deuxième remplaçant lead au T1>
## Exemple de JSON d'équipe
{
"team": "Northwind Customer Support",
"period": "2026-12-14..2027-01-08",
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"holidays": ["2026-12-25", "2026-12-28", "2027-01-01"],
"coverage": {"lead": 1, "tier2": 2, "oncall": 1, "any": 4},
"coverage_overrides": [
{"dates": "2026-12-24", "coverage": {"tier2": 1, "any": 3}},
{"dates": "2026-12-31", "coverage": {"tier2": 1, "any": 3}}
],
"blackout": [
{"dates": "2027-01-05..2027-01-06", "reason": "January billing run, all hands"}
],
"peak_days": "2026-12-21..2027-01-01",
"carryover_max": 5,
"people": [
{"name": "Maya", "roles": ["lead", "tier2"], "allowance": 25, "taken": 17, "worked_peak_last_year": true},
{"name": "Ade", "roles": ["lead", "tier2"], "allowance": 25, "taken": 19},
{"name": "Omar", "roles": ["tier2", "oncall"], "allowance": 25, "taken": 20},
{"name": "Lena", "roles": ["tier1", "tier2"], "allowance": 25, "taken": 10},
{"name": "Jonas", "roles": ["tier1", "oncall"], "allowance": 25, "taken": 18},
{"name": "Sam", "roles": ["tier1", "oncall"], "allowance": 25, "taken": 15, "worked_peak_last_year": true},
{"name": "Priya", "roles": ["tier1"], "days": ["mon", "wed", "thu"], "allowance": 15, "taken": 9}
]
}
## Exemple de CSV de congés
person,start,end,type,status,submitted,half_day,notes
Maya,2026-12-21,2026-12-24,vacation,approved,2026-09-30,,family visit
Omar,2026-12-29,2027-01-08,vacation,approved,2026-10-02,,"flights booked, Lagos"
Lena,2026-12-14,2026-12-15,training,approved,2026-11-01,,Zendesk admin course
Jonas,2026-12-21,2026-12-23,vacation,requested,2026-10-20,,
Ade,2026-12-29,2026-12-31,vacation,requested,2026-10-25,,
Sam,2026-12-24,2027-01-04,vacation,requested,2026-11-05,,"skiing, worked last Christmas"
Lena,2026-12-30,2027-01-08,vacation,requested,2026-11-12,,
Priya,2026-12-16,2026-12-16,vacation,requested,2026-11-20,yes,school nativity play
Jonas,2027-01-04,2027-01-08,vacation,requested,2026-11-25,,
Priya,2026-12-21,2026-12-23,vacation,requested,2026-11-28,,
## Exemple de sortie du planificateur
# Plan de couverture des congés : Northwind Customer Support (2026-12-14 au 2027-01-08)
Couverture minimale par jour ouvré : any 4, lead 1, oncall 1, tier2 2. Jours fériés (fermé) : 25 déc, 28 déc, 01 janv.
**Verdict : dépassement de solde sur les congés approuvés** - 4 des 7 demandes ouvertes peuvent être approuvées sans trou ; 3 nécessitent une décision.
## Demandes (dans l'ordre de décision : ceux qui ont travaillé le pic l'année dernière d'abord, puis premier arrivé premier servi)
| # | Personne | Dates | Jours ouvrés | Type | Soumise | Décision | Pourquoi | Alternative |
|---|---|---|---|---|---|---|---|---|
| 1 | Sam | Jeu 24 déc - Lun 04 janv | 5 | vacation | 2026-11-05 | approuver | - | - |
| 2 | Jonas | Lun 21 déc - Mer 23 déc | 3 | vacation | 2026-10-20 | approuver | - | - |
| 3 | Ade | Mar 29 déc - Jeu 31 déc | 3 | vacation | 2026-10-25 | conflit | any/tier2 en manque le 29 déc, 30 déc | Mer 16 déc au Ven 18 déc |
| 4 | Lena | Mer 30 déc - Ven 08 janv | 7 | vacation | 2026-11-12 | interdiction | clôture de facturation janvier, tous les mains (05 janv, 06 janv) | raccourcir à Jeu 31 déc - Jeu 31 déc (1 jour(s) ouvré(s), de retour avant l'interdiction) |
| 5 | Priya | Mer 16 déc - Mer 16 déc (demi) | 0.5 | vacation | 2026-11-20 | approuver | - | - |
| 6 | Jonas | Lun 04 janv - Ven 08 janv | 5 | vacation | 2026-11-25 | dépassement de solde | nécessite 5 jours, 4 restants | - |
| 7 | Priya | Lun 21 déc - Mer 23 déc | 2 | vacation | 2026-11-28 | approuver | - | - |
## Calendrier (approuvé + recommandé)
```
D J
11111222222233000000
45678123458901145678
MTWTFMTWTFMTWTFMTWTF
Maya .....VVVVHH...H.....
Ade .........HHxxxH.....
Omar .........HHVVVHVVVVV
Lena TT.......HH.xxHxxxxx
Jonas .....vvv.HH...Hxxxxx
Sam ........vHHvvvHv....
Priya .-h.-v-v.HH-..H.-..-
cover any +++++++++ +++ +++++
cover lead +++++++++ +++ +++++
cover oncall +++++++++ +++ +++++
cover tier2 +++++++++ +++ +++++
```
V/T/S/P/U/O congé approuvé vacation/formation/maladie/parental/sans solde/autre, minuscule = demande recommandée pour approbation, h = demi-journée, x = demande non approuvée, H = jour férié, - = pas un jour de travail, + / ! = rôle couvert / en manque.
## Soldes (jours)
| Personne | Solde | Pris avant | Approuvé ici | Recommandé | Restant | Jours de pointe off |
|---|---|---|---|---|---|---|
| Maya | 25 | 17 | 4 | 0 | 4 | 4 |
| Ade | 25 | 19 | 0 | 0 | 6 | 0 |
| Omar | 25 | 20 | 8 | 0 | -3 | 3 |
| Lena | 25 | 10 | 0 | 0 | 15 | 0 |
| Jonas | 25 | 18 | 0 | 3 | 4 | 3 |
| Sam | 25 | 15 | 0 | 5 | 5 | 4 |
| Priya | 15 | 9 | 0 | 2.5 | 3.5 | 2 |
## Constats
- **ÉLEVÉ LV02** Le congé approuvé d'Omar dépasse le solde de 3 jours (20 + 8 pris sur 25) ; confirmez le report ou les jours sans solde avec les RH.
- **MOYEN LV02** Jonas 04 janv-08 janv : nécessite 5 jours, 4 restants ; approuvez un voyage plus court ou convenez de jours sans solde
- **MOYEN LV04** Ade 29 déc-31 déc : conflit (any/tier2 en manque le 29 déc, 30 déc) ; proposez Mer 16 déc au Ven 18 déc
- **MOYEN LV04** Lena 30 déc-08 janv : interdiction (clôture de facturation janvier, tous les mains (05 janv, 06 janv)) ; proposez de raccourcir à Jeu 31 déc - Jeu 31 déc (1 jour(s) ouvré(s), de retour avant l'interdiction)
- **FAIBLE LV03** lead a une personne de rechange (Maya, Ade pour un minimum de 1) ; deux absences à la fois le cassent.
- **FAIBLE LV06** Ade aura encore 6 jours restants (limite de report 5) ; 1 jour(s) sont perdus sauf s'ils sont réservés.
- **FAIBLE LV06** Lena aura encore 15 jours restants (limite de report 5) ; 10 jour(s) sont perdus sauf s'ils sont réservés.
- **INFO LV07** Pas de marge sur 4 jour(s) (21 déc, 22 déc, 23 déc, 29 déc) : un jour de maladie casse la couverture ; prévoyez un remplaçant.
## Script du planificateur Python
```python
#!/usr/bin/env python3
"""Planificateur de congés d'équipe et de couverture (bibliothèque standard Python 3.8+ uniquement).
Vérifie les demandes de congés d'une équipe par rapport aux règles d'effectif minimum jour par jour, décide quelles
demandes peuvent être approuvées sans laisser de trou, suggère des dates alternatives pour le reste, et suit
les soldes et qui obtient les jours de pointe des fêtes.
JSON d'équipe :
{
"team": "Support Desk", "period": "2026-12-14..2027-01-08",
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"holidays": ["2026-12-25", "2026-12-28", "2027-01-01"], (bureau fermé, pas de couverture nécessaire)
"coverage": {"lead": 1, "tier2": 2, "oncall": 1, "any": 4}, ("any" = total des personnes qui travaillent)
"coverage_overrides": [{"dates": "2026-12-24", "coverage": {"tier2": 1, "any": 3}}],
"blackout": [{"dates": "2027-01-05..2027-01-06", "reason": "billing run"}],
"peak_days": "2026-12-21..2027-01-01",
"people": [
{"name": "Maya", "roles": ["lead", "tier2"], "allowance": 25, "taken": 17, "worked_peak_last_year": true},
{"name": "Priya", "roles": ["tier1"], "days": ["mon", "wed", "thu"], "allowance": 15, "taken": 9}
]
}
Chaque personne qui travaille remplit UN créneau de rôle par jour (un lead qui est aussi tier2 compte une fois), résolu
comme une correspondance bipartite.
CSV de congés (en-tête requis) : person,start,end,type,status,submitted,half_day,notes
type vacation (compte contre le solde) | sick | training | parental | unpaid | other
status approved | requested | declined (ignoré)
half_day yes = demi-journée de solde ; la personne n'est pas comptée pour la couverture ce jour-là
Utilisation :
python3 leave_cover.py team.json leave.csv [--json]
python3 leave_cover.py --self-test
Codes de sortie : 0 = les congés approuvés maintiennent la couverture et personne ne dépasse son solde, 1 = les congés
approuvés cassent déjà la couverture ou dépassent un solde, 2 = mauvaise entrée.
"""
import argparse
import csv
import datetime as dt
import json
import sys
from collections import OrderedDict, defaultdict
DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
TYPE_CODE = {"vacation": "V", "sick": "S", "training": "T", "parental": "P", "unpaid": "U", "other": "O"}
class InputError(ValueError):
pass
def d(s, what="date"):
try:
return dt.date.fromisoformat(str(s).strip())
except ValueError:
raise InputError("%s %r n'est pas AAAA-MM-JJ" % (what, s))
def date_range(spec):
spec = str(spec)
a, _, b = spec.partition("..")
a, b = d(a), d(b or a)
if b < a:
raise InputError("la plage %r se termine avant de commencer" % spec)
return [a + dt.timedelta(days=i) for i in range((b - a).days + 1)]
# ----------------------------------------------------------------------------- entrées
def load_team(cfg):
if "period" not in cfg or not cfg.get("people"):
raise InputError("le JSON d'équipe nécessite 'period' et 'people'")
t = dict(cfg)
t["days"] = date_range(cfg["period"])
t["workdays"] = [w.lower()[:3] for w in cfg.get("workdays", DAYS[:5])]
t["holidays"] = {d(h, "holiday") for h in cfg.get("holidays", [])}
t["coverage"] = {k: int(v) for k, v in (cfg.get("coverage") or {}).items()}
over = {}
for o in cfg.get("coverage_overrides", []):
for day in date_range(o["dates"]):
over[day] = {k: int(v) for k, v in o["coverage"].items()}
t["overrides"] = over
t["blackout"] = [(set(date_range(b["dates"])), b.get("reason", "blackout")) for b in cfg.get("blackout", [])]
t["peak"] = set(date_range(cfg["peak_days"])) if cfg.get("peak_days") else set()
people = OrderedDict()
for p in cfg["people"]:
if "name" not in p:
raise InputError("chaque personne nécessite un nom")
q = dict(p)
q["roles"] = list(p.get("roles", []))
q["days"] = [w.lower()[:3] for w in p.get("days", t["workdays"])]
q["allowance"] = float(p.get("allowance", 0))
q["taken"] = float(p.get("taken", 0))
q["carryover_max"] = p.get("carryover_max", cfg.get("carryover_max"))
people[p["name"]] = q
t["people"] = people
roles = {r for p in people.values() for r in p["roles"]}
for r in list(t["coverage"]) + [r for o in over.values() for r in o]:
if r != "any" and r not in roles:
raise InputError("le rôle de couverture %r n'est détenu par personne" % r)
return t
def load_leave(path, team):
out = []
with open(path, newline="", encoding="utf-8") as f:
rows = [r for r in f if r.strip() and not r.lstrip().startswith("#")]
for n, row in enumerate(csv.DictReader(rows), 2):
name = (row.get("person") or "").strip()
if name not in team["people"]:
raise InputError("ligne de congé %d : personne inconnue %r" % (n, name))
typ = (row.get("type") or "vacation").strip().lower()
if typ not in TYPE_CODE:
raise InputError("ligne de congé %d : le type doit être l'un de %s" % (n, ", ".join(TYPE_CODE)))
status = (row.get("status") or "requested").strip().lower()
if status not in ("approved", "requested", "declined"):
raise InputError("ligne de congé %d : le statut doit être approved, requested ou declined" % n)
start, end = d(row.get("start"), "start"), d(row.get("end") or row.get("start"), "end")
if end < start:
raise InputError("ligne de congé %d : fin avant début" % n)
out.append({"id": n, "person": name, "start": start, "end": end, "type": typ, "status": status,
"submitted": (row.get("submitted") or "").strip() or "9999-12-31",
"half": (row.get("half_day") or "").strip().lower() in ("yes", "y", "true", "1"),
"notes": (row.get("notes") or "").strip()})
return out
# ----------------------------------------------------------------------------- cœur
def is_workday(team, day):
return DAYS[day.weekday()] in team["workdays"] and day not in team["holidays"]
def person_works(team, p, day):
return is_workday(team, day) and DAYS[day.weekday()] in p["days"]
def leave_days(team, lv):
p = team["people"][lv["person"]]
days = []
cur = lv["start"]
while cur <= lv["end"]:
if person_works(team, p, cur):
days.append(cur)
cur += dt.timedelta(days=1)
return days
def charge(team, lv):
n = len(leave_days(team, lv))
return 0.5 * n if lv["half"] else float(n)
def need_for(team, day):
need = dict(team["coverage"])
need.update(team["overrides"].get(day, {}))
return need
def shortfall(team, day, off):
"""Renvoie {rôle : manquant} pour un jour étant donné l'ensemble des personnes absentes."""
if not is_workday(team, day):
return {}
avail = [p for n, p in team["people"].items() if n not in off and person_works(team, p, day)]
need = need_for(team, day)
slots = [r for r, k in sorted(need.items()) if r != "any" for _ in range(k)]
match = {} # index de créneau -> nom de personne
def try_assign(pi, seen):
for si, role in enumerate(slots):
if role in avail[pi]["roles"] and si not in seen:
seen.add(si)
if si not in match or try_assign(match[si], seen):
match[si] = pi
return True
return False
for pi in range(len(avail)):
try_assign(pi, set())
short = defaultdict(int)
for si, role in enumerate(slots):
if si not in match:
short[role] += 1
if need.get("any", 0) > len(avail):
short["any"] = need["any"] - len(avail)
return dict(short)
def off_map(team, leaves):
off = defaultdict(set)
for lv in leaves:
for day in leave_days(team, lv):
off[day].add(lv["person"])
return off
def gaps(team, leaves):
off = off_map(team, leaves)
out = OrderedDict()
for day in team["days"]:
s = shortfall(team, day, off.get(day, set()))
if s:
out[day] = s
return out
def total(g):
return sum(sum(v.values()) for v in g.values())
def new_gaps(before, after):
out = OrderedDict()
for day, s in after.items():
b = before.get(day, {})
worse = {r: k - b.get(r, 0) for r, k in s.items() if k > b.get(r, 0)}
if worse:
out[day] = worse
return out
def blackout_hit(team, lv):
days = set(leave_days(team, lv))
for bdays, reason in team["blackout"]:
hit = sorted(days & bdays)
if hit:
return hit, reason
return None, None
def suggest(team, current, lv, max_shift=15):
"""Décalage le plus proche de la même demande (même longueur calendaire) qui maintient la couverture."""
length = (lv["end"] - lv["start"]).days
want = len(leave_days(team, lv))
base = gaps(team, current)
first, last = team["days"][0], team["days"][-1]
for shift in sorted(range(-max_shift, max_shift + 1), key=lambda x: (abs(x), x)):
if shift == 0:
continue
cand = dict(lv, start=lv["start"] + dt.timedelta(days=shift), end=lv["start"] + dt.timedelta(days=shift + length))
if cand["start"] < first or cand["end"] > last:
continue
if len(leave_days(team, cand)) < want or blackout_hit(team, cand)[0]:
continue
if not new_gaps(base, gaps(team, current + [cand])):
return cand
return None
def trimmed(team, current, lv):
"""Partie la plus longue d'une demande qui touche une interdiction, avant le premier jour d'interdiction, qui maintient la couverture."""
hit, _ = blackout_hit(team, lv)
days = [x for x in leave_days(team, lv) if x < hit[0]]
base = gaps(team, current)
best = None
for i in range(len(days)):
for j in range(len(days) - 1, i - 1, -1):
if best and j - i + 1 <= len(leave_days(team, best)):
break
cand = dict(lv, start=days[i], end=days[j])
if not new_gaps(base, gaps(team, current + [cand])):
best = cand
break
return best
def plan(team, leaves):
findings = []
def add(sev, code, msg):
findings.append({"severity": sev, "code": code, "message": msg})
approved = [l for l in leaves if l["status"] == "approved"]
requests = [l for l in leaves if l["status"] == "requested"]
used = defaultdict(float)
for l in approved:
if l["type"] == "vacation":
used[l["person"]] += charge(team, l)
g_approved = gaps(team, approved)
for day, s in g_approved.items():
add("high", "LV01", "%s %s : les congés approuvés laissent déjà %s en manque" % (
day.isoformat(), DAYS[day.weekday()].title(), ", ".join("%s de %d" % (r, k) for r, k in sorted(s.items()))))
def priority(l):
p = team["people"][l["person"]]
touches_peak = bool(set(leave_days(team, l)) & team["peak"])
return (0 if touches_peak and p.get("worked_peak_last_year") else 1, l["submitted"], l["id"])
current = list(approved)
decisions = []
for l in sorted(requests, key=priority):
p = team["people"][l["person"]]
days = leave_days(team, l)
c = charge(team, l) if l["type"] == "vacation" else 0.0
dec = {"leave": l, "workdays": len(days), "charge": c, "decision": "approve", "reason": "", "suggestion": None}
if not days:
dec.update(decision="no workdays", reason="tombe sur des jours fériés / jours non travaillés uniquement")
elif l["type"] == "vacation" and p["allowance"] and p["taken"] + used[l["person"]] + c > p["allowance"] + 1e-9:
left = p["allowance"] - p["taken"] - used[l["person"]]
dec.update(decision="over allowance", reason="nécessite %g jours, %g restants" % (c, max(left, 0)))
else:
hit, reason = blackout_hit(team, l)
if hit:
dec.update(decision="blackout", reason="%s (%s)" % (reason, ", ".join(x.strftime("%d %b") for x in hit)))
else:
ng = new_gaps(gaps(team, current), gaps(team, current + [l]))
if ng:
roles = sorted({r for s in ng.values() for r in s})
dec.update(decision="conflict", reason="%s en manque le %s" % (
"/".join(roles), ", ".join(x.strftime("%d %b") for x in ng)))
if dec["decision"] == "approve":
current.append(l)
used[l["person"]] += c
elif dec["decision"] in ("conflict", "blackout"):
s = suggest(team, current, l)
if s:
dec["suggestion"] = "%s au %s" % (s["start"].strftime("%a %d %b"), s["end"].strftime("%a %d %b"))
elif dec["decision"] == "blackout":
t = trimmed(team, current, l)
if t:
dec["suggestion"] = "raccourcir à %s - %s (%d jour(s) ouvré(s), de retour avant l'interdiction)" % (
t["start"].strftime("%a %d %b"), t["end"].strftime("%a %d %b"), len(leave_days(team, t)))
decisions.append(dec)
approved_peak = defaultdict(int)
for l in current:
approved_peak[l["person"]] += len(set(leave_days(team, l)) & team["peak"])
for dec in decisions:
l = dec["leave"]
p = team["people"][l["person"]]
if dec["decision"] != "approve" and p.get("worked_peak_last_year") and set(leave_days(team, l)) & team["peak"]:
lucky = [n for n, k in approved_peak.items() if k and not team["people"][n].get("worked_peak_last_year")]
add("medium", "LV05", "%s a travaillé le pic l'année dernière mais cette demande est %s pendant que %s obtiennent les jours de pointe ; "
"envisagez un échange." % (l["person"], dec["decision"], ", ".join(sorted(lucky)) or "personne"))
g_final = gaps(team, current)
allowance = []
for n, p in team["people"].items():
planned_req = sum(dec["charge"] for dec in decisions if dec["leave"]["person"] == n and dec["decision"] == "approve")
appr = used[n] - planned_req
left = p["allowance"] - p["taken"] - used[n] if p["allowance"] else None
allowance.append({"person": n, "allowance": p["allowance"], "taken_before": p["taken"], "approved": appr,
"recommended": planned_req, "left": left, "peak_days_off": approved_peak.get(n, 0)})
if p["allowance"] and p["taken"] + appr > p["allowance"] + 1e-9:
add("high", "LV02", "Le congé approuvé de %s dépasse le solde de %g jours (%g + %g pris sur %g) ; "
"confirmez le report ou les jours sans solde avec les RH." % (n, p["taken"] + appr - p["allowance"], p["taken"],
appr, p["allowance"]))
cm = p.get("carryover_max")
if left is not None and cm is not None and left > float(cm):
add("low", "LV06", "%s aura encore %g jours restants (limite de report %g) ; %g jour(s) sont perdus sauf s'ils sont réservés."
% (n, left, float(cm), left - float(cm)))
for role, k in sorted(team["coverage"].items()):
if role == "any":
continue
holders = [n for n, p in team["people"].items() if role in p["roles"]]
if len(holders) <= k:
add("medium", "LV03", "Seulement %d personnes peuvent couvrir %s (minimum %d) : toute absence de %s casse la couverture ; "
"formez un remplaçant polyvalent." % (len(holders), role, k, ", ".join(holders)))
elif len(holders) == k + 1:
add("low", "LV03", "%s a une personne de rechange (%s pour un minimum de %d) ; deux absences à la fois le cassent."
% (role, ", ".join(holders), k))
for day, s in g_final.items():
if day not in g_approved:
add("high", "LV01", "%s toujours en manque après les décisions : %s" % (day, s))
for dec in decisions:
if dec["decision"] in ("conflict", "blackout"):
l = dec["leave"]
add("medium", "LV04", "%s %s-%s : %s (%s)%s" % (
l["person"], l["start"].strftime("%d %b"), l["end"].strftime("%d %b"), dec["decision"], dec["reason"],
(" ; proposez " + dec["suggestion"]) if dec["suggestion"] else " ; aucune alternative sans trou dans les 15 jours"))
elif dec["decision"] == "over allowance":
l = dec["leave"]
add("medium", "LV02", "%s %s-%s : %s ; approuvez un voyage plus court ou convenez de jours sans solde" % (
l["person"], l["start"].strftime("%d %b"), l["end"].strftime("%d %b"), dec["reason"]))
thin = []
off = off_map(team, current)
for day in team["days"]:
if not is_workday(team, day):
continue
working = [n for n, p in team["people"].items() if n not in off.get(day, set()) and person_works(team, p, day)]
if len(working) == need_for(team, day).get("any", 0):
thin.append(day)
if thin:
add("info", "LV07", "Pas de marge sur %d jour(s) (%s) : un jour de maladie casse la couverture ; prévoyez un remplaçant." % (
len(thin), ", ".join(x.strftime("%d %b") for x in thin)))
order = {"high": 0, "medium": 1, "low": 2, "info": 3}
findings.sort(key=lambda f: (order[f["severity"]], f["code"]))
return {"team": team.get("team", "Team"), "period": team["period"], "gaps_approved": g_approved,
"gaps_final": g_final, "decisions": decisions, "allowance": allowance, "findings": findings,
"current": current, "thin": thin}
# ----------------------------------------------------------------------------- rapport
def grid(team, leaves_final, all_leaves):
days = [x for x in team["days"] if x.weekday() < 5 or DAYS[x.weekday()] in team["workdays"]]
final_ids = {l["id"] for l in leaves_final}
cell = defaultdict(dict)
for l in all_leaves:
if l["status"] == "declined":
continue
for day in leave_days(team, l):
if l["id"] in final_ids:
c = TYPE_CODE[l["type"]] if l["status"] == "approved" else TYPE_CODE[l["type"]].lower()
else:
c = "x"
if l["half"]:
c = "h" if c != "x" else "x"
cell[l["person"]][day] = c
width = max([len(n) for n in team["people"]] + [len("cover " + r) for r in team["coverage"]]) + 2
months = " " * width + "".join(x.strftime("%b")[0] if i == 0 or x.month != days[i - 1].month
else " " for i, x in enumerate(days))
L = [months, " " * width + "".join(str(x.day // 10) for x in days),
" " * width + "".join(str(x.day % 10) for x in days),
" " * width + "".join(DAYS[x.weekday()][0].upper() for x in days)]
for n, p in team["people"].items():
row = []
for x in days:
if x in team["holidays"]:
row.append("H")
elif not person_works(team, p, x):
row.append("-")
else:
row.append(cell[n].get(x, "."))
L.append(n.ljust(width) + "".join(row))
off = off_map(team, leaves_final)
for role in sorted(team["coverage"]):
row = []
for x in days:
if not is_workday(team, x):
row.append(" ")
continue
s = shortfall(team, x, off.get(x, set()))
row.append("!" if role in s else "+")
L.append(("cover " + role).ljust(width) + "".join(row))
return L
def report(team, leaves, r):
L = ["# Plan de couverture des congés : %s (%s)" % (r["team"], r["period"].replace("..", " au ")), ""]
need = ", ".join("%s %d" % (k, v) for k, v in sorted(team["coverage"].items()))
L.append("Couverture minimale par jour ouvré : %s. Jours fériés (fermé) : %s." % (
need, ", ".join(x.strftime("%d %b") for x in sorted(team["holidays"])) or "aucun"))
n_appr = sum(1 for dec in r["decisions"] if dec["decision"] == "approve")
verdict = ("les congés approuvés cassent déjà la couverture" if r["gaps_approved"] else
"dépassement de solde sur les congés approuvés" if any(f["code"] == "LV02" and f["severity"] == "high" for f in r["findings"])
else "couvert")
L += ["", "**Verdict : %s** - %d des %d demandes ouvertes peuvent être approuvées sans trou ; %d nécessitent une décision." % (
verdict, n_appr, len(r["decisions"]), len(r["decisions"]) - n_appr), ""]
L += ["## Demandes (dans l'ordre de décision : ceux qui ont travaillé le pic l'année dernière d'abord, puis premier arrivé premier servi)", "",
"| # | Personne | Dates | Jours ouvrés | Type | Soumise | Décision | Pourquoi | Alternative |",
"|---|---|---|---|---|---|---|---|---|"]
for i, dec in enumerate(r["decisions"], 1):
l = dec["leave"]
L.append("| %d | %s | %s - %s%s | %s | %s | %s | %s | %s | %s |" % (
i, l["person"], l["start"].strftime("%a %d %b"), l["end"].strftime("%a %d %b"), " (demi)" if l["half"] else "",
("%g" % (0.5 * dec["workdays"])) if l["half"] else dec["workdays"], l["type"], l["submitted"][:10],
dec["decision"], dec["reason"] or "-", dec["suggestion"] or "-"))
L += ["", "## Calendrier (approuvé + recommandé)", "", "```"] + grid(team, r["current"], leaves) + [
"```", "V/T/S/P/U/O congé approuvé vacation/formation/maladie/parental/sans solde/autre, minuscule = demande recommandée "
"pour approbation, h = demi-journée, x = demande non approuvée, H = jour férié, - = pas un jour de travail, "
"+ / ! = rôle couvert / en manque.", ""]
L += ["## Soldes (jours)", "", "| Personne | Solde | Pris avant | Approuvé ici | Recommandé | Restant | Jours de pointe off |",
"|---|---|---|---|---|---|---|"]
for a in r["allowance"]:
L.append("| %s | %g | %g | %g | %g | %s | %d |" % (a["person"], a["allowance"], a["taken_before"], a["approved"],
a["recommended"], "-" if a["left"] is None else "%g" % a["left"],
a["peak_days_off"]))
L += ["", "## Constats", ""]
if not r["findings"]:
L.append("- aucun")
for f in r["findings"]:
L.append("- **%s %s** %s" % (f["severity"].upper(), f["code"], f["message"]))
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------- auto-test
def self_test():
import os
import tempfile
cases = []
def ok(name, cond, detail=""):
cases.append((name, bool(cond), detail))
base = {"team": "T", "period": "2026-12-14..2026-12-25", "holidays": ["2026-12-25"], "peak_days": "2026-12-21..2026-12-24",
"coverage": {"lead": 1, "dev": 1, "any": 2},
"people": [{"name": "A", "roles": ["lead", "dev"], "allowance": 10, "taken": 0},
{"name": "B", "roles": ["dev"], "allowance": 10, "taken": 9},
{"name": "C", "roles": ["lead"], "allowance": 10, "taken": 0, "worked_peak_last_year": True},
{"name": "D", "roles": ["dev"], "days": ["mon", "tue"], "allowance": 5, "taken": 0}]}
t = load_team(base)
ok("les week-ends et jours fériés ne sont pas des jours ouvrés", not is_workday(t, d("2026-12-19")) and not is_workday(t, d("2026-12-25")))
sf = shortfall(t, d("2026-12-16"), {"B", "C", "D"})
ok("une personne remplit un créneau (A ne peut pas être lead et dev)", sf.get("any") == 1 and sf.get("lead", 0) + sf.get("dev", 0) == 1, sf)
ok("la correspondance trouve A=dev, C=lead", shortfall(t, d("2026-12-16"), {"B", "D"}) == {})
ok("le temps partiel ne compte que sur ses propres jours", shortfall(t, d("2026-12-17"), {"A", "B"}) == {"dev": 1, "any": 1}, shortfall(t, d("2026-12-17"), {"A", "B"}))
lv = {"id": 1, "person": "D", "start": d("2026-12-14"), "end": d("2026-12-20"), "type": "vacation",
"status": "requested", "submitted": "2026-11-01", "half": False, "notes": ""}
ok("les jours de congé sautent les jours non travaillés", len(leave_days(t, lv)) == 2)
ok("la demi-journée coûte 0.5", charge(t, dict(lv, half=True)) == 1.0)
d0 = tempfile.mkdtemp()
csvp = os.path.join(d0, "l.csv")
with open(csvp, "w") as f:
f.write("person,start,end,type,status,submitted,half_day,notes\n"
"A,2026-12-21,2026-12-22,vacation,approved,2026-10-01,,\n"
"C,2026-12-21,2026-12-21,vacation,requested,2026-11-10,,\n"
"B,2026-12-14,2026-12-16,vacation,requested,2026-10-05,,\n"
"D,2026-12-15,2026-12-15,training,requested,2026-11-01,,\n")
leaves = load_leave(csvp, t)
r = plan(t, leaves)
by = {(dec["leave"]["person"]): dec for dec in r["decisions"]}
ok("C est en conflit avec le congé approuvé de A (pas de lead)", by["C"]["decision"] == "conflict", by["C"])
ok("le conflit obtient une date alternative", by["C"]["suggestion"] is not None, by["C"])
ok("B dépasse le solde (3 jours, 1 restant)", by["B"]["decision"] == "over allowance", by["B"])
ok("la formation de D est approuvée", by["D"]["decision"] == "approve", by["D"])
ok("la personne qui a travaillé le pic est décidée en premier", r["decisions"][0]["leave"]["person"] == "C", [x["leave"]["person"] for x in r["decisions"]])
codes = {f["code"] for f in r["findings"]}
ok("LV03 point de défaillance unique pour lead", "LV03" in codes, codes)
ok("LV05 drapeau d'équité pour C", "LV05" in codes, codes)
ok("pas de trous dus aux congés approuvés", not r["gaps_approved"])
with open(csvp, "a") as f:
f.write("C,2026-12-21,2026-12-21,sick,approved,2026-12-01,,\n")
r2 = plan(t, load_leave(csvp, t))
ok("LV01 trou dans les congés approuvés", any(f["code"] == "LV01" and f["severity"] == "high" for f in r2["findings"]))
t2 = load_team(dict(base, blackout=[{"dates": "2026-12-16", "reason": "release"}]))
r3 = plan(t2, load_leave(csvp, t2))
ok("l'interdiction précède la couverture", any(x["decision"] == "blackout" for x in r3["decisions"]) or
all(x["leave"]["person"] != "B" or x["decision"] == "over allowance" for x in r3["decisions"]))
lvb = dict(lv, person="A", start=d("2026-12-16"), end=d("2026-12-16"))
ok("blackout_hit détecte le chevauchement", blackout_hit(t2, lvb)[0] == [d("2026-12-16")])
try:
load_team(dict(base, coverage={"qa": 1}))
ok("rôle de couverture inconnu rejeté", False)
except InputError:
ok("rôle de couverture inconnu rejeté", True)
rep = report(t, leaves, r)
ok("le rapport a une grille et un verdict", "Verdict" in rep and "cover lead" in rep)
for name, good, detail in cases:
print("%s %s%s" % ("PASS" if good else "FAIL", name, "" if good else " -> %r" % (detail,)))
passed = sum(1 for _, g, _ in cases if g)
print("%d/%d réussis" % (passed, len(cases)))
return 0 if passed == len(cases) else 1
def to_json(r):
def conv(o):
if isinstance(o, dt.date):
return o.isoformat()
if isinstance(o, dict):
return {(k.isoformat() if isinstance(k, dt.date) else k): conv(v) for k, v in o.items()}
if isinstance(o, (list, tuple)):
return [conv(x) for x in o]
return o
return json.dumps(conv(r), indent=2)
def main(argv=None):
ap = argparse.ArgumentParser(description="Planifier les congés d'équipe par rapport à la couverture minimale.")
ap.add_argument("team", nargs="?")
ap.add_argument("leave", nargs="?")
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not (a.team and a.leave):
ap.print_usage(sys.stderr)
return 2
try:
with open(a.team, encoding="utf-8") as f:
team = load_team(json.load(f))
leaves = load_leave(a.leave, team)
r = plan(team, leaves)
except (InputError, OSError, json.JSONDecodeError, KeyError) as e:
print("erreur : %s" % e, file=sys.stderr)
return 2
print(to_json(r) if a.json else report(team, leaves, r), end="\n" if a.json else "")
bad = r["gaps_approved"] or any(f["code"] == "LV02" and f["severity"] == "high" for f in r["findings"])
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())
```
Connectez-vous pour voir le prompt complet
Continuer avec:
En vous connectant, vous acceptez nos Conditions et Confidentialité
Utilisation
Ce prompt est conçu pour être utilisé avec business. Copiez le contenu ci-dessus et collez-le dans votre outil d’IA préféré.
Pour de meilleurs résultats, personnalisez les espaces réservés (indiqués par des crochets ou des majuscules) selon vos besoins.
Discussion
0 commentaires