[Amazon Connect 拡張] オペレーション時間に祝日等を追加してみる

2019.11.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

CX事業本部の平内(SIN)です。

このブログは、「Amazon Connect 拡張」と題して、手順に従って設定するだけで、Amazon Connectの機能を拡張できるパーツを紹介するものです。

Amazon Connect(以下、Connect)では、オペレーション時間を設定し、営業時間に応じて処理を分岐することが可能です。

しかし、オペレーション時間の設定は、曜日と時間のみで、祝祭日や、特別な会社の休養日には対応できません。

今回は、このオペレーション時間をきめ細かく設定できるようにしてみました。

2 構成

構成は、以下のとおりです。

顧客から受けた電話のフローは、現在日時を元に営業時間かどうかを返すLambda関数を呼び出します(①)。呼び出されたLambda関数は、S3に置かれた設定ファイルを読み込み(②)、その判定結果を返します(③)。

判定結果が営業時間内であれば、キューに転送してエージェントを呼び出します(④)。時間外であれば、その旨のアナウンスを顧客に返し切断します(⑤)。

S3上の設定ファイルは、保守担当者によって適宜更新されます(⑥)。

3 設置方法

設置の要領は以下のとおりです。

(1) GitHub

必要なファイルは、GitHubから取得できます。

$ git clone https://github.com/furuya02/AmazonConnectExtension001.git

ファイル構成は、概ね以下のとおりになっています。

.
├── cdk
│   ├── cdk.json
│   ├── bin
│   │   └── cdk.ts <= CDKコード
│   ├── lib
│   │   └── cdk-stack.ts  <= CDKコード
│   ├── cdk.json
│   ├── src
│   │   └── lambda <= 時間判定を行うLambda関数
│   └── tsconfig.json
└── sample
    ├── OperationTimeFlow <= 問い合わせフローのサンプル
    └── OperationTime.txt <= 設定ファイルのサンプル

(2) CDKのデプロイ

CDKでビルド・デプロイします。CDKを初めて利用する場合は、cdk bootstrapが必要です。

% cd AmazonConnectExtension001
% cd cdk
% npm install
% npm run build
% npx cdk synth
% npx cdk bootstrap --profile MyProfile <= 当該アカウント(リージョン)で初めて利用する場合
% npx cdk deploy --profile MyProfile
% cd ..

デプロイが成功すると、最後にコンソールにS3のバケット名が表示されますのでコピーしておきます。

Outputs:
AmazonConnectExtension001Stack.SettingBucket = connect-ex-opetime-setting-bucket-xxxxxxxxxxxx

(3) 設定ファイルのアップロード

sampleの中にある設定ファイルの例(OperationTime.txt)を編集し、S3にアップロードします。

$ aws s3 cp sample/OperationTime.txt s3://connect-ex-opetime-setting-bucket-xxxxxxxxxxxx/ --profile=MyProfile

(4) Lambdaの有効化

Conectのインスタンスの設定画面で、問い合わせフロー > AWS Lambda から、connect-ex-opetime-functionnを追加します。

(5) 問い合わせフローからの利用

  • sample/OperationTimeFlowをインポートします。

  • AWS Lambda関数を呼び出すプロックの関数を選択するOperationTime

  • 設定を変更したら、公開します。

以上で、設置は完了です。

4 設定ファイル

S3上の設定ファイルは、以下のように、曜日ごとの営業時間及び、休日の2種類を設定するテキストファイルです。

(1) 曜日ごとの営業時間

カンマ区切りで下記のとおり

曜日,始業時間,就業時間

(2) 休日

1行に1件で設定します。年を省略した場合は、年に関係なく祝日と判定されます。

月/日
年/月/日

なお、空行や、#以降(コメント)は、無視されます。

OperationTime.txt(例)

月,09:00,19:00
火,09:00,19:00
水,09:00,19:00
木,09:00,19:00
金,09:00,19:00


1/1 #元旦
2019/1/8 #成人の日

1/13 # 会社の創立記念日

# 2/11 #建国記念日
2019/2/12 #建国記念日(振替)

2019/3/21 #春分の日

# 4/29 #昭和の日
2019/4/30 #昭和の日(振替)

5/3 #憲法記念日
5/4 #みどりの日 
5/5 #こどもの日

2019/7/16 #海の日

8/11 #山の日

2019/9/17 #敬老の日(9月第三月曜日)
# 9/23 #秋分の日
2019/9/24 #秋分の日(振替)

2019/10/8 #体育の日(10月の第2日曜日)

11/3 #文化の日
11/23 #勤労感謝の日
# 12/23 #天皇誕生日
2019/12/24 #天皇誕生日(振替)

5 参考

参考までに、今回作成したAWS CDK及び、Lambdaのコードを少し紹介させて下さい。

(1) AWS CDK

作成しているリソースは、S3のバケットとLambdaファンクションのみです。

