سؤالی که یک نقشه‌حرارتی باید صادقانه جواب بدهد

«آیا کسی هفته‌ی بعد زیربار زیادی است؟» به‌نظر می‌رسد باید از فهرست تسک راحت جواب داده شود — تسک‌ها را به‌ازای هر نفر بشمار، تمام. اما تسکی با تخمین ۸ساعته که ۷۵ درصد کامل است، ۸ ساعت کار باقیمانده ندارد، و تسکی که سه هفته کش می‌آید کاملاً متعلق به هفته‌ای که اتفاقاً سررسیدش هست نیست. هرکدام را اشتباه بگیرید و نقشه‌حرارتی یا سر آدم‌هایی که واقعاً حالشان خوب است گرگ‌گرگ می‌کند، یا کسی که واقعاً غرق شده را جا می‌اندازد.

مرحله‌ی اول: کار باقیمانده، نه کار کل

WKFGo از چیزی که واقعاً باقی مانده شروع می‌کند، نه چیزی که در ابتدا تخمین زده شده بود:

remaining := task.EstimatedTime * (1 - task.Progress/100)

تسکی با تخمین ۲۰ساعته و علامت‌زده‌شده ۷۵ درصد تمام، ۵ ساعت باقیمانده به نقشه‌حرارتی می‌دهد، نه ۲۰. همین یک خط تفاوت بین نقشه‌حرارتی‌ای است که واقعیت را بازتاب می‌دهد و یکی که فقط جدول تخمین‌تان را دوباره نمایش می‌دهد — پیگیری پیشرفت فقط اگر واقعاً عدد را تغییر دهد اینجا اهمیت دارد.

مرحله‌ی دوم: پخش بین همه‌ی مسئولین

اگر یک تسک دو مسئول داشته باشد، هیچ‌کدام کل تخمین باقیمانده را حمل نمی‌کند — به‌طور مساوی تقسیم می‌شود:

share := remaining / float64(len(visible))

این یک ساده‌سازی است (فرض می‌کند سهم مساوی است، که همیشه درست نیست)، اما یک ساده‌سازیِ صادقانه است: جایگزینش — نشان‌دادن کل تخمین باقیمانده زیر هر مسئول — همان ساعت‌ها را دوبار می‌شمرد و ظرفیت کل تیم را همان لحظه‌ای که بیش از یک نفر یک تسک را به‌اشتراک بگذارد بدتر از واقعیت نشان می‌دهد.

مرحله‌ی سوم: پخش روی هفته‌هایی که واقعاً کش می‌آید

تسکی که سه هفته دیگر سررسید دارد، کل سهم باقیمانده‌اش را در همان یک هفته خالی نمی‌کند — به‌طور مساوی روی بازه از زمانی که کار می‌تواند شروع شود (الان، یا یک تاریخ شروع آینده) تا سررسید پخش می‌شود:

spanLen := endIdx - startIdx + 1
perWeek := share / float64(spanLen)

این همان بخشی است که یک نقشه‌حرارتیِ ساده‌لوحانه‌ی «گروه‌بندی بر اساس تاریخ سررسید» اشتباه می‌گیرد — سه هفته‌ی آرام و بعد یک جهش نگران‌کننده نشان می‌دهد، وقتی تصویر صادقانه یک بار پیوسته پخش‌شده روی هر سه هفته است. تسک‌های سررسیدگذشته تنها استثنای عمدی هستند: کل سهم باقیمانده‌شان کاملاً در هفته‌ی فعلی می‌نشیند، چون «سررسیدگذشته» از قبل یعنی پنجره‌ی پخش‌شدن به الان جمع شده.

مرحله‌ی چهارم: تبدیل ساعت‌ها به یک سیگنال، نه فقط یک عدد

ساعت‌های تخصیص‌یافته‌ی خام فقط کنار یک پایه‌ی ظرفیت معنا می‌دهند:

cell.Utilization = int(math.Round(cell.Allocated / capacity * 100))

ظرفیت هفتگیِ پیش‌فرض ۴۰ ساعت به‌ازای هر نفر است، با امکان تغییر به‌ازای هر استقرار از طریق WORKLOAD_WEEKLY_CAPACITY — چون هر تیمی هفته‌ی ۴۰ساعته کار نمی‌کند، و سخت‌کدکردن این فرض هر همکار پاره‌وقت یا تیم چهارروزه را بی‌صدا اشتباه «زیربار» برچسب می‌زد.

چیزی که کنار گذاشته می‌شود، و چرا اهمیت دارد

