Amazon Connect 연락처 레코드를 사용하여 회의 및 전환 식별 - Amazon Connect

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

Amazon Connect 연락처 레코드를 사용하여 회의 및 전환 식별

고객 응대 레코드는 고객 센터의 고객 응대와 연결된 이벤트를 캡처합니다. 모든 새 연락처에 대해 Amazon Connect는 연락처 레코드를 생성하고 연락처에 고유한 고객 응대 ID를 할당합니다.

에이전트가 다른 에이전트와 상담할 때마다(Amazon Connect 내부 또는 외부, 수신자 부담 또는 내부 직통 전화번호 사용) Amazon Connect는 상담 레그 연락처 레코드를 생성하고 이 레그에 대한 새 고객 응대 ID를 발급합니다.

기본 연락처 레코드와 후속 상담 레그 연락처 레코드는 초기 고객 응대 ID, 다음 고객 응대 ID 및 이전 고객 응대 ID와 같은 여러 고객 응대 ID 필드를 통해 함께 연결할 수 있습니다.

이 주제에서는 이러한 필드를 사용하여 고객 응대 레코드에서 회의 및 전환을 구분하는 방법을 설명합니다. 또한 상담 통화, 회의 또는 전환과 같은 상담 작업 유형을 설정하는 로직을 제공합니다.

용어

이 주제에서는 다음 용어가 사용됩니다.

상담 통화

세 명의 참가자가 있는 통화:

  1. 개시자(예: 고객)

  2. 수신자(예: 에이전트)

  3. 상담 요청을 받는 참가자(예: 감독자 또는 외부 타사 번역사)

상담 통화는 상담 통화, 전환 통화 또는 회의 통화로 이어질 수 있습니다.

상담 통화

개시자가 보류 상태인 동안 수신자 에이전트가 다른 참가자(예: 동일한 Amazon Connect 인스턴스 또는 외부 엔터티의 에이전트)와 상담하는 통화입니다.

통화가 연결 해제되면 Amazon Connect는 에이전트를 통화 후 작업(ACW) 상태로 전환합니다. 이 상태로 전환되면 고객 응대 레코드가 타임스탬프로 업데이트됩니다. 상담 통화의 경우, 상담 요청을 받는 참가자가 고객보다 일찍 연결을 끊습니다.

고객 응대 레코드는 에이전트가 AfterContactWorkStartTimestamp 아래의 ACW 상태로 전환되었을 때의 타임스탬프를 기록합니다.

통화 전환

수신자는 상담 요청을 받는 참가자에게 개시자를 전환합니다. 이 경우 수신자 에이전트는 상담 요청을 받는 에이전트보다 먼저 ACW로 전환됩니다.

회의 통화

수신자는 개시자 및 상담 요청을 받는 참가자와 회의(3방향 통화)를 개시합니다.

Amazon Connect를 사용하면 4명 이상의 참가자가 함께 회의할 수 있습니다. 내부 통화의 경우, 상담 요청을 받는 참가자는 상담 및 회의 상황 모두에서 수신자보다 먼저 ACW로 전환됩니다. 그러나 차이점은 회의 상황에서 상담 요청을 받는 참가자도 고객과 대화할 수 있지만 상담 사례에서는 수신자가 고객을 보류 상태로 둔다는 것입니다.

다음 섹션에서는 고객 응대 레코드에서 이러한 각 유형의 통화를 식별하는 방법을 설명합니다.

상담 통화에 대한 고객 응대 레코드

고객이 에이전트 1에게 전화를 건다고 가정해 보겠습니다. 에이전트는 다른 사람에게 전환하거나 상담하지 않습니다. 통화 연결이 끊어지면 고객 응대 레코드는 다음 샘플과 같습니다(관련 필드만 표시됨).

{ "AWSAccountId": "account-id", "Agent": { "ARN": "agent-arn", "AfterContactWorkStartTimestamp": "2024-08-02T17:50:53Z", . . "Username": "Agent1" }, "ContactId": "497f04ca-6de1-408f-9b8a-ec57bcc99b31", . . "InitialContactId": null, "NextContactId": null, "PreviousContactId": null, . . }

