AmazonConnect のオペレータ状態変化などをリアルタイムに取得する方法

# はじめに
この記事は、[NTTコミュニケーションズ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nttcom) 6日目の記事です。
# 概要
Amazon Connectのエージェントイベントストリームを利用してオペレータの状態情報等をAWS側にて取得してみました。
オペレータ側にて取得するのではなくサーバ側で一括取得できることから、CC全体のリアルタイム情報を取得するのに役立ちます。


## Amazon ConnectのAPIなど他の取得方法との違い
・APIでは各ユーザのステータスを取得することができない。
・CCPから情報を取得する場合、オペレータ側での情報取得となるので、集約することができない。
・イベントストリームでは、サーバ側にてオペレータ情報を集約取得できる。

# 今後の発展性
・座席管理システムなどオペレータの現在の状態をリアルタイムに外部で利用する場合での活用が考えられます。
・オペレータのステータス変化から終話や保留の時間を検知できるので、その時間情報をもとにアラートを出すなどの活用も考えられます。

# 環境
lambdaの環境はpython3.8です。
AmazonConnectの新機能であるchat機能については考慮していません。

# 構成
構成は下の図の通りになっています。AmazonConnectからイベントデータをKinesisに流し、lambdaをKinesisトリガで起動しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339600/4e93ce36-fc15-1d92-ae90-f3fa6a69723f.png)


# 事前設定方法

## ・kinesis data streamの作成
①AWSコンソールからkinesisを検索
②下記のようにデータストリームの作成をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339600/6f81e169-d594-869b-00fa-77b5d6ea1cc0.png)
③**Kinesis ストリームの名前**、**シャード数**を設定して作成。(シャード数はとりあえず1で大丈夫です。)

## ・AmazonConnectのインスタンスにてデータストリーミングの設定を実施
①AWSコンソールからAmazonConnect選択、資料するインスタンスにてデータストリーミングを選択。
②下記のようにデータストリーミングの有効化＋エージェントイベントに作成したストリームを設定。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339600/60063498-9726-3b49-6da9-817888b76752.png)

## ・lambda にてトリガーにkinesis data streamを設定
作成する際に必要な権限としては、
**AmazonKinesisReadOnlyAccess**
**AWSLambdaKinesisExecutionRole**
の２つです。

トリガの追加からkinesisを設定。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339600/b994daba-86d3-a2a1-23b2-dcfa6d72f615.png)
バッチウィンドウ等は必要に応じて変えてください。


# 取得方法
データは下のコードの様に、jsonのデータを取得し、それを利用して確認できる。
詳細は[こちらの公式ドキュメント](https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/connect-ag.pdf)の122ページ参照

## 取得できるものの例
・オペレータ名
・Offlineにした時間
・Availableにした時間
・ACW開始時刻(通話終了時刻)
・ACW終了時刻
・エージェントとの通話開始時刻
・通話保留開始
・通話保留終了

現状ではcloud watchに投げているだけですが、そのタイミングでDBにデータ保管などを行うことも可能です。

## 注意点等
・特殊なパターンはテストしてません。(転送・三者通話など)

## コード
```python3:lamda
import base64
import json

def lambda_handler(event, context):

    for record in event['Records']:
        b64_data = record['kinesis']['data']
        data = base64.b64decode(b64_data).decode('utf-8')
        kinesis_json_datas = json.loads(data)

        # ステータス変更のイベントのみを確認する
        if kinesis_json_datas["EventType"] == "STATE_CHANGE":

            # offlineに変更したタイミング
            if kinesis_json_datas["CurrentAgentSnapshot"] and kinesis_json_datas["PreviousAgentSnapshot"] and kinesis_json_datas["CurrentAgentSnapshot"]["AgentStatus"]["Name"] == "Offline" and kinesis_json_datas["PreviousAgentSnapshot"]["AgentStatus"]["Name"] != "Offline":
                print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                print("Offlineになりました：" + kinesis_json_datas["CurrentAgentSnapshot"]["AgentStatus"]["StartTimestamp"])

            # Availableに変更したタイミング
            elif kinesis_json_datas["CurrentAgentSnapshot"] and kinesis_json_datas["PreviousAgentSnapshot"] and kinesis_json_datas["CurrentAgentSnapshot"]["AgentStatus"]["Name"] == "Available" and kinesis_json_datas["PreviousAgentSnapshot"]["AgentStatus"]["Name"] != "Available":
                print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                print("Availableになりました：" + kinesis_json_datas["CurrentAgentSnapshot"]["AgentStatus"]["StartTimestamp"])

            # ACW開始タイミング。(通話終了タイミング)
            elif kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"] and kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"][0]["State"] == "ENDED":
                print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                print("通話終了タイミングです：" + kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"][0]["StateStartTimestamp"])

            # ACW→通話可能への変更タイミング
            elif kinesis_json_datas["PreviousAgentSnapshot"] and kinesis_json_datas["PreviousAgentSnapshot"]["Contacts"] and (not kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"]) and kinesis_json_datas["PreviousAgentSnapshot"]["Contacts"][0]["State"] == "ENDED":
                print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                print("ACW終了タイミング：" + kinesis_json_datas["EventTimestamp"])
            
            # 通話中のステータス変更について            
            elif kinesis_json_datas["PreviousAgentSnapshot"] and kinesis_json_datas["PreviousAgentSnapshot"]["Contacts"] and kinesis_json_datas["CurrentAgentSnapshot"] and kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"]: 

                # エージェントと繋がったタイミング
                if (not kinesis_json_datas["PreviousAgentSnapshot"]["Contacts"][0]["ConnectedToAgentTimestamp"]) and kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"][0]["ConnectedToAgentTimestamp"]:
                    print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                    print("エージェント接続タイミング：" + kinesis_json_datas["EventTimestamp"])

                # 通話保留タイミング
                elif kinesis_json_datas["PreviousAgentSnapshot"]["Contacts"][0]["State"] == "CONNECTED" and kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"][0]["State"] == "CONNECTED_ONHOLD":
                    print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                    print("通話保留開始：" + kinesis_json_datas["EventTimestamp"])

                # 通話解除タイミング
                elif kinesis_json_datas["PreviousAgentSnapshot"]["Contacts"][0]["State"] == "CONNECTED_ONHOLD" and kinesis_json_datas["CurrentAgentSnapshot"]["Contacts"][0]["State"] == "CONNECTED":
                    print("エージェント名：" + kinesis_json_datas["CurrentAgentSnapshot"]["Configuration"]["Username"])
                    print("通話保留終了：" + kinesis_json_datas["EventTimestamp"])

    return {'statusCode': 200, 'body': 'FINISH'}
```

#　終わりに
今回はCloudWatchに投げましたが、様々なシステムに投げることができるので色々連携する際に試してみてください。
明日は[@y-i](https://qiita.com/y-i)さんの記事です。