Pythonを使って中小印刷会社の営業が面倒な手配数量選択を自動化してみた話


筆者のスペック

  • 地方の工学部機械工学科にてPython・C++を触る
  • ROSを介して自律ロボットの制御をやってました
  • なんやかんやあって印刷会社の営業(内勤多め)をやってます。

自動化前の状況

私が現在担当している品目は、主に"軟包材"と呼ばれるアルミ袋です。
(ポテチの袋やシャンプーの詰め替えパックをイメージしてくれれば嬉しいです)

この品目は製造にかかる時間が最短でも2ヶ月かかるため、
前もってヌケモレなく手配しておくことが最重要課題です。

また、原材料が2000m刻みの規格のロール状であるため、
任意の数量を得ることが難しいです。
(想像以上に出来すぎてしまう場合がある)

下に上り数量の表の例を記載します。
これも実際は最大で±それぞれ10%の出来高数変動が存在します。
(オペレータの腕がいいとセッティング時のロスが少ない)

[袋] 8000m 6000m 4000m 2000m
製品A 81000 58500 40000 14000
製品B 76000 57000 38000 15000
製品C 35000 25500 17000 6000

そして、納期の2~3か月前にはお客様より先行手配がエクセル添付で届きます。
品名を確認し、上り数量一覧表(紙)の該当行に定規を置き、
右端の2000mから順に数量を満たす手配メーターを決定します。
上り数量が要求数にぎりぎりな場合は電卓で*0.9をし安全を確認します。

この作業が多い時で50品目ほど繰り返されます。
数的にはまぁ手作業でもできなくはないのですが、
表の行や左右の見間違えが即、数欠けや過納品につながりますので
細心の注意が要求されます。
(まぁ何度もやってると品目聞いただけでメーターごとの上りがおおよそ把握できるんで自分で間違いに気付けるんですが…)

そこで私は自動化を決意しました。

プログラム構成

上り数量一覧表をエクセルにcsv形式で保存。コマンドにて自動化プログラムを実行し、入力情報を食わせ、出力を得る。

  • 入力情報
    • 注文番号
    • 必要数
  • 出力情報
    • 注文番号
    • 仕入先
    • 袋形状
    • 版番号
    • 品名
    • 必要数
    • 上り数量
    • 手配メータ―数

※エクセルオンリーでもできそうですがVBAよりPythonのほうがとっつきやすく、またこういうファイル間横断プログラムをマクロでやるのはあんまエレガントじゃないよなぁ(笑)って思いこんな回りくどいことしてます。もっと効率いい方法ありましたら是非ご教授願います。

コード


#################################################
###コマンドから製品コードと数量を取得、最適メーター数を出力#####
##################################################
import numpy as np
import pandas as pd
#pd.set_option('display.unicode.east_asian_width', True)
miss_coefficient = 0.9
pd.set_option('display.max_rows', 1000)
pd.set_option('display.max_columns', 1000)
df1 = pd.read_csv('inputdate/order.csv',header=None)#入力データ読込部
df2 = pd.read_csv('basedate/number.csv',header=0,encoding="shift-jis")#データベース参照部
#仕入先,袋形状,コード,直近版番号,品名,2000m,4000m,6000m,8000m,10000m,12000m,14000m,16000m,18000m,20000m
df2 = df2.fillna(0)
#print(df2)#データべースチェック用
list_df = pd.DataFrame(columns=['仕入先','注文番号','袋形状','直近版番号','品名','注文数(手配数)','上り数量','手配m数'])

for i in range(len(df1)):
    code  = df1.loc[i, 0]#製品コード##int
    order = df1.loc[i, 1].astype(float)#数量←locで指定するとnumpyになる##float
    df3 = df2[df2['コード'] == str(code)].reset_index()#df3は製品コードに対応する行#行番号を0にして読込
    #print(df3)
    if df3.empty:
        print('注文番号',end="")
        print(code,end="")
        print('に合致する番号がデータベース上にありません!')
        continue
    #print(type(order))#<class 'numpy.float64'>
    #print(type(df3))###<class 'pandas.core.frame.DataFrame'>

    meter_list = [
        '2000m',
        '4000m',
        '6000m',
        '8000m',
        '10000m',
        '12000m',
        '14000m',
        '16000m',
        '18000m',
        '20000m',
    ]

    for meter in meter_list:
        num = df3.loc[0, meter]
        if num * miss_coefficient <= order:
            continue

        arrange_number = num.astype(int) # 上り数量
        arrange_meter = meter # 手配メーター数

        break

    #再度dateframeに組み込み
    tmp_se = pd.Series([
        (df3['仕入先'].to_string(name = False,index = False)).ljust(6),
        (df3['コード'].to_string(name = False,index = False)).ljust(4),
        (df3['袋形状'].to_string(name = False,index = False)).ljust(4),
        (df3['直近版番号'].to_string(name = False,index = False)).ljust(10),
        (df3['品名'].to_string(name = False,index = False)).ljust(25),
        str(order.astype(int)).center(10),#int
        str(arrange_number).center(10),
        str(arrange_meter).center(10)], index=list_df.columns )
    #実はSeriesは1次元の配列(ベクトル)なので縦とか横とか言う概念がないので、縦でも横でも同じことをさしているのだそうです
    list_df = list_df.append(tmp_se, ignore_index=True )
    #ここも要注意ポイントで、.append は戻り値で新しいインスタンスを返します。 なので、代入しなおしてください。
#print(list_df)#蓄積したリストを最後に表示
#得られたリストのソート
list_df = list_df.sort_values(['仕入先', '袋形状'], ascending=[False, False])
print(list_df)
list_df.to_csv("outputdate/result.csv", encoding="shift_jis")

今後の課題

手配数量を一発で決定するという課題は解決された。

しかしこの出力結果でそのまま発注できるわけではなく、
さらにエクセル方眼紙製の発注書にコピぺしFAXしなければなりません。
できれば発注書まで一気に自動作成できるようになれば、かなり労力が削減でき、
その時間を別のお客様のもとで過ごす、という選択肢も可能になるやもしれません。

参考URL

Python pandas で動的に行を追加するTips(プログラマ向け)
https://qiita.com/567000/items/d8a29bb7404f68d90dd4