zbparse/flask_app/general/商务技术评分提取.py
2024-12-25 14:35:52 +08:00

416 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import os
import re
import time
from collections import defaultdict
from flask_app.general.doubao import get_total_tokens
from flask_app.general.json_utils import clean_json_string, extract_content_from_json
from flask_app.general.model_continue_query import process_continue_answers
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 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
# 格式要求:
# 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,具体规则如下:
# 1. 如果评审因素存在嵌套,使用嵌套键值对表示:
# -主评审因素的键名后需附加括号表示该主因素下所有子因素总分例如产品技术响应8分
# -子评审因素作为主评审因素的内层键名
# 2. 如果评审因素不存在嵌套,那么键名就是该评审因素
# 3. 每个评审因素的最内层键值都是列表,列表中包含描述评分及要求的字典,字典需包含以下键:
# '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。
# '要求':说明评分标准。
# 4.若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注',值为该要求。
def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type):
# 定义默认的评审结果字典
DEFAULT_EVALUATION_REVIEW = {
"技术评分": "",
"商务评分": ""
}
# 如果 truncate_file 是空字符串,直接返回包含空字符串的字典
if not evaluation_method_path:
return DEFAULT_EVALUATION_REVIEW.copy()
def run_first_query(file_path):
print("判断有无评分")
# 上传文件并获取文件ID
file_id = upload_file(file_path)
# 定义用户查询
query = (
"""根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求如果有,返回'',否则返回''
要求与指南:
1. 评分要求主要以表格形式呈现,且有评分因素及评分要求、标准。
2. 竞争性磋商文件通常无评分要求,但若满足'1.'的内容,也请返回''
3. 仅返回'''',不需要其他解释或内容。
"""
) # 应对竞争性谈判这种无评分要求的情况
# 执行查询
return qianwen_long(file_id, query),file_id
def run_second_qeury(file_id):
print("获取评分项...")
# 执行 user_query 相关的逻辑
user_query_1 = (
"""
你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格请你列出该文件的技术评分商务评分投标报价评分以及它们对应的具体评分要求请以JSON格式返回结果。
格式要求:
1.总体结构:
-JSON 的最外层包含三个键:技术评分、商务评分 和 投标报价评分。
-每个大项(如技术评分、商务评分)下包含具体的评分项,评分项按以下规则表示。
2.评分项表示规则:
-层级嵌套规则:
若评分因素存在嵌套关系主评分因素需附加括号括号中注明该主评分项的总分。例如产品技术响应8分子评分因素作为嵌套键列在主评分因素之下无需再附加括号表示评分。
若评分因素不存在嵌套关系:键名直接为评分因素,无需附加括号表示总分。
-评分项内容:
-每个评分项最内层值都是一个列表,列表中包含一个或多个描述评分标准的字典。
-字典结构如下:
评分(可选):具体得分(如 8分若为定性指标如“合格制”可标明“合格制”。
要求:详细描述评分标准及要求。
3.备注信息:
-若评分部分包含附加信息(如大项评分的整体要求,未直接归属于具体评分项),需添加一个 备注 键,值为该附加信息。
要求与指南:
1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答
2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体,尤其是
3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制'
4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项
5. 若表格中商务和技术评分混合一起,请根据实际表格内容进行准确分类。
6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分''商务评分''投标报价评分',而不必严格按照表格中的名称。
7. 若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项"
禁止内容:
1. 确保所有输出内容均基于提供的实际招标文件内容(除了最外层的三个评分大项名称),不使用任何预设的示例作为回答。
2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名。
以下为示例输出,仅供格式参考:
{
"一包": {
"技术评分": {
"实施方案(16分)":{
"总体实施方案":[
{
"评分":8,
"要求":"根据投标人总体实施方案进行评分"
}
],
"项目实施要点":[
{
"评分":8,
"要求":"根据投标人对项目实施要点、难点进行评分"
}
]
},
"设计创意": [
{
"评分": "10分",
"要求": "主题突出形式多样内容与形式完美统一得10分其他酌情打分"
}
],
"备注": "技术标采用暗标形式,暗标不得出现投标人名称、人员姓名。"
},
"商务评分": {
"主要监理岗位的职责": [
{
"评分": "4分",
"要求": "1、总监理工程师的职责全面、清晰、合理得 2 分;一般的 1 分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 2 分;一般的 1 分。"
}
],
"制造商实力": [
{
"评分": "3分",
"要求": "一级证书得3分二级证书得1分其他不得分"
},
{
"评分": "2分",
"要求": "行业销量排名连续前 2 名,得 2 分,第 3-6 名得 0.5 分,其他不得分"
}
]
},
"投标报价评分": {
"投标报价是否出现违反计价规范": [
{
"评分": "合格制",
"要求": "A:投标报价未违反计价规范的评审意见为“合格”B投标报价违反计价规范的评审意见为“不合格”"
}
]
}
}
}
"""
)
user_query_2 = (
"""
你是一个对招投标业务非常熟悉的专家。根据该文档中的评标办法表格请你列出该文件的技术评分商务评分投标报价评分以及它们对应的具体评分要求请以JSON格式返回结果。
格式要求:
1.总体结构:
-JSON 的最外层包含三个键:技术评分、商务评分 和 投标报价评分。
-每个大项(如技术评分、商务评分)下包含具体的评分项,评分项按以下规则表示。
2.评分项表示规则:
-层级嵌套规则:
若评分因素存在嵌套关系主评分因素需附加括号括号中注明该主评分项的总分。例如产品技术响应8分子评分因素作为嵌套键列在主评分因素之下无需再附加括号表示评分。
若评分因素不存在嵌套关系:键名直接为评分因素,无需附加括号表示总分。
-评分项内容:
-每个评分项最内层值都是一个列表,列表中包含一个或多个描述评分标准的字典。
-字典结构如下:
评分(可选):具体得分(如 8分若为定性指标如“合格制”可标明“合格制”。
要求:详细描述评分标准及要求。
3.备注信息:
-若评分部分包含附加信息(如大项评分的整体要求,未直接归属于具体评分项),需添加一个 备注 键,值为该附加信息。
要求与指南:
1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答
2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体
3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制'
4. 若表格中商务和技术评分混合一起,请根据你对招投标业务的熟悉,对实际表格内容的评分因素进行准确分类,归类至商务评分或技术评分。
5. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分''商务评分''投标报价评分',而不必严格按照表格中的名称。
6. 若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项"
特殊情况:
最外层键名为各大评分项;但是如果该招标、采购活动有多个分包,则最外层键名为对应的包名,如'一包',内部格式不变。
禁止内容:
1. 确保所有输出内容均基于提供的实际招标文件内容(除了最外层的三个评分大项名称),不使用任何预设的示例作为回答。
2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名。
以下为示例输出,仅供格式参考:
{
"一包": {
"技术评分": {
"产品技术响应8分":{
"常规参数符合":[
{
"评分":"4分",
"要求":"未标★项为常规参数每条负偏离扣1分本项满分4分。"
}
],
"摄影机":[
{
"评分":"4分",
"要求":"支持1路H.265编码、25fps、32MP分辨率的拼接摄像机视频实时预览功能满分4分"
}
]
},
"实施方案": [
{
"评分": "10分",
"要求": "实施方案清晰、完整、合理、可行的得 10 分。"
}
],
"备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分不得分。"
},
"商务评分": {
"主要监理岗位的职责": [
{
"评分": "4分",
"要求": "1、总监理工程师的职责全面、清晰、合理得 2 分;一般的 1 分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 2 分;一般的 1 分。"
}
],
"制造商实力": [
{
"评分": "3分",
"要求": "一级证书得3分二级证书得1分其他不得分"
},
{
"评分": "2分",
"要求": "行业销量排名连续前 2 名,得 2 分,第 3-6 名得 0.5 分,其他不得分"
}
]
},
"投标报价评分": {
"投标报价是否出现违反计价规范": [
{
"评分": "合格制",
"要求": "A:投标报价未违反计价规范的评审意见为“合格”B投标报价违反计价规范的评审意见为“不合格”"
}
]
}
}
}
"""
)
# 执行第二个查询
user_query = user_query_1 if zb_type == 1 else user_query_2
questions_to_continue = []
temp_final={}
evaluation_res = qianwen_long(file_id, user_query,2,1,True) # 有些重复的键名只有qianwen_long_text能保留
message = evaluation_res[0]
total_tokens = evaluation_res[1]
# print(evaluation_res)
# 清理和处理响应
cleaned_evaluation_res = extract_content_from_json(message,True) # 处理重复键名的情况
# print(json.dumps(cleaned_evaluation_res,ensure_ascii=False,indent=4))
if not cleaned_evaluation_res and total_tokens > 5900:
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, 0, file_id)
temp_final.update(continued_results)
result_data = process_data_based_on_key(temp_final) # 处理不知名外键的情况
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 的内容
def get_default_result():
return {
'技术评分': '本招标文件没有技术评分!',
'商务评分': '本招标文件没有商务评分!'
}
# 如果 judge_res 包含 '是',直接运行第二步查询
if '' in judge_res:
return run_second_qeury(file_id)
# 标准化路径,避免多种表示形式造成的误判
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:
return run_second_qeury(file_id)
# 默认返回结果
return get_default_result()
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\招标文件\招标test文件夹\招标04.pdf'
invalid_path=r'C:\Users\Administrator\Desktop\文件解析问题\文件解析问题\be901ea0-adc9-47b8-9ada-5c3bc0dd9434\ztbfile.docx'
# 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))