AI研究所 - AI(人工知能)に脅かされないために、AI(人工知能)を作る側の人間になる -

HOME  >  機械学習だけじゃない!Pythonの便利な使い方 ~会社の雑務をPythonで自動化する?!その③

公開日:2019.03.15 

機械学習だけじゃない!Pythonの便利な使い方 ~会社の雑務をPythonで自動化する?!その③

カテゴリー: AI(人工知能)の作り方

Python大好きなAI研究所のショウと申します。

前回と前々回に引き続き機械学習だけではないPythonの実用的な使い方をお伝えしたいと思います!

今回のテーマは、「Gmailから抽出した依頼メールを自動的にタスク分けし、業務ステータスごとに分類。本日のやることリストをSlackに送る」です。

第一回の記事はこちら
第二回の記事はこちら

前回の記事の簡単なおさらいを見てみましょう。
以下の其の三まで進めておりました。

其の一 : Googleにアクセスし、ユーザー認証データを取得し、認証を実行します。
其の二 : Gmailのサービスを取得し、GmailのAPIが使えるようにします。
其の三 : メッセージの一覧を取得し、該当するメッセージを振り分けます。
其の四 : そのメッセージを分割し、タスク分けをして、スプレッドシートに転記します。
其の五 : 分割した本文とスプレッドシートの内容から、Slackに送るテンプレート文を作成します。
其の六 : Slackにメッセージを送ります。

今回は其の四からのスタートです。

スプレッドシートにアクセスして、前回取得した情報をタスクステータスとして転記する作業からスタートします。

まずはコードの全体を見てみましょう。

import gspread
from oauth2client.service_account import ServiceAccountCredentials

import pandas as pd
from datetime import datetime

from dateutil.relativedelta import relativedelta

import jpholiday

import re

def access_sheet(text_dict):
    scope = ['https://spreadsheets.google.com/feeds',
            'https://www.googleapis.com/auth/drive']

    credentials = ServiceAccountCredentials.from_json_keyfile_name('My Project.json', scope)
    gc = gspread.authorize(credentials)
    wks = gc.open('管理表').sheet1


    #スプレッドシートの内容をデータフレームに変換
    # df=pd.DataFrame(wks.get_all_records())
    # これ一発でできるが、列の順番がバラバラになるので、仕方なしに下記の方法。
    
    wks_records=wks.get_all_records()
    columns=list(wks_records[0].keys())
    rows=[list(record.values()) for record in wks_records]
    df=pd.DataFrame(rows,columns=columns)

    # print(df.shape)
    # print(df.columns)
    # print(df['ジャンル別'])
    # print(df.loc[:,'入稿日'])
    # print(df.loc[1,'入稿日'])

    return wks,df

