AlexaからAKASHIに打刻してみた

「アレクサ、出勤打刻してー!」

そんな一言から仕事が始められたらいいなという人向けの記事です。


私は、勤怠管理として AKASHI を使っていますが、

普段はテレワークをすることが多く、出勤連絡するまでに以下のように手順が多くて煩わしく感じていました。

  1. PCを起動(パスワード入力)
  2. VPN接続(パスワード入力)
  3. ブラウザ起動
  4. AKASHIを開く
  5. ID/パスワード入力して認証
  6. ようやく出勤打刻

Alexaに「出勤打刻して!」とワンアクションで出勤連絡できないか試したので記します。

AKASHIでアクセストークンの発行

  1. AKASHIにログイン
    • まだの方はトライアル申し込みから始めてください。無料期間もあるようです。
  2. マイページ >APIトークンからアクセストークンを作成し、コピーして控えておきます。
    • サイドメニューに表示がない方は所属会社のシステム管理者の許可が必要です。
    • APIトークンの有効期限は3週間くらい。
      • APIトークンを有効期限前に更新する処理は今後実装予定。

Alexaスキルの作成

  1. Amazon開発者コンソールを開く
  2. スキル >スキルの作成 から作成します。
  3. 呼び出し > スキルの呼び出し名 を確認。
    • 2.の手順だと「タイムカード」になっています。「アレクサ、タイムカードを起動して」というとスキルが起動するようになります。
    • ポイント:音声認識になるので、Alexaが認識できない言葉(造語や同音異義語)を用いると、期待通りに動かないことがあります。AKASHIはまだブランド名として認識されていないようで、ここに「アカシ」と指定してもAlexaは「明石」や「証」と認識してスキルが起動しませんでした。他に「勤怠」も正しく認識される確率が半々くらいだったので避けました。結果「タイムカード」に落ち着きました。
  4. スロット > +スロットタイプ からスロットを作成します。
    • スロットは「出勤」「退勤」などの命令の種類になります。
    • 今回は「出勤」「退勤」「休憩開始」「休憩終了」の4つを作成しました。
      • AKASHI上は他に「直行」「直帰」も打刻できますが、在宅勤務では使わないので不要。
      • 同義語を登録することで、類似の言葉や聞き取りづらい用語を寄せたりすることができます。(任意)
        • 「終業」と言ってしまった場合やAlexaが「大金」と認識してしまった場合でも「退勤」と再認識させることができます。
  5. 対話モデル > インテント から命令の音声パターンを追加します。
    • インテントとは「出勤打刻して」の「出勤打刻」の部分です。「XXXXして」という命令がくるよ。ということをAlexaに設定します。
      • {キーワード}の形式で書きます。今回は「XXXX打刻して」「XXXXして」「XXXX」の3つを作成しました。
        • 「出勤」打刻して、「休憩開始」して、「退勤」のような3つの言い方ができるようにしました。自分のみが利用する分には自分の言い回しが決まっていると思うのでそれに合えば特定の1種類だけ作成するだけでも良いです。
    • インテントスロット からインテントと先に作成したスロットをプルダウンから選択して紐づけます。
    • 最後に保存ボタンとデプロイボタンを押して完了です。Amazon開発者コンソールは一旦ここまでで次にAWS LambdaでAPI呼び出しを実装します。

