diff --git a/flask_app/general/doubao.py b/flask_app/general/doubao.py index bd05cf0..f3f1562 100644 --- a/flask_app/general/doubao.py +++ b/flask_app/general/doubao.py @@ -185,7 +185,7 @@ def read_txt_to_string(file_path): return f"错误:读取文件时发生错误。详细信息:{e}" @sleep_and_retry -@limits(calls=10, period=1) # 每秒最多调用4次 +@limits(calls=10, period=1) # 每秒最多调用10次 def doubao_model(full_user_query): print("call doubao...") # 相关参数 diff --git a/flask_app/general/商务技术评分提取.py b/flask_app/general/商务技术评分提取.py new file mode 100644 index 0000000..f18f0db --- /dev/null +++ b/flask_app/general/商务技术评分提取.py @@ -0,0 +1,419 @@ +import json +import re +import time +from collections import defaultdict + +from flask_app.general.通义千问long import upload_file, qianwen_long + + +def remove_unknown_scores(data): + if isinstance(data, dict): + return { + k: remove_unknown_scores(v) + for k, v in data.items() + if not (k == "评分" and v in ["未知", "/", ""]) + } + elif isinstance(data, list): + return [remove_unknown_scores(item) for item in data] + else: + return data +def combine_technical_and_business(data, target_values): + data=remove_unknown_scores(data) + extracted_data = {} # 根级别存储所有数据 + technical_found = False + business_found = False + + def extract_nested(data, parent_key='', is_technical=False, is_business=False): + nonlocal technical_found, business_found + if isinstance(data, dict): + for key, value in data.items(): + current_key = f"{parent_key}.{key}" if parent_key else key + + # 检查是否为技术标的内容 + if any(target in key for target in target_values): + if not is_technical: + extracted_data[key] = value + technical_found = True + continue + + # 默认其他所有内容都归为商务标 + else: + if not is_business: + if '商务评分' not in extracted_data: + extracted_data['商务评分'] = {} # 确保它是字典 + extracted_data['商务评分'][key] = value + business_found = True + continue + + if isinstance(value, dict) or isinstance(value, list): + extract_nested(value, current_key, is_technical, is_business) + + elif isinstance(data, list): + for index, item in enumerate(data): + extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + + extract_nested(data) + + if not technical_found: + extracted_data['技术评分'] = '' + if not business_found: + extracted_data['商务评分'] = '' + + return extracted_data + +def parse_json_with_duplicates(raw_string): + """ + 解析具有重复键的 JSON 字符串,将所有重复的键值对存储为列表。 + + Args: + json_string (str): 需要解析的 JSON 字符串。 + + Returns: + dict: 解析后的字典,重复的键对应的值为列表。 + + eg:输入:"综合实力": { + "评分": "2分", + "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" + }, + "综合实力": { + "评分": "2分", + "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" + } + 输出:"综合实力": [ + { + "评分": "2分", + "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" + }, + { + "评分": "2分", + "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" + }] + """ + + def custom_object_pairs_hook(pairs): + d = defaultdict(list) + for key, value in pairs: + try: + # 如果值是字典或列表,递归处理 + if isinstance(value, dict): + value = process_dict(value) + elif isinstance(value, list): + value = process_list(value) + d[key].append(value) + except Exception as e: + d[key].append(value) # 根据需求决定是否跳过或保留原值 + # 将有多个值的键转换为列表,单个值的键保持原样 + return {key: (values if len(values) > 1 else values[0]) for key, values in d.items()} + + def process_dict(d): + """ + 递归处理字典,确保所有重复键的值为列表。 + + Args: + d (dict): 需要处理的字典。 + + Returns: + dict: 处理后的字典。 + """ + try: + return custom_object_pairs_hook(d.items()) + except Exception as e: + return {} + + def process_list(l): + """ + 递归处理列表,确保列表中的所有字典也被处理。 + + Args: + l (list): 需要处理的列表。 + + Returns: + list: 处理后的列表。 + """ + try: + return [process_dict(item) if isinstance(item, dict) else item for item in l] + except Exception as e: + return [] + + """输入字符串,提取 { 和 } 之间的内容,并将其解析为字典""" + if not raw_string.strip(): + return {} + match = re.search(r'\{[\s\S]*\}', raw_string) + if match: + try: + json_string = match.group(0) + return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook) + except json.JSONDecodeError as e: + print(f"json_utils: extract_content_from_json: JSON decode error: {e}") + return {} + else: + print("json_utils: extract_content_from_json: No valid JSON content found.") + return {} + +# 防止外键只有一个'一包'的情况 +def process_data_based_on_key(data): + exclude_word = ["包", "未知", "评分因素"] + # 获取字典的键列表 + keys = list(data.keys()) + # 检查键的数量是否为1并且 exclude_word 中的任何词包含在 keys[0] 中 + if len(keys) == 1 and any(word in keys[0] for word in exclude_word): + # 返回内层的字典 + return data[keys[0]] + + # 如果条件不满足,则返回原始字典 + return data + +def reorganize_data(input_dict, include=None): + """ + 重组输入字典,将“技术评分”和“商务评分”提升为最外层键, + 并将包含在 include 列表中的包名的数据嵌套在相应的评分类别下。 + + 如果 input_dict 的顶层键不包含任何 include 列表中的项,则返回原始字典。 + + :param input_dict: 原始输入字典 + :param include: 包名列表,例如 ['一包', '二包', '三包'] + :return: 重组后的字典 + """ + if include is None: + include = [] + + # 检查是否有任何顶层键包含在 include 列表中 + has_include = any(key in include for key in input_dict.keys()) + + if not has_include: + # 没有包含任何指定的包名,直接返回原始字典 + return input_dict + + # 初始化新的字典结构 + reorganized = { + "技术评分": {}, + "商务评分": {} + } + + # 遍历每一个包(如 "一包", "二包") + for package, categories in input_dict.items(): + # 处理技术评分 + if "技术评分" in categories: + reorganized["技术评分"][package] = categories["技术评分"] + + # 处理商务评分 + if "商务评分" in categories: + reorganized["商务评分"][package] = categories["商务评分"] + + return reorganized + +def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): + # 定义默认的评审结果字典 + DEFAULT_EVALUATION_REVIEW = { + "技术评分": "", + "商务评分": "" + } + # 如果 truncate_file 是空字符串,直接返回包含空字符串的字典 + if not evaluation_method_path: + return DEFAULT_EVALUATION_REVIEW.copy() + + def run_first_query(file_path): + # 上传文件并获取文件ID + file_id = upload_file(file_path) + + # 定义用户查询 + query = ( + "根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求,如果有,返回'是',否则返回'否'。" + ) # 应对竞争性谈判这种无评分要求的情况 + + # 执行查询 + return qianwen_long(file_id, query),file_id + def run_second_qeury(file_id): + # 执行 user_query 相关的逻辑 + user_query_1 = ( + """ + 你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格,请你列出该文件的技术评分,商务评分,投标报价评分以及它们对应的具体评分要求,请以JSON格式返回结果。 + 格式要求: + 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,外层键名为主评审因素,嵌套的子评审因素作为内层键名,最内键值为列表,列表中包含描述评分及要求的字典。每个字典的键包括: + '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 + '要求':说明评分标准。 + 若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注' ,值为该要求;请勿擅自添加不属于'评审因素'的键名。 + + 要求与指南: + 1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 + 2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 + 3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' + 4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 + 5. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 + 6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称,若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + + 以下为示例输出,仅供格式参考: + { + "一包": { + "技术评分": { + "实施方案":{ + "总体实施方案":[ + { + "评分":8, + "要求":"根据投标人总体实施方案进行评分" + } + ], + "项目实施要点":[ + { + "评分":8, + "要求":"根据投标人对项目实施要点、难点进行评分" + } + ] + }, + "主要监理岗位的职责": [ + { + "评分": "4分", + "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" + } + ], + "备注": "技术标采用定性方式评审的, “不合格”仅限于投标文件出现违反国家强制性条文标准的情况,否则技术标评审结论为“合格” 。" + }, + "商务评分": { + "控制系统内主板": [ + { + "评分": "10分", + "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" + } + ], + "制造商技术实力": [ + { + "评分": "3分", + "要求": "一级证书得3分,二级证书得1分,其他不得分" + }, + { + "评分": "2分", + "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" + } + ] + }, + "投标报价评分": { + "投标报价是否出现违反计价规范": [ + { + "评分": "合格制", + "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" + } + ] + } + } + } + """ + ) + user_query_2 = ( + """ +你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格,请你列出该文件的技术评分,商务评分,投标报价评分以及它们对应的具体评分要求,请以JSON格式返回结果。 +格式要求: +请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,外层键名为主评审因素,嵌套的子评审因素作为内层键名,最内键值为列表,列表中包含描述评分及要求的字典。每个字典的键包括: + '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 + '要求':说明评分标准。 +若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注' ,值为该要求;请勿擅自添加不属于'评审因素'的键名。 + +要求与指南: +1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 +2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 +3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' +4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 +5. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 +6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称,若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + +以下为示例输出,仅供格式参考: +{ + "一包": { + "技术评分": { + "主要监理岗位的职责": [ + { + "评分": "4分", + "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" + } + ], + "备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分(50分)不得分。" + }, + "商务评分": { + "控制系统内主板": [ + { + "评分": "10分", + "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" + } + ], + "制造商技术实力": [ + { + "评分": "3分", + "要求": "一级证书得3分,二级证书得1分,其他不得分" + }, + { + "评分": "2分", + "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" + } + ] + }, + "投标报价评分": { + "投标报价是否出现违反计价规范": [ + { + "评分": "合格制", + "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" + } + ] + } + } +} + """ + ) + # 执行第二个查询 + user_query = user_query_1 if zb_type == 1 else user_query_2 + evaluation_res = qianwen_long(file_id, user_query) # 有些重复的键名,只有qianwen_long_text能保留 + # print(evaluation_res) + # 清理和处理响应 + cleaned_evaluation_res = parse_json_with_duplicates(evaluation_res) # 处理重复键名的情况 + result_data = process_data_based_on_key(cleaned_evaluation_res) # 处理不知名外键的情况 + include = ['一包', '二包', '三包', '四包', '五包'] + target_values = ['技术', '设计', '实施'] + updated_jsons = {} + + # 检查是否有外层键匹配 include 列表 + if any(key for key in result_data if + any(included in key for included in include)): # 检查result_data中的任何键是否包含include列表中的任意一个项。 + # 有匹配的项,处理这些项 + for key in result_data: + if any(item in key for item in include): + inner_dict = result_data[key] + updated_jsons[key] = combine_technical_and_business(inner_dict, + target_values) # 对于分包,单独对分包内的'技术评分''商务评分'作处理 + else: + # 没有匹配的项,对整个字典运行 + updated_jsons = combine_technical_and_business(result_data, target_values) + final_res = reorganize_data(updated_jsons, include) # 重新组织字典,尤其是分包的情况 + return final_res + try: + judge_res,file_id = run_first_query(evaluation_method_path) + # 检查 judge_res 的内容 + if '是' in judge_res: + # 执行 user_query 相关的逻辑 + return run_second_qeury(file_id) + else: + judge_res,file_id=run_first_query(invalid_path) + if '是' in judge_res: + # 执行 user_query 相关的逻辑 + return run_second_qeury(file_id) + else: + # 如果 judge 是 False,直接返回默认的技术标和商务标的结构 + result_data = {} + result_data['技术评分'] = '本招标文件没有技术评分!' + result_data['商务评分'] = '本招标文件没有商务评分!' + return result_data + except Exception as e: + print(f"Error in combine_evaluation_standards: {e}") + # 在出错时返回默认的包含空字符串的字典 + return DEFAULT_EVALUATION_REVIEW.copy() + +if __name__ == "__main__": + start_time=time.time() + # truncate_file=r"C:\Users\Administrator\Desktop\招标文件-采购类\tmp2\2024-新疆-塔城地区公安局食药环分局快检实验室项目_evaluation_method.pdf" + evaluation_method_path = r'C:\Users\Administrator\Desktop\招标文件\output2\zbtest7_evaluation_method.pdf' + invalid_path=r'C:\Users\Administrator\Desktop\招标文件\output2\zbtest7_evaluation_method.pdf' + # truncate_file = "C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件(统计局智能终端二次招标)_evaluation_method.pdf" + # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\广水市妇幼招标文件最新(W改)_evaluation_method.pdf" + # truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf" + # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf" + res = combine_evaluation_standards(evaluation_method_path,invalid_path,1) + print(json.dumps(res, ensure_ascii=False, indent=4)) + end_time=time.time() + print("elapsed time:"+str(end_time-start_time)) \ No newline at end of file diff --git a/flask_app/general/投标人须知正文提取指定内容.py b/flask_app/general/投标人须知正文提取指定内容.py index bc46777..c1eb081 100644 --- a/flask_app/general/投标人须知正文提取指定内容.py +++ b/flask_app/general/投标人须知正文提取指定内容.py @@ -287,6 +287,40 @@ def extract_sections(data, target_values): return result +#提取两个大标题之间的内容 +def extract_between_sections(data, target_values): + target_found = False + extracted_data = {} + current_section_title = "" + section_pattern = re.compile(r'^[一二三四五六七八九十]+$') # 匹配 "一", "二", "三" 等大标题 + current_block = {} + + # 遍历所有键值对 + for key, value in data.items(): + # 只匹配形如 "一": "竞争性磋商响应文件" 的章节标题 + if section_pattern.match(key): #匹配到大标题... + if target_found: + # 如果已经找到了符合的章节,并且遇到了另一个章节 + # 保存当前块并重置 + if current_block: + extracted_data[current_section_title] = current_block + current_block = {} + target_found = False + + # 检查当前标题是否包含 target_values 中的任意关键词 + if any(tv in value for tv in target_values): + target_found = True # 找到了目标章节,开始捕获后续内容 + current_section_title = value # 保存章节标题内容 + + elif target_found: # 匹配到普通序号... + current_block[key] = value + + # 保存最后一个块(如果有的话) + if current_block: + extracted_data[current_section_title] = current_block + + return extracted_data + def get_requirements_with_gpt(merged_baseinfo_path, selection): """ 根据 selection 的值选择相应的用户查询,并调用大模型获取要求。 @@ -395,7 +429,7 @@ def get_requirements_with_gpt(merged_baseinfo_path, selection): return {"error": f"无效的 selection 值: {selection}. 请选择 1、2 或 3。"} # 调用大模型并处理响应 try: - res = qianwen_long_stream(file_id, user_query) + res = qianwen_long_stream(file_id, user_query,1) cleaned_res = clean_json_string(res) return cleaned_res except Exception as e: diff --git a/flask_app/general/通义千问long.py b/flask_app/general/通义千问long.py index e2a067d..99bf9a6 100644 --- a/flask_app/general/通义千问long.py +++ b/flask_app/general/通义千问long.py @@ -1,12 +1,10 @@ import ast import json +import logging import re import threading from functools import wraps - -from flask import g from ratelimit import limits, sleep_and_retry -import random import time from pathlib import Path from openai import OpenAI @@ -56,8 +54,34 @@ def shared_rate_limit(func): rate_limiter() # 通过共享的限流器 return func(*args, **kwargs) return wrapper + +def extract_error_details(error_message): + """ + 从错误消息中提取错误代码和内部错误代码。 + 假设错误消息的格式包含 'Error code: XXX - {...}' + """ + # 提取数值型错误代码 + error_code_match = re.search(r'Error code:\s*(\d+)', error_message) + error_code = int(error_code_match.group(1)) if error_code_match else None + + # 提取内部错误代码字符串(如 'data_inspection_failed') + error_code_string = None + error_dict_match = re.search(r'Error code:\s*\d+\s*-\s*(\{.*\})', error_message) + if error_dict_match: + error_dict_str = error_dict_match.group(1) + try: + # 使用 ast.literal_eval 解析字典字符串 + error_dict = ast.literal_eval(error_dict_str) + error_code_string = error_dict.get('error', {}).get('code') + print(error_code_string) + except Exception as e: + print(f"解析错误消息失败: {e}") + + return error_code, error_code_string @shared_rate_limit def qianwen_long(file_id, user_query, max_retries=2, backoff_factor=1.0): + logger = logging.getLogger('model_log') # 通过日志名字获取记录器 + # logger.info(f"Received query: {user_query}") """ 基于上传的文件 ID 和用户查询生成响应,并在失败时自动重试。 参数: @@ -97,59 +121,35 @@ def qianwen_long(file_id, user_query, max_retries=2, backoff_factor=1.0): except Exception as exc: # 提取错误代码 error_code, error_code_string = extract_error_details(str(exc)) - print(f"第 {attempt} 次尝试失败,查询:'{user_query}',错误:{exc}") + logger.error(f"第 {attempt} 次尝试失败,查询:'{user_query}',错误:{exc}", exc_info=True) if error_code == 429: #超qps/qpm if attempt <= max_retries: sleep_time = backoff_factor * (2 ** (attempt - 1)) # 指数退避 - print(f"错误代码为 429,将在 {sleep_time} 秒后重试...") + logger.warning(f"错误代码为 429,将在 {sleep_time} 秒后重试...") time.sleep(sleep_time) else: print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败(429 错误)。") break elif error_code == 400 and error_code_string in ['data_inspection_failed', 'ResponseTimeout','DataInspectionFailed','response_timeout','request_timeout',"RequestTimeOut"]: - print(f"错误代码为 400 - {error_code_string},将调用 qianwen_long_stream 执行一次...") + logger.warning(f"错误代码为 400 - {error_code_string},将调用 qianwen_long_stream 执行一次...") try: # 超时就调用 qianwen_long_stream return qianwen_long_stream(file_id, user_query, max_retries=0) # 禁用内部重试 except Exception as stream_exc: - print(f"调用 qianwen_long_stream 时出错:{stream_exc}") + logger.error(f"调用 qianwen_long_stream 时出错:{stream_exc}", exc_info=True) break # 跳出循环,不再重试 else: # 对于非 429 和非特定 400 错误,不进行重试,直接抛出异常 - print(f"遇到非 429 或非 'data_inspection_failed' 的 400 错误(错误代码:{error_code}),不进行重试。") + logger.error(f"遇到非 429 或非 'data_inspection_failed' 的 400 错误(错误代码:{error_code}),不进行重试。") break return "" - -def extract_error_details(error_message): - """ - 从错误消息中提取错误代码和内部错误代码。 - 假设错误消息的格式包含 'Error code: XXX - {...}' - """ - # 提取数值型错误代码 - error_code_match = re.search(r'Error code:\s*(\d+)', error_message) - error_code = int(error_code_match.group(1)) if error_code_match else None - - # 提取内部错误代码字符串(如 'data_inspection_failed') - error_code_string = None - error_dict_match = re.search(r'Error code:\s*\d+\s*-\s*(\{.*\})', error_message) - if error_dict_match: - error_dict_str = error_dict_match.group(1) - try: - # 使用 ast.literal_eval 解析字典字符串 - error_dict = ast.literal_eval(error_dict_str) - error_code_string = error_dict.get('error', {}).get('code') - print(error_code_string) - except Exception as e: - print(f"解析错误消息失败: {e}") - - return error_code, error_code_string - - @shared_rate_limit def qianwen_long_stream(file_id, user_query, max_retries = 2, backoff_factor = 1.0): + logger = logging.getLogger('model_log') # 通过日志名字获取记录器 + # logger.info(f"Received query: {user_query}") """ 使用之前上传的文件,根据用户查询生成响应,并实时显示流式输出。 参数: @@ -211,27 +211,27 @@ def qianwen_long_stream(file_id, user_query, max_retries = 2, backoff_factor = 1 except Exception as exc: # 提取错误代码 error_code, error_code_string = extract_error_details(str(exc)) - print(f"第 {attempt} 次尝试失败,查询:'{user_query}',错误:{exc}") + logger.error(f"第 {attempt} 次尝试失败,查询:'{user_query}',错误:{exc}", exc_info=True) if error_code == 429: if attempt <= max_retries: sleep_time = backoff_factor * (2 ** (attempt - 1)) # 指数退避 - print(f"错误代码为 429,将在 {sleep_time} 秒后重试...") + logger.warning(f"错误代码为 429,将在 {sleep_time} 秒后重试...") time.sleep(sleep_time) else: - print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败(429 错误)。") + logger.error(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败(429 错误)。") break elif error_code == 400 and error_code_string in ['data_inspection_failed', 'ResponseTimeout', 'DataInspectionFailed', 'response_timeout','RequestTimeOut','request_timeout']: if attempt == 1: # 只重试一次 - print(f"错误代码为 400 - {error_code_string},将立即重试...") + logger.warning(f"错误代码为 400 - {error_code_string},将立即重试...") continue # 直接跳到下一次循环(即重试一次) else: - print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败(400 - {error_code_string})。") + logger.error(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败(400 - {error_code_string})。") break else: # 对于非 429 和非特定 400 错误,不进行重试,直接抛出异常 - print(f"遇到非 429 或非 'data_inspection_failed...' 的 400 错误(错误代码:{error_code}),不进行重试。") + logger.error(f"遇到非 429 或非 'data_inspection_failed...' 的 400 错误(错误代码:{error_code}),不进行重试。") break # 如果所有尝试都失败了,返回空字符串 diff --git a/flask_app/logger_setup.py b/flask_app/logger_setup.py index ea7e0e3..f293211 100644 --- a/flask_app/logger_setup.py +++ b/flask_app/logger_setup.py @@ -1,8 +1,11 @@ # flask_app/logger_setup.py import logging +import sys import uuid from datetime import datetime, timedelta +from logging.handlers import RotatingFileHandler + from flask import g import os @@ -22,6 +25,27 @@ class CSTFormatter(logging.Formatter): s = ct.strftime("%Y-%m-%d %H:%M:%S") return s +def create_logger_main(log_name, log_dir='/flask_project/flask_app/static/output', max_bytes=10*1024*1024, backup_count=5): + """ + 根据名称创建全局日志记录器。 + """ + os.makedirs(log_dir, exist_ok=True) # 确保日志目录存在 + + logger = logging.getLogger(log_name) + if not logger.hasHandlers(): # 防止重复添加处理器 + logger.setLevel(logging.INFO) + + # 创建文件处理器 + file_handler = RotatingFileHandler( + os.path.join(log_dir, f'{log_name}.log'), + maxBytes=max_bytes, + backupCount=backup_count, + encoding='utf-8' + ) + file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + logger.addHandler(file_handler) + + return logger def create_logger(app, subfolder): """ 创建一个唯一的 logger 和对应的输出文件夹。 diff --git a/flask_app/old_version/商务评分技术评分整合.py b/flask_app/old_version/商务评分技术评分整合.py new file mode 100644 index 0000000..2f81642 --- /dev/null +++ b/flask_app/old_version/商务评分技术评分整合.py @@ -0,0 +1,102 @@ +# -*- encoding:utf-8 -*- +import json + +from flask_app.general.json_utils import clean_json_string +from flask_app.general.商务技术评分提取 import combine_technical_and_business +from flask_app.general.通义千问long import upload_file, qianwen_long + +def combine_evaluation_standards(evaluation_method): + # 商务标、技术标评分项:千问 + file_id = upload_file(evaluation_method) + # user_query_2 = ( + # "根据该文档中的评标办法前附表,请你列出该文件的技术评分,商务评分,投标报价评审标准以及它们对应的具体评分要求,若对应内容中存在其他信息,在键名如'技术评分'中新增子键名'备注'存放该信息。如果评分内容(因素)不是这3个,则返回文档中给定的评分内容(因素)以及它的评分要求。请以json格式返回结果,不要回答有关形式、资格、响应性评审标准的内容") + user_query_2 = ( + """ +你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格,请你列出该文件的技术评分,商务评分,投标报价评分以及它们对应的具体评分要求,请以JSON格式返回结果。 +格式要求: + 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,外层键名为主评审因素,嵌套的子评审因素作为内层键名,最内键值为列表,列表中包含描述评分及要求的字典。每个字典的键包括: + '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 + '要求':说明评分标准。 + 若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注' ,值为该要求;请勿擅自添加不属于'评审因素'的键名。 + +要求与指南: + 1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 + 2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 + 3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' + 4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 + 5. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 + 6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称,若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + +以下为示例输出,仅供格式参考: + { + "一包": { + "技术评分": { + "实施方案":{ + "总体实施方案":[ + { + "评分":8, + "要求":"根据投标人总体实施方案进行评分" + } + ], + "项目实施要点":[ + { + "评分":8, + "要求":"根据投标人对项目实施要点、难点进行评分" + } + ] + }, + "主要监理岗位的职责": [ + { + "评分": "4分", + "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" + } + ], + "备注": "技术标采用定性方式评审的, “不合格”仅限于投标文件出现违反国家强制性条文标准的情况,否则技术标评审结论为“合格” 。" + }, + "商务评分": { + "控制系统内主板": [ + { + "评分": "10分", + "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" + } + ], + "制造商技术实力": [ + { + "评分": "3分", + "要求": "一级证书得3分,二级证书得1分,其他不得分" + }, + { + "评分": "2分", + "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" + } + ] + }, + "投标报价评分": { + "投标报价是否出现违反计价规范": [ + { + "评分": "合格制", + "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" + } + ] + } + } + } + """ + ) + + evaluation_res = qianwen_long(file_id, user_query_2) + # print(evaluation_res) + target_values1 = ['技术标','技术部分','设计', '实施',"技术评分"] + update_json = combine_technical_and_business(clean_json_string(evaluation_res), target_values1) + return update_json #商务标技术标整合 +if __name__ == "__main__": + # evaluation_method="C:\\Users\\Administrator\\Desktop\\招标文件\\output2\\zbtest3_evaluation_method.pdf" + evaluation_method=r'C:\Users\Administrator\Desktop\招标文件\output2\zbtest7_evaluation_method.pdf' + # evaluation_method= r"C:\Users\Administrator\Desktop\new招标文件\output2\gcHBDL-2024-0017-001-招标文件_evaluation_method.pdf" + evaluation_standards_res=combine_evaluation_standards(evaluation_method) + # 从结果中提取"商务标"和"技术标" + technical_standards = {"技术评分": evaluation_standards_res.get("技术评分", {})} + commercial_standards = {"商务评分": evaluation_standards_res.get("商务评分", {})} + # 返回技术标和商务标 + print(json.dumps(technical_standards,ensure_ascii=False,indent=4)) + print(json.dumps(commercial_standards, ensure_ascii=False, indent=4)) \ No newline at end of file diff --git a/flask_app/old_version/工程标商务技术评分.py b/flask_app/old_version/工程标商务技术评分.py new file mode 100644 index 0000000..d820c3b --- /dev/null +++ b/flask_app/old_version/工程标商务技术评分.py @@ -0,0 +1,51 @@ +def combine_technical_and_business(data, target_values1, target_values2): + extracted_data = {} # 根级别存储所有数据 + technical_found = False + business_found = False + + def extract_nested(data, parent_key='', is_technical=False, is_business=False): + nonlocal technical_found, business_found + if isinstance(data, dict): + for key, value in data.items(): + current_key = f"{parent_key}.{key}" if parent_key else key + + # 检查是否为技术标的内容 + if any(target in key for target in target_values1): + if not is_technical: + # 直接存储在根级别 + extracted_data[key] = value + technical_found = True + # 标记为技术标内容并停止进一步处理这个分支 + continue + + # 检查是否为商务标的内容 + elif any(target in key for target in target_values2): + if not is_business: + # 存储在'商务标'分类下 + if '商务标' not in extracted_data: + extracted_data['商务标'] = {} + extracted_data['商务标'][key] = value + business_found = True + # 标记为商务标内容并停止进一步处理这个分支 + continue + + # 如果当前值是字典或列表,且不在技术或商务分类下,继续递归搜索 + if isinstance(value, dict) or isinstance(value, list): + extract_nested(value, current_key, is_technical, is_business) + + elif isinstance(data, list): + for index, item in enumerate(data): + extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + + # 开始从顶级递归搜索 + extract_nested(data) + + # 处理未找到匹配的情况 + if not technical_found: + extracted_data['技术标'] = '' + if not business_found: + extracted_data['商务标'] = '' + + return extracted_data +target_values2=['投标报价','商务标','商务部分','报价部分','业绩','信誉','分值','计算公式','信用','人员','资格','奖项','认证','荣誉'] +# update_json=combine_technical_and_business(clean_json_string(evaluation_res),target_values1,target_values2) \ No newline at end of file diff --git a/flask_app/old_version/招标文件解析_old.py b/flask_app/old_version/招标文件解析_old.py index f0bae9d..52ea4b2 100644 --- a/flask_app/old_version/招标文件解析_old.py +++ b/flask_app/old_version/招标文件解析_old.py @@ -13,7 +13,7 @@ from flask_app.工程标.投标人须知正文提取指定内容工程标 import import concurrent.futures from flask_app.old_version.基础信息整合_old import combine_basic_info from flask_app.old_version.资格审查模块old_old import combine_review_standards -from flask_app.工程标.商务评分技术评分整合 import combine_evaluation_standards +from flask_app.old_version.商务评分技术评分整合 import combine_evaluation_standards from flask_app.general.format_change import pdf2docx, docx2pdf from flask_app.general.docx截取docx import copy_docx diff --git a/flask_app/old_version/解析old_old.py b/flask_app/old_version/解析old_old.py index 6aea0b4..6fc50ab 100644 --- a/flask_app/old_version/解析old_old.py +++ b/flask_app/old_version/解析old_old.py @@ -12,7 +12,7 @@ from flask_app.工程标.投标人须知正文提取指定内容工程标 import import concurrent.futures from flask_app.工程标.基础信息整合快速版 import combine_basic_info from flask_app.工程标.资格审查模块 import combine_review_standards -from flask_app.工程标.商务评分技术评分整合 import combine_evaluation_standards +from flask_app.old_version.商务评分技术评分整合 import combine_evaluation_standards from flask_app.general.format_change import pdf2docx, docx2pdf,doc2docx from flask_app.general.docx截取docx import copy_docx diff --git a/flask_app/old_version/评分标准提取main.py b/flask_app/old_version/评分标准提取main.py new file mode 100644 index 0000000..1fcdd34 --- /dev/null +++ b/flask_app/old_version/评分标准提取main.py @@ -0,0 +1,142 @@ +# -*- encoding:utf-8 -*- +import json +import re +import time +from collections import defaultdict + +from flask_app.general.商务技术评分提取 import combine_technical_and_business, parse_json_with_duplicates, \ + process_data_based_on_key, reorganize_data +from flask_app.general.通义千问long import upload_file, qianwen_long + +def combine_evaluation_standards(truncate_file): + # 定义默认的评审结果字典 + DEFAULT_EVALUATION_REVIEW = { + "技术评分": "", + "商务评分": "" + } + # 如果 truncate_file 是空字符串,直接返回包含空字符串的字典 + if not truncate_file: + return DEFAULT_EVALUATION_REVIEW.copy() + try: + # 上传文件并获取文件ID + file_id = upload_file(truncate_file) + + # 定义用户查询 + user_query1 = ( + "根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求,如果有,返回'是',否则返回'否'。" + ) # 应对竞争性谈判这种无评分要求的情况 + # 执行查询 + judge_res = qianwen_long(file_id, user_query1) + # 默认 judge 为 True + judge = True + # 检查 judge_res 的内容 + if '否' in judge_res: + judge = False + if judge: + # 执行 user_query 相关的逻辑 + # user_query = "根据该文档中的评标办法前附表或者评分标准表,请你列出该文件的技术评分,商务评分,投标报价评审标准以及它们对应的具体评分要求,外层键名分别为'技术评分','商务评分','投标报价'。如果评分内容不是这3个,则返回文档中给定的评分内容以及它的评分要求,都以json的格式返回结果,如果该采购活动有多个包,则最外层键名为对应的包名。请不要回答有关资格审查的内容" + user_query = ( + """ +你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格,请你列出该文件的技术评分,商务评分,投标报价评分以及它们对应的具体评分要求,请以JSON格式返回结果。 +格式要求: + 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,外层键名为主评审因素,嵌套的子评审因素作为内层键名,最内键值为列表,列表中包含描述评分及要求的字典。每个字典的键包括: + '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 + '要求':说明评分标准。 + 若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注' ,值为该要求;请勿擅自添加不属于'评审因素'的键名。 + +要求与指南: + 1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 + 2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 + 3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' + 4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 + 5. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 + 6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称,若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + +以下为示例输出,仅供格式参考: + { + "一包": { + "技术评分": { + "主要监理岗位的职责": [ + { + "评分": "4分", + "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" + } + ], + "备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分(50分)不得分。" + }, + "商务评分": { + "控制系统内主板": [ + { + "评分": "10分", + "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" + } + ], + "制造商技术实力": [ + { + "评分": "3分", + "要求": "一级证书得3分,二级证书得1分,其他不得分" + }, + { + "评分": "2分", + "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" + } + ] + }, + "投标报价评分": { + "投标报价是否出现违反计价规范": [ + { + "评分": "合格制", + "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" + } + ] + } + } + } + """ + ) + # 执行第二个查询 + evaluation_res = qianwen_long(file_id, user_query) #有些重复的键名,只有qianwen_long_text能保留 + # print(evaluation_res) + # 清理和处理响应 + cleaned_evaluation_res = parse_json_with_duplicates(evaluation_res) #处理重复键名的情况 + result_data = process_data_based_on_key(cleaned_evaluation_res) #处理不知名外键的情况 + include = ['一包', '二包', '三包', '四包', '五包'] + target_values = ['技术', '设计', '实施'] + updated_jsons = {} + + # 检查是否有外层键匹配 include 列表 + if any(key for key in result_data if any(included in key for included in include)): #检查result_data中的任何键是否包含include列表中的任意一个项。 + # 有匹配的项,处理这些项 + for key in result_data: + if any(item in key for item in include): + inner_dict = result_data[key] + updated_jsons[key] = combine_technical_and_business(inner_dict, target_values) #对于分包,单独对分包内的'技术评分''商务评分'作处理 + else: + # 没有匹配的项,对整个字典运行 + updated_jsons = combine_technical_and_business(result_data, target_values) + final_res=reorganize_data(updated_jsons,include) #重新组织字典,尤其是分包的情况 + return final_res + else: + # 如果 judge 是 False,直接返回默认的技术标和商务标的结构 + result_data = {} + result_data['技术评分'] = '本招标文件没有技术评分!' + result_data['商务评分'] = '本招标文件没有商务评分!' + return result_data + + except Exception as e: + print(f"Error in combine_evaluation_standards: {e}") + # 在出错时返回默认的包含空字符串的字典 + return DEFAULT_EVALUATION_REVIEW.copy() + + +if __name__ == "__main__": + start_time=time.time() + truncate_file=r"C:\Users\Administrator\Desktop\招标文件-采购类\tmp2\2024-新疆-塔城地区公安局食药环分局快检实验室项目_evaluation_method.pdf" + # truncate_file = "C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件(统计局智能终端二次招标)_evaluation_method.pdf" + # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\广水市妇幼招标文件最新(W改)_evaluation_method.pdf" + # truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf" + # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf" + res = combine_evaluation_standards(truncate_file) + print(json.dumps(res, ensure_ascii=False, indent=4)) + end_time=time.time() + print("elapsed time:"+str(end_time-start_time)) diff --git a/flask_app/routes/工程标解析main.py b/flask_app/routes/工程标解析main.py index 4ae1822..328f536 100644 --- a/flask_app/routes/工程标解析main.py +++ b/flask_app/routes/工程标解析main.py @@ -16,8 +16,8 @@ from flask_app.工程标.投标人须知正文提取指定内容工程标 import import concurrent.futures from flask_app.工程标.基础信息整合快速版 import combine_basic_info from flask_app.工程标.资格审查模块 import combine_review_standards -from flask_app.工程标.商务评分技术评分整合 import combine_evaluation_standards -from flask_app.general.format_change import pdf2docx, docx2pdf,doc2docx +from flask_app.general.商务技术评分提取 import combine_evaluation_standards +from flask_app.general.format_change import pdf2docx, docx2pdf def get_global_logger(unique_id): @@ -110,12 +110,12 @@ def fetch_project_basic_info(invalid_path, merged_baseinfo_path, merged_baseinfo tobidders_notice = invalid_path basic_res = combine_basic_info(merged_baseinfo_path, merged_baseinfo_path_more, tobidders_notice, clause_path) result = basic_res + end_time = time.time() + logger.info(f"基础信息 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 基础信息: {exc}") # 返回默认值 result = {"基础信息": {}} - end_time = time.time() - logger.info(f"基础信息 done,耗时:{end_time - start_time:.2f} 秒") return result @@ -133,12 +133,12 @@ def fetch_qualification_review(evaluation_method, qualification, output_folder, evaluation_method, qualification, output_folder, tobidders_notice_table, clause_path, invalid_path, merged_baseinfo_path, notice_path ) result = review_standards_res + end_time = time.time() + logger.info(f"资格审查 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 资格审查: {exc}") # 返回默认值 result = {"资格审查": {}} - end_time = time.time() - logger.info(f"资格审查 done,耗时:{end_time - start_time:.2f} 秒") return result @@ -148,7 +148,7 @@ def fetch_evaluation_standards(invalid_path, evaluation_method,logger): start_time = time.time() if not evaluation_method: evaluation_method = invalid_path - evaluation_standards_res = combine_evaluation_standards(evaluation_method) + evaluation_standards_res = combine_evaluation_standards(evaluation_method,invalid_path,1) technical_standards = {"技术评分": evaluation_standards_res.get("技术评分", {})} commercial_standards = {"商务评分": evaluation_standards_res.get("商务评分", {})} end_time = time.time() @@ -166,12 +166,12 @@ def fetch_invalid_requirements(invalid_docpath, output_folder, logger): try: find_invalid_res = combine_find_invalid(invalid_docpath, output_folder) result = find_invalid_res + end_time = time.time() + logger.info(f"无效标与废标 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 无效标与废标: {exc}") # 返回默认值 result = {"无效标与废标": {}} - end_time = time.time() - logger.info(f"无效标与废标 done,耗时:{end_time - start_time:.2f} 秒") return result @@ -185,12 +185,12 @@ def fetch_bidding_documents_requirements(invalid_path, merged_baseinfo_path_more selection = 1 fetch_bidding_documents_requirements_json = extract_from_notice(merged_baseinfo_path_more, clause_path, selection) result = {"投标文件要求": fetch_bidding_documents_requirements_json} + end_time = time.time() + logger.info(f"投标文件要求 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 投标文件要求: {exc}") # 返回默认值 result = {"投标文件要求": {}} - end_time = time.time() - logger.info(f"投标文件要求 done,耗时:{end_time - start_time:.2f} 秒") return result # 开评定标流程 @@ -203,12 +203,12 @@ def fetch_bid_opening(invalid_path, merged_baseinfo_path_more, clause_path, logg selection = 2 fetch_bid_opening_json = extract_from_notice(merged_baseinfo_path_more, clause_path, selection) result = {"开评定标流程": fetch_bid_opening_json} + end_time = time.time() + logger.info(f"开评定标流程 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 开评定标流程: {exc}") # 返回默认值 result = {"开评定标流程": {}} - end_time = time.time() - logger.info(f"开评定标流程 done,耗时:{end_time - start_time:.2f} 秒") return result #分段返回 diff --git a/flask_app/routes/货物标解析main.py b/flask_app/routes/货物标解析main.py index 87fb1d2..5c086c6 100644 --- a/flask_app/routes/货物标解析main.py +++ b/flask_app/routes/货物标解析main.py @@ -11,7 +11,7 @@ import concurrent.futures from flask_app.货物标.提取json货物标版 import convert_clause_to_json from flask_app.general.无效标和废标公共代码 import combine_find_invalid from flask_app.货物标.资格审查main import combine_qualification_review -from flask_app.货物标.评分标准提取main import combine_evaluation_standards +from flask_app.general.商务技术评分提取 import combine_evaluation_standards import logging @@ -24,7 +24,6 @@ def get_global_logger(unique_id): # 创建全局线程池 executor = ThreadPoolExecutor() - def preprocess_files(output_folder, file_path, file_type,logger): logger.info("starting 文件预处理...") start_time = time.time() @@ -85,12 +84,12 @@ def fetch_project_basic_info(invalid_path, merged_baseinfo_path, procurement_pat basic_res = combine_basic_info(merged_baseinfo_path, procurement_path, clause_path, invalid_path) base_info, good_list = post_process_baseinfo(basic_res, logger) result = base_info, good_list + end_time = time.time() + logger.info(f"基础信息 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 基础信息: {exc}") # 返回默认值 result = {"基础信息": {}}, [] - end_time = time.time() - logger.info(f"基础信息 done,耗时:{end_time - start_time:.2f} 秒") return result @@ -100,12 +99,12 @@ def fetch_qualification_review(invalid_path, qualification_path, notice_path, lo try: review_standards_res = combine_qualification_review(invalid_path, qualification_path, notice_path) result = review_standards_res + end_time = time.time() + logger.info(f"资格审查 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 资格审查: {exc}") # 返回默认值 result = {"资格审查": {}} - end_time = time.time() - logger.info(f"资格审查 done,耗时:{end_time - start_time:.2f} 秒") return result def fetch_evaluation_standards(invalid_path, evaluation_method_path,logger): @@ -113,7 +112,7 @@ def fetch_evaluation_standards(invalid_path, evaluation_method_path,logger): start_time = time.time() if not evaluation_method_path: evaluation_method_path = invalid_path - evaluation_standards_res = combine_evaluation_standards(evaluation_method_path) + evaluation_standards_res = combine_evaluation_standards(evaluation_method_path,invalid_path,2) technical_standards = {"技术评分": evaluation_standards_res.get("技术评分", {})} commercial_standards = {"商务评分": evaluation_standards_res.get("商务评分", {})} end_time = time.time() @@ -129,12 +128,12 @@ def fetch_invalid_requirements(invalid_docpath, output_folder, logger): try: find_invalid_res = combine_find_invalid(invalid_docpath, output_folder) result = find_invalid_res + end_time = time.time() + logger.info(f"无效标与废标 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 无效标与废标: {exc}") # 返回默认值 result = {"无效标与废标": {}} - end_time = time.time() - logger.info(f"无效标与废标 done,耗时:{end_time - start_time:.2f} 秒") return result def fetch_bidding_documents_requirements(invalid_path, merged_baseinfo_path, clause_path, logger): @@ -146,12 +145,12 @@ def fetch_bidding_documents_requirements(invalid_path, merged_baseinfo_path, cla try: fetch_bidding_documents_requirements_json = extract_from_notice(merged_baseinfo_path, clause_path, selection) result = {"投标文件要求": fetch_bidding_documents_requirements_json} + end_time = time.time() + logger.info(f"投标文件要求 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 投标文件要求: {exc}") # 返回默认值,假设默认值为一个空字典 result = {"投标文件要求": {}} - end_time = time.time() - logger.info(f"投标文件要求 done,耗时:{end_time - start_time:.2f} 秒") return result @@ -165,12 +164,12 @@ def fetch_bid_opening(invalid_path, merged_baseinfo_path, clause_path, logger): try: fetch_bid_opening_json = extract_from_notice(merged_baseinfo_path, clause_path, selection) result = {"开评定标流程": fetch_bid_opening_json} + end_time = time.time() + logger.info(f"开评定标流程 done,耗时:{end_time - start_time:.2f} 秒") except Exception as exc: logger.error(f"Error in 开评定标流程: {exc}") # 返回默认值,假设默认值为一个空字典 result = {"开评定标流程": {}} - end_time = time.time() - logger.info(f"开评定标流程 done,耗时:{end_time - start_time:.2f} 秒") return result @@ -200,10 +199,8 @@ def post_process_baseinfo(base_info,logger): if '采购要求' in pure_base_info and not procurement_reqs: # 若采购要求为空,删除该键 pure_base_info.pop('采购要求') - # 更新基础信息 base_info['基础信息'] = pure_base_info - # logger.info(f"Extracted good_list: {good_list}") return base_info, good_list except Exception as e: @@ -275,6 +272,8 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id): #TODO:重置一下投标文件格式提取那部分的代码 #TODO:小解析考虑提速:1:直接pdf转文本,再切分。后期考虑。 + +#TODO: ec7d5328-9c57-450f-baf4-2e5a6f90ed1d #商务标这里改为列表最里层 #good_list 金额 截取上下文 if __name__ == "__main__": diff --git a/flask_app/start_up.py b/flask_app/start_up.py index beea5f7..c90c425 100644 --- a/flask_app/start_up.py +++ b/flask_app/start_up.py @@ -1,10 +1,8 @@ # flask_app/start_up.py -import logging from flask import Flask, request, g - from flask_app.ConnectionLimiter import ConnectionLimiter -from flask_app.logger_setup import CSTFormatter, create_logger +from flask_app.logger_setup import create_logger_main from flask_app.routes.get_deviation import get_deviation_bp from flask_app.routes.little_zbparse import little_zbparse_bp from flask_app.routes.upload import upload_bp @@ -17,14 +15,8 @@ class FlaskAppWithLimiter(Flask): self.connection_limiters = {} def create_app(): app = FlaskAppWithLimiter(__name__) - - # 设置日志的全局配置(如果需要) - handler = logging.StreamHandler() - handler.setFormatter(CSTFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) - app.logger.addHandler(handler) - app.logger.setLevel(logging.INFO) - app.logger.propagate = False # 禁止日志传播到根日志记录器 - + # 创建全局日志记录器 + app.global_logger = create_logger_main('global_log') # 全局日志记录器 # 注册蓝图 app.register_blueprint(get_deviation_bp) app.register_blueprint(little_zbparse_bp) diff --git a/flask_app/工程标/商务评分技术评分整合.py b/flask_app/工程标/商务评分技术评分整合.py deleted file mode 100644 index 0f28560..0000000 --- a/flask_app/工程标/商务评分技术评分整合.py +++ /dev/null @@ -1,201 +0,0 @@ -# -*- encoding:utf-8 -*- -import json - -from flask_app.general.json_utils import clean_json_string -from flask_app.general.通义千问long import upload_file, qianwen_long - -# def combine_technical_and_business(data, target_values1, target_values2): -# extracted_data = {} # 根级别存储所有数据 -# technical_found = False -# business_found = False -# -# def extract_nested(data, parent_key='', is_technical=False, is_business=False): -# nonlocal technical_found, business_found -# if isinstance(data, dict): -# for key, value in data.items(): -# current_key = f"{parent_key}.{key}" if parent_key else key -# -# # 检查是否为技术标的内容 -# if any(target in key for target in target_values1): -# if not is_technical: -# # 直接存储在根级别 -# extracted_data[key] = value -# technical_found = True -# # 标记为技术标内容并停止进一步处理这个分支 -# continue -# -# # 检查是否为商务标的内容 -# elif any(target in key for target in target_values2): -# if not is_business: -# # 存储在'商务标'分类下 -# if '商务标' not in extracted_data: -# extracted_data['商务标'] = {} -# extracted_data['商务标'][key] = value -# business_found = True -# # 标记为商务标内容并停止进一步处理这个分支 -# continue -# -# # 如果当前值是字典或列表,且不在技术或商务分类下,继续递归搜索 -# if isinstance(value, dict) or isinstance(value, list): -# extract_nested(value, current_key, is_technical, is_business) -# -# elif isinstance(data, list): -# for index, item in enumerate(data): -# extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) -# -# # 开始从顶级递归搜索 -# extract_nested(data) -# -# # 处理未找到匹配的情况 -# if not technical_found: -# extracted_data['技术标'] = '' -# if not business_found: -# extracted_data['商务标'] = '' -# -# return extracted_data -def remove_unknown_scores(data): - if isinstance(data, dict): - return { - k: remove_unknown_scores(v) - for k, v in data.items() - if not (k == "评分" and v in ["未知", "/", ""]) - } - elif isinstance(data, list): - return [remove_unknown_scores(item) for item in data] - else: - return data -def combine_technical_and_business(data, target_values): - data=remove_unknown_scores(data) - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False - - def extract_nested(data, parent_key='', is_technical=False, is_business=False): - nonlocal technical_found, business_found - if isinstance(data, dict): - for key, value in data.items(): - current_key = f"{parent_key}.{key}" if parent_key else key - - # 检查是否为技术标的内容 - if any(target in key for target in target_values): - if not is_technical: - extracted_data[key] = value - technical_found = True - continue - - # 默认其他所有内容都归为商务标 - else: - if not is_business: - if '商务评分' not in extracted_data: - extracted_data['商务评分'] = {} - extracted_data['商务评分'][key] = value - business_found = True - continue - - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) - - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) - - extract_nested(data) - - if not technical_found: - extracted_data['技术评分'] = '' - if not business_found: - extracted_data['商务评分'] = '' - - return extracted_data -def combine_evaluation_standards(evaluation_method): - # 商务标、技术标评分项:千问 - file_id = upload_file(evaluation_method) - # user_query_2 = ( - # "根据该文档中的评标办法前附表,请你列出该文件的技术评分,商务评分,投标报价评审标准以及它们对应的具体评分要求,若对应内容中存在其他信息,在键名如'技术评分'中新增子键名'备注'存放该信息。如果评分内容(因素)不是这3个,则返回文档中给定的评分内容(因素)以及它的评分要求。请以json格式返回结果,不要回答有关形式、资格、响应性评审标准的内容") - user_query_2 = ( - """你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格,请你列出该文件的技术评分,商务评分,投标报价评审以及它们对应的具体评分要求,请以json格式返回结果,最外层键名分别是'技术评分','商务评分','投标报价评审',请在这三大项评分中分别用若干键值对表示具体评分项,外层键名为各评审因素,可能存在嵌套关系,但最内层键值为一个列表,列表中包含若干(可为一)描述该评审因素的评分及要求的字典,内层键名分别是'评分'和'要求',若无评分,可删去'评分'键值对,'要求'中说明了该评审因素的评分标准;若这三大项评分中存在其他信息,则在相应评分大块内部新增键名'备注'存放该信息,键值为具体的要求,否则不需要。如果评分内容(因素)不是这三大项,则返回文档中给定的评分内容(因素)以及它们的具体评分要求。 -特殊情况处理: -如果评分内容(因素)不是这三大项,则返回表格中给定的评分内容(因素)代替最外层键名'技术评分','商务评分','投标报价评分',并且返回它们的具体评分要求。 - -要求与指南: -1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 -2. 若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" -3. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 -4. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 -5. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' -6. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 - -以下为示例输出,仅供格式参考: - { - "一包": { - "技术评分": { - "实施方案":{ - "总体实施方案":[ - { - "评分":8, - "要求":"根据投标人总体实施方案进行评分" - } - ], - "项目实施要点":[ - { - "评分":8, - "要求":"根据投标人对项目实施要点、难点进行评分" - } - ] - }, - "主要监理岗位的职责": [ - { - "评分": "4分", - "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" - } - ], - "备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分(50分)不得分。" - }, - "商务评分": { - "控制系统内主板": [ - { - "评分": "10分", - "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" - } - ], - "制造商技术实力": [ - { - "评分": "3分", - "要求": "一级证书得3分,二级证书得1分,其他不得分" - }, - { - "评分": "2分", - "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" - } - ] - }, - "投标报价评审": { - "投标报价是否出现违反计价规范": [ - { - "评分": "合格制", - "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" - } - ] - } - } - } - """ - ) - - evaluation_res = qianwen_long(file_id, user_query_2) - # print(evaluation_res) - target_values1 = ['技术标','技术部分','设计', '实施',"技术评分"] - # target_values2=['投标报价','商务标','商务部分','报价部分','业绩','信誉','分值','计算公式','信用','人员','资格','奖项','认证','荣誉'] - # update_json=combine_technical_and_business(clean_json_string(evaluation_res),target_values1,target_values2) - update_json = combine_technical_and_business(clean_json_string(evaluation_res), target_values1) - return update_json #商务标技术标整合 -if __name__ == "__main__": - # evaluation_method="C:\\Users\\Administrator\\Desktop\\招标文件\\招标01_evaluation_method.pdf" - evaluation_method= r"C:\Users\Administrator\Desktop\招标文件\招标04_evaluation_method.pdf" - evaluation_standards_res=combine_evaluation_standards(evaluation_method) - # 从结果中提取"商务标"和"技术标" - technical_standards = {"技术评分": evaluation_standards_res.get("技术评分", {})} - commercial_standards = {"商务评分": evaluation_standards_res.get("商务评分", {})} - # 返回技术标和商务标 - print(json.dumps(technical_standards,ensure_ascii=False,indent=4)) - print(json.dumps(commercial_standards, ensure_ascii=False, indent=4)) \ No newline at end of file diff --git a/flask_app/工程标/截取pdf.py b/flask_app/工程标/截取pdf.py index 2cd0128..20e9946 100644 --- a/flask_app/工程标/截取pdf.py +++ b/flask_app/工程标/截取pdf.py @@ -597,18 +597,18 @@ def truncate_pdf_specific_engineering(pdf_path, output_folder, selections, uniqu if __name__ == "__main__": start_time = time.time() - # input_path = "C:\\Users\\Administrator\\Desktop\\new招标文件\\工程标" - # input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\0b1861e6-c7f6-4541-9182-b1384ba84f3b\\ztbfile.pdf" + # input_path = r"C:\Users\Administrator\Desktop\new招标文件\工程标" + input_path=r"C:\Users\Administrator\Desktop\fsdownload\ec7d5328-9c57-450f-baf4-2e5a6f90ed1d\ztbfile.pdf" # input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\2-招标文件.pdf" - input_path=r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf" - output_folder = r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\all" - # files=truncate_pdf_multiple(input_path,output_folder) - # print(files) + # input_path=r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf" + output_folder = r"C:\Users\Administrator\Desktop\fsdownload\ec7d5328-9c57-450f-baf4-2e5a6f90ed1d\tmp" + files=truncate_pdf_multiple(input_path,output_folder) + print(files) # selections = [4, 1] # 仅处理 selection 4、1 # files=truncate_pdf_specific_engineering(input_path,output_folder,selections) - selection = 4 # 例如:1 - 投标人须知前附表+正文, 2 - 评标办法, 3 -资格审查条件 4-招标公告 5-无效标 - generated_files = truncate_pdf_main(input_path, output_folder, selection) - print(generated_files) + # selection = 2 # 例如:1 - 投标人须知前附表+正文, 2 - 评标办法, 3 -资格审查条件 4-招标公告 5-无效标 + # generated_files = truncate_pdf_main(input_path, output_folder, selection) + # print(generated_files) # print("生成的文件:", generated_files) end_time = time.time() print("耗时:" + str(end_time - start_time)) diff --git a/flask_app/工程标/投标人须知正文提取指定内容工程标.py b/flask_app/工程标/投标人须知正文提取指定内容工程标.py index 7023db9..d1bda60 100644 --- a/flask_app/工程标/投标人须知正文提取指定内容工程标.py +++ b/flask_app/工程标/投标人须知正文提取指定内容工程标.py @@ -1,7 +1,7 @@ import json import re from flask_app.general.投标人须知正文提取指定内容 import process_nested_data, transform_json, get_requirements_with_gpt, \ - extract_sections, concatenate_keys_values + extract_sections, concatenate_keys_values, extract_between_sections # 对于每个target_value元素,如果有完美匹配json_data中的键,那就加入这个完美匹配的键名,否则,把全部模糊匹配到的键名都加入 @@ -40,39 +40,6 @@ def extract_json(data, target_values): results[subkey] = data[subkey] return results -def extract_between_sections(data, target_values): - target_found = False - extracted_data = {} - current_section_title = "" - section_pattern = re.compile(r'^[一二三四五六七八九十]+$') # 匹配 "一", "二", "三" 等大标题 - current_block = {} - - # 遍历所有键值对 - for key, value in data.items(): - # 只匹配形如 "一": "竞争性磋商响应文件" 的章节标题 - if section_pattern.match(key): - if target_found: - # 如果已经找到了符合的章节,并且遇到了另一个章节 - # 保存当前块并重置 - if current_block: - extracted_data[current_section_title] = current_block - current_block = {} - target_found = False - - # 检查当前标题是否包含 target_values 中的任意关键词 - if any(tv in value for tv in target_values): - target_found = True # 找到了目标章节,开始捕获后续内容 - current_section_title = value # 保存章节标题内容 - - elif target_found: # 只捕获目标值之后的内容 - current_block[key] = value - - # 保存最后一个块(如果有的话) - if current_block: - extracted_data[current_section_title] = current_block - - return extracted_data - def sort_clean_data_keys(data): # 预处理:删除键名中的空格 def preprocess_key(key): @@ -105,7 +72,8 @@ def extract_from_notice(merged_baseinfo_path,clause_path, type): if type == 1: target_values = ["投标","投标文件","响应文件"] elif type == 2: - target_values = ["开标", "评标", "定标","磋商程序","中标"] + # target_values = ["开标", "评标", "定标","磋商程序","中标"] + target_values=["开标", "评标", "定标","评审","成交","合同","磋商程序", "中标", "程序", "步骤"] elif type == 3: target_values = ["重新招标、不再招标和终止招标","重新招标","重新采购", "不再招标", "不再采购","终止招标","终止采购"] elif type == 4: diff --git a/flask_app/工程标/资格审查模块.py b/flask_app/工程标/资格审查模块.py index 787394a..54aa022 100644 --- a/flask_app/工程标/资格审查模块.py +++ b/flask_app/工程标/资格审查模块.py @@ -134,16 +134,16 @@ def combine_review_standards(evaluation_method, qualification_path, output_folde if __name__ == "__main__": start_time = time.time() - evaluation_method = r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\ztbfile_evaluation_method.pdf" - qualification_path=r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\ztbfile_qualification.pdf" - output_folder=r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782" - notice_path=r'C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\ztbfile_notice.pdf' + output_folder = r"C:\Users\Administrator\Desktop\fsdownload\ec7d5328-9c57-450f-baf4-2e5a6f90ed1d\tmp" + evaluation_method = os.path.join(output_folder,"ztbfile_evaluation_method.pdf") + qualification_path="" + notice_path=os.path.join(output_folder,"ztbfile_notice.pdf") # knowledge_name="zbtest20" - clause_path = r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\clause1.json" + clause_path = "" tobidders_notice_table = r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\ztbfile_tobidders_notice_table.pdf" - invalid_path = r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\ztbfile_invalid.pdf" - merged_baseinfo_path = r"C:\Users\Administrator\Desktop\fsdownload\bb61d137-794c-4760-8da7-ebc10cdc2782\ztbfile_merged_baseinfo.pdf" + invalid_path = os.path.join(output_folder,"gcHBDL-2024-0181-001-招标文件_invalid.pdf") + merged_baseinfo_path = os.path.join(output_folder,"gcHBDL-2024-0181-001-招标文件_merged_baseinfo.pdf") res = combine_review_standards(evaluation_method, qualification_path, output_folder, tobidders_notice_table, clause_path, invalid_path, merged_baseinfo_path,notice_path) print(json.dumps(res, ensure_ascii=False, indent=4)) diff --git a/flask_app/货物标/截取pdf货物标版.py b/flask_app/货物标/截取pdf货物标版.py index 284965d..e175928 100644 --- a/flask_app/货物标/截取pdf货物标版.py +++ b/flask_app/货物标/截取pdf货物标版.py @@ -500,8 +500,15 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header, b base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] evaluation_method_file = os.path.join(output_folder, f"{base_file_name}_evaluation_method.pdf") if os.path.isfile(evaluation_method_file): - print(f"找到评分办法章节文件: {evaluation_method_file},直接返回。") - return evaluation_method_file + print(f"找到评分办法章节文件: {evaluation_method_file},生成新文件。") + # 获取文件路径和文件名 + new_file_name = f"{base_file_name}_qualification2.pdf" + new_file_path = os.path.join(output_folder, new_file_name) + # 复制文件 + with open(evaluation_method_file, 'rb') as original_file: + with open(new_file_path, 'wb') as new_file: + new_file.write(original_file.read()) + return new_file_path else: temp = truncate_pdf_main(pdf_path, output_folder, 2, "qualification2") if len(temp) > 0: @@ -774,17 +781,17 @@ def truncate_pdf_specific_goods(pdf_path, output_folder, selections, unique_id=" # ztbfile.pdf少资格评审 包头少符合性评审 if __name__ == "__main__": logger = get_global_logger("123") - input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles" + # input_path = r"C:\Users\Administrator\Desktop\new招标文件\货物标" # input_path = r"C:\Users\Administrator\Desktop\招标文件-采购类\2024-贵州-贵州医科大学附属医院导视系统零星制作安装项目.pdf" # input_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\zbtest4_evaluation_method.pdf" - # input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件_procurement.pdf" - # input_path=r"C:\Users\Administrator\Desktop\fsdownload\42bd5604-fb85-43ff-821f-a1ea78fec115\ztbfile.pdf" - # output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\tmp" - output_folder = r"C:\Users\Administrator\Desktop\招标文件-采购类\all" - # files = truncate_pdf_multiple(input_path, output_folder,logger) + # input_path = r"C:\Users\Administrator\Desktop\货物标\zbfiles\2-招标文件(广水市教育局封闭管理).pdf" + input_path=r"C:\Users\Administrator\Desktop\fsdownload\ff2acdba-3a55-48a7-aa7a-61d9f89b909a\ztbfile.pdf" + output_folder = r"C:\Users\Administrator\Desktop\fsdownload\ff2acdba-3a55-48a7-aa7a-61d9f89b909a\tmp" + # output_folder = r"C:\Users\Administrator\Desktop\new招标文件\output2" + files = truncate_pdf_multiple(input_path, output_folder,logger) # selections = [3,5] # files=truncate_pdf_specific_goods(input_path,output_folder,selections) - # print(files) - selection = 3 # 例如:1 - 公告, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 5-采购需求 - generated_files = truncate_pdf_main(input_path, output_folder, selection) - print(generated_files) + print(files) + # selection = 2 # 例如:1 - 公告, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 5-采购需求 + # generated_files = truncate_pdf_main(input_path, output_folder, selection) + # print(generated_files) diff --git a/flask_app/货物标/技术参数要求提取.py b/flask_app/货物标/技术参数要求提取.py index b5e38d3..8564791 100644 --- a/flask_app/货物标/技术参数要求提取.py +++ b/flask_app/货物标/技术参数要求提取.py @@ -295,12 +295,19 @@ def generate_prompt(judge_res, full_text=None): 任务:你负责解析采购文件,提取采购需求,并以JSON格式返回,不要遗漏该项目需要采购的货物(或系统)。 要求与指南: -1. 精准定位:请运用文档理解能力,定位文件中的采购需求部分。若有采购清单,请直接根据采购清单上的货物(或系统)名称给出结果,注意你无需提取诸如'说明'、'规格'、'技术参数'、'描述'等列的内容,即你不需要给出详细的采购要求,仅返回采购的货物或系统或模块名称;若没有采购清单,则从表格或文本中摘取采购信息。 +1. 精准定位:请运用文档理解能力,定位文件中的采购需求部分。 + -若有采购清单,请直接根据采购清单上的货物(或系统)名称给出结果,若没有采购清单,则从表格或文本中摘取采购信息。 + -注意采购目标通常在诸如'名称'列,且每个目标占据一个单元格,你无需提取诸如'说明'、'规格'、'参数'、'描述'等其他列的内容,即你不需要给出详细的采购要求,更不要将这些单元格内的描述拆分作为其的子键,你仅返回采购的货物或系统或模块名称; 2. 采购目标:采购种类通常有硬件(如设备、货物)和软件(如系统软件、应用APP),一次采购活动可以同时包含这两种类型。 -3. 系统归属:一些采购活动可能将采购目标划分为若干系统和货物,每个系统可能包含若干货物,则将这些货物名称作为该系统的二级键,注意这种包含关系是通过表格结构或文档标题层次来得出的;系统可以只包含总体'系统功能'而无货物。 -5. 软件需求:对于软件应用或系统软件采购,若有多个系统且序号分明,请不要遗漏,最多仅需列出系统模块构成(若有),并作为该系统键值的一部分,无需在模块下再细分功能。 -5. 系统功能:若采购的某系统提及总体系统功能,则在系统值中添加'系统功能'二级键,不展开具体内容。 -6. 完整性:确保不遗漏系统内的货物,也不添加未提及的内容。若'采购清单'中未提取的货物(或系统)名称在形如'主要设备功能指标'的标题下有详细参数指标要求,请将该货物名也添加至返回中。 +3. 层级结构: + -采购活动可能将目标划分为多个系统或货物。若文档通过标题或表格层次标明这种归属关系,请在JSON中以嵌套形式表示: + -系统作为一级键,包含的货物作为二级键。 + -如果系统仅提到"系统功能",在该系统的值中添加"系统功能"作为二级键,具体内容不展开。 + -系统可以只包含“系统功能”,无需列出具体货物。 +4. 软件需求:对于软件系统或应用采购,若有多个系统且序号分明,请不要遗漏;若明确列出系统模块,提取模块名称并作为系统的子键,无需在模块下再细分功能。 +5. 完整性: + -确保系统内的所有货物均被提取,避免遗漏或添加未提及的内容。 + -若某货物(或系统、模块)在“主要设备功能指标”或类似标题下有详细参数说明,但未在前面清单或表格中诸如'名称'的列中列出,也需添加到结果中。 特殊情况: 1.若采购的货物或系统或模块名称前存在三角▲,△、五角★,☆,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲,△或★,☆符号,如'★高清摄像机'。 @@ -510,8 +517,8 @@ def test_all_files_in_folder(input_folder, output_folder): if __name__ == "__main__": start_time=time.time() # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx" - truncate_docfile=r"C:\Users\Administrator\Desktop\货物标\output1\6_2定版视频会议磋商文件_procurement.docx" - truncate_file=r'C:\Users\Administrator\Desktop\货物标\output1\6.2定版视频会议磋商文件_procurement.pdf' + truncate_docfile=r"C:\Users\Administrator\Desktop\new招标文件\货物标\HBDL-2024-0481-001-招标文件.docx" + truncate_file=r'C:\Users\Administrator\Desktop\new招标文件\货物标\HBDL-2024-0481-001-招标文件.pdf' # invalid_path="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf" # truncate_file="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile_procurement.docx" # output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\tmp" diff --git a/flask_app/货物标/投标人须知正文提取指定内容货物标版.py b/flask_app/货物标/投标人须知正文提取指定内容货物标版.py index 9459687..339709b 100644 --- a/flask_app/货物标/投标人须知正文提取指定内容货物标版.py +++ b/flask_app/货物标/投标人须知正文提取指定内容货物标版.py @@ -2,43 +2,7 @@ import json import re from functools import cmp_to_key -from flask_app.general.投标人须知正文提取指定内容 import process_nested_data, transform_json, get_requirements_with_gpt,concatenate_keys_values - - -#提取两个大标题之间的内容 -def extract_between_sections(data, target_values): - target_found = False - extracted_data = {} - current_section_title = "" - section_pattern = re.compile(r'^[一二三四五六七八九十]+$') # 匹配 "一", "二", "三" 等大标题 - current_block = {} - - # 遍历所有键值对 - for key, value in data.items(): - # 只匹配形如 "一": "竞争性磋商响应文件" 的章节标题 - if section_pattern.match(key): #匹配到大标题... - if target_found: - # 如果已经找到了符合的章节,并且遇到了另一个章节 - # 保存当前块并重置 - if current_block: - extracted_data[current_section_title] = current_block - current_block = {} - target_found = False - - # 检查当前标题是否包含 target_values 中的任意关键词 - if any(tv in value for tv in target_values): - target_found = True # 找到了目标章节,开始捕获后续内容 - current_section_title = value # 保存章节标题内容 - - elif target_found: # 匹配到普通序号... - current_block[key] = value - - # 保存最后一个块(如果有的话) - if current_block: - extracted_data[current_section_title] = current_block - - return extracted_data - +from flask_app.general.投标人须知正文提取指定内容 import get_requirements_with_gpt, concatenate_keys_values, extract_between_sections # def process_with_outer_key(data): # processed_data = {} @@ -84,7 +48,7 @@ def extract_from_notice(merged_baseinfo_path, clause_path, type): # 映射 type 到 target_values type_target_map = { 1: ["投标文件", "响应文件", "响应性文件"], - 2: ["开标", "评标", "定标", "磋商程序", "中标", "程序", "步骤"], + 2: ["开标", "评标", "定标","评审","成交","合同","磋商程序", "中标", "程序", "步骤"], 3: ["重新招标、不再招标和终止招标", "重新招标", "重新采购", "不再招标", "不再采购", "终止招标", "终止采购"], 4: ["评标"] # 测试 } @@ -111,7 +75,6 @@ def extract_from_notice(merged_baseinfo_path, clause_path, type): # transformed_data = process_with_outer_key(extracted_data) #取消注释这三行 # final_result = process_nested_data(transformed_data) # return final_result - print(json.dumps(extracted_data_concatenated,ensure_ascii=False,indent=4)) return extracted_data_concatenated # 如果 clause_path 为空或提取数据失败,调用回退函数 @@ -125,8 +88,8 @@ def extract_from_notice(merged_baseinfo_path, clause_path, type): #TODO:可以通过判断格式来看是否需要调用GPT 1.1 2.1.... if __name__ == "__main__": - clause_path = r'C:\Users\Administrator\Desktop\招标文件\output4\tmp\clause1.json' - merged_baseinfo_path=r"C:\Users\Administrator\Desktop\fsdownload\a110ed59-00e8-47ec-873a-bd4579a6e628\ztbfile_merged_baseinfo.pdf" + clause_path = r'C:\Users\Administrator\Desktop\fsdownload\550424e5-c105-4ae4-969e-4a80447b4e3f\clause1.json' + merged_baseinfo_path=r"C:\Users\Administrator\Desktop\fsdownload\550424e5-c105-4ae4-969e-4a80447b4e3f\ztbfile_merged_baseinfo.pdf" # file_path = 'D:\\flask_project\\flask_app\\static\\output\\fee18877-0c60-4c28-911f-9a5f7d1325a7\\clause1.json' try: res = extract_from_notice(merged_baseinfo_path,clause_path, 2) # 可以改变此处的 type 参数测试不同的场景 diff --git a/flask_app/货物标/评分标准提取main.py b/flask_app/货物标/评分标准提取main.py deleted file mode 100644 index 85b1b97..0000000 --- a/flask_app/货物标/评分标准提取main.py +++ /dev/null @@ -1,323 +0,0 @@ -# -*- encoding:utf-8 -*- -import json -import re -import time -from collections import defaultdict -from flask_app.general.通义千问long import upload_file, qianwen_long - - -def combine_technical_and_business(data, target_values): - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False - - def extract_nested(data, parent_key='', is_technical=False, is_business=False): - nonlocal technical_found, business_found - if isinstance(data, dict): - for key, value in data.items(): - current_key = f"{parent_key}.{key}" if parent_key else key - - # 检查是否为技术标的内容 - if any(target in key for target in target_values): - if not is_technical: - extracted_data[key] = value - technical_found = True - continue - - # 默认其他所有内容都归为商务标 - else: - if not is_business: - if '商务评分' not in extracted_data: - extracted_data['商务评分'] = {} - extracted_data['商务评分'][key] = value - business_found = True - continue - - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) - - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) - - extract_nested(data) - - if not technical_found: - extracted_data['技术评分'] = '' - if not business_found: - extracted_data['商务评分'] = '' - - return extracted_data - - -# 防止外键只有一个'一包'的情况 -def process_data_based_on_key(data): - exclude_word = ["包", "未知", "评分因素"] - # 获取字典的键列表 - keys = list(data.keys()) - # 检查键的数量是否为1并且 exclude_word 中的任何词包含在 keys[0] 中 - if len(keys) == 1 and any(word in keys[0] for word in exclude_word): - # 返回内层的字典 - return data[keys[0]] - - # 如果条件不满足,则返回原始字典 - return data - - -def parse_json_with_duplicates(raw_string): - """ - 解析具有重复键的 JSON 字符串,将所有重复的键值对存储为列表。 - - Args: - json_string (str): 需要解析的 JSON 字符串。 - - Returns: - dict: 解析后的字典,重复的键对应的值为列表。 - - eg:输入:"综合实力": { - "评分": "2分", - "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" - }, - "综合实力": { - "评分": "2分", - "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" - } - 输出:"综合实力": [ - { - "评分": "2分", - "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" - }, - { - "评分": "2分", - "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" - }] - """ - - def custom_object_pairs_hook(pairs): - d = defaultdict(list) - for key, value in pairs: - try: - # 如果值是字典或列表,递归处理 - if isinstance(value, dict): - value = process_dict(value) - elif isinstance(value, list): - value = process_list(value) - d[key].append(value) - except Exception as e: - d[key].append(value) # 根据需求决定是否跳过或保留原值 - # 将有多个值的键转换为列表,单个值的键保持原样 - return {key: (values if len(values) > 1 else values[0]) for key, values in d.items()} - - def process_dict(d): - """ - 递归处理字典,确保所有重复键的值为列表。 - - Args: - d (dict): 需要处理的字典。 - - Returns: - dict: 处理后的字典。 - """ - try: - return custom_object_pairs_hook(d.items()) - except Exception as e: - return {} - - def process_list(l): - """ - 递归处理列表,确保列表中的所有字典也被处理。 - - Args: - l (list): 需要处理的列表。 - - Returns: - list: 处理后的列表。 - """ - try: - return [process_dict(item) if isinstance(item, dict) else item for item in l] - except Exception as e: - return [] - - """输入字符串,提取 { 和 } 之间的内容,并将其解析为字典""" - if not raw_string.strip(): - return {} - match = re.search(r'\{[\s\S]*\}', raw_string) - if match: - try: - json_string = match.group(0) - return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook) - except json.JSONDecodeError as e: - print(f"json_utils: extract_content_from_json: JSON decode error: {e}") - return {} - else: - print("json_utils: extract_content_from_json: No valid JSON content found.") - return {} - -def reorganize_data(input_dict, include=None): - """ - 重组输入字典,将“技术评分”和“商务评分”提升为最外层键, - 并将包含在 include 列表中的包名的数据嵌套在相应的评分类别下。 - - 如果 input_dict 的顶层键不包含任何 include 列表中的项,则返回原始字典。 - - :param input_dict: 原始输入字典 - :param include: 包名列表,例如 ['一包', '二包', '三包'] - :return: 重组后的字典 - """ - if include is None: - include = [] - - # 检查是否有任何顶层键包含在 include 列表中 - has_include = any(key in include for key in input_dict.keys()) - - if not has_include: - # 没有包含任何指定的包名,直接返回原始字典 - return input_dict - - # 初始化新的字典结构 - reorganized = { - "技术评分": {}, - "商务评分": {} - } - - # 遍历每一个包(如 "一包", "二包") - for package, categories in input_dict.items(): - # 处理技术评分 - if "技术评分" in categories: - reorganized["技术评分"][package] = categories["技术评分"] - - # 处理商务评分 - if "商务评分" in categories: - reorganized["商务评分"][package] = categories["商务评分"] - - return reorganized - -def combine_evaluation_standards(truncate_file): - # 定义默认的评审结果字典 - DEFAULT_EVALUATION_REVIEW = { - "技术评分": "", - "商务评分": "" - } - # 如果 truncate_file 是空字符串,直接返回包含空字符串的字典 - if not truncate_file: - return DEFAULT_EVALUATION_REVIEW.copy() - try: - # 上传文件并获取文件ID - file_id = upload_file(truncate_file) - - # 定义用户查询 - user_query1 = ( - "根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求,如果有,返回'是',否则返回'否'。" - ) # 应对竞争性谈判这种无评分要求的情况 - - # 执行查询 - judge_res = qianwen_long(file_id, user_query1) - # 默认 judge 为 True - judge = True - - # 检查 judge_res 的内容 - if '否' in judge_res: - judge = False - - if judge: - # 执行 user_query 相关的逻辑 - # user_query = "根据该文档中的评标办法前附表或者评分标准表,请你列出该文件的技术评分,商务评分,投标报价评审标准以及它们对应的具体评分要求,外层键名分别为'技术评分','商务评分','投标报价'。如果评分内容不是这3个,则返回文档中给定的评分内容以及它的评分要求,都以json的格式返回结果,如果该采购活动有多个包,则最外层键名为对应的包名。请不要回答有关资格审查的内容" - user_query = ( - """ -你是一个对招投标业务非常熟悉的专家。根据该文档中的评分标准表格中的内容,请你列出该文件的技术评分,商务评分,投标报价评审以及它们对应的具体评分要求,请以json格式返回结果,最外层键名分别是'技术评分','商务评分','投标报价评分',请在这三大项评分中分别用若干键值对表示具体评分项,外层键名为各评审因素,键值为一个列表,列表中包含若干(可为一)描述该评审因素的评分及要求的字典,内层键名分别是'评分'和'要求',若无评分,可删去'评分'键值对,'要求'中说明了该评审因素的评分标准;若这三大项评分中存在其他信息,则在相应评分大块内部新增键名'备注'存放该信息,键值为具体的要求,否则不需要。如果评分内容(因素)不是这三大项,则返回表格中给定的评分内容(因素)以及它们的具体评分要求。 -要求与指南: -1.请首先定位评分细则的表格,不要回答有关资格审查、符合性审查的内容,也不要从评标办法正文中(表格外)提取回答 -2.若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如{"技术评分":"本项目无技术评分项"} -3. 如果该招标活动有多个包,则最外层键名为对应的包名,否则不需要 -4.你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体。 -5. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' -6. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 - -以下为示例输出,仅供格式参考: - { - "一包": { - "技术评分": { - "主要监理岗位的职责": [ - { - "评分": "4分", - "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" - } - ], - "备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分(50分)不得分。" - }, - "商务评分": { - "控制系统内主板": [ - { - "评分": "10分", - "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" - } - ], - "制造商技术实力": [ - { - "评分": "3分", - "要求": "一级证书得3分,二级证书得1分,其他不得分" - }, - { - "评分": "2分", - "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" - } - ] - }, - "投标报价评审": { - "投标报价是否出现违反计价规范": [ - { - "评分": "合格制", - "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" - } - ] - } - } - } - """ - ) - # 执行第二个查询 - evaluation_res = qianwen_long(file_id, user_query) #有些重复的键名,只有qianwen_long_text能保留 - # print(evaluation_res) - # 清理和处理响应 - cleaned_evaluation_res = parse_json_with_duplicates(evaluation_res) #处理重复键名的情况 - result_data = process_data_based_on_key(cleaned_evaluation_res) #处理不知名外键的情况 - include = ['一包', '二包', '三包', '四包', '五包'] - target_values = ['技术', '设计', '实施'] - updated_jsons = {} - - # 检查是否有外层键匹配 include 列表 - if any(key for key in result_data if any(included in key for included in include)): #检查result_data中的任何键是否包含include列表中的任意一个项。 - # 有匹配的项,处理这些项 - for key in result_data: - if any(item in key for item in include): - inner_dict = result_data[key] - updated_jsons[key] = combine_technical_and_business(inner_dict, target_values) #对于分包,单独对分包内的'技术评分''商务评分'作处理 - else: - # 没有匹配的项,对整个字典运行 - updated_jsons = combine_technical_and_business(result_data, target_values) - final_res=reorganize_data(updated_jsons,include) #重新组织字典,尤其是分包的情况 - return final_res - else: - # 如果 judge 是 False,直接返回默认的技术标和商务标的结构 - result_data = {} - result_data['技术评分'] = '本招标文件没有技术评分!' - result_data['商务评分'] = '本招标文件没有商务评分!' - return result_data - - except Exception as e: - print(f"Error in combine_evaluation_standards: {e}") - # 在出错时返回默认的包含空字符串的字典 - return DEFAULT_EVALUATION_REVIEW.copy() - - -if __name__ == "__main__": - start_time=time.time() - truncate_file=r"C:\Users\Administrator\Desktop\招标文件-采购类\tmp2\2024-新疆-塔城地区公安局食药环分局快检实验室项目_evaluation_method.pdf" - # truncate_file = "C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件(统计局智能终端二次招标)_evaluation_method.pdf" - # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\广水市妇幼招标文件最新(W改)_evaluation_method.pdf" - # truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf" - # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf" - res = combine_evaluation_standards(truncate_file) - print(json.dumps(res, ensure_ascii=False, indent=4)) - end_time=time.time() - print("elapsed time:"+str(end_time-start_time)) diff --git a/flask_app/货物标/资格审查main.py b/flask_app/货物标/资格审查main.py index 74b67ca..0cf0784 100644 --- a/flask_app/货物标/资格审查main.py +++ b/flask_app/货物标/资格审查main.py @@ -482,31 +482,35 @@ def combine_qualification_review(invalid_path, qualification_path, notice_path): "key": "资格性审查", "query": ''' 问题:该招标文件中规定的资格性审查标准是什么的? - 输出要求: - 1.请以json格式给出,外层为'资格性审查',最内层的值需要用列表包裹。 - 2.一层嵌套内的键需要总结分类为某类评审因素。 - 3.你的回答要与原文完全一致,不要回答有关符合性审查的内容。 - 4.仔细检查你所选取的标准,若发现这些标准实际上是在描述不允许出现的资格性审查情况,则将外键替换为'资格性审查(以下情况不得出现)',并将这些标准写入其中。 - 5.最大细分为二层嵌套即可。 + 要求与指南: + 1.请以 JSON 格式 返回内容,外层键名为 "资格性审查",最内层键值为一个字符串列表(列表长度可以为一),每个元素为一个具体的评审标准。 + 2.根据文本内容,可以选择以下两种方式组织回答: + 使用嵌套键值对:最多嵌套一层,嵌套键名应为各评审因素或评审内容,键值为具体的评审标准列表,列表长度可为一。 + 不使用嵌套键值对:直接将评审标准作为键值,无需分类嵌套。 + 3.键值中的字符串内容要与原文一致,不得增添或遗漏,也不要回答有关符合性审查的内容。 + 特殊情况: + 仔细检查评审标准或内容,若发现这些标准或内容实际上是在描述资格性审查中不允许出现的情况,则将外键名替换为'资格性审查(以下情况不得出现)',并将这些标准写入其中。 输出示例1: { - "资格性审查": { #一层嵌套 - "某类评审因素": [ #二层嵌套 - "因素1", - "因素2" + "资格性审查": { + "评审因素1": [ + "评审标准或内容1", + "评审标准或内容2" + ], + "评审因素2":[ + "评审标准或内容3", + "评审标准或内容4" ] ... } } 输出示例2: { - "资格性审查(以下情况不得出现)": { #若发现文中出现均为反向标准,用像该示例一样的处理 - "某类不允许的评审因素": [ #二层嵌套 - "因素1", - "因素2" - ] + "资格性审查(以下情况不得出现)": [ + "内容1", + "内容2" ... - } + ] } ''' }, @@ -514,31 +518,35 @@ def combine_qualification_review(invalid_path, qualification_path, notice_path): "key": "符合性审查", "query": ''' 问题:该招标文件中规定的符合性审查标准是什么的? - 输出要求: - 1.请以json格式给出,外层为'符合性审查',最内层的值需要用列表包裹。 - 2.一层嵌套内的键需要总结分类为某类评审因素或是直接使用原文中的评审因素字段、标题。 - 3.你的回答要与原文完全一致,也不要回答有关资格性审查的内容。 - 4.仔细检查你所选取的标准,若发现这些标准实际上是在描述不允许出现的符合性审查情况,则将外键替换为'符合性审查(以下情况不得出现)',并将这些标准写入其中。 - 5.最大细分为二层嵌套即可。 + 要求与指南: + 1.请以 JSON 格式 返回内容,外层键名为 "符合性审查",最内层键值为一个字符串列表(列表长度可以为一),每个元素为一个具体的评审标准。 + 2.根据文本内容,可以选择以下两种方式组织回答: + 使用嵌套键值对:最多嵌套一层,嵌套键名应为各评审因素或评审内容,键值为具体的评审标准列表,列表长度可为一。 + 不使用嵌套键值对:直接将评审标准作为键值,无需分类嵌套。 + 3.键值中的字符串内容要与原文一致,不得增添或遗漏,也不要回答有关资格性审查的内容。 + 特殊情况: + 仔细检查评审标准或内容,若发现这些标准或内容实际上是在描述符合性审查中不允许出现的情况,则将外键名替换为'符合性审查(以下情况不得出现)',并将这些标准写入其中。 输出示例1: { - "符合性审查": { #一层嵌套 - "某类评审因素": [ #二层嵌套 - "因素1", - "因素2" + "符合性审查": { + "评审因素1": [ + "评审标准或内容1", + "评审标准或内容2" + ], + "评审因素2":[ + "评审标准或内容3", + "评审标准或内容4" ] ... } } 输出示例2: { - "符合性审查(以下情况不得出现)": { #若发现文中出现均为反向标准,用像该示例一样的处理 - "某类不允许的评审因素": [ #二层嵌套 - "因素1", - "因素2" - ] + "符合性审查(以下情况不得出现)": [ + "内容1", + "内容2" ... - } + ] } ''' } @@ -664,83 +672,6 @@ def combine_qualification_review(invalid_path, qualification_path, notice_path): # 最终处理结果,例如打印或保存 return {"资格审查": processed_data} - -# def combine_qualification_review(invalid_path, output_folder, qualification_path, notice_path): -# DEFAULT_QUALIFICATION_REVIEW = { -# "资格审查": { -# "资格审查": "", -# "符合性审查": "" -# } -# } -# -# def process_file(file_path, invalid_path): -# file_id = upload_file(file_path) -# first_query = """ -# 该文档中是否有关于资格性审查标准的具体内容,是否有关于符合性审查标准的具体内容?请以json格式给出回答,外键分别为'资格性审查'和'符合性审查',键值仅限于'是','否',输出格式示例如下: -# { -# "资格性审查":"是", -# "符合性审查":"是" -# } -# """ -# qianwen_ans = clean_json_string(qianwen_long(file_id, first_query)) -# user_queries = [ -# { -# "key": "资格性审查", -# "query": "该招标文件中规定的资格性审查标准是怎样的?请以json格式给出,外层为'资格性审查',你的回答要与原文完全一致,不可擅自总结删减,也不要回答有关符合性性审查的内容。" -# }, -# { -# "key": "符合性审查", -# "query": "该招标文件中规定的符合性审查标准是怎样的?请以json格式给出,外层为'符合性审查',你的回答要与原文完全一致,不可擅自总结删减,也不要回答有关资格性审查的内容。" -# } -# ] -# combined_res = {} -# file_id2 = None # 延迟上传 invalid_path -# def process_single_query(query_info): -# nonlocal file_id2 -# key = query_info["key"] -# query = query_info["query"] -# # 根据键值决定使用哪个 file_id -# if qianwen_ans.get(key) == "否": -# print("no") -# if not file_id2: -# file_id2 = upload_file(invalid_path) -# current_file_id = file_id2 -# else: -# current_file_id = file_id -# -# # 调用大模型获取回答 -# ans = qianwen_long(current_file_id, query) -# cleaned_data = clean_json_string(ans) -# processed = process_dict(preprocess_dict(cleaned_data)) -# return processed -# -# # 使用线程池并行处理查询 -# with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: -# futures = [executor.submit(process_single_query, q) for q in user_queries] -# for future in concurrent.futures.as_completed(futures): -# result = future.result() -# combined_res.update(result) -# return combined_res -# -# try: -# if not qualification_path: -# file_to_process = invalid_path -# else: -# file_to_process = qualification_path -# -# combined_res = process_file(file_to_process,invalid_path) -# match_keys = find_chapter_clause_references(combined_res) -# -# if not match_keys: -# return {"资格审查": combined_res} -# -# return process_additional_queries(combined_res, match_keys, output_folder, notice_path,invalid_path) #还要跳转到第一章 -# -# except Exception as e: -# print(f"Error in combine_qualification_review: {e}") -# return DEFAULT_QUALIFICATION_REVIEW.copy() - - # 整合基础信息核心代码 # [{'资格性审查.资格要求': '符合本采购文件第一章第二款要求,并提供合格有效的证明材料'}, {'资格性审查.没有重大违法记录的书面声明': '是否提交参加政府采购活动前三年内在经营活动中没有重大违法记录的书面承诺或声明(格式要求详见本项目采购文件第六章相关格式要求)'}] if __name__ == "__main__": @@ -750,10 +681,10 @@ if __name__ == "__main__": output_folder=r"D:\flask_project\flask_app\static\output\output1\c911b0f8-0ff4-4718-80e3-86f464f313d3" # qualification_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\52e54b20-c975-4cf3-a06b-6f146aaa93f5\\ztbfile_qualification1.pdf" # qualification_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\6558a50a-13ea-4279-a5db-684935481c39\\ztbfile_qualification2.pdf" - qualification_path=r"C:\Users\Administrator\Desktop\fsdownload\16fd6b4e-3975-4c83-8ba6-1bc9263a6a5b\ztbfile_qualification1.pdf" + qualification_path=r"C:\Users\Administrator\Desktop\货物标\output3\招标文件(定稿)_qualification1.pdf" # notice_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\6558a50a-13ea-4279-a5db-684935481c39\\ztbfile_notice.pdf" # notice_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\52e54b20-c975-4cf3-a06b-6f146aaa93f5\\ztbfile_notice.pdf" - notice_path=r"C:\Users\Administrator\Desktop\fsdownload\16fd6b4e-3975-4c83-8ba6-1bc9263a6a5b\ztbfile_notice.pdf" + notice_path=r"C:\Users\Administrator\Desktop\new招标文件\货物标\tmp1\HBDL-2024-0362-001-招标文件_notice.pdf" # knowledge_name = "6.2视频会议docx" # invalid_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf" # invalid_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\52e54b20-c975-4cf3-a06b-6f146aaa93f5\\ztbfile.pdf"