zbparse/flask_app/general/商务技术评分提取.py

446 lines
24 KiB
Python
Raw Normal View History

2024-12-11 17:42:51 +08:00
import json
2024-12-25 14:35:52 +08:00
import os
2024-12-11 17:42:51 +08:00
import re
import time
from collections import defaultdict
from flask_app.general.doubao import get_total_tokens, read_txt_to_string, doubao_model
from flask_app.general.file2markdown import convert_file_to_markdown
from flask_app.general.format_change import get_pdf_page_count, pdf2docx
2024-12-24 15:13:11 +08:00
from flask_app.general.json_utils import clean_json_string, extract_content_from_json
2024-12-20 17:24:49 +08:00
from flask_app.general.model_continue_query import process_continue_answers
from flask_app.general.通义千问long import upload_file, qianwen_long, qianwen_plus
2024-12-11 17:42:51 +08:00
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 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
2024-12-25 14:35:52 +08:00
# 格式要求:
# 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,具体规则如下:
# 1. 如果评审因素存在嵌套,使用嵌套键值对表示:
# -主评审因素的键名后需附加括号表示该主因素下所有子因素总分例如产品技术响应8分
# -子评审因素作为主评审因素的内层键名
# 2. 如果评审因素不存在嵌套,那么键名就是该评审因素
# 3. 每个评审因素的最内层键值都是列表,列表中包含描述评分及要求的字典,字典需包含以下键:
# '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。
# '要求':说明评分标准。
# 4.若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注',值为该要求。
2024-12-11 17:42:51 +08:00
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):
print("判断有无评分")
2024-12-11 17:42:51 +08:00
# 上传文件并获取文件ID
file_id = upload_file(file_path)
# 定义用户查询
query = (
"""根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求如果有,返回'',否则返回''
要求与指南
2024-12-17 14:47:19 +08:00
1. 评分要求主要以表格形式呈现且有评分因素及评分要求标准
2. 竞争性磋商文件通常无评分要求但若满足'1.'的内容也请返回''
3. 仅返回''''不需要其他解释或内容
"""
2024-12-11 17:42:51 +08:00
) # 应对竞争性谈判这种无评分要求的情况
# 执行查询
return qianwen_long(file_id, query),file_id
def run_second_qeury(file_id,processed_filepath,model_type):
2024-12-25 14:35:52 +08:00
print("获取评分项...")
2024-12-11 17:42:51 +08:00
# 执行 user_query 相关的逻辑
user_query_1 = (
"""
你是一个对招投标业务非常熟悉的专家根据该文档中的评标办法表格请你列出该文件的技术评分商务评分投标报价评分以及它们对应的具体评分要求请以JSON格式返回结果
2024-12-25 14:35:52 +08:00
格式要求
1.总体结构
-JSON 的最外层包含三个键技术评分商务评分 投标报价评分
-每个大项如技术评分商务评分下包含具体的评分项评分项按以下规则表示
2.评分项表示规则
-层级嵌套规则
若评分因素存在嵌套关系主评分因素需附加括号括号中注明该主评分项的总分例如产品技术响应8子评分因素作为嵌套键列在主评分因素之下无需再附加括号表示评分
若评分因素不存在嵌套关系键名直接为评分因素无需附加括号表示总分
-评分项内容
-每个评分项最内层值都是列表列表中包含描述评分标准或要求的字典
-字典个数
默认为1个字典若某评分因素包括多个评分标准多个表格单元格可以用多个并列字典表示
-字典结构如下
评分该评分标准的总分 8不能是一个范围数字如0-8若为定性指标合格制可标明相应的定性指标无评分时可删去'评分'键值对
要求说明评分标准或要求
-禁止情况
禁止将同个单元格内的内容拆分至多个字典中禁止遗漏单元格内任何信息包括注的内容
2024-12-25 14:35:52 +08:00
3.备注信息
-若评分部分包含附加信息如大项评分的整体要求未直接归属于具体评分项需添加一个 备注 值为该附加信息
2024-12-11 17:42:51 +08:00
要求与指南
1. 请首先定位评分细则的表格不要回答有关资格审查的内容也不要从评标办法正文中提取回答
2. 若表格中商务和技术评分混合一起请根据你对招投标业务的熟悉对实际表格内容的评分因素进行准确分类归类至商务评分或技术评分
3. 若表中的评分大项不是这三个请你根据语义及你对招投标业务的熟悉分别映射到'技术评分''商务评分''投标报价评分'而不必严格按照表格中的名称
特殊情况
1. 最外层键名为各大评分项但是如果该招标采购活动有多个分包则最外层键名为对应的包名'一包'内部格式不变
2. 若大项的'xx评分'要求未在文中说明则键名'xx评分'的键值设为字符串'本项目无xx评分项'例如"技术评分":"本项目无技术评分项"而非默认的字典格式
禁止内容
1. 确保所有输出内容均基于提供的实际招标文件内容除了最外层的三个评分大项名称不使用任何预设的示例作为回答
2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名
2024-12-11 17:42:51 +08:00
以下为示例输出仅供格式参考
{
"一包": {
"技术评分": {
2024-12-25 14:35:52 +08:00
"实施方案(16分)":{
2024-12-11 17:42:51 +08:00
"总体实施方案":[
{
"评分":8,
"要求":"根据投标人总体实施方案进行评分"
}
],
"项目实施要点":[
{
"评分":8,
"要求":"根据投标人对项目实施要点、难点进行评分。"
2024-12-11 17:42:51 +08:00
}
]
},
2024-12-25 14:35:52 +08:00
"设计创意": [
2024-12-11 17:42:51 +08:00
{
2024-12-25 14:35:52 +08:00
"评分": "10分",
"要求": "主题突出形式多样内容与形式完美统一得10分其他酌情打分。"
2024-12-11 17:42:51 +08:00
}
],
2024-12-25 14:35:52 +08:00
"备注": "技术标采用暗标形式,暗标不得出现投标人名称、人员姓名。"
2024-12-11 17:42:51 +08:00
},
"商务评分": {
2024-12-25 14:35:52 +08:00
"主要监理岗位的职责": [
2024-12-11 17:42:51 +08:00
{
2024-12-25 14:35:52 +08:00
"评分": "4分",
"要求": "1、总监理工程师的职责全面、清晰、合理得 2 分;一般的 1 分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 2 分;一般的 1 分。"
2024-12-11 17:42:51 +08:00
}
],
2024-12-25 14:35:52 +08:00
"制造商实力": [
2024-12-11 17:42:51 +08:00
{
"评分": "3分",
"要求": "一级证书得3分二级证书得1分其他不得分。"
2024-12-11 17:42:51 +08:00
},
{
"评分": "2分",
"要求": "行业销量排名连续前 2 名,得 2 分,第 3-6 名得 0.5 分,其他不得分。"
2024-12-11 17:42:51 +08:00
}
]
},
"投标报价评分": {
"投标报价是否出现违反计价规范": [
{
"评分": "合格制",
"要求": "A:投标报价未违反计价规范的评审意见为“合格”B投标报价违反计价规范的评审意见为“不合格”"
}
]
}
}
}
"""
)
user_query_2 = (
"""
你是一个对招投标业务非常熟悉的专家根据该文档中的评标办法表格请你列出该文件的技术评分商务评分投标报价评分以及它们对应的具体评分要求请以JSON格式返回结果
格式要求
2024-12-25 14:35:52 +08:00
1.总体结构
-JSON 的最外层包含三个键技术评分商务评分 投标报价评分
-每个大项如技术评分商务评分下包含具体的评分项评分项按以下规则表示
2.评分项表示规则
-层级嵌套规则
若评分因素内容存在嵌套关系主评分因素需附加括号括号中注明该主评分项的总分例如产品技术响应8子评分因素作为嵌套键名列在主评分因素之下无需再附加括号表示评分子评分因素不可从'评分标准'中总结
若评分因素内容不存在嵌套关系键名直接为评分因素无需附加括号表示总分
2024-12-25 14:35:52 +08:00
-评分项内容
-每个评分项最内层值都是列表列表中包含描述评分标准或要求的字典
-字典个数
默认为1个字典若某评分因素包括多个评分标准多个表格单元格可以用多个并列字典表示
-字典结构如下
评分该评分标准的总分 8不能是一个范围数字如0-8若为定性指标合格制可标明相应的定性指标无评分时可删去'评分'键值对
要求说明评分标准或要求
-禁止情况
禁止将同个单元格内的内容拆分至多个字典中禁止遗漏单元格内任何信息包括注的内容
2024-12-25 14:35:52 +08:00
3.备注信息
-若评分部分包含附加信息如大项评分的整体要求未直接归属于具体评分项需添加一个 备注 值为该附加信息
2024-12-11 17:42:51 +08:00
要求与指南
1. 请首先定位评分细则的表格不要回答有关资格审查的内容也不要从评标办法正文中提取回答
2. 若表格中商务和技术评分混合一起请根据你对招投标业务的熟悉对实际表格内容的评分因素进行准确分类归类至商务评分或技术评分
3. 若表中的评分大项不是这三个请你根据语义及你对招投标业务的熟悉分别映射到'技术评分''商务评分''投标报价评分'而不必严格按照表格中的名称
2024-12-24 17:32:00 +08:00
特殊情况
1. 最外层键名为各大评分项但是如果该招标采购活动有多个分包则最外层键名为对应的包名'一包'内部格式不变
2. 2. 若大项的'xx评分'要求未在文中说明则键名'xx评分'的键值设为字符串'本项目无xx评分项'例如"技术评分":"本项目无技术评分项"而非默认的字典格式
禁止内容
1. 确保所有输出内容均基于提供的实际招标文件内容除了最外层的三个评分大项名称不使用任何预设的示例作为回答
2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名
2024-12-11 17:42:51 +08:00
以下为示例输出仅供格式参考
{
"一包": {
"技术评分": {
2024-12-25 14:35:52 +08:00
"产品技术响应8分":{
"常规参数符合":[
{
"评分":"4分",
"要求":"未标★项为常规参数每条负偏离扣1分本项满分4分。"
}
],
"控制系统":[
2024-12-25 14:35:52 +08:00
{
"评分":"4分",
"要求":"所投电梯控制系统技术先进、市场美誉度高与整梯同品牌得4分所投电梯控制系统的技术基本先进、市场美誉度较高与整梯不同品牌得2分。"
2024-12-25 14:35:52 +08:00
}
]
},
"实施方案": [
2024-12-11 17:42:51 +08:00
{
2024-12-25 14:35:52 +08:00
"评分": "10分",
"要求": "实施方案清晰、完整、合理、可行的得 10 分。实施方案一般的得5分"
2024-12-11 17:42:51 +08:00
}
],
2024-12-25 14:35:52 +08:00
"备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分不得分。"
2024-12-11 17:42:51 +08:00
},
"商务评分": {
2024-12-25 14:35:52 +08:00
"主要监理岗位的职责": [
2024-12-11 17:42:51 +08:00
{
2024-12-25 14:35:52 +08:00
"评分": "4分",
"要求": "1、总监理工程师的职责全面、清晰、合理得 2 分;一般的 1 分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 2 分;一般的 1 分。"
2024-12-11 17:42:51 +08:00
}
],
2024-12-25 14:35:52 +08:00
"制造商实力": [
2024-12-11 17:42:51 +08:00
{
"评分": "3分",
"要求": "一级证书得3分二级证书得1分其他不得分"
},
{
"评分": "2分",
2024-12-25 14:35:52 +08:00
"要求": "行业销量排名连续前 2 名,得 2 分,第 3-6 名得 0.5 分,其他不得分"
2024-12-11 17:42:51 +08:00
}
]
},
"投标报价评分": {
"投标报价是否出现违反计价规范": [
{
"评分": "合格制",
"要求": "A:投标报价未违反计价规范的评审意见为“合格”B投标报价违反计价规范的评审意见为“不合格”"
}
]
}
}
}
"""
)
# 执行第二个查询
user_query = user_query_1 if zb_type == 1 else user_query_2
if model_type==4:
full_text = read_txt_to_string(processed_filepath)
user_query += f"\n文件内容:\n{full_text}\n"
2024-12-20 17:24:49 +08:00
questions_to_continue = []
temp_final={}
if model_type==4:
# evaluation_res=doubao_model(user_query,True)
evaluation_res = qianwen_plus(user_query, True)
else:
evaluation_res = qianwen_long(file_id, user_query,2,1,True) # 有些重复的键名只有qianwen_long_text能保留
2024-12-20 17:24:49 +08:00
message = evaluation_res[0]
total_tokens = evaluation_res[1]
2024-12-11 17:42:51 +08:00
# print(evaluation_res)
# 清理和处理响应
2024-12-24 15:13:11 +08:00
cleaned_evaluation_res = extract_content_from_json(message,True) # 处理重复键名的情况
2024-12-20 17:24:49 +08:00
# print(json.dumps(cleaned_evaluation_res,ensure_ascii=False,indent=4))
max_tokens = 7900 if model_type == 4 else 5900
if not cleaned_evaluation_res and total_tokens > max_tokens:
2024-12-20 17:24:49 +08:00
questions_to_continue.append((user_query, evaluation_res))
else:
temp_final.update(cleaned_evaluation_res)
if questions_to_continue:
continued_results = process_continue_answers(questions_to_continue, model_type, file_id)
2024-12-20 17:24:49 +08:00
temp_final.update(continued_results)
result_data = process_data_based_on_key(temp_final) # 处理不知名外键的情况
2024-12-11 17:42:51 +08:00
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
2024-12-25 14:35:52 +08:00
2024-12-11 17:42:51 +08:00
try:
judge_res,file_id = run_first_query(evaluation_method_path)
# 检查 judge_res 的内容
2024-12-25 14:35:52 +08:00
def get_default_result():
return {
'技术评分': '本招标文件没有技术评分!',
'商务评分': '本招标文件没有商务评分!'
}
# 如果 judge_res 包含 '是',直接运行第二步查询
2024-12-11 17:42:51 +08:00
if '' in judge_res:
if get_pdf_page_count(evaluation_method_path)<=20:
model_type=4 #qianwen-plus
processed_filepath = convert_file_to_markdown(evaluation_method_path,"extract2.txt") # 转markdown格式
else:
evaluation_method_docxpath=pdf2docx(evaluation_method_path)
file_id=upload_file(evaluation_method_docxpath)
model_type = 2 #qianwen-long
processed_filepath = ""
return run_second_qeury(file_id,processed_filepath,model_type)
2024-12-25 14:35:52 +08:00
# 标准化路径,避免多种表示形式造成的误判
eval_path = os.path.abspath(evaluation_method_path)
invalid_eval_path = os.path.abspath(invalid_path)
# 判断路径是否一致
if eval_path == invalid_eval_path:
# 如果路径一致,直接返回默认结果
return get_default_result()
# 路径不一致,尝试运行第一次查询
judge_res, file_id = run_first_query(invalid_path)
# 如果 judge_res 包含 '是',运行第二步查询
if '' in judge_res:
model_type = 0
processed_filepath = ""
return run_second_qeury(file_id, processed_filepath, model_type)
2024-12-25 14:35:52 +08:00
# 默认返回结果
return get_default_result()
2024-12-11 17:42:51 +08:00
except Exception as e:
print(f"Error in combine_evaluation_standards: {e}")
# 在出错时返回默认的包含空字符串的字典
return DEFAULT_EVALUATION_REVIEW.copy()
#目前评分这块如果表格过长会有问题可以考虑textin+doubao小于20页用text>20页转word->qianwen-long
2024-12-11 17:42:51 +08:00
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\文件解析问题\文件解析问题\82a6f11d-cfcd-4cb4-93e9-940fa24abb21\ztbfile_evaluation_method.pdf'
invalid_path=r'C:\Users\Administrator\Desktop\文件解析问题\文件解析问题\1414cb9c-7bf4-401c-8761-2acde151b9c2\ztbfile.docx'
2024-12-11 17:42:51 +08:00
# 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,2)
2024-12-11 17:42:51 +08:00
print(json.dumps(res, ensure_ascii=False, indent=4))
end_time=time.time()
print("elapsed time:"+str(end_time-start_time))