cel.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. #!/usr/bin/env python3
  2. import json
  3. import re
  4. from datetime import datetime
  5. from datetime import timedelta as td
  6. fieldsFilter = ('id', 'linkedid', 'appdata', 'lastdata')
  7. class CdrChannel:
  8. def __init__(self, chanData):
  9. self._str = chanData
  10. try:
  11. (self.tech,
  12. self.peer,
  13. self.context,
  14. self.id,
  15. self.leg) = re.match(r'(\w+)/(\w+)(?:@([\w-]+))?-([0-9a-f]{8})(?:;([0-9]+))?', chanData).groups()
  16. except:
  17. pass
  18. def __repr__(self):
  19. return self._str
  20. class CdrEvent:
  21. def __init__(self, eventData):
  22. eventData = dict(eventData)
  23. for key, value in eventData.items():
  24. if ((key not in fieldsFilter) and (value is not None) and (str(value) != '')):
  25. if key == 'extra':
  26. setattr(self, key, json.loads(value))
  27. elif key in ('channame','channel','dstchannel'):
  28. setattr(self, key, CdrChannel(value))
  29. else:
  30. setattr(self, key, value)
  31. def __getattr__(self, attr):
  32. return None
  33. class CdrEvents:
  34. def __init__(self,linkedid):
  35. self._events = []
  36. self._channels = []
  37. self._dstchannels = []
  38. self.start = 0
  39. self.linkedid = linkedid
  40. def add(self, event):
  41. if not isinstance(event, CdrEvent):
  42. event = CdrEvent(event)
  43. if not self.start:
  44. self.start = event.calldate
  45. if event.channel not in self._channels:
  46. self._channels[event.channel] = event
  47. if event.dstchannel not in self._dstchannels:
  48. self._dstchannels[event.dstchannel] = event.dst
  49. if (event.lastapp == 'Queue'):
  50. for e in self._events:
  51. if (e.lastapp == 'Queue') and (e.uniqueid == event.uniqueid):
  52. if (event.disposition == 'ANSWERED'):
  53. e.disposition = 'ANSWERED'
  54. if event.dstchannel in self._channels:
  55. dialevent = self._channels[event.dstchannel]
  56. e.duration = e.start-dialevent.start + dialevent.duration
  57. e.billsec = dialevent.billsec
  58. return # if it not fiars queue event - miss it
  59. if (event.lastapp == 'Dial' and event.dstchannel in self._dstchannels):
  60. event.dst = self._dstchannels[event.dstchannel]
  61. self.end = event.calldate + td(seconds=event.duration)
  62. self._events.append(event)
  63. @property
  64. def all(self):
  65. return self._events
  66. @property
  67. def first(self):
  68. return self._events[0] if len(self._events) > 0 else None
  69. @property
  70. def second(self):
  71. return self._events[1] if len(self._events) > 1 else None
  72. @property
  73. def last(self):
  74. return self._events[-1] if len(self._events) > 0 else None
  75. def filter(self, key, value, func='__eq__'):
  76. result = CdrEvents()
  77. for event in self._events:
  78. if getattr(str(getattr(event, key)), func)(value):
  79. result.add(event)
  80. return result
  81. def has(self, value, key='disposition'):
  82. for event in self._events:
  83. if getattr(event, key) == value:
  84. return True
  85. return False
  86. def __len__(self):
  87. return len(self._events)
  88. def simple(self):
  89. res = []
  90. for event in self._events:
  91. simple_event = {'start': event.calldate,
  92. 'answered': event.disposition=='ANSWERED',
  93. 'duration': event.billsec,
  94. 'waiting': event.duration = event.billsec,
  95. 'application': event.lastapp,
  96. 'src': event.src,
  97. 'dst': event.dst,
  98. 'uniqueid': event.uniqueid,
  99. 'file': event.recordingfile}
  100. res.append(simple_event)
  101. return res
  102. class CdrUserEvents(CdrEvents):
  103. def __init__(self, user):
  104. self._user = user
  105. self._events = []
  106. self.start = 0
  107. self.end = 0
  108. def add(self, event):
  109. if not isinstance(event, CdrEvent):
  110. event = CdrEvent(event)
  111. if self._user in (event.src, event.dst, event.cnum):
  112. if len(self._events) == 0:
  113. self.start = event.calldate
  114. self.end = event.calldate + td(seconds=event.duration)
  115. self._events.append(event)
  116. else:
  117. if (event.calldate + td(seconds=event.duration)) > self.end:
  118. self.end = event.calldate + td(seconds=event.duration)
  119. if event.calldate < self.start:
  120. self.start = event.calldate
  121. lo = 0
  122. hi = len(self._events)
  123. while lo < hi:
  124. mid = (lo+hi)//2
  125. if event.calldate <= self._events[mid].calldate:
  126. hi = mid
  127. else:
  128. lo = mid+1
  129. self._events.insert(lo, event)
  130. def simple(self):
  131. res = []
  132. for event in self._events:
  133. simple_event = {'start': event.calldate,
  134. 'answered': event.disposition=='ANSWERED',
  135. 'duration': event.duration,
  136. #'waiting': event.waiting,
  137. 'application': event.lastapp,
  138. 'dst': event.dst,
  139. 'uniqueid': event.uniqueid,
  140. 'file': event.recordingfile}
  141. res.append(simple_event)
  142. return res
  143. class CelEvents(CdrEvents):
  144. def has(self, value, key='eventtype'):
  145. return super().has(value, key)
  146. class CdrCall:
  147. def __init__(self, event=None):
  148. self.events = CdrEvents()
  149. if event is not None:
  150. self.events.add(event)
  151. self.linkedid = event['linkedid']
  152. else:
  153. self.linkedid = None
  154. @property
  155. def start(self):
  156. return self.events.first.calldate
  157. @property
  158. def disposition(self):
  159. return self.events.first.disposition
  160. @property
  161. def file(self):
  162. return self.events.first.recordingfile
  163. @property
  164. def src(self):
  165. return self.events.first.cnum
  166. @property
  167. def direction(self):
  168. if self.events.first.dcontext == 'from-internal':
  169. if self.events.first.outbound_cnum is not None:
  170. return 'outbound'
  171. else:
  172. return 'local'
  173. else:
  174. return 'inbound'
  175. @property
  176. def dst(self):
  177. # placeholder
  178. # TODO: determine last dst based on disposition
  179. if self.direction == 'outbound':
  180. return self.events.first.dst
  181. elif self.direction == 'local':
  182. return self.events.first.dst
  183. else:
  184. if self.events.first.dstchannel is not None:
  185. return self.events.first.dstchannel.peer
  186. else:
  187. return self.events.first.dst
  188. @property
  189. def did(self):
  190. if self.direction == 'inbound':
  191. return self.events.first.did
  192. else:
  193. return None
  194. @property
  195. def duration(self):
  196. if not self.isAnswered:
  197. return 0
  198. else:
  199. if len(self.events) > 1:
  200. return self.events.second.billsec
  201. else:
  202. return self.events.first.billsec
  203. @property
  204. def waiting(self):
  205. if not self.isAnswered:
  206. return self.events.first.duration
  207. else:
  208. # TODO: exclude time on ivr
  209. return self.events.first.duration - self.duration
  210. @property
  211. def isAnswered(self):
  212. return self.disposition == 'ANSWERED';
  213. class CdrUserCall(CdrCall):
  214. def __init__(self, user, event=None):
  215. self.user = user
  216. self.events = CdrUserEvents(user)
  217. if event is not None:
  218. self.events.add(event)
  219. self.linkedid = event['linkedid']
  220. else:
  221. self.linkedid = None
  222. @property
  223. def file(self):
  224. return self.events.first.recordingfile
  225. @property
  226. def src(self):
  227. return self.events.first.cnum
  228. @property
  229. def direction(self):
  230. if self.user in (self.events.first.src, self.events.first.cnum):
  231. return 'outbound'
  232. else:
  233. return 'inbound'
  234. @property
  235. def dst(self):
  236. return self.events.first.dst
  237. @property
  238. def did(self):
  239. if self.direction == 'inbound':
  240. return self.events.first.did
  241. else:
  242. return None
  243. @property
  244. def duration(self):
  245. if not self.isAnswered:
  246. return 0
  247. else:
  248. return self.events.first.billsec
  249. @property
  250. def waiting(self):
  251. if not self.isAnswered:
  252. return self.events.first.duration
  253. else:
  254. # TODO: exclude time on ivr
  255. return self.events.first.duration - self.duration
  256. @property
  257. def isAnswered(self):
  258. return self.disposition == 'ANSWERED';
  259. @property
  260. def simple(self):
  261. return {'start': self.start,
  262. 'direction': self.direction,
  263. 'answered': self.isAnswered,
  264. 'duration': self.duration,
  265. 'waiting': self.waiting,
  266. 'src': self.src,
  267. 'dst': self.dst,
  268. 'did': self.did,
  269. 'linkedid': self.linkedid,
  270. 'file': self.file,
  271. }#'events': self.events.simple()}
  272. class CelCall:
  273. def __init__(self, event=None):
  274. self.events = CelEvents()
  275. if event is not None:
  276. self.events.add(event)
  277. self.linkedid = event['linkedid']
  278. else:
  279. self.linkedid = None
  280. @property
  281. def start(self):
  282. return self.events.first.eventtime