Web 版排班表小工具

vrd-0

最近的這一整個月幾乎都在做這個小工具 — VGHKS Random Duty Generator。它是一個 web application,目的是輔助敝科住院醫師每一到兩個月的排班流程,以期減少班表的不平均程度以及 QOD 班的次數。(注意:就目前的流程可能不適用於其他單位的使用)

GitHub: https://github.com/tsaiid/vghks-random-duty
Demo Site: http://radtools.tsai.it/vghks-random-duty/

敝料目前排班方式

為了公平起見,我們以計算點數的方式來產生班別,規定星期五的班為 1 點,假日班(含六、日、國定假日)為 2 點,平日班不計點,而如果有國定假日,其前一天也當成星期五算做 1 點。以今年十月的月曆(如下圖)來看:

calendar-2015-10

共有 10 個假日班(包含 10/09 雙十節的補假),5 個星期五班(10/08 雖為星期四,但做為假日前一天,列入 1 點計算),如果有 4 個人加入排班(好像有點辛苦 ^^a),下列的排法可能是某個可以接受的公平狀態:

suggested_patterns

這樣算起來,點數和班數的狀態還蠻平均的。然後依序將週五和假日班填好,其餘平日班就交給總醫師去處理。

人工排班可能產生的問題

  1. 費時費力
  2. 容易有不均的情形

人工排班雖然也不會花很多時間,但畢竟還是要費一些心力,而且如果要排出「均勻一點」的班表也不是那麼容易,所謂均勻一點大概是不要把班都集中在前半月、後半月,或是常常有 QOD(兩天值一班)的情況,這時候就會想用電腦來輔助一下。

開始重做吧

回想當年在馬袓當醫官的時候,也是為了排班的作業,用 Excel 做了一個小工具,可惜因為保密規定沒辦法帶出來。雖然排法不同無法直接套用,但總是多少可以省一點力氣吧!

要重做的話就得選個適當的平台:

Excel

以前用 Excel 是因為沒有其他的選擇,現在的排班表也是寫在 Excel 裡的,其實對大家來說,或許轉換和適應的難度較低(例如之前也曾經做過 Excel 中的亂數序號產生器),不過就排班這件事來說,在 Excel 裡要花不少工夫來做萬年曆的部份,有點麻煩,而且如果只針對特定 cell 來取值,也不容易確保資料正確性。另外,Excel 得用 VBScript 來寫,得額外設安全性,也是讓我比較抗拒一些。

Web

Web 平台有一些現成的日曆套件可用,所以要克服的就是模擬 Excel 那種 Cell 的概念來做班表的生成、排序和調整,幸好這不是太困難(因為日曆套件設計得很完整)。它的好處在於可以跨平台,而且可以遠端使用,另外,javascript 和 HTML DOM 的操作也是我近幾年比較熟的,比起重新去熟悉 VBScript,好上手得多。

使用方法

1. 選擇月份模式並移至欲排班的月份(預設是下個月)

vrd-2

2. 檢査月曆中的假日是否有誤(Google 提供,與人事行政局的有一點落差),可點選做刪除的動作,或新增自訂假日

vrd-4

3. 設定人數

vrd-1

4. 檢査建議班表是否可以接受,可點選「更新建議班表」隨機調整或手動修改

vrd-3

5. 依次填入班表,直到建議班表中「五」、「假」的紅字皆消除

6. 填入班表時,也可設定不値班的入員,自動排班時將會被排除

7. 選擇是否開啓「QOD 上限」或「Std Dev」,並設定其數值

vrd-5

8. 點選「自動排班」,等候亂數班表產生

vrd-6

9. 若無法產生結果,可重新執行,或調降、關閉參數

vrd-7

vrd-8

設計的細節

目前用到的前端元件有:

  • Bootstrap — 主要的 css style
  • Bootstrap Switch — 用來美化 checkbox, radio 的套件
  • BootstrapDialog — 用來客製化 bootstrap modal 的套件
  • Font Awesome — 拿一些小 icon font 出來用
  • jQuery UI — 用了 slider, modal 等等小工具,以及用在 calendar 上的 theme
  • jQuery UI Slider Pips — 加強版的 jQuery UI Slider
  • FullCalendar — 畫月曆的工具,也是最重要的一部份,以 Ajax 的方式進行互動,也可以 drag and drop,已經是相當成熟的工具了
  • jQuery BlockUI — 用於顯示正在排班中的工具,另外它也提供 Growl style 的提示方式
  • CryptoJS — 用來算 MD5 hash 的 javascript lib
  • ExcellentExport.js — 可以把 html table 轉成 excel 格式輸出
  • jQuery contentEditable — 讓 HTML5 中的 contentEditable 屬性可以有 callback 可以呼叫

