DX担当者になったら考えたいこと

Posted on 12/30/2020
このエントリーをはてなブックマークに追加

「DX(デジタル・トランスフォーメーション)」というキーワードを目にすることが大変多くなった。

この5年間の間のだけでもIndustry 4.0, Society 5.0, ディープラーニング, AIといった様々なキーワードが入れ替わり立ち替わり似たようなメッセージを発し続けている。 それらの描く美しい未来像に触れ技術の要素の習得に力を入れつつも、 思い通りにならない会社組織を恨めしく思ったり、社会の重鈍さに腹を立てたりしてきたエンジニアは少なくないと思う。 あるいは一歩進んで、そういった現状を言語化・図示した資料に出会い、社内でのICT介護のやりとりを思い浮かべながら、 「よくぞ言ってくれた、これを檄文にみんなを決起させよう」と気持ちを高ぶらせているところなのかもしれない。

私自身そんなエンジニアの一人なのかもしれない。

だが、これは「無敵の『DX』でなんとかしてくださいよォーー」の前兆ではないのか?

「『DX』は手段です」と、したり顔で公演する識者の講演に首がもげそうなくらい頷くモブへと一直線に舗装された道の入り口に立っているのではないか?

そんな未来に到達する前に少しだけ悪あがきをしてみたい。

DX担当者に任命されたら…

ある時、あなたは上司に呼び出されたとしよう。

なんと「社長から『DX』を推進するように指示があった。『DX』は何やらパソコンを活用する話で若い人の柔軟な考え方が大事だそうだから君に任せたい。」という。

なんとも危ない匂いのする話だ。どこかでダメと書いてあった始まりそのものではないか。

社長の頭の中

さて、よくわかりもしないDXをまるっと若手のあなたに押し付けた社長は全くのアホで要高度ICT介護の上司なのだろうか。

だが、特に一部上場企業などのサラリーマン社長であれば、実績を積み、類まれな何らかの才能を発揮してその立場についていると考えるのが妥当だろうし、その立場から様々な形で一流の指南役も社内外についていると見るのが良いと思う。

私自身もしがないサラリーマンで、社長の経験もなくなったこともない立場の人間の経験は想像でしか語れないが、幸い、"CEO Job Description"とググるとCEO(≒社長)の役割についていくつもの記事があり、 ドラッカーなど著名な経営研究家が色々な名言を残してくれている。

また、CEOの直面する「課題」などもアンケートが取られていたりする。

そういった情報をまとめると

  • (サラリーマン)社長の最も重要なミッションは「会社を永続させること」
であり、そのために会社に関するメッセージを顧客・社員・投資家・政府などあらゆる方面とやりとりし、 それらをまとめ上げながら実行に移していく(指示を出す)といった役割を担っているようだ。

会社が永続できなくなる落とし穴は山ほどある。

もっとも当たり前のものは既存事業のシェアを落とすなどしてコストが売上より大きくなり赤字転落後そのまま運転資金が枯渇すること。「永続」すれば良いと言ってゾンビ飛行ギリギリとなってしまえば、成長が期待できない会社として投資家に愛想をつかされ、これもまた資金調達が苦しくなる。

だが、現在においては、人種や性別などの多様性対応を誤り、顧客離れ、製品の生産やサービスの提供が止まってしまったり、温暖化などの環境対策を狙いとした政策への対応に失敗し厳しい罰則を受けたり、 品質検査の不正行為で賠償に発展したり、昨今コロナ禍で顕在化したように市場が蒸発したりしてしまうこともある。

全部優秀なスタッフに任せることができれば良いが、意外と社長によるきめ細かなマニュアル運転が必要になってしまっている企業は多いと思う。

そんな中、投資家、つきあいのある政治家(官庁/政治団体)、社外取締役から「御社のDXはどうなっていますか?」とくると、何かしらの回答準備が必要になってくる。

社長は思う。

「Industry 4.0, Society 5.0, AI, 自分がポジションに付く前に似たような話はたくさんあったじゃないか。なんで今また夏休みの宿題を最後の1日でこなさないとならないような状況に…。とはいえ、なんとか形にしていかないと。」

デジタルネイティブな会社であれば、社長になったあとでデジタル技術に対する意識が急激に高まるという間抜けな話にはならないだろう。だが、生産や営業といった役割分化した組織の中で上がってくると、いくらゼネラリスト養成学校のJTCといえども壁の向こうを本当の本気で見るのは社長になったときくらいだ。

DX担当者として…

過小評価かもしれないがこうやって社長の頭の中を仮定すると、DX担当者として最初に着手することは「会社の永続」と「DX」を結びつけることだ。

そして、その方向性としては大きく分けると

  1. 既存事業のExploit(売上増加[アップセル]/コスト削減)
  2. 新規事業のExplore
の2つになる。

幸い、この方向でまとめている記事を見つけたので紹介する。