에이전트 1이 다른 에이전트(에이전트 2)와 상담 통화를 개시하는 경우 상담, 전환 또는 회의가 됩니다.

다음 샘플 고객 응대 레코드는 개시 에이전트(에이전트 1)와 수신자 에이전트(에이전트 2)에게 어떻게 표시되는지 보여 줍니다.

  • 개시 에이전트(에이전트 1)

    { "Agent": { "ARN": "agent-arn" "AfterContactWorkStartTimestamp": "2024-08-02T17:50:53Z", . . "Username": "Agent1" }, "ContactId": "497f04ca-6de1-408f-9b8a-ec57bcc99b31", "InitialContactId": null, "NextContactId": "6aa058d3-e771-4544-8e93-f5ce9c9003b3", . . }
  • 수신자 에이전트(에이전트 2)

    { "Agent": { "ARN": "agent-arn", "AfterContactWorkStartTimestamp": "2024-08-02T17:51:07Z", . . "Username": "Agent2" }, "ContactId": "6aa058d3-e771-4544-8e93-f5ce9c9003b3", "InitialContactId": "497f04ca-6de1-408f-9b8a-ec57bcc99b31", "NextContactId": null, "PreviousContactId": "497f04ca-6de1-408f-9b8a-ec57bcc99b31", . . }

    고객 응대 레코드의 두 부분 간 관계는 다음 다이어그램에 나와 있습니다.

    상담 통화 중 에이전트 1과 에이전트 2 간의 관계입니다.

    여기서 에이전트 1(A1)과 에이전트 2(A2)는 다음에 의해 연결됩니다.

    • N = 다음 고객 응대 ID 이 필드는 초기 레그의 고객 응대 레코드에 나타납니다. 이 에이전트가 상담한 마지막 에이전트의 고객 응대 ID입니다(이 경우 마지막 에이전트는 A2임).

    • P = 이전 고객 응대 ID. 이 필드는 상담 레그의 연락처 레코드에 표시됩니다. 이 레그에 전화를 건 레그의 고객 응대 ID입니다. 이 경우에는 A1입니다.

    다이어그램에 표시되어 있지 않은 내용은 다음과 같습니다.

    • 초기 고객 응대 ID: 에이전트 1(A1)과 고객(C) 간 첫 번째 상호 작용의 고객 응대 ID입니다.

    • 고객 응대 ID: 지정된 상호 작용의 고유 식별자입니다.

    고객 응대 ID, 초기 고객 응대 ID 및 이전 고객 응대 ID는 시스템 속성입니다. 각각에 대한 설명은 시스템 속성 섹션을 참조하세요.

이 모델은 여러 에이전트가 참여하는 상담 통화로 확장할 수 있습니다. 다음은 확장 방법에 대한 예제 사용 사례입니다.

  • 사용 사례 1: 에이전트 1이 에이전트 2를, 에이전트 2가 에이전트 3을, 에이전트 3이 에이전트 4를 초대합니다. 이전 고객 응대 ID는 항상 이전 에이전트입니다. 다음 다이어그램에서 이 사용 사례를 보여 줍니다.

    A1은 A2를, A2는 A3을, A3은 A4를 초대하며, 이전 고객 응대 ID는 항상 이전 에이전트입니다.
  • 사용 사례 2: 에이전트 1이 에이전트 2를, 에이전트 1이 에이전트 3을, 에이전트 1이 에이전트 4를 초대합니다. 이전 고객 응대 ID는 항상 에이전트 1입니다. 다음 다이어그램에서 이 사용 사례를 보여 줍니다.

    A1이 에이전트 2를, A1이 A3을, A1이 A4를 초대하며, 이전 고객 응대 ID는 항상 A1입니다.
  • 사용 사례 3: 에이전트 1이 에이전트 2를, 에이전트 2가 에이전트 4 및 에이전트 5를, 에이전트 1이 에이전트 3을 초대합니다. 에이전트 2 및 3의 이전 고객 응대 ID는 에이전트 1입니다. 에이전트 4 및 5의 경우 이전 고객 응대 ID는 에이전트 2입니다. 다음 다이어그램에서 이 사용 사례를 보여 줍니다.

    A1이 A2를, A2가 A4 및 A5를, A1이 A3을 초대합니다.

