Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.
Identifizieren von Konferenzen und Übertragungen mithilfe von Amazon-Connect-Kontaktdatensätzen
Kontaktdatensätze erfassen die Ereignisse, die mit einem Kontakt in Ihrem Contact Center verknüpft sind. Für jeden neuen Kontakt erstellt Amazon Connect einen Kontaktdatensatz und weist dem Kontakt eine eindeutige Kontakt-ID zu.
Jedes Mal, wenn ein Kundendienstmitarbeiter einen anderen Kundendienstmitarbeiter konsultiert (intern in Amazon Connect oder extern über eine gebührenfreie Nummer oder DID (Direct Inward Dialing)-Nummer), erstellt Amazon Connect einen Kontaktdatensatz für den Beratungsabschnitt und vergibt eine neue Kontakt-ID für diesen Abschnitt.
Der Hauptkontaktdatensatz und alle nachfolgenden Kontaktdatensätze für den Beratungsabschnitt können durch mehrere Kontakt-ID-Felder miteinander verknüpft sein, z. B. „Erste Kontakt-ID“, „Nächste Kontakt-ID“ und „Vorherige Kontakt-ID“.
In diesem Thema wird erklärt, wie Sie diese Felder verwenden können, um zwischen Konferenzen und Übertragungen in Kontaktdatensätzen zu unterscheiden. Es bietet auch eine Logik zur Bestimmung der Art des Beratungsvorgangs: Beratungsgespräch, Konferenz oder Weiterleitung.
Inhalt
Terminologie
In diesem Thema wird die folgende Terminologie verwendet:
- Beratergespräch
-
Ein Telefongespräch mit drei Teilnehmern:
-
Der Initiator, zum Beispiel ein Kunde
-
Der Empfänger, zum Beispiel ein Kundendienstmitarbeiter
-
Ein konsultierter Teilnehmer, zum Beispiel ein Supervisor oder ein externer Übersetzer
Bei einem Beratergespräch kann es sich um ein Beratungsgespräch, ein Weiterleitungsgespräch oder eine Telefonkonferenz handeln.
-
- Beratungsgespräch
-
Ein Anruf, bei dem der empfangende Kundendienstmitarbeiter einen anderen Teilnehmer konsultiert (z. B. einen Kundendienstmitarbeiter in derselben Amazon-Connect-Instance oder eine externe Entität), während der Initiator in die Warteschleife gesetzt wird.
Nachdem ein Anruf unterbrochen wurde, versetzt Amazon Connect den Kundendienstmitarbeiter in den Status „Anrufnachbearbeitung“ (ACW). Der Kontaktdatensatz wird mit dem Zeitstempel des Wechsels in diesen Status aktualisiert. Bei Beratungsgesprächen trennt der konsultierte Teilnehmer die Verbindung früher als der Kunde.
Im Kontaktdatensatz wird unter
AfterContactWorkStartTimestampder Zeitstempel aufgezeichnet, wann der Kundendienstmitarbeiter in den Status „ACW“ versetzt wurde. - Weiterleitungsanruf
-
Der Empfänger überträgt den Initiator an den konsultierten Teilnehmer. In diesem Fall wechselt der empfangende Kundendienstmitarbeiter früher in den ACW-Status als der konsultierte Kundendienstmitarbeiter.
- Telefonkonferenz
-
Der Empfänger startet eine Telefonkonferenz mit dem Initiator und dem konsultierten Teilnehmer (dreiseitige Telefonkonferenz).
Mit Amazon Connect können mehr als drei Teilnehmer gemeinsam Konferenzen abhalten. Bei internen Anrufen wechselt der konsultierte Teilnehmer sowohl bei Beratungsgesprächen als auch in Konferenzen früher in den ACW-Status als der Empfänger. Der Unterschied besteht jedoch darin, dass in einer Konferenz der konsultierte Teilnehmer auch mit dem Kunden sprechen kann, während in einem Beratungsgespräch der Kunde vom Empfänger in die Warteschleife gesetzt wird.
In den folgenden Abschnitten wird erläutert, wie Sie diese Anruftypen in einem Kontaktdatensatz erkennen können.
Kontaktdatensätze für Beratergespräche
Angenommen, der Kunde ruft Agent1 an. Der Kundendienstmitarbeiter leitet den Anruf nicht weiter und berät sich nicht mit anderen. Wenn der Anruf getrennt wird, sieht der Kontaktdatensatz wie im folgenden Beispiel aus (nur die relevanten Felder werden angezeigt):
{ "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, . . }
Wenn Agent1 ein Beratungsgespräch mit einem anderen Kundendienstmitarbeiter (Agent2) starten würde, würde es sich um eine Beratung, eine Weiterleitung oder eine Konferenz handeln.
Der folgende Beispiel-Kontaktdatensatz zeigt, wie dies für den initiierenden Kundendienstmitarbeiter (Agent1) und den empfangenden Kundendienstmitarbeiter (Agent2) aussehen würde:
-
Initiierender Kundendienstmitarbeiter (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", . . } -
Empfangender Kundendienstmitarbeiter (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", . . }Die Beziehung zwischen den beiden Teilen des Kontaktdatensatzes ist im folgenden Diagramm dargestellt:
Dabei sind Agent1 (A1) und Agent2 (A2) miteinander verbunden durch:
-
N = Nächste Kontakt-ID. Dieses Feld erscheint im Kontaktdatensatz für den ersten Abschnitt. Dies ist die Kontakt-ID des letzten Kundendienstmitarbeiters, den dieser Kundendienstmitarbeiter konsultiert hat (in diesem Fall ist der letzte Kundendienstmitarbeiter A2).
-
P= Vorherige Kontakt-ID. Dieses Feld erscheint im Kontaktdatensatz für den Beratungsabschnitt. Dies ist die Kontakt-ID des Abschnitts, in dem dieser Abschnitt aufgerufen wurde. In diesem Fall ist dies A1.
Nicht im Diagramm gezeigt:
-
Erste Kontakt-ID: Dies ist die Kontakt-ID der ersten Interaktion zwischen Agent1 (A1) und dem Kunden (C).
-
Kontakt-ID: Dies ist die eindeutige Kennung einer bestimmten Interaktion.
„Kontakt-ID“, „Erste Kontakt-ID“ und „Vorherige Kontakt-ID“ sind Systemattribute. Eine Beschreibung dazu finden Sie unter Systemattribute.
-
Dieses Modell kann auf ein Beratungsgespräch ausgedehnt werden, an dem mehrere Kundendienstmitarbeiter beteiligt sind. Im Folgenden finden Sie Beispielanwendungsfälle für die Erweiterung.
-
Anwendungsfall 1: Agent1 lädt Agent2 ein, Agent2 lädt Agent3 ein und Agent3 lädt Agent4 ein. „Vorherige Kontakt-ID“ ist immer der vorherige Kundendienstmitarbeiter. Das folgende Diagramm veranschaulicht diesen Anwendungsfall.
-
Anwendungsfall 2: Agent1 lädt Agent2 ein, Agent1 lädt Agent3 ein und Agent1 lädt Agent4 ein. „Vorherige Kontakt-ID“ ist immer Agent1. Das folgende Diagramm veranschaulicht diesen Anwendungsfall.
-
Anwendungsfall 3: Agent1 lädt Agent2 ein, Agent2 lädt Agent4 und Agent5 ein und Agent1 lädt Agent3 ein. „Vorherige Kontakt-ID“ für Agent2 und Agent3 ist Agent1. „Vorherige Kontakt-ID“ für Agent4 und Agent5 ist Agent2. Das folgende Diagramm veranschaulicht diesen Anwendungsfall.
So identifizieren Sie Beratergespräche
-
Schritt 1: Gruppieren aller Abschnitte, die dem Hauptkontakt zugeordnet sind
-
Schritt 2: Identifizieren der Beziehung zwischen den einzelnen Paaren anhand der Kontakt-ID-Felder („Vorherige Kontakt-ID“, „Nächste Kontakt-ID“, „Erste Kontakt-ID“ und „Kontakt-ID“). Prüfen Sie weitere Felder im Kontaktdatensatz, um die Art des Beratungsvorgangs zu ermitteln: Beratung/Weiterleitung oder Konferenz.
Schritt 1: Gruppieren aller Abschnitte, die dem Hauptkontakt zugeordnet sind
In diesem Schritt gruppieren Sie alle Anrufe, die von einem bestimmten Initiator/Anrufer initiiert wurden. Von Interesse sind die Felder „Kontakt-ID“, „Vorherige Kontakt-ID“, „Nächste Kontakt-ID“, „Erste Kontakt-ID“ und „Kontakt-ID“. Auf diese Weise können Sie auch nachvollziehen, wie viele Abschnitte zur Bearbeitung des Anrufs benötigt wurden. Der Arbeitsablauf hierfür sieht wie folgt aus:
-
Bestimmen des Initiators: Dies ist der Kontaktdatensatz, in dem das
InitialContactId-FeldNULList. Außerdem istPreviousContactIdebenfallsNULLfür diesen Datensatz. -
Jeder Kontaktdatensatz, bei dem das
InitialContactId-Feld derContactIddes Initiator-Kontaktdatensatzes entspricht, steht in Zusammenhang mit diesem Kontaktdatensatz.
Schritt 2: Identifizieren der Beziehung zwischen den einzelnen Paaren anhand der Kontakt-ID-Felder
Sie können die folgende Logik verwenden, um Beratungen von Weiterleitungen und Konferenzen zu unterscheiden. Die Logik verwendet die Zeitstempelfelder, die im Kontaktdatensatz angegeben sind. Alle relevanten Felder wurden als code markiert.
Beratungsgespräche
Der Initiator führt ein Beratungsgespräch mit einer anderen Person innerhalb derselben Amazon-Connect-Instance (intern) oder extern zu dieser Instance (extern), wobei eine DID oder eine gebührenfreie Nummer verwendet wird.
-
Merkmale interner Beratungsgespräche:
-
Der konsultierte Kundendienstmitarbeiter wechselt vor dem Initiator-Kundendienstmitarbeiter in den ACW-Status.
-
Der konsultierte Kundendienstmitarbeiter spricht nie mit dem Kunden, weil der Kunde vom Initiator in die Warteschleife gesetzt wurde. Daher ist das Feld
AgentInteractionDurationfür den konsultierten Kundendienstmitarbeiter NULL.
-
-
Merkmale externer Beratungsgespräche:
-
Die Wartedauer des Kunden ist beim Initiator höher als die Interaktionsdauer der externen Partei (
ExternalThirdPartyInteractionDuration).
-
Telefonkonferenzen
Konferenzen des Initiators mit einem anderen Teilnehmer innerhalb derselben Amazon-Connect-Instance (intern) oder außerhalb dieser Instance (extern), wobei eine DID- oder eine gebührenfreie Nummer verwendet wird.
-
Merkmale interner Beratungsgespräche:
-
Der konsultierte Kundendienstmitarbeiter wechselt vor dem Initiator-Kundendienstmitarbeiter in den ACW-Status.
-
Der konsultierte Kundendienstmitarbeiter spricht mit dem Kunden:
AgentInteractionDurationist nicht NULL.
-
-
Merkmale externer Beratungsgespräche:
-
Die Wartedauer des Kunden ist beim Initiator geringer als die Interaktionsdauer der externen Partei (
ExternalThirdPartyInteractionDuration). Das bedeutet, dass der Kunde kurzzeitig in die Warteschleife gesetzt wurde und dann alle Teilnehmer an dem Gespräch beteiligt waren.
-
Übertragen von Anrufen
Der Initiator führt ein Beratungsgespräch mit einer anderen Person innerhalb derselben Amazon-Connect-Instance (intern) oder extern zu dieser Instance (extern), wobei eine DID oder eine gebührenfreie Nummer verwendet wird.
-
Merkmale interner Beratungsgespräche:
-
Der konsultierte Kundendienstmitarbeiter wechselt nach dem Initiator-Kundendienstmitarbeiter in den ACW-Status.
-
Das Feld
TransferCompletedTimestampfür den Initiator-Kundendienstmitarbeiter ist ungleich NULL.
-
-
Merkmale externer Beratungsgespräche:
-
Der Initiator wechselt in den ACW-Status (
AfterContactWorkStartTimestamp), bevor der externe Abschnitt getrennt wird (DisconnectTimestamp). -
Das Feld
TransferCompletedTimestampfür den Initiator-Kundendienstmitarbeiter ist ungleich NULL.
-
Codeausschnitte
Die folgenden Beispielcodeaussschnitte (in SQL, Java Script und Python) zeigen, wie Sie Konferenz-, Weiterleitungs- und Beratergespräche identifizieren können, indem Sie die im vorherigen Abschnitt beschriebene Logik nutzen. Diese Codeausschnitte dienen als Beispiel und sind nicht für die Produktion vorgesehen.
SQL-Code
-- 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-Code
"""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-Code
// 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(); }