この記事と重複する部分もでてくるが、次回は、ここからさらに具体的な打ち手の選択肢へと掘り下げたい。

メモ

vueのプロジェクト設定メモ

Posted on 12/28/2020
このエントリーをはてなブックマークに追加

メモの要略

vue-cliで作ったSingle Page Webアプリのプロジェクト設定でつまづいたところのメモ

  1. 環境変数(.envファイル)をvueのプロジェクトで読み込んで使う
  2. Vueファイルに対してESlintとPrettierを設定し、vscodeで使う

前提環境

  • vue
    • vue/cli: 4.5.9
    • vue: 2.6.12
  • eslint: 7.16.0
  • prettier: 2.2.1

1. 環境変数の読み込みをできるようにする

公式ドキュメント(Modes and Environment Variables | Vue CLI)によると、vue-cliでは.envファイルで定義した環境変数をプロジェクトやpublic/index.html内の埋め込み変数として使えるとのこと。 しかし、何が悪いのかはっきりしなかったが、デフォルトでは.envファイルを読み込まなかったので注意として記しておく。

解決策

プロジェクトにdotenv-webpackを追加すること

yarn add -D dotenv-webpack

.envファイルの作り方での注意

公式ドキュメント通り

  • .envファイル内で定義する変数名はPREFIXとしてVUE_APP_を与え、VUE_APP_SOMETHINGという名前にすること
  • .envファイルはpackage.jsonと同じ、プロジェクトのルートに置くこと
  • .envファイルを更新したら開発サーバ(yarn serve)を再起動すること

環境変数の使い方

.envファイルでVUE_APP_SOMETHINGという変数を定義したとして

  • .js/.vueではprocess.env.VUE_APP_SOMETHINGで参照する
  • public/index.htmlでは<%= VUE_APP_SOMETHING %>で参照する

参考

vue.js - Vue-cli 3 Environment Variables all undefined - Stack Overflow
同じような症状にあった方がいる様子。Dylan Nguyenさんがdotenv-webpackのインストールが必要だった旨に言及。

2. Vueファイルに対してESlintとPrettierを設定

2020年11月頃にPrettierのLintとの併用における公式設定が変ったらしいとのことで、Qiitaなどに転がっていた2020年6月頃の情報が古くなっていたことから試行錯誤。

これを読んでいるあなたも一旦公式ドキュメントを確認したほうが良いかも。情報古くなるの早すぎ…。

必要なパッケージ

プロジェクトに追加するものとして

yarn add -D eslint prettier eslint-plugin-vue eslint-config-prettier eslint-plugin-prettier
  • eslint: 7.16.0
  • prettier: 2.2.1
  • eslint-plugin-vue: 7.3.0
  • eslint-config-prettier: 7.1.0
  • eslint-plugin-prettier: 3.3.0

VSCodeに追加するものとして

設定ファイル

prettierrcは作らずにeslintrcに含めてしまいました

vscodeのsetting.json

veturを入れている多いのでvalidationを無効にする設定とeslintの有効対象設定. 心を込めた手作業にするためautosaveなどはonにしてない。

{
  "vetur.validation.template": false,
  "eslint.workingDirectories": ["./"],
  "eslint.validate": ["vue", "javascript", "html"]
}

.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es6: true,
  },
  plugins: ['prettier'],
  extends: [
    'plugin:vue/recommended',
    'eslint:recommended',
    'prettier',
    'prettier/vue',
  ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'prettier/prettier': [
      'error',
      {
        endOfLine: 'auto',
        singleQuote: true,
        semi: false,
      },
    ],
  },
  parserOptions: { parser: 'babel-eslint' },
}

参考

GMail APIとPandasでメールボックスの大掃除

Posted on 12/12/2020
このエントリーをはてなブックマークに追加

Googleアカウントの保存ポリシー変更の件、みなさんいかがされていますか?

Google ストレージの仕組みに関する今後の変更 - Gmail ヘルプ

Google Oneも契約しているので容量には余裕があるのですが、GMailには二度どころか一度も読むことのないようなメールが山と溜まっています。年末にもなりましたし掃除でもするかとポチポチ消しはじめ、 楽○さんやY○hooさんが熱心にお勧めしてくださっている人生を豊かにするサービスや商品に関するメールを心苦しながら読まぬうちに削除し、 Promotionカテゴリ下に28,000件あった未読メールのうち、14,000件まで減りましたが、 はて、それでも14,000件ある…残りがどこから来たのか気になったので分析することにしました。

GMail削除UI改善のお知らせ

これまでGMailの検索結果の「全部選択」はリスト表示されているページのメールだけがまとめて選択できたのはご存知でしょうか?本日付で試してみたところ「全部選択」後に選択範囲を「検索結果全体に拡張」できるようになっていたので、まとめて削除が大変捗るようになっています。

Work Package

  • GMail APIでメールリストを取得
  • Pandasで前処理
  • グラフ化

環境

  • Python 3.7
  • Windows 10
  • Scoop
    • wget

