12.11 解析优化

This commit is contained in:
zy123 2024-12-11 17:42:51 +08:00
parent dcb56d5bfd
commit e6b2c4c6e6
22 changed files with 944 additions and 829 deletions

View File

@ -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...")
# 相关参数

View File

@ -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))

View File

@ -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:

View File

@ -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
# 如果所有尝试都失败了,返回空字符串

View File

@ -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 和对应的输出文件夹

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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
#分段返回

View File

@ -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__":

View File

@ -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)

View File

@ -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))

View File

@ -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))

View File

@ -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:

View File

@ -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))

View File

@ -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)

View File

@ -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"

View File

@ -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 参数测试不同的场景

View File

@ -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))

View File

@ -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"