import json import os import re import regex import time from concurrent.futures import ThreadPoolExecutor from flask_app.general.llm.doubao import generate_full_user_query from flask_app.general.llm.通义千问long_plus import qianwen_plus from flask_app.general.通用功能函数 import process_string_list from collections import OrderedDict from docx import Document def clean_dict_datas(extracted_contents, keywords, excludes): # 让正则表达式提取到的东西格式化 all_text1 = {} # 用于存储 len(text_list) == 1 的结果,按序号保存 all_text2 = {} # 用于存储 len(text_list) > 1 的结果,按序号保存 # 定义用于分割句子的正则表达式,包括中文和西文的结束标点 split_pattern = r'(?<=[。!?\!\?])' clean_pattern = (r'^\s*(?:[((]\s*\d+\s*[)))]|' r'[A-Za-z]?\d+(?:\.\s*\d+)+[\s*\.、.))]?|' #12.1 r'[一二三四五六七八九十]+、|' r'[A-Za-z]\s*[))\.、.]?\s*)|' r'\d+\s*[))\.、.]?\s*|' r'[\s*\.、.))]?') idx = 0 # 共享的序号标记 for key, text_list in extracted_contents.items(): if len(text_list) == 1: for data in text_list: # print(data) # 检查是否包含任何需要排除的字符串 if any(exclude in data for exclude in excludes): continue # 如果包含任何排除字符串,跳过这个数据 # 去掉开头的序号,eg:1 | (1) |(2) | 1. | 2.(全角点)| 3、 | 1.1 | 2.3.4 | A1 | C1.1 | 一、 # print("---------") # print(data) data = re.sub(clean_pattern, '', data).strip() # print(data) keyword_match = re.search(keywords, data) if keyword_match: # 从关键词位置开始查找结束标点符号 start_pos = keyword_match.start() # 截取从关键词开始到后面的内容 substring = data[start_pos:] # 按定义的结束标点分割 sentences = re.split(split_pattern, substring, 1) if len(sentences) > 0 and sentences[0]: # 只取第一句,保留标点 cleaned_text = data[:start_pos] + sentences[0] # eg:经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。潜在投标人应自行承担现场考察的全部费用、责任和风险。 # 经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。 else: cleaned_text = data # 如果没有标点,使用整个字符串 else: # 如果没有找到关键词,保留原文本 cleaned_text = data # 删除空格 cleaned_text_no_spaces = cleaned_text.replace(' ', '').replace(' ', '') # 如果长度大于8,则添加到结果列表 if len(cleaned_text_no_spaces) > 8: all_text1[idx] = cleaned_text_no_spaces idx += 1 # 更新共享序号 else: # print(text_list) # print("*********") # 用于处理结构化文本,清理掉不必要的序号,并将分割后的段落合并,最终形成更简洁和格式化的输出。 data = re.sub(clean_pattern, '', text_list[0]).strip() # 只去除第一个的序号 # 将修改后的第一个元素和剩余的元素连接起来 text_list[0] = data # 更新列表中的第一个元素 joined_text = "\n".join(text_list) # 如果列表中有多个元素,则连接它们 # 删除空格 joined_text_no_spaces = joined_text.replace(' ', '').replace(' ', '') all_text2[idx] = joined_text_no_spaces idx += 1 # 更新共享序号 return all_text1, all_text2 # all_texts1要额外用gpt all_text2直接返回结果 def preprocess_paragraphs(elements): ''' 处理跨页的段落,程序逻辑判断两个段落能否合并在一起。 ''' processed = [] # 初始化处理后的段落列表 index = 0 flag = False # 初始化标志位 # 定义列表项的模式 list_item_pattern = re.compile( r'^\s*(' r'[(\(]\d+[)\)]|' # 匹配:(1) 或 (1) r'[A-Za-z]\.\s*|' # 匹配:A. 或 b. r'[一二三四五六七八九十]+、|' # 匹配:一、二、三、 r'第[一二三四五六七八九十百零]+[章节部分节]|' # 匹配:第x章,第x部分,第x节 r'[A-Za-z]\d+(?:\.\d+)*[\s\.、.)\)]?|' # 匹配:A1.2 等 r'\d+(?:\.\d+)+[\s\.、.)\)]?(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 匹配:数字序号如1.1 1.1.1 r'(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万]))|' # 数字后空格,空格后非指定关键字 r'(?=\d+[、..])(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])' # 数字后直接跟顿号或点号 r')' ) # 新增的正则表达式,用于匹配以数字序号开头的段落 pattern_numeric_header = re.compile( r'^(? max_space_count for space in re.findall(r'\s+', text)) # 正则表达式用于检测页面标记 pattern_marker = re.compile(r'\$\$index_mark_\d+\$\$') # 辅助函数:查找上一个非空且非标记的段落 def find_prev_text(current_index): for i in range(current_index - 1, -1, -1): if isinstance(elements[i], str): return '', -1 try: text = elements[i].text.strip() except AttributeError: continue # 如果段落对象没有 text 属性,跳过 if text and not pattern_marker.search(text): return text, i return '', -1 # 辅助函数:查找下一个非空且非标记的段落 def find_next_text(current_index): for i in range(current_index + 1, len(elements)): if isinstance(elements[i], str): return '', -1 try: text = elements[i].text.strip() except AttributeError: continue # 如果段落对象没有 text 属性,跳过 # 跳过空白段落和页面标记 if not text or pattern_marker.search(text): continue return text, i return '', -1 while index < len(elements): if isinstance(elements[index], str): processed.append(elements[index]) index += 1 continue try: current_text = elements[index].text.strip() # 去除当前段落的前后空白 # print(current_text) except AttributeError: # 如果段落对象没有 text 属性,跳过该段落 index += 1 continue # 检查当前段落是否为页面标记 if pattern_marker.search(current_text): # 动态查找前一个非空段落 prev_text, prev_index = find_prev_text(index) # 动态查找后一个非空段落 next_text, next_index = find_next_text(index) # 应用现有的合并逻辑 if prev_text and next_text and not has_long_spaces(prev_text) and not has_long_spaces(next_text): if not prev_text.endswith(('。', '!', '?')): # ',', ',', 先注释了,如果逗号,可能还没结束。 # 检查后一个段落是否为列表项 if not list_item_pattern.match(next_text) and len(prev_text) > 30: # 合并前后段落 merged_text = prev_text + ' ' + next_text # 为了可读性添加空格 if prev_index < len(elements): # 移除 processed 中的前一个段落 if processed and processed[-1] == prev_text: processed.pop() # 添加合并后的文本 processed.append(merged_text) # 跳过标记以及前后所有空白段落,直到 next_index index = next_index + 1 continue # 继续下一个循环 # 如果不满足合并条件,跳过标记及其周围的空白段落 # 计算下一个需要处理的索引 # 从当前 index 向下,跳过所有连续的空白段落和标记 skip_index = index + 1 while skip_index < len(elements): if isinstance(elements[skip_index], str): break try: skip_text = elements[skip_index].text.strip() except AttributeError: skip_index += 1 continue # 如果段落对象没有 text 属性,跳过 if skip_text == '' or pattern_marker.search(skip_text): skip_index += 1 else: break index = skip_index continue # 继续下一个循环 # 检查是否为以数字序号开头的段落eg:11.2 match = pattern_numeric_header.match(current_text) if not match: match = pattern_numeric_header_fallback.match(current_text) if match: # 当前段落以数字序号开头,直接添加到 processed processed.append(current_text) flag = True # 设置标志位,准备处理下一个段落 index += 1 continue else: if flag: if not list_item_pattern.match(current_text): if processed: # **新增逻辑开始** next_non_empty_text, next_non_empty_index = find_next_text(index) is_next_numbered = False if next_non_empty_text: is_next_numbered = bool( pattern_numeric_header.match(next_non_empty_text) or pattern_numeric_header_fallback.match(next_non_empty_text) ) if is_next_numbered and len(processed[-1]) > 30: # 只有在下一个段落以数字序号开头且上一个段落长度大于30时,才将当前段落追加到上一个段落 processed[-1] = processed[-1] + ' ' + current_text else: # 否则,不追加,而是作为新的段落添加 processed.append(current_text) # **新增逻辑结束** else: # **新增处理:匹配 list_item_pattern 的段落也应被保存** processed.append(current_text) # 无论是否追加,都将 flag 重置 flag = False index += 1 continue else: # flag 为 False,直接添加到 processed processed.append(current_text) index += 1 continue return processed def extract_text_with_keywords(processed_paragraphs, keywords, follow_up_keywords): if isinstance(keywords, str): keywords = [keywords] extracted_paragraphs = OrderedDict() continue_collecting = False current_section_pattern = None active_key = None # 新增的条件:检查是否匹配 pattern_numbered 或 pattern_parentheses pattern_numbered = re.compile(r'^\s*([一二三四五六七八九十]{1,2})\s*、\s*') pattern_parentheses = re.compile(r'^\s*[((]\s*([一二三四五六七八九十]{1,2})\s*[))]\s*') pattern_letter_initial = re.compile(r'^([A-Z])\s*[..、]?\s*(.*)$') def match_keywords(text, patterns): # 首先检查关键词是否匹配 for pattern in patterns: if re.search(pattern, text, re.IGNORECASE): return True return False def extract_from_text(text, current_index): nonlocal continue_collecting, current_section_pattern, active_key if text == "": return current_index def process_matching_section(): nonlocal current_index, continue_collecting, active_key, current_section_pattern found_next_number = False current_section_pattern = None try: while current_index < len(processed_paragraphs) - 1: current_index += 1 try: next_text = processed_paragraphs[current_index].strip() except IndexError: print(f"IndexError: current_index={current_index} is out of range.") break # Exit the loop if index is out of range if not next_text: continue # Skip empty lines if not found_next_number: # Modify the regular expression to support the required format number_pattern = ( r'^([A-Za-z]+(?:[..][A-Za-z]+)*)|' # 仅字母模式 r'^([0-9]+(?:[..][0-9]+)*)|' # 仅数字模式 r'([((]\s*(?:[一二三四五六七八九十]{1,2}|\d+)\s*[))])|' # 括号内的中文数字或阿拉伯数字 r'((?:[一二三四五六七八九十]{1,2}|\d+)\s*、)' # 中文数字或阿拉伯数字加顿号 ) next_section_number = re.match(number_pattern, next_text) if next_section_number: found_next_number = True if next_section_number.group(1): # 仅字母模式 section_parts = next_section_number.group(1).split('.') dynamic_pattern = r'^' + r'[A-Za-z]+' + r'(?:[..][A-Za-z]+)*' + r'\b' elif next_section_number.group(2): # 仅数字模式 section_parts = next_section_number.group(2).split('.') dynamic_pattern = r'^' + r'[0-9]+' + r'(?:[..][0-9]+)*' + r'\b' elif next_section_number.group(3): # 括号内的中文数字或阿拉伯数字 dynamic_pattern = r'^[((]\s*(?:[一二三四五六七八九十]{1,2}|\d+)\s*[))]' elif next_section_number.group(4): # 中文数字或阿拉伯数字加顿号 dynamic_pattern = r'^(?:[一二三四五六七八九十]{1,2}|\d+)\s*、' else: dynamic_pattern = None if dynamic_pattern: current_section_pattern = re.compile(dynamic_pattern) if current_section_pattern and re.match(current_section_pattern, next_text): extracted_paragraphs[active_key].append(next_text) else: continue_collecting = False active_key = None break except Exception as e: print(f"An unexpected error occurred in process_matching_section: {e}") # Optionally, you can re-raise the exception or handle it as needed # raise e if continue_collecting: if text == '[$$table_start$$]': current_index += 1 while (processed_paragraphs[current_index] != '[$$table_over$$]'): extracted_paragraphs[active_key].append(processed_paragraphs[current_index]) current_index += 1 return current_index if current_section_pattern and re.match(current_section_pattern, text): continue_collecting = False active_key = None else: if active_key is not None: extracted_paragraphs[active_key].append(text) return current_index try: if match_keywords(text, keywords): active_key = text extracted_paragraphs[active_key] = [text] if (re.match(pattern_numbered, text) or re.match(pattern_parentheses, text) or re.match(pattern_letter_initial,text)) and len(text.replace(' ','')) < 10: continue_collecting = True process_matching_section() elif match_keywords(text, follow_up_keywords): continue_collecting = True section_number = re.match(r'^(\d+([..]\d+)*)\s*[..、]?', text) if section_number: current_section_number = section_number.group(1) level_count = current_section_number.count('.') parts = current_section_number.split('.') matched_patterns = [] if len(parts) > 1: for i in range(1, 6): parent_section_parts = parts[:-1].copy() try: parent_section_parts[-1] = str(int(parent_section_parts[-1]) + i) except ValueError: print(f"ValueError: Unable to convert '{parent_section_parts[-1]}' to int.") continue parent_pattern = r'^' + r'\s*[..、]\s*'.join(parent_section_parts) + r'(?!\s*[..、]\s*\d+)' matched_patterns.append(parent_pattern) try: current_top_level_num = int(current_section_number.split('.')[0]) for i in range(1, 6): next_top_level_num = current_top_level_num + i next_top_level_pattern = r'^' + str(next_top_level_num) + r'\s*[..、](?!\d)' if next_top_level_pattern not in matched_patterns: matched_patterns.append(next_top_level_pattern) except ValueError: print(f"ValueError: Unable to convert '{current_section_number.split('.')[0]}' to int.") combined_pattern = r'(' + r')|('.join(matched_patterns) + r')' try: current_section_pattern = re.compile(combined_pattern) except re.error as re_err: print(f"Regex compilation error: {re_err} with pattern '{combined_pattern}'") else: process_matching_section() # Reusing the common function for matching sections except Exception as e: print(f"An unexpected error occurred in extract_from_text: {e} 当前index:{current_index}") return current_index index = 0 while index < len(processed_paragraphs): # print(processed_paragraphs[index].strip()) index = extract_from_text(processed_paragraphs[index].strip(), index) # print("--------------") index += 1 return extracted_paragraphs # 分割表格中单元格文本 def split_cell_text(text): # 定义用于提取括号内容的正则表达式,支持中英文括号 bracket_pattern = re.compile(r'[((][^(()))]+[))]') # 1. 先提取并替换括号内容 bracket_contents = [] def replace_bracket_content(match): bracket_contents.append(match.group(0)) # 保存括号内容 return f"" # 使用占位符替换括号内容 item_with_placeholders = bracket_pattern.sub(replace_bracket_content, text) # print("-----------") # print(text) # 2. 分割句子,保证句子完整性(按标点符号和序号分割) split_sentences = regex.split( r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号后分割 r'(?\d+(?:[..]\d+)+)(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万台份家]))|' # 匹配多级编号,(?=(?>\d+(?:[..]\d+)+)让多级编号匹配“一口气”完成;限制后面不能是指定关键字 r'(?", lambda m: bracket_contents[int(m.group(1))], s) for s in split_sentences] # 4. 过滤空字符串 split_sentences = [s for s in split_sentences if s.strip()] # print(split_sentences) return split_sentences def extract_file_elements(file_path): ''' 文件预处理----按文件顺序提取文本和表格,并合并跨页表格 ''' doc = Document(file_path) doc_elements = doc.element.body doc_paragraphs = doc.paragraphs doc_tables = doc.tables pre_table_head = None table_combine = False paragraph_index = 0 tables_index = 0 doc_contents = [] pattern_marker = re.compile(r'\$\$index_mark_\d+\$\$') # 遍历文件元素 for element in doc_elements: # 如果是段落 if element.tag.endswith('}p'): try: # Ensure we only process paragraphs if paragraph_index is within range if paragraph_index < len(doc_paragraphs): # print(f"Processing paragraph {paragraph_index}/{len(doc_paragraphs)}") text = doc_paragraphs[paragraph_index].text if pre_table_head: # If we are still processing a table, check conditions if (text == '' or pattern_marker.search(text)): paragraph_index += 1 continue else: doc_contents.append('[$$table_over$$]') table_combine = False pre_table_head = None doc_contents.append(doc_paragraphs[paragraph_index]) paragraph_index += 1 else: raise IndexError(f"Paragraph index {paragraph_index} is out of range. Total paragraphs: {len(doc_paragraphs)}") except IndexError as e: print(f"Error processing paragraph: {e}") continue # Skip to the next element if an error occurs # 如果是表格 elif element.tag.endswith('}tbl'): try: if tables_index < len(doc_tables): # print(f"Processing table {tables_index}/{len(doc_tables)}") table = doc_tables[tables_index] table_content = [] for row_idx, row in enumerate(table.rows): if row_idx == 0: if pre_table_head: table_combine = True if pre_table_head == row.cells[0].text: continue else: pre_table_head = row.cells[0].text doc_contents.append('[$$table_start$$]') continue for cell in row.cells: cell_text = cell.text.strip() if len(cell_text) > 8: cell_text = split_cell_text(cell_text) table_content += cell_text if table_combine: if doc_contents and table_content and not doc_contents[-1].endswith(('。', '!', '?', ';')): doc_contents[-1] += ' ' + table_content[0] table_content.pop(0) doc_contents.extend(table_content) tables_index += 1 else: raise IndexError(f"Table index {tables_index} is out of range. Total tables: {len(doc_tables)}") except IndexError as e: print(f"Error processing table: {e}") continue # Skip to the next element if an error occurs return doc_contents 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*列(?!\s*公式)', # 增加负向前瞻,排除“下列公式” r'以\s*下(?!\s*(?:公式|简称))', # 增加负向前瞻,排除“以下公式” r'其\s*他.*?情\s*形\s*[::]', r'包\s*括' ] doc_contents = extract_file_elements(file_path) processed_paragraphs = preprocess_paragraphs(doc_contents) extracted_contents = extract_text_with_keywords(processed_paragraphs, [keywords], follow_up_keywords) all_texts1,all_texts2 = clean_dict_datas(extracted_contents, keywords, excludes) # 列表 # print(all_texts2) seen_contents = set() # 使用集合来跟踪已出现的内容 unique_all_texts1 = {} # 存储去重后的内容 for key, content in all_texts1.items(): content_key = content[:25] # 提取前25个字符作为去重依据 if content_key not in seen_contents: unique_all_texts1[key] = content # 保留第一次出现的内容 seen_contents.add(content_key) # 1. 得到有序的 all_text1_items all_text1_items = sorted(unique_all_texts1.items(), key=lambda x: x[0]) # 2. 得到纯内容列表 all_texts1_list = [content for (_, content) in all_text1_items] # Proceed only if there is content to write selected_contents = {} final_list=[f"未解析到'{result_key}'!"] if all_texts1_list or all_texts2: with open(output_file, 'w', encoding='utf-8') as file: counter = 1 for content in all_texts1_list: file.write(f"{counter}. {content}\n") file.write("..............." + '\n') counter += 1 # 生成用户查询 if not all_texts1_list: print("无需调用大模型获取选中的序号!") num_list=[] else: user_query = generate_full_user_query(output_file, user_query) model_ans = qianwen_plus(user_query) # 豆包模型返回结果 # file_id = upload_file(output_file) # model_ans = qianwen_long(file_id, user_query) num_list = process_string_list(model_ans) # 处理模型返回的序号 print(result_key + "选中的序号:" + str(num_list)) # print(all_texts1_list) # print(all_text1_items) for index in num_list: if 1 <= index <= len(all_texts1_list): original_global_idx = all_text1_items[index - 1][0] content = all_text1_items[index - 1][1] selected_contents[original_global_idx] = content # 把选中的 all_text1 内容 + all_text2 合并 merged_dict = {} # 先合并用户选中的 all_text1 for idx, txt in selected_contents.items(): merged_dict[idx] = txt # 再合并 all_text2 for idx, txt in all_texts2.items(): merged_dict[idx] = txt final_list = [txt for idx, txt in sorted(merged_dict.items(), key=lambda x: x[0])] return {result_key: final_list} except Exception as e: print(f"handle_query 在处理 {result_key} 时发生异常: {e}") return {result_key: [f"未解析到'{result_key}'!"]} def combine_find_invalid(invalid_docpath, output_dir): os.makedirs(output_dir, exist_ok=True) queries = [ ( r'否\s*决|' r'无\s*效\s*投\s*标|' r'无\s*效\s*文\s*件|' r'(?:文\s*件|投\s*标|响\s*应)\s*[\u4e00-\u9fa5]?\s*(?:无|失)\s*效|' r'无\s*效\s*响\s*应|' r'无\s*效\s*报\s*价|' r'无\s*效\s*标|' r'视\s*为\s*无\s*效|' r'被\s*拒\s*绝|' r'将\s*拒\s*绝|' r'予\s*以\s*拒\s*绝', """以下是从招标文件中摘取的内容,文本中序号分明,各信息之间以...............分割。 任务目标: 从文本中筛选所有描述否决投标,拒绝投标,投标、响应无效或类似表述的情况,并返回对应的序号。 要求与指南: 1.投标相关主体:包括但不限于“投标人”、“中标人”、“供应商”、“联合体投标各方”、“响应人”、“应答人”或其他描述投标方的词语。 1.文本中可能存在无关的信息,请准确筛选符合条件的信息,即怎样的情况下,投标相关主体的投标将被否决、拒绝,作为无效标,或者是投标无效、响应无效等,请返回符合条件的信息的序号。 2.若条款内容包含'否决投标的情况说明'这样的笼统描述,而未说明具体的情形,则无需添返回该条款。 输出格式: 以 [x, x, x] 的形式返回,x 为符合条件的信息的序号,为自然数。无需额外返回解释与说明。 如果文本中没有符合条件的信息,请返回 []。 特殊情况: 如果某序号的内容明显分为几部分且一部分内容符合筛选条件,但其他部分明显是无关内容,请返回符合部分的字符串内容代替序号。 示例输出,仅供格式参考: [1,3,4,6] 文本内容:{full_text} """, os.path.join(output_dir, "temp1.txt"), "否决和无效投标情形" ), ( r'废\s*标', """以下是从招标文件中摘取的内容,文本中序号分明,文本内之间的信息以'...............'分割。 任务目标: 请根据以下内容,筛选出 废标项的情况 (明确描述导致 废标 的情况)并返回对应的序号。 要求与指南: 文本中可能存在无关的信息,请准确筛选符合条件的信息,并将符合条件的信息的序号返回。 输出格式: 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的信息的序号,为自然数。无需额外返回解释与说明。 如果文本中没有任何符合条件的废标情况,请返回 []。 示例输出,仅供格式参考: [1,3,4,6] 文本内容:{full_text} """, os.path.join(output_dir, "temp2.txt"), "废标项" ), ( r'不\s*得(?!\s*(分|力))|禁\s*止\s*投\s*标', """以下是从招标文件中摘取的内容,文本中序号分明,文本内的条款以'...............'分割。条款规定了各方不得存在的情形。请根据以下要求进行筛选: **投标相关主体与非投标相关主体的定义**: 投标相关主体:包括但不限于“投标人”、“中标人”、“供应商”、“联合体投标各方”、“响应人”、“应答人”或其他描述投标方的词语。 非投标相关主体:包括但不限于“招标人”、“采购人”、“评标委员会”或其他描述非投标方的词语。 **筛选要求**: 1. **仅筛选**明确描述投标相关主体禁止情形或不得存在的情形的条款,不包含笼统或未具体说明情形的条款。例如: 若条款内容包含'投标人不得存在的其他关联情形'这样的笼统描述,而未说明具体的情形,则无需添加该条款。 2. **排除**仅描述非投标相关主体行为限制或禁止情形的条款,例如“招标人不得泄露信息”或“评标委员会不得收受贿赂”,则无需返回。 3. 若条款同时描述了对投标相关主体与非投标相关主体的行为限制、禁止情形,也需返回。 4. **特殊情况**:如果条款中包含“磋商小组”、”各方“等既能指代投标相关主体又能指代非投标相关主体的词汇: 若在语境中其指代或包含投标相关主体,则应将其考虑在内;否则,排除该条款。 **输出格式**: 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的条款的序号,为自然数。无需额外返回解释与说明。 如果没有符合条件的条款,返回 `[]`。 **示例**: - **符合条件**: - `1. 投标人不得...` → 包含,返回序号 1。 - `3. 联合体投标各方不得...` → 包含,返回序号 3。 - **不符合条件**: - `2. 采购人不得...` → 主语为“采购人”,排除。 -示例输出: [1,3] 请根据上述筛选要求,阅读以下文本内容,并返回符合条件的条款序号, 文本内容:{full_text} """, 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, invalid_docpath, 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} 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 = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx' pdf_path = r'C:\Users\Administrator\Desktop\货物标\zbfiles\output6\招标文件(107国道)_invalid.pdf' output_dir = r"C:\Users\Administrator\Desktop\货物标\zbfiles\output6" # invalid_added = insert_mark(pdf_path) # invalid_added_docx = pdf2docx(invalid_added) invalid_added_docx=r'C:\Users\Administrator\Desktop\fsdownload\355fbb20-e439-48dd-abdd-7645d424af36\invalid_added.docx' results = combine_find_invalid(invalid_added_docx, output_dir) end_time = time.time() print("Results:", json.dumps(results, ensure_ascii=False, indent=4)) # # print("Elapsed time:", str(end_time - start_time))