核酸抽检比对系统
学校导师给的需求,我自己设计实现,已投入学校使用。
背景就是学校疫情常态化后,每天需要抽取人员进行核酸,需要进行这部分人员的比对,之前只能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(包含外出或集中隔离人员,不需要参加抽检)”给去除掉。
方案草图:
系统名称:核酸抽检比对系统
说明:
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、打开软件
双击打开软件,然后进入到软件的主页面
2、选择文件
点击浏览即可选择文件,分别选择本轮应检名单,本轮请假名单和实际抽检结果(可多选),所有文件名称见参考文件
【重要】:实际抽检结果命名为“日期+**”的格式,如4月1号=>“4.1”
【重要】:应检名单日期格式也要按照参考文件的格式
3、比对漏检并导出
比对漏检:点击【比对漏检】按钮,进行漏检比对,显示结果
导出漏检:先点击【比对漏检】后,再点击【导出漏检】按钮,提示导出成功后,导出文件在软件同目录下
4、比对补检并导出
比对漏检:点击【比对补检】按钮,进行补检比对,显示结果
导出漏检:先点击【比对补检】后,再点击【导出补检】按钮,提示导出成功后,导出文件在软件同目录下
评论区