2024年9月5日木曜日

長日半休システム 予定半休も組み込んだ実装

 半日が予定として組まれたときを考慮する問題は、最高難度です。

問題は、

Σ(夜勤+半長日)==Σ(長日+半夜勤)

という関係において、半長日が予定として組まれた場合も考慮することにあります。

最初に解を示します。

この解は、設計した動作検証のため、予定半日を0から4日に変化させ、さらに、長日と夜勤回数を強制的に4回-5回に変化させています。列制約はチェックを外してブランクにしています。

GUIでは、このような特殊な関係は記述できないので、Pythonで記述していきます。今、自動アサインする半長日と、予定半長日を合体した仮想的な変数をDとします。

Dは、半長日を置き換えて、

Σ(夜勤+D)==Σ(長日+半夜勤)

となればよい訳です。Dは、論理式では記述できないので、以前示した策で、表示開始日を通常よりー1にして、WORK領域とした日を変数として使用します。

すると、

Σ(D+半夜勤)<=1

とすれば、Σ(夜勤-長日)<=±1

にすることが出来ます。

次にDの制約について考えます。

D:1→Assert(半長日または、OR(予定半日))

となることが要件です。これは、長日の方が夜勤より多いならば、「半長日または一つでも予定半日あること」がAsssertされなければいけないということを指しています。

D:0→Assert(!半長日)

これは、長日の方が多くないならば、半長日は、非Activeでなければならないという要件です。

さらに半長日と予定半日のOR(=Σ予定半日>=1)は、排他的でなければなりません。排他的というのは、同時にActiveになることはない、ということです。

以上の制約をPythonで記述すればOKです。

以下で、ユーザ要求による実装部が、今回のプロジェクト実装部の全てです。超上級問題と言ってよいと思います。

ユーザ様は、他の病院を参考にした、とおっしゃっていましたが、私の知る限りにおいて、このような特殊な実装例(公休数を変化させ半休数で長夜勤差を補償するシステム)は、知りません。もし、それで運用している病院があったとしたら、それは人力によるものしか考えられません。実装仕様が複雑すぎて、プログラミングできないだろう、と思います。(このような複雑な仕様を組み込めるのは、スケジュールナースしかないだろう、と思います。)


なお、この実装では、ユーザ要求ではない例も実装しています。QOLを簡単に比較することが出来るので、明日以降やってみましょう。

import sc3

def work_check():
    for person in 半日:
        v=shift_schedules[person][0][0]
        if v !="":
            print("予定エラー "+staffdef[person]+":"+v)
            print("!!!!!\t ERROR 本プロジェクトは、表示開始日をWorkとして使用します。\n予定を全てブランクにしてください。\n")
            raise Exception

def 長日勤範囲を夜勤範囲と同じにする():
    for person in 最大夜勤回数.keys():
        valueMax=最大夜勤回数[person]
        valueMin=0
        if person in 最小夜勤回数:
            valueMin=最小夜勤回数[person]
        list=[]
        for day in 今月:
            v=sc3.GetShiftVar(person,day,"長日勤");
            list.append(v)
        sc3.AddHard(sc3.SeqLE(valueMin,valueMax,list),staffdef[person]+"長日夜勤回数");


def xor1(v1,v2):
    v=(v1&~v2)| (~v1&v2)
    return v

if ユーザ要求による実装:
    work_check() 
    for person in 半日:
        listA=[]
        listB=[]
        D=sc3.GetShiftVar(person,0,"長多半Ⅰ")
        listA.append(D)
        listC=[]
        listC.append(D)
        listE=[]
        listF=[]
        for day in 今月:
            v1=sc3.GetShiftVar(person,day,"夜勤")
            listA.append(v1)

            v2=sc3.GetShiftVar(person,day,"長日勤")
            v3=sc3.GetShiftVar(person,day,"夜多半Ⅰ")
            listC.append(v3)
            listB.append(v2|v3)
            v4=sc3.GetShiftVar(person,day,"長多半Ⅰ")
            listF.append(v4)
            v5=sc3.GetShiftVar(person,day,"半日")
            listE.append(v5)

        半日or=sc3.Or(listE)
        長多半or=sc3.Or(listF)
    
        x=半日or|長多半or
        sc3.AddHard(~半日or|~長多半or,"半日と長多半は同時にActive不可"+str(person))
        sc3.AddHard(~D |x,"~D|x person="+str(person));#Dならばx
        sc3.AddHard(D  |~長多半or,"D|~長多半or person="+str(person));#Dでないなら長多半1はNonActive

        listF.append(半日or)
        sc3.AddHard(sc3.SeqLE(0,1,listF),"ListF person="+str(person))#Σ長多半1+Or(半日)<=1
        sc3.AddHard(sc3.SeqComp(listA,listB),"SeqComp person"+str(person))#Σ夜勤+D==Σ(長日勤+夜多半1)
        sc3.AddSoft(sc3.SeqError(0,a_なるべく0にする,allowable_errors,listC),"ListC person"+str(person),3)#D+Σ夜多半1<=1 センターに制約する場合、範囲を0,1とすれば、センターにする力は働かない
else:
    長日勤範囲を夜勤範囲と同じにする()
    for person in 半日:
        for day in 今月:
            v=sc3.GetShiftVar(person,day,"夜多半Ⅰ")
            sc3.AddHard(~v,"夜多半1禁止")
            v=sc3.GetShiftVar(person,day,"長多半Ⅰ")
            sc3.AddHard(~v,"長多半1禁止")

 

0 件のコメント:

コメントを投稿