Lambda関数の作成

  1. AWSコンソールにログインします。持っていない方はサインアップから登録が必要です。
    • AWSの利用には料金が発生することがあります。
  2. 次を参考にLambdaの関数を作成します。
  3. AKASHIのAPIを実行するメソッドを追加します。2.で記載したコードから差分だけ記載します。
    • スロット名は自身で設定したものに変更して下さい。
    • ACCESS_TOKENは控えておいたAKASHIのアクセストークン、COMPANY_IDはAKASHIにログインする際の企業IDをLambdaの環境変数に設定します。
    • import os  
      import json    
      import urllib.request  
      from urllib.parse import urlencode 
      from datetime import datetime
      ・・・
      class AkashiStampIntentHandler(AbstractRequestHandler):
          def can_handle(self, handler_input):
              return ask_utils.is_intent_name("KintaiIntent")(handler_input) # カスタムインテント(KintaiIntent=XX打刻、XXして)と言われたら発火する
      
          def handle(self, handler_input):
              slots = handler_input.request_envelope.request.intent.slots
              kintai_original_value = slots['KintaiType'].value # カスタムスロット(出勤、退勤、休憩開始、休憩終了)の言葉の変数を抽出
              kintai_values = slots['KintaiType'].resolutions.resolutions_per_authority[0].values
      
              action_codes = {
                  11: '出勤',
                  12: '退勤',
                  21: '直行',
                  22: '直帰',
                  31: '休憩開始',
                  32: '休憩終了'
              }
      
      
              if kintai_values is not None:
                  type_value = int(kintai_values[0].value.id) # 同義語含めて該当するスロットのid
              else: 
                  type_value = None
      
              print(f"スロット: {kintai_original_value}")
              print(f"スロットname: {kintai_values[0].value.name}")
              print(f"type_value: {type_value}")
      
              token = os.environ['ACCESS_TOKEN']
              company_id = os.environ['COMPANY_ID']
              stamped_at = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
      
              if any(word in kintai_original_value for word in ["テレワーク", "リモートワーク", "在宅勤務"]):
                  telework = True
                  telework_msg = "テレワーク"
              else:
                  telework = False
                  telework_msg = ""
      
              # POSTリクエストのパラメータを構築
              request_params = {
                  'token': token,
                  'type': type_value,
                  'stamped_at': stamped_at,
                  'telework': telework,
                  # 'timezone': timezone
              }
      
              # POSTリクエストを送信するURL
              url = f"https://atnd.ak4.jp/api/cooperation/{company_id}/stamps"
      
              # POSTリクエストを構築
              data = json.dumps(request_params).encode('utf-8')
              headers = {'Content-Type': 'application/json'}
              request = urllib.request.Request(url, data=data, headers=headers, method='POST')
      
              # リクエストを送信し、レスポンスを取得
              with urllib.request.urlopen(request) as response:
                  response_data = response.read().decode('utf-8')
                  response_code = response.getcode()
      
              # レスポンスを出力
              print(f"Response Code: {response_code}")
              print(f"Response Body: {response_data}")
      
              # JSON文字列をPythonのデータ構造に変換
              response_data = json.loads(response_data)
      
              # successとtypeの値を取り出す
              if response_data['success']:
                  type_value = action_codes[response_data['response']['type']]
                  time_value = response_data['response']['stampedAt'].split(' ')[1][:5]
                  alert_count = response_data['response']['alertCount'] or 0
                  alert_count = response_data['response']['alertCount'] or 0
                  speak_output = f"{time_value} に{telework_msg}{type_value} 打刻をしました。アラート件数は{alert_count}件です。"
              else:
                  speak_output = "打刻に失敗しました。"
      
      
              return (
                  handler_input.response_builder
                  .speak(speak_output)
                  .response
              )
      
      
      sb = SkillBuilder() # 元からあるコード
      
      sb.add_request_handler(LaunchRequestHandler()) # 元からあるコード
      sb.add_request_handler(AkashiStampIntentHandler()) # ★追加
      
    • 画面のデプロイボタンを押します。しばらく待ちます。

シミュレータでのテスト

  1. Amazon開発者コンソールからテストできるので開きます。
  2. 「テスト」をクリックし、「開発中」プルダウンを選択します。
  3. Alexaシミュレータから文字入力またはマイクで指示を出します。
    • 「タイムカード起動して」と言うと「ハロー」と返事が返ってくれば成功です。
    • その後に、「出勤打刻して」と言うと、「何時何分に出勤打刻をしました。アラート件数は~」と言うはずです。
      • AKASHIの打刻画面または出勤簿を確認し、出勤打刻ができていれば成功です。
    • 「テレワーク開始」「テレワーク終了」というと、テレワーク記録がAKASHIに登録されます。

実機での検証

  1. スマホのAlexaアプリを起動します。
    • その他 > マイスキル > 開発 から「タイムカード」を選択します。
    • スキルを有効にします。既に有効になっていた場合も一度無効してから有効する方が動作が安定します。
      • この操作をしないとスキルのデプロイがすぐに反映しない場合があります。Alexaアプリから命令したらうまくいくのにAmazon Echoからはうまく動作しない場合はこの手順を実施してください。
  2. Amazon Echoに、シミュレータの時と同じように音声で命令してみてください。
    • 「アレクサ、タイムカードで退勤打刻して」と1文で命令することもできます。
    • 途中でも触れましたが、スキルの呼び出し名を「タイムカード」以外にした方は言葉によってはAlexaが誤認識した可能性があります。

