From 7e8f368324c68ecc76a94ac658b61daf0e14d7e4 Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Fri, 17 Jan 2025 10:47:43 +0800 Subject: [PATCH] =?UTF-8?q?1.17=20=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/general/file2markdown.py | 1 + flask_app/general/test_doubao.py | 178 ------------------ flask_app/general/商务技术评分提取.py | 15 +- flask_app/general/截取pdf通用函数.py | 13 +- .../general/投标人须知正文提取指定内容.py | 4 +- .../投标人须知正文条款提取成json文件.py | 83 ++++---- flask_app/general/无效标和废标公共代码.py | 5 +- flask_app/routes/货物标解析main.py | 2 - flask_app/货物标/截取pdf货物标版.py | 6 +- 9 files changed, 68 insertions(+), 239 deletions(-) delete mode 100644 flask_app/general/test_doubao.py diff --git a/flask_app/general/file2markdown.py b/flask_app/general/file2markdown.py index b655c04..7481732 100644 --- a/flask_app/general/file2markdown.py +++ b/flask_app/general/file2markdown.py @@ -40,6 +40,7 @@ class TextinOcr(object): } return requests.post(url, data=image, headers=headers, params=options) +#调用textIn:pdf/word->markdown def convert_file_to_markdown(file_path, file_name="extract1.txt"): # 获取文件的绝对路径所在的文件夹 output_folder = os.path.dirname(os.path.abspath(file_path)) diff --git a/flask_app/general/test_doubao.py b/flask_app/general/test_doubao.py deleted file mode 100644 index a0ebb47..0000000 --- a/flask_app/general/test_doubao.py +++ /dev/null @@ -1,178 +0,0 @@ -import os -import time - -import requests -from ratelimit import sleep_and_retry, limits -def read_txt_to_string(file_path): - """ - 读取txt文件内容并返回一个包含所有内容的字符串,保持原有格式。 - - 参数: - - file_path (str): txt文件的路径 - - 返回: - - str: 包含文件内容的字符串 - """ - try: - with open(file_path, 'r', encoding='utf-8') as file: # 确保使用适当的编码 - content = file.read() # 使用 read() 保持文件格式 - return content - except FileNotFoundError: - return "错误:文件未找到。" - except Exception as e: - return f"错误:读取文件时发生错误。详细信息:{e}" -def generate_full_user_query(file_path, prompt_template): - """ - 根据文件路径和提示词模板生成完整的user_query。 - - 参数: - - file_path (str): 需要解析的文件路径。 - - prompt_template (str): 包含{full_text}占位符的提示词模板。 - - 返回: - - str: 完整的user_query。 - """ - # 假设extract_text_by_page已经定义,用于提取文件内容 - full_text=read_txt_to_string(file_path) - # 格式化提示词,将提取的文件内容插入到模板中 - user_query = prompt_template.format(full_text=full_text) - - return user_query -def get_total_tokens(text): - """ - 调用 API 计算给定文本的总 Token 数量。 - - 参数: - - text (str): 需要计算 Token 的文本。 - - model (str): 使用的模型名称,默认值为 "ep-20241119121710-425g6"。 - - 返回: - - int: 文本的 total_tokens 数量。 - """ - # API 请求 URL - url = "https://ark.cn-beijing.volces.com/api/v3/tokenization" - - # 获取 API 密钥 - doubao_api_key = os.getenv("DOUBAO_API_KEY") - if not doubao_api_key: - raise ValueError("DOUBAO_API_KEY 环境变量未设置") - - # 请求头 - headers = { - "Content-Type": "application/json", - "Authorization": "Bearer " + doubao_api_key - } - model = "ep-20241119121710-425g6" - # 请求体 - payload = { - "model": model, - "text": [text] # API 文档中要求 text 是一个列表 - } - - try: - response = requests.post(url, headers=headers, json=payload) - response.raise_for_status() - response_data = response.json() - total_tokens=response_data["data"][0]["total_tokens"] - return total_tokens - except Exception as e: - print(f"获取 Token 数量失败:{e}") - return 0 - -@sleep_and_retry -@limits(calls=10, period=1) # 每秒最多调用10次 -def doubao_model(full_user_query, need_extra=False): - print("call doubao...") - # 相关参数 - url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions" - doubao_api_key = os.getenv("DOUBAO_API_KEY") - - # 定义主模型和备用模型 - models = { - "pro_32k": "ep-20241119121710-425g6", # 豆包Pro 32k模型 - "pro_128k": "ep-20241119121743-xt6wg" # 128k模型 - } - - # 判断用户查询字符串的长度 - token_count = get_total_tokens(full_user_query) - if token_count > 31500: - selected_model = models["pro_128k"] # 如果长度超过32k,直接使用128k模型 - else: - selected_model = models["pro_32k"] # 默认使用32k模型 - - # 请求头 - headers = { - "Content-Type": "application/json", - "Authorization": "Bearer " + doubao_api_key - } - - max_retries_429 = 2 # 针对 429 错误的最大重试次数 - max_retries_other = 1 # 针对其他错误的最大重试次数 - attempt = 0 - response = None # 确保 response 被定义 - - while True: - # 请求数据 - data = { - "model": selected_model, - "messages": [ - { - "role": "user", - "content": full_user_query - } - ], - "temperature": 0.2 - } - try: - response = requests.post(url, headers=headers, json=data) # 设置超时时间为10秒 - response.raise_for_status() # 如果响应状态码不是200,将引发HTTPError - - # 获取响应 JSON - response_json = response.json() - - # 获取返回内容 - content = response_json["choices"][0]["message"]["content"] - - # 获取 completion_tokens - completion_tokens = response_json["usage"].get("completion_tokens", 0) - - # 根据 need_extra 返回不同的结果 - if need_extra: - return content, completion_tokens - else: - return content - - except requests.exceptions.RequestException as e: - # 获取状态码并处理不同的重试逻辑 - status_code = response.status_code if response is not None else None - print(f"请求失败,状态码: {status_code}") - print("请求失败,完整的响应内容如下:") - if response is not None: - print(response.text) # 打印原始的响应内容,可能是 JSON 格式,也可能是其他格式 - - # 如果是 429 错误 - if status_code == 429: - if attempt < max_retries_429: - wait_time = 2 if attempt == 0 else 4 - print(f"状态码为 429,等待 {wait_time} 秒后重试...") - time.sleep(wait_time) - else: - print(f"状态码为 429,已达到最大重试次数 {max_retries_429} 次。") - break # 超过最大重试次数,退出循环 - else: - # 针对其他错误 - if attempt < max_retries_other: - print("非 429 错误,等待 1 秒后重试...") - time.sleep(1) - else: - print(f"非 429 错误,已达到最大重试次数 {max_retries_other} 次。") - break # 超过最大重试次数,退出循环 - - attempt += 1 # 增加重试计数 - - # 如果到这里,说明所有尝试都失败了 - print(f"请求失败,已达到最大重试次数。") - if need_extra: - return None, 0 - else: - return None \ No newline at end of file diff --git a/flask_app/general/商务技术评分提取.py b/flask_app/general/商务技术评分提取.py index ed47eaa..5fe02a7 100644 --- a/flask_app/general/商务技术评分提取.py +++ b/flask_app/general/商务技术评分提取.py @@ -241,21 +241,12 @@ def reorganize_data(input_dict, include=None): reorganized["商务评分"][package] = categories["商务评分"] return reorganized -# 格式要求: -# 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,具体规则如下: -# 1. 如果评审因素存在嵌套,使用嵌套键值对表示: -# -主评审因素的键名后需附加括号,表示该主因素下所有子因素总分,例如:产品技术响应(8分) -# -子评审因素作为主评审因素的内层键名 -# 2. 如果评审因素不存在嵌套,那么键名就是该评审因素 -# 3. 每个评审因素的最内层键值都是列表,列表中包含描述评分及要求的字典,字典需包含以下键: -# '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 -# '要求':说明评分标准。 -# 4.若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注',值为该要求。 + def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): # 定义默认的评审结果字典 DEFAULT_EVALUATION_REVIEW = { - "技术评分": "", - "商务评分": "" + "技术评分": "未解析到'技术评分'项!", + "商务评分": "未解析到'商务评分'项!" } # 如果 truncate_file 是空字符串,直接返回包含空字符串的字典 if not evaluation_method_path: diff --git a/flask_app/general/截取pdf通用函数.py b/flask_app/general/截取pdf通用函数.py index eea8be5..9ef489b 100644 --- a/flask_app/general/截取pdf通用函数.py +++ b/flask_app/general/截取pdf通用函数.py @@ -269,10 +269,9 @@ def extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, for i, page in enumerate(pdf_document.pages[begin_page:end_limit], start=begin_page): text = page.extract_text() or "" cleaned_text = clean_page_content(text, common_header) - if output_suffix == "tobidders_notice": + if output_suffix == "tobidders_notice2": #extract_pages_twice_tobidders_notice会调用该代码 catalog_pattern = regex.compile(r'\s*目\s*录\s*$', regex.MULTILINE) - if exclusion_pattern and flag and (start_page is not None) and regex.search(exclusion_pattern, - cleaned_text): + if exclusion_pattern and flag and (start_page is not None) and regex.search(exclusion_pattern,cleaned_text): flag = False continue if begin_page == 0 and catalog_pattern.search(cleaned_text): @@ -287,14 +286,17 @@ def extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, start_page = i continue if start_page is not None: - if output_suffix == "tobidders_notice" or output_suffix == 'notice': # 因为投标人须知前附表中经常出现'见招标公告',导致被not regex.search(begin_pattern, cleaned_text)pass掉 + if output_suffix in ["tobidders_notice", "notice", "tobidders_notice2"]: # 使用列表判断多个匹配项 + # 判断 end_pattern 是否匹配且当前页大于起始页 if regex.search(end_pattern, cleaned_text) and i > start_page: end_page = i break else: + # 如果 end_pattern 匹配且 begin_pattern 不匹配 if regex.search(end_pattern, cleaned_text) and not regex.search(begin_pattern, cleaned_text): end_page = i break + return start_page, end_page @@ -410,7 +412,7 @@ def extract_pages_tobidders_notice(pdf_path, output_folder, begin_page, common_h cleaned_text = clean_page_content(text, common_header) # 如果已经找到中间页,且当前页匹配排除模式,则跳过 - if exclusion_pattern and regex.search(exclusion_pattern, cleaned_text) and mid_page is not None: + if exclusion_pattern and mid_page is not None and regex.search(exclusion_pattern, cleaned_text): continue # 如果为第 0 页之后的目录,直接跳过 @@ -532,6 +534,7 @@ def extract_pages_twice_tobidders_notice(pdf_document, common_header, begin_page return start_page1, end_page1, end_page1 # 如果不包含排除关键词,继续提取第二部分 + output_suffix='tobidders_notice2' _, end_page2 = extract_pages_generic(pdf_document, end_pattern, end_pattern, start_page2 - 1, common_header, exclusion_pattern, output_suffix) diff --git a/flask_app/general/投标人须知正文提取指定内容.py b/flask_app/general/投标人须知正文提取指定内容.py index 9f5f1e2..d2a875a 100644 --- a/flask_app/general/投标人须知正文提取指定内容.py +++ b/flask_app/general/投标人须知正文提取指定内容.py @@ -377,7 +377,7 @@ def extract_from_notice(merged_baseinfo_path, clause_path, type): if clause_path and clause_path.strip(): with open(clause_path, 'r', encoding='utf-8') as file: data = json.load(file) - if len(data) >= 60: + if len(data) >= 60: #默认clause中少于60条视为json提取失败! # 尝试使用大章节筛选 extracted_data = extract_between_sections(data, target_values,flag) if extracted_data: @@ -406,7 +406,7 @@ def extract_from_notice(merged_baseinfo_path, clause_path, type): if __name__ == "__main__": # file_path = 'C:\\Users\\Administrator\\Desktop\\fsdownload\\3bffaa84-2434-4bd0-a8ee-5c234ccd7fa0\\clause1.json' merged_baseinfo_path=r"C:\Users\Administrator\Desktop\fsdownload\b29de31a-297e-42cf-b9ba-6859e530a472\ztbfile_merged_baseinfo.pdf" - clause_path=r"C:\Users\Administrator\Desktop\fsdownload\b29de31a-297e-42cf-b9ba-6859e530a472\clause1.json" + clause_path=r"D:\flask_project\flask_app\static\output\output1\2c4be864-bdab-405d-95cb-9d945d8627b3\tmp\clause1.json" try: res = extract_from_notice(merged_baseinfo_path,clause_path, 2) # 可以改变此处的 type 参数测试不同的场景 res2 = json.dumps(res, ensure_ascii=False, indent=4) diff --git a/flask_app/general/投标人须知正文条款提取成json文件.py b/flask_app/general/投标人须知正文条款提取成json文件.py index 9bb9827..58028e8 100644 --- a/flask_app/general/投标人须知正文条款提取成json文件.py +++ b/flask_app/general/投标人须知正文条款提取成json文件.py @@ -4,7 +4,9 @@ import re import regex from PyPDF2 import PdfReader -from flask_app.货物标.截取pdf货物标版 import clean_page_content,extract_common_header +from flask_app.货物标.截取pdf货物标版 import clean_page_content, extract_common_header + + def compare_headings(current, new): """ 比较两个标题的层次关系,并确保新标题比当前标题大且最高位数字差值不超过5。 @@ -41,7 +43,8 @@ def should_add_newline(content, keywords, max_length=20): content_str = ''.join(content).strip() return any(keyword in content_str for keyword in keywords) or len(content_str) <= max_length -def handle_content_append(current_content, line_content, append_newline, keywords,in_special_section): + +def handle_content_append(current_content, line_content, append_newline, keywords, in_special_section): if append_newline: if should_add_newline(current_content, keywords): current_content.append('\n') # 添加换行符 @@ -51,6 +54,7 @@ def handle_content_append(current_content, line_content, append_newline, keyword current_content.append('\n') return append_newline + def parse_text_by_heading(text): keywords = ['包含', '以下'] data = {} @@ -64,14 +68,14 @@ def parse_text_by_heading(text): temp_title = None # 临时存储以点号开头但不带数字的标题 # 定义所有需要的正则表达式模式 - 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*(.*)$') # 初始扫描时匹配 A内容 或 A. 内容 - pattern_letter = re.compile(r'^([A-Z])[..、]\s*(.*)$') # 主循环中严格匹配 A. 内容 + 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*(.*)$') # 初始扫描时匹配 A内容 或 A. 内容 # pattern_arabic = re.compile(r'^(\d+、)\s*(.+)$') # 1、内容 initial_heading_pattern = None - special_section_keywords = ['文件的组成', '文件的构成','文件包括:','文件包括:','雷同认定','包括以下内容'] # 定义特殊章节关键词 + special_section_keywords = ['文件的组成', '文件的构成', '文件包括:', '文件包括:', '雷同认定', + '包括以下内容'] # 定义特殊章节关键词 in_special_section = False # 标志是否在特殊章节中 lines = text.split('\n') @@ -94,26 +98,24 @@ def parse_text_by_heading(text): # 检查是否匹配任何标题模式 patterns = [ r'^(?