MerchantPainter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. from .DataStream import EMchPosmap as pos_map, span_days, time_border, calc_interval
  2. from .MerchantReader import MerchantReader
  3. from matplotlib.figure import Figure
  4. from matplotlib import ticker
  5. from io import BytesIO
  6. import numpy as np
  7. from .algorithm import calc_mchratios, calc_morder_send
  8. import time as time
  9. import logging
  10. logger = logging.getLogger('painter')
  11. _all_mchids = set()
  12. def add_mchid(mchid):
  13. if mchid not in _all_mchids:
  14. _all_mchids.add(mchid)
  15. def get_mchids():
  16. return list(_all_mchids)
  17. def allpathes(reader: MerchantReader, tuple_pathes: dict, days: list, spec=None):
  18. count = len(days)
  19. show_detail = True if len(list(tuple_pathes.keys())) == 1 else False
  20. if show_detail == False:
  21. all_datas = reader.init_data(count)
  22. else:
  23. all_datas = None
  24. for mchid, tup in tuple_pathes.items():
  25. add_mchid(mchid)
  26. ch_datas = reader.init_data(count)
  27. for _card_type, _spec in tup:
  28. if spec is not None and _spec != spec:
  29. continue
  30. if show_detail:
  31. detail_datas = reader.init_data(count)
  32. else:
  33. detail_datas = None
  34. for i, day in enumerate(days):
  35. data = reader.read(day, mchid, _card_type, _spec)
  36. if data is not None:
  37. column_pos = i * 86400
  38. view = ch_datas[:, column_pos:column_pos + 86400]
  39. view += data
  40. if show_detail:
  41. view = detail_datas[:, column_pos:column_pos + 86400]
  42. view += data
  43. if show_detail:
  44. yield mchid, _card_type, _spec, detail_datas
  45. if all_datas is not None:
  46. all_datas += ch_datas
  47. yield mchid, None, None, ch_datas
  48. if show_detail == False:
  49. yield 'all', None, None, all_datas
  50. class MerchantPainter(object):
  51. def __init__(self, start_time: int, end_time: int, mchids: set = None, card_types: set = None, spec: int = None, filter_wave: int = None):
  52. self._reader = MerchantReader()
  53. _start_time, _end_time, self._mchids, self._card_types, self._spec, self._filter_wave = start_time, end_time, mchids, card_types, spec, filter_wave
  54. if _end_time is None:
  55. _end_time = int(time.time())
  56. end_time = self._reader.near_stamp(_end_time, False)
  57. if end_time is None:
  58. raise Exception('data is empty')
  59. if _start_time is None or start_time > end_time:
  60. _start_time = end_time - 7200
  61. start_time = self._reader.near_stamp(_start_time, True)
  62. if start_time is None:
  63. raise Exception('data is empty')
  64. stime = lambda t: time.strftime('%d-%H:%M:%S', time.localtime(t))
  65. logger.debug("near_stamp start_time %d , %s end_time=%s", start_time, stime(start_time), stime(end_time))
  66. interval = calc_interval(start_time, end_time)
  67. start_time = time_border(interval, start_time, True)
  68. end_time = time_border(interval, end_time, False)
  69. logger.debug("time_border start_time %d , %s end_time=%s", start_time, stime(start_time), stime(end_time))
  70. self._days = span_days(start_time, end_time)
  71. stime = lambda t: time.strftime('%d-%H:%M:%S', time.localtime(t))
  72. sdays = [stime(day) for day in self._days]
  73. logger.debug(sdays)
  74. self._start_time = start_time
  75. self._end_time = end_time
  76. self._interval = interval
  77. pass
  78. def _fig_funs(self):
  79. def create():
  80. fig = Figure(figsize=(19, 8))
  81. ax = fig.subplots()
  82. ax.set_title('success ratio')
  83. ax.set(xlabel='time', ylabel='ratio')
  84. return ax, fig
  85. def flush(ax, fig, xticks=None, xlables=None, yticks=None, ylables=None):
  86. ax.yaxis.set_major_formatter(ticker.PercentFormatter(xmax=1, decimals=4))
  87. if xticks is not None:
  88. ax.set_xticks(ticks=xticks)
  89. if xlables is not None:
  90. ax.set_xticklabels(xlables)
  91. if yticks is not None:
  92. ax.set_yticks(ticks=yticks)
  93. if ylables is not None:
  94. ax.set_yticklabels(ylables)
  95. fig.autofmt_xdate()
  96. ax.grid()
  97. fig.subplots_adjust(left=0.1, right=0.8, top=0.95, bottom=0.1)
  98. ax.legend(bbox_to_anchor=(1, 1), loc='upper left')
  99. buf = BytesIO()
  100. fig.savefig(buf, format="png")
  101. return buf
  102. return create, flush
  103. def paint_ratios(self):
  104. tuple_pathes = self._reader.many_tuple_path(self._days, self._mchids, self._card_types, self._spec)
  105. gen = allpathes(self._reader, tuple_pathes, self._days, self._spec)
  106. if len(self._days) == 0:
  107. return BytesIO()
  108. day_stamp = self._days[0]
  109. fig_create, fig_flush = self._fig_funs()
  110. ax, fig = fig_create()
  111. x = np.array([d - self._start_time for d in range(self._start_time, self._end_time)])
  112. if self._filter_wave is not None and self._filter_wave > 1:
  113. window = np.ones(self._filter_wave) / self._filter_wave
  114. else:
  115. window = None
  116. mchid_ratios = []
  117. for _mchid, _card_type, _spec, _data in gen:
  118. succ, count, y = calc_mchratios(_data, pos_map, self._start_time - day_stamp, self._end_time - day_stamp)
  119. y = np.convolve(y, window, 'same') if window is not None else y
  120. label, ratio = self._label(chname=_mchid, succ=succ, count=count, card_type=_card_type, spec=_spec)
  121. ax.plot(x, y, ls='-', label=label)
  122. if _card_type is None and _spec is None and type(_mchid) is int:
  123. mchid_ratios.append((_mchid, ratio))
  124. xticks = [d - self._start_time for d in range(self._start_time, self._end_time + 1, self._interval)]
  125. xlables = [time.strftime('%d-%H:%M:%S', time.localtime(d + self._start_time)) for d in xticks]
  126. buf = fig_flush(ax, fig, xticks=xticks, xlables=xlables)
  127. mchid_ratios = sorted(mchid_ratios, key=lambda x: (x[1], x[0]), reverse=True)
  128. result = []
  129. for mchid,ratio in mchid_ratios:
  130. result.append(f'{mchid}:{ratio}')
  131. return buf, result
  132. def _label(self, chname, succ, count, card_type=None, spec=None):
  133. _card_type = None
  134. if card_type == 1:
  135. _card_type = 'SY'
  136. elif card_type == 2:
  137. _card_type = 'SH'
  138. elif card_type == 4:
  139. _card_type = 'YD'
  140. elif card_type == 5:
  141. _card_type = 'LT'
  142. elif card_type == 6:
  143. _card_type = 'DX'
  144. elif card_type == 7:
  145. _card_type = 'TH'
  146. lable = f"{chname}"
  147. if _card_type is not None:
  148. lable += f"-{_card_type}"
  149. if spec is not None:
  150. lable += f"-{spec}"
  151. if count > 0:
  152. ratio = round(succ * 100 / count, 2)
  153. else:
  154. ratio = 0.00
  155. lable += f":{succ}/{count}={ratio}%"
  156. return lable,ratio
  157. ##################################################################################################################################################
  158. def _fig_bar_funs(self):
  159. def create():
  160. fig = Figure(figsize=(19, 16))
  161. ax_count, ax_amount = fig.subplots(2, 1)
  162. ax_count.set_title('sending order count monitor')
  163. ax_count.set(xlabel='merchant id', ylabel='count')
  164. ax_amount.set_title('sending order amount monitor')
  165. ax_amount.set(xlabel='merchant id', ylabel='amount')
  166. return ax_count, ax_amount, fig
  167. def end(ax, xticks=None, xlables=None, yticks=None, ylables=None):
  168. if xticks is not None:
  169. ax.set_xticks(ticks=xticks)
  170. if xlables is not None:
  171. ax.set_xticklabels(xlables)
  172. if yticks is not None:
  173. ax.set_yticks(ticks=yticks)
  174. if ylables is not None:
  175. ax.set_yticklabels(ylables)
  176. ax.legend()
  177. ax.grid()
  178. def flush(fig):
  179. fig.autofmt_xdate()
  180. buf = BytesIO()
  181. fig.savefig(buf, format="png")
  182. return buf
  183. return create, end,flush
  184. def paint_refilling(self):
  185. tuple_pathes = self._reader.many_tuple_path(self._days, self._mchids, self._card_types, self._spec)
  186. gen = allpathes(self._reader, tuple_pathes, self._days, self._spec)
  187. if len(self._days) == 0:
  188. return BytesIO()
  189. day_stamp = self._days[0]
  190. fig_create, fig_end, fig_flush = self._fig_bar_funs()
  191. ax_count, ax_amount, fig = fig_create()
  192. lables = list()
  193. datas = list()
  194. mchids = list()
  195. for _mchid, _card_type, _spec, _data in gen:
  196. lables.append(f"{_mchid}")
  197. logger.debug(_mchid)
  198. mchids.append(_mchid)
  199. ret = calc_morder_send(_data, pos_map, self._start_time - day_stamp, self._end_time - day_stamp)
  200. datas.append(ret)
  201. send_count, submit_count, succ_count, fail_count, submit_amount, succ_amount, fail_amount, send_amount, lack = ret
  202. logger.debug("send=%d submit=%d succ=%d fail=%d",send_count, submit_count, succ_count, fail_count)
  203. cratio = succ_count / (succ_count + fail_count + 0.0001)
  204. aratio = succ_amount / (succ_amount + fail_amount + 0.0001)
  205. logger.debug("cratio=%.2f aratio=%.2f", cratio, aratio)
  206. send_count, submit_count, succ_count, fail_count, submit_amount, succ_amount, fail_amount, send_amount, lack = zip(*datas)
  207. self.draw_count(ax_count, lables, send_count, submit_count, succ_count, fail_count, fig_end)
  208. self.draw_amount(ax_amount, lables, submit_amount, succ_amount, fail_amount, send_amount, lack, fig_end)
  209. return fig_flush(fig), mchids
  210. def draw_count(self, ax, lables, send_count, submit_count, succ_count, fail_count, fig_end):
  211. width = 0.24
  212. x_asix = np.arange(len(lables))
  213. rect_send = ax.bar(x_asix - width * 0.5, list(send_count), width, label='sending', align='center')
  214. rect_submit = ax.bar(x_asix - width * 1.5, list(submit_count), width, label='summit',align='center')
  215. rect_succ = ax.bar(x_asix + width * 0.5, list(succ_count), width, label='succ',align='center')
  216. rect_fail = ax.bar(x_asix + width * 1.5, list(fail_count), width, label='fail', align='center')
  217. ax.bar_label(rect_send, padding=0, rotation=270, fmt='%d')
  218. ax.bar_label(rect_submit, padding=0, rotation=270, fmt='%d')
  219. ax.bar_label(rect_succ, padding=0, rotation=270, fmt='%d')
  220. ax.bar_label(rect_fail, padding=0, rotation=270, fmt='%d')
  221. fig_end(ax, xticks=x_asix, xlables=lables)
  222. pass
  223. def draw_amount(self, ax, lables, submit_amount, succ_amount, fail_amount, send_amount, lack, fig_end):
  224. width = 0.18
  225. x_asix = np.arange(len(lables))
  226. rect_submit = ax.bar(x_asix - width * 2, list(submit_amount), width, label='summit',align='center')
  227. rect_send = ax.bar(x_asix - width, list(send_amount), width, label='sending', align='center')
  228. rect_msucc = ax.bar(x_asix, list(lack), width, label='may succ', align='center')
  229. rect_succ = ax.bar(x_asix + width, list(succ_amount), width, label='succ',align='center')
  230. rect_fail = ax.bar(x_asix + width * 2, list(fail_amount), width, label='fail', align='center')
  231. ax.bar_label(rect_submit, padding=0, rotation=270, fmt='%.2f')
  232. ax.bar_label(rect_send, padding=0, rotation=270, fmt='%.2f')
  233. ax.bar_label(rect_msucc, padding=0, rotation=270, fmt='%.2f')
  234. ax.bar_label(rect_succ, padding=0, rotation=270, fmt='%.2f')
  235. ax.bar_label(rect_fail, padding=0, rotation=270, fmt='%.2f')
  236. fig_end(ax, xticks=x_asix, xlables=lables)
  237. pass