# -*- coding: utf-8 -*- import json import os.path import time import re from flask_app.general.无效标和废标公共代码 import read_tables_from_docx, extract_text_with_keywords, \ preprocess_text_list, clean_dict_datas, extract_table_with_keywords from flask_app.general.通义千问long import upload_file, qianwen_long,qianwen_long_text from flask_app.general.通用功能函数 import process_string_list from docx import Document from concurrent.futures import ThreadPoolExecutor, as_completed from collections import OrderedDict # 只读取前附表中的最后一列(省钱,但容易漏内容) def read_docx_last_column(truncate_file): # 尝试打开文档 try: doc = Document(truncate_file) except Exception as e: print(f"Error opening file: {e}") return [] last_column_values = [] # 读取文档中的所有表格 if not doc.tables: print("No tables found in the document.") return last_column_values # 遍历文档中的每个表格 for table in doc.tables: # 获取表格的最后一列 for row in table.rows: last_cell = row.cells[-1] # 获取最后一个单元格 # 去除内容前后空白并删除文本中的所有空格 cleaned_text = last_cell.text.strip().replace(' ', '') last_column_values.append(cleaned_text) return last_column_values # 处理无效投标 def extract_values_if_contains(data, includes): """ 递归检查字典中的值是否包含列表 'includes' 中的内容。 如果包含,将这些值添加到一个列表中并返回。 参数: data (dict): 字典或从 JSON 解析得到的数据。 includes (list): 包含要检查的关键词的列表。 返回: list: 包含满足条件的值的列表。 """ included_values = [] # 初始化结果列表 # 定义递归函数来处理嵌套字典 def recursive_search(current_data): if isinstance(current_data, dict): for key, value in current_data.items(): if isinstance(value, dict): # 如果值是字典,递归搜索 recursive_search(value) elif isinstance(value, str): # 如果值是字符串,检查是否包含任何 includes 中的关键词 if any(include in value for include in includes): included_values.append(value) elif isinstance(current_data, list): for item in current_data: # 如果是列表,递归每个元素 recursive_search(item) # 开始递归搜索 recursive_search(data) return included_values # 你是一个文本助手,文本内的信息以'...............'分割,你负责准确筛选所需的信息并返回,每块信息要求完整,不遗漏,你不得擅自进行总结或删减。 # 以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,请你根据该内容回答:否决投标或拒绝投标或无效投标或使投标失效的情况有哪些?文本中可能存在无关的信息,请你准确筛选符合的信息并将它的序号返回。请以[x,x,x]格式返回给我结果,x为符合的信息的序号。 # 以上是原文内容,文本内的信息以'...............'分割,请你根据该信息回答:否决投标或拒绝投标或无效投标或使投标失效的情况有哪些?文本中可能存在无关的信息,请你准确筛选所需的信息并返回。最终结果以json列表格式返回给我,键名为'否决和无效投标情形',你的回答完全忠于原文内容,且回答内容与原文内容一致,要求完整与准确,不能擅自总结或者概括。", def handle_query(file_path, user_query, output_file, result_key, keywords): try: excludes = ["说明表", "重新招标", "否决所有", "否决投标的条件", "备注:", "本人保证:", "我方"] follow_up_keywords = [r'情\s*形\s*之\s*一', r'情\s*况\s*之\s*一', r'下\s*列', r'以\s*下'] extracted_contents = extract_text_with_keywords(file_path, [keywords], follow_up_keywords) # 字典结果 all_texts1, all_texts2 = clean_dict_datas(extracted_contents, keywords, excludes) # 列表 # table_data_list=read_docx_last_column(truncate_file) #从投标人须知前附表中提取信息生成列表data,每个元素为'一行信息' table_data_list = read_tables_from_docx(file_path) all_tables1, all_tables2 = extract_table_with_keywords(table_data_list, keywords, follow_up_keywords) qianwen_txt = all_texts1 + all_tables1 # Proceed only if there is content to write selected_contents = set() # 使用 set 去重 if qianwen_txt: with open(output_file, 'w', encoding='utf-8') as file: counter = 1 for content in qianwen_txt: file.write(f"{counter}. {content}\n") file.write("..............." + '\n') counter += 1 file_id = upload_file(output_file) # qianwen_ans = qianwen_long(file_id, user_query) qianwen_ans = qianwen_long_text(file_id, user_query) num_list = process_string_list(qianwen_ans) print(result_key + "选中的序号:" + str(num_list)) for index in num_list: if index - 1 < len(qianwen_txt): content = qianwen_txt[index - 1] selected_contents.add(content) # 无论 qianwen_txt 是否为空,都添加 all_texts2 和 all_tables2 的内容 selected_contents.update(all_texts2) selected_contents.update(all_tables2) # 如果 selected_contents 不为空,则返回结果,否则返回空字符串 if selected_contents: res = {result_key: list(selected_contents)} else: res = {result_key: ""} return res except Exception as e: print(f"handle_query 在处理 {result_key} 时发生异常: {e}") return {result_key: ""} def combine_find_invalid(file_path, output_dir): os.makedirs(output_dir, exist_ok=True) queries = [ ( r'否\s*决|无\s*效\s*投\s*标|无\s*效\s*文\s*件|文\s*件\s*无\s*效|无\s*效\s*响\s*应|无\s*效\s*报\s*价|无\s*效\s*标|视\s*为\s*无\s*效|被\s*拒\s*绝|予\s*以\s*拒\s*绝|投\s*标\s*失\s*效|投\s*标\s*无\s*效', "以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,请你根据该内容回答:否决投标或拒绝投标或无效投标或投标失效的情况有哪些?文本中可能存在无关的信息,请你准确筛选符合的信息并将它的序号返回。请以[x,x,x]格式返回给我结果,x为符合的信息的序号,若情况不存在,返回[]。", os.path.join(output_dir, "temp1.txt"), "否决和无效投标情形" ), ( r'废\s*标', "以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,请你根据该内容回答:废标项的情况有哪些?文本中可能存在无关的信息,请你准确筛选符合的信息并将它的序号返回。请以[x,x,x]格式返回给我结果,x为符合的信息的序号,若情况不存在,返回[]。", os.path.join(output_dir, "temp2.txt"), "废标项" ), ( r'不\s*得|禁\s*止\s*投\s*标', "以上是从招标文件中摘取的内容,文本内之间的信息以'...............'分割,每条信息规定了各方不得存在的情形,请回答:在这些信息中,主语是投标人或中标人或供应商或联合体投标各方或磋商小组的信息有哪些?不要返回主语是招标人或采购人或评标委员会的信息,请你筛选所需的信息并将它的序号返回。请以[x,x,x]格式返回给我结果,示例返回为[1,4,6],若情况不存在,返回[]。", os.path.join(output_dir, "temp3.txt"), "不得存在的情形" ) ] results = [] # 使用线程池来并行处理查询 with ThreadPoolExecutor() as executor: futures = [] for keywords, user_query, output_file, result_key in queries: future = executor.submit(handle_query, file_path, user_query, output_file, result_key, keywords) futures.append((future, result_key)) # 保持顺序 time.sleep(0.5) # 暂停0.5秒后再提交下一个任务 for future, result_key in futures: try: result = future.result() except Exception as e: print(f"线程处理 {result_key} 时出错: {e}") result = {result_key: ""} results.append(result) combined_dict = {} for d in results: combined_dict.update(d) print("无效标与废标done...") return {"无效标与废标项": combined_dict} # TODO:无效标目前以整个docx文档作为输入,可能导致后面两章不必要的信息也导入。 无效投标至少>8个字 if __name__ == '__main__': start_time = time.time() # truncate_json_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output4\\tmp2\\竞争性谈判文件(3)_tobidders_notice_part1\\truncate_output.json" # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output4\\招标文件(实高电子显示屏)_tobidders_notice_part1.docx" clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json" # doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx" doc_path = 'D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.docx' output_dir = "D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\tmp" results = combine_find_invalid(doc_path, output_dir) end_time = time.time() print("Elapsed time:", str(end_time - start_time)) print("Results:", json.dumps(results, ensure_ascii=False, indent=4))