2020年4月9日木曜日

土日夜勤した場合10日Window内で公休を1日取得2日ある場合はその月内でもう1日取得

実は、前に実装した土日夜勤した場合前後の3日間で代休を取る が破綻してしまいました。1年を通してシミュレーションしてみると2021年5月の連休で物理的に取得不可能なことが判り、クライアントに1ヶ月3分割することを提案しました。で、出てきた仕様が次です。
■土日夜勤の場合、公休が2日取得となりますが そのうち1日は必ず「1か月を3分割し、そのWindow以内に公休を消化する」にし、もう1日は同月内であれば、いつでも良いとする。もちろん、金土夜勤の場合、日月夜勤の場合は今まで通り「1か月を3分割し、そのWindow以内に公休を消化する」こととする。
■仕様の背景は、理解していません。とりあえず、実装し易い形に変形しています。 以下をPythonで実装しています。 土日夜勤した場合10日Window内で公休を1日取得2日ある場合はその月内でもう1日取得()
ここでの、ポイントは、公休(代休)が取得される論理です。例えば、A Window期間中に土日のどちらか夜勤があれば、A Window中、公休は必ず1個取得されます。これが、Ac==Aa|Ab 部になります。さらに、B Window中に土日連続夜勤があったとき(Bab) A Window で公休が発生するというロジックです。夜勤数、公休数共に、2個以下に制限してあるので、この条件が発生するのはAc==Babでしか起こりえません。 また、公休が発生しないのは、(Aa|Ab|Bab) のどれも該当しない、つまり夜勤が発生しないときは、公休を生成させないということに符合します。 ということで、簡単なロジックで、必要にして十分、つまり同値であることが判ります。同値のルーチンは、前々回に説明しました。また、Acや、Acは、ΣXi>=1 という関係です。これは、前回説明しました。それぞれのWindow内でORを取ればよいことが判ります。 同様にして、他のWindowも処理できます。 かなり複雑な仕様ですが、Pythonで100行足らずで記述できました。かなり難しい部類のプログラムだと思いますが、SeqCompや、SeqExpr等の制約関数を使っていないので、性能的にもMaxになっているはずです。

a:土夜 b:日夜
c:公休(土日以外)
A:Window1 B:Window2 C:Window3
Ac== (Aa| Ab |Bab)
Bc== (Ba| Bb |Cab)
Cc== (Ca| Cb |Aab)
Σ(a|b)<=2 今月
Σc<=2 今月

def 等号制約(a,b,s):
    sc3.AddHard( (a|~b) &(~a|b),s)
 
def 代休window処理_v2(person,window):
    holiday_domain=[]
    sat_sun=[]
    ss=[]
    for day in window:
        if day in 土 or day in 日:
            sat_sun.append(day)
        else:
            holiday_domain.append(day)
        if day in 土 and day+1 in window:
            ss.append(day)
    
    vreturn_list=[]
    vsat_sun=[]
    vholidays=[]
    vssdays=[]
    for day in ss:
         #sc3.print('土'+str(day)+'\n')
         v1=sc3.GetShiftVar(person,day,'入り')#
         v2=sc3.GetShiftVar(person,day+1,'明け')
         vssdays.append(v1&v2)#土日連続夜勤

    for day in sat_sun:
         #sc3.print('土'+str(day)+'\n')
         v1=sc3.GetShiftVar(person,day,'入り')#
         v2=sc3.GetShiftVar(person,day,'明け')
         vsat_sun.append(v1|v2)#土日に夜勤
    for day in holiday_domain:
         #sc3.print('H'+str(day))
         v1=sc3.GetShiftVar(person,day,'公休')#
         vholidays.append(v1)#公休
    s=staffdef[person]+daydef[day]+'代休処理'
    #sc3.AddSoft(sc3.SeqComp(vsat_sun,vholidays),s,7)
    vreturn_list.append(sc3.Or(vsat_sun))
    vreturn_list.append(sc3.Or(vholidays))
    vreturn_list.append(sc3.Or(vssdays))
    return vreturn_list
       


def 土日夜勤した場合10日Window内で公休を1日取得2日ある場合はその月内でもう1日取得():
    今月日数=len(今月)
    WindowDays=今月日数/3 #3分割
    win1=[]
    win2=[]
    win3=[]
    for day in 今月:
        if len(win1)<   WindowDays:
            win1.append(day)
        elif len(win2)< WindowDays:
            win2.append(day)
        else:
            win3.append(day)
    sc3.print('window1=',str(len(win1))+'\n')
    sc3.print('window2=',str(len(win2))+'\n')
    sc3.print('window3=',str(len(win3))+'\n')
    
    
    #今月で見たときに土日出勤数==公休数とする
    sat_sun=[]
    holiday_domain=[]
    for day in 今月:
        if day in 土 or day in 日:
            sat_sun.append(day)
        else:
            holiday_domain.append(day)

    for person in 夜勤可能:
        vsat_sun=[]
        vholidays=[]
        vlist1=代休window処理_v2(person,win1)#Window内では1日に限定
        vlist2=代休window処理_v2(person,win2)#Window内では1日に限定
        vlist3=代休window処理_v2(person,win3)#Window内では1日に限定
    
        for day in sat_sun:
            #sc3.print('土'+str(day)+'\n')
            v1=sc3.GetShiftVar(person,day,'入り')#
            v2=sc3.GetShiftVar(person,day,'明け')
            vsat_sun.append(v1|v2)
        for day in holiday_domain:
            #sc3.print('H'+str(day))
            v1=sc3.GetShiftVar(person,day,'公休')#
            vholidays.append(v1)
        s=staffdef[person]+daydef[day]+'今月処理'
       
        sc3.AddHard(sc3.SeqLE(0,2,vsat_sun),s+'土日')#土日夜勤は2回以内
        sc3.AddHard(sc3.SeqLE(0,2,vholidays),s)#公休2回以内
        等号制約(vlist1[0]|vlist2[2],vlist1[1],'Ac==(Aa|Ab|Bab)') 
        等号制約(vlist2[0]|vlist3[2],vlist2[1],'Bc==(Ba|Bb|Cab)') 
        等号制約(vlist3[0]|vlist1[2],vlist3[1],'Cc==(Ca|Cb|Aab)') 

0 件のコメント:

コメントを投稿