Procházet zdrojové kódy

feat: 新增数据时段分析

SongZihuan před 3 roky
rodič
revize
5df3c6d0f9
6 změnil soubory, kde provedl 388 přidání a 6 odebrání
  1. 14 0
      setup.sql
  2. 8 2
      sql/mysql_db.py
  3. 48 0
      tk_ui/admin_event.py
  4. 46 3
      tk_ui/admin_menu.py
  5. 263 1
      tk_ui/admin_program.py
  6. 9 0
      tool/color.py

+ 14 - 0
setup.sql

@@ -117,3 +117,17 @@ SELECT garbage_user.GarbageID       AS GarbageID,
        garbage_checker.CheckerPhone AS CheckerPhone
 FROM garbage_user
          LEFT JOIN garbage_checker on garbage_user.GarbageID = garbage_checker.GarbageID;
+
+
+-- 创建函数
+CREATE FUNCTION get_avg(num1 int, num2 int)
+    RETURNS DECIMAL(5, 4)
+    not deterministic
+    reads sql data
+    COMMENT '计算两个数相除'
+BEGIN
+    IF num2 = 0 or num1 = 0 THEN
+        RETURN 0;
+    END IF;
+    RETURN num1 / num2;
+END;

+ 8 - 2
sql/mysql_db.py

@@ -48,7 +48,8 @@ class MysqlDB(HGSDatabase):
                where: Union[str, List[str]] = None,
                limit: Optional[int] = None,
                offset: Optional[int] = None,
-               order_by: Optional[List[Tuple[str, str]]] = None):
+               order_by: Optional[List[Tuple[str, str]]] = None,
+               group_by: Optional[List[str]] = None):
         if type(where) is list and len(where) > 0:
             where: str = " WHERE " + " AND ".join(f"({w})" for w in where)
         elif type(where) is str and len(where) > 0:
@@ -72,8 +73,13 @@ class MysqlDB(HGSDatabase):
         else:
             offset = f" OFFSET {offset}"
 
+        if group_by is None:
+            group_by: str = ""
+        else:
+            group_by = "GROUP BY " + ", ".join(group_by)
+
         columns: str = ", ".join(columns)
-        return self.__search(f"SELECT {columns} FROM {table} {where} {order_by} {limit} {offset};")
+        return self.__search(f"SELECT {columns} FROM {table} {where} {group_by} {order_by} {limit} {offset};")
 
     def insert(self, table: str, columns: list, values: Union[str, List[str]]):
         columns: str = ", ".join(columns)

+ 48 - 0
tk_ui/admin_event.py

@@ -559,3 +559,51 @@ class UpdateGarbageCheckEvent(AdminEventBase):
             self.station.show_warning("更新失败", f"更新垃圾袋-检测结果失败")
         else:
             self.station.show_msg("更新成功", f"成功更新{res}个垃圾袋-检测结果")
+
+
+class CountThrowTimeEvent(AdminEventBase):
+    """
+    任务: 按时段-区域统计数据
+    """
+
+    def func(self, column: List, get_name: Callable):
+        res = {}
+        cur = self._db.search(columns=["DATE_FORMAT(UseTime,'%H') AS days", "count(GarbageID) AS count", *column],
+                              table="garbage",
+                              group_by=["days", *column],
+                              order_by=[(c, "DESC") for c in column] + [("days", "ASC")],
+                              where="UseTime IS NOT NULL")
+        if cur is None:
+            return None
+        loc_list = cur.fetchall()
+        loc_type = []
+        for i in loc_list:
+            name = get_name(i)
+            if name not in loc_type:
+                loc_type.append(name)
+            lst: List = res.get(name, list())
+            lst.append(i)
+            res[name] = lst
+        res['res_type'] = loc_type
+
+        return res
+
+    def __init__(self, gb_station):
+        super().__init__(gb_station)
+        self.thread = None
+        self._program: Optional[admin_program.StatisticsBaseProgram] = None
+
+    def start(self, column: List, get_name: Callable, program):
+        self.thread = TkThreading(self.func, column, get_name)
+        self._program = program
+        return self
+
+    def is_end(self) -> bool:
+        return not self.thread.is_alive()
+
+    def done_after_event(self):
+        res: Optional[Dict[str, str]] = self.thread.wait_event()
+        if res is None:
+            self.station.show_warning("数据分析", "数据获取时发生错误")
+        else:
+            self._program.show_result(res)

+ 46 - 3
tk_ui/admin_menu.py

@@ -207,11 +207,54 @@ class UpdateMenu(AdminMenu):
 class StatisticsMenu(AdminMenu):
     def __init__(self, station, win, color):
         super().__init__(station, win, color, "数据分析")
