半日が予定として組まれたときを考慮する問題は、最高難度です。
問題は、
Σ(夜勤+半長日)==Σ(長日+半夜勤)
という関係において、半長日が予定として組まれた場合も考慮することにあります。
最初に解を示します。
この解は、設計した動作検証のため、予定半日を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 件のコメント:
コメントを投稿