背景

大批量的excel、txt、压缩包等文件,要进行实现文件管理。

核心需求:需要能够对每一次标注(或其他)任务,提供以下信息

  • 任务背景,描述大致情况,说明为啥要做这件事
  • 任务规则,说明标注规则,每个标注要素的具体含义,或者数据处理的依据
  • 任务问题,说明在当下的规则下,可能会遇到的问题,以及是否有对应措施。
  • 任务迭代,子任务可能存在时间周期跨度较久问题,直接以新任务建文件夹,注明依赖项任务
  • 任务输入与输出文件,对于标注来说,注明每个表字段的具体含义,语料的具体特征,如原始语料问题、某个字段大量为空,越具体越好。对于输出文件,注明处理逻辑。

现状:以语料为例,不同语料的标注规则各有不同,语料的格式不一(excel、doc、pdf、json、txt等),存储的硬件环境不一(本地、外网的不同服务器上、内网的不同服务器上、制品库等)

需求:算法需要某一份标注数据,需要经历如下步骤:回忆在哪=>查找手工记录的归档文件(如果有)=>登录服务器(包含切换网络)=>进入资源目录

上面的流程有一点麻烦,但并不是大问题,重要的不能保证找到的那一份数据文件,是历史上所有的数据文件,也有可能在另外服务器的某个角落,有着类似的数据文件。并且即使归档文件记录了全量的数据,但文件实际分布在不同的环境,取文件的时候也是个小麻烦。如果能全量提供需求的数据,并在一个地方统一取文件就好了。

文件夹与文件命名规范

四位序号_项目名作为每个阶段项目的文件夹名,每个子任务以 batch_两位序号_任务核心要求作为命名规范,如下:

image-20231129103952032

每个子文件夹内部文件命名及存放方式如下:

对于处理的中间文件,不限于excel,python脚本等,都放在 middleware文件夹中,多个阶段的中间件文件,以 01序号自增建文件夹。

对于每个任务需求,除了需求方提供的文件,中间产生的任务文件,都属于中间文件。

另外需求方输入文件放 input文件夹,可能需要备份,语料性质的原始文件,必须要备份。输出文件放在 output文件夹,必须要备份。中间文件放在 middleware文件夹,可能要备份。

每个 batch的根目录,会自动生成一份 readme.txt里面的内容,来源于在管理系统上填写的内容,具体字段库表 task_batch_list

image-20231129110609109

功能模块

技术选型

前台:

  • Vue3

后台:

  • Django

规划

一期

能展示服务器下的资源目录(第三方标注工具的目录不在一起),这里的优化是统一到一个目录下,搭个文件服务器,界面提供下载

业务上时每个task对应一批(多个文件夹或多个文件)数据文件,可以给每个task打标签,通过标签以及分类来达到归档查询的目的,标签要带上是否加密的标签,未加密数据文件支持在线预览

实际业务多以要素标注为主,集成标注的每个要素:时间、地点、公司名等

涉及整理收集的文件,提供收集背景、下载及对应的说明即可

每个task提供原始数据文件、结果文件、工作区如果是第三方标注工具,应提供对应的第三方位置:如brat提供data的资源目录、labelstudio提供前台登录账号及projectID等;

支持关键字搜索,搜索的区域是每个task的标签及分类、展示的时候默认以时间降序

见《django》篇

二期

需求:

  • 前台新增登录界面

  • 摒弃从服务器后台新建文件夹、上传文件;支持前台填报页面直接新建文件夹,支持前台直接上传文件

  • 文件展示

    • 将后台文件,读取路径后展示给前台
  • 文件备份

    • 现状分析:每个 batch下的多个文件夹下,存在一个或多个需要备份的文件。
    • 界面设计:
      • 是否要列出所有文件。 懒加载列出,避免一个文件夹内有几千个文本。前台备份操作是,支持选中多个文件/全选,然后指定备份路径(通常是每个 batch下新建个 reserved文件夹(为了命名的排序,刚好在最后)作为备份文件夹),然后后台拿到多个文件名,新建对应的 input等文件夹,把归档文件拷贝进去,附带上 readme,然后将多个文件夹压缩成单文件,文件名带上哈希
      • 支持数据接入。前台的备份界面。支持将选择的多个文件夹,备份至其他的服务器

其他

语料管理系统

1.支持文件夹分类,或嵌套分类,以便区分日单位标注任务下的:原始语料、结果语料、说明文档等文件夹

至少是三栏布局,点击日任务时,右侧显示日任务的描述、关联关务、标注需求背景等字段

导出时,支持勾选,支持不同日,不同任务等细粒度勾选

预览应设置文件上线,过大的文件只能下载

文件夹中小文件语料过多时,要按需加载

任务指派时语料的处理,

  • 原始语料需要放在后台指定路径(按需求分)

目前只做语料管理系统,但要考虑到后期对接语料标注系统

语料标注系统

  • 要素标注
  • 分类标注
  • 校验
  • 第三方

常见预料格式处理需求

pdf分页成pdf

pdf分页成图片

图片转pdf

支持多文件夹处理

常用工具xnConvert

语料管理平台开发

环境

  • 后台

    虚拟环境:env_python_tagData

    python包:

    1
    django

库表结构

登录模块

UI

  • 色彩规范

image-20231130174523408

废弃

以下内容为初期手动维护 json文件的思路,

文件备份自动化

设计出一套文件管理规则后,基于json文件对每个标注任务进行管理,及时记录各个信息。

并使用脚本进行备份

