侧边栏壁纸
博主头像
这就是之谦博主等级

我们的征途是星辰大海

  • 累计撰写 182 篇文章
  • 累计创建 3 个标签
  • 累计收到 16 条评论
标签搜索

目 录CONTENT

文章目录

核酸抽检比对系统

这就是之谦
2022-06-03 / 0 评论 / 0 点赞 / 407 阅读 / 4,500 字
温馨提示:
本文最后更新于 2022-06-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

核酸抽检比对系统

学校导师给的需求,我自己设计实现,已投入学校使用。

背景就是学校疫情常态化后,每天需要抽取人员进行核酸,需要进行这部分人员的比对,之前只能excel函数手动比对,较为繁琐,容易出错。

源码和数据(数据做了脱敏处理)

链接:https://pan.baidu.com/s/1H84LNKgotF3L0zqdg4rIkQ
提取码:7doc

1、需求

项目背景:

高校每天都需要进行不少于20%的核酸检测抽测。基本上每4天或5天进行一轮抽测。具体抽测安排参见“00.研究生核酸检测20%台账(不含走读)4.1-4.4.xlsx”,每天抽测完毕,校医院会反馈实际参加抽测的名单,参见“4.1日核酸抽检.xlsx”。因为有部分同学会漏检,所以需要及时发现,然后通知漏检的同学补检。如果整个一轮(4天或5天),最后还要检查一下是否有无故不参加抽检的同学。

比对时需要把“核酸抽检请假名单.xlsx(包含外出或集中隔离人员,不需要参加抽检)”给去除掉。

方案草图:

系统名称:核酸抽检比对系统

image-20220603204157188

说明:

1)比对结果按“培养单位”排序

2)系统左上角标注“关于”菜单。参考下面的2个系统

3)找一个合适的.ico文件,制作出exe文件,使软件能够在没有python的电脑上运行。参见基金系统中的“生成exe.txt”

制作一个软件使用说明文件

2、代码实现

思路:

对核酸抽检结果进行比对,不涉及抽取过程(抽取过程由学校管理系统进行抽取)

比对过程,读取excel文件,根据身份证号为比对key进行比对,然后展示,多做一个导出excel按钮

实现很简单,直接上代码

"""
    Author: liuxuewei
    Date: 2022-4-16
    Desc: 核酸抽检系统
"""
import os
import re
from tkinter.filedialog import askopenfilename, askopenfilenames
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap import utility
import numpy as np
import pandas as pd
import tkinter as tk
import tkinter.messagebox