今後のAKASHIに期待すること

  1. Alexaのアカウントリンク機能が使えるようにOAuth連携できればこのスキルを公開することができるので対応してほしいです。
  2. 他のAKASHIのAPIもAlexaから使えるようにしたい。
    • AKASHI 公開API 仕様
    • 工数管理も使っているが工数の記録がめんどうだったり忘れたりします。音声でタスクの開始終了が自動計算されるとか、そこまでいかなくとも、音声の履歴がAKASHIの工数画面から見れれば工数入力する際のメモにできるかなと思いました。

参考

【Rails4】【heroku】PDFを生成する

環境

Rails 4.1.8
CentOS(開発環境)
heroku

目的

Railsで画面をPDFで出力できるようにする
herokuでも上記のことができる


1. PDF出力Gemの選定
いくつかあるが、今回は画面をそのままPDF出力できればよいのでPDFkitかWicked PDFの二択だった。
PDFkitの方がPDFを保存する方法が簡単そうだったのと、比較的開発が活発だったため。


2. Gemfile に以下を追記して、bundle installする

  gem 'wkhtmltopdf-binary'
  gem 'pdfkit'


3. ViewをPDFで出力する
ほとんど他のサイトの通り。1点bootstrapを当てたい場合だけ補足。

  # stylesheetはcssにコンパイルされたものしか反映されないよう(scssもダメだった)。bootstrapをgemでインストールしている場合の指定方法。
  html = render_to_string template: "sample/show"
  pdf = PDFKit.new(html, encoding: "UTF-8")
  pdf.stylesheets << "#{Gem.loaded_specs['twitter-bootstrap-rails'].full_gem_path}/app/assets/stylesheets/twitter-bootstrap-static/bootstrap.css.erb"
  pdf.to_pdf
  # gemはvendor配下に入っているため、そのパスまで辿って指定した。herokuでも効いている。


4. エラーの対処
to_pdf したところで下記のエラーが出たので、その対応。

  error while loading shared libraries: libfontconfig.so.1: cannot open shared object file: No such file or directory

ローカルの開発環境にライブラリが足りないらしいのでインストールした

  yum install libXext  libXrender  fontconfig  libfontconfig.so.1


5. 文字化けの対応
4まででPDFが出るようになったが、日本語が文字化けしているのでその対応
こちらのサイトのIPAフォントを入れる箇所をそのまま実行