def update_sheet(text_dict):
    global today
    wks,df=access_sheet(text_dict)

    today=datetime.strptime(text_dict['Date'], '%Y/%m/%d %H:%M:%S')

    todo_date={"入稿日":today.strftime("%Y/%m/%d"),"校正日":"","社内提出日":"","最終調整日":"","最終校正日":"","納品期日":""}

    tomorrow=today+relativedelta(days=1)
    weekend=tomorrow.weekday()

    holiday=[h[0] for h in jpholiday.year_holidays(2019)]

    task_count=1
    while task_count!=6:

        if weekend==5:
            tomorrow=tomorrow+relativedelta(days=2)
            weekend=tomorrow.weekday()
        if weekend==6:
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()

        for h in holiday:
            if jpholiday.is_holiday(datetime.date(tomorrow)):
                tomorrow=tomorrow+relativedelta(days=1)
                weekend=tomorrow.weekday()

        if task_count==1:
            todo_date['校正日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==2:
            todo_date['社内提出日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==3:
            todo_date['最終調整日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==4:
            todo_date['最終校正日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==5:
            todo_date['納品期日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()

        task_count+=1

    last_id=df['管理ID'].tolist()[-1]

    task4mail,tasks=parse_dict(text_dict)

    new_rows=[]
    temp_id=last_id
    for k,v in tasks.items():
        temp_arr=[]
        for i in range(v):
            temp_id=temp_id+1
            temp_arr.append(temp_id)
            temp_arr.append('執筆中')
            if k=="MM":
                temp_arr.append(f'{k}の日々。')
                temp_arr.append('日々')
                temp_arr.append(k)
            if k=="SS":
                temp_arr.append(f'{k}どうなるか。')
                temp_arr.append('どうなる')
                temp_arr.append(k)
            if k=="LL":
                temp_arr.append(f'{k}への思い。')
                temp_arr.append('思い')
                temp_arr.append(k)
        new_rows.append(temp_arr)

    new_df_rows=[row+list(todo_date.values()) for row in new_rows]
    new_df=pd.DataFrame(new_df_rows,columns=df.columns)
    update_df=df.append(new_df)
    new_index=[i for i in range(len(update_df.index))]
    update_df.index=new_index 


    to_boss=[]
    for i,column in enumerate(update_df.columns.tolist()):
        if i>5:
            to_do_column=update_df[column].where(update_df[column].apply(is_today)).tolist()
            to_do_index=update_df[column].where(update_df[column].apply(is_today)).index.tolist()
            indices=[j for j,to_do in enumerate(to_do_column) if str(to_do)!='nan']
            if indices!=[]:
                for k,id_ in enumerate(update_df.loc[indices,'管理ID'].tolist()):
                    to_boss.append(f'本日は ID_{id_} {column.replace("日","")} が {k+1} 件です。')


    [wks.append_row(row) for row in new_df_rows]

    return task4mail,to_boss       

def is_today(x):
    global today
    date_=datetime.strptime(x,'%Y/%m/%d')
    if datetime.date(today)==datetime.date(date_):
        return True
    else:
        return False

def parse_dict(text_dict):
    task4mail=[]
    tasks={}
    for line in text_dict['Body'].split():
        try:
            task=re.search(r'[A-Z]{2}[\w]+件',line).group()
            task4mail.append(task)
            temp_key=task.split('が')[0]
            temp_value=task.split('が')[-1].replace('件','')
            tasks[temp_key]=int(temp_value)
        except:
            pass
    return task4mail,tasks

ここから、一つづつのコードを説明していきます。

import gspread
from oauth2client.service_account import ServiceAccountCredentials

import pandas as pd
from datetime import datetime

from dateutil.relativedelta import relativedelta

import jpholiday

import re

pandas 等の必要なモジュールをインポートします。

def access_sheet(text_dict):
    scope = ['https://spreadsheets.google.com/feeds',
            'https://www.googleapis.com/auth/drive']
    
    credentials = ServiceAccountCredentials.from_json_keyfile_name('My Project.json', scope)
    gc = gspread.authorize(credentials)
    wks = gc.open('管理表').sheet1
    
    
    #スプレッドシートの内容をデータフレームに変換
    # df=pd.DataFrame(wks.get_all_records())
    # これ一発でできるが、列の順番がバラバラになるので、仕方なしに下記の方法。
    
    wks_records=wks.get_all_records()
    columns=list(wks_records[0].keys())
    rows=[list(record.values()) for record in wks_records]
    df=pd.DataFrame(rows,columns=columns)
    
    return wks,df

スプレッドシートにアクセスして、事前にダウンロードした json ファイルを使い、”管理表”という名前のワークシートの sheet1 を取得します。

get_all_records()で sheet1 の全てのレコードを取得しています。

column(列名)を取得した後、row(行)を一行ずつリスト内包表記を使って取得していきます。

pandas を使って、データフレームに変換します。

wks オブジェクトと df データフレームオブジェクトを返す関数を作成します。

def update_sheet(text_dict):
    global today
    wks,df=access_sheet(text_dict)
    
    today=datetime.strptime(text_dict['Date'], '%Y/%m/%d %H:%M:%S')
    
    todo_date={"入稿日":today.strftime("%Y/%m/%d"),"校正日":"","社内提出日":"","最終調整日":"","最終校正日":"","納品期日":""}
    
    tomorrow=today+relativedelta(days=1)
    weekend=tomorrow.weekday()
    
    holiday=[h[0] for h in jpholiday.year_holidays(2019)]

ここからメインの関数作成です。

メール本文から作成した text_dict オブジェクトを引数とし、todayオブジェクトをグローバル変数にすることを宣言します。

先程作ったaccess_sheet()からワークシートオブジェクトとデータフレームオブジェクトを受け取ります。

メールを受け取った日付をdatetimeのstrptimeメソッドで文字列からdatetimeオブジェクトに変換し、todayに格納します。

スプレッドシートに新しいタスクを記入するために”入稿日”以外の空の辞書を作成します。

次の日を relativedelta を使って取得します。

relativedelta を使えば、(例えば今年の2月28日の次は3月1日)と判定してくれます。

明日が土日かどうかを weekday() を使って判定します。(月曜から日曜までが1から6で返される。)

今年の祝日をjpholiday.year_holidays(年)を使って、リストとして取得します。

    task_count=1
    while task_count!=6:
    
        if weekend==5:
            tomorrow=tomorrow+relativedelta(days=2)
            weekend=tomorrow.weekday()
        if weekend==6:
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
    
        for h in holiday:
            if jpholiday.is_holiday(datetime.date(tomorrow)):
                tomorrow=tomorrow+relativedelta(days=1)
                weekend=tomorrow.weekday()
    
        if task_count==1:
            todo_date['校正日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==2:
            todo_date['社内提出日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==3:
            todo_date['最終調整日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==4:
            todo_date['最終校正日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
        if task_count==5:
            todo_date['納品期日']=tomorrow.strftime("%Y/%m/%d")
            tomorrow=tomorrow+relativedelta(days=1)
            weekend=tomorrow.weekday()
    
        task_count+=1

タスク数(期限日分)だけ while 文を使って、

”明日は「校正日」、次の日は土日なので、月曜日は「社内提出日」”等、条件を追記してタスクを振り分けています。

祝日かどうかも先にループの先頭で判定して、祝日以外を日付が指すように調整しています。

    last_id=df['管理ID'].tolist()[-1]

    task4mail,tasks=parse_dict(text_dict)
    
    new_rows=[]
    temp_id=last_id
    for k,v in tasks.items():
        temp_arr=[]
        for i in range(v):
            temp_id=temp_id+1
            temp_arr.append(temp_id)
            temp_arr.append('執筆中')
            if k=="MM":
                temp_arr.append(f'{k}の日々。')
                temp_arr.append('日々')
                temp_arr.append(k)
            if k=="SS":
                temp_arr.append(f'{k}どうなるか。')
                temp_arr.append('どうなる')
                temp_arr.append(k)
            if k=="LL":
                temp_arr.append(f'{k}への思い。')
                temp_arr.append('思い')
                temp_arr.append(k)
        new_rows.append(temp_arr)

すべてのタスクが格納できたら、すでに割り振りされている、一番最後のタスクIDを管理表から取得します。

parse_dict()に text_dict を渡し、Slackに送るためのリストと辞書を取得します。

管理表に転記するための新しい行を作るために、空のリストを作ります。

items()でキーとバリューに分け、バリューはリストなので、そのリストの数分だけ for 文 でループさせ、新しいIDと 種類別に 決められたタイトルとジャンルを new_rows に付け足していきます。

    new_df_rows=[row+list(todo_date.values()) for row in new_rows]
    new_df=pd.DataFrame(new_df_rows,columns=df.columns)
    update_df=df.append(new_df)
    new_index=[i for i in range(len(update_df.index))]
    update_df.index=new_index 

新しい行のリストをデータフレームに変換するために、new_rows と todo_date の バリューを連結させます。

新しくタスクを加えた行分だけ、データフレームを作成します。

元々あったワークシートのデータフレームと、新しく作ったデータフレームを合体させます。

Index番号を正常に戻すため、合体させたデータフレームの長さを 整数のリストにして、合体させたデータフレームのインデックスに指定させます。

    to_boss=[]
    for i,column in enumerate(update_df.columns.tolist()):
        if i>5:
            to_do_column=update_df[column].where(update_df[column].apply(is_today)).tolist()
            to_do_index=update_df[column].where(update_df[column].apply(is_today)).index.tolist()
            indices=[j for j,to_do in enumerate(to_do_column) if str(to_do)!='nan']
            if indices!=[]:
                for k,id_ in enumerate(update_df.loc[indices,'管理ID'].tolist()):
                    to_boss.append(f'本日は ID_{id_} {column.replace("日","")} が {k+1} 件です。')
    
    
    [wks.append_row(row) for row in new_df_rows]
    
    return task4mail,to_boss 

報告する用の定型文を作るため、空のリストを作成します。

データフレームの列名を enumerate メソッドを使用し、対応するインデックス番号と一緒にループさせます。

定型文に必要なのは 「校正日以降」なので、インデックス番号6以降を指定します。ちなみに、インデックス番号は「0」からスタートします。

pandas の where() メソッドと 後で書くis_today関数をapply()メソッドに渡し、列名と今日の日付ごとに対応した、ステータスを定型文として、to_bossに付け足していきます。

その後、一気にスプレッドシートへアクセスし、内容を更新していきます。

Slackに送るための task4mail , to_boss を返します。

def is_today(x):
    global today
    date_=datetime.strptime(x,'%Y/%m/%d')
    if datetime.date(today)==datetime.date(date_):
        return True
    else:
        return False

先程の メソッドに渡す本日のタスクを探すための関数です。

一行づつ受け取った 日付情報を 引数x として受け取り、datetime オブジェクトに直し、本日の日付かどうかを判定します。

本日の日付ならば True 、違うなら False を返します。

def parse_dict(text_dict):
    task4mail=[]
    tasks={}
    for line in text_dict['Body'].split():
        try:
            task=re.search(r'[A-Z]{2}[\w]+件',line).group()
            task4mail.append(task)
            temp_key=task.split('が')[0]
            temp_value=task.split('が')[-1].replace('件','')
            tasks[temp_key]=int(temp_value)
        except:
            pass
    return task4mail,tasks

受け取ったメール本文の辞書から、正規表現を使用して、種類別と件数別に分けます。

try で re.search()が 例外を返しても、except で pass を指定して、for 文 が最後まで回るようにしていきます。

task4mail と tasks に振り分けます。

!!いよいよ実行

コマンドラインから

$ python main.py

を実行します。

するとスプレッドシートが更新され

Slackで指定したチャンネルにきちんと投稿されています。

いかがだったでしょうか?
実は、まだまだコードに無駄が多いなぁと思っている箇所もあり、もっとスマートにできたらいいなと思っています。

みなさんも、まずはコピー&ペーストでもいいと思いますのでチャレンジしてみてください!
PythonができるとAIや業務効率化の助けになりますよ!!

タグ:


参考になったら「いいね!」と「シェア」をお願いします!!

このエントリーをはてなブックマークに追加
AI(人工知能)セミナー開催中
AI(人工知能)入門セミナー AI(人工知能)入門セミナー IoT入門セミナー

AI入門ブログの中の人

AI研究所 研究スタッフ
通称:マサミ
アメリカ・サンフランシスコにある情報メディアの専門学校を卒業。大手金融会社での事務経験を経て、2016年9月よりAI研究所に入所。見習い研究員として、AI技術を日々勉強中。

Facebookページ