상담 통화를 식별하는 방법

  1. 1단계: 기본 연락처와 연결된 모든 레그 그룹화

  2. 2단계: 고객 응대 ID 필드를 사용하여 각 페어 간의 관계 식별(이전 고객 응대 ID, 다음 고객 응대 ID, 초기 고객 응대 ID 및 고객 응대 ID). 고객 응대 레코드의 추가 필드를 검사하여 상담/전환 또는 회의와 같은 상담 작업 유형을 식별합니다.

1단계: 기본 연락처와 연결된 모든 레그 그룹화

이 단계는 지정된 개시자/발신자가 개시한 모든 통화를 그룹화하는 데 도움이 됩니다. 관심 필드는 고객 응대 ID, 이전 고객 응대 ID, 다음 고객 응대 ID, 초기 고객 응대 ID 및 고객 응대 ID입니다. 또한 통화를 해결하는 데 걸린 레그 수를 이해하는 데도 도움이 됩니다. 이에 대한 워크플로는 다음과 같습니다.

  1. 개시자 설정: InitialContactId 필드가 NULL인 고객 응대 레코드입니다. 이 레코드의 경우 PreviousContactId 또한 NULL입니다.

  2. InitialContactId 필드가 개시자 고객 응대 레코드의 ContactId와 동일한 모든 고객 응대 레코드는 이 고객 응대 레코드와 관련이 있습니다.

2단계: 고객 응대 ID 필드를 사용하여 각 페어 간의 관계 식별

다음 로직을 사용하여 상담과 전환, 회의를 식별할 수 있습니다. 로직은 고객 응대 레코드에 기록된 타임스탬프 필드를 사용합니다. 모든 관련 필드가 code로 표시되었습니다.

상담 통화

개시자는 동일한 Amazon Connect 인스턴스(내부) 내에서 또는 해당 인스턴스 외부(외부)에서 DID 또는 수신자 부담 전화번호를 사용하여 다른 당사자와 상담합니다.

  • 내부 상담 특성:

    • 상담 요청을 받는 에이전트가 개시자 에이전트보다 먼저 ACW로 전환됩니다.

    • 상담 요청을 받는 에이전트는 고객과 대화하지 않습니다. 이는 고객이 개시자에 의해 대기 상태가 되었기 때문입니다. 따라서 상담 요청을 받는 에이전트의 AgentInteractionDuration 필드는 0입니다.

  • 외부 상담 특성:

    • 개시자의 고객 보류 기간이 외부 당사자의 상호 작용 기간(ExternalThirdPartyInteractionDuration)보다 깁니다.

회의 통화

개시자는 동일한 Amazon Connect 인스턴스(내부) 내에서 또는 해당 인스턴스 외부(외부)에서 DID 또는 수신자 부담 전화번호를 사용하여 다른 참가자와 회의합니다.

  • 내부 상담 특성:

    • 상담 요청을 받는 에이전트가 개시자 에이전트보다 먼저 ACW로 전환됩니다.

    • 상담 요청을 받는 에이전트가 고객과 대화합니다. AgentInteractionDuration은 0이 아닙니다.

  • 외부 상담 특성:

    • 개시자의 고객 보류 기간이 외부 당사자의 상호 작용 기간(ExternalThirdPartyInteractionDuration)보다 짧습니다. 즉, 고객이 잠시 보류된 후 모든 참가자가 통화에 참여했습니다.

통화 전송

개시자는 동일한 Amazon Connect 인스턴스(내부) 내에서 또는 해당 인스턴스 외부(외부)에서 DID 또는 수신자 부담 전화번호를 사용하여 다른 당사자와 상담합니다.

  • 내부 상담 특성:

    • 상담 요청을 받는 에이전트가 개시자 에이전트 후에 ACW로 전환됩니다.

    • 개시자 에이전트의 경우 TransferCompletedTimestamp 필드가 0이 아닙니다.

  • 외부 상담 특성:

    • 외부 레그가 연결 해제되기 전에(DisconnectTimestamp) 개시자가 ACW(AfterContactWorkStartTimestamp)로 전환됩니다.

    • 개시자 에이전트의 경우 TransferCompletedTimestamp 필드가 0이 아닙니다.