-        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(5)]
-        self.btn_name = ["时间分析", "积分分析", "信用分析", "失信用户", "通过率"]
+        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(6)]
+        self.btn_name = ["时段分析", "时间分析", "积分分析", "信用分析", "失信用户", "通过率"]
 
     def conf_gui(self, color: str, n: int = 1):
         super().conf_gui(color, n)
+        self.btn[0]['command'] = self.statistics_time_command
+
+    def statistics_time_command(self):
+        self.station.to_menu("时段分析")
+
+
+class StatisticsTimeMenu(AdminMenu):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析")
+        self.btn: List[tk.Button] = [tk.Button(self.frame) for _ in range(7)]
+        self.btn_name = ["按投放区域", "按投放类型", "投放类型和区域", "按检查结果", "按检查结果和类型", "按检查结果和区域",
+                         "详细分类"]
+
+    def conf_gui(self, color: str, n: int = 1):
+        super().conf_gui(color, n)
+        self.btn[0]['command'] = self.by_loc
+        self.btn[1]['command'] = self.by_type
+        self.btn[2]['command'] = self.by_type_and_type
+        self.btn[3]['command'] = self.by_result
+        self.btn[4]['command'] = self.by_result_and_type
+        self.btn[5]['command'] = self.by_result_and_loc
+        self.btn[6]['command'] = self.by_detail
+
+    def by_loc(self):
+        self.station.to_program("时段分析-按投放区域")
+
+    def by_type(self):
+        self.station.to_program("时段分析-按投放类型")
+
+    def by_type_and_type(self):
+        self.station.to_program("时段分析-按投放类型和区域")
+
+    def by_result(self):
+        self.station.to_program("时段分析-按检查结果")
+
+    def by_result_and_type(self):
+        self.station.to_program("时段分析-按检查结果和类型")
+
+    def by_result_and_loc(self):
+        self.station.to_program("时段分析-按检查结果和区域")
+
+    def by_detail(self):
+        self.station.to_program("时段分析-详细分类")
 
 
-all_menu = [MainMenu, CreateMenu, DeleteMenu, SearchMenu, UpdateMenu, StatisticsMenu]
+all_menu = [MainMenu, CreateMenu, DeleteMenu, SearchMenu, UpdateMenu, StatisticsMenu, StatisticsTimeMenu]

+ 263 - 1
tk_ui/admin_program.py

@@ -3,6 +3,12 @@ import tkinter as tk
 import tkinter.ttk as ttk
 from tkinter.filedialog import askdirectory, askopenfilename
 
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
+from matplotlib.axes import Axes
+import numpy as np
+from matplotlib.figure import Figure
+
+from tool.color import random_color
 from tool.type_ import *
 from tool.tk import make_font, set_tk_disable_from_list
 from tool.login import create_uid
@@ -11,6 +17,7 @@ from conf import Config
 from . import admin
 from . import admin_event as tk_event
 
+from sql import DBBit
 from sql.user import find_user_by_name
 from core.garbage import GarbageType
 
@@ -1735,9 +1742,264 @@ class UpdateGarbageCheckResultProgram(AdminProgram):
         self.where_enter['state'] = 'normal'
 
 
