Trên một tổng đài IP hiện đại, một cuộc gọi từ khách hàng gần như không bao giờ chỉ có "ring → answer → hangup" đơn giản. Nó đi qua rất nhiều tầng logic:
Với chuỗi 5–6 khâu như vậy, khi supervisor mở báo cáo và thấy một cuộc gọi có duration = 47s, result = no_answer, câu hỏi đầu tiên luôn là: cuộc gọi mất ở khâu nào?
Hai cuộc gọi cùng no_answer + 47s có thể mang ý nghĩa hoàn toàn khác nhau. Nếu tổng đài chỉ ghi lại kết quả cuối cùng, supervisor sẽ chẩn đoán sai và ra quyết định vận hành sai.
Bài viết này mổ xẻ cách nền tảng Zorio Contact Center Platform giải bài toán trên: từ mô hình dữ liệu, cách gán nhãn từng chặng cuộc gọi, đến cách dựng báo cáo waterfall để supervisor thấy rõ hành trình.
Trong bất kỳ tổng đài IP hiện đại nào, mỗi cuộc gọi được chia thành các leg — mỗi kết nối một chiều là một leg riêng biệt:
Khi có transfer, một B-leg mới sẽ được tạo — tổng đài giữ A-leg (khách hàng không phải quay số lại), chỉ thay thế B-leg cũ. Điều này có nghĩa: một cuộc gọi có thể có nhiều B-leg nối tiếp nhau, nhưng chỉ có một A-leg duy nhất từ đầu đến cuối.
Zorio khai thác chính xác đặc tính này. Mỗi hàng trong bảng cdrs (Call Detail Record) ứng với một A-leg — một cuộc gọi trọn vẹn từ góc nhìn của khách. Bên cạnh đó, một bảng phụ tên cdr_legs ghi lại từng B-leg riêng biệt, mỗi lần chuyển tiếp là một dòng mới.
1900xxxx A-leg khởi tạoKết quả trong database:
cdrs: 1 row (A-leg, duration ~ 8 phút, transfer_count = 1, result = answered)cdr_legs: 2 row (B-leg tới 2001, B-leg tới 2015) — mỗi row có stage, agent_extension, billsec riêngNhờ tách biệt A-leg và B-leg trong hai bảng, ta trả lời được cả hai câu hỏi:
Nhân xử lý cuộc gọi cho phép gán các biến metadata (call context variable) vào cuộc gọi khi nó đi qua từng khâu. Zorio định nghĩa một bộ biến tiền tố zorio_* để làm nhật ký thời gian thực. Đây là những biến quan trọng nhất:
| Biến | Ý nghĩa | Ví dụ giá trị |
|---|---|---|
zorio_routing_stage | Chặng cuộc gọi đang ở đâu trong flow routing | ivr, queue, ring_group, extension |
zorio_routing_path | Chuỗi các chặng đã đi qua, ngăn cách > | ivr>queue>extension |
zorio_exit_reason | Lý do cuộc gọi rời tổng đài | answered, caller_abandoned_queue |
zorio_ivr_id / _name | ID + tên IVR cuộc gọi đang ở | 12 / Chào mừng buổi sáng |
zorio_queue_id / _name | ID + tên hàng đợi | 4 / sales_hn |
zorio_ring_group_id / _name | ID + tên nhóm đổ chuông | 2 / Nhóm Kinh doanh |
zorio_agent_name | Tên agent nhấc máy | Nguyễn Văn A (2001) |
zorio_leg_stage | Stage của B-leg đang được tạo | extension, queue, external |
zorio_leg_entity_id / _name | Entity tương ứng với leg đó | 2001 / Extension 2001 |
zorio_inbound_did | Số DID khách gọi vào | 1900xxxx |
zorio_displayed_cid | Caller ID hiển thị cho agent | 0912345678 |
zorio_a_leg_events | JSON các event trên A-leg (audit log) | [{ev:"ivr_dtmf",val:"1"},…] |
Nguyên tắc thiết kế:
zorio_routing_stage, append vào zorio_routing_path, và (nếu là stage cuối) set zorio_exit_reason. Nếu để cuối cuộc gọi mới tính, khi có sự cố nhân xử lý dừng đột ngột giữa chừng, sẽ mất thông tin không hồi phục được.zorio_inbound_did — DID gốc phải giữ nguyên qua mọi transfer để phân tích đúng "khách gọi số nào". Đây là một pitfall phổ biến khi viết handler mới.zorio_a_leg_events là audit log dạng JSON append-only — mỗi event (DTMF khách bấm, timeout queue, agent join/leave) được push vào cuối array. Khi có tranh chấp với khách ("tôi bấm phím 2 nhưng vào sai bộ phận"), supervisor có bằng chứng thời gian thực.Cuộc gọi kết thúc, nhân xử lý cuộc gọi sinh sự kiện hangup_complete với toàn bộ metadata snapshot. Zorio nhận sự kiện này qua một service realtime nội bộ, sau đó forward sang tầng ứng dụng để ghi vào MySQL:
Điểm mấu chốt là computeResult — function tổng hợp tất cả tín hiệu (hangup cause, billsec, exit_reason, transfer_count, agent extension có được set không) để đưa ra 7 giá trị kết quả cuối cùng:
result | Ý nghĩa | Điều kiện điển hình |
|---|---|---|
| answered | Agent nhấc máy, có nói chuyện | billsec > 0, NORMAL_CLEARING, có agent |
| no_answer | Không ai nhấc máy | NORMAL_CLEARING + billsec = 0, hoặc timeout |
| busy | Máy bận | USER_BUSY / SIP 486 |
| cancelled | Khách bỏ máy giữa chừng | ORIGINATOR_CANCEL trước khi answer |
| rejected | Bị từ chối chủ động | SIP 603, agent reject, out of hours |
| voicemail | Chuyển vào hộp thư | vm_recorded, vm_too_short |
| failed | Lỗi kỹ thuật | NORMAL_TEMPORARY_FAILURE, network… |
Việc chuẩn hoá 7 kết quả này rất quan trọng vì nhân xử lý cuộc gọi sinh ra tới hơn 40 hangup cause khác nhau (theo chuẩn Q.850 telco). Nếu để supervisor tự phân tích cause code, không thể ra được báo cáo có ý nghĩa. Zorio đóng gói toàn bộ logic đó vào một helper class duy nhất, dùng chung cho cả CDR inbound và outbound, đảm bảo nhất quán số liệu.
billsec = 0 nghĩa là chưa từng answer. Zorio map case này thành no_answer, không phải answered. Bỏ qua chi tiết này, tỷ lệ Answer Rate toàn hệ thống sẽ sai lệch 5–15%.routing_stage và exit_reason — hai cột đá tảng cho báo cáoTrong bảng cdrs, hai enum sau được "chưng cất" từ chuỗi zorio_routing_path và zorio_exit_reason lúc INSERT:
routing_stage ENUM(
'not_routed', 'out_of_hours', 'ivr', 'queue',
'ring_group', 'extension', 'voicemail', 'external'
)
exit_reason ENUM(
'answered',
'caller_abandoned_ivr', 'caller_abandoned_queue', 'caller_abandoned_ringing',
'timeout_ivr', 'timeout_queue', 'timeout_ring',
'no_route', 'out_of_hours',
'agent_busy', 'agent_reject', 'technical_failure',
'vm_recorded', 'vm_too_short', 'vm_record_failed'
)
Sự khác biệt:
routing_stage cho biết chặng cuối cùng cuộc gọi đứng lại (chặng sâu nhất đã tới)exit_reason cho biết cuộc gọi rời chặng đó vì lý do gìVí dụ với cuộc gọi khách bỏ máy giữa lúc chờ trong hàng đợi 30 giây: routing_stage = 'queue', exit_reason = 'caller_abandoned_queue', queue_wait_time = 30, result = 'cancelled'.
Nhờ tách hai chiều thông tin này, supervisor lọc trên dashboard theo bất kỳ tổ hợp nào:
WHERE exit_reason = 'caller_abandoned_ivr'WHERE exit_reason = 'timeout_queue' AND queue_wait_time > 60WHERE transfer_count > 0 AND result = 'no_answer'final_destination_type — "đích đến cuối cùng là gì?"Một chi tiết dễ bỏ sót: nếu cuộc gọi kết thúc ở IVR (khách bấm 0 thoát), thì "đích đến" là gì? Nếu kết thúc ở voicemail thì là hộp thư của extension nào? Zorio dùng thêm cột final_destination_type với 10 giá trị enum để phân loại:
final_destination_type | Nghĩa | Ví dụ label |
|---|---|---|
| agent_ext | Bắt trực tiếp máy nhánh, có agent nhấc máy | Nguyễn Văn A (2001) |
| agent_queue | Vào queue rồi được agent bắt máy | Queue Sales · Nguyễn Văn A (2001) |
| agent_rg | Vào ring group rồi có agent bắt máy | Nhóm Kinh doanh · Trần B (2015) |
| ivr | Kết thúc trong IVR (khách bỏ/bấm exit) | IVR: Chào mừng buổi sáng |
| queue_unanswered | Vào queue nhưng không có agent bắt máy | Queue Sales |
| rg_unanswered | Vào ring group nhưng không có agent | Nhóm Kinh doanh |
| voicemail | Chuyển vào hộp thư | Voicemail 2001 |
| external | Chuyển ra số ngoại tuyến (forward, mobile) | Forward 0912345678 |
| not_routed | Không match route nào | — |
| hangup | Kết thúc bất thường | — |
Đi kèm là final_destination_label — chuỗi human-readable đã dựng sẵn, hiển thị ngay trên báo cáo mà không cần join thêm bảng nào. Khi supervisor mở màn hình chi tiết một cuộc gọi trong Zorio, họ thấy ngay:
1900xxxx → 0912345678 · Kết quả:answered· Thời lượng: 8 phút
Đường đi: IVR "Chào mừng buổi sáng" → Queue "sales_hn" (chờ 12s) → Nguyễn Văn A (2001) → transfer → Trần B (2015)
Đích đến cuối:agent_ext— Trần B (2015)
Không cần chạy query SQL, không cần join, không cần đoán.
Cấu trúc dữ liệu bên trên là nền móng cho một kiểu báo cáo cực kỳ hữu ích trong contact center: Waterfall report — biểu diễn hành trình cuộc gọi dưới dạng thanh ngang phân đoạn màu.
zorio_exit_reason càng sớm càng tốt — mỗi handler khi có tình huống "cuộc gọi rời khỏi handler này" (timeout, khách hangup, chuyển tiếp) phải set ngay tại đó. Không gộp về xử lý ở cuối vì nếu chuỗi có 5 handler, khó truy được ai quyết định exit.routing_stage, exit_reason, result, final_destination_type đều là ENUM MySQL, không phải string tự do. Query nhanh hơn (index tốt), báo cáo nhất quán (không có "Timeout Queue" vs "timeout_queue"), ngăn typo khi viết handler mới.a_leg_events JSON — mọi event nhỏ (DTMF, agent join/leave, transfer request) đều nên push vào audit log. Chi phí rẻ (JSON append trong RAM), giá trị cao khi debug/dispute với khách.Trong 15 năm ngành tổng đài IP, phần lớn hệ thống chỉ ghi lại "khách gọi số A, kéo dài B giây, ai bắt máy". Đó là CDR flat — đủ dùng cho tính cước, không đủ cho vận hành contact center chuyên nghiệp.
Zorio thiết kế mô hình CDR theo hướng stage-aware: mỗi chặng được gắn thẻ ngay khi xảy ra, mỗi B-leg lưu độc lập, hangup cause chuẩn hoá về 7 kết quả cuối, và mỗi cuộc gọi có final_destination rõ ràng. Kết quả:
zorio_* là data pipeline tự nhất quánVới doanh nghiệp đang tìm giải pháp contact center có khả năng truy dấu chi tiết từng cuộc gọi — từ IVR đến agent đến transfer — Zorio là nền tảng được thiết kế từ ngày đầu cho bài toán này, không phải một CDR flat sau đó "cố ép" thêm cột.
Đặt lịch demo để trải nghiệm waterfall report, truy vết multi-hop và toàn bộ nền tảng contact center Zorio.