class FileSearchEngine(ttk.Frame):

    def __init__(self, master):
        super().__init__(master, padding=15)
        self.pack(fill=BOTH, expand=YES)

        # 展示的名字——缩写
        self.path_var_all = ttk.StringVar()  # 应检人员
        self.path_var_leave = ttk.StringVar()  # 请假人员
        self.path_var_day = ttk.StringVar()  # 每日实际抽检结果

        # 完整路径
        self.path_all = ttk.StringVar()  # 应检人员
        self.path_leave = ttk.StringVar()  # 请假人员
        self.path_day_list = []  # 每日实际抽检结果

        self.day_time_list = []  # 应检名单的文件名的日期列表

        self.out_lou = []  # 漏检名单
        self.out_bu = []  # 补检名单

        self.create_title()
        self.create_about()

        self.option_lf = ttk.Labelframe(self, text="选择文件", padding=15)  # text="选择文件",
        self.option_lf.pack(fill=X, expand=YES, anchor=N)

        self.create_path_row_all()
        self.create_path_row_day()
        self.create_path_row_leave()

        self.create_results_view()

    def create_about(self):
        """创建关于about内容"""
        menu_row = ttk.Frame(self)
        menu = ttk.Menu(menu_row)
        menu.add_command(label='作者: 刘学伟')
        menu.add_command(label='指导老师: 王绍卿')
        menu.add_command(label='学院: 山东理工大学计算机学院')
        menu.add_command(label='班级: 软件1805')
        menu.add_command(label=' QQ: 1211304872')

        mb = ttk.Menubutton(
            master=self,
            text="关于",
            bootstyle=LIGHT,
            menu=menu,
        )
        mb.pack(fill=X, expand=YES, pady=12, padx=(15, 0))
        menu_row.pack(side=LEFT, padx=(15, 0))

    def create_title(self):
        # photo = ttk.PhotoImage("001.png")
        theLabel = ttk.Label(self,
                             text="山东理工大学核酸抽检比对系统",  # 内容
                             justify=LEFT,  # 对齐方式
                             # image=photo,  # 加入图片
                             compound=CENTER,  # 关键:设置为背景图片
                             font=("华文行楷", 20),  # 字体和字号
                             # fg="yellow", # 前景色
                             )
        theLabel.pack(padx=(15, 0))

    def create_path_row_all(self):
        """创建本轮应检名单Label"""
        path_row = ttk.Frame(self.option_lf)
        path_row.pack(fill=X, expand=YES, pady=12)
        path_lbl = ttk.Label(path_row, text="本轮应检名单", width=10)
        path_lbl.pack(side=LEFT, padx=(15, 0))
        path_ent = ttk.Entry(path_row, textvariable=self.path_var_all)
        path_ent.pack(side=LEFT, fill=X, expand=YES, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="浏览",
            command=self.on_browse_all,
            # bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)

    def create_path_row_leave(self):
        """创建本轮请假名单Label"""
        path_row = ttk.Frame(self.option_lf)
        path_row.pack(fill=X, expand=YES, pady=12)
        path_lbl = ttk.Label(path_row, text="本轮请假名单", width=10)
        path_lbl.pack(side=LEFT, padx=(15, 0))
        path_ent = ttk.Entry(path_row, textvariable=self.path_var_leave)
        path_ent.pack(side=LEFT, fill=X, expand=YES, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="浏览",
            command=self.on_browse_leave,
            # bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="导出漏检",
            command=self.out_lou_excel,
            bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="导出补检",
            command=self.out_bu_excel,
            bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)

    def create_path_row_day(self):
        """创建实际检测结果Label"""
        path_row = ttk.Frame(self.option_lf)
        path_row.pack(fill=X, expand=YES, pady=12)
        path_lbl = ttk.Label(path_row, text="实际抽检结果", width=10)
        path_lbl.pack(side=LEFT, padx=(15, 0))
        # path_ent = ttk.Entry(path_row, textvariable=self.path_var_day)
        self.path_ent = ttk.Text(path_row, width=10, height=5)
        self.path_ent.insert(END, "")
        self.path_ent.pack(side=LEFT, anchor=NW, fill=X, expand=YES, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="浏览",
            command=self.on_browse_day,
            # bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="比对漏检",
            command=self.on_search_not_test,
            # bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)
        browse_btn = ttk.Button(
            master=path_row,
            text="比对补检",
            command=self.on_search_patch,
            # bootstyle=OUTLINE,
            width=8
        )
        browse_btn.pack(side=LEFT, padx=5)

    def create_results_view(self):
        """创建结果视图"""
        ybar = ttk.Scrollbar(self, orient='vertical')  # 竖直滚动条

        # tree = Treeview(window, show='headings', columns=cols, yscrollcommand=ybar.set)
        self.resultview = ttk.Treeview(
            master=self,
            bootstyle=INFO,
            columns=[0, 1, 2, 3, 4, 5, 6, 7],
            show=HEADINGS,
            yscrollcommand=ybar.set
        )
        ybar['command'] = self.resultview.yview
        ybar.pack(side='right', fill='y')

        self.resultview.pack(fill=BOTH, expand=YES, pady=10)

        self.resultview.heading(0, text='姓名', anchor=W)
        self.resultview.heading(1, text='学号', anchor=W)
        self.resultview.heading(2, text='班级', anchor=W)
        self.resultview.heading(3, text='身份证号', anchor=W)
        self.resultview.heading(4, text='联系电话', anchor=W)
        self.resultview.heading(5, text='应检日期', anchor=W)
        self.resultview.heading(6, text='培养单位', anchor=W)
        self.resultview.heading(7, text='比对结果', anchor=W)
        self.resultview.column(
            column=0,
            anchor=W,
            width=utility.scale_size(self, 50),
            stretch=False
        )
        self.resultview.column(
            column=1,
            anchor=W,
            width=utility.scale_size(self, 100),
            stretch=False
        )
        self.resultview.column(
            column=2,
            anchor=W,
            width=utility.scale_size(self, 50),
            stretch=False
        )
        self.resultview.column(
            column=3,
            anchor=W,
            width=utility.scale_size(self, 100),
            stretch=False
        )
        self.resultview.column(
            column=4,
            anchor=W,
            width=utility.scale_size(self, 100)
        )
        self.resultview.column(
            column=5,
            anchor=W,
            width=utility.scale_size(self, 80)
        )
        self.resultview.column(
            column=6,
            anchor=W,
            width=utility.scale_size(self, 80)
        )
        self.resultview.column(
            column=7,
            anchor=W,
            width=utility.scale_size(self, 80)
        )

    # 选择单个文件,返回文件完整路径
    def on_browse_all(self):
        """Callback for directory browse"""
        path = askopenfilename(title="Browse directory")
        if path:
            self.path_all.set(path)
            self.path_var_all.set(os.path.basename(path))
        # 重新选择应检名单后,清空原有查询内容
        x = self.resultview.get_children()
        for item in x:
            self.resultview.delete(item)

    # 选择单个文件,返回文件完整路径
    def on_browse_leave(self):
        """Callback for directory browse"""
        path = askopenfilename(title="Browse directory")
        if path:
            self.path_leave.set(path)
            self.path_var_leave.set(os.path.basename(path))

    # 可选择多个文件,返回文件完整路径
    def on_browse_day(self):
        """Callback for directory browse"""
        path = askopenfilenames(title="Browse directory")
        if path:
            self.path_day_list = list(path)
            list_var = ""
            self.day_time_list = []
            for i in list(path):
                list_var = list_var + os.path.basename(i) + "\n"
                self.day_time_list.append(re.findall(r"^(\d+\.\d+)?", os.path.basename(i))[0])
            self.path_var_day.set(list_var)
            self.path_ent.delete('1.0', END)
            self.path_ent.insert(END, self.path_var_day.get())

    def on_search_not_test(self):
        """Search for a term based on the search type"""
        path_all = self.path_all.get()
        path_leave = self.path_leave.get()
        path_day_list = self.path_day_list

        # print(path_all)
        # print(path_leave)
        # print(path_day_list)

        list = self.find_not_test_list(path_all=path_all, path_leave=path_leave, path_day_list=path_day_list)
        # print(list)
        self.insert_row(list=list, type="漏检")

    def on_search_patch(self):
        path_all = self.path_all.get()
        path_leave = self.path_leave.get()
        path_day_list = self.path_day_list

        # print(path_all)
        # print(path_leave)
        # print(path_day_list)

        list = self.find_patch_list(path_all=path_all, path_leave=path_leave, path_day_list=path_day_list)
        # print(list)
        self.insert_row(list=list, type="补检")

    def insert_row(self, list, type):
        if not list:
            return
        x = self.resultview.get_children()
        for item in x:
            self.resultview.delete(item)
        if type == '漏检':
            for i in list:
                _name = i[1]
                _xuehao = i[2]
                _banji = i[3]
                _shenfenzheng = i[4]
                _phone = i[5]
                _time = i[6]
                _danwei = i[7]
                _duibi = type

                self.resultview.insert(
                    parent='',
                    index=END,
                    values=(_name, _xuehao, _banji, _shenfenzheng, _phone, _time, _danwei, _duibi)
                )
        else:
            for i in list:
                _name = i[2]
                _xuehao = ""
                _banji = ""
                _shenfenzheng = i[4]
                _phone = i[5]
                _time = i[14]
                _danwei = i[7]
                _duibi = type

                self.resultview.insert(
                    parent='',
                    index=END,
                    values=(_name, _xuehao, _banji, _shenfenzheng, _phone, _time, _danwei, _duibi)
                )

    # 漏检名单
    def find_not_test_list(self, path_all, path_leave, path_day_list):
        self.out_bu = []
        if not path_all:  # 请假名单可以为空
            tk.messagebox.askokcancel(title='提示', message='请选择应检名单')
            return
        if not path_day_list:
            tk.messagebox.askokcancel(title='提示', message='请选择实际抽检名单')
            return
        # 请假名单为空,需要给定一个空list,否则read_excel报错
        if not path_leave:
            leave_list = []
        else:
            leave_list = pd.read_excel(path_leave, header=0, skiprows=1, dtype=str)['身份证号码'].tolist()  # 读取请假名单
            for i in range(len(leave_list)):  # 身份证号统一小写x转为大写X,防止判断为非同一人
                leave_list[i] = leave_list[i].upper()
            # print(leave_list)

        all_list = pd.read_excel(path_all, usecols=[0, 1, 2, 3, 4, 5, 6, 7], header=0, skiprows=1, dtype=str)[
            '身份证号码'].tolist()  # 读取应检名单
        for i in range(len(all_list)):  # 身份证号统一小写x转为大写X,防止判断为非同一人
            all_list[i] = all_list[i].upper()
        # print(all_list)
        day_list = []
        for path in path_day_list:
            result = pd.read_excel(path, header=0, skiprows=0, dtype=str)  # 读取实际检测名单
            day_list = day_list + result['证件号码'].tolist()
        for i in range(len(day_list)):  # 身份证号统一小写x转为大写X,防止判断为非同一人
            day_list[i] = day_list[i].upper()
        # print(day_list)

        # 减去请假的
        should_set = set(all_list).difference(set(leave_list))
        # 漏测(总-请假-已测)
        not_test = list(should_set.difference(set(day_list)))
        # return not_test
        result = pd.read_excel(path_all,usecols=[0, 1, 2, 3, 4, 5, 6, 7], header=0, skiprows=1, dtype=str)
        should_test_time = list(result)[6]  # 总表的应检时间列名
        column_uniques = result[should_test_time].unique()  # 这是应检名单的日期list
        if len(set(self.day_time_list).difference(set(column_uniques))) > 0:
            tk.messagebox.askokcancel(title='提示', message='实际抽检日期与应检名单日期不匹配')
            return
        # print(column_uniques.tolist())
        # print(self.day_time_list)
        result_df_list = []
        for i in self.day_time_list:
            result_df_list.append(result.loc[result[should_test_time] == i])
        result = result_df_list[0]
        if len(result_df_list) > 0:
            for df in result_df_list[1:]:
                result = pd.concat([result, df], ignore_index=True)
        # print(should_test_time)
        # print(self.day_time.get())
        # print(type(self.day_time))
        # result = result.loc[result[should_test_time] == "4.1"]

        # print(not_test)
        result_list = []
        for i in not_test:
            list_have_null = np.array(result.loc[result['身份证号码'] == i]).tolist()
            if len(list_have_null) > 0:
                result_list.append(list_have_null[0])
        new_list_1 = sorted(result_list, key=(lambda x: (x[7], x[6])), reverse=False)  # 先按培养机构排序,然后按时间排序
        # new_list_1 = sorted(new_list_1, key=(lambda x: x[6]), reverse=False)  # 按时间排序
        if len(new_list_1) == 0:
            tk.messagebox.askokcancel(title='提示', message='没有漏检人员')

        self.out_lou = new_list_1
        return new_list_1

    # 补检名单
    def find_patch_list(self, path_all, path_leave, path_day_list):
        self.out_lou = []
        if not path_all:  # 请假名单可以为空
            tk.messagebox.askokcancel(title='提示', message='请选择应检名单')
            return
        if not path_day_list:
            tk.messagebox.askokcancel(title='提示', message='请选择实际抽检名单')
            return
        # 请假名单为空,需要给定一个空list,否则read_excel报错
        if not path_leave:
            leave_list = []
        else:
            leave_list = pd.read_excel(path_leave, header=0, skiprows=1, dtype=str)['身份证号码'].tolist()
            for i in range(len(leave_list)):  # 身份证号统一小写x转为大写X,防止判断为非同一人
                leave_list[i] = leave_list[i].upper()

        all_list = pd.read_excel(path_all, header=0, skiprows=1, dtype=str)['身份证号码'].tolist()
        for i in range(len(all_list)):  # 身份证号统一小写x转为大写X,防止判断为非同一人
            all_list[i] = all_list[i].upper()
        day_list = []
        for path in path_day_list:
            result = pd.read_excel(path, header=0, skiprows=0, dtype=str)
            day_list = day_list + result['证件号码'].tolist()
        for i in range(len(day_list)):  # 身份证号统一小写x转为大写X,防止判断为非同一人
            day_list[i] = day_list[i].upper()
        # print(day_list)

        # 减去请假的
        should_set = set(all_list).difference(set(leave_list))
        # 补测(已测-(总-请假))
        patch_test = list(set(day_list).difference(should_set))
        # return not_test
        result_df = pd.read_excel(path_all, header=0, skiprows=1, dtype=str)
        should_test_time = list(result_df)[6]  # 总表的应检时间列名
        column_uniques = result_df[should_test_time].unique()  # 这是应检名单的日期list
        if len(set(self.day_time_list).difference(set(column_uniques))) > 0:
            tk.messagebox.askokcancel(title='提示', message='实际抽检日期与应检名单日期不匹配')
            return

        result_list = []
        for path in path_day_list:
            result = pd.read_excel(path, header=0, skiprows=0, converters={'证件号码': str})
            # print(result)
            for i in patch_test:
                # 姓名,学号,班级,身份证号,联系电话,应抽检时间,培养单位,对比结果

                if not np.array(result.loc[result['证件号码'] == i]).tolist():
                    continue
                else:
                    # print(np.array(result.loc[result['证件号码'] == i]).tolist())
                    result_list.append(np.array(result.loc[result['证件号码'] == i]).tolist()[0])

        new_list_1 = sorted(result_list, key=(lambda x: (x[7], x[14])), reverse=False)  # 先按培养机构排序,然后按时间排序

        self.out_bu = new_list_1
        return new_list_1

    def out_lou_excel(self):
        if not self.out_lou:
            tk.messagebox.askokcancel(title='提示', message='请先进行比对漏检')
            return
        print(self.out_lou[0])

        df = pd.DataFrame(self.out_lou, columns=['编号', '姓名', '学号', '班级', '身份证号', '联系电话', '应检日期', '培养单位'])
        df.drop('编号', axis=1, inplace=True)  # axis参数默认为0
        df['比对结果'] = '漏检'
        name = ""  # 文件名
        for day in self.day_time_list:
            name = name + day + "日"
        name = name + "漏检名单.xlsx"
        try:
            df.to_excel(name, index=True)
        finally:
            tk.messagebox.askokcancel(title='提示', message='导出漏检名单: ' + name + ' 成功!')

    def out_bu_excel(self):
        if not self.out_bu:
            tk.messagebox.askokcancel(title='提示', message='请先进行比对补检')
            return
        # print(self.out_bu[0])

        columns = ['调查名称',
                   '人员类别',
                   '姓名',
                   '证件类型',
                   '证件号码',
                   '联系电话',
                   '职业',
                   '单位',
                   '现住地区划',
                   '现住地街道',
                   '现住地详细地址',
                   '始发地',
                   '途径地',
                   '条形码',
                   '采集时间',
                   '采集类型',
                   '采集机构',
                   '采集机构编码',
                   '检测项目',
                   '检测结果',
                   '检测机构',
                   '检测人',
                   '检测时间',
                   '备注', ]
        drop_columns = ['调查名称',
                        '人员类别',
                        # '姓名',
                        '证件类型',
                        # '证件号码',
                        # '联系电话',
                        '职业',
                        # '单位',
                        '现住地区划',
                        '现住地街道',
                        '现住地详细地址',
                        '始发地',
                        '途径地',
                        '条形码',
                        # '采集时间',
                        '采集类型',
                        '采集机构',
                        '采集机构编码',
                        '检测项目',
                        '检测结果',
                        '检测机构',
                        '检测人',
                        '检测时间',
                        '备注', ]

        df = pd.DataFrame(self.out_bu, columns=columns)
        df.drop(columns=drop_columns, axis=1, inplace=True)  # axis参数默认为0,参数为1删除列
        df['比对结果'] = '补检'
        name = ""  # 文件名
        for day in self.day_time_list:
            name = name + day + "日"
        name = name + "补检名单.xlsx"
        try:
            df.to_excel(name, index=True)
        finally:
            tk.messagebox.askokcancel(title='提示', message='导出补检名单: ' + name + ' 成功!')


if __name__ == '__main__':
    app = ttk.Window("核酸抽检比对系统", "litera")
    # app = tk.Tk()
    app.title("核酸抽检比对系统")
    FileSearchEngine(app)
    app.mainloop()

3、使用说明

核酸抽检比对系统使用说明

作者:刘学伟(软件1805)

指导老师:王绍卿

QQ:1211304872

1、打开软件

双击打开软件,然后进入到软件的主页面

image-20220417232010550

2、选择文件

点击浏览即可选择文件,分别选择本轮应检名单,本轮请假名单和实际抽检结果(可多选),所有文件名称见参考文件

【重要】:实际抽检结果命名为“日期+**”的格式,如4月1号=>“4.1”

【重要】:应检名单日期格式也要按照参考文件的格式

image-20220417232116362

3、比对漏检并导出

比对漏检:点击【比对漏检】按钮,进行漏检比对,显示结果

导出漏检:先点击【比对漏检】后,再点击【导出漏检】按钮,提示导出成功后,导出文件在软件同目录下

image-20220417232533165

4、比对补检并导出

比对漏检:点击【比对补检】按钮,进行补检比对,显示结果

导出漏检:先点击【比对补检】后,再点击【导出补检】按钮,提示导出成功后,导出文件在软件同目录下

image-20220417232604948

0

评论区