+class StatisticsBaseProgram(AdminProgram):
+    def __init__(self, station, win, color, title: str):
+        super().__init__(station, win, color, title)
+
+        self.figure_frame = tk.Frame(self.frame)
+        self.figure = Figure(dpi=100)
+        self.plt: Axes = self.figure.add_subplot(111)  # 添加子图:1行1列第1个
+
+        self.canvas = FigureCanvasTkAgg(self.figure, master=self.figure_frame)
+        self.canvas_tk = self.canvas.get_tk_widget()
+
+        self.toolbar = NavigationToolbar2Tk(self.canvas, self.figure_frame)
+
+        self.color_frame = tk.Frame(self.frame)
+        self.color_list = tk.Listbox(self.color_frame)
+        self.y_scroll = tk.Scrollbar(self.color_frame)
+        self.btn_color_hide = tk.Button(self.color_frame)
+        self.color_dict = {}
+
+        self.export_btn = tk.Button(self.frame)
+        self.refresh_btn = tk.Button(self.frame)
+        self.color_btn = tk.Button(self.frame)
+
+        self._conf("#abc88b")
+        self.__conf_font()
+
+    def _conf(self, bg_color):
+        self.bg_color = bg_color
+
+    def __conf_font(self, n: int = 1):
+        self.btn_font_size = int(14 * n)
+
+    def show_color(self):
+        self.color_list.delete(0, tk.END)  # 清空列表
+        for i in self.color_dict:
+            self.color_list.insert(tk.END, i)
+            self.color_list.itemconfig(tk.END,
+                                       selectbackground=self.color_dict[i],
+                                       bg=self.color_dict[i],
+                                       selectforeground='#FFFFFF',
+                                       fg='#000000')
+        self.color_frame.place(relx=0.45, rely=0.1, relwidth=0.2, relheight=0.70)
+
+    def hide_color(self):
+        self.color_frame.place_forget()
+
+    def conf_gui(self, n: int = 1):
+        self.__conf_font(n)
+        btn_font = make_font(size=self.btn_font_size)
+
+        self.color_frame['bg'] = self.bg_color
+        self.color_frame['bd'] = 5
+        self.color_frame['relief'] = "ridge"
+
+        self.color_list.place(relx=0, rely=0, relwidth=0.96, relheight=0.90)
+        self.y_scroll.place(relx=0.96, rely=0, relwidth=0.04, relheight=0.90)
+
+        self.y_scroll['orient'] = 'vertical'
+        self.y_scroll['command'] = self.color_list.yview
+        self.color_list['yscrollcommand'] = self.y_scroll.set
+        self.color_list['activestyle'] = tk.NONE
+
+        self.btn_color_hide['font'] = btn_font
+        self.btn_color_hide['bg'] = Config.tk_btn_bg
+        self.btn_color_hide['text'] = "关闭"
+        self.btn_color_hide['command'] = self.hide_color
+        self.btn_color_hide.place(relx=0, rely=0.90, relwidth=1, relheight=0.1)
+
+        self.figure_frame['bg'] = self.bg_color
+        self.figure_frame['bd'] = 5
+        self.figure_frame['relief'] = "ridge"
+        self.figure_frame.place(relx=0.05, rely=0.05, relwidth=0.9, relheight=0.85)
+
+        self.canvas_tk.place(relx=0, rely=0, relwidth=1.0, relheight=0.9)
+        self.toolbar.place(relx=0, rely=0.9, relwidth=1.0, relheight=0.1)
+
+        for btn, text, func, x in zip([self.color_btn, self.refresh_btn, self.export_btn],
+                                      ["显示标志", "刷新数据", "导出数据"],
+                                      [self.show_color, self.refresh, self.export],
+                                      [0.31, 0.53, 0.75]):
+            btn['font'] = btn_font
+            btn['bg'] = Config.tk_btn_bg
+            btn['text'] = text
+            btn['command'] = func
+            btn.place(relx=x, rely=0.91, relwidth=0.20, relheight=0.08)
+
+    def export(self):
+        ...
+
+    def refresh(self):
+        self.plt.cla()
+
+    def show_result(self, res: Dict[str, str]):
+        ...
+
+    def set_disable(self):
+        self.export_btn['state'] = 'disable'
+
+    def reset_disable(self):
+        self.export_btn['state'] = 'normal'
+
+
+class StatisticsTimeBaseProgram(StatisticsBaseProgram):
+    def __init__(self, station, win, color, title):
+        super().__init__(station, win, color, title)
+
+    def show_result(self, res: Dict[str, any]):
+        bottom = np.zeros(24)
+        label_num = [i for i in range(24)]
+        label_str = [f"{i}" for i in range(24)]
+        res_type_lst: List = res['res_type']
+        for res_type in res_type_lst:
+            res_count: Tuple[str] = res[res_type]
+            if len(res_count) != 0:
+                y = [0 for _ in range(24)]
+                for i in res_count:
+                    y[int(i[0])] = int(i[1])
+                color = self.color_dict.get(res_type, random_color())
+                self.color_dict[res_type] = color
+                self.plt.bar(label_num, y,
+                             color=color,
+                             align="center",
+                             bottom=bottom,
+                             tick_label=label_str,
+                             label=res_type)
+                bottom += np.array(y)
+
+        self.canvas.draw()
+        self.toolbar.update()
+
+    def export(self):
+        ...
+
+
+class StatisticsTimeLocProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-按投放区域")
+        self._conf("#abc88b")
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station).start(["Location"], lambda i: i[2], self)
+        self.station.push_event(event)
+
+
+class StatisticsTimeTypeProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-按投放类型")
+        self._conf("#abc88b")
+        self.color_dict[GarbageType.GarbageTypeStrList_ch[1]] = "#00BFFF"
+        self.color_dict[GarbageType.GarbageTypeStrList_ch[2]] = "#32CD32"
+        self.color_dict[GarbageType.GarbageTypeStrList_ch[3]] = "#DC143C"
+        self.color_dict[GarbageType.GarbageTypeStrList_ch[4]] = "#A9A9A9"
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station).start(["GarbageType"], self.get_name, self)
+        self.station.push_event(event)
+
+    @staticmethod
+    def get_name(i: Tuple):
+        data: bytes = i[2]
+        return GarbageType.GarbageTypeStrList_ch[int(data.decode('utf-8'))]
+
+
+class StatisticsTimeTypeLocProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-按投放类型和区域")
+        self._conf("#abc88b")
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station).start(["GarbageType", "Location"], self.get_name, self)
+        self.station.push_event(event)
+
+    @staticmethod
+    def get_name(i: Tuple):
+        data: bytes = i[2]
+        return f"{GarbageType.GarbageTypeStrList_ch[int(data.decode('utf-8'))]}-{i[3]}"
+
+
+class StatisticsTimeCheckResultProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-按检查结果")
+        self._conf("#abc88b")
+        self.color_dict['Pass'] = "#00BFFF"
+        self.color_dict['Fail'] = "#DC143C"
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station).start(["CheckResult"], self.get_name, self)
+        self.station.push_event(event)
+
+    @staticmethod
+    def get_name(i: Tuple):
+        data: bytes = i[2]
+        return 'Pass' if data == DBBit.BIT_1 else 'Fail'
+
+
+class StatisticsTimeCheckResultAndTypeProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-按检查结果和类型")
+        self._conf("#abc88b")
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station).start(["CheckResult", "GarbageType"], self.get_name, self)
+        self.station.push_event(event)
+
+    @staticmethod
+    def get_name(i: Tuple):
+        data_1: bytes = i[2]
+        data_2: bytes = i[3]
+        return ((f'Pass' if data_1 == DBBit.BIT_1 else 'Fail') +
+                f'-{GarbageType.GarbageTypeStrList_ch[int(data_2.decode("utf-8"))]}')
+
+
+class StatisticsTimeCheckResultAndLocProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-按检查结果和区域")
+        self._conf("#abc88b")
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station).start(["CheckResult", "Location"], self.get_name, self)
+        self.station.push_event(event)
+
+    @staticmethod
+    def get_name(i: Tuple):
+        data_1: bytes = i[2]
+        return (f'Pass' if data_1 == DBBit.BIT_1 else 'Fail') + f"-{i[3]}"
+
+
+class StatisticsTimeDetailProgram(StatisticsTimeBaseProgram):
+    def __init__(self, station, win, color):
+        super().__init__(station, win, color, "时段分析-详细分类")
+        self._conf("#abc88b")
+
+    def refresh(self):
+        super().refresh()
+        event = tk_event.CountThrowTimeEvent(self.station)
+        event.start(["CheckResult", "GarbageType", "Location"], self.get_name, self)
+        self.station.push_event(event)
+
+    @staticmethod
+    def get_name(i: Tuple):
+        data_1: bytes = i[2]
+        data_2: bytes = i[3]
+        return ((f'Pass' if data_1 == DBBit.BIT_1 else 'Fail') +
+                f'-{GarbageType.GarbageTypeStrList_ch[int(data_2.decode("utf-8"))]}' + f'-{i[4]}')
+
+
 all_program = [WelcomeProgram, CreateNormalUserProgram, CreateManagerUserProgram, CreateAutoNormalUserProgram,
                CreateGarbageProgram, DeleteUserProgram, DeleteUsersProgram, DeleteGarbageProgram,
                DeleteGarbageMoreProgram, DeleteAllGarbageProgram, SearchUserProgram, SearchUserAdvancedProgram,
                SearchGarbageProgram, SearchGarbageAdvancedProgram, SearchAdvancedProgram, UpdateUserScoreBase,
                UpdateUserReputationBase, UpdateGarbageTypeProgram, UpdateGarbageCheckResultProgram,
-               ExportGarbageProgram, ExportUserProgram, CreateUserFromCSVProgram, AboutProgram]
+               ExportGarbageProgram, ExportUserProgram, CreateUserFromCSVProgram, AboutProgram,
+               StatisticsTimeLocProgram, StatisticsTimeTypeProgram, StatisticsTimeTypeLocProgram,
+               StatisticsTimeCheckResultProgram, StatisticsTimeCheckResultAndTypeProgram,
+               StatisticsTimeCheckResultAndLocProgram, StatisticsTimeDetailProgram]

+ 9 - 0
tool/color.py

@@ -0,0 +1,9 @@
+import random
+
+
+def random_color():
+    color_lst = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
+    color = ""
+    for i in range(6):
+        color += random.choice(color_lst)
+    return "#" + color