개요

업무 효율성을 향한 여정, 그리고 개발자로 변화의 시작
영양사로 근무할 당시, 병원 급식 발주서를 자사 발주 시스템에 맞춰 수작업으로 포맷팅하는 일이 매일 반복되었습니다. 각 파일을 처리하는 데 약 30분이 소요되었고, 매일 반복되는 비효율적인 발주서 작업을 조금이라도 자동화할 수 있는 방법에 대해 고민했습니다.

Python과 Pandas를 활용해 자동화 스크립트를 개발한 결과, 작업 시간을 20초로 단축할 수 있었습니다. 이 스크립트는 단순히 개인의 작업 효율성을 높이는 데 그치지 않았으며, EXE 프로그램으로 변환해 다른 동료들도 사용할 수 있도록 배포했습니다. 이 경험은 제 업무 방식에 큰 변화를 가져왔고, 더 나아가 개발자로 전직을 결심하게된 계기가 되었습니다.

1. 비효율적인 수작업 발주서 처리

(1) 주요 문제 파악

비효율적인 발주서 처리 과정과 인수인계의 어려움

  • 비호환 파일
    병원에서 제공하는 발주서 엑셀 파일은 자사 발주 시스템과 호환되지 않음

  • 반복 작업

  • 인수인계 어려움

  • 높은 피로도와 휴먼 에러
    복잡한 요구사항과 수작업으로 실수가 자주 발생되었고, 업무 피로도도 높았음

(2) 스크린샷 예시

  • AS-IS

    image.png

    image.png

    image.png

  • TO-BE

    image.png

(3) 요구사항 일부 예시

  • 일부 품목 분리 발주
    • 특정 품목(대파, 오이 등)은 5kg, 10kg 단위로 나누어 발주
    • 예시: 대파 13kg 주문 시, 5kg + 5kg + 3kg 단위로 행을 나누어 발주
  • 선 입고, 당일 입고 처리
    • 입고 날짜에 따라 파일을 분리 생성
    • 선입고: “선입고”로 표기된 품목은 입고일보다 하루 일찍 발주
    • 당일 입고: “당일 입고”로 표기된 품목은 급식일 당일에 받도록 해당 날짜에 발주
  • 발주 금지 품목 : “직송”, “인터넷”, “병원” 등이 포함된 품목은 발주서에서 제외
  • 쌀 발주서 생성 : 쌀에 해당하는 품목은 별도의 발주서 파일로 분리
  • 일요일 예외 처리 : 일요일인 경우, A열에 “{급식일자} 사용분”을 모든 행에 기입
  • 수산물 및 육류 발주
    • 수산물과 육류는 “치료”, “환자”, “직원”으로 구분하여 발주
    • 나머지 품목은 합계로 1건만 발주

2. 자동화 스크립트 개발

자동화를 통환 효과적인 업무 개선
자동화 스크립트를 개발하면 여러 문제를 해결할 수 있을 것이라 생각하였습니다. 개발 후 실제 업무에 자동화 스크립트를 적용한 결과, 30분 걸리던 작업을 단 20초로 단축 시키며 업무 효율성이 비약적으로 향상되었습니다. 이 프로그램은 동료 영양사에게도 배포되어 팀 전체의 업무 흐름을 개선하는데 기여했습니다. 자동화 덕분에 이전에 빈번하게 발생하던 발주 누락과 오류가 사라졌고, 인수인계 역시 한층 간편 해졌습니다.

(1) 자동화 스크립트 개발

  • Python과 Pandas 라이브러리를 사용하여 개발
  • 병원 발주서를 자사 발주 시스템에 호환되는 형식으로 자동 변환하는 스크립트 작성
  • 이 스크립트는 엑셀 파일을 읽어 필요 없는 데이터를 제거하고, 포맷을 맞춰 자동으로 저장하는 기능 수행

(2) EXE 프로그램 변환

  • 회사 컴퓨터에서는 Python 설치가 힘든 환경이었으며, 동료마다 Python 설치 및 실행 방법 알려주기에는 번거로웠음
  • 자동화 프로그램 실행하기 위해서는 윈도우 프로그램이 필요했으며, Python 스크립트를 .exe 파일로 변환하는 방법을 모색함
  • 여러 방법 중 가장 대중적인 PyInstaller 를 사용해서 Python 스크립트를 윈도우 실행 파일로 패키징 함
  • 동료들이 쉽게 사용할 수 있도록 아이콘도 추가해 봄

hospital.ico

(3) 배포

  • 이 프로그램을 2주 정도 실제 사용해보면서 버그 수정하고 실제 업로드에도 문제가 없는 것을 확인
  • 결과적으로 기존 파일마다 30분 걸리던 작업이 약 20초 정도로 대폭 줄어들면서 업무 효율성이 많이 개선됨
  • 다른 발주 담당 영양사들도 사용할 수 있도록 프로그램과 사용 방법을 전달

3. 주요 기능과 로직

발주서 엑셀 자동화 로직 흐름
Excel 파일로 부터 데이터를 읽어와, 여러 조건에 맞춰 데이터를 정리 및 재처리 합니다. 최종적으로 선입고, 당일입고, 그리고 쌀 발주서와 본 발주서를 생성하여 Excel 파일로 저장합니다.

스크립트를 통해 병원 급식 발주서를 간단하게 포맷에 맞춰 수정할 수 있으며, 필요한 파일들을 자동으로 생성하여 관리하도록 도와줍니다.