後端的邏輯是:

  1. 將設定好的星期五班、假日班,該月星期五和假日的分佈,有哪一些限制(例如不可連值、不可超過多少 QOD 班,班距不可過大…)傳到 function 中。
  2. 將每個人平日班的數量算好,然後打亂。
  3. 檢查是否符合限制,如果不合,就再打亂。
  4. 經過成千上萬次的打亂後,終於有一個符合條件的結果。

Web Worker 讓大量的迴圈不會 block 住整個 UI

最早之前的 Excel 版排班工具和 Excel datasheet 是跑在同一個 thread 裡的,所以運作時整個 Excel 會像當掉一樣,完全沒辦法動作,不過因為不那麼複雜,不會卡太久,感覺比較不明顯;一開始我用 Web 做的版本也是跑在 main thread 裡 (javascript 預設就是在 main thread 裡執行),因此在出現跑不出結果的狀況時,會把整個瀏覽器卡住,也沒辦法中途停止,這樣的 user experience 實在太差,幸好現代的瀏覽器有支援 web worker 這樣的 multi-threading 機制,才讓這個小工具有繼續開發下去的可能性。

Web Worker 不像 jQuery 那樣,還有一個支援 IE6, 7, 8 的 1.x 版,為了用它也只好犧牲舊瀏覽器了(都什麼年代了還在考慮要支援舊 IE,真是辛苦喔)。

支援 Web Worker 需使用下列版本以上的瀏覽器:

Web 版排班表小工具 1
Chrome
≥ 4.0
Web 版排班表小工具 2
Firefox
≥ 3.5
Web 版排班表小工具 3
Internet Explorer
≥ 10.0

決定以標準差代表班距的平均程度

當初在考慮「班距平均度」的解法時有兩個想法,第一是去計算標準差,第二是用 Entropy (Shannon) 的概念來代表。其實兩種模式都可以量化數列混亂的程度,例如:

duty_intervals_1 = [1, 2, 3, 4, 3, 2, 1];
standardDeviation(duty_intervals_1); // 1.0301575072754257
entropy(duty_intervals_1);           // 1.950212064914747
duty_intervals_2 = [2, 2, 3, 3, 3, 2, 2];
standardDeviation(duty_intervals_2); // 0.49487165930539345
entropy(duty_intervals_2);           // 0.9852281360342516

duty_intervals_2 的標準差和 entropy 都比 duty_intervals_1 的低,也就是比較平均,但計算 entropy 是比較複雜的(時間耗費測試如下表),但也沒辦法更有意義的代表平均程度,所以最後採用的是標準差。

如上,以一個 7 個 element 的 array, 分別計算 5,000,000 次所費時間 (tested in Firefox v39.0):

算法 費時 (ms)
Standard Deviation 10,154
Entropy 19,640

潛在的問題與限制

這種排班的方式是相當不經濟的,有點像地球生命起源—「原始湯」—的概念,亂拼亂湊一番,經過大量的嘗試,忽然就有個有意義的結果了 XD。很無奈的,當然也有可能會一直跑不出結果 @_@,最理想的是可以找到一個解方程式的方式來處理(我的數學不行啊 T_T 寫程式算數學就更不行了 TT_TT),但目前大概也只能預先排除一些絕不可能有解(譬如預排時就存在有連值、超過 QOD 上限…)的情況。

目前的設計上是可以以兩個月的模式一起來排班的(workaround 是畫兩個 calendar 然後手動強制同步),但丟到原始湯後要做許多物件的轉換和檢驗,隨著要檢驗的數量增加,對速度的影響也是相當大。而且兩個月模式的限制參數也還沒調到最理想的狀態(要塞的假資料比較多,比較難測啦!^^a),目前實用度應該還不怎麼樣。

還有什麼可以加強的?

用星期五班和假日班來計算點數的方式來排班的,可能只有我們這吧!或許可以做一個開關來切換「一般」和「VGHKS」模式,這樣才有可能讓更多的人來用。所謂的「一般」模式或許是:連星期五和假日班都不用先預排,只要點一下就可以把班表生出來的那個概念吧!

不像以前在外科的時候,班表都是總醫師排好,其他人有意見的再私下喬一喬就好,現在的排班方式是把大家集合起來,排好星期五和假日班後,剩下的再給總醫師處理。每個人都有機會當到總醫師(除非我又再度換科或是不從醫了 ^^a),都有機會排班,希望現在花的一點時間(根本不只一點啊),可以為以後省下更多時間。