From f826a3f7a123685a318265fcabdf14a4ac8fad5c Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Thu, 5 Sep 2024 15:47:29 +0800 Subject: [PATCH] =?UTF-8?q?9.5=20=E8=B5=84=E6=A0=BC=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E9=83=A8=E5=88=86=E8=BF=94=E5=9B=9E=E6=9B=B4=E8=83=BD=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E5=8E=9F=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/main/截取pdf.py | 52 ++++++++----- flask_app/main/无效标和废标和禁止投标整合.py | 1 - flask_app/main/资格评审.py | 79 +++++++++++++++----- flask_app/static/提示词/资格评审.txt | 19 +++++ flask_app/static/提示词/资格评审问题.txt | 2 +- 5 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 flask_app/static/提示词/资格评审.txt diff --git a/flask_app/main/截取pdf.py b/flask_app/main/截取pdf.py index b9b2896..6973c05 100644 --- a/flask_app/main/截取pdf.py +++ b/flask_app/main/截取pdf.py @@ -39,44 +39,60 @@ def save_pages_to_new_pdf(pdf_path, output_folder, output_suffix, start_page, en print("提供的页码范围无效。") return output_pdf_path -def extract_pages_twice(pdf_path, output_folder, output_suffix): #第一次截取失败 +def extract_pages_twice(pdf_path, output_folder, output_suffix): + last_begin_index = 0 + begin_pattern = re.compile(r'第[一二三四五六七八九十]+章\s*招标公告|第一卷') + pdf_document = PdfReader(pdf_path) + for i, page in enumerate(pdf_document.pages): + text = page.extract_text() + if text: + cleaned_text = clean_page_numbers(text) + # 检查“第一章”开始的位置 + if begin_pattern.search(cleaned_text): + last_begin_index = i # 更新最后匹配的索引,页码从0开始 + print(last_begin_index) if output_suffix == "qualification": common_pattern = r'^(?:附录(?:一)?[::]|附件(?:一)?[::]|附表(?:一)?[::])' - end_pattern = r'^(第[一二三四五六七八九十]+章\s*投标人须知|评标办法|评标办法前附表)' - pdf_document = PdfReader(pdf_path) + # end_pattern = r'^(第[一二三四五六七八九十]+章\s*投标人须知|评标办法|评标办法前附表)' + end_pattern = re.compile( + common_pattern + r'(?!.*(?:资质|能力|信誉)).*$|' # 排除资质、能力、信誉的描述 + r'^(第[一二三四五六七八九十]+章\s*评标办法|评标办法前附表|投标人须知)', # 新增的匹配项 + re.MULTILINE + ) start_page = None end_page = None - for i, page in enumerate(pdf_document.pages): + # 从章节开始后的位置进行检查 + for i, page in enumerate(pdf_document.pages[last_begin_index:], start=last_begin_index): text = page.extract_text() if text: cleaned_text = clean_page_numbers(text) - - # 如果还未找到起始页,检查当前页是否满足条件作为起始页 - if start_page is None and "资格审查" in cleaned_text: + # 确定起始页,需在last_begin_index之后 + if ("资格审查" in cleaned_text or "资质条件" in cleaned_text): if re.search(common_pattern, cleaned_text, re.MULTILINE): - start_page = i # 存储符合条件的页码,页码从1开始 - - # 检查当前页是否满足条件作为结束页 - if start_page is not None and re.search(end_pattern, cleaned_text, re.MULTILINE): + if start_page is None: + start_page = i # 确保起始页不小于章节的开始页码 + # 确定结束页 + if start_page is not None and re.search(end_pattern, cleaned_text): if i > start_page: - end_page = i # 存储符合条件的结束页码,页码从1开始 - break # 可选:找到结束页后退出循环 + end_page = i + break # 找到结束页后退出循环 if start_page is None or end_page is None: print(f"未找到起始或结束页在文件 {pdf_path} 中!") return "" else: - return save_pages_to_new_pdf(pdf_path,output_folder,output_suffix,start_page,end_page) + return save_pages_to_new_pdf(pdf_path, output_folder, output_suffix, start_page, end_page) elif output_suffix == "invalid": pdf_document = PdfReader(pdf_path) total_pages = len(pdf_document.pages) # 计算总页数的三分之二 total = int(total_pages * 2 / 3) - start_page=1 + start_page = last_begin_index end_page = min(90, total) return save_pages_to_new_pdf(pdf_path, output_folder, output_suffix, start_page, end_page) + def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix): # 打开PDF文件 pdf_document = PdfReader(pdf_path) @@ -192,9 +208,9 @@ def truncate_pdf_multiple(input_path, output_folder): #TODO:需要完善二次请求。目前invalid一定能返回 前附表 须知正文如果为空的话要额外处理一下,比如说就不进行跳转(见xx表) 开评定标这里也要考虑 如果评分表为空,也要处理。 if __name__ == "__main__": - input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹" - output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹" + input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标02.pdf" + output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件" # truncate_pdf_multiple(input_path,output_folder) - selection = 5 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-资格审查条件 5-无效标 + selection = 4 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-资格审查条件 5-无效标 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 dbc2b9e..c08772c 100644 --- a/flask_app/main/无效标和废标和禁止投标整合.py +++ b/flask_app/main/无效标和废标和禁止投标整合.py @@ -292,7 +292,6 @@ def combine_find_invalid(file_path, output_dir, truncate_json_path,clause_path,t return nest_json_under_key(combined_dict, "无效标与废标项") -#TODO:1.运行时间约80s,如果成为短板需要优化多线程 if __name__ == '__main__': start_time = time.time() truncate_json_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\truncate_output.json" diff --git a/flask_app/main/资格评审.py b/flask_app/main/资格评审.py index 7c59dcf..1d94dff 100644 --- a/flask_app/main/资格评审.py +++ b/flask_app/main/资格评审.py @@ -1,9 +1,10 @@ # -*- encoding:utf-8 -*- #资格审查中,首先排除'联合体投标'和'不得存在的情况',有'符合'等的,加入matching_keys列表,否则保留原字典 +import json import re -from flask_app.main.json_utils import clean_json_string, combine_json_results, add_keys_to_json -from flask_app.main.多线程提问 import multi_threading +from flask_app.main.json_utils import clean_json_string, combine_json_results, add_keys_to_json, nest_json_under_key +from flask_app.main.多线程提问 import multi_threading,read_questions_from_file from flask_app.main.通义千问long import upload_file def merge_dictionaries_under_common_key(dicts, common_key): @@ -31,7 +32,7 @@ def generate_qual_question(matching_keys_list): #这里假设资质、信誉 # 构造完整的问题语句 question1 = (f"该招标文件中资格评审的内容是怎样的?具体内容包括{keys_string}," "请你以json格式返回结果,外层键名为'资格评审',嵌套键名为具体的字段,请你忠于原文,回答要求完整准确,不要擅自总结、删减。") - question2="该招标文件中资格评审中有关人员资格的要求是怎样的?请依次给出所需的岗位、需要的数量、资格要求、需要提交的证明材料(如具体的社保证明、技能证书等,若有时间要求请注明时间范围)、在岗要求、备注,若相关要求不存在,则以“未知”填充。请你以json格式返回结果,外层键名为'资格评审',嵌套键名为具体的要求,请你忠于原文,回答要求完整准确,不要擅自总结、删减。" + question2="该招标文件中资格评审中有关人员资格的要求是怎样的?请依次给出所需的岗位、需要的数量、资格要求、需要提交的证明材料(如具体的社保证明、技能证书等,若有时间要求请注明时间范围)、在岗要求、备注,若相关要求不存在,则无需返回该键值对。请你以json格式返回结果,外层键名为'资格评审',嵌套键名为具体的要求,请你忠于原文,回答要求完整准确,不要擅自总结、删减。" questions.append(question1) questions.append(question2) return questions @@ -45,6 +46,8 @@ def extract_matching_keys_qual(dict_data): excludes = ['联合体', '禁止投标', '不存在', '不得存在','资格','管理机构','负责人','人员'] #联合体、禁止投标的情况、人员需要额外问 # 遍历字典中的每个键值对 for key, value in dict_data.items(): + if "附件" in key and "资质" in key: + return [], [] # 如果同时出现,立即返回空列表 # 检查值是否符合任何一个包含模式 if any(pattern.search(value) for pattern in include_patterns): # 如果值符合包含模式,再检查键是否包含任何排除项 @@ -61,7 +64,7 @@ def extract_matching_keys_qual(dict_data): def get_consortium_dict(knowledge_name): qualify_list = [] consortium_questions = [ - "该招标文件对于联合体投标的要求是怎样的,请按json格式给我提供信息,外层键名为'联合体投标要求(如有)'"] + "该招标文件对于联合体投标的要求是怎样的,请按json格式给我提供信息,外层键名为'联合体投标要求(如有)',嵌套键名为你对该要求的总结,而键值需要完全与原文保持一致,不要擅自总结、删减。"] results1 = multi_threading(consortium_questions, knowledge_name) for _, response in results1: # _占位,代表ques;response[0]也是ques;response[1]是ans try: @@ -74,29 +77,68 @@ def get_consortium_dict(knowledge_name): consortium_dict = combine_json_results(qualify_list) return consortium_dict +def get_all_dict(knowledge_name): + qualification_review_file_path = '../static/提示词/资格评审.txt' # 替换为你的txt文件路径 + # # qualification_review_file_path='flask_app/static/提示词/资格评审问题.txt' + questions = read_questions_from_file(qualification_review_file_path) + qualification_list = [] + res1 = multi_threading(questions, knowledge_name) + for _, response in res1: # _占位,代表ques;response[0]也是ques;response[1]是ans + try: + if response and len(response) > 1: # 检查response存在且有至少两个元素 + qualification_list.append(response[1]) + else: + print(f"Warning: Missing or incomplete response data for query index {_}.") + except Exception as e: + print(f"Error processing response for query index {_}: {e}") + qualification_combined_res = combine_json_results(qualification_list) + return {'资格评审': qualification_combined_res} def process_qualification(qualification_review,truncate3,knowledge_name): # 资格评审 matching_keys_list, non_matching_dict = extract_matching_keys_qual(qualification_review) if not matching_keys_list: #此时要求全部写在评分办法前附表中,不需要额外提取。 - if not non_matching_dict: - print("error!") + if not non_matching_dict: #古法提取 + if truncate3!="": + print("type1") + matching_keys_list=["资质条件","财务要求","业绩要求","信誉要求","其他要求"] + ques=generate_qual_question(matching_keys_list) + file_id2 = upload_file(truncate3) + results2 = multi_threading(ques, "", file_id2, 2) # 资格评审表,调用qianwen-long + res_list = [] + if not results2: + print("未调用大模型询问资格评审文件要求!") + else: + # 打印结果 + for question, response in results2: + cleaned_res = clean_json_string(response) + res_list.append(cleaned_res) # 都是问资格评审表得出的 + merged_dict = merge_dictionaries_under_common_key(res_list, '资格评审') + consortium_dict = get_consortium_dict(knowledge_name) + updated_qualify_json = add_keys_to_json(merged_dict, consortium_dict) # 合并字典 + return updated_qualify_json + else: + print("type2") + return get_all_dict(knowledge_name) + else: + print("type3") + new_non_matching_json={'资格评审':non_matching_dict} substring = '联合体' - found_key = any(substring in key for key in non_matching_dict.keys()) #没有联合体投标,则需生成 + found_key = any(substring in key for key in non_matching_dict.keys()) #没有联合体投标,则需生成,防止重复 if not found_key: consortium_dict=get_consortium_dict(knowledge_name) - final_qualify_json = add_keys_to_json(consortium_dict, non_matching_dict) + final_qualify_json = add_keys_to_json(new_non_matching_json, consortium_dict) return final_qualify_json else: - return non_matching_dict + return new_non_matching_json elif matching_keys_list and truncate3=="": #这种情况是评分办法前附表中有要求,但是没有正确截取到'资格审查表' - #non_matching_dict 首先提取企业信息 施工设施。 - A=1 - #rag - # qualification_review_file_path = '../static/提示词/资格评审问题.txt' # 替换为你的txt文件路径 - # # qualification_review_file_path='flask_app/static/提示词/资格评审问题.txt' + print("type4") + final_qualification=get_all_dict(knowledge_name) + final_qualify_json = add_keys_to_json(final_qualification, non_matching_dict) + return final_qualify_json else: #大多数情况 + print("type5") user_querys = generate_qual_question(matching_keys_list) # 生成提问->‘附件:资格审查’ file_id2 = upload_file(truncate3) results2 = multi_threading(user_querys, "", file_id2, 2) # 资格评审表,调用qianwen-long @@ -115,10 +157,11 @@ def process_qualification(qualification_review,truncate3,knowledge_name): return final_qualify_json if __name__ == "__main__": - qualification_review={'营业执照': '具备有效的营业执照', '资质等级': '具备建设行政主管部门颁发的市政公用工程监理乙级及以上资质或房屋建筑工程监理乙级及以上资质或工程监理综合资质证书', '财务状况': '投标人须提供近三年(2018 年、2019 年、2020 年)完', '类似项目业绩': '投标人近 5 年(2017 年至今)须具有至少一项投资概算在4000 万元及以上房屋建筑工程或市政公用工程监理业绩,同时提供中标通知书及监理服务合同,项目规模及项目时间认定以监理服务合同内信息为准', '信誉': '根据《关于在招标投标活动中对失信被执行', '主要人员': '监理工程师:至少提供 1 名具有房屋建筑工程专'} - truncate3="" - knowledge_name="测试" + # qualification_review={'营业执照': '具备有效的营业执照', '资质等级': '具备建设行政主管部门颁发的市政公用工程监理乙级及以上资质或房屋建筑工程监理乙级及以上资质或工程监理综合资质证书', '财务状况': '投标人须提供近三年(2018 年、2019 年、2020 年)完', '类似项目业绩': '投标人近 5 年(2017 年至今)须具有至少一项投资概算在4000 万元及以上房屋建筑工程或市政公用工程监理业绩,同时提供中标通知书及监理服务合同,项目规模及项目时间认定以监理服务合同内信息为准', '信誉': '根据《关于在招标投标活动中对失信被执行', '主要人员': '监理工程师:至少提供 1 名具有房屋建筑工程专','不存在禁止投标的':'不存在第二章“投标人须知”第 1.4.3 项规定的情形','联合体投标':'hha'} + qualification_review={'营业执照':'具备有效的营业执照','安全生产许可证':'具备有效的安全生产许可证','资质等级':'符合第二章“投标人须知”规定','财务状况':'符合第二章“投标人须知”规定'} + truncate3="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest13_qualification.pdf" + knowledge_name="招标解析word13" res=process_qualification(qualification_review,truncate3,knowledge_name) - print(res) + print(json.dumps(res,ensure_ascii=False,indent=4)) #该招标文件中资格评审关于财务状况的内容是怎样的?请你以json格式返回结果,外层键名为'财务状况',请你忠于原文,回答要求完整准确,不要擅自总结、删减,且不要回答诸如'见投标人须知前附表'或'见第x.x项规定'这类无实质性内容的回答。 diff --git a/flask_app/static/提示词/资格评审.txt b/flask_app/static/提示词/资格评审.txt new file mode 100644 index 0000000..963403a --- /dev/null +++ b/flask_app/static/提示词/资格评审.txt @@ -0,0 +1,19 @@ +#资质要求: +1.该招标文件中资格审查部分对于投标人的资质条件(等级)要求是怎样的,要求给出完整资质要求内容,并按json格式给我提供信息,外层键名为'资质条件',嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致,不要擅自总结、删减,不要回答有关业绩要求、财务要求、人员要求、信誉要求的内容。 + +#业绩要求: +2.该招标文件中资格审查部分对于投标人的业绩要求是怎样的,并按json格式给我提供信息,外层键名为'业绩要求',嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致,不要擅自总结、删减,不要回答有关资质要求、财务要求、人员要求、信誉要求的内容。 + +#财务要求: +3.该招标文件中资格审查部分对于投标人的财务要求是怎样的,并按json格式给我提供信息,外层键名为'财务要求',嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致,不要擅自总结、删减,不要回答有关资质要求、业绩要求、人员要求、信誉要求的内容。 + +#信誉要求: +4.该招标文件中资格审查部分对于投标人的信誉要求是怎样的,请按json列表格式给我提供信息,键名为'信誉要求',键值需要完全与原文保持一致,不要擅自总结、删减,不要回答无关信誉要求的内容。 + +#(存在问题)主要人员要求: +5.该招标文件中资格审查部分对于项目经理(监理)和技术负责人的要求和数量是怎样的,以json的形式给出,键名分别是"项目经理"和"技术负责人",其他嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致,不要擅自总结、删减。 + +6.该招标文件中资格审查部分对于项目管理机构中除项目经理和技术负责人外的其他人员要求和数量是怎样的,以json的形式给出,最外层键名是'其他人员',嵌套的键名为具体的岗位名称,再嵌套的键名为你对各个岗位相应要求的总结,而对应键值需要完全与原文保持一致,不要擅自总结、删减。 + +#(需要与第一章对应)联合体投标: +7.该招标文件对于联合体投标的要求是怎样的,请按json格式给我提供信息,外层键名为'联合体投标要求(如有)',嵌套键名为你对该要求的总结,而键值需要完全与原文保持一致,不要擅自总结、删减。 diff --git a/flask_app/static/提示词/资格评审问题.txt b/flask_app/static/提示词/资格评审问题.txt index e3dc705..69bd2e5 100644 --- a/flask_app/static/提示词/资格评审问题.txt +++ b/flask_app/static/提示词/资格评审问题.txt @@ -8,7 +8,7 @@ 3.该招标文件对于投标人的财务要求是怎样的,要求给出财务报告的时间范围、营收(若利润)要求、需要提交的证明材料、备注(其他关于财务要求的内容,如有),请按json格式给我提供信息,最外层键名为'财务要求',若相关要求不存在,则在对应的键值中填'未知'。 #信誉要求: -4.该招标文件对于投标人的信誉要求是怎样的,请按json格式给我提供信息,键名为'信誉要求',对于各个'信誉要求',嵌套键名为'内容'和'证明材料',若存在未知信息,在对应的键值中填'未知',不要回答无关信誉要求的内容。 +4.该招标文件对于投标人的信誉要求是怎样的,请按json格式给我提供信息,键名为'信誉要求',请你忠于原文,回答要求完整准确,不要擅自总结、删减,不要回答无关信誉要求的内容。 #(存在问题)主要人员要求: 5.该招标文件对于投标人的项目经理(监理)和技术负责人的要求是怎样的,请依次给出需要的数量、资格要求、需要提交的证明材料(如具体的社保证明、技能证书等,若有时间要求请注明时间范围)、在岗要求、备注(除上述要求外文档中提及的其他关于项目经理(监理)和技术负责人要求的信息),以json的形式给出,键名分别是"项目经理"和"技术负责人",若相关要求不存在,则以“未知”填充。