diff --git a/flask_app/main/投标人须知正文提取指定内容.py b/flask_app/main/投标人须知正文提取指定内容.py index 24f6ecc..1697038 100644 --- a/flask_app/main/投标人须知正文提取指定内容.py +++ b/flask_app/main/投标人须知正文提取指定内容.py @@ -1,5 +1,6 @@ import json import re +from functools import cmp_to_key # 对于每个target_value元素,如果有完美匹配json_data中的键,那就加入这个完美匹配的键名,否则,把全部模糊匹配到的键名都加入 @@ -71,6 +72,31 @@ def extract_json(data, target_values): results[subkey] = data[subkey] return results +def compare_headings(a, b): + a_nums = [int(num) for num in a[0].rstrip('.').split('.') if num.isdigit()] + b_nums = [int(num) for num in b[0].rstrip('.').split('.') if num.isdigit()] + return (a_nums > b_nums) - (a_nums < b_nums) +def preprocess_data(data): + """ + 预处理数据,自动添加缺失的父层级键,并按数字顺序排序。 + """ + keys_to_add = set() + for key in data.keys(): + parts = key.split('.') + if len(parts) > 1: + parent_key = parts[0] + '.' + if parent_key not in data: + keys_to_add.add(parent_key) + + # 添加缺失的父层级键 + for parent_key in keys_to_add: + data[parent_key] = parent_key.rstrip('.') + + # 对键进行排序 + sorted_data = dict(sorted(data.items(), key=cmp_to_key(compare_headings))) + + return sorted_data + def sort_clean_data_keys(data): # 预处理:删除键名中的空格 def preprocess_key(key): @@ -96,7 +122,7 @@ def sort_clean_data_keys(data): def transform_json(data): result = {} temp = {0: result} # 初始化根字典 - + data = preprocess_data(data) # 首先,创建一个临时字典用于检查是否存在三级标题 has_subkey = {} for key in data.keys(): diff --git a/flask_app/testdir/test1.py b/flask_app/testdir/test1.py index 8c9d2f4..139d38a 100644 --- a/flask_app/testdir/test1.py +++ b/flask_app/testdir/test1.py @@ -1,101 +1,54 @@ -import json +# -*- encoding:utf-8 -*- import re -from functools import cmp_to_key - - -def compare_headings(a, b): - a_nums = [int(num) for num in a[0].rstrip('.').split('.') if num.isdigit()] - b_nums = [int(num) for num in b[0].rstrip('.').split('.') if num.isdigit()] - return (a_nums > b_nums) - (a_nums < b_nums) - +import json def preprocess_data(data): - """ - 预处理数据,自动添加缺失的父层级键,并按数字顺序排序。 - """ - keys_to_add = set() - for key in data.keys(): - parts = key.split('.') - if len(parts) > 1: - parent_key = parts[0] + '.' - if parent_key not in data: - keys_to_add.add(parent_key) - - # 添加缺失的父层级键 - for parent_key in keys_to_add: - data[parent_key] = parent_key.rstrip('.') - - # 对键进行排序 - sorted_data = dict(sorted(data.items(), key=cmp_to_key(compare_headings))) - - return sorted_data + # 根据需要预处理数据,这里假设原始数据已经是一个字典 + return data def transform_json(data): result = {} temp = {0: result} # 初始化根字典 - data=preprocess_data(data) - print(json.dumps(data,ensure_ascii=False,indent=4)) - # 首先,创建一个临时字典用于检查是否存在三级标题 + data = preprocess_data(data) + + # 首先, 创建一个临时字典用于检查是否存在三级标题 has_subkey = {} for key in data.keys(): parts = key.split('.') if len(parts) > 2 and parts[1]: - parent_key = parts[0] + '.' + parts[1] + parent_key = '.'.join(parts[:2]) has_subkey[parent_key] = True for key, value in data.items(): match = re.match(r'(\d+)(?:\.(\d+))?(?:\.(\d+))?', key) if match: levels = [int(l) for l in match.groups() if l is not None] - if (len(levels) - 1) in temp: - parent = temp[len(levels) - 1] - else: - print(f"No parent found at level {len(levels) - 1} for key '{key}'. Check the data structure.") - continue + current_level = len(levels) + parent = temp.get(current_level - 1, result) - if len(levels) == 1: # 一级标题 - # 新增逻辑:判断值中是否有 ':' 或 ':',并进行拆分 - # 优先按 '\n' 拆分 - if '\n' in value: - new_key, *new_value = value.split('\n', 1) - new_key = new_key.strip() - new_value = new_value[0].strip() if new_value else "" - # 如果没有 '\n',再检查 ':' 或 ':',并进行拆分 - elif ':' in value or ':' in value: - delimiter = ':' if ':' in value else ':' - new_key, new_value = value.split(delimiter, 1) - new_key = new_key.strip() - new_value = new_value.strip() + # 获取标题内容 + if current_level == 1: + # 一级标题 + parent[value] = {} + temp[current_level] = parent[value] + elif current_level == 2: + # 二级标题 + if has_subkey.get(key, False): + parent[value] = {} else: - new_key = value.strip() - new_value = "" - - parent[new_key] = {} - if new_value: - parent[new_key][new_key] = new_value # 使用 new_key 作为键名,而不是固定的 "content" - temp[len(levels)] = parent[new_key] - elif len(levels) == 2: # 二级标题 - new_key, *new_value = value.split('\n', 1) - new_key = new_key.strip() - new_value = new_value[0].strip() if new_value else "" - - if f"{levels[0]}.{levels[1]}" in has_subkey: - parent[new_key] = [new_value] if new_value else [] - else: - parent[new_key] = new_value - - temp[len(levels)] = parent[new_key] - else: # 三级标题 + parent[value] = value # 如果没有子键,直接赋值内容 + temp[current_level] = parent[value] + elif current_level == 3: + # 三级标题 if isinstance(parent, dict): - parent_key = list(parent.keys())[-1] - if isinstance(parent[parent_key], list): - parent[parent_key].append(value) - elif parent[parent_key]: - parent[parent_key] = [parent[parent_key], value] - else: - parent[parent_key] = [value] + # 确保父级是一个字典 + parent[value] = value elif isinstance(parent, list): parent.append(value) + temp[current_level] = value + else: + # 处理无法匹配的键 + print(f"无法匹配的键: {key}") def remove_single_item_lists(node): if isinstance(node, dict): @@ -109,34 +62,15 @@ def transform_json(data): # 示例数据 data = { - "10.1": "投标人提交的投标文件以及投标人与招标采购单位就有关投标的所有来往函电均应使用中文书写。投标人提交的支持资料和己印刷的文献可以用另一种语言,但相应内容应附有中文翻译本,在解释投标文件的修改内容时以中文翻译本为准。对中文翻译有异议的,以权威机构的译本为准。11投标文件的构成", - "11.1": "投标人编写的投标文件应包括价格文件、技术文件、商务文件,价格部分必须独立装订,编排顺序参见投标文件格式。", - "11.2": "投标文件的构成应符合法律法规及招标文件的要求。12投标文件的编写", - "12.1": "投标人应完整、真实、准确地填写招标文件中提供的投标函、投标报价一览表、投标明细报价表(如适用)以及招标文件中规定的其它所有内容。", - "12.2": "投标人对招标文件中多个包组进行投标的,其投标文件的编制可按每个包组的要求分别装订和封装。投标人应当对投标文件进行装订,对未经装订的投标文件可能发生的文件散落或缺损,由此造成的后果和责任由投标人承担。", - "12.3": "投标人必须对投标文件所提供的全部资料的真实性承担法律责任,并无条件接受招标采购单位及政府采购监督管理部门等对其中任何资料进行核实的要求。", - "12.4": "如果因为投标人的投标文件只填写和提供了本招标文件要求的部分内容和附件,或没有提供招标文件中所要求的全部资料及数据,由此造成的后果和责任由投标人承担。13投标报价", - # 可能缺失的父层级键 - # "10.": "投标文件相关内容", # 示例父层级,实际可能缺失 - # "11.": "投标文件的构成", - # "12.": "投标文件的编写", - # "13.": "投标报价" + "20.": "投标文件的式样和签署", + "20.1": "投标文件的式样:投标人应准备一份投标文件正本、电子文件和投标须知前附表中规定数目的副本,投标文件的副本可采用正本的复印件。每套投标文件须清楚地标明“正本”或“副本”。若副本与正本不符,以正本为准,装订如下:序号投标文件名称装订备注1唱标信封/投标报价一览表(加盖公章)、《投标保证金汇入情况说明》(加盖公章)、投标保证金缴付凭证复印件(加盖公章)2电子文件含价格文件、商务技术文件3价格文件独立装订成册含正、副本4商务技术文件商务文件和技术文件可一起独立装订成册,也可含正、副本以分开独立装订成册", + "20.2": "投标文件的装订要求:投标报价表部分必须单独装订,若将投标报价表与商务、技术部分等内容合为装订或在商务、技术部分等内容出现投标报价,则作无效投标处理。", + "20.3": "电子文件用MSWORD/EXCEL简体中文版制作,内容包括:由投标人自行制作的与正本文件一致的所有文件。电子文件由CD-R光盘或优盘储存,放在唱标信封内。", + "20.4": "投标文件的签署:", + "20.4.1": "投标文件的正本需打印或用不褪色墨水书写,副本可以复印。授权代表须将以书面形式出具的《法定代表人授权委托书》附在投标文件中。投标人的投标文件必须按照招标文件“第六部分投标文件格式”已明示法定代表人(或其授权代表)签署和盖章处,必须进行签字(或盖私章)和盖公章,否则视为无效投标。", + "20.4.2": "投标文件中的任何重要的插字、涂改和增删,必须由法定代表人或经其正式授权的代表在旁边签署才有效。", + "20.4.3": "投标文件的“正本”及所有“副本”的封面及每一页都必须由投标人加盖公章或骑缝章或法定代表人(或其授权代表)签署,对于未按要求盖章或签署的投标文件,均作无效投标处理。投标文件的“正本”及所有“副本”的封面及每一页都必须由投标人加盖公章或骑缝章或法定代表人(或其授权代表)签署,对于未按要求盖章或签署的投标文件,均作无效投标处理。" } -tran={ -"10.": "10", - "10.1": "投标人提交的投标文件以及投标人与招标采购单位就有关投标的所有来往函电均应使用中文书写。投标人提交的支持资料和己印刷的文献可以用另一种语言,但相应内容应附有中文翻译本,在解释投标文件的修改内容时以中文翻译本为准。对中文翻译有异议的,以权威机构的译本为准。11投标文件的构成", -"11.": "11", - "11.1": "投标人编写的投标文件应包括价格文件、技术文件、商务文件,价格部分必须独立装订,编排顺序参见投标文件格式。", - "11.2": "投标文件的构成应符合法律法规及招标文件的要求。12投标文件的编写", -"12.": "12", - "12.1": "投标人应完整、真实、准确地填写招标文件中提供的投标函、投标报价一览表、投标明细报价表(如适用)以及招标文件中规定的其它所有内容。", - "12.2": "投标人对招标文件中多个包组进行投标的,其投标文件的编制可按每个包组的要求分别装订和封装。投标人应当对投标文件进行装订,对未经装订的投标文件可能发生的文件散落或缺损,由此造成的后果和责任由投标人承担。", - "12.3": "投标人必须对投标文件所提供的全部资料的真实性承担法律责任,并无条件接受招标采购单位及政府采购监督管理部门等对其中任何资料进行核实的要求。", - "12.4": "如果因为投标人的投标文件只填写和提供了本招标文件要求的部分内容和附件,或没有提供招标文件中所要求的全部资料及数据,由此造成的后果和责任由投标人承担。13投标报价", -} -data=preprocess_data(data) -print(json.dumps(data,ensure_ascii=False,indent=4)) -# transformed = transform_json(tran) -# import pprint -# pprint.pprint(transformed) +transformed = transform_json(data) +print(json.dumps(transformed, ensure_ascii=False, indent=4)) diff --git a/flask_app/货物标/投标人须知正文提取指定内容货物标版.py b/flask_app/货物标/投标人须知正文提取指定内容货物标版.py index 9d8e1bc..a3a31d6 100644 --- a/flask_app/货物标/投标人须知正文提取指定内容货物标版.py +++ b/flask_app/货物标/投标人须知正文提取指定内容货物标版.py @@ -286,12 +286,11 @@ def extract_from_notice(clause_path, type): print(f"Error in extract_from_notice: {e}") return DEFAULT_RESULT -#TODO:’它包括:.2.对照招标文件服务内容与要‘ .号开头的情况有bug 后处理,如何组织信息。减少[] 顺便修改工程标的部分 if __name__ == "__main__": file_path = 'D:\\flask_project\\flask_app\\static\\output\\fee18877-0c60-4c28-911f-9a5f7d1325a7\\tmp\\clause1.json' # file_path = 'D:\\flask_project\\flask_app\\static\\output\\fee18877-0c60-4c28-911f-9a5f7d1325a7\\clause1.json' try: - res = extract_from_notice(file_path, 1) # 可以改变此处的 type 参数测试不同的场景 + res = extract_from_notice(file_path, 2) # 可以改变此处的 type 参数测试不同的场景 res2=json.dumps(res,ensure_ascii=False,indent=4) print(res2) except ValueError as e: diff --git a/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py b/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py index 1237d11..7cb6891 100644 --- a/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py +++ b/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py @@ -141,7 +141,7 @@ def parse_text_by_heading(text): match = re.match(r'^(\d+\.)\s*(.+)$', line_stripped) # 新增:处理以点号开头的情况 - dot_match = re.match(r'^\.(\d+)\s*(.+)$', line_stripped) + dot_match = re.match(r'^\.(\d+(?:\.\d+)*)\s*(.+)$', line_stripped) # 新增:处理没有点号的纯数字开头的情况 pure_number_match = re.match(r'^(\d+)([^.\d].*)', line_stripped) diff --git a/flask_app/货物标/货物标截取pdf.py b/flask_app/货物标/货物标截取pdf.py index d042e86..259de50 100644 --- a/flask_app/货物标/货物标截取pdf.py +++ b/flask_app/货物标/货物标截取pdf.py @@ -306,31 +306,54 @@ def extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern, beg return start_page, mid_page, end_page -def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header): # 投标人须知前附表/正文二次提取 + +def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header): begin_pattern = re.compile( - r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:(?:投标人?|磋商|供应商|谈判供应商|磋商供应商)须知前附表)+' + r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:(?:投标人?|磋商|供应商|谈判供应商|磋商供应商)须知)+' ) end_pattern = re.compile( - r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+' + r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*([\u4e00-\u9fff]+)' # 捕获中文部分 ) + exclusion_words = ["合同", "评标", "开标"] # 在这里添加需要排除的关键词 + pdf_document = PdfReader(pdf_path) exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据') + # 提取第一部分 start_page1, end_page1 = extract_pages_generic(pdf_document, begin_pattern, end_pattern, -1, common_header) if start_page1 is None or end_page1 is None: print(f"second: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!") return "", "" - # 提取第二部分 - start_page2 = end_page1 # 第二部分的开始页就是第一部分的结束页 - _, end_page2 = extract_pages_generic(pdf_document, end_pattern, end_pattern, start_page2 - 1, common_header, - exclusion_pattern) - if end_page2 is None: - print(f"second: {output_suffix} 未找到第二部分的结束页在文件 {pdf_path} 中!") - return "", "" - # 保存提取的页面 + # 保存第一部分的路径 path1 = save_extracted_pages(pdf_document, start_page1, end_page1, pdf_path, output_folder, "tobidders_notice_part1") + + # 提取第二部分 + start_page2 = end_page1 + + # 检查end_page1页面的内容 + text = pdf_document.pages[end_page1].extract_text() or "" + cleaned_text = clean_page_content(text, common_header) + match = end_pattern.search(cleaned_text) + + if match: + # 获取匹配到的中文部分 + chapter_title = match.group(1) + # 检查是否包含排除关键词 + if any(word in chapter_title for word in exclusion_words): + # 如果包含排除关键词,直接返回相同的路径 + return path1, path1 + + # 如果不包含排除关键词,继续提取第二部分 + _, end_page2 = extract_pages_generic(pdf_document, end_pattern, end_pattern, start_page2 - 1, common_header, + exclusion_pattern) + + if end_page2 is None: + print(f"second: {output_suffix} 未找到第二部分的结束页在文件 {pdf_path} 中!") + return path1, path1 + + # 保存第二部分的路径 path2 = save_extracted_pages(pdf_document, start_page2, end_page2, pdf_path, output_folder, "tobidders_notice_part2") @@ -614,14 +637,15 @@ def truncate_pdf_specific_goods(pdf_path, output_folder,selections): # TODO:交通智能系统和招标(1)(1)文件有问题 资格审查文件可能不需要默认与"evaluation"同一章 无效投标可能也要考虑 “more”的情况,类似工程标 if __name__ == "__main__": - input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\东莞支队查验招标文件.pdf" + input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\唐山市公安交通警察支队机动车查验机构视频存储回放系统竞争性谈判-招标文件正文(1).pdf" + # input_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles" # input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件_procurement.pdf" # input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\ztbfile.pdf" # output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\tmp" - output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\zboutpub" + output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\新建文件夹" files = truncate_pdf_multiple(input_path, output_folder) # files=truncate_pdf_specific_goods(input_path,output_folder) print(files) - # selection = 1# 例如:1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 5-公告 + # selection = 4# 例如:1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 5-公告 # generated_files = truncate_pdf_main(input_path, output_folder, selection) # print(generated_files)