アルバイト先のテレワーク申請の集計を自動化した話

## はじめに
私のアルバイト先ではテレワーク OK です。アルバイトのおうち作業に時給がつくのは嬉しいですね。テレワークをすると、名前、日付、曜日、時間帯、所要時間、作業内容を Slack に報告します。例えば、
```:申請内容の例
山田太郎、田中花子
1月1日
水曜日
19:00~20:31
1時間30分
シフト調整
```
みたいな感じです。これを手作業で転記し、社員さんに報告します。私の後輩が担当となって、集計を毎週してくれていました。ただ正直言って手作業の転記は無駄で面白くありません。なんとかしてやりたいと思い、Python で自動化してみました。

## 求められていたこと
ただ転記するだけでなく、申請側のミスを修正することもありました。例えば、
-  日付と曜日が一致していない
-  19:00~20:31 と書くべきところを 19:00~20:30 と書いている
-  1時間30分と書くべきところを1時間半と書いている   etc.

といったミス。また、社員さんとしては集計結果を Excel で送ってほしいとのこと。加えて、この自動集計ファイルは誰もが扱えるようなものでなくてはいけません。作成者である私だけが使えるようなものでは、引き継げないからです。
そこで、**ミスを自動修正した上で、集計結果を Excel に出力する、誰でも簡単に使えるファイル**を Python で作ることにしました。

## 担当していた後輩との合意形成まで
やろうと思ってから、Slack API やらスクレイピングやらで処理できそうな目論見は立ちました。ミスの自動集計は、`if` 文でネストしまくったらできそう。Excel 出力は… `pandas` か `openpyxl` かな。後輩が扱いやすいようにするには、`pyinstaller` で exe 化したらよさそう。

ここまで

`openpyxl` 以外触ったこと無いけど何とかなるやろ！

と思い、集計担当の後輩に提案しました。しかし Python はおろか、プログラミングを習ったことのない後輩は当然「はぁ？」というご様子。まあ、そらそうなんですよね。ここでハッとしました。


**百聞は一見にしかず。 こういうときのためにプロトタイプを作るんだ。**

何気にこのことが、初めて触れる Slack API とか `pyinstaller` とかより何倍も大きい学びでした。そんなこんなでひとまず Slack からテレワーク申請を 1 つ拾ってくるコードを書きました。それをもとに後輩と話し合い、おおよその合意を得て、全体のコーディングへと入りました。自分１人が研究のために使うコーディングとは全く勝手が違いました。

## 完成したもの
コードは最後に貼るので、先に中身の説明を簡単にさせてください。
1. exe 化することを踏まえ、後から変わりうる項目（ Token や Channel など）は、テキストファイルに書き、exe 側で読み込むという形式を取りました。
1. 申請ミスは修正し、テレワーク申請でない投稿はスルーできるような処理をしました。えげつなくネストが深くて見づれぇ…
1. 「テレワーク申請を集計し、社員さんに確認メールを送る」というテレワーク作業の申請を、テキストファイルに書き込むことで同時にできるようにしました
1. コードとは関係ないですが、テキストファイルには Token や Channel の URL の更新の仕方を記載しました。

そんな感じで、6MB の「お仕事報告自動化.exe」と「お仕事報告自動化.txt」が完成しました。exe を実行してやると、以下のような Excel をはじきます。名前の列と業務内容の列は隠しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530290/f0a2aba8-4dcd-ed5d-427e-88c2e89a871d.png)

