项目是23年的一个练手小项目,因为环节很完好,自始至终思考了许多细节,完结后很有成就感。趁春节整理出来,尽量规范化的把整个流程整理出来,为往后开发培育习惯,希望各位多指教。

项目布景

打卡机生成固定格局的数据,每个月会保存为一个Excel文件,记载了职工打卡的时刻点。数据格局如下表:

考勤序号 名字 工号 所属部门 5-4 四 5-5 五 5-6 六 5-8 一
1 张三 42 A 07:40
11:17
17:10
07:44
11:19
17:44
07:14
11:19
07:41
14:51
17:09
2 李四 53 B 07:37
08:48
11:12
14:59
16:55
07:48
11:08
14:47
16:45
07:33
11:18
14:52
16:45
08:03
11:14
15:02
17:01

需求方的打卡规矩如下:

打卡需在指定的四个时刻段内完结,同一时段打卡屡次也只记一次
	1. 06:50 ~ 09:10 
	2. 11:10 ~ 12:30
	3. 13:40 ~ 15:10
	4. 16:29 ~ 18:01

要求规划程序,核算职工每月打卡次数。


需求剖析

功用性需求

  1. 数据读写
    • Excel格局化读取,提取有用信息
    • 时刻信息的存储格局及比较办法
  2. 打卡次数核算
    • 每人每日数据核算
    • 每人整月数据求和

非功用性需求

  1. 可用性需求
    • 运行环境为Windows 7或Windows 10体系
    • 面向无代码开发经验的运用者
  2. 操作需求
    • 打卡核算时刻段可修正
    • 时刻段个数可修正

规划思路

言语、环境挑选

最开端需求方计划自己配环境跑代码,鉴于功用需求比较简略,没有性能要求,挑选Python进行开发。后来对方提出Python环境装备有困难,不要求源码,所以终究用pyinstaller打包成exe交给。

相关库

  • pandas:用于Excel读写、数据格局化存储,杀鸡用牛刀,但真香
  • json:用于读取参数装备文件

参数读取

装备文件内容

clock_in_lists :打卡规矩,要保证时刻段成对出现,不同时刻段不重合。每个时刻段的开端在左,结束在右,从小到大以此排列。
num_skip_cols:跳过表格前面的列数。
in_file_nameout_file_name:输入数据文件名和输出数据文件名。

{
  "clock_in_lists": [
    "06:50",
    "09:10",
    "11:10",
    "12:30",
    "13:40",
    "15:10",
    "16:30",
    "18:00"
  ],
  "num_skip_cols": 4,
  "in_file_name": "data.xlsx",
  "out_file_name": "out.xlsx"
}

读取代码

import json as js
import pandas as pd 
# 装备文件加载
f = open('config.json', 'r')
content = f.read()
root = js.loads(content)
CLOCK_IN_LISTS = root['clock_in_lists']
NUM_SKIP_COLS = root['num_skip_cols']
# 输入/输出文件名可以改,但不能包括中文
IN_FILE_NAME = root['in_file_name']
OUT_FILE_NAME = root['out_file_name']

数据存储

文件读取一行代码即可处理,需要思考的是如何处理每个Excel格子中的内容。原始数据输入后是一个字符串,时刻段之间用n间隔,即如下格局

07:40n11:56n15:06n16:38

考虑到后面有比较大小的要求,这儿首要运用n将时刻段相互隔开,再把时刻段转换为分钟数,用于后续比较,代码如下

# 时刻段分割
item = raw_data.loc[i][j]
if pd.isna(item):
    continue
one_day_str = item.split('n')
# 时刻字符串 --> 分钟数
def time2nmin(time_str):
  time_array = time_str.split(':')
  result = int(time_array[0])*60 int(time_array[1])
  return result

打卡规矩处理△

打卡规矩可以简略粗犷地用if-else进行处理,但我猜写出来会很难看。我看到需求中说同一时段屡次打卡也只算一次时,第一时刻想到的是桶排序的办法。四个时刻段相当于四个桶,每输入一个时刻就判别是不是在四个桶里,终究核算不为空的桶有几个。 而第二个问题是如何简化判别规矩,不用if-else,而是将时刻的单调性和有序性充分运用。首要对四个时刻段的起止点编号,线段表述该段时刻内为合法打卡时刻段,其他空白位置如B、D、F均为不合法打卡时刻段。

【开发】打卡核算项目

合法段 A C E G
左端点编号 0 2 4 6
右端点编号 1 3 5 7
段编号 0 1 2 3
非法段 B D F
左端点编号 1 3 5
右端点编号 2 4 6

观察上述表格,可以总结出两条规律 1. 合法段端点编号满意“左偶右奇”; 2. 合法段编号=左/右端点编号 // 2

至此,咱们可以规划如下判别流程:首要承认一个输入时刻地点的时刻段,然后判别该时刻段的左右端点为奇数或偶数,比方左端点是奇数,则非法;左端点是偶数,则判别该端点的段编号,给相应的“桶”增加记载。 编写代码如下

# 打卡判别
# input:
#  clock_in_nmins: 打卡规矩
#  tic_time: 刷卡时刻
#  tags: 当日打卡标签
def check_tic(clock_in_nmins, tic_time, tags):
  for i in range(0, len(clock_in_nmins)):
    # 遍历打卡时刻点
    if tic_time > clock_in_nmins[i]:
      continue
    else:
      if i % 2 == 1:
        tags[i // 2] = 1
      break

调试进程

鸿沟检验

规划进程中的思路很清晰,可是实现进程中还要检验鸿沟条件,比方小于最小的时刻和大于最大时刻的情况能否处理?卡点06:50的打卡,是否核算在内?第一个问题通过验证发现当时逻辑即可处理,第二个问题则需要加以讨论。当时的代码逻辑是先遍历找到右端点,然后检验其他内容。找右端点时,在输入时刻≤某时刻点时才停下,这种逻辑本质上是把数轴分割成了一系列左开右闭的区间(解释思路源于代码随想录的一期视频)。可是需求方的打卡分割方式其实是四个合法段是闭区间,其他非法段是开区间,可以凭借下图了解。

【开发】打卡核算项目

处理的方案便是在加载打卡规矩时,将左端点的时刻-1,这样尽管仍是左开右闭的分割,但实现了合法段构成闭区间的要求。

clock_in_nmins = []
clock_in_list_len = len(clock_in_lists)
# 此处的减1与打卡核算算法鸿沟条件有关
for i in range(0,clock_in_list_len):
	if i % 2 == 0:  
		clock_in_nmins.append(time2nmin(clock_in_lists[i]) - 1)
	else:
		clock_in_nmins.append(time2nmin(clock_in_lists[i]))

其他问题

调试中还遇到一个由于言语特性导致的问题,第一个版本的check_tic()函数中的continuei = i 1。这个写法假如见效,逻辑上其实也存在问题,可是这个写法在Python中实践并无作用。对 i值进行的修正只能在该轮循环中有用,下一循环i会被for句子指定的下标变化规矩重置。其实原因从语义上也好了解,for i in range(0,n)便是让i遍历去取range中的值的,根本不存在 1的逻辑,也无从保留这种修正。


终究作用

运用pyinstaller打包成exe,和输入文件、装备文件放在同一文件夹下,双击运行即可。终究输出作用如图

【开发】打卡核算项目

交给内容也非常简略,exe和文档

.
|-- config.json
|-- data.xlsx
|-- exe运用说明.docx
|-- record.exe
`-- out.xlsx

复盘&改善

开发进程中应该在最开端对中心功用构建单元测试,可是仍是print调试法,往后有待改善。