코드 조각

SQL, Java 스크립트 및 Python의 다음 예제 코드 조각은 이전 섹션에서 설명한 로직을 활용하여 회의, 전환 및 상담 통화를 식별하는 방법을 보여 줍니다. 이러한 코드 조각은 예제로 제공되며 프로덕션용이 아닙니다.

SQL 코드

-- Conference transfer query DO NOT EDIT -- SELECT current_cr.contact_id, current_cr.initial_contact_id, current_cr.previous_contact_id, current_cr.next_contact_id, previous_cr.agent_username as initiator_agent_username, COALESCE ( current_cr.agent_username, current_cr.customer_endpoint_address ) as recipient_agent_username, current_cr.agent_connected_to_agent_timestamp, current_cr.agent_after_contact_work_start_timestamp, current_cr.transfer_completed_timestamp, CASE WHEN previous_cr.agent_after_contact_work_start_timestamp < current_cr.agent_after_contact_work_start_timestamp AND previous_cr.transfer_completed_timestamp IS NOT NULL THEN 'TRANSFER' WHEN previous_cr.agent_after_contact_work_start_timestamp > current_cr.agent_after_contact_work_start_timestamp AND current_cr.agent_interaction_duration_ms <= 2000 THEN 'CONSULT' WHEN previous_cr.agent_after_contact_work_start_timestamp > current_cr.agent_after_contact_work_start_timestamp AND current_cr.agent_interaction_duration_ms > 2000 THEN 'CONFERENCE' WHEN current_cr.agent_username is NULL AND current_cr.initiation_method = 'EXTERNAL_OUTBOUND' AND previous_cr.agent_after_contact_work_start_timestamp > current_cr.disconnect_timestamp AND previous_cr.agent_customer_hold_duration_ms > current_cr.external_third_party_interaction_duration_ms THEN 'EXTERNAL_CONSULT' WHEN current_cr.agent_username is NULL AND current_cr.initiation_method = 'EXTERNAL_OUTBOUND' AND previous_cr.agent_after_contact_work_start_timestamp > current_cr.disconnect_timestamp AND previous_cr.agent_customer_hold_duration_ms < current_cr.external_third_party_interaction_duration_ms THEN 'EXTERNAL_CONFERENCE' WHEN current_cr.agent_username is NULL AND current_cr.initiation_method = 'EXTERNAL_OUTBOUND' AND current_cr.disconnect_timestamp > previous_cr.transfer_completed_timestamp THEN 'EXTERNAL_TRANSFER' ELSE 'START' END AS TYPE FROM contact_record_link current_cr LEFT JOIN contact_record_link previous_cr ON previous_cr.contact_id = current_cr.previous_contact_id WHERE ( -- INPUT CONTACT ID -- current_cr.initial_contact_id = 'A CONTACT ID' or current_cr.contact_id = 'SAME CONTACT ID AS ABOVE' ) order by current_cr.agent_connected_to_agent_timestamp asc

Python 코드