6. Herokuでの文字化け対応
もちろん5は開発環境のみ有効。アプリのルート直下に.fontsディレクトリを作成して、5でインストールしたttfファイル2つをコピー

  mkdir .fonts
  cp /usr/share/fonts/japanese/TrueType/*.ttf .fonts/


7. 以上をコミットしてheroku にpushすればherokuでもPDFが出力される



番外. ここまでたどり着くのに紆余曲折あったので、失敗した方法も記載する
herokuでもPDFが使えるようにするために、PDFkitのGithub wikiにある方法を実施したが、apt−getがどうのこうのでpush失敗。
次にgem wkhtmltopdf-heroku を使ったところ、herokuへのpushは成功したがherokuのDynoが消えた…
焦って、wkhtmltopdf-heroku 関連のコミットを削除したところheroku復活し事なきを得る。具体的に何が悪かったかは不明。
参考にしたサイトによるとbuildpack を使うとアプリが最悪壊れることもあるらしい。
後に6番の手法に辿り着いた。


Ruby/RailsでPDF作成Gemのまとめ - Rails Webook
GitHub - pdfkit/pdfkit: A Ruby gem to transform HTML + CSS into PDFs using the command-line utility wkhtmltopdf
http://d.hatena.ne.jp/deeeki/20120902/heroku_wkhtmltopdf_fonts
herokuで日本語フォントを奇麗に使うには? - Qiita

【Rails4】【heroku】NewRelic をherokuにインストールする

環境

Rails 4.1.8
NewRelic
heroku

目的

herokuNewRelicをインストールする
herokuがsleepするのを防ぐ

1. heroku コマンドでnewrelicをインストールする

  heroku addons:add newrelic:stark

2. Gemfile に以下を追記して、bundle installする

  gem 'newrelic_rpm', group: :production

3. newrelic.ymlをコピーする

  curl https://gist.githubusercontent.com/rwdaigle/2253296/raw/newrelic.yml > config/newrelic.yml
  # git push heroku master でアップするのを忘れないこと

4. heroku コマンドで当該のアプリ名を設定する
アプリ名を"sample"とする

  heroku config:set NEW_RELIC_APP_NAME="sample"

ここからNewRelicでherokuをsleepさせない設定
※NewRelicに登録したアプリが反映されるまで5分程かかる
5. herokuのダッシュボード-[sample]-[resources]-[New Relic APM]

6. New Relic APM-sampleの設定リンク(歯車アイコン)押下-[Change settings]-[Availability monitoring]
URL to monitor に https://sample.herokuapp.com/ (アプリのherokuページのURL)を記入して保存する

New Relic APM | Heroku Dev Center
1DynoなHerokuをNew Relicでアイドルを回避 - ぴよログ

【rails】【heroku】【bootstrap】herokuでCSS、font、JavaScriptが反映されない。

環境

Rails 4.1.8
Bootstrap3
twitter-bootstrap-rails 3.3.2.0
heroku

目的

herokuでbootstrapのCSSやfontが反映されること
fontawesomeが見つからないエラーが出ないこと

1. config/environments/production.rb の設定を書き換える

  config.serve_static_assets = true

  config.assets.compile = true # fontawesomeを表示させるのに必要だった

2. Gemfile に以下を追記

  gem 'rails_12factor', group: :production

【Rails4】【heroku】【CentOS】sessionストアにRedisを使う

環境

Ruby 2.1.5
Rails 4.1.8
CentOS 6.6
heroku

目的

  • RailsアプリのセッションストアをキャッシュからRedisに切り替える
  • 開発環境にRedisサーバを立てる
  • 本番環境はherokuでRedisのアドオンを入れる

1. 開発環境(CentOS)にRedisサーバを立てる

# 参考にしたサイトの手順通り
# EPALが入っていなかったのでEPALもインストールする
rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
yum --enablerepo=epel -y install redis

# サービスの起動と自動起動の設定
/etc/init.d/redis start
chkconfig redis on

# 確認
redis-cli

2. 開発環境(Rails)にRedisのgem(redis-rails)をインストールする

# Gemfile
gem 'redis-rails’

# デフォルト(cookieストア)の設定をコメントアウト
# config/initializers/session_store.rb
# Rails.application.config.session_store :cookie_store, key: '_XXXXXX_session'

# 開発環境用のRedisの設定追加
# config/enviroments/development.rb
config.session_store :redis_store, servers: 'redis://localhost:6379/0', expire_in: 60.minutes
# expire_inはセッションの有効期限

# この後アプリで実際にsessionを使う操作をし、実際にsessionが入っているか確認した。手順は下記サイト参照

RailsのセッションストアとしてRedisを使う(Mac/EC2:AmazonLinux) - Qiita

3. herokuでRedisが利用できるようにする

# アドオンをインストールする(今回はredis-cloudを使用。freeプランあり)
heroku addons:add rediscloud
# herokuに環境変数が設定されているか確認
heroku config:get REDISCLOUD_URL

# config/enviroments/production.rb
# 先程確認した環境変数は以下のようにheroku上で参照できる
config.session_store :redis_store, servers: ENV["REDISCLOUD_URL"], expire_in: 60.minutes

# 変更をherokuにpush
git push heroku master

あとはheroku上でもアプリを動かしてみる。herokuのマイページから、redisのダッシュボードが参照できるので動いているか確認する。

redis−cloudのfreeプランは30MB、Max 10接続。
メモリ、接続が8割を超えるとアラートメールが飛ぶようにデフォルトでなっていた。とりあえずこれで逼迫するようなたプラン変更かDBストアにするか検討する。

herokuから削除された場合

  1. herokuのredis cloud上から新しいDBを登録する
    • DB名とパスワードを入力する
  2. Endpoint を確認する
  3. herokuコマンドでherokuの環境変数を変更する
heroku config:set REDISCLOUD_URL=redis://rediscloud:[パスワード]@[Endpoint]

【Rails4】【Ajax】インジケータを表示させる

環境

Rails 4.1.8
jQuery

目的

  • Ajax通信中にインジケータ(グルグル)を表示させて、通信終了後に非表示にする。
  • インジケータ表示中はモーダルの様に半透明のレイヤーを被せて、ボタン押下等ができないようにする

1. インジケータ(グルグル)のgif画像をDLし、assets/images/に配置
今回はこちらから
Ajaxload - Ajax loading gif generator

こちらも有名
Loader Generator - Ajax loader

2. Viewにインジケータとフォームを記述

# 今回はインジケータの下に「Loading…」と表示させる
<body>
  <div id="loading">
    <div id="sending-msg"><%= 'Loading…' %></div>
  </div>
  <%= form_for @sample, :url => {action: 'sample'}, :html => {class: 'ajax-load'}, remote: true do |f| %>
    <%= f.submit('Submit') %>
  <% end %>
</body>

3. CSS

/* インジケータを背景に表示。サイズを100%にすることでモーダルになる。 */
/* 背景色と透過率は好みで */
#loading {
  width: 100%;
  height: 100%;
  z-index: 9999;
  position: fixed;
  top: 0;
  left: 0;
  background-color: #f7f7f9;
  filter: alpha(opacity=65);
  -moz-opacity: 0.65;
  -khtml-opacity: 0.65;
  opacity: 0.65;
  background-image: url('../assets/ajax-loader.gif');
  background-position: center center;
  background-repeat: no-repeat;
  background-attachment: fixed;
  display: none;
}
/* 画像下の文字の表示 */
#sending-msg {
  text-align: center;
  padding-top: 26%;
  color: #000;
  font-weight: bold;
}