import cdk = require('@aws-cdk/core');
import * as lambda  from '@aws-cdk/aws-lambda';
import * as s3  from '@aws-cdk/aws-s3';
import * as iam from '@aws-cdk/aws-iam';

// 識別するためのタグ
const tag = "connect-ex-opetime";
const settingFile = "OperationTime.txt";
// Lambdaのタイムゾーン
const timeZone = 'Asia/Tokyo';

export class AmazonConnectExtension001Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 設定ファイルを保存するバケット名
    const bucketName = tag + "-setting-bucket-" + this.account;

    //設定ファイル保存バケット
    const settingBucket = new s3.Bucket(this, tag + '-settingBucket', {
      bucketName: bucketName,
      removalPolicy: cdk.RemovalPolicy.DESTROY
    })
  
    // 営業時間判定ファンクション
    const opeTimeFunction = new lambda.Function(this, tag + '-function', {
      functionName: tag + "-function",
      code: lambda.Code.asset('src/lambda'),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_10_X,
      timeout: cdk.Duration.seconds(3),
      environment: {
          SETTING_BUCKET: settingBucket.bucketName,
          SETTING_FILE: settingFile,
          TZ: timeZone
      }
    });
  
    // 設定ファイルのRead権限追加
    opeTimeFunction.addToRolePolicy(new iam.PolicyStatement({
      resources: [settingBucket.bucketArn + "/" + settingFile],
      actions: ['s3:GetObject'] }
    ));
  
    // 出力に設定ファイル用バケット名を表示
    new cdk.CfnOutput(this, "SettingBucket", {
      value: settingBucket.bucketName
    });
  }
}

(2) Lambda

設定ファイルを読み込んで、現在時間から営業時間内の場合、{ inTime: true }、外の場合、{ inTime: false }を返します。

import AWS = require("aws-sdk");

const s3 = new AWS.S3();
const settingBucket = process.env.SETTING_BUCKET!;
const settingFile = process.env.SETTING_FILE!;
 
exports.handler = async (event: any) => {
  console.log(JSON.stringify(event));

  // オペレーション時間の取得
  let data = await s3.getObject( {
      Bucket: settingBucket,
      Key: settingFile
  }).promise();

   if (!data || !data.Body) {
    throw new Error("Read error " + settingFile);
  }
  let lines = data.Body.toString().split('\n');

  // コメント、余分な空白、及び、無効(空白)行の削除
  lines = lines.map( l => l.replace(/#.*$/, '').replace(/\s+$/, '')).filter( l => l != '');
  // 時間内かどうかのチェック
  return { inTime: CheckInTime(lines) };
}
 
function CheckInTime(lines: string[]) {
  const weekdays = ["日", "月", "火", "水", "木", "金", "土"];
  // 現在日時
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth() + 1;
  const day = now.getDate();
  const week = now.getDay();
  const hour = now.getHours();
  const miniute = now.getMinutes();

  //--------------------------------
  // 設定取得
  //--------------------------------
  // 曜日指定の抽出 金,09:00,19:00
  const weeks = lines.filter(l => 0 < weekdays.indexOf(l.split(',')[0]));
  // 祝日指定の抽出 yyyy/mm/dd
  let holidays = lines.filter(l => l.split(',')[0].split('/').length == 3);
  // 祝日指定の抽出  mm/dd 
  holidays = holidays.concat(lines.filter(l => l.split(',')[0].split('/').length == 2).map(date => { return year + '/' + date }));

  //--------------------------------
  // 曜日指定のチェック
  //--------------------------------
  let flg = false; // デフォルトで時間外(設定がない場合時間外となるため)
  const nowMiniute = (hour * 60 + miniute ); // 現在の分
  // 時間文字列を分に変換する 01:01 => 61
  const getMiniute = function(timeStr: string) {
    const [h,m] = timeStr.split(':');
    return Number(h) * 60 + Number(m);
  };
  weeks.forEach( line => {
    const [weekStr,start,end] = line.split(',');
    if(week == weekdays.indexOf(weekStr)) { // 当該曜日の設定
      // 営業時間内かどうかのチェック
      if(getMiniute(start) <= nowMiniute && nowMiniute <= getMiniute(end)){
        flg = true;
      }
    }
  });
  // 曜日指定で時間外の場合は、祝日に関係なく時間外となる
  if(!flg) {
    return false;
  }

  //--------------------------------
  // 祝日指定のチェック
  //--------------------------------
  flg = true; // デフォルトで時間内(設定がない場合時間内となるため)
  holidays.forEach( l => {
    const [y,m,d] = l.split('/');
    if(year == Number(y) && month == Number(m) && day == Number(d)) {
      flg = false;
    }
  })
  return flg;
}

6 最後に

今回は、オペレーション時間の設定を独自の設定ファイルで自由に変更できるような拡張機能を作成してみました。 日本で使用する場合、恐らく祝日への対応は必須だと思います。このような拡張を導入することは有効なのでは無いでしょうか。