کوئری پشت این فقط تسک‌های هنوز بازی را می‌گیرد — progress < 100 AND status <> 'done' — و فقط تخصیص‌هایی را می‌شمارد که هنوز در سطح کاربر-تسک علامت‌زده نشده‌اند تمام. تسکی که کسی سهم خودش را تمام کرده اما هنوز برای هم‌مسئولش باز است، حساب کسی که کارش را تمام کرده نمی‌شود. و تسکی با بدون تخمین زمانی بی‌صدا صفر به بار کسی اضافه نمی‌کند — جداگانه به‌عنوان شمار Unestimated به‌ازای هر نفر پیگیری می‌شود، مشخصاً برای اینکه «این آدم انگار حالش خوبه» بی‌صدا به معنای «این آدم دوازده تسک بدون تخمین دارد که ریاضی نمی‌تواند ببیندشان» نباشد.

ترتیب مرتب‌سازی بخشی از طراحی است، نه یک فکر بعدی

نقشه‌حرارتی افراد را بر اساس بهره‌وریِ هفته‌ی فعلی، پرتحمل‌ترین اول، مرتب می‌کند:

sort.SliceStable(hm.Users, func(i, j int) bool {
    return hm.Users[i].Weeks[0].Utilization > hm.Users[j].Weeks[0].Utilization
})

شخصی که همین‌هفته باید رویش عمل کنید اولین ردیفی است که می‌بینید — ابزار حول «همین‌الان چه‌کسی نیاز به توجه دارد» ساخته شده، نه یک فهرست الفبایی که باید مرورش کنید.

اشتباهات رایج

گروه‌بندی کل کار باقیمانده کاملاً زیر تاریخ سررسید تسک. این همان الگوی نادرست جهش-بعد-آرامش را تولید می‌کند که تسک‌های طولانی را تا هفته‌ی سررسیدشان به‌طرز فریبنده‌ای بی‌ضرر نشان می‌دهد، وقتی کار در واقع تمام آن مدت در جریان بوده.

شمردن تخمین کامل تسک صرف‌نظر از پیشرفت. نقشه‌حرارتی‌ای که پیشرفت را نادیده می‌گیرد فقط یک جدول تخمین با چند مرحله‌ی اضافه است — یک تیم را که واقعاً دارد به بک‌لاگش می‌زند بازتاب نمی‌دهد.

فرض یک هفته‌ی ۴۰ساعتیِ ثابت برای هر تیم. پیمانکاران، کارکنان پاره‌وقت، و تیم‌های هفته‌ی چهارروزه هرکدام به یک پایه‌ی ظرفیت متفاوت نیاز دارند، یا «زیربار» شروع می‌کند به معنی هیچ‌چیز.

رفتار خاموش با تسک‌های بدون تخمین به‌عنوان بار صفر. این «بار صفر» نیست، «نمی‌دانیم» است — فروپاشاندن این دو در یک عدد دقیقاً همان تسک‌هایی را که بیشترین احتمال منفجرشدنِ غیرمنتظره را دارند پنهان می‌کند.

سؤالات متداول

نقشه‌حرارتی چقدر به آینده نگاه می‌کند؟

قابل‌تنظیم، بین ۱ تا ۲۶ هفته محدود شده — به‌قدری کوتاه که دقیق بماند (تخمین‌های دورتر به‌هرحال حدس‌اند)، به‌قدری بلند که یک تصمیم استخدام یا واگذاری مجدد را از قبل ببینید.

آیا تسکی با سه مسئول سه‌قسمت تقسیم می‌شود حتی اگر یک نفر بیشتر کار واقعی را انجام دهد؟

بله — تقسیم مساوی یک ساده‌سازیِ شناخته‌شده است؛ همان بده‌بستانی است که اکثر ابزارهای سبکِ برنامه‌ریزی ظرفیت انجام می‌دهند به‌جای نیازمندی به تخمین درصدی به‌ازای هر مسئول که کسی نمی‌خواهد نگهش دارد.

بعد از تمام‌شدن یک تسک، هفته‌ی بعد چه اتفاقی برای بارش می‌افتد؟

هیچ‌چیز — کوئری زیرین فقط تسک‌های باز را می‌گیرد، پس یک تسک تمام‌شده به‌سادگی از هر محاسبه‌ی نقشه‌حرارتیِ آینده ناپدید می‌شود؛ نیازی به یک مرحله‌ی جدای «پاک‌کردن بار» نیست.

جمع‌بندی

یک عدد بار کاری فقط زمانی مفید است که ریاضی پشتش با نحوه‌ی واقعیِ اتفاق‌افتادن کار یکی باشد — باقیمانده، نه کل؛ تقسیم‌شده بین کسانی که واقعاً رویش هستند؛ پخش‌شده روی زمانی که واقعاً در جریان است، نه فقط زمان سررسید. هرکدام را رد کنید، نقشه‌حرارتی هنوز یک عدد تولید می‌کند، فقط عددی است که هیچ‌کس نباید بهش اعتماد کند.