| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- #!/usr/bin/env python3
- import json
- import re
- from datetime import datetime
- from datetime import timedelta as td
- fieldsFilter = ('id', 'linkedid', 'appdata', 'lastdata')
- class CdrChannel:
- def __init__(self, chanData):
- self._str = chanData
- try:
- (self.tech,
- self.peer,
- self.context,
- self.id,
- self.leg) = re.match(r'(\w+)/(\w+)(?:@([\w-]+))?-([0-9a-f]{8})(?:;([0-9]+))?', chanData).groups()
- except:
- pass
- def __repr__(self):
- return self._str
- class CdrEvent:
- def __init__(self, eventData):
- eventData = dict(eventData)
- for key, value in eventData.items():
- if ((key not in fieldsFilter) and (value is not None) and (str(value) != '')):
- if key == 'extra':
- setattr(self, key, json.loads(value))
- elif key in ('channame','channel','dstchannel'):
- setattr(self, key, CdrChannel(value))
- else:
- setattr(self, key, value)
- def __getattr__(self, attr):
- return None
- class CdrEvents:
- def __init__(self):
- self._events = []
- self._channels = {}
- self._dstchannels = {}
- self.start = 0
- self._waitingchannel = None
-
- def add(self, event):
- if not isinstance(event, CdrEvent):
- event = CdrEvent(event)
- if not self.start:
- self.start = event.calldate
- if (event.lastapp == 'Return' and event.billsec==0):
- return
- if (event.lastapp == 'Return'):
- event.lastapp='Dial'
- event.duration=event.billsec
- event.cnum = event.src
- if (event.channel.id not in self._channels):
- self._channels[event.channel.id] = event
- if (event.dstchannel and event.dstchannel.id not in self._dstchannels):
- self._dstchannels[event.dstchannel.id] = event.dst
- if (self._waitingchannel is not None) and (self._waitingchannel.id == event.channel.id): # if dial event after queue event
- for e in self._events:
- if (e.lastapp == 'Queue') and (e.dstchannel.id == event.channel.id):
- e.duration = (event.calldate-e.calldate).total_seconds() + event.duration
- e.billsec = event.billsec
- self._waitingchannel = None
- if (event.lastapp == 'Queue'):
- event.billsec = 0
- queueevent = event
- for e in self._events:
- if (e.lastapp == 'Queue') and (e.uniqueid == event.uniqueid):#find first queue event
- queueevent = e
- if (event.disposition == 'ANSWERED'): #if it answered - fill duration and waitng based on answered dial event
- queueevent.disposition = 'ANSWERED'
- queueevent.dstchannel = event.dstchannel
- if (event.dstchannel and event.dstchannel.id in self._channels): # if dial event before queue event
- dialevent = self._channels[event.dstchannel.id]
- queueevent.duration = (dialevent.calldate-queueevent.calldate).total_seconds() + dialevent.duration
- queueevent.billsec = dialevent.billsec
- else:
- self._waitingchannel = event.dstchannel # wait for dial event
- else:
- if queueevent.disposition != 'ANSWERED': #count total queue duration
- queueevent.duration = (event.calldate-queueevent.calldate).total_seconds() + event.duration
- if (event != queueevent):
- return; # add only forst queue event
- if (event.lastapp == 'Dial' and event.dstchannel.id in self._dstchannels):
- event.dst = self._dstchannels[event.dstchannel.id] #get dst from fisrt event with rthis channel. for transfer
- if (event.lastapp == 'Dial' and event.dstchannel.id in self._channels):
- event.dst = self._channels[event.dstchannel.id].dst #for transfer to queue
- self.end = event.calldate + td(seconds=event.duration)
- self._events.append(event)
- @property
- def all(self):
- return self._events
- @property
- def first(self):
- return self._events[0] if len(self._events) > 0 else None
- @property
- def second(self):
- return self._events[1] if len(self._events) > 1 else None
- @property
- def last(self):
- return self._events[-1] if len(self._events) > 0 else None
- def filter(self, key, value, func='__eq__'):
- result = CdrEvents()
- for event in self._events:
- if getattr(str(getattr(event, key)), func)(value):
- result.add(event)
- return result
- def has(self, value, key='disposition'):
- for event in self._events:
- if getattr(event, key) == value:
- return True
- return False
- def __len__(self):
- return len(self._events)
- def simple(self):
- res = []
- for event in self._events:
- simple_event = {'start': event.calldate,
- 'answered': event.disposition=='ANSWERED',
- 'duration': event.billsec,
- 'waiting': event.duration - event.billsec,
- 'application': event.lastapp,
- 'caller': event.cnum,
- 'dst': event.dst,
- 'uniqueid': event.uniqueid,
- 'file': event.recordingfile}
- res.append(simple_event)
- return res
- class CdrUserEvents(CdrEvents):
- def __init__(self, user):
- self._user = user
- self._events = []
- self.start = 0
- self.answer = 0
- self.end = 0
- self.recordingfile = ''
- def add(self, event):
- if not isinstance(event, CdrEvent):
- event = CdrEvent(event)
- if (event.billsec > event.duration):
- event.billsec = event.duration
- if self._user in (event.src, event.dst, event.cnum):
- if (self.answer ==0) and (event.disposition == 'ANSWERED'):
- self.answer = event.calldate + td(seconds=event.duration - event.billsec)
- self.recordingfile = event.recordingfile
- if (self.recordingfile == ''):
- self.recordingfile = event.recordingfile
- if len(self._events) == 0:
- self.start = event.calldate
- self.end = event.calldate + td(seconds=event.duration)
- self._events.append(event)
- else:
- if (event.calldate + td(seconds=event.duration)) > self.end:
- self.end = event.calldate + td(seconds=event.duration)
- if event.calldate < self.start:
- self.start = event.calldate
- lo = 0
- hi = len(self._events)
- while lo < hi:
- mid = (lo+hi)//2
- if event.calldate <= self._events[mid].calldate:
- hi = mid
- else:
- lo = mid+1
- self._events.insert(lo, event)
-
- def simple(self):
- res = []
- for event in self._events:
- simple_event = {'start': event.calldate,
- 'answered': event.disposition=='ANSWERED',
- 'duration': event.duration,
- #'waiting': event.waiting,
- 'application': event.lastapp,
- 'dst': event.dst,
- 'uniqueid': event.uniqueid,
- 'file': event.recordingfile}
- res.append(simple_event)
- return res
- class CelEvents(CdrEvents):
- def has(self, value, key='eventtype'):
- return super().has(value, key)
- class CdrCall:
- def __init__(self, event=None):
- self.events = CdrEvents()
- if event is not None:
- self.events.add(event)
- self.linkedid = event['linkedid']
- else:
- self.linkedid = None
- @property
- def start(self):
- return self.events.first.calldate
- @property
- def disposition(self):
- return self.events.first.disposition
- @property
- def file(self):
- return self.events.first.recordingfile
- @property
- def src(self):
- return self.events.first.cnum
- @property
- def direction(self):
- if self.events.first.dcontext == 'from-internal':
- if self.events.first.outbound_cnum is not None:
- return 'outbound'
- else:
- return 'local'
- else:
- return 'inbound'
- @property
- def dst(self):
- # placeholder
- # TODO: determine last dst based on disposition
- if self.direction == 'outbound':
- return self.events.first.dst
- elif self.direction == 'local':
- return self.events.first.dst
- else:
- if self.events.first.dstchannel is not None:
- return self.events.first.dstchannel.peer
- else:
- return self.events.first.dst
- @property
- def did(self):
- if self.direction == 'inbound':
- return self.events.first.did
- else:
- return None
- @property
- def duration(self):
- if not self.isAnswered:
- return 0
- else:
- if len(self.events) > 1:
- return self.events.second.billsec
- else:
- return self.events.first.billsec
- @property
- def waiting(self):
- if not self.isAnswered:
- return self.events.first.duration
- else:
- # TODO: exclude time on ivr
- return self.events.first.duration - self.duration
- @property
- def isAnswered(self):
- return self.disposition == 'ANSWERED';
- class CdrUserCall(CdrCall):
- def __init__(self, user, event=None):
- self.user = user
- self.events = CdrUserEvents(user)
- if event is not None:
- self.events.add(event)
- self.linkedid = event['linkedid']
- else:
- self.linkedid = None
- @property
- def file(self):
- return self.events.recordingfile
- @property
- def src(self):
- return self.events.first.cnum
- @property
- def direction(self):
- if self.user in (self.events.first.src, self.events.first.cnum):
- return 'outbound'
- else:
- return 'inbound'
- @property
- def dst(self):
- return self.events.first.dst
- @property
- def did(self):
- if self.direction == 'inbound':
- return self.events.first.did
- else:
- return None
- @property
- def duration(self):
- if self.events.answer == 0:
- return 0
- else:
- return (self.events.end - self.events.answer).total_seconds()
- @property
- def waiting(self):
- if self.events.answer == 0:
- return (self.events.end - self.events.start).total_seconds()
- else:
- # TODO: exclude time on ivr
- return (self.events.answer - self.events.start).total_seconds()
- @property
- def isAnswered(self):
- return self.events.answer != 0;
- @property
- def simple(self):
- return {'start': self.start,
- 'direction': self.direction,
- 'answered': self.isAnswered,
- 'duration': self.duration,
- 'waiting': self.waiting,
- 'src': self.src,
- 'dst': self.dst,
- 'did': self.did,
- 'linkedid': self.linkedid,
- 'file': self.file,
- 'transfer_from': self.events.first.transfer_from
- }#'events': self.events.simple()}
- class CelCall:
- def __init__(self, event=None):
- self.events = CelEvents()
- if event is not None:
- self.events.add(event)
- self.linkedid = event['linkedid']
- else:
- self.linkedid = None
- @property
- def start(self):
- return self.events.first.eventtime
|