3-1. HospitalOrder 클래스

  • HospitalOrder 클래스는 Excel 파일에서 데이터를 읽고, 발주 항목을 처리한 후, 발주서를 생성함
  • 주요 함수
    • start 함수: Excel 파일을 읽어오고 데이터 전처리, 조건에 따른 필터링 및 수정 후, 결과를 다시 Excel 파일로 저장하는 핵심 함수
    • process_fish_rows 함수: 수산물 항목에 대해 특정 조건을 바탕으로 행을 처리함
    • replicate_row 함수: 조건에 맞는 행을 복제하여 발주 항목을 세분화 하는 함수

(1) 데이터 읽기 및 초기 처리

  • pandas를 사용하여 Excel 파일을 읽고, 특정 조건에 맞춰 필터링하여 필요한 데이터만 남기는 작업 수행
  • input_datemeal_date를 통해 주문 일자와 급식 일자를 출력함
  • 빈 값 또는 특정 조건(ex. “직송”, “인터넷”, “병원” 등)과 일치하는 데이터는 불필요한 항목이므로 삭제함
df = pd.read_excel(excel_file)

# 주문일자와 급식일자 출력
input_date = df['Unnamed: 5'][4]
meal_date = df['Unnamed: 5'][5]
print(f"입고일: {input_date}")
print(f"급식일: {meal_date}")

df = df.dropna(subset=["Unnamed: 3"])
df = df.replace(np.nan, '', regex=True)
df = df.reset_index(drop=True)
df.index += 1
df = df.loc[:, :'Unnamed: 23']  # 오른쪽 열 삭제
df = df[3:]  # 첫 3행 제거

# 행 삭제
df = df[~df['Unnamed: 23'].str.contains("인터넷")]
df = df[~df['Unnamed: 23'].str.contains("병원")]

(2) 발주량 구분 및 통합

발주량은 “치료”, “환자”, “직원” 항목별로 나눠져있으며, 이를 환자열에 통합하여 중복된 데이터를 정리함

  • 수산물과 육류 제외
    • 입력 받은 fish_start_index , fish_end_index , meat_start_index, meat_end_index를 통해 수산물과 육류 범위를 설정
    • 수산물과, 육류 범위 내의 데이터는 따로 처리하기 위해 제외함
  • 발주 구분 통합
    • “치료”, “환자”, “직원” 나눠져있는 발주량을 환자열에 발주량 통합
    • 수산물과 육류가 아닌 항목에서, T열 또는 V열에 있는 값을 복사하여 R열 또는 T열에 넣음
or index, row in df.iterrows():
            column_index = row["Unnamed: 1"]
            if column_index >= fish_start_index and column_index <= fish_end_index and not dried_fish_list[index]:
                continue
            if column_index >= meat_start_index and column_index <= meat_end_index:
                continue
            if garlic_list[index]:
                continue

            df.at[index, 'Unnamed: 12'] = df.at[index, 'Unnamed: 14'] = df.at[index, 'Unnamed: 17'] = ''
            if "일요일" in input_date:
                df.at[index, 'Unnamed: 17'] = df.at[index, 'Unnamed: 20']
            else:
                df.at[index, 'Unnamed: 14'] = df.at[index, 'Unnamed: 20']

(3) 행 복제 및 처리

  • 특정 항목들에 대해 조건에 맞게 행을 복제하여 발주량을 나누는 작업 수행
  • 단위 수량을 넘는 항목은 새로 추가행으로 나눠서 처리함
df = self.replicate_row(df, df['Unnamed: 5'].str.contains("대파") & df['Unnamed: 11'].str.contains("KG"), 5, input_date)
df = self.replicate_row(df, df['Unnamed: 5'].str.contains("오이") & df['Unnamed: 11'].str.contains("KG"), 10, input_date)

(4) 선입고 및 당일입고 처리

  • 선입고 처리: 선입고 항목들은 별도의 Excel 파일로 저장함
  • 당일입고 처리: 당일 입고 항목들도 별도로 처리되며, 이 역시 Excel 파일로 저장함
earlier_df = df[df['Unnamed: 23'].str.contains("선입고")]
today_df = df[df['Unnamed: 23'].str.contains("당일")]
if len(earlier_df) != 0:
    earlier_df.to_excel(f"선입고 {before_input_date}.xls", startrow=1, header=None, index=False)
if len(today_df) != 0:
    today_df.to_excel(f"당일입고 {earlier_date}.xls", startrow=1, header=None, index=False)

(5) 쌀 발주서 생성

  • 쌀 항목은 따로 처리되어, 별도의 Excel 파일( 쌀 발주서)로 저장됨
rice_df = df[df['Unnamed: 5'] == "쌀"]
if len(rice_df) != 0:
    rice_df.to_excel(f"쌀발주서_{datetime.today().strftime('%H%M')}.xls", startrow=1, header=None, index=False)

(6) 최종 발주서 저장

  • 모든 데이터를 처리한 후, 최종 발주서를 저장
  • 파일명은 날짜와 시간을 기반으로 지정된 형식에 맞춰 저장됨
df.to_excel(f"본발주서 {file_input_date}.xls", startrow=1, header=None, index=False)

3-2. 프로그램 실행

  • 프로그램을 실행할 때, 필요한 인덱스(수산, 육류, 선입고 항목 번호 등)를 입력받음
  • 입력받은 값에 맞춰 start 함수가 실행됨
  • 최종적으로 처리된 결과는 여러 Excel 파일로 저장됨