4. Javascript(Coffeescript)

/* 2でフォームにajax-loadクラスをつけたので、そのAjaxの送信前、送信完了後にインジケータ制御 */
$ ->
  $(document).on("ajax:before", ".ajax-load", (event) ->
    $('#loading').show() # インジケータ表示
  ).on("ajax:complete", ".ajax-load", (event) ->
    $('#loading').hide() # インジケータ非表示
  )

【Rails4】Dropboxにファイルをアップロードする

環境

Ruby 2.1.5
Rails 4.1.8

目的

  • RailsアプリからDropboxにファイルをアップロードする


0. Dropboxアカウントを取得し、下記からDropboxのアプリを作成する
Login - Dropbox
※キャプチャが少し古いが下記のサイトが参考になる
Dropbox APIをRuby on Railsから叩く方法

1. gem をインストールする。今回はdropbox-apiというgemを使用。bundle installも忘れずに。
※以前にdropbox-sdkを使用した際も比較的簡単に使用できたが、dropbox-apiでより簡単になったと思う。

# Gemfile
gem "dropbox-api"

2. 0で作成したアプリのApp keyとApp secretをRailsに設定する。
※ config/initializers/dropbox.rb を作成して追記。

# config/initializers/dropbox.rb
Dropbox::API::Config.app_key    = "Your App key"
Dropbox::API::Config.app_secret = "Your App secret"
Dropbox::API::Config.mode       = "sandbox" # if you have a single-directory app
# Dropbox::API::Config.mode       = "dropbox" # if your app has access to the whole dropbox
# 今回はRailsアプリからは0で作成したDropboxアプリにしかアクセスしないので"sandbox"を選択した

3. 設定した情報を基に認証画面を表示・承認して、アクセストークンを発行する

# controllers/sample_contoroller.rb
def authorize
    consumer = Dropbox::API::OAuth.consumer(:authorize)
    request_token = consumer.get_request_token
    session[:token] = request_token.token
    session[:token_secret] = request_token.secret
    url = request_token.authorize_url(:oauth_callback => url_for(action: :callback)) # 承認後に呼ばれるURLを指定。4で書くアクション。
    redirect_to url
 end

4. アクセストークンを取得する
※ config/initializers/dropbox.rb を作成して追記。

# controllers/sample_contoroller.rb
def callback
  consumer = Dropbox::API::OAuth.consumer(:authorize)
  hash = { oauth_token: session[:token], oauth_token_secret: session[:token_secret]}
  request_token  = OAuth::RequestToken.from_hash(consumer, hash)
  oauth_verifier = params[:oauth_verifier]
  result = request_token.get_access_token(:oauth_verifier => oauth_verifier)
  
  # 取得したtokenを利用してClientオブジェクトを生成。
  client = Dropbox::API::Client.new :token => result.token, :secret => result.secret
end

5. 実際に動かす
①authorizeのリンクを画面に追加して、押下する
Dropboxの承認画面が表示されるので承認する
③callbackアクションにリダイレクトする
※毎回アップロードするたびにtokenを発行することはないので、callback内でDBに保存する等しておくとよい。