"""Module Compare CTR's and establish relation""" ############################################################################### # Usage python ctr_processor.py [Initial Contact ID] # Example: python CTR_Processor.py 497f04ca-6de1-408f-9b8a-ec57bcc99b31 # # Have your CTR record JSON files in the same directory as this Python module # and execute the module as noted above. The input parameter is the # Initial Contact ID / the Contact ID of the first leg of the call. # ####################################################################z########### import json import re import os import sys from dateutil import parser PATH_OF_FILES = './' JSON = '.json' ENCODING = 'UTF-8' INTERACTION_DURN_THRESHOLD = 2 TYPE_INITIAL = 'STAND ALONE' TYPE_CONSULT = 'CONSULT' TYPE_EXT_CONSULT = 'EXT_CONSULT' TYPE_EXT_CONF = 'EXT_CONFERENCE' TYPE_CONFERENCE = 'CONFERENCE' TYPE_TRANSFER = 'TRANSFER' TYPE_UNKNOWN = 'UNKNOWN' CONTACT_STATE_INT = 'INTERMEDIATE' CONTACT_STATE_FINAL = 'FINAL' CONTACT_STATE_START = 'START' PRINT_INDENT = 4 def process_ctr_records(ctr_array): """ Function to process CTR Records""" relation = {} output_list = [] if ctr_array is None : return None for i, a_record in enumerate(ctr_array): if (prev_cid := a_record.get('PreviousContactId', None)) is not None: if (parent_ctr := get_parent_node(ctr_array, a_record['ContactId'], prev_cid)) is not None: relation = establish_relation(parent_ctr, a_record) else: relation = establish_parent(a_record) if relation is not None: output_list.append(relation) return output_list def establish_parent(a_ctr): """ Establish the first record - the one that doesn't have a Previous Contact ID""" if a_ctr.get('Agent', None) is not None: return { 'Agent': a_ctr['Agent']['Username'] ,'ConnectedToAgentTimestamp': a_ctr['Agent']['ConnectedToAgentTimestamp'] ,'Root Contact ID': a_ctr['ContactId'] ,'Type': TYPE_INITIAL ,'Contact State': CONTACT_STATE_START } def establish_relation(parent, child): """ Establish Conf / Transfer / Consult relation between two Agents""" if is_external_call(child): return establish_external_relation(parent, child) else: return establish_internal_relation(parent, child) def establish_external_relation(parent, child): """ Establish Conf / Transfer / Consult relation between two Agents - External call""" ret = { 'Parties': parent['Agent']['Username'] + ' <-> External:' + child['CustomerEndpoint']['Address'] ,'Contact State': parent.get('Contact State', CONTACT_STATE_INT) ,'ConnectedToAgentTimestamp': child['ConnectedToSystemTimestamp'] } parent_acw_start_ts = parser.parse(parent['Agent']['AfterContactWorkStartTimestamp']) child_disconnect_ts = parser.parse(child['DisconnectTimestamp']) if (parent_acw_start_ts - child_disconnect_ts).total_seconds() > 0: # Parent ended after child: Consult or conference ret['Type'] = TYPE_EXT_CONSULT if (parent['Agent']['CustomerHoldDuration'] - child['ExternalThirdParty']['ExternalThirdPartyInteractionDuration']) > INTERACTION_DURN_THRESHOLD else TYPE_EXT_CONF elif ((transfer_completed_ts := parser.parse(parent.get('TransferCompletedTimestamp', None))) is not None) and \ ((child_disconnect_ts - transfer_completed_ts).total_seconds() > 0): # ACW started after transfer was completed ret['Type'] = TYPE_TRANSFER return ret def establish_internal_relation(parent, child): """ Establish Conf / Transfer / Consult relation between two Agents - Internal call""" ret = { 'Parties': parent['Agent']['Username'] + ' <-> ' + child['Agent']['Username'] ,'Contact State': parent.get('Contact State', CONTACT_STATE_INT) ,'Child Contact ID': child.get('ContactId', 'NOTHING') ,'ConnectedToAgentTimestamp': child['Agent']['ConnectedToAgentTimestamp'] } parent_acw_start_ts = parser.parse(parent['Agent']['AfterContactWorkStartTimestamp']) child_acw_start_ts = parser.parse(child['Agent']['AfterContactWorkStartTimestamp']) if (parent_acw_start_ts - child_acw_start_ts).total_seconds() > 0: # Parent ended after child: Consult or conference ret['Type'] = TYPE_CONSULT if child['Agent']['AgentInteractionDuration'] < INTERACTION_DURN_THRESHOLD else TYPE_CONFERENCE elif ((transfer_completed_ts := parser.parse(parent.get('TransferCompletedTimestamp', None))) is not None) and \ ((child_acw_start_ts - transfer_completed_ts).total_seconds() > 0): # ACW started after transfer was completed ret['Type'] = TYPE_TRANSFER return ret def is_external_call(a_record): """Is this an external call """ if (a_record.get('Agent', None) is None and a_record.get('InitiationMethod', None) == 'EXTERNAL_OUTBOUND'): return True return False def get_parent_node(ctr_array, child_cid, child_prev_cid): """ Get the parent node when we have a Previous Contact ID""" for i, a_record in enumerate(ctr_array): if (parent_cid := a_record.get('ContactId', None)) is not None: if compare_strings(parent_cid, child_prev_cid): if (parent_next_cid := a_record.get('NextContactId', None)) is not None: if compare_strings(parent_next_cid, child_cid): return a_record | {'Contact State': CONTACT_STATE_FINAL} else: return a_record else: return a_record | {'Contact State': CONTACT_STATE_INT} def compare_strings(s1, s2): """ Compare two Contact IDs""" if s1 is None or s2 is None : return False return re.search(re.compile(s2), s1) def read_all_ctr_records(a_cid): """ Read all the CTR records for a given Initial Contact ID. Modify for S3 read""" ctr_array = [] for file_name in [file for file in os.listdir(PATH_OF_FILES) if file.endswith(JSON)]: with open(PATH_OF_FILES + file_name, encoding=ENCODING) as json_file: try: a_ctr = json.load(json_file) except ValueError: print('Error in parsing JSON. File name:[', file_name, ']') if a_ctr is not None: c_id = a_ctr['ContactId'] init_cid = a_ctr.get('InitialContactId', None) if compare_strings(a_cid, c_id): ctr_array.append(a_ctr) elif compare_strings(a_cid, init_cid): ctr_array.append(a_ctr) return ctr_array def main(): """ Entry point""" if len(sys.argv) < 2: print('Incorrect number of arguments (', len(sys.argv), ') --> python ctr_processor.py [Initial Contact ID]') return else: output_list = process_ctr_records(read_all_ctr_records(sys.argv[1])) if output_list is not None and len(output_list) > 0: output_list.sort(key=lambda x: x['ConnectedToAgentTimestamp']) for i, an_entry in enumerate(output_list): print(json.dumps(an_entry, indent=PRINT_INDENT)) else: print('Unable to find Contact ID:[', sys.argv[1], '] in the input CTR Records. Please check the files and try again.') if __name__ == "__main__": main()

