From 65a7ddf1b88ec8c8a566a0d98e97a6f741c54fb5 Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Fri, 3 Jan 2025 17:36:23 +0800 Subject: [PATCH] =?UTF-8?q?1.3=20=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/general/clean_pdf.py | 53 +- flask_app/general/json_utils.py | 4 +- flask_app/general/截取pdf_main.py | 8 +- flask_app/general/读取文件/按页读取pdf.py | 2 +- flask_app/general/通用功能函数.py | 43 +- flask_app/routes/工程标解析main.py | 10 +- flask_app/static/提示词/基本信息工程标.txt | 27 +- flask_app/static/提示词/基本信息货物标.txt | 17 +- .../static/提示词/小解析基本信息工程标.txt | 2 +- .../static/提示词/小解析基本信息货物标.txt | 2 +- flask_app/static/提示词/是否相关问题.txt | 73 ++- flask_app/工程标/判断是否分包等.py | 62 ++- flask_app/工程标/基础信息整合工程标.py | 31 +- flask_app/工程标/截取pdf工程标版.py | 42 +- flask_app/货物标/基础信息解析货物标版.py | 25 +- flask_app/货物标/截取pdf货物标版.py | 13 +- flask_app/货物标/提示词/prompt1.txt | 505 ------------------ 17 files changed, 263 insertions(+), 656 deletions(-) delete mode 100644 flask_app/货物标/提示词/prompt1.txt diff --git a/flask_app/general/clean_pdf.py b/flask_app/general/clean_pdf.py index 4f72575..25c4734 100644 --- a/flask_app/general/clean_pdf.py +++ b/flask_app/general/clean_pdf.py @@ -1,8 +1,8 @@ import re from PyPDF2 import PdfReader -def extract_common_header(pdf_path): +def extract_common_header(pdf_path): def get_headers(pdf_document, start_page, pages_to_read): headers = [] for i in range(start_page, min(start_page + pages_to_read, len(pdf_document.pages))): @@ -14,32 +14,39 @@ def extract_common_header(pdf_path): headers.append(first_lines) return headers - def longest_common_prefix(strs): - if not strs: - return "" - # 找到最短的字符串长度 - min_len = min(len(s) for s in strs) - if min_len == 0: - return "" - # 从第一个字符开始逐个比较 - for i in range(min_len): - char = strs[0][i] - for s in strs[1:]: - if s[i] != char: - return strs[0][:i] - return strs[0][:min_len] - def find_common_headers(headers): if not headers: return [] - # 使用 zip 对齐所有页的对应行 + # 转置,使得每一行对应所有页的同一行 + transposed_headers = list(zip(*headers)) common_headers = [] - for lines in zip(*headers): - # 找到所有行的最长公共前缀 - common_prefix = longest_common_prefix(lines) - if len(common_prefix) >= 5: # 可以根据实际情况调整最小长度 - common_headers.append(common_prefix) + + for lines in transposed_headers: + # 将每行按空格分割成部分 + split_lines = [line.split() for line in lines] + + # 找出所有行中最短的部分数 + min_parts = min(len(parts) for parts in split_lines) + if min_parts == 0: + continue + + common_parts = [] + for part_idx in range(min_parts): + # 获取当前部分在所有行中的值 + current_parts = [parts[part_idx] for parts in split_lines] + # 检查所有部分是否相同 + if all(part == current_parts[0] for part in current_parts[1:]): + common_parts.append(current_parts[0]) + else: + break # 如果某部分不相同,停止进一步比较 + + if common_parts: + # 将共同的部分重新组合成字符串 + common_header_line = ' '.join(common_parts) + if len(common_header_line) >= 5: # 可以根据实际情况调整最小长度 + common_headers.append(common_header_line) + return common_headers pdf_document = PdfReader(pdf_path) @@ -89,6 +96,8 @@ def clean_page_content(text, common_header): if header_line.strip(): # 只处理非空行 # 替换首次出现的完整行 text = re.sub(r'^' + re.escape(header_line.strip()) + r'\n?', '', text, count=1) + # 预处理:删除文本开头的所有空白字符(包括空格、制表符等) + text = text.lstrip() # 删除文本开头的“第x页”格式的页码 text = re.sub(r'^第\d+页\s*', '', text) # 删除页码 eg:89/129 这个代码分三步走可以把89/129完全删除 diff --git a/flask_app/general/json_utils.py b/flask_app/general/json_utils.py index 26f39b1..ff803e5 100644 --- a/flask_app/general/json_utils.py +++ b/flask_app/general/json_utils.py @@ -227,6 +227,7 @@ def parse_json_with_duplicates(raw_string): print("未找到有效的 JSON 内容。") return {} # 返回空字典 +#作为最后手段,因为它是统计{ }的数量,存在缺陷:如果字符串中包含'{ }' 会出错! def extract_first_json(s): """ 从字符串中提取第一个完整的 JSON 对象。如果 JSON 对象不完整,尝试补全缺失的括号。 @@ -327,7 +328,8 @@ def extract_content_from_json(input_string,flag=False): except json.JSONDecodeError: print("方法3(非法转义修复)解析失败。") # 所有方法均失败后,尝试使用 extract_first_json 作为最后手段 - print("尝试使用 extract_first_json 作为最后手段。") + print("尝试使用 extract_first_json 作为最后手段:") + print(input_string) fixed_json_final = extract_first_json(input_string) if fixed_json_final: try: diff --git a/flask_app/general/截取pdf_main.py b/flask_app/general/截取pdf_main.py index bda0e62..6959f7c 100644 --- a/flask_app/general/截取pdf_main.py +++ b/flask_app/general/截取pdf_main.py @@ -116,13 +116,13 @@ if __name__ == "__main__": # pdf_path=r"C:\Users\Administrator\Desktop\货物标\zbfiles\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件.pdf" # pdf_path = r"C:\Users\Administrator\Desktop\货物标\zbfiles\zbtest4_evaluation_method.pdf" # pdf_path = r"C:\Users\Administrator\Desktop\招标文件\招标02.pdf" - pdf_path=r"C:\Users\Administrator\Downloads\河北省承德监狱2025年供应站水果及干果采购项目招标文件(定).pdf" + pdf_path=r"C:\Users\Administrator\Desktop\工程\test\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.pdf" # input_path=r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf" - output_folder = r"C:\Users\Administrator\Desktop\货物\test" + output_folder = r"C:\Users\Administrator\Desktop\工程\test" # selections = [1, 4] # 仅处理 selection 4、1 # selections = [1, 2, 3, 5] - # files = truncate_pdf_multiple(pdf_path, output_folder, logger, 'goods', selections) - files = truncate_pdf_multiple(pdf_path, output_folder, logger, 'goods') + # files = truncate_pdf_multiple(pdf_path, output_folder, logger, 'goods', selections) #engineering + files = truncate_pdf_multiple(pdf_path, output_folder, logger, 'engineering') print(files) # print(files[-1]) # print(files[-2]) diff --git a/flask_app/general/读取文件/按页读取pdf.py b/flask_app/general/读取文件/按页读取pdf.py index 0834d2c..1378c4b 100644 --- a/flask_app/general/读取文件/按页读取pdf.py +++ b/flask_app/general/读取文件/按页读取pdf.py @@ -118,7 +118,7 @@ def save_extracted_text_to_txt(pdf_path, txt_path): if __name__ == '__main__': # file_path='D:\\flask_project\\flask_app\\static\\output\\output1\\648e094b-e677-47ce-9073-09e0c82af210\\ztbfile_tobidders_notice_part2.pdf' - file_path=r"C:\Users\Administrator\Desktop\fsdownload\80286859-52da-4b29-8396-52b3d104c32b\ztbfile.pdf" + file_path=r"C:\Users\Administrator\Downloads\2024-福建-2024年度防汛抢险物资储备.pdf" # file_path = r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf" # file_path = 'C:\\Users\\Administrator\\Desktop\\货物标\\截取test\\交警支队机动车查验监管系统项目采购_tobidders_notice_part1.pdf' # file_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest8.pdf" diff --git a/flask_app/general/通用功能函数.py b/flask_app/general/通用功能函数.py index 23fb30c..b563ec8 100644 --- a/flask_app/general/通用功能函数.py +++ b/flask_app/general/通用功能函数.py @@ -4,9 +4,32 @@ import logging import re from flask_app.general.json_utils import clean_json_string from flask_app.general.多线程提问 import multi_threading +from flask_app.general.通义千问long import upload_file, qianwen_long from flask_app.工程标.判断是否分包等 import read_questions_from_judge -def process_judge_questions(judge_file_path, chosen_numbers, file_id, baseinfo_list1): +def get_deviation_requirements(invalid_path): + file_id=upload_file(invalid_path) + user_query="""该招标文件对响应文件(投标文件)偏离项的要求或内容是怎样的?请不要回答具体的技术参数,也不要回答具体的评分要求。请以json格式给我提供信息,外层键名为'偏离',若存在嵌套信息,嵌套内容键名为文件中对应字段或是你的总结,而嵌套键值必须与原文保持一致,若文中未涉及相关内容,在键值中填'未知'。 +注意:不使用任何预设的示例作为回答,示例仅作为格式参考。 +禁止内容: + 确保所有输出内容均基于提供的实际招标文件内容,不使用任何预设的示例作为回答。 +示例1,嵌套键值对情况: +{ + "偏离":{ + "技术要求":"以★标示的内容不允许负偏离", + "商务要求":"以★标示的内容不允许负偏离" + } +} +示例2,无嵌套键值对情况: +{ + "偏离":"所有参数需在技术响应偏离表内响应,如应答有缺项,且无有效证明材料的,评标委员会有权不予认可,视同负偏离处理" +} + """ + model_res=qianwen_long(file_id,user_query) + return clean_json_string(model_res) + +def process_judge_questions(judge_file_path, chosen_numbers, invalid_path, baseinfo_list1): + file_id=upload_file(invalid_path) judge_questions = read_questions_from_judge(judge_file_path, chosen_numbers) judge_consortium = judge_consortium_bidding(baseinfo_list1) if judge_consortium: @@ -44,11 +67,10 @@ def aggregate_basic_info(baseinfo_list,mode="engineering"): "投标有效期", "信息公示媒介" ], - "保证金相关": ["质量保证金"], + "保证金相关": [], "其他信息": [ "重新招标、不再招标和终止招标", "投标费用承担", - "招标代理服务费", "是否退还投标文件", ] } @@ -114,16 +136,13 @@ def dynamic_key_handling(key_groups, detected_keys): # 确保"技术、服务要求"存在于"采购要求"组中 if "技术、服务要求" not in key_groups["采购要求"]: key_groups["采购要求"].insert(1, "技术、服务要求") - # 处理“保证金相关”组,插到"质量保证金"前 + # 处理“保证金相关”组 elif "保证金" in key: - group = key_groups["保证金相关"] - insert_before = "质量保证金" - if insert_before in group: - index = group.index(insert_before) - if key not in group: # 避免重复插入 - group.insert(index, key) - else: - group.append(key) # 如果没有找到特定键,则追加到末尾 + if "保证金相关" not in key_groups: + key_groups["保证金相关"] = [] + # 直接追加到 "保证金相关" 组的末尾 + if key not in key_groups["保证金相关"]: + key_groups["保证金相关"].append(key) elif "联合体" in key: key_groups["项目信息"].append(key) elif "分包" in key: diff --git a/flask_app/routes/工程标解析main.py b/flask_app/routes/工程标解析main.py index cb22f04..a946a27 100644 --- a/flask_app/routes/工程标解析main.py +++ b/flask_app/routes/工程标解析main.py @@ -71,6 +71,7 @@ def preprocess_files(output_folder, file_path, file_type,logger): invalid_deleted_docx=pdf2docx(invalid_path) merged_baseinfo_path=truncate_files[-1] more_path=[merged_baseinfo_path,tobidders_notice] + merged_baseinfo_path_more=os.path.join(output_folder,"merged_baseinfo_path_more.pdf") merged_baseinfo_path_more=merge_pdfs(more_path,merged_baseinfo_path_more) clause_path = convert_clause_to_json(tobidders_notice, output_folder) # 投标人须知正文条款pdf->json @@ -83,7 +84,6 @@ def preprocess_files(output_folder, file_path, file_type,logger): 'invalid_added_docx': invalid_added_docx, 'notice_path':notice_path, 'tobidders_notice_table': tobidders_notice_table, - 'tobidders_notice': tobidders_notice, 'evaluation_method':evaluation_method, 'qualification': qualification, 'merged_baseinfo_path':merged_baseinfo_path, @@ -92,7 +92,7 @@ def preprocess_files(output_folder, file_path, file_type,logger): } # 基本信息 -def fetch_project_basic_info(invalid_deleted_docx, merged_baseinfo_path, merged_baseinfo_path_more, tobidders_notice, clause_path, logger): +def fetch_project_basic_info(invalid_deleted_docx, merged_baseinfo_path, merged_baseinfo_path_more,clause_path, logger): logger.info("starting 基础信息...") start_time = time.time() try: @@ -100,9 +100,7 @@ def fetch_project_basic_info(invalid_deleted_docx, merged_baseinfo_path, merged_ merged_baseinfo_path = invalid_deleted_docx if not merged_baseinfo_path_more: merged_baseinfo_path_more = invalid_deleted_docx - if not tobidders_notice: - tobidders_notice = invalid_deleted_docx - basic_res = combine_basic_info(merged_baseinfo_path, merged_baseinfo_path_more, tobidders_notice, clause_path) + basic_res = combine_basic_info(merged_baseinfo_path, merged_baseinfo_path_more, clause_path,invalid_deleted_docx) result = basic_res end_time = time.time() logger.info(f"基础信息 done,耗时:{end_time - start_time:.2f} 秒") @@ -217,7 +215,7 @@ def engineering_bid_main(output_folder, file_path, file_type, unique_id): # 立即启动不依赖 knowledge_name 和 index 的任务 futures = { 'base_info': executor.submit(fetch_project_basic_info,processed_data['invalid_deleted_docx'] ,processed_data['merged_baseinfo_path'],processed_data['merged_baseinfo_path_more'], - processed_data['tobidders_notice'], processed_data['clause_path'],logger), + processed_data['clause_path'],logger), 'qualification_review': executor.submit(fetch_qualification_review, processed_data['evaluation_method'], processed_data['qualification'], output_folder, processed_data['tobidders_notice_table'], diff --git a/flask_app/static/提示词/基本信息工程标.txt b/flask_app/static/提示词/基本信息工程标.txt index cd1e3c1..d38713d 100644 --- a/flask_app/static/提示词/基本信息工程标.txt +++ b/flask_app/static/提示词/基本信息工程标.txt @@ -35,13 +35,17 @@ 9.该招标文件中对投标人(或供应商)准备和参加投标活动中发生的费用是如何规定的?请以json的格式给我提供信息,键名是'投标费用承担',若存在未知信息,在对应的键值中填'未知'。 -10.招标人(或招标代理机构)对招标文件的澄清(或答疑)的截止时间是?请以json的格式给我提供信息,键名是'澄清招标文件的截止时间',若存在未知信息,在对应的键值中填'未知'。 +10.根据提供的实际招标文件内容,招标人(或招标代理机构)对招标文件的澄清(或答疑)的截止时间是?请以json的格式给我提供信息,键名是'澄清招标文件的截止时间',键值为原文中有关澄清截止时间的相关表述,不一定是明确的时间节点。 +禁止内容: + 确保所有输出内容均基于提供的实际招标文件内容,不使用任何预设的示例作为回答。 +示例输出如下,仅供格式参考: +{ + "澄清招标文件的截止时间":"在投标截止时间至少 20 个日历日前" +} -11.该文档要求扣留的质量保证金百分比是多少,请以json格式给我提供信息,键名为'质量保证金',如果没有则以'未知'填充。 +11.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 -12.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 - -13.该项目的开标时间(或开启时间)和开标地点(或开启地点、开启方式)是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',键值为原文中相关内容的表述。 +12.该项目的开标时间(或开启时间)和开标地点(或开启地点、开启方式)是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',键值为原文中相关内容的表述。 对于'开标时间',若文中没有明确时间却有诸如'同投标截止时间'的表述,则将相关表述返回;若存在未知信息,在对应的键值中填'未知', 示例输出如下,仅供格式参考: { @@ -49,15 +53,4 @@ "开标地点":"线上开标" } -13.该招标文件对响应文件(投标文件)偏离项的要求或内容是怎样的?请不要回答具体的技术参数,也不要回答具体的评分要求。请以json格式给我提供信息,外层键名为'偏离',若存在嵌套信息,嵌套内容键名为文件中对应字段或是你的总结,而嵌套键值必须与原文保持一致,若存在未知信息,在对应的键值中填'未知'。示例输出如下,仅供格式参考: -示例1,嵌套键值对情况: -{ - "偏离":{ - "技术和服务要求":"以★标示的内容为不允许负偏离的实质性要求", - "商务要求":"以★标示的内容为不允许负偏离的实质性要求" - } -} -示例2,无嵌套键值对情况: -{ - "偏离":"所有参数需在技术响应偏离表内响应,如应答有缺项,且无有效证明材料的,评标委员会有权不予认可,视同负偏离处理" -} \ No newline at end of file +13.请你根据招标文件信息,回答以下问题:是否组织踏勘现场?是否召开投标预备会(或投标答疑会)?是否退还投标文件?是否允许分包? 是否需要递交投标保证金(或磋商保证金)?是否需要提交履约保证金(或履约担保)?是否有招标代理服务费(或中标、成交服务费或采购代理服务费)?是否需要提交质量保证金?请按json格式给我提供信息,键名分别为'是否组织踏勘现场','是否召开投标预备会'(或'是否召开投标答疑会'),'是否退还投标文件',是否允许分包','是否递交投标保证金'(或'是否递交磋商保证金'),'是否提交履约保证金','是否有招标代理服务费','是否有质量保证金',键值仅限于'是','否','未知',若存在前后矛盾或原文中未提及的信息,请回答'未知'。 diff --git a/flask_app/static/提示词/基本信息货物标.txt b/flask_app/static/提示词/基本信息货物标.txt index 6fbdd5b..1ba2c7b 100644 --- a/flask_app/static/提示词/基本信息货物标.txt +++ b/flask_app/static/提示词/基本信息货物标.txt @@ -35,13 +35,17 @@ 9.该招标文件中对投标人(或供应商)准备和参加投标活动中发生的费用是如何规定的?请以json的格式给我提供信息,键名是'投标费用承担',若存在未知信息,在对应的键值中填'未知'。 -10.招标人(或招标代理机构)对招标文件的澄清(或答疑)的截止时间是?请以json的格式给我提供信息,键名是'澄清招标文件的截止时间',若存在未知信息,在对应的键值中填'未知'。 +10.根据提供的实际招标文件内容,招标人(或招标代理机构)对招标文件的澄清(或答疑)的截止时间是?请以json的格式给我提供信息,键名是'澄清招标文件的截止时间',键值为原文中有关澄清截止时间的相关表述,不一定是明确的时间节点。 +禁止内容: + 确保所有输出内容均基于提供的实际招标文件内容,不使用任何预设的示例作为回答。 +示例输出如下,仅供格式参考: +{ + "澄清招标文件的截止时间":"在投标截止时间至少 20 个日历日前" +} -11.该文档要求扣留的质量保证金百分比是多少,请以json格式给我提供信息,键名为'质量保证金',如果没有则以'未知'填充。 +11.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 -12.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 - -13.该项目的开标时间(或开启时间)和开标地点(或开启地点、开启方式)是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',键值为原文中相关内容的表述。 +12.该项目的开标时间(或开启时间)和开标地点(或开启地点、开启方式)是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',键值为原文中相关内容的表述。 对于'开标时间',若文中没有明确时间却有诸如'同投标截止时间'的表述,则将相关表述返回;若存在未知信息,在对应的键值中填'未知'。 示例输出如下,仅供格式参考: { @@ -49,6 +53,9 @@ "开标地点":"线上开标" } +13.请你根据招标文件信息,回答以下问题:是否组织踏勘现场?是否召开投标预备会(或答疑会)?是否退还投标文件?是否允许分包? 是否需要递交投标保证金(或磋商保证金)?是否需要提交履约保证金(或履约担保)?是否有招标代理服务费(或中标、成交服务费或采购代理服务费)?是否需要提交质量保证金?请按json格式给我提供信息,键名分别为'是否组织踏勘现场','是否召开投标预备会'(或'是否召开投标答疑会'),'是否退还投标文件',是否允许分包','是否递交投标保证金'(或'是否递交磋商保证金'),'是否提交履约保证金','是否有招标代理服务费','是否有质量保证金',键值仅限于'是','否','未知',若存在前后矛盾或原文中未提及的信息,请回答'未知'。 + + diff --git a/flask_app/static/提示词/小解析基本信息工程标.txt b/flask_app/static/提示词/小解析基本信息工程标.txt index c477948..c585797 100644 --- a/flask_app/static/提示词/小解析基本信息工程标.txt +++ b/flask_app/static/提示词/小解析基本信息工程标.txt @@ -2,7 +2,7 @@ 2.该招标文件的项目概况(或工程概况)是?项目基本情况是?请按json格式给我提供信息,键名分别为'项目概况','项目基本情况',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 -3.该招标文件的招标控制价(或投标限价或项目预算金额)是?请按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 +3.该招标文件的招标控制价(或投标限价或项目预算金额)是?请返回具体的金额,并按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 4.投标文件递交截止日期是?请按json格式给我提供信息,键名是'投标文件递交截止日期',若原文未提及明确日期时间,请返回原文对应的内容,若存在未知信息,在对应的键值中填'未知'。示例输出格式如下: { diff --git a/flask_app/static/提示词/小解析基本信息货物标.txt b/flask_app/static/提示词/小解析基本信息货物标.txt index 8898519..4348996 100644 --- a/flask_app/static/提示词/小解析基本信息货物标.txt +++ b/flask_app/static/提示词/小解析基本信息货物标.txt @@ -2,7 +2,7 @@ 2.该招标文件的项目概况是?项目基本情况是?请按json格式给我提供信息,键名分别为'项目概况','项目基本情况',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,而嵌套键值必须与原文保持一致,若存在未知信息,在对应的键值中填'未知'。 -3.该招标文件的最高限价(或招标控制价或预算金额)是?请按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 +3.该招标文件的最高限价(或招标控制价或预算金额)是?请返回具体的金额,并按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 4.投标文件(或响应文件)递交截止时间是?请按json格式给我提供信息,键名是'投标文件递交截止日期',若原文未提及明确日期时间,请返回原文对应的内容,若存在未知信息,在对应的键值中填'未知'。示例输出格式如下: { diff --git a/flask_app/static/提示词/是否相关问题.txt b/flask_app/static/提示词/是否相关问题.txt index 9e5acfc..d7f2f5b 100644 --- a/flask_app/static/提示词/是否相关问题.txt +++ b/flask_app/static/提示词/是否相关问题.txt @@ -1,16 +1,81 @@ 1.该招标文件对于分包的要求是怎样的?请按json格式给我提供信息,键名为'分包',若需要以嵌套键值对返回结果,那么嵌套键名为原文中的子标题或是你对相应要求的总结,而对应键值需要完全与原文保持一致。 -2.该项目的投标保证金(或磋商保证金)的内容或要求是什么?请按json格式给我提供信息,外层键名为"投标保证金"(或"磋商保证金"),若需要以嵌套键值对返回结果,那么嵌套键名为原文中的子标题或是你对相应要求的总结,而对应键值需要完全与原文保持一致。 +2.根据提供的实际招标文件内容,提取“投标保证金”(或“磋商保证金”)的具体要求,并以JSON格式返回。JSON的外层键名固定为“投标保证金”(或“磋商保证金”),必须包含以下内容: + -至少包含一个嵌套键名'金额',用于说明需要递交的具体投标保证金额或是相对金额百分比描述; + -如果文件中有其他关于投标保证金的内容及要求,也需将其逐一提取为嵌套键值对。嵌套键的键名可直接使用原文中的子标题,或基于内容进行简要总结;对应的键值必须与原文表述一致,不得修改或添加解释。 +禁止内容: + 确保所有输出内容均基于提供的实际招标文件内容,不使用任何预设的示例作为回答。 +示例输出如下,仅供格式参考: +{ + "投标保证金":{ + "金额":50000, + "递交方式":"1)采用现金方式。2)银行转账" + } +} -3.该招标文件对于投标保证金的退还相关的规章办法是怎样的?请按json格式给我提供信息,键名为'退还投标保证金',示例输出格式如下: +3.该招标文件对于投标保证金的退还相关的规章办法是怎样的?请按json格式给我提供信息,键名为'退还投标保证金',示例输出如下,仅供格式参考: { "退还投标保证金":"投标保证金的退还按《xxx》相关条款执行。" } -4.该项目对于履约保证金(履约担保)的内容或要求是怎样的,请按json格式给我提供信息,外层键名为"履约保证金",若需要以嵌套键值对返回结果,那么嵌套键名为原文中的子标题或是你对相应要求的总结,而对应键值需要完全与原文保持一致。 +4.根据提供的实际招标文件内容,提取“履约保证金”(或“履约担保”)的具体要求,并以JSON格式返回。JSON的外层键名固定为“履约保证金”,必须包含以下内容: + -至少包含一个嵌套键名'金额',用于说明需要递交的具体履约保证金额或相对金额的百分比描述; + -如果文件中有其他关于履约保证金的要求,也需将其逐一提取为嵌套键值对。嵌套键的键名可直接使用原文中的子标题,或基于内容进行简要总结;对应的键值必须与原文表述完全一致,不得修改或添加解释。 +禁止事项: + 确保所有输出内容均基于实际提供的招标文件内容; + 不得凭空假设或使用任何预设示例作为答案。 +示例输出如下,仅供格式参考: +{ + "履约保证金":{ + "金额":10000元, + "递交方式":"1)采用现金方式。2)银行转账" + } +} -5.本项目的招标代理服务费(或中标服务费、成交服务费、采购代理服务费)的相关内容是怎样的,请按json格式给我提供信息,外层键名为'招标代理服务费',若需要以嵌套键值对返回结果,那么嵌套键名为原文中的子标题或是你对相应要求的总结,而对应键值需要完全与原文保持一致。 +5.根据提供的实际招标文件内容,提取招标代理服务费(或中标服务费、成交服务费、采购代理服务费)的具体内容,并以JSON格式返回。外层键名为'招标代理服务费',必须包含以下内容: + -至少包含一个嵌套键名'金额',用于说明需要递交的具体招标代理服务费金额或是相对金额百分比描述; + -如果文件中有其他关于招标代理服务费的内容及要求,也需将其逐一提取为嵌套键值对。嵌套键的键名可直接使用原文中的子标题,或基于内容进行简要总结;对应的键值必须与原文表述一致,不得修改或添加解释。 +禁止内容: + 确保所有输出内容均基于提供的实际招标文件内容,不使用任何预设的示例作为回答。 +示例输出如下,仅供格式参考: +{ + "招标代理服务费":{ + "金额":10000, + "递交方式":"1)采用现金方式。2)银行转账" + } +} 6.该招标文件对于踏勘现场的内容或要求是怎样的,请按json格式给我提供信息,外层键名为"踏勘现场",若需要以嵌套键值对返回结果,那么嵌套键名为原文中的子标题或是你对相应要求的总结,而对应键值需要完全与原文保持一致。 7.该招标文件对于投标预备会(或投标答疑会)内容是怎样的,请按json格式给我提供信息,外层键名为"投标预备会"(或"投标答疑会"),若需要以嵌套键值对返回结果,那么嵌套键名为原文中的子标题或是你对相应要求的总结,而对应键值需要完全与原文保持一致。 + +8.根据提供的实际招标文件内容,提取“质量保证金”的具体内容,并以JSON格式返回。JSON的外层键名固定为“质量保证金”,必须包含以下内容: + -至少包含一个嵌套键名'金额',用于说明需要递交的具体履约保证金额或相对金额的百分比描述; + -如果文件中有其他关于履质量保证金的要求,也需将其逐一提取为嵌套键值对。嵌套键的键名可直接使用原文中的子标题,或基于内容进行简要总结;对应的键值必须与原文表述完全一致,不得修改或添加解释。 +禁止事项: + 确保所有输出内容均基于实际提供的招标文件内容; + 不得凭空假设或使用任何预设示例作为答案。 +示例输出如下,仅供格式参考: +{ + "履约保证金":{ + "金额":10000元, + "递交方式":"1)采用现金方式。2)银行转账" + } +} + +9.该招标文件对响应文件(投标文件)偏离项的要求或内容是怎样的?请不要回答具体的技术参数,也不要回答具体的评分要求。请以json格式给我提供信息,外层键名为'偏离',若存在嵌套信息,嵌套内容键名为文件中对应字段或是你的总结,而嵌套键值必须与原文保持一致,若文中未涉及相关内容,在键值中填'未知'。 +注意:不使用任何预设的示例作为回答,示例仅作为格式参考。 +禁止内容: + 确保所有输出内容均基于提供的实际招标文件内容; + 不使用任何预设的示例作为回答。 +示例1,嵌套键值对情况: +{ + "偏离":{ + "技术要求":"以★标示的内容不允许负偏离", + "商务要求":"以★标示的内容不允许负偏离" + } +} +示例2,无嵌套键值对情况: +{ + "偏离":"所有参数需在技术响应偏离表内响应,如应答有缺项,且无有效证明材料的,评标委员会有权不予认可,视同负偏离处理" +} diff --git a/flask_app/工程标/判断是否分包等.py b/flask_app/工程标/判断是否分包等.py index b38bd5b..e161b45 100644 --- a/flask_app/工程标/判断是否分包等.py +++ b/flask_app/工程标/判断是否分包等.py @@ -48,24 +48,24 @@ def construct_judge_questions(json_data): def merge_json_to_list(merged,tobidders_notice=""): - # print(json.dumps(merged,ensure_ascii=False,indent=4)) """Merge updates into the original data by modifying specific keys based on their value ('是' or '否'), and create a list based on these values.""" chosen_numbers = [] + chosen_numbers.append(9) - # 定义键名映射 - key_mapping = { - '是否允许分包': '分包', - '是否递交投标保证金': '投标保证金', - '是否递交磋商保证金': '磋商保证金', - '是否提交履约保证金': '履约保证金', - '是否有招标代理服务费': '招标代理服务费', - '是否组织踏勘现场': '踏勘现场', - '是否召开投标预备会': '投标预备会', - '是否召开投标答疑会': '投标答疑会', - '是否允许偏离': '偏离', - '是否退还投标文件':'退还投标文件' - } + # # 定义键名映射 + # key_mapping = { + # '是否允许分包': '分包', + # '是否递交投标保证金': '投标保证金', + # '是否递交磋商保证金': '磋商保证金', + # '是否提交履约保证金': '履约保证金', + # '是否有招标代理服务费': '招标代理服务费', + # '是否组织踏勘现场': '踏勘现场', + # '是否召开投标预备会': '投标预备会', + # '是否召开投标答疑会': '投标答疑会', + # '是否允许偏离': '偏离', + # '是否退还投标文件':'退还投标文件' + # } # 处理是否允许分包 if merged.get('是否允许分包') == '是': @@ -82,12 +82,14 @@ def merge_json_to_list(merged,tobidders_notice=""): guarantee_type = '投标' if '投标' in guarantee_key else '磋商' if merged[guarantee_key] == '是': chosen_numbers.extend([2, 3]) + merged.pop(guarantee_key, None) elif merged[guarantee_key] == '否': merged[f'{guarantee_type}保证金'] = '不提交' + merged.pop(guarantee_key, None) merged[f'退还{guarantee_type}保证金'] = '/' else: # 当既不是 '是' 也不是 '否' 时执行 - merged[f'退还{guarantee_type}保证金'] = '未知' + merged[f'是否退还{guarantee_type}保证金'] = '未知' guarantee_processed = True elif guarantee_key in merged and guarantee_processed: merged.pop(guarantee_key, None) @@ -119,16 +121,28 @@ def merge_json_to_list(merged,tobidders_notice=""): # 处理预备会/答疑会 meeting_processed = False for preparation_key in ['是否召开投标预备会', '是否召开投标答疑会']: - if preparation_key in merged: - if not meeting_processed: - if merged[preparation_key] == '是': - chosen_numbers.append(7) - elif merged[preparation_key] == '否': - meeting_type = '预备会' if '预备会' in preparation_key else '答疑会' - merged[f'投标{meeting_type}'] = '不召开' - meeting_processed = True + if preparation_key in merged and not meeting_processed: + meeting_type = '预备会' if '预备会' in preparation_key else '答疑会' + if merged[preparation_key] == '是': + chosen_numbers.append(7) + merged.pop(preparation_key,None) + elif merged[preparation_key] == '否': + merged[f'投标{meeting_type}'] = '不召开' + merged.pop(preparation_key, None) + else: + # 当既不是 '是' 也不是 '否' 时执行 + merged[f'是否召开投标{meeting_type}'] = '未知' + meeting_processed = True + elif preparation_key in merged and meeting_processed: merged.pop(preparation_key, None) + if merged.get('是否有质量保证金') == '是': + chosen_numbers.append(8) + merged.pop('是否有质量保证金',None) + elif merged.get('是否有质量保证金') == '否': + merged['质量保证金']='不提交' + merged.pop('是否有质量保证金',None) + #11.12这里有问题,正文部分的信息重要性不高,而且这里的key不好设置,先注释了 # # 初始化 questions_list # questions_list = [] @@ -158,6 +172,8 @@ def merge_json_to_list(merged,tobidders_notice=""): # for info in baseinfo_list: # merged.update(info) + # print(chosen_numbers) + # print(json.dumps(merged,ensure_ascii=False,indent=4)) return chosen_numbers, merged def read_questions_from_judge(file_path, indices): diff --git a/flask_app/工程标/基础信息整合工程标.py b/flask_app/工程标/基础信息整合工程标.py index 497ee03..ef37e05 100644 --- a/flask_app/工程标/基础信息整合工程标.py +++ b/flask_app/工程标/基础信息整合工程标.py @@ -32,25 +32,25 @@ def update_baseinfo_lists(baseinfo_list1, baseinfo_list2): return updated_list #先不带投标人须知正文,如果是未知,再直接问正文, -def process_baseinfo_list(baseinfo_list, tobidders_notice): +def process_baseinfo_list(baseinfo_list, merged_baseinfo_path): questions_list = [] for item in baseinfo_list: # print(json.dumps(item, ensure_ascii=False, indent=4)) for key, value in item.items(): if value == "未知" or (isinstance(value, dict) and all(v == "未知" for v in value.values())): question = ( - f"根据该招标文件中的信息,{key}的内容是怎样的?请按json格式给我提供信息,键名是'{key}',若存在嵌套信息,嵌套内容键名以文件中对应字段命名(或是你对相应要求的总结),而对应键值需要与原文保持一致。注意:默认情况用普通键值对返回结果即可,键名为{key};若原文中未提及'{key}'相关内容,在键值中填'未知'。" + f"根据该招标文件中的信息,{key}的内容是怎样的?请按json格式给我提供信息,键名是'{key}',若存在嵌套信息,嵌套内容键名以文件中对应字段命名(或是你对相应要求的总结),而对应键值需要与原文保持一致。注意:默认情况用普通键值对返回结果即可,外层键名为{key};若原文中未提及'{key}'相关内容,在键值中填'未知'。" ) questions_list.append(question) if questions_list: - file_id = upload_file(tobidders_notice) + file_id = upload_file(merged_baseinfo_path) baseinfo_results = multi_threading(questions_list, "", file_id, 2) return [clean_json_string(res) for _, res in baseinfo_results] if baseinfo_results else [] else: return [] -def combine_basic_info(merged_baseinfo_path,merged_baseinfo_path_more, tobidders_notice, clause_path): +def combine_basic_info(merged_baseinfo_path,merged_baseinfo_path_more, clause_path,invalid_path): """ 综合和处理基础信息,生成最终的基础信息字典。 @@ -67,8 +67,6 @@ def combine_basic_info(merged_baseinfo_path,merged_baseinfo_path_more, tobidders baseinfo_prompt_file_path = 'flask_app/static/提示词/基本信息工程标.txt' file_id = upload_file(merged_baseinfo_path) questions = read_questions_from_file(baseinfo_prompt_file_path) - more_query = "请你根据招标文件信息,回答以下问题:是否组织踏勘现场?是否召开投标预备会(或投标答疑会)?是否退还投标文件?是否允许分包? 是否需要递交投标保证金?是否需要提交履约保证金(履约担保)?是否有招标代理服务费?请按json格式给我提供信息,键名分别为'是否组织踏勘现场','是否召开投标预备会','是否退还投标文件',是否允许分包','是否递交投标保证金','是否提交履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知',若存在矛盾信息,请回答'未知'。" - questions.append(more_query) baseinfo_results = multi_threading(questions, "", file_id, 2) baseinfo_list1 = [clean_json_string(res) for _, res in baseinfo_results] if baseinfo_results else [] chosen_numbers, merged = merge_json_to_list(baseinfo_list1.pop()) @@ -79,8 +77,8 @@ def combine_basic_info(merged_baseinfo_path,merged_baseinfo_path_more, tobidders with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: # 提交两个任务 - future1 = executor.submit(process_judge_questions, judge_file_path, chosen_numbers, file_id, baseinfo_list1) - future2 = executor.submit(process_baseinfo_list, baseinfo_list1_copy, tobidders_notice) #只问tobidders_notice + future1 = executor.submit(process_judge_questions, judge_file_path, chosen_numbers, invalid_path, baseinfo_list1) + future2 = executor.submit(process_baseinfo_list, baseinfo_list1_copy, merged_baseinfo_path) #未知的内容再问一次 future3 = executor.submit(extract_from_notice, merged_baseinfo_path_more, clause_path, 3) # 新增的多线程任务 # 等待两个任务完成并获取结果 @@ -88,23 +86,24 @@ def combine_basic_info(merged_baseinfo_path,merged_baseinfo_path_more, tobidders baseinfo_list2 = future2.result() rebidding_situation = future3.result() # 获取提取失败的情况 - updated_list = update_baseinfo_lists(baseinfo_list1, baseinfo_list2) + updated_baseinfo_list = update_baseinfo_lists(baseinfo_list1, baseinfo_list2) update_json = add_outer_key(rebidding_situation, "重新招标、不再招标和终止招标") - updated_list.append(update_json) - aggregated_baseinfo = aggregate_basic_info(updated_list) + updated_baseinfo_list.append(update_json) + aggregated_baseinfo = aggregate_basic_info(updated_baseinfo_list) return {"基础信息": aggregated_baseinfo} if __name__ == "__main__": start_time = time.time() - merged_baseinfo_path = r"D:\flask_project\flask_app\static\output\output1\6f2010ee-d7cd-4787-a26a-2db8233d179a\ztbfile_merged_baseinfo.pdf" - more=r"D:\flask_project\flask_app\static\output\output1\6f2010ee-d7cd-4787-a26a-2db8233d179a\merged_baseinfo_path_more.pdf" + merged_baseinfo_path = r"C:\Users\Administrator\Desktop\工程\test\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.docx" + more=r"C:\Users\Administrator\Desktop\工程\test\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.docx" # output_folder="C:\\Users\\Administrator\\Desktop\\招标文件\\special_output" tobidders_notice_table = r"D:\flask_project\flask_app\static\output\output1\6f2010ee-d7cd-4787-a26a-2db8233d179a\ztbfile_tobidders_notice_table.pdf" - tobidders_notice = r"D:\flask_project\flask_app\static\output\output1\6f2010ee-d7cd-4787-a26a-2db8233d179a\ztbfile_tobidders_notice.pdf" - clause_path = r"D:\flask_project\flask_app\static\output\output1\6f2010ee-d7cd-4787-a26a-2db8233d179a\clause1.json" - res = combine_basic_info(merged_baseinfo_path,more,tobidders_notice, clause_path) + # tobidders_notice = r"D:\flask_project\flask_app\static\output\output1\6f2010ee-d7cd-4787-a26a-2db8233d179a\ztbfile_tobidders_notice.pdf" + clause_path = "" + invalid_path=r"C:\Users\Administrator\Desktop\工程\test\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.docx" + res = combine_basic_info(merged_baseinfo_path,more, clause_path,invalid_path) print(json.dumps(res, ensure_ascii=False, indent=4)) end_time = time.time() print("elapsed_time:" + str(end_time - start_time)) diff --git a/flask_app/工程标/截取pdf工程标版.py b/flask_app/工程标/截取pdf工程标版.py index 302dd01..bd7a9f3 100644 --- a/flask_app/工程标/截取pdf工程标版.py +++ b/flask_app/工程标/截取pdf工程标版.py @@ -1,24 +1,26 @@ -#flask_app/工程标/截取pdf工程标版.py +# flask_app/工程标/截取pdf工程标版.py import regex import os import time from PyPDF2 import PdfReader, PdfWriter from flask_app.general.clean_pdf import clean_page_content -from flask_app.general.截取pdf通用函数 import get_start_and_common_header,save_extracted_pages,get_invalid_file +from flask_app.general.截取pdf通用函数 import get_start_and_common_header, save_extracted_pages, get_invalid_file from flask_app.general.通用功能函数 import get_global_logger + def extract_pages_tobidders_notice(pdf_path, output_folder, begin_pattern, begin_page, common_header, is_secondary_match): pdf_document = PdfReader(pdf_path) - exclusion_pattern = regex.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据|文件组成|文件构成|文件的编制|文件编制') + exclusion_pattern = regex.compile( + r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据|文件组成|文件构成|文件的编制|文件编制') def run_extraction(): start_page = None mid_page = None end_page = None chapter_type = None # 用于存储“章”或“部分” - combined_mid_pattern= None + combined_mid_pattern = None end_pattern = None for i, page in enumerate(pdf_document.pages): @@ -130,8 +132,9 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter pdf_document = PdfReader(pdf_path) start_page = None end_page = None - flag=True - exclusion_pattern = regex.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据|文件组成|文件构成|文件的编制|文件编制') + flag = True + exclusion_pattern = regex.compile( + r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据|文件组成|文件构成|文件的编制|文件编制') # 遍历文档的每一页,查找开始和结束短语的位置 for i in range(len(pdf_document.pages)): page = pdf_document.pages[i] @@ -139,14 +142,15 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter if text: cleaned_text = clean_page_content(text, common_header) if flag and regex.search(exclusion_pattern, cleaned_text): - flag=False + flag = False continue if regex.search(begin_pattern, cleaned_text) and i >= begin_page: if start_page and (output_suffix == "notice" or output_suffix == "invalid"): pass else: start_page = i - if start_page is not None and regex.search(end_pattern, cleaned_text) and not regex.search(begin_pattern,cleaned_text): + if start_page is not None and regex.search(end_pattern, cleaned_text) and not regex.search(begin_pattern, + cleaned_text): condition = i > start_page if condition: is_invalid_condition = output_suffix == "invalid" and i > 30 # 这边默认无效投标至少有30页 @@ -172,7 +176,8 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header, l if output_suffix == "qualification": print("twice:qualificaiton!") # 动态设置 include_keys - include_keys = ["资格审查", "资质审查", "符合性审查", "资格性检查", "符合性检查","资格检查","能力","信誉"] + include_keys = ["资格审查", "资质审查", "符合性审查", "资格性检查", "符合性检查", "资格检查", "能力", + "信誉"] # 定义起始匹配模式,仅匹配“附录”、“附件”或“附表” begin_pattern = r'^(?:附录(?:[一1])?[::]|附件(?:[一1])?[::]|附表(?:[一1])?[::])' # 定义结束匹配模式 - 附录、附件、附表等(移除负向前瞻) @@ -221,7 +226,8 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header, l print(f"{output_suffix} twice: 未找到起始或结束页在文件 {pdf_path} 中!") return [] else: - return [save_extracted_pages(pdf_path, output_folder, start_page, end_page, output_suffix, common_header)] + return [ + save_extracted_pages(pdf_path, output_folder, start_page, end_page, output_suffix, common_header)] else: print(f"{output_suffix} twice: 未定义的输出后缀。") return [] @@ -230,7 +236,7 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header, l return [] -def truncate_pdf_main_engineering(input_path, output_folder, selection,logger,output_suffix="default"): +def truncate_pdf_main_engineering(input_path, output_folder, selection, logger, output_suffix="default"): try: # 内嵌的处理单个文件的函数 def process_single_file(file_path, output_folder, selection): @@ -284,7 +290,8 @@ def truncate_pdf_main_engineering(input_path, output_folder, selection,logger,ou r'(?:[\u4e00-\u9fff、()()]*?)' r'(?=.*(?:(?:磋商|谈判)(?=.*(?:办法|方法|内容))|(?:评标|评定|评审)))' # 修改的部分 r'[\u4e00-\u9fff、()()]*\s*$|' - r'^\s*(?