Alexa reminderAPI パーミションチェックは会話の早い段階でやらないとUXを損なうという話


(1)リマインダーを実装した。しかし問題が起きてた(^o^)

以前書いた記事の内容をついに先月実装しました。
「Alexaスキル のリテンション率向上のために、リマインダーAPIの導入を検討した話、およびサンプルコード」

リマインダー実装前ではこうだった会話が、

Alexa:ではプランクをします。(中略)
 あと3秒、2秒、1秒、終わりです。

リマインダー実装後はこうなりました(^o^)

Alexa:ではプランクをします。(中略)
 あと3秒、2秒、1秒、終わりです。
 リマインダーをセットしたい場合、あさっての0時から23時の間で登録できます。何時にしますか?
User:8時
Alexa:8時ですね。午前ですか?午後ですか?
User:午後
Alexa:リマインダーにセットしました。時間になると私が喋ってお知らせします。日時を変更する場合は、アレクサアプリのリマインダーで編集してください。お疲れ様でした。

よかったことと、新たな課題

その時間に家に居ないとキャッチできないので、Echo Dotなど画面なし端末だと気づかないで終了、という問題が起きがちでした。
こりゃリマインダーは期待したほど効果ないな〜と思ってたんですが、
Echo Showを使うようになったら、リマインダーの時間の前後に、スクリーンにずっとリマインダーを表示してくれるんで、
「あ、今日は体操の日だな」と確実に思い出せるようになりました。(^o^)

しばらくこの形で自分ちで運用テストしてからAlexaスキルストアでもバージョンアップしました。
しかし、某さんから以下のように指摘をうけました。
「ユーザーがパーミションを設定してないと、途中まで進んでからエラーで弾かれるので、がっかりかもしれませんね」と。

Alexa:ではプランクをします。(中略)
 あと3秒、2秒、1秒、終わりです。
 リマインダーをセットしたい場合、あさっての0時から23時の間で登録できます。何時にしますか?
User:8時
Alexa:8時ですね。午前ですか?午後ですか?
User:午後
Alexa:リマインダーをセットする権限が無いようです。

たしかに、時間を聞いたし午前か午後かも言わせてから、「権限無いっす」と・・。
しかも、既存ユーザーの人は改めてスマホアプリを開くことなんて滅多にないはず。
これはアカン!

エラーハンドリングが中途半端なまま寝落ち。。で、配慮不足のコードをそのまま出してしまったもよう。
情けない。。

ユーザー数も大きく減ってしまいましたし、せっかく先月から頂けるようになったリワードの金額も半分に減ってしまいました。
Oさんからのご指摘はバージョンアップリリース直後にいただいてたんで、その時すぐに直せばよかったのに、
後回しにしちゃってて、リワードの金額を見てケツに火がついた次第です。(^^;)

(2) 配慮が足りない

もういちど、唐突ですが長州力で再現します。

Alexa:ではプランクをします。(中略)
 あと3秒、2秒、1秒、終わりです。   
 リマインダーをセットしたい場合、あさっての0時から23時の間で登録できます。何時にしますか?  ←ここで気づけ!
長州力:8時
Alexa:8時ですね。午前ですか?午後ですか?
長州力:午後
Alexa:リマインダーをセットする権限が無いようです。 
長州力:なんだとこの野郎!

そりゃ長州じゃなくても怒るよなぁ。。

長州力氏の神経を逆撫でする前、常識で考えて2往復前の会話に入れるべきでした。
うう。。

(3)知恵を振り絞って直したコード

UNAUTHORIZEDの場合は、ユーザー自身に、アレクサアプリで、リマインダーに関する権限をONにしてもらう必要があります。
調べていると、こういう記事が見つかりました。

How to Use the Reminders API with the Alexa Skills Kit Software Development Kit for Java
January 29, 2019
Suhem Parack

天の助け!
こちらを活用して、リマインダーをセットする前にリマインダーのパーミションがONかOFFかを確認し、
会話の流れを分岐することに成功しました。

Alexa:ではプランクをします。(中略)あと3秒、2秒、1秒、終わりです。

**(パーミションがOFFの場合)** 
Alexa:次のワークアウトの時間をリマインダーに登録することができるようになりました。
   よろしければ、アレクサアプリを開いてアクティビティーをご覧ください。