JS 코드

// Has a dependency on the following Node.js modules: - date-fns, fs, path //sample input: node index.js 497f04ca-6de1-408f-9b8a-ec57bcc99b31 const fs = require('fs'); const path = require('path'); const { parseISO } = require('date-fns'); const PATH_OF_FILES = './'; const JSON_EXT = '.json'; const ENCODING = 'UTF-8'; const INTERACTION_DURATION_THRESHOLD = 2; const CONTACT_TYPES = { INITIAL: 'STAND ALONE', CONSULT: 'CONSULT', EXTERNAL_CONSULT: 'EXT_CONSULT', EXTERNAL_CONFERENCE: 'EXT_CONFERENCE', CONFERENCE: 'CONFERENCE', TRANSFER: 'TRANSFER', EXTERNAL_TRANSFER: 'EXT_TRANSFER', }; const CONTACT_STATES = { INTERMEDIATE: 'INTERMEDIATE', FINAL: 'FINAL', START: 'START', }; const PRINT_INDENT = 4; function processCtrRecords(ctrArray) { if (!ctrArray) return null; const outputList = []; ctrArray.forEach(record => { let relation = null; const prevCid = record.PreviousContactId; if (prevCid) { const parentRecord = findParentRecord(ctrArray, record.ContactId, prevCid); if (parentRecord) { relation = establishRelation(parentRecord, record); } } else { relation = establishInitialRecord(record); } if (relation) { outputList.push(relation); } }); return outputList; } function establishInitialRecord(record) { if (record.Agent) { return { 'Agent': record.Agent.Username, 'ConnectedToAgentTimestamp': record.Agent.ConnectedToAgentTimestamp, 'Root Contact ID': record.ContactId, 'Type': CONTACT_TYPES.INITIAL, 'Contact State': CONTACT_STATES.START, }; } } function establishRelation(parent, child) { return isExternalCall(child) ? establishExternalRelation(parent, child) : establishInternalRelation(parent, child); } function establishExternalRelation(parent, child) { const parentAcwStartTs = parent.Agent?.AfterContactWorkStartTimestamp ? parseISO(parent.Agent.AfterContactWorkStartTimestamp) : null; const childDisconnectTs = child.DisconnectTimestamp ? parseISO(child.DisconnectTimestamp) : null; const relation = { 'Parties': `${parent.Agent.Username} <-> External:${child.CustomerEndpoint.Address}`, 'Contact State': parent['Contact State'] || CONTACT_STATES.INTERMEDIATE, 'ConnectedToAgentTimestamp': child.ConnectedToSystemTimestamp, }; if (parentAcwStartTs && childDisconnectTs && (parentAcwStartTs - childDisconnectTs) > 0) { if (parent.Agent.CustomerHoldDuration - child.ExternalThirdParty.ExternalThirdPartyInteractionDuration > INTERACTION_DURATION_THRESHOLD) { relation['Type'] = CONTACT_TYPES.EXTERNAL_CONSULT; } else { relation['Type'] = CONTACT_TYPES.EXTERNAL_CONFERENCE; } } else if (parent.TransferCompletedTimestamp) { const transferCompletedTs = parseISO(parent.TransferCompletedTimestamp); if (transferCompletedTs && childDisconnectTs && (childDisconnectTs - transferCompletedTs) > 0) { relation['Type'] = CONTACT_TYPES.EXTERNAL_TRANSFER; } } return relation; } function establishInternalRelation(parent, child) { const parentAcwStartTs = parent.Agent?.AfterContactWorkStartTimestamp ? parseISO(parent.Agent.AfterContactWorkStartTimestamp) : null; const childAcwStartTs = child.Agent?.AfterContactWorkStartTimestamp ? parseISO(child.Agent.AfterContactWorkStartTimestamp) : null; const relation = { 'Parties': `${parent.Agent.Username} <-> ${child.Agent.Username}`, 'Contact State': parent['Contact State'] || CONTACT_STATES.INTERMEDIATE, 'Child Contact ID': child.ContactId || 'NOTHING', 'ConnectedToAgentTimestamp': child.Agent.ConnectedToAgentTimestamp, }; if (parentAcwStartTs && childAcwStartTs && (parentAcwStartTs - childAcwStartTs) > 0) { relation['Type'] = child.Agent.AgentInteractionDuration < INTERACTION_DURATION_THRESHOLD ? CONTACT_TYPES.CONSULT : CONTACT_TYPES.CONFERENCE; } else if (parent.TransferCompletedTimestamp) { const transferCompletedTs = parseISO(parent.TransferCompletedTimestamp); if (transferCompletedTs && childAcwStartTs && (childAcwStartTs - transferCompletedTs) > 0) { relation['Type'] = CONTACT_TYPES.TRANSFER; } } return relation; } function isExternalCall(record) { return !record.Agent && record.InitiationMethod === 'EXTERNAL_OUTBOUND'; } function findParentRecord(ctrArray, childCid, childPrevCid) { for (const record of ctrArray) { const parentCid = record.ContactId; if (compareStrings(parentCid, childPrevCid)) { const parentNextCid = record.NextContactId; if (parentNextCid && compareStrings(parentNextCid, childCid)) { return { ...record, 'Contact State': CONTACT_STATES.FINAL }; } else { return { ...record, 'Contact State': CONTACT_STATES.INTERMEDIATE }; } } } return null; } function compareStrings(s1, s2) { return s1 && s2 && s1.includes(s2); } function readAllCtrRecords(contactId) { return fs.readdirSync(PATH_OF_FILES) .filter(file => file.endsWith(JSON_EXT)) .map(fileName => JSON.parse(fs.readFileSync(path.join(PATH_OF_FILES, fileName), ENCODING))) .filter(record => compareStrings(contactId, record.ContactId) || compareStrings(contactId, record.InitialContactId)); } function main() { const [initialContactId] = process.argv.slice(2); if (!initialContactId) { console.log('Usage: node index.js [Initial Contact ID]'); return; } const outputList = processCtrRecords(readAllCtrRecords(initialContactId)); if (outputList.length) { outputList.sort((a, b) => new Date(a.ConnectedToAgentTimestamp) - new Date(b.ConnectedToAgentTimestamp)); outputList.forEach(entry => console.log(JSON.stringify(entry, null, PRINT_INDENT))); } else { console.log(`Unable to find Contact ID: [${initialContactId}]. Please check and try again.`); } } if (require.main === module) { main(); }