11.23 修改商务要求bug

This commit is contained in:
zy123 2024-11-23 15:38:52 +08:00
parent 52a2cb6092
commit 823a5f0a46
6 changed files with 267 additions and 241 deletions

View File

@ -229,7 +229,6 @@ def engineering_bid_main(output_folder, downloaded_file_path, file_type, unique_
yield json.dumps({'error': f'Error processing {key}: {str(exc)}'}, ensure_ascii=False)
#TODO:基本信息,判断是否这里,打勾逻辑取消了。
#TODO:缩进
if __name__ == "__main__":
start_time = time.time()
output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\new_test1"

View File

@ -2,10 +2,12 @@
import json
import re
from PyPDF2 import PdfReader
from flask_app.general.doubao import read_txt_to_string
from flask_app.general.json_utils import combine_json_results,clean_json_string
from flask_app.general.通义千问long import upload_file,qianwen_long_stream
from flask_app.货物标.截取pdf货物标版 import extract_common_header, clean_page_content
from flask_app.general.doubao import doubao_model
#正则表达式判断原文中是否有商务、服务、其他要求
def find_exists(truncate_file, required_keys):
@ -102,7 +104,7 @@ def generate_queries(truncate_file, required_keys):
# print(query_base)
return queries
def generate_user_query_template(required_keys):
def generate_user_query_template(required_keys,processed_filepath):
import textwrap
import json
@ -111,11 +113,11 @@ def generate_user_query_template(required_keys):
# 定义每个键对应的示例内容
example_content1 = {
"技术要求": ["相关技术要求以及服务要求1", "相关技术要求以及服务要求2"],
"技术要求": ["相关技术要求1", "相关技术要求2"],
"服务要求": ["服务要求1", "服务要求2"],
"商务要求": {
"★产品质保期": ["所投 LED 整屏不低于 3 年,健身器材整套不低于 2 年"],
"售后服务方案": ["包含产品配送、安装保障方案"]
"子因素名1": ["商务要求1"],
"子因素名2": ["商务要求2"]
},
"其他要求": {
"子因素名1": ["关于项目采购的其他要求1...", "关于项目采购的其他要求2..."],
@ -133,7 +135,7 @@ def generate_user_query_template(required_keys):
"子因素名1": ["相关服务要求1", "相关服务要求2"],
"子因素名2": ["相关服务要求3", "相关服务要求4"]
},
"商务要求": ["所投 LED 整屏不低于 3 年,健身器材整套不低于 2 年", "包含产品配送、安装保障方案"],
"商务要求": ["商务要求1", "商务要求2"],
"其他要求": ["关于项目采购的其他要求1..."],
"技术、服务要求": {
"子因素名1": ["相关技术、服务要求内容1"],
@ -167,7 +169,7 @@ def generate_user_query_template(required_keys):
outer_keys_str = ', '.join([f"'{key}'" for key in sorted_keys])
# 使用三引号定义多行字符串,便于编辑和维护
prompt_instruction = textwrap.dedent(f"""请你根据该货物类招标文件中的采购要求部分内容,请告诉我该项目采购的{keys_str}分别是什么请以json格式返回结果默认情况下外层键名是{outer_keys_str},键值为字符串列表,每个字符串表示具体的一条要求,内容需要与原文保持一致,不可擅自总结删减
prompt_instruction = textwrap.dedent(f"""请你根据该货物类招标文件中的采购要求部分内容(技术、服务及商务要求部分内容),请告诉我该项目采购的{keys_str}分别是什么请以json格式返回结果默认情况下外层键名是{outer_keys_str},键值为字符串列表,每个字符串表示具体的一条要求,请按原文内容回答,保留三角▲、五角星★和序号(若有),不要擅自增添内容
要求与指南
1. 默认情况无需嵌套键值为字符串列表若存在嵌套结构嵌套键名是原文中该要求下相应子标题最多一层嵌套
@ -177,10 +179,11 @@ def generate_user_query_template(required_keys):
a. 一个对象字典其键为子因素名值为字符串列表
b. 一个字符串列表表示具体的一条条要求若只有一条要求也用字符串列表表示
- 最多只允许一层嵌套
3. 请优先定位正文部分的大标题'xx要求'在其之后提取'xx要求'相关内容由于要求的位置比较集中请尽量避免在全文各处寻找
4. 在提取技术要求或技术服务要求时若有你无需从采购清单或表格中提取货物名以及参数要求你仅需定位到原文中相应位置正文部分而非表格中并提取正文内容通常一类要求写在一块大标题下否则键值为空列表
5. 若无相关要求键值为[]
""")
3. 请优先定位正文部分的大标题'xx要求'在其之后提取'xx要求'相关内容
4. 若章节开头位置或者采购清单中除了需要采购的货物数量单位之外还有带三角或五角星的描述内容如工期要求质保要求等商务要求请将该部分内容提取出来添加在键名为'商务要求'的字典的键值部分,注意请不要返回Markdown语法必要时使用冒号':'将相关信息拼接在一起
5. 在提取技术要求或技术服务要求时若有你无需从采购清单或表格中提取货物名以及参数要求你仅需定位到原文中大标题'技术要求''技术、服务要求'部分提取正文内容若内容全在表格中键值为空列表[]
6. 若无相关要求键值为[]
""" )
# 过滤 example_content1 和 example_content2 以仅包含 sorted_keys
def filter_content(example_content, keys):
@ -192,7 +195,8 @@ def generate_user_query_template(required_keys):
# 将过滤后的示例转换为格式化的 JSON 字符串
json_example1_str = json.dumps(filtered_example_content1, indent=4, ensure_ascii=False)
json_example2_str = json.dumps(filtered_example_content2, indent=4, ensure_ascii=False)
# 从文件中读取内容
# full_text = read_txt_to_string(processed_filepath)
# 完整的用户查询模板,包含两份示例输出
user_query_template = f"""
{prompt_instruction}
@ -201,27 +205,31 @@ def generate_user_query_template(required_keys):
{json_example1_str}
示例 2
{json_example2_str}
"""
"""
# 文本内容:{full_text}
return user_query_template
def get_business_requirements(procurement_path):
def get_business_requirements(procurement_path,processed_filepath):
file_id=upload_file(procurement_path)
required_keys = ["\s*术\s*要\s*求","\s*务\s*要\s*求", "\s*务\s*要\s*求", "\s*他\s*要\s*求"]
contained_keys=find_exists(procurement_path,required_keys)
print(contained_keys)
# queries = generate_queries(truncate_file, contained_keys)
user_query=generate_user_query_template(contained_keys)
business_requirements=qianwen_long_stream(file_id,user_query)
user_query=generate_user_query_template(contained_keys,processed_filepath)
# print(user_query)
model_res=qianwen_long_stream(file_id,user_query)
# model_res=doubao_model(user_query)
# Combine and fill missing keys with default values
final_res = clean_json_string(business_requirements)
final_res = clean_json_string(model_res)
# final_res.update({key: final_res.get(key, "") for key in required_keys})
return final_res
#TODO:改为先判断,再摘取
if __name__ == "__main__":
# truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\e4be098d-b378-4126-9c32-a742b237b3b1\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\货物标\output1\2-招标文件广水市教育局封闭管理_procurement.pdf"
truncate_file=r"C:\Users\Administrator\Desktop\fsdownload\5901b181-b55f-4107-9f30-c85d607b1fa0\ztbfile_procurement.pdf"
processed_filepath=""
# file_id = upload_file(truncate_file)
res=get_business_requirements(truncate_file)
res=get_business_requirements(truncate_file,"")
print(json.dumps(res, ensure_ascii=False, indent=4))

View File

@ -123,7 +123,7 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter
else:
# 原有的处理逻辑保持不变
if output_suffix == "qualification1":
if output_suffix == "qualification1" or output_suffix=="procurement":
exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据')
start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, common_header, exclusion_pattern, output_suffix)
# 针对 selection = 6 的特殊处理
@ -832,16 +832,16 @@ def truncate_pdf_specific_goods(pdf_path, output_folder, selections,unique_id="1
#ztbfile.pdf少资格评审 包头少符合性评审
if __name__ == "__main__":
# input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles"
input_path = r"C:\Users\Administrator\Desktop\货物标\zbfiles\2-招标文件.pdf"
input_path = r"C:\Users\Administrator\Desktop\new招标文件\货物标"
# input_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\zbtest4_evaluation_method.pdf"
# input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件_procurement.pdf"
# input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\ztbfile.pdf"
# output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\tmp"
output_folder=r"C:\Users\Administrator\Desktop\new招标文件\output5"
# files = truncate_pdf_multiple(input_path, output_folder)
selections = [3,5]
files=truncate_pdf_specific_goods(input_path,output_folder,selections)
print(files)
# selection = 5# 例如1 - 公告, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2与评标办法一致 4.投标人须知前附表part1 投标人须知正文part2 5-采购需求
# generated_files = truncate_pdf_main(input_path, output_folder, selection)
# print(generated_files)
# selections = [3,5]
# files=truncate_pdf_specific_goods(input_path,output_folder,selections)
# print(files)
selection = 5# 例如1 - 公告, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2与评标办法一致 4.投标人须知前附表part1 投标人须知正文part2 5-采购需求
generated_files = truncate_pdf_main(input_path, output_folder, selection)
print(generated_files)

View File

@ -3,6 +3,8 @@ import json
import os
import re
import time
from collections import defaultdict
from copy import deepcopy
from flask_app.general.file2markdown import convert_pdf_to_markdown
from flask_app.general.format_change import pdf2docx
@ -14,137 +16,180 @@ from flask_app.general.doubao import doubao_model, generate_full_user_query, pdf
from flask_app.货物标.技术参数要求提取后处理函数 import postprocess, all_postprocess
def generate_key_paths(data, parent_key='', good_list=None, seen=None):
def generate_key_paths(data):
"""
生成嵌套字典中的键路径并提取最内层的键名
同时提取特定模式的键 '交换机-1', '交换机-2'的父路径
如果同一层级下只有'交换机-1'但没有'交换机-2'则视为错误输入将键名中的后缀'-1'删除直接修改为'交换机'保持顺序
处理输入的字典生成 key_paths, grouped_paths good_list并根据条件修改原始字典
参数:
data (dict): 输入的字典数据
parent_key (str): 上级键路径用于递归调用
good_list (list): 用于存储去重后的最内层键名
seen (set): 用于跟踪已添加到 good_list 的元素
data (dict): 输入的嵌套字典
返回:
tuple: 包含键路径列表最内层键名列表分组路径列表以及 no_keys_added 的元组
(key_paths, good_list, grouped_paths, no_keys_added)
tuple: 包含 key_paths, grouped_paths good_list 的元组
"""
if good_list is None:
good_list = []
if seen is None:
seen = set()
# 编译用于匹配后缀的正则表达式模式
pattern = re.compile(r'(.+)-\d+$')
# 初始化结果列表
key_paths = []
grouped_paths = set() # 使用集合避免重复路径
no_keys_added = True # 默认假设没有添加任何键
grouped_set = set() # 使用集合避免重复
good_list = []
# Step 1: Collect keys that match the pattern
pattern = re.compile(r'(.+)-\d+$') # 匹配形如 '交换机-1', '交换机-2' 的键
prefix_groups = {}
other_keys = []
def recurse(current_dict, path):
"""
递归遍历字典处理 key_paths grouped_paths并收集 good_list
for key in list(data.keys()): # 使用 list(data.keys()) 防止修改字典时出错
clean_key = key.replace(" ", "")
match = pattern.match(clean_key)
if match:
prefix = match.group(1)
if prefix not in prefix_groups:
prefix_groups[prefix] = []
prefix_groups[prefix].append(key)
else:
other_keys.append(key)
# Step 2: Handle grouped keys
for prefix, keys in prefix_groups.items():
current_prefix_path = f"{parent_key}.{prefix}" if parent_key else prefix
if len(keys) > 1:
# 多个键匹配同一前缀:添加到 grouped_paths
grouped_paths.add(current_prefix_path)
if prefix not in seen:
good_list.append(prefix)
seen.add(prefix)
no_keys_added = False
else:
# 只有一个键匹配:重命名键,删除后缀,并保持顺序
old_key = keys[0]
new_key = prefix # 删除后缀后的新键名
value = data.pop(old_key) # 移除旧键并获取其值
# 插入新键以保持顺序
# 由于 Python 3.7+ 字典保持插入顺序,我们需要重新插入键
# 创建一个新的临时字典来保持顺序
temp_dict = {}
for k in data:
temp_dict[k] = data[k]
if k == parent_key or (parent_key and k.startswith(parent_key + ".")):
# 在适当的位置插入新键
continue
data[new_key] = value # 添加新键
key_path = f"{parent_key}.{new_key}" if parent_key else new_key
key_paths.append(key_path)
if prefix not in seen:
good_list.append(prefix)
seen.add(prefix)
no_keys_added = False
# 注意:由于直接修改字典,键的顺序可能会受到影响
# Step 3: Handle other keys
for key in other_keys:
value = data[key]
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_grouped_paths, sub_no_keys_added = generate_key_paths(
value, current_key, good_list, seen
)
key_paths.extend(sub_key_paths)
grouped_paths.update(sub_grouped_paths) # 合并子分组路径到当前分组路径
# 更新 no_keys_added
no_keys_added = no_keys_added and sub_no_keys_added
参数:
current_dict (dict): 当前遍历的字典
path (list): 当前路径的键列表
"""
# 第一遍遍历,统计每个基名的出现次数
base_name_count = {}
base_names = {}
for key in current_dict.keys():
match = pattern.match(key)
if match:
base = match.group(1)
else:
# 空字典视为叶子节点
clean_key = key.replace(" ", "")
key_paths.append(current_key.replace(" ", ""))
if clean_key not in seen:
good_list.append(clean_key) # 去掉空格后添加
seen.add(clean_key)
base = key
base_names[key] = base
base_name_count[base] = base_name_count.get(base, 0) + 1
# 更新 no_keys_added
no_keys_added = False
elif isinstance(value, list):
# 列表类型视为叶子节点,无论是否为空
key_paths.append(current_key.replace(" ", ""))
clean_key = key.replace(" ", "")
if clean_key not in seen:
good_list.append(clean_key) # 去掉空格后添加
seen.add(clean_key)
# 更新 no_keys_added
no_keys_added = False
elif value in {"未知", "", "/"}:
# 特定值视为叶子节点
key_paths.append(current_key.replace(" ", ""))
clean_key = key.replace(" ", "")
if clean_key not in seen:
good_list.append(clean_key) # 去掉空格后添加
seen.add(clean_key)
# 更新 no_keys_added
no_keys_added = False
else:
# 其他情况视为叶子节点
key_paths.append(current_key.replace(" ", ""))
clean_key = key.replace(" ", "")
if clean_key not in seen:
good_list.append(clean_key) # 去掉空格后添加
seen.add(clean_key)
# 更新 no_keys_added
no_keys_added = False
# Step 4: 删除 key_paths 中包含在 grouped_paths 中的元素
key_paths = [path for path in key_paths if path not in grouped_paths] #排除同一层下既有'xx',又有'xx-1'的情况
return key_paths, good_list, grouped_paths, no_keys_added
# 第二遍遍历,根据基名的出现次数分类
keys_to_rename = {}
for key, base in base_names.items():
if base_name_count[base] == 1:
# 检查是否是最内层(值为列表)
value = current_dict[key]
if isinstance(value, list):
current_path = '.'.join(path + [base])
key_paths.append(current_path)
# 收集 good_list保持顺序且不重复
if base not in good_list:
good_list.append(base)
# 如果原键名有后缀,需要记录以便后续重命名
if key != base:
keys_to_rename[key] = base
elif isinstance(value, dict):
# 继续递归处理
recurse(value, path + [base])
else:
# 记录分组路径,确保唯一性
grouped_set.add('.'.join(path + [base]))
# 执行键名的重命名,同时保持原有顺序
if keys_to_rename:
new_ordered_dict = {}
for key in current_dict.keys():
if key in keys_to_rename:
new_key = keys_to_rename[key]
new_ordered_dict[new_key] = current_dict[key]
else:
new_ordered_dict[key] = current_dict[key]
current_dict.clear()
current_dict.update(new_ordered_dict)
# 对于基名重复的键,继续递归(如果值是字典)
for key, base in base_names.items():
if base_name_count[base] > 1:
value = current_dict[key]
if isinstance(value, dict):
recurse(value, path + [base])
elif isinstance(value, list):
# 如果值是列表,仍需收集基名到 good_list
if base not in good_list:
good_list.append(base)
# 深拷贝数据以避免修改原始输入
data_copy = deepcopy(data)
# 开始递归遍历
recurse(data_copy, [])
# 转换 grouped_set 为列表,并排序以保持一致性
# 为了保持 grouped_paths 的顺序与它们首次出现的顺序一致,
# 我们需要在递归过程中记录它们的顺序,而不是简单地排序。
# 因此,修改 grouped_set 为列表并在添加时检查重复。
grouped_paths = []
for path in data.keys():
pass # This is a placeholder if needed for order
# 实际上,由于 grouped_set 已经去重且不保顺序,
# 我们需要重新遍历数据_copy 来收集 grouped_paths 按顺序
def collect_grouped_paths(current_dict, path, collected):
for key in current_dict.keys():
match = pattern.match(key)
if match:
base = match.group(1)
else:
base = key
current_path = '.'.join(path + [base])
if current_path in grouped_set and current_path not in collected:
collected.append(current_path)
value = current_dict[key]
if isinstance(value, dict):
collect_grouped_paths(value, path + [base], collected)
collected_grouped_paths = []
collect_grouped_paths(data_copy, [], collected_grouped_paths)
grouped_paths = collected_grouped_paths
return key_paths, grouped_paths, good_list, data_copy
def rename_keys(data):
"""
对整个数据结构进行重命名处理
"""
def rename_keys_recursive(current_dict):
"""
递归地重命名字典中的键确保同一层级下具有相同基名的键被正确编号
"""
if not isinstance(current_dict, dict):
return current_dict
key_order = list(current_dict.keys())
base_name_dict = defaultdict(list)
# 辅助函数:提取基名(去除可能的 -数字 后缀)
def get_base_name(key):
if '-' in key:
parts = key.rsplit('-', 1)
if parts[1].isdigit():
return parts[0]
return key
# 将键按基名分组
for key in key_order:
base = get_base_name(key)
base_name_dict[base].append(key)
new_dict = {}
for key in key_order:
base = get_base_name(key)
keys = base_name_dict[base]
if len(keys) > 1:
# 如果存在同基名的多个键,则进行重命名
if base not in new_dict:
# 按原始顺序对需要重命名的键进行排序
sorted_keys = sorted(keys, key=lambda x: key_order.index(x))
for idx, original_key in enumerate(sorted_keys, start=1):
new_key = f"{base}-{idx}"
# 如果值是字典,递归处理
if isinstance(current_dict[original_key], dict):
new_dict[new_key] = rename_keys_recursive(current_dict[original_key])
else:
new_dict[new_key] = current_dict[original_key]
else:
# 如果没有重复的基名,保持原名
if isinstance(current_dict[key], dict):
new_dict[key] = rename_keys_recursive(current_dict[key])
else:
new_dict[key] = current_dict[key]
return new_dict
for top_key, top_value in data.items():
if isinstance(top_value, dict):
data[top_key] = rename_keys_recursive(top_value)
return data
def combine_and_update_results(original_data, updates):
@ -226,7 +271,7 @@ def combine_and_update_results(original_data, updates):
# """
#文件内容以markdown格式组织其中表格部分若有以html语法组织
def get_technical_requirements(file_path,invalid_path):
def get_technical_requirements(file_path,invalid_path,processed_filepath):
docx_file_path=pdf2docx(file_path)
file_id=upload_file(docx_file_path)
first_query_template="该文件是否说明了采购需求,即需要采购哪些货物?如果有,请回答'',否则,回答''" #防止截取失败
@ -297,7 +342,7 @@ def get_technical_requirements(file_path,invalid_path):
2. 采购目标采购目标通常有硬件如设备货物和软件如系统软件应用APP一次采购活动可能同时包含这两种类型对于工程类的施工建设采购需求无需提取
3. 非清单形式处理若未出现采购清单则从表格或文字中摘取采购信息
4. 系统归属一些采购活动可能将采购目标划分为若干系统和货物每个系统可能包含若干货物则将这些货物名称作为该系统的二级键系统可以只包含总体'系统功能'而无货物
5. 软件需求对于软件应用或系统软件需求仅需列出系统模块构成若有并作为系统键值的一部分无需在模块下再细分功能
5. 软件需求对于软件应用或系统软件需求仅需列出系统模块构成若有并作为系统键值的一部分无需在模块下再细分功能
6. 系统功能若采购的某系统提及总体系统功能则在系统值中添加'系统功能'二级键不展开具体内容
7. 完整性确保不遗漏系统内的货物也不添加未提及的内容
@ -359,19 +404,16 @@ def get_technical_requirements(file_path,invalid_path):
else:
# processed_filepath = convert_pdf_to_markdown(file_path) # 转markdown格式
# processed_filepath=r"C:\Users\Administrator\Desktop\货物标\extract_files\107国道.txt"
processed_filepath = pdf2txt(file_path) # 纯文本提取
user_query=generate_full_user_query(processed_filepath,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) #转字典
normal_paths,good_list,grouped_paths,no_keys_added= generate_key_paths(cleaned_res['采购需求']) # 提取需要采购的货物清单 key_list交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' 输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'}
if no_keys_added:
ffinal_res = postprocess(cleaned_res)
else:
# user_query_template = "请你根据该货物标中采购要求部分的内容,请你给出\"{}\"的技术参数或采购要求请以json格式返回结果外层键名为\"{}\", 键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。"
user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增添内容,尤其是不可擅自添加序号。
key_paths, grouped_paths, good_list, data_copy= generate_key_paths(cleaned_res['采购需求']) # 提取需要采购的货物清单 key_list交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' 输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'}
modified_data=rename_keys(data_copy)
# user_query_template = "请你根据该货物标中采购要求部分的内容,请你给出\"{}\"的技术参数或采购要求请以json格式返回结果外层键名为\"{}\", 键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。"
user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增添内容,尤其是不可擅自添加序号。
要求与指南
1. 如果该货物没有相关采购要求或技术参数要求键值应为空列表
2. 如果存在嵌套结构且原文为Markdown 的表格语法'摄像机|有效像素|≥900W像素' 请不要返回该Markdown语法而是使用冒号':'将相关信息拼接在一起生成一条完整且清晰的技术参数或采购要求描述作为列表中的一个字符串"摄像机有效像素≥900W像素"
@ -395,7 +437,7 @@ def get_technical_requirements(file_path,invalid_path):
]
}}
"""
user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在多种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增添内容。
user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在多种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增添内容。
请注意以下特殊情况
要求与指南
1. 如果该货物没有相关采购要求或技术参数要求键值应为空列表
@ -426,38 +468,38 @@ def get_technical_requirements(file_path,invalid_path):
]
}}
"""
queries = []
for key in normal_paths:
# 将键中的 '.' 替换为 '下的'
modified_key = key.replace('.', '下的')
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
new_query = user_query_template.format(modified_key, key, modified_key)
queries.append(new_query)
queries = []
for key in key_paths:
# 将键中的 '.' 替换为 '下的'
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)
# print(response)
technical_requirements_combined_res = combine_json_results(technical_requirements)
# 处理 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)
# print(response)
technical_requirements_combined_res = combine_json_results(technical_requirements)
"""根据所有键是否已添加处理技术要求"""
# 更新原始采购需求字典
final_res=combine_and_update_results(cleaned_res['采购需求'], technical_requirements_combined_res)
ffinal_res=all_postprocess(final_res)
# final_res = postprocess(cleaned_res)
ffinal_res["货物列表"] = good_list
"""根据所有键是否已添加处理技术要求"""
# 更新原始采购需求字典
final_res=combine_and_update_results(modified_data, technical_requirements_combined_res)
ffinal_res=all_postprocess(final_res)
# final_res = postprocess(cleaned_res)
ffinal_res["货物列表"] = good_list
# 输出最终的 JSON 字符串
return {"采购需求":ffinal_res}
@ -496,53 +538,18 @@ def test_all_files_in_folder(input_folder, output_folder):
# "业务视频应用": {
# "视频回放": {}
# },
#TODO:{ 顺序变了
# "采购需求": {
# "高清数字枪机-1": [],
# "枪机支架-1": [],
# "高清数字半球机-1": [],
# "网络硬盘录像机-1": [],
# "监硬控硬盘-1": [],
# "交换机-1": [],
# "交换机-2": [],
# "监视器-1": [],
# "电源线-1": [],
# "网线-1": [],
# "水晶头-1": [],
# "PVC线槽-1": [],
# "辅料-1": [],
# "安装调试-1": []
# }
# }
# {
# "采购需求": {
# "交换机-1": [],
# "交换机-2": [],
# "高清数字枪机": [],
# "枪机支架": [],
# "高清数字半球机": [],
# "网络硬盘录像机": [],
# "监硬控硬盘": [],
# "监视器": [],
# "电源线": [],
# "网线": [],
# "水晶头": [],
# "PVC线槽": [],
# "辅料": [],
# "安装调试": []
# }
# }
if __name__ == "__main__":
start_time=time.time()
# truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\fsdownload\db79e9e0-830e-442c-8cb6-1d036215f8ff\ztbfile_procurement.pdf"
truncate_file=r"C:\Users\Administrator\Desktop\货物标\output1\陕西省公安厅交通警察总队高速公路交通安全智能感知巡查系统项目 (1)_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)
processed_filepath = pdf2txt(truncate_file)
res=get_technical_requirements(truncate_file,invalid_path,processed_filepath)
json_string = json.dumps(res, ensure_ascii=False, indent=4)
print(json_string)
# # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1"