下面两个json文件,可以拆分成四个左右的表,暂时不做可视化操作维护。

文件格式规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
[
{
"id": "0006",
"project": "0006_苏粮合同单据_风控",
"file": [
{
"filename": "",
"file_origin": "",
"get_file_time": ""
},
{
"filename": "",
"file_origin": "",
"get_file_time": ""
}
],
"tag": [
{
"batch": "01",
"tag_filename": "",
"desc": "对苏粮合同中的各类pdf单据,进行切分,然后按类型汇总",
"start_time": "",
"end_time": "",
"file_type": "",
"file_number": "",
"tools": "",
"tools_path": "",
"category": "03_数据整理",
"external_data": [],
"sovled": "1"
},
{
"batch": "02",
"tag_filename": "",
"desc": "整理苏粮合同中的样例数据",
"start_time": "",
"end_time": "",
"file_type": "",
"file_number": "",
"tools": "",
"tools_path": "",
"category": "03_数据整理",
"external_data": [],
"sovled": "1"
}
]
},
{
"id": "0007",
"project": "0007_聊城审计档案_治理",
"file": [
{
"filename": "0007_2022审计档案.rar",
"file_origin": "知语群:聊城审计档案",
"get_file_time": "2023-05-18"
}
],
"tag": [
{
"batch": "01",
"tag_filename": "",
"desc": "聊城审计的档案扫描样例数据已上传群共享,解压密码:8321055。请三位评估下:(1)客户的扫描文件我们能否处理,需不需要对客户的扫描做约束要求(2)客户提供的excel统计表格是否满足要求,是否需要调整 (3)对照南京市局,分析下聊城审计档案的数据跟南京的有多大差异,我们落地聊城审计档案需不需要额外的处理工作量",
"start_time": "",
"end_time": "",
"file_type": "",
"file_number": "",
"tools": "",
"tools_path": "",
"category": "",
"external_data": [],
"sovled": "0"
}
]
},
{
"id": "0008",
"project": "0008_海门检察院",
"file": [
{
"filename": "0008_06海门检察院",
"file_origin": "74/home/rhino/zkq/2023/06海门检察院/检察院土地执行",
"get_file_time": "2023-05-19"
}
],
"tag": [
{
"batch": "01",
"tag_filename": "0008_06海门检察院",
"desc": "算法人员对文件夹中的行政决定处罚书pdf做了与处理后,给到txt,标注26号要求演示的8个要素",
"start_time": "2023-05-22",
"end_time": "2023-05-22",
"file_type": "txt",
"file_number": "238",
"tools": "labelstudio",
"tools_path": "http://10.45.150.74:8082/projects/76/data?tab=157",
"category": "01_数据标注_NER",
"external_data": [],
"sovled": "1"
}
]
}
]

每个task下的json

1
2
3
4
5
6
7
8
[
{
"batch": "01",
"source_file_path": "0008_海门检察院/batch_01_演示数据标注/tag_origin_v1/*.txt",
"tag_result_path": "0008_海门检察院/batch_01_演示数据标注/tag_result_v1/project-76-at-2023-05-22-16-00-f4e5a0fd.json",
"backup_name": "0008_海门检察院/batch_01_演示数据标注/0008_backup_batch_01.zip"
}
]

backup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import os
import json
import glob
import shutil
import asyncio
ods_path = './ods/readme.json'
dwd_path = './dwd/'
backup_path = './backup/'

async def merge_readme():
# ods目录下的readme.json
ods_readme_content = []
# 每个实际任务下的readme.json
paths = glob.glob(dwd_path + '/*/readme.json')

with open(ods_path, 'r', encoding='utf-8') as fr:
ods_readme_content = json.load(fr)
for task in ods_readme_content:
id = task['id']
for batch in task['tag']:
external_data = batch['external_data']
outer_batch_id = int(batch['batch'])
for path in paths:
if id in path:
with open(path, 'r', encoding='utf-8') as fr1:
task_content = json.load(fr1)
for task in task_content:
inner_batch_id = int(task['batch'])
if inner_batch_id == outer_batch_id:
external_data.append(task)
with open('readme_merge.json', 'w') as fw:
json.dump(ods_readme_content, fw, ensure_ascii=False)

async def backup():
# 等待文件合并完成
await merge_readme()

zip_path_list = []
rel_path = []
with open('./readme_merge.json', 'r', encoding='utf-8') as fr:
content = json.load(fr)
for task in content:
for item in task['tag']:
backup_name = item['external_data'][0]['backup_name']
if (backup_name != ''):
# 只带.zip后缀的文件名列表
zip_path_list.append(backup_name.split('/')[-1])
# 待备份的文件路径列表
rel_path.append(os.path.join('./dwd', backup_name))
print(zip_path_list, rel_path)
# 备份文件夹下的zip包列表
backup_file_list = os.listdir('/app/tag/backup')
# 两个index是同步对应的
for i in range(len(zip_path_list)):
# 检查是否已备份
if zip_path_list[i] in backup_file_list:
print('{0} 已备份过,跳过备份!'.format(zip_path_list[i]))
else:
print('正在备份{0}...'.format(zip_path_list[i]))
shutil.copy(rel_path[i], backup_path)
print('备份完成')

if __name__ == '__main__':
asyncio.run(backup())

必要的人工

平时只需要维护资源索引目录即可

定期如每月,上传至指定目录后,更新依赖的JSON配置文件(后期如果新增了后台管理),这个配置文件也可以复用,通过后台新增的数据,也同步到配置里