2020年3月14日土曜日

勤務数の年度内平準化

月内平準化は、SC3内で単独処理が出来ますが、年度平準化は、出来ません。そこで、Excelを使用して、年度内の平準化を試みます。

下は、スタッフプロパティで、土日祭日の日勤と
夜勤数の累計を読み込んでいます。今回初期値として0を入れました。

累計数が多いスタッフは、なるべくその勤務をさせたくない、という風に考えます。
そうすると、累計数に応じてその勤務をさせたくないというソフトレベルを上げていけばよい、ということになります。

以下がそのソースです。実装上のポイントは2つあります。

<ソフト制約を書く場合のポイント>
1)できるだけソフト制約を使わない
2)ソフト制約同士のコンフリクトを避ける

ソフト制約を使えば使うほど、重くなります。なので、使わずに済めば それに越したことはありません。

2番めのポイントは、ソフト制約同士のコンフリクトを避ける、ということです。コンフリクトがあるかないかは、Default状態で、エラーの有無で確認することが出来ます。コンフリクトがあると、通常状態もしくは、予定が白紙状態でもエラーカウントがされる状態となります。このような状態であると、なにがしかの性能上の劣化をもたらします。なので、Default状態では、エラーカウントがされない、ということを目指してください。0にすることは難しいかもしれませんが、Defaultでのエラー数を少なくすることは、意味があります。

さて、実装に戻りますが、一番下の累計からアサインされるようにしたいのですが、1)の原則より、
一番下では、ソフト制約化しません。2番目の累計数から順次ソフトレベルを上げる実装としています。また、既にハード予定入力がされている場合は、1)原則により、ソフト制約化しません。ハード制約の場合、shift_schedules[person][day][0]はブランクではなく、かつshift_schedules[person][day][1]が0になっています。

最後に、SC3GUIでの問題なのですが、ダイナミックにソフトレベルを変更するのに、
sc3.AddSoft(~v1,s,soft_level)
とは書いていません。ここのsoft_levelは、定数を指定する必要があります。GUIは、ここの定数を見て、求解画面の適用チェックを出しています。GUIはそれほど賢くないので、定数で書いていないと、ソフト制約が無視されてしまうことになります。

後は、当月の当該のカウントをExcelに出力して、累計との加算処理をExcelで行えばよいです。
 この辺の処理は、ユーザさまにお願いすることになりますが、 求解を何度も行うのが、普通なので単純な実装とすることはできません。マニュアルでお願いしたほうがよいかもしれません。





def write_not_preferred(person,day,not_preferred_shift,level):
    v1=sc3.GetShiftVar(person,day,not_preferred_shift)
    s=staffdef[person]+daydef[day]+' '+not_preferred_shift+'は、好ましくない ソフトレベル='+str(level)
    if level==1:
        sc3.AddSoft(~v1,s,1)
    elif level==2:
        sc3.AddSoft(~v1,s,2)
    elif level==3:
        sc3.AddSoft(~v1,s,3)
    elif level==4:
        sc3.AddSoft(~v1,s,4)
    else:
        sc3.print("Unsupported level ")
    sc3.print(s+'を行いました\n')

def not_preferred(day_list,person,not_preferred_shift,level):
    for day in day_list:
        ts=shift_schedules[person][day][0]
        if ts =='':
            write_not_preferred(person,day,not_preferred_shift,level)
        elif shift_schedules[person][day][1] !=0:#ソフト制約なら適用
            write_not_preferred(person,day,not_preferred_shift,level)

            
def 累計処理Sub(累計属性,禁止persons1,禁止persons2,daylist,not_preferred_shift):
    #休日日勤不可 夜勤不可者を除いて累計のsetをつくり、少ない順にレベルを上げる
    s=set()
    for person in 累計属性.keys():
        if person in 夜勤なし:
            continue
        if person in 休日日勤不可:
            continue
        累計=累計属性[person]
        s.add(累計)
    level=0
    for 累計 in s:
        for person in 全スタッフ:
            if person in 禁止persons1:
                continue
            if person in 禁止persons2:
                continue
            if 累計==累計属性[person]:
                if level==0:#最小累計は、制約にしない
                    continue
                not_preferred(daylist,person,not_preferred_shift,level) #累計が多くなるほど、そのシフトを行うのは嫌だ
        level+=1            

def 累計処理():
    
    累計処理Sub(土日祭日の日勤回数累計属性,休日日勤不可,休日日勤不可,今月休診日,'日勤')
    累計処理Sub(土日祭日の夜勤回数累計属性,休日日勤不可,夜勤なし,今月休診日,'夜勤')
/pre>

0 件のコメント:

コメントを投稿