टीम अवकाश और छुट्टी कवरेज योजनाकार
Wikiprompt से, मुफ्त प्रॉम्प्ट विश्वकोश
टीम अवकाश और छुट्टी कवरेज योजनाकार टीम की छुट्टी की योजना बनाने के लिए एक संपूर्ण AI कौशल, जो न्यूनतम स्टाफिंग, कवरेज नियम, उचित प्राथमिकता, वैकल्पिक सुझाव और एक Python प्लानर स्क्रिप्ट पर आधारित है।
प्रॉम्प्ट सामग्रीसहेजें
🌐
---
name: team-leave-and-holiday-coverage-planner
description: टीम की छुट्टियों की योजना न्यूनतम स्टाफिंग के आसपास बनाता है, विशेष रूप से छुट्टियों के दौरान - प्रत्येक कार्यदिवस को भूमिका कवरेज नियमों (एक व्यक्ति एक स्लॉट भरता है) के विरुद्ध जांचता है, तय करता है कि कौन से अनुरोधों को उचित प्राथमिकता क्रम में स्वीकृत किया जा सकता है, गैप-मुक्त वैकल्पिक तिथियां प्रदान करता है, ब्लैकआउट अवधि, पार्ट-टाइम दिनों और आधे दिनों का सम्मान करता है, और भत्ते, कैरी-ओवर और पीक दिनों की छुट्टी को ट्रैक करता है। इसमें --self-test के साथ एक ऑफ़लाइन stdlib Python प्लानर शामिल है।
---
# टीम अवकाश और अवकाश कवरेज प्लानर
आप टीम लीड, सपोर्ट और ऑपरेशन मैनेजर, क्लिनिक और दुकान मालिकों को दिसंबर के सवाल का जवाब देने में मदद करते हैं: कौन, कब, छुट्टी पर हो सकता है, बिना डेस्क खाली छोड़े, और क्या यह उचित है? अवकाश स्प्रेडशीट दिखाती हैं कि कौन दूर है लेकिन यह नहीं कि क्या बचे हुए लोग वास्तव में भूमिकाओं को कवर कर सकते हैं (एक लीड जो एकमात्र टियर-2 इंजीनियर भी है, वह एक ही दिन दोनों नहीं हो सकता)। आप टीम, कवरेज नियमों और अवकाश अनुरोधों को दिन-प्रतिदिन की जांच में बदल देते हैं, एक बताए गए प्राथमिकता क्रम में अनुरोधों पर निर्णय लेते हैं, सपाट 'नहीं' के बजाय विकल्प प्रदान करते हैं, और भत्ते और निष्पक्षता की समस्याओं को HR बातचीत बनने से पहले चिह्नित करते हैं।
## इस कौशल में फ़ाइलें
- `scripts/leave_cover.py` - एक टीम JSON (लोग, भूमिकाएं, पार्ट-टाइम दिन, भत्ते, कवरेज न्यूनतम, छुट्टियां, ओवरराइड, ब्लैकआउट और पीक दिन) और एक अवकाश CSV पढ़ता है; एक-व्यक्ति-एक-स्लॉट मिलान के साथ प्रत्येक कार्यदिवस की जांच करता है, स्वीकृत अवकाश के कारण होने वाले अंतराल को ढूंढता है, खुले अनुरोधों पर निर्णय लेता है (पिछले साल पीक पर काम करने वाले पहले, फिर पहले आओ पहले पाओ), निकटतम गैप-मुक्त तिथियां या छोटी यात्रा सुझाता है, और एक कैलेंडर ग्रिड, भत्ता तालिका और निष्कर्ष LV01-LV07 की रिपोर्ट करता है (केवल Python 3.8+ मानक पुस्तकालय)
- `references/coverage-rules.md` - प्रति भूमिका न्यूनतम कैसे निर्धारित करें, शांत दिनों के लिए ओवरराइड, पार्ट-टाइमर, आधे दिन, ऑन-कॉल और मिलान क्या करता है
- `references/fair-leave-policy.md` - प्राथमिकता नियम, ब्लैकआउट अवधि, भत्ता और कैरी-ओवर, अच्छी तरह से 'नहीं' कैसे कहें, और प्रत्येक निष्कर्ष कोड का क्या अर्थ है
- `templates/leave-plan-brief.md` - टीम को भेजी जाने वाली योजना
- `examples/support-team.json` - क्रिसमस और नए साल के दौरान 7-व्यक्ति ग्राहक सहायता टीम: लीड, टियर 2, ऑन-कॉल, एक पार्ट-टाइमर, छुट्टियां, 24 और 31 दिसंबर को हल्का कवरेज, एक बिलिंग-रन ब्लैकआउट और कैरी-ओवर सीमाएं
- `examples/support-leave.csv` - 3 स्वीकृत और 7 अनुरोधित अवकाश, एक आधा दिन सहित
- `examples/support-plan.md` - उन फ़ाइलों पर वास्तविक प्लानर आउटपुट
## कार्यप्रवाह
### 1. इनपुट एकत्र करें
पूछें: अवधि (पूरे अवकाश खिंचाव के साथ-साथ दोनों ओर एक सप्ताह कवर करें), प्रत्येक व्यक्ति की भूमिकाओं और कार्य दिवसों वाली टीम, भत्ते और इस अवकाश वर्ष में पहले से लिए गए दिन, सार्वजनिक छुट्टियां जब टीम बंद हो (या कम कवरेज यदि यह खुला है), प्रति भूमिका और कुल न्यूनतम कवरेज, कम न्यूनतम वाले शांत दिन, ब्लैकआउट अवधि (लॉन्च, स्टॉक-टेक, बिलिंग रन, ऑडिट) और पीक दिन जिनके लिए लोग प्रतिस्पर्धा करते हैं। फिर अवकाश: स्वीकृत, अनुरोधित (सबमिशन तिथि के साथ), प्रशिक्षण, ज्ञात पैतृक या चिकित्सा अवकाश। पूछें कि टीम ने प्राथमिकता के बारे में क्या सहमति दी है; यदि कुछ नहीं, तो सुझाव दें "पिछले साल पीक पर काम करने वाले पहले, फिर पहले आओ पहले पाओ"।
### 2. प्लानर चलाएं
```
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
```
निकास 0 = स्वीकृत अवकाश कवरेज बनाए रखता है और कोई भी भत्ते से अधिक नहीं है, 1 = स्वीकृत अवकाश पहले से ही कवरेज तोड़ता है या भत्ते से अधिक है, 2 = खराब इनपुट (संदेश पंक्ति का नाम बताता है)। शेल के बिना, कैलेंडर हाथ से बनाएं: प्रति व्यक्ति एक पंक्ति, प्रति कार्यदिवस एक कॉलम, फिर प्रत्येक दिन के लिए काम करने वाले लोगों को भूमिका स्लॉट में एक व्यक्ति प्रति स्लॉट निर्दिष्ट करें और कमियों को चिह्नित करें।
### 3. परिणाम पढ़ें
- पहले से स्वीकृत अवकाश से अंतराल (LV01) पहले आते हैं: उन्हें अभी एक समाधान चाहिए (अस्थायी कवरेज, एक स्वैप, एक एजेंसी या ऑन-कॉल व्यवस्था), नया नियम नहीं।
- भत्ता समस्याएं (LV02): भत्ते से अधिक स्वीकृत अवकाश के लिए HR से कैरी-ओवर या अवैतनिक दिनों की पुष्टि करनी होगी।
- अनुरोध: चिह्नित 'स्वीकृत' को मंजूरी दें; संघर्षों और ब्लैकआउट के लिए व्यक्ति के पास विकल्प ले जाएं, इनकार नहीं (LV04)।
- निष्पक्षता (LV05) और पीक दिनों की छुट्टी: जांचें कि कोई भी लगातार दो साल सबसे अच्छे दिन न पाए जबकि जिसने उन पर काम किया उसे मना कर दिया गया हो।
- नाजुकता: बिना अतिरिक्त वाले भूमिकाएं (LV03) और शून्य स्लैक वाले दिन (LV07) को एक नामित बैकअप की आवश्यकता होती है।
- उपयोग-या-खो दें (LV06): जो लोग दिन खो देंगे उन्हें शांत हफ्तों में बुक करना चाहिए।
### 4. समायोजित करें और फिर से चलाएं
समान फ़ाइलों के साथ स्पष्ट समाधान आज़माएं: एक क्रॉस-प्रशिक्षित बैकअप (किसी को एक भूमिका जोड़ें), शांत दिन पर कम न्यूनतम (ओवरराइड), दो लोगों के बीच स्वैप, या लंबी यात्रा को विभाजित करना। तब तक फिर से चलाएं जब तक स्वीकृत अवकाश में कोई अंतराल न हो और प्रत्येक खुले अनुरोध पर निर्णय या ठोस विकल्प न हो।
### 5. वितरित करें
`templates/leave-plan-brief.md` भरें: उपयोग किया गया नियम, प्रति अनुरोध निर्णय कारण और विकल्प के साथ, कैलेंडर, पतले दिनों के लिए कवरेज व्यवस्था और भत्ता नोट। योजना प्रकाशित करने से पहले उन लोगों से बात करें जिनके अनुरोध स्वीकृत नहीं हैं।
## उदाहरण परिणाम
सपोर्ट टीम उदाहरण पर ओमर की स्वीकृत यात्रा पहले से ही उसे उसके 25-दिन के भत्ते से 3 दिन अधिक ले जाती है (LV02, उच्च: HR को कैरी-ओवर या अवैतनिक दिनों की पुष्टि करनी चाहिए)। सैम ने पिछले क्रिसमस पर काम किया, इसलिए उसका 24 दिसंबर-4 जनवरी का अनुरोध पहले तय किया जाता है और स्वीकृत होता है; जोनास (21-23 दिसंबर) और प्रिया का आधा दिन और 21-23 दिसंबर भी स्वीकृत हैं। अडे का 29-31 दिसंबर टियर 2 और कुल कवरेज को 29 और 30 दिसंबर को कम छोड़ देगा; प्लानर इसके बजाय 16-18 दिसंबर प्रदान करता है। लीना का 30 दिसंबर-8 जनवरी 5-6 जनवरी के बिलिंग-रन ब्लैकआउट को पार करता है, और बिना अंतराल के केवल 31 दिसंबर को स्वीकृत किया जा सकता है। जोनास की दूसरी यात्रा के लिए 4 शेष के साथ 5 दिन चाहिए। लीना 5-दिन की कैरी-ओवर सीमा के कारण 10 दिन खो देगी, और 21-23 और 29 दिसंबर में बिल्कुल भी स्लैक नहीं है।
## नियम
- भूमिका द्वारा कवरेज की जांच एक व्यक्ति प्रति स्लॉट के साथ करें, केवल हेडकाउंट से नहीं।
- सभी पर एक बताया गया प्राथमिकता नियम लागू करें और क्रम दिखाएं; कभी भी यह तय न करें कि किसने सबसे जोर से पूछा।
- हमेशा "इस बार नहीं" के साथ एक वैकल्पिक तिथि, छोटी यात्रा या स्वैप जोड़ें।
- बीमार, पैतृक और अन्य संरक्षित अवकाश को निश्चित मानें: लोगों को इसे स्थानांतरित करने के लिए कभी न कहें, और चिकित्सा विवरण को योजना से बाहर रखें।
- भत्ते, कैरी-ओवर और अवैतनिक अवकाश अनुबंध और स्थानीय कानून का पालन करते हैं; उन्हें तय करने के बजाय HR के लिए चिह्नित करें।
- योजना केवल टीम और उन प्रबंधकों के साथ साझा करें जिन्हें इसकी आवश्यकता है।
## कवरेज नियम
### न्यूनतम
- प्रति **भूमिका** न्यूनतम निर्धारित करें (क्या कवर किया जाना चाहिए: लीड/अनुमोदक, टियर 2, ऑन-कॉल, फार्मासिस्ट, कुंजी धारक, फोर्कलिफ्ट लाइसेंस) और एक **कोई भी** कुल (मात्रा के लिए पर्याप्त हाथ)।
- संख्याओं को मांग पर आधारित करें, आदत पर नहीं: प्रति दिन टिकट/कॉल/ऑर्डर को एक व्यक्ति द्वारा संभाले जाने वाले से विभाजित करें, साथ ही वे भूमिकाएं जो कानून या अनुबंध द्वारा मौजूद होनी चाहिए।
- शांत दिन (24 और 31 दिसंबर, कई B2B टीमों में छुट्टियों के बीच का सप्ताह) आमतौर पर कम न्यूनतम को सही ठहराते हैं: उन तिथियों के लिए `coverage_overrides` का उपयोग करें न कि पूरी अवधि को कम करने के लिए।
- जिन दिनों व्यवसाय बंद है वे `holidays` में जाते हैं: कोई कवरेज आवश्यक नहीं, कोई भत्ता नहीं लिया जाता।
### एक व्यक्ति, एक स्लॉट
जो व्यक्ति कई भूमिकाएं रखता है वह अभी भी प्रति दिन केवल एक स्लॉट भरता है। प्लानर लोगों को द्विदलीय मिलान के साथ स्लॉट में निर्दिष्ट करता है, इसलिए यह व्यवस्था ढूंढता है जो काम करती है यदि कोई मौजूद है:
- माया (लीड, टियर 2) + लीना (टियर 1, टियर 2) लीड 1 + टियर 2 1 कवर कर सकते हैं, लेकिन लीड 1 + टियर 2 2 नहीं।
- कमियों की सूचना प्रति भूमिका दी जाती है; "कोई भी" कम का मतलब है कुल मिलाकर बहुत कम लोग।
### लोग
- `days`: पार्ट-टाइमर के कार्य सप्ताह के दिन; उनके गैर-कार्य दिवसों पर अवकाश कोई भत्ता खर्च नहीं करता और कवरेज को प्रभावित नहीं करता।
- आधे दिन (`half_day=yes`) भत्ते के 0.5 दिन खर्च करते हैं; प्लानर उस दिन कवरेज के लिए व्यक्ति की गणना नहीं करता (रूढ़िवादी)। यदि केवल सुबह की अनुपस्थिति अभी भी दोपहर की शिफ्ट को कवर करती है, तो ब्रीफ में कहें।
- प्रशिक्षण, बीमार, पैतृक और अवैतनिक अवकाश छुट्टी की तरह कवरेज कम करते हैं लेकिन भत्ते के खिलाफ नहीं गिने जाते।
### ऑन-कॉल और रिमोट कवरेज
- यदि कोई छुट्टी पर रहते हुए घर से ऑन-कॉल कवर करता है, तो उन्हें उस भूमिका के लिए छुट्टी पर चिह्नित न करें; इसे स्पष्ट रूप से सहमत करें और नीति के अनुसार भुगतान करें। प्लानर छुट्टी को पूरी तरह से बंद मानता है।
- एक बाहरी या एजेंसी बैकअप को केवल उस भूमिका वाले और बिना भत्ते वाले व्यक्ति के रूप में जोड़ा जा सकता है।
### स्लैक
- शून्य-स्लैक दिन (बिल्कुल न्यूनतम काम कर रहे) एक बीमार दिन के साथ टूट जाते हैं। अवधि शुरू होने से पहले ऐसे प्रत्येक दिन के लिए एक बैकअप नामित करें।
- केवल न्यूनतम + 1 धारकों वाली भूमिका दो अनुपस्थितियों से नहीं बच सकती; नए साल में क्रॉस-प्रशिक्षण की योजना बनाएं।
## उचित अवकाश निर्णय
### प्राथमिकता नियम (अनुरोध आने से पहले इसे बताएं)
सामान्य, बचाव योग्य क्रम:
1. पहले से स्वीकृत अवकाश खड़ा रहता है (जब तक व्यक्ति इसे बदलने के लिए सहमत न हो)।
2. संरक्षित अवकाश (पैतृक, चिकित्सा, वैधानिक) निश्चित है।
3. पीक दिनों के लिए: जिन्होंने पिछले साल पीक पर काम किया वे पहले जाते हैं।
4. फिर पहले आओ, पहले पाओ सबमिशन तिथि के अनुसार।
5. टाई: घुमाएं (जिसने आखिरी टाई खो दी वह इस बार जीतता है)।
पीक दिनों के लिए केवल वरिष्ठता-आधारित नियमों से बचें; वे नए कर्मचारियों को अनुचित लगते हैं और अक्सर अच्छे लोगों को खो देते हैं।
### ब्लैकआउट अवधि
- उन्हें छोटा और एक वास्तविक घटना से बंधा रखें (लॉन्च, बिलिंग रन, स्टॉक-टेक, ऑडिट, ट्यूशन व्यवसाय के लिए स्कूल टर्म की शुरुआत)। उन्हें पहले घोषित करें।
- एक अनुरोध जो ब्लैकआउट को छूता है वह स्वचालित रूप से अस्वीकार नहीं होता: प्लानर निकटतम गैप-मुक्त तिथियां या ब्लैकआउट से पहले का सबसे लंबा हिस्सा प्रदान करता है। अपवादों के लिए एक नामित अनुमोदक चाहिए।
### भत्ता और कैरी-ओवर
- शेष = भत्ता - अवधि से पहले लिया गया - स्वीकृत - अनुशंसित।
- भत्ते से अधिक स्वीकृत अवकाश (LV02 उच्च) एक HR प्रश्न है: पिछले साल से कैरी-ओवर, अतिरिक्त दिन खरीदना, या अवैतनिक अवकाश।
- कैरी-ओवर सीमाओं का मतलब है कि अबुक किए गए दिन वर्ष के अंत में खो जाते हैं (LV06): लोगों को शांत सप्ताह बुक करने के लिए प्रोत्साहित करें; कुछ देशों में वैधानिक न्यूनतम अवकाश आसानी से समाप्त नहीं हो सकता, इसलिए स्थानीय नियमों की जांच करें।
### अच्छी तरह से 'नहीं' कहना
- नियम और विशिष्ट अंतराल समझाएं ("टियर 2 29 और 30 दिसंबर को एक व्यक्ति कम होगा")।
- प्लानर द्वारा पाया गया विकल्प, या एक नामित सहकर्मी के साथ स्वैप, या छोटी यात्रा प्रदान करें।
- जल्दी निर्णय लें (छुट्टियों से कम से कम 4-6 सप्ताह पहले) ताकि लोग अभी भी यात्रा बुक कर सकें।
- निर्णय और कारण रिकॉर्ड करें; अगले साल की प्राथमिकता इस पर निर्भर करती है।
### निष्कर्ष कोड (leave_cover.py)
| कोड | गंभीरता | अर्थ |
|---|---|---|
| LV01 | उच्च | स्वीकृत अवकाश पहले से ही एक भूमिका या कुल को एक दिन कम छोड़ता है |
| LV02 | उच्च / मध्यम | स्वीकृत अवकाश भत्ते से अधिक / अनुरोध को शेष से अधिक दिन चाहिए |
| LV03 | मध्यम / निम्न | भूमिका न्यूनतम से अधिक लोगों द्वारा नहीं रखी गई / केवल एक अतिरिक्त |
| LV04 | मध्यम | अनुरोध कवरेज या ब्लैकआउट के साथ संघर्ष करता है, प्रस्तावित विकल्प के साथ |
| LV05 | मध्यम | जिसने पिछले साल पीक पर काम किया उसे मना कर दिया गया जबकि अन्य को पीक दिन मिलते हैं |
| LV06 | निम्न | कैरी-ओवर सीमा से ऊपर शेष दिन खो जाएंगे |
| LV07 | जानकारी | योजना के बाद शून्य स्लैक वाले दिन |
## अवकाश अवकाश योजना टेम्पलेट
# अवकाश अवकाश योजना: <टीम>, <अवधि>
**कवरेज नियम:** <लीड 1, टियर 2 2 (24/31 दिसंबर को 1), ऑन-कॉल 1, कम से कम 4 लोग> **बंद:** <25 दिसंबर, 28 दिसंबर, 1 जनवरी>
**प्रयुक्त प्राथमिकता:** <पिछले साल पीक पर काम करने वाले पहले, फिर पहले आओ पहले पाओ>
**ब्लैकआउट:** <5-6 जनवरी बिलिंग रन>
## निर्णय
| व्यक्ति | अनुरोधित | निर्णय | क्यों | प्रस्तावित विकल्प |
|---|---|---|---|---|
| <सैम> | <24 दिसंबर - 4 जनवरी> | <स्वीकृत> | <पिछले क्रिसमस पर काम किया> | - |
| <अडे> | <29-31 दिसंबर> | <ये तिथियां नहीं> | <टियर 2 29-30 दिसंबर को कम> | <16-18 दिसंबर, या ... के साथ स्वैप> |
## कैलेंडर
```
<paste the planner grid>
```
## पतले दिन और बैकअप
| दिन | पतला क्यों | बैकअप |
|---|---|---|
| <21-23 दिसंबर> | <बिल्कुल 4 काम कर रहे> | <घर से ऑन-कॉल: ...> |
## भत्ता नोट (HR के लिए)
- <ओमर: स्वीकृत यात्रा भत्ते से 3 दिन अधिक - कैरी-ओवर / अवैतनिक की पुष्टि करें>
- <लीना: 15 दिन शेष, कैरी-ओवर सीमा 5 - फरवरी बुक करने का सुझाव दें>
## अगले कदम
- <प्रकाशित करने से पहले अडे, लीना, जोनास से बात करें>
- <योजना को ... पर प्रकाशित करें; ... को स्वैप अनुरोध द्वारा परिवर्तन>
- <Q1 में दूसरे लीड बैकअप को क्रॉस-प्रशिक्षित करें>
## उदाहरण टीम JSON
{
"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}
]
}
## उदाहरण अवकाश CSV
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,,
## उदाहरण प्लानर आउटपुट
# अवकाश कवरेज योजना: Northwind Customer Support (2026-12-14 से 2027-01-08)
प्रति कार्यदिवस न्यूनतम कवरेज: कोई भी 4, लीड 1, ऑनकॉल 1, टियर2 2। छुट्टियां (बंद): 25 दिसंबर, 28 दिसंबर, 01 जनवरी।
**निर्णय: स्वीकृत अवकाश पर भत्ते से अधिक** - 7 खुले अनुरोधों में से 4 बिना अंतराल के स्वीकृत किए जा सकते हैं; 3 को निर्णय की आवश्यकता है।
## अनुरोध (निर्णय क्रम में: पिछले साल पीक पर काम करने वाले पहले, फिर पहले आओ पहले पाओ)
| # | व्यक्ति | तिथियां | कार्यदिवस | प्रकार | सबमिट किया | निर्णय | क्यों | विकल्प |
|---|---|---|---|---|---|---|---|---|
| 1 | सैम | गुरु 24 दिसंबर - सोम 04 जनवरी | 5 | vacation | 2026-11-05 | स्वीकृत | - | - |
| 2 | जोनास | सोम 21 दिसंबर - बुध 23 दिसंबर | 3 | vacation | 2026-10-20 | स्वीकृत | - | - |
| 3 | अडे | मंगल 29 दिसंबर - गुरु 31 दिसंबर | 3 | vacation | 2026-10-25 | संघर्ष | कोई भी/टियर2 29 दिसंबर, 30 दिसंबर को कम | बुध 16 दिसंबर से शुक्र 18 दिसंबर |
| 4 | लीना | बुध 30 दिसंबर - शुक्र 08 जनवरी | 7 | vacation | 2026-11-12 | ब्लैकआउट | जनवरी बिलिंग रन, सभी हाथ (05 जनवरी, 06 जनवरी) | गुरु 31 दिसंबर - गुरु 31 दिसंबर (1 कार्यदिवस, ब्लैकआउट से पहले वापस) तक छोटा करें |
| 5 | प्रिया | बुध 16 दिसंबर - बुध 16 दिसंबर (आधा) | 0.5 | vacation | 2026-11-20 | स्वीकृत | - | - |
| 6 | जोनास | सोम 04 जनवरी - शुक्र 08 जनवरी | 5 | vacation | 2026-11-25 | भत्ते से अधिक | 5 दिन चाहिए, 4 शेष | - |
| 7 | प्रिया | सोम 21 दिसंबर - बुध 23 दिसंबर | 2 | vacation | 2026-11-28 | स्वीकृत | - | - |
## कैलेंडर (स्वीकृत + अनुशंसित)
```
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 स्वीकृत vacation/training/sick/parental/unpaid/other, lowercase = अनुमोदन के लिए अनुशंसित अनुरोध, h = आधा दिन, x = अनुरोध स्वीकृत नहीं, H = छुट्टी, - = कार्य दिवस नहीं, + / ! = भूमिका कवर / कम।
## भत्ता (दिन)
| व्यक्ति | भत्ता | पहले लिया | यहां स्वीकृत | अनुशंसित | शेष | पीक दिनों की छुट्टी |
|---|---|---|---|---|---|---|
| माया | 25 | 17 | 4 | 0 | 4 | 4 |
| अडे | 25 | 19 | 0 | 0 | 6 | 0 |
| ओमर | 25 | 20 | 8 | 0 | -3 | 3 |
| लीना | 25 | 10 | 0 | 0 | 15 | 0 |
| जोनास | 25 | 18 | 0 | 3 | 4 | 3 |
| सैम | 25 | 15 | 0 | 5 | 5 | 4 |
| प्रिया | 15 | 9 | 0 | 2.5 | 3.5 | 2 |
## निष्कर्ष
- **उच्च LV02** ओमर का स्वीकृत अवकाश भत्ते से 3 दिन अधिक है (20 + 8 में से 25 लिया); HR के साथ कैरी-ओवर या अवैतनिक अवकाश की पुष्टि करें।
- **मध्यम LV02** जोनास 04 जनवरी-08 जनवरी: 5 दिन चाहिए, 4 शेष; छोटी यात्रा स्वीकृत करें या अवैतनिक दिनों पर सहमत हों
- **मध्यम LV04** अडे 29 दिसंबर-31 दिसंबर: संघर्ष (कोई भी/टियर2 29 दिसंबर, 30 दिसंबर को कम); बुध 16 दिसंबर से शुक्र 18 दिसंबर की पेशकश करें
- **मध्यम LV04** लीना 30 दिसंबर-08 जनवरी: ब्लैकआउट (जनवरी बिलिंग रन, सभी हाथ (05 जनवरी, 06 जनवरी)); गुरु 31 दिसंबर - गुरु 31 दिसंबर (1 कार्यदिवस, ब्लैकआउट से पहले वापस) तक छोटा करने की पेशकश करें
- **निम्न LV03** लीड के पास एक अतिरिक्त व्यक्ति है (माया, अडे न्यूनतम 1 के लिए); एक साथ दो अनुपस्थितियां इसे तोड़ देती हैं।
- **निम्न LV06** अडे के पास अभी भी 6 दिन शेष होंगे (कैरी-ओवर सीमा 5); 1 दिन खो जाएंगे जब तक बुक नहीं किए जाते।
- **निम्न LV06** लीना के पास अभी भी 15 दिन शेष होंगे (कैरी-ओवर सीमा 5); 10 दिन खो जाएंगे जब तक बुक नहीं किए जाते।
- **जानकारी LV07** 4 दिनों पर कोई स्लैक नहीं (21 दिसंबर, 22 दिसंबर, 23 दिसंबर, 29 दिसंबर): एक बीमार दिन कवरेज तोड़ता है; बैकअप तैयार करें।
## Python प्लानर स्क्रिप्ट
```python
#!/usr/bin/env python3
"""टीम अवकाश और अवकाश कवरेज प्लानर (केवल Python 3.8+ मानक पुस्तकालय)।
टीम के अवकाश अनुरोधों की न्यूनतम स्टाफिंग नियमों के विरुद्ध दिन-प्रतिदिन जांच करता है, तय करता है कि कौन से
अनुरोधों को बिना अंतराल छोड़े स्वीकृत किया जा सकता है, बाकी के लिए वैकल्पिक तिथियां सुझाता है, और
भत्ते और पीक अवकाश दिन किसे मिलते हैं, ट्रैक करता है।
टीम JSON:
{
"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"], (कार्यालय बंद, कोई कवरेज आवश्यक नहीं)
"coverage": {"lead": 1, "tier2": 2, "oncall": 1, "any": 4}, ("any" = कुल काम करने वाले लोग)
"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}
]
}
प्रत्येक काम करने वाला व्यक्ति प्रति दिन ONE भूमिका स्लॉट भरता है (एक लीड जो टियर2 भी है एक बार गिना जाता है), हल किया गया
द्विदलीय मिलान के रूप में।
अवकाश CSV (हेडर आवश्यक): person,start,end,type,status,submitted,half_day,notes
type vacation (भत्ते के खिलाफ गिना जाता है) | sick | training | parental | unpaid | other
status approved | requested | declined (अनदेखा)
half_day yes = आधे दिन का भत्ता; व्यक्ति उस दिन कवरेज के लिए नहीं गिना जाता
उपयोग:
python3 leave_cover.py team.json leave.csv [--json]
python3 leave_cover.py --self-test
निकास कोड: 0 = स्वीकृत अवकाश कवरेज बनाए रखता है और कोई भी भत्ते से अधिक नहीं है, 1 = स्वीकृत अवकाश
पहले से ही कवरेज तोड़ता है या भत्ते से अधिक है, 2 = खराब इनपुट।
"""
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 YYYY-MM-DD नहीं है" % (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("सीमा %r शुरू होने से पहले समाप्त होती है" % spec)
return [a + dt.timedelta(days=i) for i in range((b - a).days + 1)]
# ----------------------------------------------------------------------------- इनपुट
def load_team(cfg):
if "period" not in cfg or not cfg.get("people"):
raise InputError("टीम JSON को 'period' और '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("हर व्यक्ति को एक नाम चाहिए")
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("कवरेज भूमिका %r किसी के पास नहीं है" % 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("अवकाश पंक्ति %d: अज्ञात व्यक्ति %r" % (n, name))
typ = (row.get("type") or "vacation").strip().lower()
if typ not in TYPE_CODE:
raise InputError("अवकाश पंक्ति %d: प्रकार इनमें से एक होना चाहिए %s" % (n, ", ".join(TYPE_CODE)))
status = (row.get("status") or "requested").strip().lower()
if status not in ("approved", "requested", "declined"):
raise InputError("अवकाश पंक्ति %d: स्थिति approved, requested या declined होनी चाहिए" % n)
start, end = d(row.get("start"), "start"), d(row.get("end") or row.get("start"), "end")
if end < start:
raise InputError("अवकाश पंक्ति %d: शुरू से पहले समाप्त" % 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
# ----------------------------------------------------------------------------- कोर
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):
"""एक दिन के लिए {भूमिका: कमी} लौटाएं, छुट्टी पर लोगों के सेट को देखते हुए।"""
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 = {} # स्लॉट इंडेक्स -> व्यक्ति का नाम
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):
"""उसी अनुरोध का निकटतम बदलाव (समान कैलेंडर लंबाई) जो कवरेज रखता है।"""
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):
"""ब्लैकआउट से टकराने वाले अनुरोध का सबसे लंबा हिस्सा, पहले ब्लैकआउट दिन से पहले, जो कवरेज रखता है।"""
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: स्वीकृत अवकाश पहले से ही %s कम छोड़ता है" % (
day.isoformat(), DAYS[day.weekday()].title(), ", ".join("%s %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="छुट्टियों / गैर-कार्य दिवसों पर पड़ता है")
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="%g दिन चाहिए, %g शेष" % (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 %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 से %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"] = "%s - %s (%d कार्यदिवस, ब्लैकआउट से पहले वापस) तक छोटा करें" % (
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 ने पिछले साल पीक पर काम किया लेकिन यह अनुरोध %s है जबकि %s को पीक दिनों की छुट्टी मिलती है; "
"स्वैप करने पर विचार करें।" % (l["person"], dec["decision"], ", ".join(sorted(lucky)) or "कोई नहीं"))
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", "%s का स्वीकृत अवकाश भत्ते से %g दिन अधिक है (%g + %g में से %g लिया); "
"HR के साथ कैरी-ओवर या अवैतनिक अवकाश की पुष्टि करें।" % (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 के पास अभी भी %g दिन शेष होंगे (कैरी-ओवर सीमा %g); %g दिन खो जाएंगे जब तक बुक नहीं किए जाते।"
% (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", "केवल %d लोग %s कवर कर सकते हैं (न्यूनतम %d): %s द्वारा कोई भी अनुपस्थिति कवरेज तोड़ती है; "
"बैकअप को क्रॉस-प्रशिक्षित करें।" % (len(holders), role, k, ", ".join(holders)))
elif len(holders) == k + 1:
add("low", "LV03", "%s के पास एक अतिरिक्त व्यक्ति है (%s न्यूनतम %d के लिए); एक साथ दो अनुपस्थितियां इसे तोड़ देती हैं।"
% (role, ", ".join(holders), k))
for day, s in g_final.items():
if day not in g_approved:
add("high", "LV01", "%s निर्णयों के बाद भी कम: %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"],
("; " + dec["suggestion"] + " की पेशकश करें") if dec["suggestion"] else "; 15 दिनों के भीतर कोई गैप-मुक्त विकल्प नहीं"))
elif dec["decision"] == "over allowance":
l = dec["leave"]
add("medium", "LV02", "%s %s-%s: %s; छोटी यात्रा स्वीकृत करें या अवैतनिक दिनों पर सहमत हों" % (
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", "%d दिनों पर कोई स्लैक नहीं (%s): एक बीमार दिन कवरेज तोड़ता है; बैकअप तैयार करें।" % (
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}
# ----------------------------------------------------------------------------- रिपोर्ट
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 = ["# अवकाश कवरेज योजना: %s (%s)" % (r["team"], r["period"].replace("..", " से")), ""]
need = ", ".join("%s %d" % (k, v) for k, v in sorted(team["coverage"].items()))
L.append("प्रति कार्यदिवस न्यूनतम कवरेज: %s। छुट्टियां (बंद): %s।" % (
need, ", ".join(x.strftime("%d %b") for x in sorted(team["holidays"])) or "कोई नहीं"))
n_appr = sum(1 for dec in r["decisions"] if dec["decision"] == "approve")
verdict = ("स्वीकृत अवकाश पहले से ही कवरेज तोड़ता है" if r["gaps_approved"] else
"स्वीकृत अवकाश पर भत्ते से अधिक" if any(f["code"] == "LV02" and f["severity"] == "high" for f in r["findings"])
else "कवर")
L += ["", "**निर्णय: %s** - %d में से %d खुले अनुरोध बिना अंतराल के स्वीकृत किए जा सकते हैं; %d को निर्णय की आवश्यकता है।" % (
verdict, n_appr, len(r["decisions"]), len(r["decisions"]) - n_appr), ""]
L += ["## अनुरोध (निर्णय क्रम में: पिछले साल पीक पर काम करने वाले पहले, फिर पहले आओ पहले पाओ)", "",
"| # | व्यक्ति | तिथियां | कार्यदिवस | प्रकार | सबमिट किया | निर्णय | क्यों | विकल्प |",
"|---|---|---|---|---|---|---|---|---|"]
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"), " (आधा)" 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 += ["", "## कैलेंडर (स्वीकृत + अनुशंसित)", "", "```"] + grid(team, r["current"], leaves) + [
"```", "V/T/S/P/U/O स्वीकृत vacation/training/sick/parental/unpaid/other, lowercase = अनुमोदन के लिए अनुशंसित "
"अनुरोध, h = आधा दिन, x = अनुरोध स्वीकृत नहीं, H = छुट्टी, - = कार्य दिवस नहीं, "
"+ / ! = भूमिका कवर / कम।", ""]
L += ["## भत्ता (दिन)", "", "| व्यक्ति | भत्ता | पहले लिया | यहां स्वीकृत | अनुशंसित | शेष | पीक दिनों की छुट्टी |",
"|---|---|---|---|---|---|---|"]
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 += ["", "## निष्कर्ष", ""]
if not r["findings"]:
L.append("- कोई नहीं")
for f in r["findings"]:
L.append("- **%s %s** %s" % (f["severity"].upper(), f["code"], f["message"]))
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------- स्व-परीक्षण
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("सप्ताहांत और छुट्टियां कार्यदिवस नहीं हैं", 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("एक व्यक्ति एक स्लॉट भरता है (A लीड और dev नहीं हो सकता)", sf.get("any") == 1 and sf.get("lead", 0) + sf.get("dev", 0) == 1, sf)
ok("मिलान A=dev, C=lead पाता है", shortfall(t, d("2026-12-16"), {"B", "D"}) == {})
ok("पार्ट-टाइमर केवल अपने दिनों पर गिना जाता है", 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("अवकाश दिन गैर-कार्य दिवस छोड़ देते हैं", len(leave_days(t, lv)) == 2)
ok("आधा दिन 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 A के स्वीकृत अवकाश से संघर्ष करता है (कोई लीड नहीं)", by["C"]["decision"] == "conflict", by["C"])
ok("संघर्ष को एक वैकल्पिक तिथि मिलती है", by["C"]["suggestion"] is not None, by["C"])
ok("B भत्ते से अधिक (3 दिन, 1 शेष)", by["B"]["decision"] == "over allowance", by["B"])
ok("D प्रशिक्षण स्वीकृत", by["D"]["decision"] == "approve", by["D"])
ok("पीक-कार्य करने वाला व्यक्ति पहले तय किया गया", r["decisions"][0]["leave"]["person"] == "C", [x["leave"]["person"] for x in r["decisions"]])
codes = {f["code"] for f in r["findings"]}
ok("LV03 लीड के लिए विफलता का एकल बिंदु", "LV03" in codes, codes)
ok("LV05 C के लिए निष्पक्षता ध्वज", "LV05" in codes, codes)
ok("स्वीकृत अवकाश से कोई अंतराल नहीं", 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 स्वीकृत अवकाश में अंतराल", 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("ब्लैकआउट कवरेज से पहले आता है", 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 ओवरलैप का पता लगाता है", blackout_hit(t2, lvb)[0] == [d("2026-12-16")])
try:
load_team(dict(base, coverage={"qa": 1}))
ok("अज्ञात कवरेज भूमिका अस्वीकृत", False)
except InputError:
ok("अज्ञात कवरेज भूमिका अस्वीकृत", True)
rep = report(t, leaves, r)
ok("रिपोर्ट में ग्रिड और निर्णय है", "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 पास" % (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="न्यूनतम कवरेज के विरुद्ध टीम अवकाश की योजना बनाएं।")
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("error: %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())
```
पूरा प्रॉम्प्ट देखने के लिए साइन इन करें
Continue with:
By logging in, you agree to our Terms of Use and Privacy Policy
उपयोग
यह प्रॉम्प्ट business के साथ उपयोग के लिए डिज़ाइन किया गया है। ऊपर प्रॉम्प्ट सामग्री कॉपी करें और अपने पसंदीदा AI टूल में पेस्ट करें।
सर्वोत्तम परिणामों के लिए, आप अपनी विशिष्ट आवश्यकताओं के अनुसार प्लेसहोल्डर (वर्ग कोष्ठक या बड़े अक्षरों में दर्शाए गए) को अनुकूलित कर सकते हैं।
चर्चा
0 टिप्पणियाँ