// services/cdrFeedParser.js // Parse cdr_feed JSON payloads (field names vary by Webex release). function normKey(key) { return String(key).toLowerCase().replace(/[^a-z0-9]/g, ''); } function indexCdrRecord(item) { if (!item || typeof item !== 'object') return {}; const idx = {}; for (const [k, v] of Object.entries(item)) { idx[normKey(k)] = v; } return idx; } /** Read a CDR field by any known alias (camelCase or report column label). */ export function cdrField(item, ...aliases) { if (!item) return null; const idx = indexCdrRecord(item); for (const alias of aliases) { const v = idx[normKey(alias)]; if (v != null && v !== '') return v; } return null; } export function cdrRecordId(item) { return cdrField( item, 'reportId', 'Report ID', 'reportID', 'callRecordingId', ); } export function cdrStartTime(item) { return cdrField( item, 'startTime', 'start', 'callStartTime', 'Start time', 'Start Time', 'answerTime', 'Answer time', 'Answer Time', ); } export function cdrDurationSeconds(item) { const raw = cdrField( item, 'durationSeconds', 'duration', 'callDuration', 'talkDuration', 'length', 'Duration', ); return Number(raw || 0); } export function cdrDirectionValue(item) { return String(cdrField( item, 'direction', 'callDirection', 'callType', 'directionIndicator', 'Direction', 'Call direction', 'Call Direction', ) || '').toUpperCase(); } export function cdrDispositionValue(item) { return String(cdrField( item, 'status', 'result', 'callResult', 'callOutcome', 'callStatus', 'Disposition', 'Answer indicator', 'Call outcome', ) || 'unknown'); } export function cdrCallingNumber(item) { return cdrField( item, 'callingNumber', 'callingParty', 'callingName', 'Calling number', 'Calling Number', 'User number', 'User Number', ); } export function cdrCalledNumber(item) { return cdrField( item, 'calledNumber', 'calledParty', 'calledName', 'Called number', 'Called Number', ); } export function cdrDedupKey(item) { const id = cdrRecordId(item); if (id) return `id:${id}`; return [ cdrStartTime(item) || '', cdrCallingNumber(item) || '', cdrCalledNumber(item) || '', cdrDurationSeconds(item) || '', ].join('|'); } export function extractCdrFeedList(data) { if (!data) return []; if (Array.isArray(data)) return data; if (Array.isArray(data.items)) return data.items; if (data.items && typeof data.items === 'object' && data.items !== null) { const keys = Object.keys(data.items).filter((k) => /^\d+$/.test(k)).sort((a, b) => parseInt(a, 10) - parseInt(b, 10)); if (keys.length > 0) return keys.map((k) => data.items[k]); } const topKeys = Object.keys(data).filter((k) => /^\d+$/.test(k)).sort((a, b) => parseInt(a, 10) - parseInt(b, 10)); if (topKeys.length > 0) return topKeys.map((k) => data[k]); for (const key of ['records', 'cdrRecords', 'callHistory', 'calls']) { if (Array.isArray(data[key])) return data[key]; } if (data.data) { if (Array.isArray(data.data)) return data.data; if (Array.isArray(data.data.items)) return data.data.items; } for (const v of Object.values(data)) { if (Array.isArray(v)) return v; } return []; } export function describeCdrFeedPayload(data) { if (!data) return 'empty body'; if (Array.isArray(data)) return `array[${data.length}]`; const keys = Object.keys(data); const parts = [`keys=${keys.slice(0, 12).join(',')}`]; for (const k of ['items', 'records', 'cdrRecords', 'next', 'data']) { const v = data[k]; if (Array.isArray(v)) parts.push(`${k}.length=${v.length}`); else if (v && typeof v === 'object') parts.push(`${k}.keys=${Object.keys(v).slice(0, 6).join(',')}`); } return parts.join(' '); }