from .DataStream import EMchPosmap as pos_map, span_days, time_border, calc_interval from .MerchantReader import MerchantReader from matplotlib.figure import Figure from matplotlib import ticker from io import BytesIO import numpy as np from .algorithm import calc_mchratios, calc_morder_send import time as time import logging logger = logging.getLogger('painter') _all_mchids = set() def add_mchid(mchid): if mchid not in _all_mchids: _all_mchids.add(mchid) def get_mchids(): return list(_all_mchids) def allpathes(reader: MerchantReader, tuple_pathes: dict, days: list, spec=None): count = len(days) show_detail = True if len(list(tuple_pathes.keys())) == 1 else False if show_detail == False: all_datas = reader.init_data(count) else: all_datas = None for mchid, tup in tuple_pathes.items(): add_mchid(mchid) ch_datas = reader.init_data(count) for _card_type, _spec in tup: if spec is not None and _spec != spec: continue if show_detail: detail_datas = reader.init_data(count) else: detail_datas = None for i, day in enumerate(days): data = reader.read(day, mchid, _card_type, _spec) if data is not None: column_pos = i * 86400 view = ch_datas[:, column_pos:column_pos + 86400] view += data if show_detail: view = detail_datas[:, column_pos:column_pos + 86400] view += data if show_detail: yield mchid, _card_type, _spec, detail_datas if all_datas is not None: all_datas += ch_datas yield mchid, None, None, ch_datas if show_detail == False: yield 'all', None, None, all_datas class MerchantPainter(object): def __init__(self, start_time: int, end_time: int, mchids: set = None, card_types: set = None, spec: int = None, filter_wave: int = None): self._reader = MerchantReader() _start_time, _end_time, self._mchids, self._card_types, self._spec, self._filter_wave = start_time, end_time, mchids, card_types, spec, filter_wave if _end_time is None: _end_time = int(time.time()) end_time = self._reader.near_stamp(_end_time, False) if end_time is None: raise Exception('data is empty') if _start_time is None or start_time > end_time: _start_time = end_time - 7200 start_time = self._reader.near_stamp(_start_time, True) if start_time is None: raise Exception('data is empty') stime = lambda t: time.strftime('%d-%H:%M:%S', time.localtime(t)) logger.debug("near_stamp start_time %d , %s end_time=%s", start_time, stime(start_time), stime(end_time)) interval = calc_interval(start_time, end_time) start_time = time_border(interval, start_time, True) end_time = time_border(interval, end_time, False) logger.debug("time_border start_time %d , %s end_time=%s", start_time, stime(start_time), stime(end_time)) self._days = span_days(start_time, end_time) stime = lambda t: time.strftime('%d-%H:%M:%S', time.localtime(t)) sdays = [stime(day) for day in self._days] logger.debug(sdays) self._start_time = start_time self._end_time = end_time self._interval = interval pass def _fig_funs(self): def create(): fig = Figure(figsize=(19, 8)) ax = fig.subplots() ax.set_title('success ratio') ax.set(xlabel='time', ylabel='ratio') return ax, fig def flush(ax, fig, xticks=None, xlables=None, yticks=None, ylables=None): ax.yaxis.set_major_formatter(ticker.PercentFormatter(xmax=1, decimals=4)) if xticks is not None: ax.set_xticks(ticks=xticks) if xlables is not None: ax.set_xticklabels(xlables) if yticks is not None: ax.set_yticks(ticks=yticks) if ylables is not None: ax.set_yticklabels(ylables) fig.autofmt_xdate() ax.grid() fig.subplots_adjust(left=0.1, right=0.8, top=0.95, bottom=0.1) ax.legend(bbox_to_anchor=(1, 1), loc='upper left') buf = BytesIO() fig.savefig(buf, format="png") return buf return create, flush def paint_ratios(self): tuple_pathes = self._reader.many_tuple_path(self._days, self._mchids, self._card_types, self._spec) gen = allpathes(self._reader, tuple_pathes, self._days, self._spec) if len(self._days) == 0: return BytesIO() day_stamp = self._days[0] fig_create, fig_flush = self._fig_funs() ax, fig = fig_create() x = np.array([d - self._start_time for d in range(self._start_time, self._end_time)]) if self._filter_wave is not None and self._filter_wave > 1: window = np.ones(self._filter_wave) / self._filter_wave else: window = None mchid_ratios = [] for _mchid, _card_type, _spec, _data in gen: succ, count, y = calc_mchratios(_data, pos_map, self._start_time - day_stamp, self._end_time - day_stamp) y = np.convolve(y, window, 'same') if window is not None else y label, ratio = self._label(chname=_mchid, succ=succ, count=count, card_type=_card_type, spec=_spec) ax.plot(x, y, ls='-', label=label) if _card_type is None and _spec is None and type(_mchid) is int: mchid_ratios.append((_mchid, ratio)) xticks = [d - self._start_time for d in range(self._start_time, self._end_time + 1, self._interval)] xlables = [time.strftime('%d-%H:%M:%S', time.localtime(d + self._start_time)) for d in xticks] buf = fig_flush(ax, fig, xticks=xticks, xlables=xlables) mchid_ratios = sorted(mchid_ratios, key=lambda x: (x[1], x[0]), reverse=True) result = [] for mchid,ratio in mchid_ratios: result.append(f'{mchid}:{ratio}') return buf, result def _label(self, chname, succ, count, card_type=None, spec=None): _card_type = None if card_type == 1: _card_type = 'SY' elif card_type == 2: _card_type = 'SH' elif card_type == 4: _card_type = 'YD' elif card_type == 5: _card_type = 'LT' elif card_type == 6: _card_type = 'DX' elif card_type == 7: _card_type = 'TH' lable = f"{chname}" if _card_type is not None: lable += f"-{_card_type}" if spec is not None: lable += f"-{spec}" if count > 0: ratio = round(succ * 100 / count, 2) else: ratio = 0.00 lable += f":{succ}/{count}={ratio}%" return lable,ratio ################################################################################################################################################## def _fig_bar_funs(self): def create(): fig = Figure(figsize=(19, 16)) ax_count, ax_amount = fig.subplots(2, 1) ax_count.set_title('sending order count monitor') ax_count.set(xlabel='merchant id', ylabel='count') ax_amount.set_title('sending order amount monitor') ax_amount.set(xlabel='merchant id', ylabel='amount') return ax_count, ax_amount, fig def end(ax, xticks=None, xlables=None, yticks=None, ylables=None): if xticks is not None: ax.set_xticks(ticks=xticks) if xlables is not None: ax.set_xticklabels(xlables) if yticks is not None: ax.set_yticks(ticks=yticks) if ylables is not None: ax.set_yticklabels(ylables) ax.legend() ax.grid() def flush(fig): fig.autofmt_xdate() buf = BytesIO() fig.savefig(buf, format="png") return buf return create, end,flush def paint_refilling(self): tuple_pathes = self._reader.many_tuple_path(self._days, self._mchids, self._card_types, self._spec) gen = allpathes(self._reader, tuple_pathes, self._days, self._spec) if len(self._days) == 0: return BytesIO() day_stamp = self._days[0] fig_create, fig_end, fig_flush = self._fig_bar_funs() ax_count, ax_amount, fig = fig_create() lables = list() datas = list() mchids = list() for _mchid, _card_type, _spec, _data in gen: lables.append(f"{_mchid}") logger.debug(_mchid) mchids.append(_mchid) ret = calc_morder_send(_data, pos_map, self._start_time - day_stamp, self._end_time - day_stamp) datas.append(ret) send_count, submit_count, succ_count, fail_count, submit_amount, succ_amount, fail_amount, send_amount, lack = ret logger.debug("send=%d submit=%d succ=%d fail=%d",send_count, submit_count, succ_count, fail_count) cratio = succ_count / (succ_count + fail_count + 0.0001) aratio = succ_amount / (succ_amount + fail_amount + 0.0001) logger.debug("cratio=%.2f aratio=%.2f", cratio, aratio) send_count, submit_count, succ_count, fail_count, submit_amount, succ_amount, fail_amount, send_amount, lack = zip(*datas) self.draw_count(ax_count, lables, send_count, submit_count, succ_count, fail_count, fig_end) self.draw_amount(ax_amount, lables, submit_amount, succ_amount, fail_amount, send_amount, lack, fig_end) return fig_flush(fig), mchids def draw_count(self, ax, lables, send_count, submit_count, succ_count, fail_count, fig_end): width = 0.24 x_asix = np.arange(len(lables)) rect_send = ax.bar(x_asix - width * 0.5, list(send_count), width, label='sending', align='center') rect_submit = ax.bar(x_asix - width * 1.5, list(submit_count), width, label='summit',align='center') rect_succ = ax.bar(x_asix + width * 0.5, list(succ_count), width, label='succ',align='center') rect_fail = ax.bar(x_asix + width * 1.5, list(fail_count), width, label='fail', align='center') ax.bar_label(rect_send, padding=0, rotation=270, fmt='%d') ax.bar_label(rect_submit, padding=0, rotation=270, fmt='%d') ax.bar_label(rect_succ, padding=0, rotation=270, fmt='%d') ax.bar_label(rect_fail, padding=0, rotation=270, fmt='%d') fig_end(ax, xticks=x_asix, xlables=lables) pass def draw_amount(self, ax, lables, submit_amount, succ_amount, fail_amount, send_amount, lack, fig_end): width = 0.18 x_asix = np.arange(len(lables)) rect_submit = ax.bar(x_asix - width * 2, list(submit_amount), width, label='summit',align='center') rect_send = ax.bar(x_asix - width, list(send_amount), width, label='sending', align='center') rect_msucc = ax.bar(x_asix, list(lack), width, label='may succ', align='center') rect_succ = ax.bar(x_asix + width, list(succ_amount), width, label='succ',align='center') rect_fail = ax.bar(x_asix + width * 2, list(fail_amount), width, label='fail', align='center') ax.bar_label(rect_submit, padding=0, rotation=270, fmt='%.2f') ax.bar_label(rect_send, padding=0, rotation=270, fmt='%.2f') ax.bar_label(rect_msucc, padding=0, rotation=270, fmt='%.2f') ax.bar_label(rect_succ, padding=0, rotation=270, fmt='%.2f') ax.bar_label(rect_fail, padding=0, rotation=270, fmt='%.2f') fig_end(ax, xticks=x_asix, xlables=lables) pass