以下のように出力しました。
当直
内科拘束
整形拘束
短時間宿直シフトは、暦日通りに出力しています。スケジュールナースのシフトから補正しています。
プロジェクトの全Pythonソースです。mainのPython部(制約)が最初、post部のPythonのmain def post_main() が最後に配置することが注意点です。制約のpythonとpostのpythonは、別プロセスであることにも注意してください。通信はできません。
post処理では、shift_solutionがシフト解配列として利用可能ですので、ひたすら解を読んで上記フォーマットとなるように整形しています。タスク解(診療科)も利用可能ですが、上記フォーマット出力するには、シフト解のみで十分です。
本プロジェクトは、大変に複雑な仕様です。本コードは、そのための開発デバッグ検証用も兼ねており、開発当初から、改変を積み重ねてきました。冗長なコードになっているのはそのためです。
ユーザには、スケジュール解ではなく、開発当初より、本Excel解を提出してデバッグにご協力いただきました。元々の顧客フォーマットが上のようなものなので、違和感なく検証いただけたと思います。Excel解の提出のフェーズが完了しました。以降、スケジュールナースプロジェクトを使用した、デバッグフェーズに移行します。
import sc3 import sys import os import csv import re import win32gui, win32con import pywin.dialogs.list import calendar from collections import namedtuple for person in 非常勤: for day in 今月: if shift_schedules[person][day][0]=='': v=sc3.GetShiftVar(person,day,'その他') s='非常勤は予定入力がないときはその他 '+staffdef[person]+' '+daydef[day] sc3.AddHard(v,s) def get_open_file_name(title): filter='xlsx\0*.xlsx\0' customfilter='Other file types\0*.*\0' fname, customfilter, flags=win32gui.GetOpenFileNameW( InitialDir=project_file_path, Flags=win32con.OFN_ALLOWMULTISELECT|win32con.OFN_EXPLORER, File='', DefExt='csv', Title=title,# Filter=filter, CustomFilter=customfilter, FilterIndex=0) return str(fname) def draw拘束(ws,c,person,str): if person in 内科: ws.Cells(c.Row+3,c.Column).Value=str elif person in 整形外科: ws.Cells(c.Row+4,c.Column).Value=str def day_solution_analysis(ws,c,n,day): #clear 宿日直person=-1 日直person=-1 宿直person=-1 for person in 非常勤: if shift_solution[person][day]=='その他': continue if '宿日直' in shift_solution[person][day]: if 宿日直person!=-1: print("fatal error 宿日直は、 most one work per a day") exit() 宿日直person=person continue if '日直(日中のみ)' == shift_solution[person][day]: if 日直person!=-1: print("fatal error 日直は、 most one work per a day") exit() 日直person=person continue if '宿直'in shift_solution[person][day]: if 宿直person!=-1: print("fatal error 宿直は、 at most one work per a day") exit() 宿直person=person continue print("fatal error ") exit() list=[] 短宿person=-1 拘束宿list=[] 拘束日直list=[] 日直拘束宿日直person=-1 宿直拘束宿日直person=-1 for person in 常勤: if shift_solution[person][day]=='その他': continue if shift_solution[person][day]=='宿日直': if 宿日直person!=-1: print("fatal error 宿日直は、 most one work per a day") exit() 宿日直person=person continue if '30分'in shift_solution[person][day] or '60分' in shift_solution[person][day] or '90分' in shift_solution[person][day]: if 短宿person!=-1: print(staffdef[person],daydef[day]) print("fatal error 短宿は、 at most one work per a day") exit() 短宿person=person print(staffdef[短宿person],daydef[day],shift_solution[短宿person][day]) continue if '日直(日中のみ)' in shift_solution[person][day] : if 日直person!=-1: print(staffdef[person],daydef[day]) print("fatal error 日直は、 at most one work per a day") exit() 日直person=person continue if '宿直'==shift_solution[person][day] : if 宿直person!=-1: print("fatal error 宿直は、 at most one work per a day") exit() 宿直person=person continue if '日直拘束宿日直'==shift_solution[person][day]: if 日直拘束宿日直person!=-1: print("fatal error 日直拘束宿日直は、 at most one work per a day") exit() 日直拘束宿日直person=person continue if '宿直拘束宿日直'==shift_solution[person][day]: if 宿直拘束宿日直person!=-1: print("fatal error 宿直拘束宿日直は、 at most one work per a day") exit() 宿直拘束宿日直person=person continue if '拘束宿'==shift_solution[person][day]: 拘束宿list.append(person) continue if '拘束日直'==shift_solution[person][day]: 拘束日直list.append(person) continue #print("list add",staffdef[person]) list.append((person,shift_solution[person][day])) if 短宿person!=-1:#Draw 短縮 print(短宿person,staffdef[短宿person],daydef[day],n,shift_solution[短宿person][day]) str=staffdef[短宿person]+' ' if '拘束' in shift_solution[短宿person][day]: draw拘束(ws,c,短宿person,staffdef[短宿person]) if shift_solution[短宿person][day]=='夜30分宿直' or shift_solution[短宿person][day]=='夜30分宿直拘束付': str +='17:00~17:30' ws.Cells(c.Row+1,c.Column+1).Value=str elif shift_solution[短宿person][day]=='夜60分宿直' or shift_solution[短宿person][day]=='夜60分宿直拘束付': str +='17:00~18:00' ws.Cells(c.Row+1,c.Column+1).Value=str elif shift_solution[短宿person][day]=='朝30分宿直' or shift_solution[短宿person][day]=='朝30分宿日直拘束付': str +='8:00~8:30' print("!!朝30分",staffdef[person],daydef[day],n.Row,n.Column) if n!=None: print(staffdef[短宿person],daydef[day]) ws.Cells(n.Row+1,n.Column+1).Value=str elif shift_solution[短宿person][day]=='朝60分宿直' or shift_solution[短宿person][day]=='朝60分宿日直拘束付': str +='7:30~8:30' if n!=None: ws.Cells(n.Row+1,n.Column+1).Value=str print(staffdef[短宿person],daydef[day],n.Row+1,n.Column,ws.Cells(n.Row+1,n.Column).Value) elif shift_solution[短宿person][day]=='朝90分宿直' or shift_solution[短宿person][day]=='朝90分宿日直拘束付': str +='7:00~8:30' if n!=None: ws.Cells(n.Row+1,n.Column+1).Value=str if 日直person!=-1 or 日直拘束宿日直person!=-1: if 宿直person==-1 and 宿直拘束宿日直person==-1: print("fatal error 日直があるなら宿直もないとだめ") exit() if 宿直person!=-1: strlast=staffdef[宿直person] else: strlast=staffdef[宿直拘束宿日直person] if 日直person!=-1: str=staffdef[日直person]+'/'+strlast ws.Cells(c.Row+2,c.Column).Value=str if 宿直拘束宿日直person!=-1: draw拘束(ws,c,宿直拘束宿日直person,strlast) else: draw拘束(ws,c,宿直person,strlast) elif 日直拘束宿日直person!=-1: str=staffdef[日直拘束宿日直person]+'/'+strlast ws.Cells(c.Row+2,c.Column).Value=str draw拘束(ws,c,日直拘束宿日直person,str) elif 宿日直person!=-1: str=staffdef[宿日直person] ws.Cells(c.Row+2,c.Column).Value=str draw拘束(ws,c,宿日直person,str) elif 宿直person!=-1: str=staffdef[宿直person] ws.Cells(c.Row+2,c.Column).Value=str draw拘束(ws,c,宿直person,str) else: print("fatal error ") exit() if len(拘束日直list)==1: str=staffdef[拘束日直list[0]] if 宿直person==-1 and len(拘束宿list)!=1: print("fatal error 拘束日直があるなら、拘束宿日直もしくは宿直もないとだめ") exit() if 拘束日直list[0] in 内科: if 宿直person not in 内科 and 拘束宿list[0] not in 内科: print("fatal error") exit() elif 拘束日直list[0] in 整形外科: if 宿直person not in 整形外科 and 拘束宿list[0] not in 整形外科: print("fatal error") exit() if 宿直person !=-1: str=staffdef[拘束日直list[0]]+'/'+staffdef[宿直person] elif len(拘束宿list)==1: str=staffdef[拘束日直list[0]]+'/'+staffdef[拘束宿list[0]] print("拘束日直list[0]",str) draw拘束(ws,c,拘束日直list[0],str) elif len(拘束日直list)==2: if 宿直person==-1 or len(拘束宿list)!=1: print("fatal error 拘束日直があるなら、拘束宿直および宿直どちらもないとだめ") exit() if 拘束日直list[0] in 内科: if 宿直person in 内科: str=staffdef[拘束日直list[0]]+'/'+staffdef[宿直person] draw拘束(ws,c,拘束日直list[0],str) str=staffdef[拘束日直list[1]]+'/'+staffdef[拘束宿list[0]] draw拘束(ws,c,拘束日直list[1],str) elif 拘束宿list[0] in 内科: str=staffdef[拘束日直list[0]]+'/'+staffdef[拘束宿list[0]] draw拘束(ws,c,拘束日直list[0],str) str=staffdef[拘束日直list[1]]+'/'+staffdef[宿直person] draw拘束(ws,c,拘束日直list[1],str) else: print("fatal error 拘束日直が内科なら、拘束宿直および宿直どちらは内科") exit() elif 拘束日直list[0] in 整形外科: if 宿直person in 整形外科: str=staffdef[拘束日直list[0]]+'/'+staffdef[宿直person] draw拘束(ws,c,拘束日直list[0],str) str=staffdef[拘束日直list[1]]+'/'+staffdef[拘束宿list[0]] draw拘束(ws,c,拘束日直list[1],str) elif 拘束宿list[0] in 整形外科: str=staffdef[拘束日直list[0]]+'/'+staffdef[拘束宿list[0]] draw拘束(ws,c,拘束日直list[0],str) str=staffdef[拘束日直list[1]]+'/'+staffdef[宿直person] draw拘束(ws,c,拘束日直list[1],str) else: print("fatal error 拘束日直が整形外科なら、拘束宿直および宿直どちらは整形外科") exit() elif len(拘束宿list) >=1: for person in 拘束宿list: str=staffdef[person] draw拘束(ws,c,person,str) #draw拘束(ws,c,拘束宿person,str) if len(list) <=2: for tuple in list: if '拘束宿日直' not in tuple[1]: print(staffdef[tuple[0]],tuple[1]) print("fatal error ") exit() str=staffdef[tuple[0]] print('拘束宿日直',str) draw拘束(ws,c,tuple[0],str) else: print("len(list)",len(list),len(拘束宿list),len(拘束日直list)) print("fatal error programming error") exit() def get_sheet_name(): s=daydef[制約開始日] year_str=s[0:4] month=int(s[5:7]) sheet_name=year_str+'年'+str(month)+'月' return sheet_name def get_year(): s=daydef[制約開始日] year_str=s[0:4] return int(year_str) def get_month(): s=daydef[制約開始日] month=int(s[5:7]) return month def get_day(): s=daydef[制約開始日] day=int(s[8:10]) return day def find_address(moncal,D): row=0 for r in moncal: if D not in r: row+=1 continue return (row,r.index(D)) #次月最初の日:最終行の最終列+1を出す row=len(moncal)-1 col=len(moncal[row]) return (row,col) raise IndexError def init_row_columns(): global start_row global start_column global row_interval global column_interval start_row=6 start_column=2 row_interval=6 column_interval=2 def findcell(ws,moncal,D): tuple=find_address(moncal,D) init_row_columns() row=start_row+row_interval*tuple[0] column=start_column+column_interval*tuple[1] c=namedtuple("Row","Column") c.Row=row c.Column=column return c def clear_cells(ws): init_row_columns() for r in range(6): for d in range(7): row=start_row+r*row_interval column=start_column+d*column_interval print(row,column) ws.Cells(row ,column+1).Value="" ws.Cells(row ,column).Value="" ws.Cells(row+1,column+1).Value="" ws.Cells(row+1,column).Value="" ws.Cells(row+2,column+1).Value="" ws.Cells(row+2,column).Value="" ws.Cells(row+3,column).Value="" ws.Cells(row+4,column).Value="" def post_main(): print('\n\n*********ポスト処理を実行中です。*************\n') import win32com.client#pywin32をインポート #すでにExcelが起動されている場合はそのタスクが使われる #エラー終了するとタスクは残ります try : xl = win32com.client.Dispatch("Excel.Application") except: print("can not invoke excel") exit() #動いている様子を見てみる xl.Visible = False #file=get_open_file_name("Open Excel File") #print(file) os.chdir(project_file_path) filename="2024当直拘束表.xlsx" file=os.path.join(project_file_path,filename) file.replace("/","\\") #なぜか逆スラッシュでないと動かない wb = xl.Workbooks.Open(file) # Excelシートオブジェクト ws = wb.Worksheets(get_sheet_name()) # 指定したシートを選択 # Select()の使用前にシートのActivate()が必要 ws.Activate() calendar.setfirstweekday(6)#日曜日を最初に mon_cal=calendar.monthcalendar(get_year(),get_month())#行列でリスト print(findcell(ws,mon_cal,1)) clear_cells(ws) for day in 今月区間: D=day-制約開始日+1 print(D) c=findcell(ws,mon_cal,D) n=findcell(ws,mon_cal,D+1) #print(c.Row,c.Column) day_solution_analysis(ws,c,n,day) wb.Close(True)# Trueで保存。False=Defaultでブックを保存せずにクローズ # Excel終了 xl.Quit() print('\n\n*********ポスト処理を実行終了しました。*************\n')