From c98cd94bcd78acde0aa9c8eed9f643a9448dc7ac Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Thu, 19 Sep 2024 11:33:17 +0800 Subject: [PATCH] =?UTF-8?q?9.19=20=E6=8A=95=E6=A0=87=E4=BA=BA=E9=A1=BB?= =?UTF-8?q?=E7=9F=A5=E6=8F=90=E5=8F=96=E6=8C=87=E5=AE=9A=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=8C=E6=9B=B4=E5=8A=A0=E5=81=A5=E5=A3=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/encodings.xml | 7 + flask_app/main/test.py | 152 ++++++++++-------- flask_app/main/ttt.py | 113 +++++++++++-- flask_app/main/截取pdf.py | 12 +- flask_app/main/投标人须知正文提取指定内容.py | 77 ++++++--- .../main/投标人须知正文条款提取成json文件.py | 41 +++-- flask_app/main/招标文件解析.py | 7 +- flask_app/货物标/test.py | 80 +++++---- flask_app/货物标/商务服务其他要求提取.py | 1 + 9 files changed, 317 insertions(+), 173 deletions(-) create mode 100644 .idea/encodings.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..28b03da --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/flask_app/main/test.py b/flask_app/main/test.py index c490fe5..26daceb 100644 --- a/flask_app/main/test.py +++ b/flask_app/main/test.py @@ -1,77 +1,91 @@ +# -*- encoding:utf-8 -*- import json +def find_keys_by_value(target_value, json_data): + # 查找完全匹配目标值的键,如果没有找到,检查字符串值是否包含目标值 + matched_keys = [k for k, v in json_data.items() if v == target_value] + if not matched_keys: + matched_keys = [k for k, v in json_data.items() if isinstance(v, str) and v.startswith(target_value)] + print(matched_keys) + return matched_keys -def combine_technical_and_business(data, target_values1, target_values2): - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False +#若match_keys中有3.1,那么以3.1为前缀的键都会被找出来,如3.1.1 3.1.2... +def find_keys_with_prefix(key_prefix, json_data): + # 查找以特定前缀开始的所有键 + subheadings = [k for k in json_data if k.startswith(key_prefix)] + return subheadings - def extract_nested(data, parent_key='', is_technical=False, is_business=False): - nonlocal technical_found, business_found - if isinstance(data, dict): - for key, value in data.items(): - current_key = f"{parent_key}.{key}" if parent_key else key +def extract_json(data, target_values): + results = {} + for target_value in target_values: + matched_keys = find_keys_by_value(target_value, data) + for key in matched_keys: + key_and_subheadings = find_keys_with_prefix(key, data) + for subkey in key_and_subheadings: + if "." in subkey: + parent_key = subkey.rsplit('.', 1)[0] + top_level_key = parent_key.split('.')[0] + '.' + # 特别处理定标相关的顶级键,确保不会重复添加其他键 + if top_level_key not in results: + results[top_level_key] = target_value + # 添加或更新父级键 + if parent_key not in results: + if parent_key in data: + results[parent_key] = data[parent_key] + # 添加当前键 + results[subkey] = data[subkey] + return results - # 检查是否为技术标的内容 - if any(target in key for target in target_values1): - if not is_technical: - # 直接存储在根级别 - extracted_data[key] = value - technical_found = True - # 标记为技术标内容并停止进一步处理这个分支 - continue - - # 检查是否为商务标的内容 - elif any(target in key for target in target_values2): - if not is_business: - # 存储在'商务标'分类下 - if '商务标' not in extracted_data: - extracted_data['商务标'] = {} - extracted_data['商务标'][key] = value - business_found = True - # 标记为商务标内容并停止进一步处理这个分支 - continue - - # 如果当前值是字典或列表,且不在技术或商务分类下,继续递归搜索 - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) - - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) - - # 开始从顶级递归搜索 - extract_nested(data) - - # 处理未找到匹配的情况 - if not technical_found: - extracted_data['技术标'] = '' - if not business_found: - extracted_data['商务标'] = '' - - return extracted_data - -# 示例数据和调用代码 +# 示例 JSON 数据 data = { - "商x务": { - "投标报价": {"方案": "详细报价"}, - "合同条款": {"期限": "一年"} - }, - "商x务标": { - "商务": {"商务x标": "商业方案"}, - "商务条款": {"期限": "一年"} - }, - "技x术标": { - "技术要求": [{"性能指标": "高性能"}, {"分值": "6"}] - }, - "投标x报价细节": { - "价格": "100万元", - "条件": "包运费" - } + "3.1": "投标文件的组成", + "3.1.1": "投标文件应包括下列内容:(1)投标函及投标函附录;(2)法定代表人身份证明;(3)联合体协议书(如有);(4)投标保证金;(5)监理服务费投标报价表;(6)监理大纲;(7)监理机构;(8)资格审查资料;(9)投标人须知前附表规定的其他材料。", + "3.1.2": "招标公告规定不接受联合体投标的,或投标人没有组成联合体的,投标文件不包括本章第3.1.1项第(3)目所指的联合体协议书。", + "3.1.3": "投标人须知前附表规定不允许分包的,投标文件不包括本章第3.1.1项第(8)目所指的拟分包项目情况表。", + "3.2": "投标报价", + "3.2.1": "投标报价是投标人按照招标文件的要求完成投标人须知前附表规定监理服务阶段监理工作所需的费用。", + "3.2.2": "招标人是否设置最高投标限价见投标人须知前附表,如采用设置了最高投标限价,投标人在投标书中的报价应在最高投标限价范围内,最高投标限价在招标文件中或最迟在投标截止时间前15日,通过“电子交易平台”以书面形式发给所有下载招标文件的投标人。", + "3.2.3": "投标人应按照招标文件规定的格式和内容计算投标报价。", + "3.2.4": "其他报价规定见投标人须知前附表。", + "3.3": "投标有效期", + "3.3.1": "在投标人须知前附表规定的投标有效期内,投标人不得要求撤销或修改其投标文件。", + "3.3.2": "出现特殊情况需要延长投标有效期的,招标人以书面形式通知所有投标人延长投标有效期。投标人同意延长的,应相应延长其投标保证金的有效期,但不得要求或被允许修改或撤销其投标文件;投标人拒绝延长的,其投标失效,但投标人有权收回其投标保证金。", + "3.4": "投标保证金", + "3.4.1": "投标人须知前附表规定提交投标保证金的,投标人在递交投标文件的同时,应按投标人须知前附表规定的形式、金额、递交截止时间、递交方式提交投标保证金,并作为其投标文件的组成部分。联合体投标的,其投标保证金由牵头人递交,并应符合投标人须知前附表的规定。联合体其他成员提交保证金的,保证金无效。投标保证金有效期均应与投标有效期一致。招标人如果按本章第3.3.2项的规定延长了投标有效期,则投标保证金有效期也相应延长。", + "3.4.2": "招标人与中标人签订监理合同后5日内,向中标人和未中标的投标人退还投标保证金及银行同期存款利息;如果要求中标人提供履约担保的,中标人的投标保证金在中标人提交了履约担保并签订监理合同后5日内退还。", + "3.4.3": "投标保证金有效期与投标文件有效期一致,招标人按规定延长投标文件有效期的,投标保证金按本章投标人须知第3.3款的规定执行。", + "3.4.4": "有下列情形之一的,投标保证金将不予退还:(1)投标人在规定的投标有效期内撤销或修改其投标文件;(2)中标人在收到中标通知书后,无正当理由拒签合同协议书或未按招标文件规定提交履约担保;(3)中标人在签订合同时向招标人提出附加条件;(4)推荐的中标候选人在中标通知书发出前放弃中标;(5)投标人不接受依据评标办法的规定对其投标文件中细微偏差进行澄清和补正;(6)投标人存在以他人名义投标、与他人串通投标、以行贿手段谋取中标、弄虚作假等行为。", + "3.5": "资格审查资料", + "3.5.1": "投标人应按招标文件第八章“投标文件格式”中规定的表格内容填写资格审查表,并按各资格审查表的具体要求提供相关证件及证明材料。招标文件中提到的“近3年”除有特别说明外,指从投标截止日往前推算的3年,如投标截止日为2018年2月1日,则近3年是指2015年2月1日至2018年1月31日。其他情况依此类推。", + "3.5.2": "“投标人近年财务状况表”具体年份要求见投标人须知前附表。财务状况的年份要求指的是年度。如投标截止日如在当年6月30日以前,则近年指上上个年度往前推算的3年,例如投标截止日为2018年5月30日,近三年是指2014年度、2015年度、2016年度。投标截止日如在当年6月30日以后,则近三年是指上个年度往前推算的3年,例如投标截止日为2018年7月30日,近三年是指2015年度、2016年度、2017年度。", + "3.5.3": "“投标人近年已完工的类似工程一览表”具体年份要求见投标人须知前附表。", + "3.5.4": "招标公告规定接受联合体投标的,本章第3.5.1项规定的表格和资料应包括联合体各方相关情况。", + "3.6": "投标文件的编制", + "3.6.1": "投标文件应按“投标文件格式”进行编写,如有必要,可以增加附页,作为投标文件的组成部分。其中,投标函附录在满足招标文件实质性要求的基础上,可以提出比招标文件要求更有利于招标人的承诺。", + "3.6.2": "投标文件应当对招标文件有关工期、投标有效期、质量要求、技术标准和要求、招标范围等实质性内容作出响应。", + "3.6.3": "投标文件制作(1)投标文件由投标人使用“市电子交易平台”自带的“投标文件制作工具”制作生成。(2)投标人在编制投标文件时应当建立分级目录,并按照标签提示导入相关内容。(3)投标文件中证明资料的“复印件”均为“原件的扫描件”,(4)投标文件中的已标价工程量清单数据文件应与招标人提供的工程量清单数据文件格式一致。(5)第八章投标文件格式文件要求盖单位章和(或)签字的地方,投标人均应使用CA数字证书加盖投标人的单位电子印章和(或)法定代表人的个人电子印章或电子签名章。联合体投标的,投标文件由联合体牵头人按上述规定加盖联合体牵头人单位电子印章和(或)法定代表人的个人电子印章或电子签名章。(6)投标文件制作完成后,将生成一份加密的电子投标文件和一份不加密的电子投标文件。(7)投标人将不加密的电子投标文件复制到一张U盘中,U盘表面粘贴“标签贴”,并将项目名称、招标编号、投标人名称等信息填写在“标签贴”上。(8)投标文件制作的具体方法详见“投标文件制作工具”中的帮助文档。", + "3.6.4": "投标文件份数投标文件包括加密的电子投标文件和不加密的电子投标文件(U盘备份)各一份。投标人中标后向招标人另行提供与投标时相同的纸质版投标文件份数见投标人须知前附表。", + "4.": "投标", + "4.1": "投标文件的密封与标记", + "4.1.1": "投标文件的加密加密的电子投标文件应按照本章第3.6.3项要求制作并加密,未按要求加密的电子投标文件,招标人(“市电子交易平台”)将拒收并提示。", + "4.1.2": "不加密的电子投标文件的密封和标识(1)不加密的电子投标文件(U盘备份)应单独密封包装,并在封套的封口处加贴封条。(2)不加密的电子投标文件(U盘备份)封套上应写明的其他内容见投标人须知前附表。3未按本章第4.1.2项要求密封和加写标记的投标文件,招标人将拒收。", + "4.2": "投标文件的递交", + "4.2.1": "在招标公告规定的投标截止时间前,投标人可以修改或撤回已递交的投标文件。", + "4.2.2": "投标人对加密的电子投标文件进行撤回的,在“市电子交易平台”直接进行撤回操作;投标人对不加密的电子投标文件(U盘备份)进行撤回的,应以书面形式通知招标人,撤回的书面通知应加盖投标人的单位章或由法定代表人或其委托代理人签字(指亲笔签名),招标人收到书面通知后,向投标人出具签收凭证。", + "4.2.3": "投标人撤回投标文件的,招标人自收到投标人书面撤回通知之日按公共资源交易中心退还已收取的投标保证金。", + "4.2.4": "投标人修改投标文件的,应当先按本章第4.2项的规定撤回投标文件,再使用“投标文件制作工具”制作成完整的投标文件,并按照本章第3条、第4条规定进行编制、密封、标记和递交。", + "4.2.5": "任何情况下,投标人都有义务保证其递交的加密的电子投标文件和不加密的电子投标文件(U盘备份)内容一致,否则造成的后果由投标人自行承担。", + "4.3": "投标文件的修改与撤回", + "4.3.1": "在送交投标文件截止期以前,投标人可以更改或撤回投标文件,并按本章第项的规定操作。", + "4.3.2": "送交投标文件截止期以后,投标文件不得更改。需对投标文件做出澄清时,必须按照本须知第23条的规定办理。", + "4.3.4": "如果在送交投标文件截止期以后且投标文件有效期内撤回投标文件,则按本须知第3.4.4款的规定不予退还其投标担保。", } -target_values2 = ["商务标", "投标报价"] -target_values1 = ['技术标', '设计', '实施', '方案'] -result = combine_technical_and_business(data, target_values1, target_values2) -evaluation_combined_res = json.dumps(result, ensure_ascii=False, indent=4) -print(evaluation_combined_res) +# 目标值 +target_values = ["投标文件","投标"] + +# 提取数据 +extracted_data = extract_json(data, target_values) + +# 打印结果 +print(json.dumps(extracted_data, indent=4, ensure_ascii=False)) diff --git a/flask_app/main/ttt.py b/flask_app/main/ttt.py index 7eb4d31..9746aba 100644 --- a/flask_app/main/ttt.py +++ b/flask_app/main/ttt.py @@ -1,19 +1,104 @@ +import json import re -pattern = re.compile(r'(\b\d+\s*\.\s*\d+\s*\.\s*\d+\b)|(\b3\s*\.\s*2\b)') -text = '3.1.3已标价工程量清单中漏报了某个工程子目的单价、合价或总额价则漏报的工程 子目单价、合价和总额价视为已含入其他工程子目的单价、合价和总额价之中。' +def transform_json(data): + result = {} + temp = {0: result} # 初始化根字典 -match = pattern.search(text) -if match: - print("匹配成功:", match.group()) -else: - print("未找到匹配") + # 首先,创建一个临时字典用于检查是否存在三级标题 + has_subkey = {} + for key in data.keys(): + parts = key.split('.') + if len(parts) > 2 and parts[1]: + parent_key = parts[0] + '.' + parts[1] + has_subkey[parent_key] = True -# 使用 findall 查看所有匹配 -all_matches = pattern.findall(text) -print("所有匹配:", all_matches) + 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 -# 打印文本的前10个字符的ASCII值,检查是否有不可见字符 -print("文本前10个字符的ASCII值:") -for char in text[:10]: - print(f"{char}: {ord(char)}") \ No newline at end of file + if len(levels) == len(match.groups()): + if isinstance(parent, list): + parent.append(value) + else: + # 对于根级别,使用完整的值作为键 + if len(levels) == 1: + parent[value] = {} + temp[len(levels)] = parent[value] + else: + parent[value.split()[0]] = value + else: + new_key = value + if '\n' in value and len(levels) == 2 and f"{levels[0]}.{levels[1]}" not in has_subkey: + new_key, new_value = value.split('\n', 1) + new_key = new_key.strip() + new_value = new_value.strip() + if isinstance(parent, list): + if len(parent) == 0 or not isinstance(parent[-1], dict): + parent.append({}) + parent[-1][new_key] = new_value + else: + parent[new_key] = new_value + else: + if isinstance(parent, list): + if len(parent) == 0 or not isinstance(parent[-1], dict): + parent.append({}) + parent = parent[-1] + if new_key not in parent: + parent[new_key] = [] + temp[len(levels)] = parent[new_key] + + def remove_single_item_lists(node): + if isinstance(node, dict): + for key in list(node.keys()): + node[key] = remove_single_item_lists(node[key]) + if isinstance(node[key], list) and not node[key]: + node[key] = "" + elif isinstance(node, list) and len(node) == 1: + return remove_single_item_lists(node[0]) + return node + + return remove_single_item_lists(result) + +# 示例 JSON 数据 +data = { + "3.1": "投标文件的组成", + "3.1.1": "投标文件应包括下列内容:(1)投标函及投标函附录;(2)法定代表人身份证明;(3)联合体协议书(如有);(4)投标保证金;(5)监理服务费投标报价表;(6)监理大纲;(7)监理机构;(8)资格审查资料;(9)投标人须知前附表规定的其他材料。", + "3.1.2": "招标公告规定不接受联合体投标的,或投标人没有组成联合体的,投标文件不包括本章第3.1.1项第(3)目所指的联合体协议书。", + "3.1.3": "投标人须知前附表规定不允许分包的,投标文件不包括本章第3.1.1项第(8)目所指的拟分包项目情况表。", + "3.6": "投标文件的编制", + "3.6.1": "投标文件应按“投标文件格式”进行编写,如有必要,可以增加附页,作为投标文件的组成部分。其中,投标函附录在满足招标文件实质性要求的基础上,可以提出比招标文件要求更有利于招标人的承诺。", + "4.": "投标", + "4.1": "投标文件的密封与标记", + "4.1.1": "投标文件的加密加密的电子投标文件应按照本章第3.6.3项要求制作并加密,未按要求加密的电子投标文件,招标人(“市电子交易平台”)将拒收并提示。", + "4.1.2": "不加密的电子投标文件的密封和标识(1)不加密的电子投标文件(U盘备份)应单独密封包装,并在封套的封口处加贴封条。(2)不加密的电子投标文件(U盘备份)封套上应写明的其他内容见投标人须知前附表。3未按本章第4.1.2项要求密封和加写标记的投标文件,招标人将拒收。", + "4.2": "投标文件的递交", + "4.2.1": "在招标公告规定的投标截止时间前,投标人可以修改或撤回已递交的投标文件。", + "4.2.2": "投标人对加密的电子投标文件进行撤回的,在“市电子交易平台”直接进行撤回操作;投标人对不加密的电子投标文件(U盘备份)进行撤回的,应以书面形式通知招标人,撤回的书面通知应加盖投标人的单位章或由法定代表人或其委托代理人签字(指亲笔签名),招标人收到书面通知后,向投标人出具签收凭证。", + "4.3": "投标文件的修改与撤回", + "4.3.1": "在送交投标文件截止期以前,投标人可以更改或撤回投标文件,并按本章第项的规定操作。", + "4.3.2": "送交投标文件截止期以后,投标文件不得更改。需对投标文件做出澄清时,必须按照本须知第23条的规定办理。", + "4.3.4": "如果在送交投标文件截止期以后且投标文件有效期内撤回投标文件,则按本须知第3.4.4款的规定不予退还其投标担保。", +} +def sort_data_keys(data): + # 将键转换成由整数构成的元组,作为排序依据 + def key_func(key): + return tuple(int(part) for part in re.split(r'\D+', key) if part) + # 对字典键进行排序 + sorted_keys = sorted(data.keys(), key=key_func) + # 创建一个新的字典,按照排序后的键添加键值对 + sorted_data = {key: data[key] for key in sorted_keys} + return sorted_data + +sorted_data=sort_data_keys(data) +# 调用 transform_json 函数 +transformed_data = transform_json(sorted_data) + +# 打印结果 +print(json.dumps(transformed_data, indent=4, ensure_ascii=False)) \ No newline at end of file diff --git a/flask_app/main/截取pdf.py b/flask_app/main/截取pdf.py index 7a4a5c4..56dc3e7 100644 --- a/flask_app/main/截取pdf.py +++ b/flask_app/main/截取pdf.py @@ -263,9 +263,11 @@ def truncate_pdf_multiple(input_path, output_folder): # TODO:需要完善二次请求。目前invalid一定能返回 前附表 须知正文如果为空的话要额外处理一下,比如说就不进行跳转(见xx表) 开评定标这里也要考虑 如果评分表为空,也要处理。 if __name__ == "__main__": - input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest1.pdf" - output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹" - # truncate_pdf_multiple(input_path,output_folder) - selection = 3 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-资格审查条件 5-招标公告 6-无效标 - generated_files = truncate_pdf_main(input_path, output_folder, selection) + # input_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\4bda9fde-89fc-4e5e-94a4-ce6c43010f74\\ztbfile.pdf" + # output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\4bda9fde-89fc-4e5e-94a4-ce6c43010f74" + input_path="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest2.pdf" + output_folder="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹" + truncate_pdf_multiple(input_path,output_folder) + # selection = 3 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-资格审查条件 5-招标公告 6-无效标 + # generated_files = truncate_pdf_main(input_path, output_folder, selection) # # print("生成的文件:", generated_files) diff --git a/flask_app/main/投标人须知正文提取指定内容.py b/flask_app/main/投标人须知正文提取指定内容.py index cd19894..131fc2b 100644 --- a/flask_app/main/投标人须知正文提取指定内容.py +++ b/flask_app/main/投标人须知正文提取指定内容.py @@ -2,15 +2,15 @@ import json import re -# 定义查找与目标值匹配的键的函数 +# 对于每个target_value元素,如果有完美匹配json_data中的键,那就加入这个完美匹配的键名,否则,把全部模糊匹配到的键名都加入 def find_keys_by_value(target_value, json_data): - matched_keys = [k for k, v in json_data.items() if v == target_value] + matched_keys = [k for k, v in json_data.items() if v == target_value] #首先检查 JSON 中的每个键值对,如果值完全等于目标值,则将这些键收集起来。 if not matched_keys: - matched_keys = [k for k, v in json_data.items() if isinstance(v, str) and v.startswith(target_value)] - return matched_keys + matched_keys = [k for k, v in json_data.items() if isinstance(v, str) and v.startswith(target_value)] #如果没有找到完全匹配的键,它会检查字符串类型的值是否以目标值开头,并收集这些键。 + return matched_keys # eg:[3.1,3.1.1,3.1.2,3.2...] -# 定义查找以特定前缀开始的键的函数 +# 定义查找以特定前缀开始的键的函数,eg:若match_keys中有3.1,那么以3.1为前缀的键都会被找出来,如3.1.1 3.1.2... def find_keys_with_prefix(key_prefix, json_data): subheadings = [k for k in json_data if k.startswith(key_prefix)] return subheadings @@ -28,8 +28,8 @@ def extract_json(data, target_values): parent_key = subkey.rsplit('.', 1)[0] top_level_key = parent_key.split('.')[0] + '.' # 特别处理定标相关的顶级键,确保不会重复添加其他键 - if target_value == "定标" and top_level_key not in results: - results[top_level_key] = "定标" + if top_level_key not in results: + results[top_level_key] = target_value # 添加或更新父级键 if parent_key not in results: if parent_key in data: @@ -38,32 +38,65 @@ def extract_json(data, target_values): results[subkey] = data[subkey] return results +def sort_clean_data_keys(data): + # 预处理:删除键名中的空格 + def preprocess_key(key): + return re.sub(r'\s+', '', key) + + # 将键转换成由整数构成的元组,作为排序依据 + def key_func(key): + return tuple(int(part) for part in re.split(r'\D+', key) if part) + + # 创建一个新的字典,键名经过预处理 + preprocessed_data = {preprocess_key(key): value for key, value in data.items()} + + # 对预处理后的字典键进行排序 + sorted_keys = sorted(preprocessed_data.keys(), key=key_func) + + # 创建一个新的字典,按照排序后的键添加键值对 + sorted_data = {key: preprocessed_data[key] for key in sorted_keys} + + return sorted_data # 转换结构化的JSON数据 def transform_json(data): result = {} - temp = {0: result} + temp = {0: result} # 初始化根字典 + + # 首先,创建一个临时字典用于检查是否存在三级标题 + has_subkey = {} + for key in data.keys(): + parts = key.split('.') + if len(parts) > 2 and parts[1]: + parent_key = parts[0] + '.' + parts[1] + 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] - parent = temp[len(levels) - 1] + 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 if len(levels) == len(match.groups()): if isinstance(parent, list): parent.append(value) else: - # 对于没有 \n 的情况,使用首个空格分割的词作为键 - parent[value.split()[0]] = value + # 对于根级别,使用完整的值作为键 + if len(levels) == 1: + parent[value] = {} + temp[len(levels)] = parent[value] + else: + parent[value.split()[0]] = value else: - new_key = value.split()[0] - if '\n' in value and len(levels) == 2: - # 处理换行情况并分割键和值 + new_key = value + if '\n' in value and len(levels) == 2 and f"{levels[0]}.{levels[1]}" not in has_subkey: new_key, new_value = value.split('\n', 1) new_key = new_key.strip() new_value = new_value.strip() - # 确保父级是字典 if isinstance(parent, list): if len(parent) == 0 or not isinstance(parent[-1], dict): parent.append({}) @@ -79,13 +112,12 @@ def transform_json(data): parent[new_key] = [] temp[len(levels)] = parent[new_key] - # 修改函数以移除只有一个元素的列表和空列表 def remove_single_item_lists(node): if isinstance(node, dict): for key in list(node.keys()): node[key] = remove_single_item_lists(node[key]) if isinstance(node[key], list) and not node[key]: - node[key] = "" # 如果列表为空,转换为空字符串 + node[key] = "" elif isinstance(node, list) and len(node) == 1: return remove_single_item_lists(node[0]) return node @@ -96,7 +128,7 @@ def transform_json(data): # 读取JSON数据,提取内容,转换结构,并打印结果 def extract_from_notice(clause_path, type): if type == 1: - target_values = ["投标文件", "投标"] + target_values = ["投标","投标文件"] elif type == 2: target_values = ["开标", "评标", "定标"] elif type == 3: @@ -106,15 +138,18 @@ def extract_from_notice(clause_path, type): with open(clause_path, 'r', encoding='utf-8') as file: data = json.load(file) extracted_data = extract_json(data, target_values) # 读取json - transformed_data = transform_json(extracted_data) + sorted_data=sort_clean_data_keys(extracted_data) #对键进行排序 + transformed_data = transform_json(sorted_data) return transformed_data # 假设原始数据文件路径 if __name__ == "__main__": - file_path = 'tt.json' + file_path = 'D:\\flask_project\\flask_app\\static\\output\\cfd4959d-5ea9-4112-8b50-9e543803f029\\clause1.json' + # file_path='C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\clause1.json' try: res = extract_from_notice(file_path, 3) # 可以改变此处的 type 参数测试不同的场景 - print(res) + res2=json.dumps(res,ensure_ascii=False,indent=4) + print(res2) except ValueError as e: print(e) diff --git a/flask_app/main/投标人须知正文条款提取成json文件.py b/flask_app/main/投标人须知正文条款提取成json文件.py index ec1b8ac..a055243 100644 --- a/flask_app/main/投标人须知正文条款提取成json文件.py +++ b/flask_app/main/投标人须知正文条款提取成json文件.py @@ -26,19 +26,23 @@ def extract_text_from_pdf(file_path): return text def extract_section(text, start_pattern, end_phrases): + # 查找开始模式 start_match = re.search(start_pattern, text) if not start_match: return "" # 如果没有找到匹配的开始模式,返回空字符串 - start_index = start_match.start() - end_index = len(text) - print(text[start_index:]) - for phrase in end_phrases: - # Use multiline mode with `re.MULTILINE` - match = re.search(phrase, text[start_index:], re.MULTILINE) #Hello, world!\nWelcome to OpenAI. 在多行字符串多,要 re.MULTILINE以匹配每一行的开头,否则只会匹配字符串的开头。 - if match: - end_index = start_index + match.start() - break + start_index = start_match.end() # 从匹配的结束位置开始 + # 初始化结束索引为文本总长度 + end_index = len(text) + + # 遍历所有结束短语,查找第一个出现的结束短语 + for phrase in end_phrases: + match = re.search(phrase, text[start_index:], flags=re.MULTILINE) + if match: + end_index = start_index + match.start() # 更新结束索引为匹配到的开始位置 + break # 找到第一个匹配后立即停止搜索 + + # 提取并返回从开始模式后到结束模式前的内容 return text[start_index:end_index] def compare_headings(current, new): @@ -121,6 +125,7 @@ def convert_to_json(file_path, start_word, end_phrases): raise ValueError("Unsupported file format") # 提取从 start_word 开始到 end_phrases 结束的内容 text = extract_section(text, start_word, end_phrases) + # print(text) parsed_data = parse_text_by_heading(text) return parsed_data @@ -131,7 +136,7 @@ def convert_clause_to_json(input_path,output_folder,type=1): if type==1: start_word = "投标人须知正文" end_phrases = [ - r'^第[一二三四五六七八九十]+章\s+评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', + r'^第[一二三四五六七八九十]+章\s*评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', ] else: @@ -142,16 +147,18 @@ def convert_clause_to_json(input_path,output_folder,type=1): output_path = os.path.join(output_folder, file_name) with open(output_path, 'w', encoding='utf-8') as f: json.dump(result, f, indent=4, ensure_ascii=False) + print(f"投标人须知正文条款提取成json文件: The data has been processed and saved to '{output_path}'.") return output_path if __name__ == "__main__": - file_path = 'C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest20_tobidders_notice.pdf' - start_word = "投标人须知正文" - end_phrases = [ - r'^第[一二三四五六七八九十]+章\s+评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', - r'^附表:', r'^附表一:', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', - ] - output_folder = 'C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹' + # file_path = 'D:\\flask_project\\flask_app\\static\\output\\cfd4959d-5ea9-4112-8b50-9e543803f029\\ztbfile_tobidders_notice.pdf' + file_path='C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest1_tobidders_notice.pdf' + # start_word = "投标人须知正文" + # end_phrases = [ + # r'^第[一二三四五六七八九十]+章\s+评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', + # r'^附表:', r'^附表一:', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', + # ] + output_folder = 'D:\\flask_project\\flask_app\\static\\output\\cfd4959d-5ea9-4112-8b50-9e543803f029\\tmp' try: output_path = convert_clause_to_json(file_path,output_folder) print(f"Final JSON result saved to: {output_path}") diff --git a/flask_app/main/招标文件解析.py b/flask_app/main/招标文件解析.py index c600725..d2461e6 100644 --- a/flask_app/main/招标文件解析.py +++ b/flask_app/main/招标文件解析.py @@ -29,6 +29,7 @@ logger=None # 可能有问题:pdf转docx导致打勾符号消失 def preprocess_files(output_folder, downloaded_file_path, file_type, unique_id): logger.info("starting 文件预处理...") + logger.info("output_folder..."+output_folder) # 根据文件类型处理文件路径 if file_type == 1: # docx docx_path = downloaded_file_path @@ -240,7 +241,7 @@ def main_processing(output_folder, downloaded_file_path, file_type, unique_id): # deleteKnowledge(processed_data['knowledge_index']) if __name__ == "__main__": - output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\test3" + output_folder = "flask_app/static/output/zytest1" # truncate0 = os.path.join(output_folder, "ztb_tobidders_notice_table.pdf") # truncate1=os.path.join(output_folder,"ztb_evaluation_method.pdf") @@ -249,10 +250,10 @@ if __name__ == "__main__": start_time = time.time() file_type = 1 #1:docx 2:pdf 3:其他 - input_file = "C:\\Users\\Administrator\\Desktop\\招标文件\\test3\\zbtest20.docx" + input_file = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest11.pdf" # file_path = main_processing(output_folder, input_file, file_type, "uuidzyzy11") - preprocess_files(output_folder, input_file, file_type, "unique_id") + preprocess_files(output_folder, input_file, file_type, "zytest1") end_time = time.time() elapsed_time = end_time - start_time # 计算耗时 print(f"Function execution took {elapsed_time} seconds.") diff --git a/flask_app/货物标/test.py b/flask_app/货物标/test.py index 395cba7..a6f7a15 100644 --- a/flask_app/货物标/test.py +++ b/flask_app/货物标/test.py @@ -2,55 +2,48 @@ import json -def combine_technical_and_business(data, target_values1, target_values2): - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False +def combine_technical_and_business(data, target_values1): + extracted_data = {} # 根级别存储所有数据 + technical_found = False + business_found = False - def extract_nested(data, parent_key='', is_technical=False, is_business=False): - nonlocal technical_found, business_found - if isinstance(data, dict): - for key, value in data.items(): - current_key = f"{parent_key}.{key}" if parent_key else key + def extract_nested(data, parent_key='', is_technical=False, is_business=False): + nonlocal technical_found, business_found + if isinstance(data, dict): + for key, value in data.items(): + current_key = f"{parent_key}.{key}" if parent_key else key - # 检查是否为技术标的内容 - if any(target in key for target in target_values1): #模糊匹配 - if not is_technical: - # 直接存储在根级别 - extracted_data[key] = value - technical_found = True - # 标记为技术标内容并停止进一步处理这个分支 - continue + # 检查是否为技术标的内容 + if any(target in key for target in target_values1): + if not is_technical: + extracted_data[key] = value + technical_found = True + continue - # 检查是否为商务标的内容 - elif any(target in key for target in target_values2): - if not is_business: - # 存储在'商务标'分类下 - if '商务标' not in extracted_data: - extracted_data['商务标'] = {} - extracted_data['商务标'][key] = value - business_found = True - # 标记为商务标内容并停止进一步处理这个分支 - continue + # 默认其他所有内容都归为商务标 + else: + if not is_business: + if '商务标' not in extracted_data: + extracted_data['商务标'] = {} + extracted_data['商务标'][key] = value + business_found = True + continue - # 如果当前值是字典或列表,且不在技术或商务分类下,继续递归搜索 - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) + if isinstance(value, dict) or isinstance(value, list): + extract_nested(value, current_key, is_technical, is_business) - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + elif isinstance(data, list): + for index, item in enumerate(data): + extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) - # 开始从顶级递归搜索 - extract_nested(data) + extract_nested(data) - # 处理未找到匹配的情况 - if not technical_found: - extracted_data['技术标'] = '' - if not business_found: - extracted_data['商务标'] = '' + if not technical_found: + extracted_data['技术标'] = '' + if not business_found: + extracted_data['商务标'] = '' - return extracted_data + return extracted_data # 示例数据 @@ -219,15 +212,14 @@ def get_evaluation_standards(truncate_file): include = ['一包', '二包', '三包', '四包', '五包'] target_values1 = ['技术', '设计', '实施', '方案'] - target_values2 = ['投标报价', '商务标', '商务部分', '报价部分', '业绩', '信誉', '分值', '计算公式', '信用', '人员', - '资格', '奖项', '认证', '荣誉'] + updated_jsons = {} for key in data.keys(): if any(item in key for item in include): inner_dict = data[key] # 将处理后的结果存储到updated_jsons中,每个包名为键 - updated_jsons[key] = combine_technical_and_business(inner_dict, target_values1, target_values2) + updated_jsons[key] = combine_technical_and_business(inner_dict, target_values1) # 将updated_jsons转换为JSON格式 evaluation_combined_res = json.dumps(updated_jsons, ensure_ascii=False, indent=4) diff --git a/flask_app/货物标/商务服务其他要求提取.py b/flask_app/货物标/商务服务其他要求提取.py index b4c1e93..6d84f1b 100644 --- a/flask_app/货物标/商务服务其他要求提取.py +++ b/flask_app/货物标/商务服务其他要求提取.py @@ -7,6 +7,7 @@ from flask_app.main.通义千问long import qianwen_long, upload_file from flask_app.货物标.货物标截取pdf import extract_common_header, clean_page_content +#正则表达式判断原文中是否有商务、服务、其他要求 def find_exists(truncate_file, required_keys): common_header = extract_common_header(truncate_file) pdf_document = PdfReader(truncate_file)