**(パーミションがONの場合)** 
Alexa:リマインダーをセットしたい場合、あさっての0時から23時の間で登録できます。何時にしますか?
User:8時
Alexa:8時ですね。午前ですか?午後ですか?
User:午後
Alexa:リマインダーにセットしました。時間になると私が喋ってお知らせします。日時を変更する場合は、アレクサアプリのリマインダーで編集してください。お疲れ様でした。

どうですか、自然になりましたよね?? (๑˃̵ᴗ˂̵)و

追加したコードは以下です。
パーミションチェックは、リクエストJsonの「handlerInput.requestEnvelope.context.System.user.permissions」に値が入ってるかどうかで判断しています。
複数のパーミションをチェックする場合はもっと複雑な書き方になると思うのですが、このスキルでは「リマインダーAPI」1個だけでしたので、
この書き方でOKでした。

修正前index.js

          //プランクのセリフを仕込む
          var speechText  = 'プランク・・・・・・・・・あと3秒、あと2秒、あと1秒、終わりです。'; 

          speechText = speechText + '<break time="1000ms"/>お疲れ様でした。<break time="500ms"/>またお会いしましょう。' ; 
          return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .withShouldEndSession(true)        
            .getResponse();
修正後index.js

          //プランクのセリフを仕込む
          var speechText  = 'プランク・・・・・・・・・あと3秒、あと2秒、あと1秒、終わりです。'; 

          //speechText = speechText + '<break time="1000ms"/>お疲れ様でした。<break time="500ms"/>またお会いしましょう。' ; 
          //return handlerInput.responseBuilder
          //  .speak(speechText)
          //  .reprompt(repromptText)
          //  .withShouldEndSession(true)        
          //  .getResponse();

          //追加ここから---------------------

          //パーミションをチェックして、処理分岐する。
          var permissions = handlerInput.requestEnvelope.context.System.user.permissions;
          if (permissions == null){
            //まだパーミションをオンにしてない人は、2回に1回、ガイダンスを喋りカードも送出する。
            if ((history.length % 2) < 1){ 
              //偶数回。historyというのは過去の起動日時を永続セッションに配列化して記録したもの。
              speechText = speechText + '<break time="1000ms"/> 次のワークアウトの時間をリマインダーに登録することができるようになりました。<break time="500ms"/>よろしければ、アレクサアプリを開いてアクティビティーをご覧ください。';
              speechText = speechText + '<break time="1000ms"/>お疲れ様でした。<break time="500ms"/>またお会いしましょう。' ; 
              return handlerInput.responseBuilder
                .speak(speechText)
                .withAskForPermissionsConsentCard(['alexa::alerts:reminders:skill:readwrite'])
                .withShouldEndSession(true)
                .getResponse();
            } else {
              speechText = speechText + '<break time="1000ms"/>お疲れ様でした。<break time="500ms"/>またお会いしましょう。' ; 
              return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(repromptText)
                .withShouldEndSession(true)        
                .getResponse();
            }
          } else {
            //リマインダー権限あり
            speechText  = speechText + '<break time="1000ms"/>リマインダーをセットしたい場合、あさっての0時から23時の間で登録できます。何時にしますか?'; 
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(repromptText)
                .withShouldEndSession(false)        
                .getResponse();
          }


          //追加ここまで---------------------

これはAlexa Developerに登録してあるスキルのバージョンアップは不要でしたので、先ほどサーバーサイドでしれっとコードを上書きして、差し替え完了しました。
ふー。

(4) 残った課題

(1)のコードの中で、エラーハンドリングが二つあり、そのうち一つは解決策がまだ見つかってません。

  • リマインダーのパーミションをオンにしてない場合(UNAUTHORIZEDの場合)
    →こちらは今回対応できました。

  • リマインダー対応機種じゃあ無い場合(DEVICE_NOT_SUPPORTED)
    →こちらは事前判定の方法が見つからず、手当ができてません。
     リマインダー対応機種:Amazon Echo Dot、Echo Showは対応でした。他はわからず。
     リマインダー非対応機種:スマホ用のAlexa Appは確実に非対応でした。他はわからず。

なんかいい方法が見つかったら、事前判定のロジックを入れようと思います。

以上、やってはいけない、影響力甚大なアンチパターンの報告でした!(^o^