zbparse/flask_app/货物标/技术参数要求提取.py
2024-11-16 16:14:53 +08:00

386 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.

# -*- encoding:utf-8 -*-
import json
import os
import re
from flask_app.general.多线程提问 import multi_threading
from flask_app.general.通义千问long import qianwen_long, upload_file
from flask_app.general.json_utils import clean_json_string, combine_json_results
from flask_app.货物标.截取pdf货物标版 import truncate_pdf_main
from flask_app.general.doubao import doubao_model,generate_full_user_query
def generate_key_paths(data, parent_key=''):
"""
生成嵌套字典中的键路径,并提取最内层的键名。
同时,提取特定模式的键(如 '交换机-1', '交换机-2')的父路径。
参数:
data (dict): 输入的字典数据
parent_key (str): 上级键路径,用于递归调用
返回:
tuple: 包含键路径列表、最内层键名列表、分组路径列表以及 no_keys_added 的元组
(key_paths, good_list, grouped_paths, no_keys_added)
"""
key_paths = []
good_list = []
grouped_paths = set() # 使用集合避免重复路径
no_keys_added = True # 默认假设没有添加任何键
for key, value in data.items():
# 构建当前的键路径
current_key = f"{parent_key}.{key}" if parent_key else key
if isinstance(value, dict):
if value:
# 递归调用,并获取子路径、子 good_list、子分组路径以及子 no_keys_added
sub_key_paths, sub_good_list, sub_grouped_paths, sub_no_keys_added = generate_key_paths(value, current_key)
key_paths.extend(sub_key_paths)
good_list.extend(sub_good_list)
grouped_paths.update(sub_grouped_paths) # 合并子分组路径到当前分组路径
# 更新 no_keys_added
no_keys_added = no_keys_added and sub_no_keys_added
else:
# 空字典视为叶子节点
clean_key = key.replace(" ", "")
# 使用正则提取具有相同前缀的键
match = re.match(r'(.+)-\d+$', clean_key)
if match:
goods_name = match.group(1) # 提取货物名称前缀
# 构建分组路径
goods_path = f"{parent_key}.{goods_name}" if parent_key else goods_name
goods_path = goods_path.replace(" ", "")
grouped_paths.add(goods_path) # 添加到集合中不添加到key_paths
else:
# 非匹配项则直接添加到key_paths中
key_paths.append(current_key.replace(" ", ""))
good_list.append(clean_key) # 去掉空格后添加
# 更新 no_keys_added
no_keys_added = False
elif isinstance(value, list):
if value:
# 非空列表视为叶子节点
key_paths.append(current_key.replace(" ", ""))
good_list.append(key.replace(" ", "")) # 去掉空格后添加
# 更新 no_keys_added
no_keys_added = False
else:
# 空列表也视为叶子节点(根据需求可以调整)
key_paths.append(current_key.replace(" ", ""))
good_list.append(key.replace(" ", "")) # 去掉空格后添加
# 更新 no_keys_added
no_keys_added = False
elif value in {"未知", "", "/"}:
# 特定值视为叶子节点
key_paths.append(current_key.replace(" ", ""))
good_list.append(key.replace(" ", "")) # 去掉空格后添加
# 更新 no_keys_added
no_keys_added = False
else:
# 其他情况视为叶子节点
key_paths.append(current_key.replace(" ", ""))
good_list.append(key.replace(" ", "")) # 去掉空格后添加
# 更新 no_keys_added
no_keys_added = False
return key_paths, good_list, grouped_paths, no_keys_added
def combine_and_update_results(original_data, updates):
def normalize_key(key):
"""
规范化键名:
- 替换全角点号为半角点号。
- 删除所有空格(包括半角和全角)。
"""
# 替换全角点号(.、。)为半角点号(.
key = key.replace('', '.').replace('', '.')
# 删除所有空格(半角空格和全角空格)
key = key.replace(' ', '').replace('\u3000', '')
return key
def normalize_original_data(d):
"""
递归规范化原始数据字典的键。
"""
if not isinstance(d, dict):
return d
normalized = {}
for k, v in d.items():
nk = normalize_key(k)
normalized[nk] = normalize_original_data(v)
return normalized
def normalize_update_value(value):
"""
递归规范化更新字典中嵌套的字典的键。
"""
if isinstance(value, dict):
return {normalize_key(k): normalize_update_value(v) for k, v in value.items()}
else:
return value
def recursive_update(data, key, value):
"""
递归更新嵌套字典。
"""
keys = key.split('.')
for k in keys[:-1]:
data = data.setdefault(k, {})
if isinstance(value, dict) and isinstance(data.get(keys[-1], None), dict):
data[keys[-1]] = {**data.get(keys[-1], {}), **value}
else:
data[keys[-1]] = value
# 1. 规范化原始数据字典的键
original_data = normalize_original_data(original_data)
# 2. 规范化更新字典的键
normalized_updates = {}
for key, value in updates.items():
nk = normalize_key(key)
nv = normalize_update_value(value)
normalized_updates[nk] = nv
# 3. 执行递归更新
for key, value in normalized_updates.items():
recursive_update(original_data, key, value)
return original_data
def postprocess(data):
"""递归地转换字典中的值为列表,如果所有键对应的值都是'/', '{}''未知'"""
def convert_dict(value):
# 如果所有值是'/', '{}' 或 '未知'
if all(v in ['/', '未知', {}] for v in value.values()):
return list(value.keys())
else:
# 如果不满足条件,则递归处理嵌套的字典
return {k: convert_dict(v) if isinstance(v, dict) else v for k, v in value.items()}
# 递归处理顶层数据
return {key: convert_dict(val) if isinstance(val, dict) else val for key, val in data.items()}
# user_query1 = """
# 请你首先定位该采购文件中的采购清单或采购需求部分,请告诉我需要采购的货物,如果有采购清单,请直接根据清单上的货物(或系统)名称给出结果,注意不要返回'说明'或'规格'或'技术参数'列中的内容若没有采购清单你要从表格中或文中摘取需要采购的系统和货物采购需求中可能包含层次关系例如采购的某系统中可能包含几种货物那么你需要用嵌套键值对表示这种关系且不要遗漏该系统中包含的货物你的输出请以json格式返回最外层键名为'采购需求',嵌套键名为对应的系统名称或货物名称,需与原文保持一致,无需给出采购数量和单位。以下为需要考虑的特殊情况:如果采购清单中同一层级(或同一系统)下存在同名货物且它们的采购要求有所不同,请你以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,例如若采购清单中有两种型号的'交换机',那么你应返回两个键名,'交换机-1'和'交换机-2';如有未知内容,在对应键值处填'未知'。以下为考虑了特殊情况的示例输出:
# {
# "采购需求": {
# "交换机-1"{},
# "交换机-2":{},
# "门禁管理系统": {},
# "交通监控视频子系统": {
# "高清视频抓拍像机":{},
# "补光灯":{}
# },
# "LED全彩显示屏": {}
# }
# }
# """
def get_technical_requirements(file_path,invalid_path):
file_id=upload_file(file_path)
first_query_template="该文件是否说明了采购需求,即需要采购哪些货物?如果有,请回答'',否则,回答''"
# first_query=generate_full_user_query(file_path,first_query_template)
# judge_res=doubao_model(first_query)
judge_res=qianwen_long(file_id,first_query_template)
prompt_template1 = '''
任务解析采购文件提取采购需求并以JSON格式返回。
要求与指南:
1. 精准定位:运用文档理解能力,找到文件中的采购需求部分。
2. 系统归属:若货物明确属于某个系统,则将其作为该系统的二级键。
3. 非清单形式处理:若未出现采购清单,则从表格或文字中摘取系统和货物信息。
4. 软件需求:对于软件应用需求,列出系统模块构成,并作为系统键值的一部分。
5. 系统功能:若文中提及系统功能,则在系统值中添加'系统功能'二级键,不展开具体内容。
6. 完整性:确保不遗漏系统内的货物,也不添加未提及的内容。
输出格式:
1.JSON格式最外层键名为'采购需求'
2.嵌套键名为系统或货物名称,与原文保持一致。
3.键值应为空对象({{}}),仅返回名称。
4.不包含'说明''规格''技术参数'等列内容。
5.层次关系用嵌套键值对表示。
6.最后一级键内值留空或填'未知'(如数量较多或未知内容)。
特殊情况处理:
1.同一层级下同名但采购要求不同的货物,以'货物名-编号'区分编号从1递增。
2.对于工程施工、建设相关的采购需求,你无需提取,提取的范围仅限软件、硬件,。
示例输出结构:
{{
"采购需求": {{
"交换机-1": {{}},
"交换机-2": {{}},
"门禁管理系统": {{
// 可包含其他货物或模块
}},
"交通监控视频子系统": {{
"系统功能": {{}},
"高清视频抓拍像机": {{}},
"补光灯": {{}}
}},
"LED全彩显示屏": {{}}
// 其他系统和货物
}}
}}
注意事项:
1.严格按照上述要求执行,确保输出准确性和规范性。
2.如有任何疑问或不确定内容,请保留原文描述,必要时使用'未知'标注。
'''
prompt_template2 = '''
任务解析采购文件提取采购需求并以JSON格式返回。
要求与指南:
1. 精准定位:运用文档理解能力,找到文件中的采购需求部分。
2. 系统归属:若货物明确属于某个系统,则将其作为该系统的二级键。
3. 非清单形式处理:若未出现采购清单,则从表格或文字中摘取系统和货物信息。
4. 软件需求:对于软件应用需求,列出系统模块构成,并作为系统键值的一部分。
5. 系统功能:若文中提及系统功能,则在系统值中添加'系统功能'二级键,不展开具体内容。
6. 完整性:确保不遗漏系统内的货物,也不添加未提及的内容。
输出格式:
1.JSON格式最外层键名为'采购需求'
2.嵌套键名为系统或货物名称,与原文保持一致。
3.键值应为空对象({{}}),仅返回名称。
4.不包含'说明''规格''技术参数'等列内容。
5.层次关系用嵌套键值对表示。
6.最后一级键内值留空或填'未知'(如数量较多或未知内容)。
特殊情况处理:
同一层级下同名但采购要求不同的货物,以'货物名-编号'区分编号从1递增。
示例输出结构:
{{
"采购需求": {{
"交换机-1": {{}},
"交换机-2": {{}},
"门禁管理系统": {{
// 可包含其他货物或模块
}},
"交通监控视频子系统": {{
"系统功能": {{}},
"高清视频抓拍像机": {{}},
"补光灯": {{}}
}},
"LED全彩显示屏": {{}}
// 其他系统和货物
}}
}}
文件内容(已包含):{full_text}
注意事项:
1.严格按照上述要求执行,确保输出准确性和规范性。
2.如有任何疑问或不确定内容,请保留原文描述,必要时使用'未知'标注。
'''
if '' in judge_res:
file_id=upload_file(invalid_path)
print("调用invalid_path")
model_res=qianwen_long(file_id,prompt_template1)
print(model_res)
else:
user_query=generate_full_user_query(file_path,prompt_template2)
model_res=doubao_model(user_query)
# model_res = qianwen_long(file_id,prompt_template1)
print(model_res)
# cleaned_res = clean_json_string(model_res) #转字典
# keys_list,good_list,grouped_paths,no_keys_added= generate_key_paths(cleaned_res['采购需求']) # 提取需要采购的货物清单 key_list交通监控视频子系统.高清视频抓拍像机 ...
# if no_keys_added:
# final_res = postprocess(cleaned_res)
# else:
# # user_query_template = "请你根据该货物标中采购要求部分的内容,请你给出\"{}\"的技术参数或采购要求请以json格式返回结果外层键名为\"{}\", 键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。"
# user_query_template = """
# 请你根据该货物标中采购要求部分的内容,请你给出\"{}\"的技术参数或采购要求请以json格式返回结果键名为\"{}\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,需与原文完全一致,即若技术参数前存在序号也要保留,但你不可擅自增添或删减。以下为需要考虑的特殊情况:如果该货物没有相关采购要求或技术参数要求,键值为空列表。示例输出格式如下:
# {{
# "摄像机控制键盘": [
# "1、支持串行 RS232/RS422 和 IP 混合控制,允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;",
# "2、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机。"
# ]
# }}
# """
# user_query_template_two="""
# 请你根据该货物标中采购要求部分的内容,请你给出\"{}\"的技术参数或采购要求由于该货物存在多种不同的采购要求或技术参数请你请逐一列出请以json格式返回结果请你以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,需与原文完全一致,即若技术参数前存在序号也要保留,但你不可擅自增添或删减。示例输出格式如下:
# {{
# "交换机-1": [
# "1、支持固化千兆电口≥8 个固化千兆光口≥2 个,桌面型设备;",
# "2、支持静态链路聚合"
# ]
# "交换机-2":[
# "1、交换容量≥52Gbps包转发率≥38.69Mpps",
# "2、提供国家强制性产品认证证书及测试报告3C"
# ]
# }}
# """
# queries = []
# for key in keys_list:
# # 将键中的 '.' 替换为 '下的'
# modified_key = key.replace('.', '下的')
# # 使用修改后的键填充第一个占位符,原始键填充第二个占位符
# new_query = user_query_template.format(modified_key, key, modified_key)
# queries.append(new_query)
#
# # 处理 grouped_paths 中的项,应用 user_query_template_two
# for grouped_key in grouped_paths:
# # 将键中的 '.' 替换为 '下的'
# modified_grouped_key = grouped_key.replace('.', '下的')
# # 使用修改后的键填充第一个占位符,原始键填充第二个占位符
# new_query = user_query_template_two.format(modified_grouped_key, grouped_key, modified_grouped_key)
# queries.append(new_query)
# results = multi_threading(queries, "", file_id, 2)
# technical_requirements = []
# if not results:
# print("errror!未获得大模型的回答!")
# else:
# # 打印结果
# for question, response in results:
# technical_requirements.append(response)
# technical_requirements_combined_res = combine_json_results(technical_requirements)
#
# """根据所有键是否已添加处理技术要求"""
# # 更新原始采购需求字典
# final_res=combine_and_update_results(cleaned_res['采购需求'], technical_requirements_combined_res)
# # final_res = postprocess(cleaned_res)
# final_res["货物列表"] = good_list
#
# # 输出最终的 JSON 字符串
# return {"采购需求": final_res}
def test_all_files_in_folder(input_folder, output_folder):
# 确保输出文件夹存在
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 遍历指定文件夹中的所有文件
for filename in os.listdir(input_folder):
file_path = os.path.join(input_folder, filename)
# 检查是否是文件
if os.path.isfile(file_path):
print(f"处理文件: {file_path}")
# 调用函数处理文件
try:
json_result = get_technical_requirements(file_path)
# 定义输出文件的路径
output_file_path = os.path.join(output_folder, os.path.splitext(filename)[0] + '.json')
# 保存JSON结果到文件
with open(output_file_path, 'w', encoding='utf-8') as json_file:
json.dump(json_result, json_file, ensure_ascii=False, indent=4)
print(f"结果已保存到: {output_file_path}")
except Exception as e:
print(f"处理文件 {file_path} 时出错: {e}")
if __name__ == "__main__":
# truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\货物标\output1\招标文件(107国道)_procurement.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"
# file_id = upload_file(truncate_file)
invalid_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\a110ed59-00e8-47ec-873a-bd4579a6e628\\ztbfile.pdf"
# file_id=upload_file(truncate_file)
res=get_technical_requirements(truncate_file,invalid_path)
json_string = json.dumps(res, ensure_ascii=False, indent=4)
print(json_string)
# # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1"
# # output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output3"
# # test_all_files_in_folder(input_folder, output_folder)