View File

@ -5,6 +5,9 @@ from collections import OrderedDict
from collections import defaultdict
#传输技术参数需求的时候后处理
def extract_matching_keys(data, good_list, special_keys=None, parent_key=''):
import re
from collections import defaultdict
def get_suffix(n):
"""
根据数字n返回对应的字母后缀
@ -23,9 +26,10 @@ def extract_matching_keys(data, good_list, special_keys=None, parent_key=''):
if isinstance(data, dict):
for key, value in data.items():
clean_key = key.replace(" ", "") # 去除键中的空格
if isinstance(value, list):
if key not in special_keys and any(pattern.match(key) for pattern in patterns):
counter[key] += 1
if clean_key not in special_keys and any(pattern.match(clean_key) for pattern in patterns):
counter[clean_key] += 1
elif isinstance(value, dict):
count_matching_keys(value, patterns, special_keys, counter)
elif isinstance(data, list):
@ -44,21 +48,22 @@ def extract_matching_keys(data, good_list, special_keys=None, parent_key=''):
if isinstance(data, dict):
for key, value in data.items():
clean_key = key.replace(" ", "") # 去除键中的空格
if isinstance(value, list):
# 处理值为列表的键
if any(pattern.match(key) for pattern in patterns):
new_key = generate_key(key, parent_key, key_counter, suffix_map, special_keys)
if any(pattern.match(clean_key) for pattern in patterns):
new_key = generate_key(clean_key, parent_key, key_counter, suffix_map, special_keys)
filtered_data[new_key] = value
elif isinstance(value, dict):
# 继续递归处理嵌套字典
new_parent_key = key if parent_key == '' else f"{parent_key}{key}"
new_parent_key = clean_key if parent_key == '' else f"{parent_key}{clean_key}"
process_data(value, patterns, special_keys, key_counter, suffix_map,
filtered_data, new_parent_key)
filtered_data, new_parent_key)
elif isinstance(data, list):
for item in data:
if isinstance(item, (dict, list)):
process_data(item, patterns, special_keys, key_counter, suffix_map,
filtered_data, parent_key)
filtered_data, parent_key)
def generate_key(key, parent_key, key_counter, suffix_map, special_keys):
"""生成新的键名"""
@ -71,9 +76,13 @@ def extract_matching_keys(data, good_list, special_keys=None, parent_key=''):
return key
if special_keys is None:
special_keys = []
special_keys = ["系统功能"] # 默认值为 ["系统功能"]
patterns = [re.compile(r'^' + re.escape(g) + r'(?:-\d+)?$') for g in good_list]
# 去除 good_list 中的空格
clean_good_list = [g.replace(" ", "") for g in good_list]
# 构建匹配的正则表达式
patterns = [re.compile(r'^' + re.escape(g) + r'(?:-\d+)?$') for g in clean_good_list]
# 先统计所有匹配键的出现次数,仅统计值为列表的键
key_counter = count_matching_keys(data, patterns, special_keys)
@ -89,6 +98,7 @@ def extract_matching_keys(data, good_list, special_keys=None, parent_key=''):
return filtered_data
def postprocess(data):
"""递归地转换字典中的值为列表,如果所有键对应的值都是'/', '{}''未知'"""
def convert_dict(value):

View File

@ -1,13 +1,14 @@
import concurrent.futures
import json
import time
from flask_app.general.doubao import pdf2txt
from flask_app.货物标.技术参数要求提取 import get_technical_requirements
from flask_app.general.通义千问long import upload_file
from flask_app.货物标.商务服务其他要求提取 import get_business_requirements
#获取采购清单
def fetch_procurement_reqs(procurement_path, invalid_path):
# procurement_docpath = pdf2docx(procurement_path) # 采购需求docx
# 定义默认的 procurement_reqs 字典
@ -24,13 +25,13 @@ def fetch_procurement_reqs(procurement_path, invalid_path):
return DEFAULT_PROCUREMENT_REQS.copy()
try:
processed_filepath = pdf2txt(procurement_path) # 纯文本提取
# 使用 ThreadPoolExecutor 并行处理 get_technical_requirements 和 get_business_requirements
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交任务给线程池
future_technical = executor.submit(get_technical_requirements, procurement_path, invalid_path)
future_technical = executor.submit(get_technical_requirements, procurement_path, invalid_path,processed_filepath)
time.sleep(0.5) # 保持原有的延时
future_business = executor.submit(get_business_requirements, procurement_path)
future_business = executor.submit(get_business_requirements, procurement_path,processed_filepath)
# 获取并行任务的结果
technical_requirements = future_technical.result()
@ -56,13 +57,14 @@ def fetch_procurement_reqs(procurement_path, invalid_path):
# 在出错时返回默认的包含空字符串的字典
return DEFAULT_PROCUREMENT_REQS.copy()
#TODO:技术要求可以在技术参数之后执行,把完整的技术参数输入,问大模型,除了上述内容还有哪些
#TODO:技术要求可以在技术参数之后执行,把完整的技术参数输入,问大模型,除了上述内容还有哪些,这样的话把技术标和其他的区分开。
if __name__ == "__main__":
start_time=time.time()
output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\货物标output"
# file_path="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目_procurement.pdf"
procurement_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\db79e9e0-830e-442c-8cb6-1d036215f8ff\\ztbfile_procurement.pdf"
procurement_docpath="C:\\Users\\Administrator\\Desktop\\fsdownload\\db79e9e0-830e-442c-8cb6-1d036215f8ff\\ztbfile_procurement.docx"
procurement_path = r"C:\Users\Administrator\Desktop\fsdownload\5901b181-b55f-4107-9f30-c85d607b1fa0\ztbfile_procurement.pdf"
procurement_docpath=r"C:\Users\Administrator\Desktop\fsdownload\5901b181-b55f-4107-9f30-c85d607b1fa0"
invalid_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\db79e9e0-830e-442c-8cb6-1d036215f8ff\\ztbfile.pdf"
res=fetch_procurement_reqs(procurement_path,invalid_path)
print(json.dumps(res, ensure_ascii=False, indent=4))