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