## 全体のコード
実際に動かすには、別に「お仕事報告自動化.txt 」が必要ですが、ここでは本質的なポイントではないので省略します。
```python:お仕事報告自動化.py
def from_txt():
    with open("お仕事報告_自動化.txt", "r", encoding='utf-8') as f:
        files = list(map(lambda s: s.strip(), f.readlines()))

    str_date = files[10][19:]
    salary_application = files[12][27:]
    salary_appli_start = files[11][16:]
    your_name = files[13][15:]
    salary_appli_date = files[14]

    token = files[22][6:]
    channel = files[23][8:]
    return str_date, salary_appli_start, salary_application, your_name, token, channel, salary_appli_date

def label():
    # 1 行目とテレワーク集計をしたことのテレワーク申請
    from openpyxl.styles.alignment import Alignment
    from openpyxl.styles import PatternFill, Font

    ws.title = 'お仕事報告_申請'

    ws['A2'] = your_name
    ws['B2'] = f"{int(date[31:33])}月{int(date[34:])}日"
    ws["C2"] = w_list[datetime.date(int(date[26:30]), int(date[31:33]), int(date[34:])).weekday()]
    ws["D2"] = salary_appli_start
    ws["E2"] = salary_application
    ws["F2"] = "テレワーク申請メール作成"

    ws['A1'] = '名前'
    ws["A1"].alignment = Alignment(horizontal="center")
    ws['A1'].fill = PatternFill(patternType='solid', fgColor='d3d3d3')
    ws['A1'].font = Font(bold=True)
    ws.column_dimensions['A'].width = 12
    ws['B1'] = '日付'
    ws["B1"].alignment = Alignment(horizontal="center")
    ws['B1'].fill = PatternFill(patternType='solid', fgColor='d3d3d3')
    ws['B1'].font = Font(bold=True)
    ws.column_dimensions['B'].width = 10
    ws['C1'] = '曜日'
    ws["C1"].alignment = Alignment(horizontal="center")
    ws['C1'].fill = PatternFill(patternType='solid', fgColor='d3d3d3')
    ws['C1'].font = Font(bold=True)
    ws.column_dimensions['C'].width = 9
    ws['D1'] = '時間帯'
    ws["D1"].alignment = Alignment(horizontal="center")
    ws['D1'].fill = PatternFill(patternType='solid', fgColor='d3d3d3')
    ws['D1'].font = Font(bold=True)
    ws.column_dimensions['D'].width = 12
    ws['E1'] = '業務時間'
    ws["E1"].alignment = Alignment(horizontal="center")
    ws['E1'].fill = PatternFill(patternType='solid', fgColor='d3d3d3')
    ws['E1'].font = Font(bold=True)
    ws.column_dimensions['E'].width = 10
    ws['F1'] = '業務内容'
    ws["F1"].alignment = Alignment(horizontal="center")
    ws['F1'].fill = PatternFill(patternType='solid', fgColor='d3d3d3')
    ws['F1'].font = Font(bold=True)
    ws.column_dimensions['F'].width = 30

def to_excel():
    # ミス修正をしつつ、Slack から Excel へ
    j = 1
    for val in texts:
        datas = val["text"].split()
        flag = False
        for i, data  in enumerate(datas):

            if i == 0:
                # 「なお」や「また」から始まる投稿はスルー
                if data[0].isdigit() or data[0] == "な" or data[0] == "ま":
                    k = 0
                    break

                elif "、" in data:
                    flag = True
                    names = re.split("、", data)
                    data = names[0]

                elif "，" in data:
                    flag = True
                    names = re.split("，", data)
                    data = names[0]

            elif i == 1:
                d = w_list[datetime.date(int(str_date[:4]), int(data[:data.find("月")]), int(data[data.find("月")+1:data.find("日")])).weekday()]

            elif i == 2:
                data = d

            elif i == 3:
                if "~" in data:
                    data = data[:data.find("~")] + "~" + data[data.find("~")+1:]

                elif "～" in data:

                    data = data[:data.find("～")] + "~" + data[data.find("～")+1:]

                elif "〜" in data:
                    data = data[:data.find("〜")] + "~" + data[data.find("〜")+1:]

                if data[4] == "1":
                    data = data[:4] + "0" + data[5:]
                elif data[4] == "6":
                    data = data[:4] + "5" + data[5:]

                elif data[-1] == "0":
                    data = data[:-1] + "1"
                elif data[-1] == "5":
                    data = data[:-1] + "6"


            elif i == 4:
                if data[-2] == "6":
                    data = data[:-2] + "5" + "分"

                elif data[-2] == "1":
                    data = data[:-2] + "0" + "分"

                elif data[-1] == "半":
                    data = data[:-1] + "30分"

            elif i == 5:
                if "(" in data:
                    data = data[:data.find("(")]
                elif "（" in data:
                    data = data[:data.find('（')]

            if not flag:
                k = 0
                ws.cell(row=j+2 ,column=i+1).value = data


            else:
                k = len(names) - 1
                for l, name in enumerate(names):
                    if i == 0 and l != 0:
                        ws.cell(row=j+2+l ,column=i+1).value = name
                    else:
                        ws.cell(row=j+2+l ,column=i+1).value = data


        j += 1+k


import requests, time, re, openpyxl, datetime, os, sys
os.chdir(os.path.dirname(sys.argv[0]))

w_list = '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日'

str_date, salary_application, salary_appli_start, your_name, token, channel, date = from_txt()

oldest = time.mktime(datetime.datetime.strptime(str_date, '%Y-%m-%d').timetuple())
url = "https://slack.com/api/conversations.history"
headers = {"Authorization": "Bearer "+ token}
params = {
   "channel": channel,
    "oldest": oldest
}

texts = requests.get(url, headers=headers, params=params).json()["messages"]

wb = openpyxl.Workbook()
ws = wb.active
label()
to_excel()

wb.save(f'お仕事報告_{str_date}～.xlsx')
wb.close()
```