import json import time from flask_app.general.doubao import doubao_model from flask_app.general.format_change import pdf2docx, docx2pdf,doc2docx from flask_app.general.json_utils import clean_json_string from flask_app.general.通用功能函数 import get_global_logger from flask_app.general.截取pdf_main import truncate_pdf_multiple from flask_app.货物标.提取采购需求main import fetch_procurement_reqs from flask_app.货物标.技术参数要求提取后处理函数 import extract_matching_keys from flask_app.货物标.资格审查main import combine_qualification_review import concurrent.futures logger = None def get_nested(dic, keys, default=None): for key in keys: if isinstance(dic, dict): dic = dic.get(key, default) else: return default return dic def prepare_for_zige_info(zige_review): try: zige_info = "" fuhe_info = "" zigefuhe_info = "" # 检查是否存在"资格性和符合性审查" if "资格性和符合性审查" in zige_review: # 情况3:只有"申请人资格要求"和"资格性和符合性审查" if zige_review.get("申请人资格要求", {}) or zige_review.get("资格性和符合性审查", {}): zigefuhe_info = json.dumps({ "申请人资格要求": zige_review.get("申请人资格要求", {}), "符合性审查": zige_review.get("资格性和符合性审查", {}) }, ensure_ascii=False, indent=4) else: # 情况1和2:存在分开的资格审查和符合性审查 if zige_review.get("申请人资格要求", {}) or zige_review.get("资格性审查", {}): zige_info = json.dumps({ "申请人资格要求": zige_review.get("申请人资格要求", {}), "资格性审查": zige_review.get("资格性审查", {}) }, ensure_ascii=False, indent=4) # 检查符合性审查的键值是否为空 fuhe_key = "符合性审查" if "符合性审查" in zige_review else "符合性审查(以下情况不得出现)" if zige_review.get(fuhe_key, {}): fuhe_info = json.dumps({ "符合性审查": zige_review.get(fuhe_key, {}) }, ensure_ascii=False, indent=4) return zige_info, fuhe_info, zigefuhe_info except KeyError as e: print(f"缺少关键字: {e}") # 异常时直接返回空字符串 return "", "", "" def extract_zige_deviation_table(zige_info, fuhe_info, zigefuhe_info): prompt_template1 = """ 任务:给出一份文本,根据文本提取资格性检查的具体评审标准。 输出要求: 1.以json格式返回结果,不要输出其他内容。 2.键名为"资格性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 要求与指南: 1. 评审标准是具体的内容,不要返回诸如'本项目的特定资格要求:'这种标题性质且不能体现具体评审标准的内容。 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 文本内容:{full_text} """ prompt_template2 = """ 任务:给出一份文本,根据文本提取符合性检查的具体评审标准。 输出要求: 1.以json格式返回结果,不要输出其他内容。 2.键名为"符合性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 3.仔细检查你所选取的标准,若发现这些标准实际上是在描述不允许出现的符合性审查情况,则将外键替换为'符合性检查(以下情况不得出现)',并将这些标准写入其中。 要求与指南: 1. 评审标准应该是具体的内容,不要返回诸如'本项目的特定符合性要求:'这种标题性质且不能体现具体评审标准的内容。 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 输出示例1: {{ "符合性检查": [ "因素1", "因素2", ... ] }} 输出示例2: {{ "符合性检查(以下情况不得出现)": [ "因素1", "因素2", ... ] }} 文本内容:{full_text} """ prompt_template3 = """ 任务:给出一份文本,根据文本提取资格性检查和符合性检查的具体评审标准。 输出要求: 1.以json格式返回结果,不要输出其他内容。 2.键名为"资格性和符合性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 要求与指南: 1. 评审标准应该是具体的内容,不要返回诸如'本项目的特定符合性要求:'这种标题性质且不能体现具体评审标准的内容。 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 文本内容:{full_text} """ def get_model_response(query): return doubao_model(query) result = {"资格审查": {}} if zigefuhe_info: # 如果zigefuhe_info非空,使用prompt_template3 user_query3 = prompt_template3.format(full_text=zigefuhe_info) model_res3 = get_model_response(user_query3) zigefuhe_deviation = clean_json_string(model_res3) result["资格审查"] = zigefuhe_deviation else: zige_deviation = {} fuhe_deviation = {} # 提交 zige_info 和 fuhe_info 的模型调用 with concurrent.futures.ThreadPoolExecutor() as executor: futures = {} if zige_info != "": user_query1 = prompt_template1.format(full_text=zige_info) futures["zige"] = executor.submit(get_model_response, user_query1) if fuhe_info != "": user_query2 = prompt_template2.format(full_text=fuhe_info) futures["fuhe"] = executor.submit(get_model_response, user_query2) # 获取结果 for key, future in futures.items(): try: model_res = future.result() if key == "zige": zige_deviation = clean_json_string(model_res) elif key == "fuhe": fuhe_deviation = clean_json_string(model_res) except Exception as e: print(f"Error processing {key}: {e}") # 合并结果 result["资格审查"] = { "资格性检查": zige_deviation.get("资格性检查", zige_deviation), "符合性检查": fuhe_deviation.get("符合性检查", fuhe_deviation), } return result def extract_business_deviation(procurement): new_data = {} counter = 1 if "服务要求" in procurement: new_data[f"招标要求{counter}"] = procurement["服务要求"] counter += 1 # Extract "商务要求" if "商务要求" in procurement: new_data[f"招标要求{counter}"] = procurement["商务要求"] counter += 1 # Extract "其他要求" if "其他要求" in procurement: new_data[f"招标要求{counter}"] = procurement["其他要求"] counter += 1 business_requirements_string = json.dumps(new_data, ensure_ascii=False, indent=4) # print(business_requirements_string) prompt_template1 = """请帮我从以下文本中摘取商务要求部分,并将信息重新组织,键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,保留三角▲、五角星★(若有),但是去除开头的序号(若有)。 #角色 你是一个专业的招投标业务专家,擅长从招标文件中总结商务要求的部分,并逐条列出,作为编写商务要求偏离表的前置准备。 #要求与指南: 1. 每条内容需要有实际的含义、要求,不能光有标题性质的表述如'售后服务期限(质保期)及要求'。 2. 你的回答内容需从所给文本中整理,尽量不改变原文的表达,请勿擅自添加三角▲、五角星★(除非以下要求与指南3.的特殊情况) 3. 若输入文本中存在嵌套键值对格式,且键值本身语义完整且符合'商务要求',可直接将其添加至'商务要求'的键值中;若键值本身语义表达不完整,可将键值对用冒号':'拼接之后作为一条商务要求。 4. 对于以三角▲或五角星★开头的字符串: a. 如果该字符串仅为标题性质的表述且不具备实际商务要求的含义,请根据语义关联性将其开头的三角▲或五角星★添加到紧随其后的若干(可为一)内容之后,形成完整的商务要求,并确保整个内容连贯。 注:默认在该字符串后面的一个字符串开头添加三角▲或五角星★,若有明确的序号或者语义表示了其后若干字符串之间的相关性,那么可在这些字符串开头都添加三角▲或五角星★,作为若干商务要求。 b. 如果该字符串已经包含实际的商务要求,那么该内容作为一条完整的商务要求,保留开头的三角▲或五角星★。 - 示例输入: ``` "★ 提供高质量的售后服务,服务期限不少于两年。" ``` - 示例输出: ``` "★ 提供高质量的售后服务,服务期限不少于两年。" ``` c. 无论哪种情况,都需确保不遗漏任何以三角▲或五角星★开头的重要信息。 5. 若无商务要求,键值为空列表,即[] ### 示例输入如下: {{ "招标要求1": ["▲(1)整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班,依照 7×12小时对可视化督察巡控平台进行操作,确保平台稳定运行。","▲ (一) 投标人","1.投标人需要获得 ISO9001 质量管理体系认证 、ISO 14001 环境管理体系认证及 OHSAS18001 职业健康安全管理体系认证。","2.投标人具备网络运营商资格。"] "招标要求2": {{ "合同履行期限": ["★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。"], "交货地点": ["采购人指定地点"], "报价方式": ["(1)本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。","(2)因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。"], "其他要求": ["无。"] }} }} ### 对应的参考输出如下: {{ "商务要求":[ "▲整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班,依照 7×12小时对可视化督察巡控平台进行操作,确保平台稳定运行。", "▲投标人 获得 ISO9001 质量管理体系认证 、ISO 14001 环境管理体系认证及 OHSAS18001 职业健康安全管理体系认证。", "▲投标人具备网络运营商资格" "★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。", "交货地点:采购人指定地点", "本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。", "因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。" ] }} 文本内容:{full_text} """ user_query1 = prompt_template1.format(full_text=business_requirements_string) model_res1 = doubao_model(user_query1) # print(model_res) business_req_deviation = clean_json_string(model_res1) prompt_template2 = """以下文本是项目采购需求的商务要求部分,请你帮我从键值列表中各字符串中提取带星★或带三角▲的要求项,你的返回格式同输入文本格式,外键名为'商务要求带星',键值为字符串列表,其中每个字符串为带星★或带三角▲的要求项。 要求与指南: 1. 每个星★或三角▲要求占据一个字符串。 2. 若没有带星★或带三角▲的要求项,键值为空列表,即[] 特殊情况处理: 对于输入类似于'技术要求中带★条款项不满足的视为无效投标'这种描述带星★或带三角▲的响应情况的,它本身不是带星或带三角的要求,因此不需要添加进字符串列表中;仅需把本身是带★或带三角▲的要求添加进来。 ### 示例输入如下: {{ "商务要求": [ "考虑设备兼容性、项目实施、交付及售后服务", "★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。", "▲本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。" ] }} ### 对应的输出如下: {{ "商务要求带星": [ "★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。", "▲本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。" ] }} 文本内容:{full_text} """ user_query2 = prompt_template2.format(full_text=model_res1) model_res2 = doubao_model(user_query2) business_star_req_deviation = clean_json_string(model_res2) return business_req_deviation, business_star_req_deviation def get_tech_star_deviation(tech_string): prompt_template = """以下输入文本包含采购货物的技术参数要求或采购要求。请从每个键对应的字符串列表中提取带有星★或三角▲的要求项。返回格式应与输入文本格式相同,为JSON格式,每个键名保持不变,键值为包含对应货物、系统或功能模块的带星或带三角要求项的字符串列表。 要求与指南: 1. 如果某个货物、系统或功能模块下没有带星★或带三角▲的要求项,则不返回该键值对。 2. 每个带星★或带三角▲的要求项应作为单独的字符串。 3. 如果所有设备、系统或功能模块中都没有带星★或带三角▲的要求项,则直接返回空字典 {{}}。 ### 示例输入1如下: {{ "控制键盘": [ "普通要求xx", "★带星要求xx" ] "摄像机"[ "★带星要求xx", "▲带三角要求xx", "普通要求xx" ] "交换机":[ "普通要求xx", "普通要求xxx" ] }} ### 对应的输出如下: {{ "摄像机控制键盘": [ "★带星要求xx" ] "摄像机"[ "★带星要求xx", "▲带三角要求xx" ] }} ### 示例输入2如下: {{ "控制键盘": [ "普通要求xx", "普通要求xxx" ] "摄像机"[ "普通要求xx" ] }} ### 对应的输出如下: {{}} 输入文本内容:{full_text} """ user_query = prompt_template.format(full_text=tech_string) model_res = doubao_model(user_query) # print(model_res) tech_star_deviation = clean_json_string(model_res) filtered_dict = {key: value for key, value in tech_star_deviation.items() if value} #过滤键值为空列表,二重保险。 return filtered_dict def process_functions_in_parallel(tech_deviation_info, procurement_reqs, zige_info, fuhe_info, zigefuhe_info): # 准备输入参数 # 定义任务和对应参数 tasks = [ ("tech_star_deviation", get_tech_star_deviation, (tech_deviation_info,)), ("business_deviation_and_star", extract_business_deviation, (procurement_reqs,)), ("zigefuhe_deviation", extract_zige_deviation_table, (zige_info, fuhe_info, zigefuhe_info)), ] results = {} # 执行多线程任务 with concurrent.futures.ThreadPoolExecutor() as executor: future_to_task = {executor.submit(func, *args): name for name, func, args in tasks} time.sleep(0.5) for future in concurrent.futures.as_completed(future_to_task): name = future_to_task[future] try: result = future.result() # 处理返回值(如果任务返回多个值,需要解包) if name == "business_deviation_and_star": results["business_deviation"], results["business_star_deviation"] = result else: results[name] = result except Exception as e: print(f"Task {name} failed with exception: {e}") results[name] = None # 返回结果 return ( results.get("tech_star_deviation"), results.get("business_deviation"), results.get("business_star_deviation"), results.get("zigefuhe_deviation") ) def get_tech_and_business_deviation(file_path,file_type,unique_id,output_folder): global logger logger = get_global_logger(unique_id) if file_type == 1: # docx docx_path=file_path pdf_path = docx2pdf(file_path) # 将docx转换为pdf以供后续处理 elif file_type == 2: # pdf # docx_path=pdf2docx(file_path) docx_path="" pdf_path = file_path elif file_type == 3: # doc docx_path=doc2docx(file_path) pdf_path = docx2pdf(file_path) else: logger.error("Unsupported file type provided. Preprocessing halted.") return None selections=[1,3,5] files=truncate_pdf_multiple(pdf_path,output_folder,logger,'goods',selections) notice_path=files[0] qualification_file=files[1] procurement_file=files[2] # invalid_path=docx_path invalid_path=docx_path if docx_path != "" else pdf_path #可能是pdf docx if not procurement_file: procurement_file=invalid_path tech_deviation={} with concurrent.futures.ThreadPoolExecutor() as executor: # 提交任务到线程池 future_procurement = executor.submit(fetch_procurement_reqs, procurement_file, invalid_path) time.sleep(1) future_review = executor.submit(combine_qualification_review, invalid_path, qualification_file, notice_path) try: # 获取函数执行结果 procurement_reqs = future_procurement.result() except Exception as e: logger.error(f'fetch_procurement_reqs 出现异常: {e}') procurement_reqs = {} # 或根据需要进行处理 try: review_standards_res = future_review.result() except Exception as e: logger.error(f'combine_qualification_review 出现异常: {e}') review_standards_res = {} # 或根据需要进行处理 tech_requirements = get_nested(procurement_reqs, ["采购需求"], {}) if tech_requirements: good_list = tech_requirements.pop('货物列表', []) # 如果 '货物列表' 不存在,返回 [] logger.info("Collected good_list from the processing function: %s", good_list) tech_deviation = extract_matching_keys(tech_requirements, good_list) tech_deviation_info = json.dumps(tech_deviation, ensure_ascii=False, indent=4) else: tech_deviation_info="" zige_info, fuhe_info, zigefuhe_info = prepare_for_zige_info(review_standards_res.get("资格审查", {})) tech_star_deviation, business_deviation, business_star_deviation, zigefuhe_deviation = process_functions_in_parallel( tech_deviation_info=tech_deviation_info, procurement_reqs=procurement_reqs, zige_info=zige_info, fuhe_info=fuhe_info, zigefuhe_info=zigefuhe_info ) return tech_deviation,tech_star_deviation,business_deviation,business_star_deviation,zigefuhe_deviation if __name__ == "__main__": file_path=r"C:\Users\Administrator\Desktop\fsdownload\5950ad84-30c8-4643-b6de-b13ef5be7a5c\ztbfile.pdf" file_type=2 output_folder = r"C:\Users\Administrator\Desktop\fsdownload\5950ad84-30c8-4643-b6de-b13ef5be7a5c\tmp" tech_deviation,tech_star_deviation,business_deviation,business_star_deviation,zigefuhe_deviation=get_tech_and_business_deviation(file_path,file_type,"123",output_folder) print("技术偏离表") print(json.dumps(tech_deviation,ensure_ascii=False,indent=4)) print("技术带星") print(json.dumps(tech_star_deviation,ensure_ascii=False,indent=4)) print("商务偏离表") print(json.dumps(business_deviation, ensure_ascii=False, indent=4)) print("商务带星") print(json.dumps(business_star_deviation, ensure_ascii=False, indent=4)) print("资格审查") print(json.dumps(zigefuhe_deviation, ensure_ascii=False, indent=4))