GMail APIでメールリストを取得

準備

Python Quickstart  |  Gmail API  |  Google Developersに従って何も迷うことはありません。 APIを有効化し、Desktopアプリ用のcredential.jsonを取得します。これはPythonコードと同じディレクトリに置きましょう。

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
wget https://raw.githubusercontent.com/googleworkspace/python-samples/master/gmail/quickstart/quickstart.py
python quickstart.py

スクリプト実行の際、ブラウザが開いて認証をするとtoken.pickleが保存され、以降のアクセスではこれを使うことができるようになります。

メールリスト取得

コードの全体はこちら⇒Fetch GMail List

メールのリストを取得してmessage.jsonというファイルに保存します。

メールIDの取得

messages().listのAPIでは、GMail内部で割り振られたメールIDを取得できます。

広告に分類されたメールを引っこ抜きます。リファレンスではlistでは好きな件数だけ情報を引き抜けるような事になっているようですが、実態としては1回のリクエストで500件までのようです。

try:
    maxResults = 500
    request = service.users().messages().list(userId='me', labelIds=['CATEGORY_PROMOTIONS'], maxResults=maxResults)
    while request is not None:
        results = request.execute()
        consumed_token += 5
        messages = results.get('messages', [])
        db += messages
        request = service.users().messages().list_next(request, results)
finally:
    df = pd.DataFrame(db)
    df['from'] = pd.Series(dtype='string')
    df['subject'] = pd.Series(dtype='string')
    df['timestamp'] = pd.Series(dtype='string')

    df.to_json('messages.json', orient='records')

メールヘッダー情報の取得: 'From', 'Subject', 'Date'

APIを叩ける回数を調べると1秒間に50件のメール情報まで引き抜けるようでしたので、ちょっとSleepさせてます。(Usage limits  |  Gmail API  |  Google Developers)

try: 
    for i in tqdm.tqdm(df.index):
        if not pd.isnull(df['from'][i]):
            # Skip fetched data
            continue
        result = service.users().messages().get(userId='me', id=df['id'][i], format='metadata', 
                                                metadataHeaders=['From','Subject','Date']).execute()
        consumed_token += 5
        body = result.get('payload',[])

        if not body:
            print("Token Limit per minutes may be reached")
            break

        for el in body['headers']:
            if el['name'] == 'From':
                df.loc[i, 'from'] = el['value']
            elif el['name'] == 'Subject':
                df.loc[i,'subject'] = el['value']
            elif el['name'] == 'Date':
                df.loc[i, 'timestamp'] = pd.Timestamp(el['value'])
        time.sleep(1/40)
finally:
    df.to_json('messages.json', orient='records')
    print("Consumed Token {}".format(consumed_token))

Pandasで前処理

さて、ここから取得した情報をjupyter notebookで内容分析します。

import numpy as np
import seaborn as sns
import matplotlib.pylab as plt
from matplotlib.ticker import PercentFormatter
import pandas as pd

import re

df = pd.read_json('messages.json')

前処理

まず、From形式情報からemailアドレスを取り出します。

def domain(row):
    m = re.search('\<(.*)\>', row)
    if m is None:
        return row
    return m.group(1)
df['email'] = df['from'].apply(domain)
# HEAT MAPを作るときに使う変数
df['Year'] = df['timestamp'].apply(lambda x: x.strftime('%Y'))
df['Month'] = df['timestamp'].apply(lambda x: x.strftime('%m'))

メール送信者の集計

aggregations = {
  'email': ['count']
}
grouped = df.groupby(['email']).agg(aggregations)
grouped.columns = ["_".join(x) for x in grouped.columns.ravel()]
grouped = grouped.sort_values('email_count', ascending=False).reset_index()

グラフ化

メール送信者のトップ20とメールボックスに占める割合を算出します

grouped["cumpercentage"] = grouped["email_count"].cumsum()/grouped["email_count"].sum()*100

fig, ax = plt.subplots()
ax.bar(grouped.email[0:20], grouped["email_count"][0:20], color="C0")
ax2 = ax.twinx()
ax2.plot(grouped.email[0:20], grouped["cumpercentage"][0:20], color="C1", marker="D", ms=7)
ax2.yaxis.set_major_formatter(PercentFormatter())

ax.tick_params(axis="y", colors="C0")
ax2.tick_params(axis="y", colors="C1")
for tick in ax.get_xticklabels():
    tick.set_rotation(90)
plt.show()

ついでにどのくらい熱心にメールを下さっているのかチェック

p = pd.pivot_table(df[df['email']==grouped['email'][0]], values='id', columns=['Year'] , index=['Month'], aggfunc='count')
sns.heatmap(p, cmap=sns.light_palette("seagreen", as_cmap=True),  linewidths=.5)

4年以上メールくれてる

まとめ

  • トップ20のメール送信者を削除すれば50%くらいが削除できそう
  • 読んでない広告メールは購読解除しよう