Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.
Identification des transferts et des conférences à l’aide des enregistrements de contacts Amazon Connect
Les enregistrements des contacts capturent les événements associés à un contact dans votre centre de contact. Pour chaque nouveau contact, Amazon Connect crée un enregistrement de contact et lui attribue un ID de contact.
Chaque fois qu’un agent consulte un autre agent (au sein d’Amazon Connect ou en dehors via un numéro gratuit ou un numéro d’appel direct), Amazon Connect crée un enregistrement de contact pour ce canal de consultation et émet un nouvel ID de contact pour ce canal.
L’enregistrement de contact principal et tout enregistrement de contact ultérieur pour ce canal de consultation peuvent être reliés entre eux par plusieurs champs d’ID de contact (ID de contact initial, ID de contact suivant et ID de contact précédent, par exemple).
Cette rubrique explique comment utiliser ces champs pour différencier les transferts et les conférences dans les enregistrements de contacts. Elle partage également une logique qui permet d’établir le type d’opération consultative : appel de consultation, conférence ou transfert.
Table des matières
Terminologie
La terminologie suivante est utilisée dans cette rubrique :
- Appel consultatif
-
Appel auquel participent trois personnes :
-
L’initiateur (par exemple, un client)
-
Le destinataire (par exemple, un agent)
-
Le participant consulté (par exemple, un superviseur ou un traducteur externe)
Un appel consultatif peut finir par être un appel de consultation, un transfert téléphonique ou une conférence téléphonique.
-
- Appel de consultation
-
Appel au cours duquel l’agent qui a reçu l’appel consulte un autre participant (par exemple, un agent de la même instance Amazon Connect ou une entité externe), tandis que l’initiateur est mis en attente.
Lorsqu’un appel est terminé, Amazon Connect attribue à l’agent l’état Travail après appel (ACW). La date et l’heure auxquelles cet état a été activé sont ajoutées à l’enregistrement du contact. Dans le cas des appels de consultation, le participant consulté se déconnecte avant le client.
La date et l’heure auxquelles l’état ACW a été attribué à l’agent sous
AfterContactWorkStartTimestampsont ajoutées à l’enregistrement du contact. - Transfert téléphonique
-
Le destinataire transfère l’initiateur au participant consulté. Dans ce cas, l’agent qui a reçu l’appel obtient l’état ACW avant l’agent consulté.
- Conférence téléphonique
-
Le destinataire met en communication l’initiateur avec le participant consulté (appel à trois).
Amazon Connect permet à plus de trois interlocuteurs de participer à la même conférence téléphonique. Pour les appels de consultation et les conférences téléphoniques internes, le participant consulté obtient l’état ACW avant le destinataire de l’appel. En revanche, lors d’une conférence téléphonique, le participant consulté peut également parler avec le client, tandis que lors d’un appel de consultation, le destinataire de l’appel met en attente le client.
Les sections suivantes expliquent comment identifier chacun de ces types d’appels dans un enregistrement de contact.
Enregistrements de contacts pour les appels consultatifs
Supposons que le client appelle l’agent 1. L’agent ne transfère pas l’appel et ne consulte aucune autre personne. Lorsque l’appel est terminé, l’enregistrement du contact ressemble à l’exemple suivant (seuls les champs pertinents sont affichés) :
{ "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, . . }
Si Agent1 initiait un appel consultatif avec un autre agent (Agent2), il s’agirait d’un appel de consultation, d’un transfert téléphonique ou d’une conférence téléphonique.
L’exemple d’enregistrement de contact suivant montre à quoi cela ressemblerait pour l’agent ayant initié l’appel (Agent1) et l’agent ayant reçu l’appel (Agent2) :
-
Agent initiateur (Agent1)
{ "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", . . } -
Agent destinataire (Agent2)
{ "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", . . }La relation entre les deux parties de l’enregistrement de contact est présentée dans le diagramme suivant :
Où l’agent 1 (A1) et l’agent 2 (A2) sont reliés par :
-
N = ID de contact suivant. Ce champ apparaît dans l’enregistrement de contact correspondant au canal initial. Il s’agit de l’ID de contact du dernier agent consulté par cet agent (dans ce cas, le dernier agent est A2).
-
P = ID de contact précédent. Ce champ apparaît dans l’enregistrement de contact correspondant au canal de consultation. Il s’agit de l’ID de contact du canal qui a appelé ce canal. Dans ce cas, il s’agit de l’agent A1.
Informations non représentées dans le diagramme :
-
ID de contact initial : ID de contact de la première interaction entre l’agent 1 (A1) et le client (C).
-
ID de contact : identifiant unique d’une interaction donnée.
L’ID de contact, l’ID de contact initial et l’ID de contact précédent sont des attributs système. Pour obtenir une description de chacun d’eux, consultez Attributs système.
-
Ce modèle peut être étendu à un appel de consultation impliquant plusieurs agents. Voici des exemples de cas d’utilisation montrant comment étendre ce modèle.
-
Cas d’utilisation 1 : l’agent 1 invite l’agent 2, l’agent 2 invite l’agent 3 et l’agent 3 invite l’agent 4. L’ID de contact précédent est toujours celui de l’agent précédent. Le diagramme suivant illustre ce cas d'utilisation.
-
Cas d’utilisation 2 : l’agent 1 invite l’agent 2, l’agent 1 invite l’agent 3 et l’agent 1 invite l’agent 4. L’ID de contact précédent est toujours A1. Le diagramme suivant illustre ce cas d'utilisation.
-
Cas d’utilisation 3 : l’agent 1 invite l’agent 2, l’agent 2 invite l’agent 4 et l’agent 5, l’agent 1 invite l’agent 3. L’ID de contact précédent pour les agents 2 et 3 est A1. Pour les agents 4 et 5, l’ID de contact précédent est A2. Le diagramme suivant illustre ce cas d'utilisation.
Comment identifier les appels consultatifs
-
Étape 1 : regrouper tous les canaux associés au contact principal
-
Étape 2 : identifier la relation entre chaque paire en utilisant leurs champs ID de contact (ID de contact précédent, ID de contact suivant, ID de contact initial et ID de contact). Examinez les champs supplémentaires de l’enregistrement de contact pour identifier le type d’opération consultative : appel de consultation, transfert ou conférence.
Étape 1 : regrouper tous les canaux associés au contact principal
Cette étape vous permet de regrouper tous les appels initiés par un initiateur/appelant donné. Les champs d’intérêt sont ID de contact, ID de contact précédent, ID de contact suivant, ID de contact initial et ID de contact. Cela vous permet également de comprendre le nombre de canaux nécessaires pour résoudre l’appel. Voici comment se présente le flux de travail correspondant :
-
Identification de l’initiateur : enregistrement de contact où le champ
InitialContactIdindiqueNULL. En outre,PreviousContactIdindique égalementNULLpour cet enregistrement. -
Chaque enregistrement de contact dont le champ
InitialContactIdcorrespond à la valeurContactIdde l’enregistrement de contact de l’initiateur est lié à cet enregistrement de contact.
Étape 2 : identifier la relation entre chaque paire en utilisant leurs champs ID de contact
Vous pouvez recourir à la logique suivante pour faire la distinction entre les appels de consultation, les transferts et les conférences. Cette logique utilise les champs de date et heure indiqués dans l’enregistrement de contact. Tous les champs pertinents ont été marqués comme code.
Appels de consultation
L’initiateur consulte une autre partie, au sein de la même instance Amazon Connect (interne) ou en dehors de cette instance (externe), à l’aide d’un numéro d’appel direct ou d’un numéro gratuit.
-
Caractéristiques des consultations internes :
-
L’agent consulté reçoit l’état ACW avant l’agent initiateur.
-
L’agent consulté ne parle jamais avec le client, car celui-ci a été mis en attente par l’initiateur. Le champ
AgentInteractionDurationcorrespondant à l’agent consulté indique donc ZÉRO.
-
-
Caractéristiques des consultations externes :
-
Le temps d’attente du client de l’initiateur est supérieur à la durée d’interaction (
ExternalThirdPartyInteractionDuration) de la partie externe.
-
Conférences téléphoniques
L’initiateur met en communication un autre participant au sein de la même instance Amazon Connect (interne) ou en dehors de cette instance (externe), à l’aide d’un numéro d’appel direct ou d’un numéro gratuit.
-
Caractéristiques des consultations internes :
-
L’agent consulté reçoit l’état ACW avant l’agent initiateur.
-
L’agent consulté discute avec le client : la valeur du champ
AgentInteractionDurationest différente de ZÉRO.
-
-
Caractéristiques des consultations externes :
-
Le temps d’attente du client de l’initiateur est inférieur à la durée d’interaction (
ExternalThirdPartyInteractionDuration) de la partie externe. Cela signifie que le client a été brièvement mis en attente, puis que tous les interlocuteurs ont participé à l’appel.
-
Transférer des appels
L’initiateur consulte une autre partie, au sein de la même instance Amazon Connect (interne) ou en dehors de cette instance (externe), à l’aide d’un numéro d’appel direct ou d’un numéro gratuit.
-
Caractéristiques des consultations internes :
-
L’agent consulté reçoit l’état ACW après l’agent initiateur.
-
Le champ
TransferCompletedTimestampest différent de ZÉRO pour l’agent initiateur.
-
-
Caractéristiques des consultations externes :
-
L’initiateur reçoit l’état ACW (
AfterContactWorkStartTimestamp) avant que le canal externe ne soit déconnecté (DisconnectTimestamp). -
Le champ
TransferCompletedTimestampest différent de ZÉRO pour l’agent initiateur.
-
Extraits de code
Les exemples d’extraits de code suivants (en SQL, JavaScript et Python) montrent comment identifier les conférences téléphoniques, les transferts téléphoniques et les appels de consultation en s’appuyant sur la logique décrite dans la section précédente. Ces extraits sont fournis à titre d’exemple et ne sont pas destinés à être utilisés en production.
Code 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
Code 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()
Code 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(); }