269 lines
12 KiB
Python
269 lines
12 KiB
Python
# -*- encoding:utf-8 -*-
|
||
import json
|
||
import logging
|
||
import re
|
||
from flask_app.general.json_utils import clean_json_string
|
||
from flask_app.general.llm.多线程提问 import multi_threading
|
||
from flask_app.general.llm.通义千问long import upload_file, qianwen_long
|
||
from flask_app.old_version.判断是否分包等_old import read_questions_from_judge
|
||
|
||
def get_deviation_requirements(invalid_path):
|
||
file_id=upload_file(invalid_path)
|
||
user_query="""该招标文件对响应文件(投标文件)偏离项的要求或内容是怎样的?请不要回答具体的技术参数,也不要回答具体的评分要求。请以json格式给我提供信息,外层键名为'偏离',若存在嵌套信息,嵌套内容键名为文件中对应字段或是你的总结,而嵌套键值必须与原文保持一致,若文中未涉及相关内容,在键值中填'未知'。
|
||
注意:不使用任何预设的示例作为回答,示例仅作为格式参考。
|
||
禁止内容:
|
||
确保所有输出内容均基于提供的实际招标文件内容,不使用任何预设的示例作为回答。
|
||
示例1,嵌套键值对情况:
|
||
{
|
||
"偏离":{
|
||
"技术要求":"以★标示的内容不允许负偏离",
|
||
"商务要求":"以★标示的内容不允许负偏离"
|
||
}
|
||
}
|
||
示例2,无嵌套键值对情况:
|
||
{
|
||
"偏离":"所有参数需在技术响应偏离表内响应,如应答有缺项,且无有效证明材料的,评标委员会有权不予认可,视同负偏离处理"
|
||
}
|
||
"""
|
||
model_res=qianwen_long(file_id,user_query)
|
||
return clean_json_string(model_res)
|
||
|
||
def process_judge_questions(judge_file_path, chosen_numbers, invalid_path, baseinfo_list1):
|
||
file_id=upload_file(invalid_path)
|
||
judge_questions = read_questions_from_judge(judge_file_path, chosen_numbers)
|
||
judge_consortium = judge_consortium_bidding(baseinfo_list1)
|
||
if judge_consortium:
|
||
judge_consortium_question = (
|
||
"该招标文件对于联合体投标的要求是怎样的,请按json格式给我提供信息,"
|
||
"外层键名为'联合体投标要求',其中有一个嵌套键值对为:\"是否接受联合体投标\":\"是\""
|
||
)
|
||
judge_questions.append(judge_consortium_question)
|
||
if not judge_questions:
|
||
print("process_judge_questions:没有需要处理的 judge_questions,跳过 multi_threading 调用。")
|
||
return # 或者根据需要返回其他值
|
||
# file_id3 = upload_file(merged_baseinfo_path)
|
||
res2 = multi_threading(judge_questions, "", file_id, 2)
|
||
for question, response in res2:
|
||
baseinfo_list1.append(clean_json_string(response))
|
||
|
||
def aggregate_basic_info(baseinfo_list,mode="engineering"):
|
||
"""
|
||
将基础信息列表中的数据进行合并和分类。
|
||
|
||
参数:
|
||
- baseinfo_list (list): 包含多个基础信息的列表。
|
||
|
||
返回:
|
||
- dict: 合并和分类后的基础信息字典。
|
||
"""
|
||
key_groups = {
|
||
"招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式","项目联系方式"],
|
||
"项目信息": ["项目名称", "项目编号", "项目概况", "项目基本情况", "招标控制价", "投标竞争下浮率"],
|
||
"关键时间/内容": [
|
||
"投标文件递交截止日期",
|
||
"开标时间",
|
||
"开标地点",
|
||
"澄清招标文件的截止时间",
|
||
"投标有效期",
|
||
"信息公示媒介"
|
||
],
|
||
"保证金相关": [],
|
||
"其他信息": [
|
||
"重新招标、不再招标和终止招标",
|
||
"投标费用承担",
|
||
"是否退还投标文件",
|
||
]
|
||
}
|
||
# 如果模式是 'goods',则添加 "采购要求" 组
|
||
if mode == 'goods':
|
||
key_groups["采购要求"] = [
|
||
"采购需求",
|
||
"技术要求",
|
||
"服务要求",
|
||
"商务要求",
|
||
"其他要求"
|
||
]
|
||
# 定义采购要求的默认值
|
||
DEFAULT_PROCUREMENT_REQS = {
|
||
"采购需求": {},
|
||
"技术要求": ["未提供"],
|
||
"商务要求": ["未提供"],
|
||
"服务要求": ["未提供"],
|
||
"其他要求": ["未提供"]
|
||
}
|
||
combined_data = {}
|
||
relevant_keys_detected = set()
|
||
|
||
# 合并所有基础信息并收集相关键
|
||
for baseinfo in baseinfo_list:
|
||
combined_data.update(baseinfo)
|
||
relevant_keys_detected.update(baseinfo.keys())
|
||
|
||
# 动态调整键组
|
||
dynamic_key_handling(key_groups, relevant_keys_detected)
|
||
|
||
# 创建一个副本以存储未分类的项目
|
||
unclassified_items = {k: v for k, v in combined_data.items() if
|
||
k not in [item for sublist in key_groups.values() for item in sublist]}
|
||
|
||
# 定义一个辅助函数,用于查找包含目标键的实际键
|
||
def find_matching_key(combined_data, target_key):
|
||
for k in combined_data:
|
||
if target_key in k:
|
||
return k
|
||
return None
|
||
|
||
# 按键组分类并嵌套
|
||
for group_name, keys in key_groups.items():
|
||
group_data = {}
|
||
for key in keys:
|
||
if group_name == "采购要求":
|
||
# 在“采购要求”组中使用子字符串匹配
|
||
matched_key = find_matching_key(combined_data, key)
|
||
if matched_key:
|
||
group_data[matched_key] = combined_data[matched_key]
|
||
# 从未分类项目中移除已匹配的键
|
||
unclassified_items.pop(matched_key, None)
|
||
else:
|
||
group_data[key] = DEFAULT_PROCUREMENT_REQS.get(key, "未提供")
|
||
else:
|
||
# 在其他组中使用严格匹配
|
||
if key in combined_data:
|
||
group_data[key] = combined_data[key]
|
||
# 从未分类项目中移除已匹配的键
|
||
unclassified_items.pop(key, None)
|
||
else:
|
||
group_data[key] = "未提供"
|
||
|
||
# 特殊处理 "招标人/代理信息" 组中的 "项目联系方式"
|
||
if group_name == "招标人/代理信息" and "项目联系方式" in group_data:
|
||
project_contact = group_data["项目联系方式"]
|
||
if isinstance(project_contact, dict):
|
||
if all(inner_value == "未知" for inner_value in project_contact.values()):
|
||
# 移除 "项目联系方式"
|
||
del group_data["项目联系方式"]
|
||
|
||
combined_data[group_name] = group_data
|
||
|
||
# 将剩余未分类的键值对添加到 "其他信息" 组(如果存在)
|
||
if "其他信息" in combined_data:
|
||
combined_data["其他信息"].update(unclassified_items)
|
||
else:
|
||
combined_data["其他信息"] = unclassified_items
|
||
|
||
# 移除顶层的未分类键值对
|
||
for key in list(combined_data.keys()):
|
||
if key not in key_groups:
|
||
del combined_data[key]
|
||
|
||
return combined_data
|
||
|
||
|
||
def dynamic_key_handling(key_groups, detected_keys):
|
||
# 检查和调整键组配置
|
||
for key in detected_keys:
|
||
if "技术、服务要求" in detected_keys:
|
||
# 如果检测到“技术、服务要求”,则移除“技术要求”和“服务要求”
|
||
if "技术要求" in key_groups["采购要求"]:
|
||
key_groups["采购要求"].remove("技术要求")
|
||
if "服务要求" in key_groups["采购要求"]:
|
||
key_groups["采购要求"].remove("服务要求")
|
||
# 确保"技术、服务要求"存在于"采购要求"组中
|
||
if "技术、服务要求" not in key_groups["采购要求"]:
|
||
key_groups["采购要求"].insert(1, "技术、服务要求")
|
||
# 处理“保证金相关”组
|
||
elif "保证金" in key:
|
||
if "保证金相关" not in key_groups:
|
||
key_groups["保证金相关"] = []
|
||
# 直接追加到 "保证金相关" 组的末尾
|
||
if key not in key_groups["保证金相关"]:
|
||
key_groups["保证金相关"].append(key)
|
||
elif "联合体" in key:
|
||
key_groups["项目信息"].append(key)
|
||
elif "分包" in key:
|
||
key_groups["项目信息"].append(key)
|
||
elif "踏勘现场" in key:
|
||
key_groups["其他信息"].append(key)
|
||
elif "投标预备会" in key:
|
||
key_groups["其他信息"].append(key)
|
||
elif "偏离" in key:
|
||
key_groups["其他信息"].append(key)
|
||
elif "递交方式" in key or "递交地点" in key:
|
||
group = key_groups["关键时间/内容"]
|
||
insert_after = "投标文件递交截止日期"
|
||
if insert_after in group:
|
||
index = group.index(insert_after)
|
||
# 确保新键不重复
|
||
if key not in group:
|
||
group.insert(index + 1, key)
|
||
else:
|
||
# 如果“投标文件递交截止日期”不存在,则追加到末尾
|
||
if key not in group:
|
||
group.append(key)
|
||
|
||
def judge_consortium_bidding(baseinfo_list):
|
||
updated_list = []
|
||
accept_bidding = False
|
||
for baseinfo in baseinfo_list:
|
||
# 检查 "是否接受联合体投标" 键是否存在且其值为 "是"
|
||
if "是否接受联合体投标" in baseinfo and baseinfo["是否接受联合体投标"] == "是":
|
||
accept_bidding = True
|
||
# 从字典中移除特定键值对
|
||
baseinfo.pop("是否接受联合体投标", None)
|
||
# # 将修改后的 json 数据转换回 JSON 字符串(如果需要)
|
||
# updated_info = json.dumps(json_data)
|
||
updated_list.append(baseinfo)
|
||
# 更新原始列表,如果你想保留修改
|
||
baseinfo_list[:] = updated_list
|
||
return accept_bidding
|
||
|
||
#字符串列表转为普通列表,从大模型回答中提取
|
||
def process_string_list(string_list):
|
||
# 使用正则表达式匹配方括号内的内容
|
||
try:
|
||
match = re.search(r'\[(.*?)\]', string_list)
|
||
if match:
|
||
# 获取匹配的内容,即方括号内的部分
|
||
content_inside_brackets = match.group(1)
|
||
if content_inside_brackets: # 检查内容是否为空
|
||
# 提取所有数字项,并转换为整数
|
||
numbers = [
|
||
int(item.strip()) for item in content_inside_brackets.split(',')
|
||
if re.match(r'^\d+$', item.strip()) # 正则表达式判断是否为纯数字
|
||
]
|
||
return numbers
|
||
else:
|
||
return [] # 如果内容为空,直接返回空列表
|
||
else:
|
||
return [] # 如果没有匹配到内容,返回空列表
|
||
except Exception as e:
|
||
print(f"Error occurred: {e}")
|
||
return [] # 出现任何异常时返回空列表
|
||
|
||
#统计tokens,非常粗糙;现在直接调用qianwen doubao的相关接口直接统计,该函数废弃。
|
||
def count_tokens(text):
|
||
"""
|
||
统计文本中的 tokens 数量:
|
||
1. 英文字母+数字作为一个 token(如 DN90)。
|
||
2. 数字+小数点/百分号作为一个 token(如 0.25%)。
|
||
3. 单个中文字符作为一个 token。
|
||
4. 单个符号或标点符号作为一个 token。
|
||
5. 忽略空白字符(空格、空行等)。
|
||
"""
|
||
# 正则表达式:
|
||
# - 英文字母和数字组合:DN90
|
||
# - 数字+小数点/百分号组合:0.25%
|
||
# - 单个中文字符:[\u4e00-\u9fff]
|
||
# - 单个非空白符号:[^\s]
|
||
token_pattern = r'[a-zA-Z0-9]+(?:\.\d+)?%?|[\u4e00-\u9fff]|[^\s]'
|
||
tokens = re.findall(token_pattern, text)
|
||
return len(tokens)# 返回 tokens 数量和匹配的 token 列表
|
||
|
||
#根据id获取日志
|
||
def get_global_logger(unique_id):
|
||
if unique_id is None:
|
||
return logging.getLogger() # 获取默认的日志器
|
||
logger = logging.getLogger(unique_id)
|
||
return logger
|
||
|