12.3
This commit is contained in:
parent
643fc904a3
commit
2119f9879d
@ -576,7 +576,6 @@ def combine_find_invalid(file_path, output_dir):
|
||||
print("无效标与废标done...")
|
||||
return {"无效标与废标项": combined_dict}
|
||||
|
||||
# TODO:无效标目前以整个docx文档作为输入,可能导致后面两章不必要的信息也导入。 无效投标至少>8个字
|
||||
if __name__ == '__main__':
|
||||
start_time = time.time()
|
||||
# truncate_json_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output4\\tmp2\\竞争性谈判文件(3)_tobidders_notice_part1\\truncate_output.json"
|
||||
|
@ -514,8 +514,6 @@ def truncate_pdf_specific_engineering(pdf_path, output_folder, selections, uniqu
|
||||
logger.error(f"Error in truncate_pdf_specific_engineering: {e}")
|
||||
return [""] * len(selections) # 返回与 selections 数量相同的空字符串列表
|
||||
|
||||
|
||||
#TODO:zbtest8 zbtest18有问题 后期需要完善,截取需要截两次,第一次严格第二次宽松
|
||||
if __name__ == "__main__":
|
||||
input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\new_test\\zbtest8.pdf"
|
||||
# input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\68549b0b-e892-41a9-897c-c3694535ee61\\ztbfile.pdf"
|
||||
|
@ -278,6 +278,7 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id):
|
||||
|
||||
#TODO:同系统下的多个货物,记录一下数量
|
||||
#TODO:设备前面带星,而不是要求前面带星。
|
||||
#TODO:重置一下投标文件格式提取那部分的代码
|
||||
#商务标这里改为列表最里层
|
||||
#good_list 金额 截取上下文
|
||||
if __name__ == "__main__":
|
||||
|
0
flask_app/test_case/__init__.py
Normal file
0
flask_app/test_case/__init__.py
Normal file
416
flask_app/test_case/test_combine_and_update_results.py
Normal file
416
flask_app/test_case/test_combine_and_update_results.py
Normal file
@ -0,0 +1,416 @@
|
||||
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
|
||||
|
||||
|
||||
# 测试用例1:复杂情况
|
||||
def test_complex_case():
|
||||
original_data = {
|
||||
'user.name': 'Alice',
|
||||
'user。details': {
|
||||
'age': 30,
|
||||
'address line': '123 Main St', # 全角空格
|
||||
'preferences': {
|
||||
'color': 'blue',
|
||||
'food': 'sushi'
|
||||
}
|
||||
},
|
||||
'status': 'active',
|
||||
'metrics': {
|
||||
'score': 85,
|
||||
'rank': 5
|
||||
},
|
||||
'list_field': [1, 2, 3]
|
||||
}
|
||||
|
||||
updates = {
|
||||
'user. name': 'Bob', # 更新user.name
|
||||
'user.details.age': 31, # 更新嵌套字段
|
||||
'user.details.addressline': '456 Elm St', # 更新地址,键中有空格被移除
|
||||
'user.details.preferences.hobby': 'cycling', # 添加新的嵌套字段
|
||||
'status': 'inactive', # 更新顶级字段
|
||||
'metrics.score': {'current': 90, 'max': 100}, # 更新为字典
|
||||
'metrics.new_metric': 50, # 添加新的顶级嵌套字段
|
||||
'new_field': 'new_value', # 添加新的顶级字段
|
||||
'list_field': [4, 5] # 更新列表字段
|
||||
}
|
||||
|
||||
expected_result = {
|
||||
'user': {
|
||||
'name': 'Bob',
|
||||
'details': {
|
||||
'age': 31,
|
||||
'addressline': '456 Elm St',
|
||||
'preferences': {
|
||||
'color': 'blue',
|
||||
'food': 'sushi',
|
||||
'hobby': 'cycling'
|
||||
}
|
||||
}
|
||||
},
|
||||
'status': 'inactive',
|
||||
'metrics': {
|
||||
'score': {'current': 90, 'max': 100},
|
||||
'rank': 5,
|
||||
'new_metric': 50
|
||||
},
|
||||
'list_field': [4, 5],
|
||||
'new_field': 'new_value'
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试复杂情况失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试复杂情况通过。")
|
||||
|
||||
|
||||
# 测试用例2:更新为空
|
||||
def test_empty_updates():
|
||||
original_data = {
|
||||
'key1': 'value1',
|
||||
'key2': {
|
||||
'subkey': 'subvalue'
|
||||
}
|
||||
}
|
||||
updates = {}
|
||||
expected_result = {
|
||||
'key1': 'value1',
|
||||
'key2': {
|
||||
'subkey': 'subvalue'
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试更新为空失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试更新为空通过。")
|
||||
|
||||
|
||||
# 测试用例3:原始数据为空
|
||||
def test_empty_original():
|
||||
original_data = {}
|
||||
updates = {
|
||||
'new.key': 'new value',
|
||||
'nested。key.sub key': 123
|
||||
}
|
||||
expected_result = {
|
||||
'new.key': 'newvalue',
|
||||
'nested.key': {
|
||||
'subkey': 123
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试原始数据为空失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试原始数据为空通过。")
|
||||
|
||||
|
||||
# 测试用例4:更新中包含非字典类型的值
|
||||
def test_non_dict_values():
|
||||
original_data = {
|
||||
'a.b': {
|
||||
'c.d': 1
|
||||
},
|
||||
'list.field': [1, 2, 3]
|
||||
}
|
||||
updates = {
|
||||
'a.b.c.d': {'new': 2},
|
||||
'list.field': 'not a list anymore'
|
||||
}
|
||||
expected_result = {
|
||||
'a': {
|
||||
'b': {
|
||||
'c': {
|
||||
'd': {'new': 2}
|
||||
}
|
||||
}
|
||||
},
|
||||
'list.field': 'not a list anymore'
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试更新中包含非字典类型的值失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试更新中包含非字典类型的值通过。")
|
||||
|
||||
|
||||
# 测试用例5:键中包含全角空格
|
||||
def test_full_width_spaces():
|
||||
original_data = {
|
||||
'key with fullwidth spaces': 'value1',
|
||||
'nested.key with.spaces': {
|
||||
'sub key': 'subvalue'
|
||||
}
|
||||
}
|
||||
updates = {
|
||||
'key with fullwidth spaces': 'updated_value',
|
||||
'nested.key with spaces.new subkey': 'new_subvalue'
|
||||
}
|
||||
expected_result = {
|
||||
'keywithfullwidthspaces': 'updated_value',
|
||||
'nested': {
|
||||
'keywithspaces': {
|
||||
'subkey': 'new_subvalue'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试键中包含全角空格失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试键中包含全角空格通过。")
|
||||
|
||||
|
||||
# 测试用例6:复杂嵌套和不同数据类型
|
||||
def test_nested_and_various_types():
|
||||
original_data = {
|
||||
'config.settings': {
|
||||
'resolution': '1080p',
|
||||
'volume': 75,
|
||||
'controls': {
|
||||
'jump': 'space',
|
||||
'crouch': 'ctrl'
|
||||
}
|
||||
},
|
||||
'user.preferences': {
|
||||
'theme': 'dark',
|
||||
'notifications': True
|
||||
}
|
||||
}
|
||||
|
||||
updates = {
|
||||
'config.settings.resolution': '4K',
|
||||
'config.settings.controls.run': 'shift', # 添加新控制
|
||||
'config.settings.volume': 80, # 更新音量
|
||||
'user.preferences.theme': 'light', # 更新主题
|
||||
'user.preferences.language': 'en-US', # 添加新偏好
|
||||
'new.section.setting': 'enabled' # 添加新部分
|
||||
}
|
||||
|
||||
expected_result = {
|
||||
'config': {
|
||||
'settings': {
|
||||
'resolution': '4K',
|
||||
'volume': 80,
|
||||
'controls': {
|
||||
'jump': 'space',
|
||||
'crouch': 'ctrl',
|
||||
'run': 'shift'
|
||||
}
|
||||
}
|
||||
},
|
||||
'user': {
|
||||
'preferences': {
|
||||
'theme': 'light',
|
||||
'notifications': True,
|
||||
'language': 'en-US'
|
||||
}
|
||||
},
|
||||
'new.section': {
|
||||
'setting': 'enabled'
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试复杂嵌套和不同数据类型失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试复杂嵌套和不同数据类型通过。")
|
||||
|
||||
|
||||
# 测试用例7:键仅包含空格和点号
|
||||
def test_keys_only_spaces_and_dots():
|
||||
original_data = {
|
||||
'. ..': 'value1',
|
||||
' ': 'value2', # 仅全角空格
|
||||
'nested.key': {
|
||||
'.sub.key.': 'subvalue'
|
||||
}
|
||||
}
|
||||
updates = {
|
||||
'...': 'updated_value1',
|
||||
'nested.key..sub.key.': 'updated_subvalue'
|
||||
}
|
||||
expected_result = {
|
||||
'..': 'updated_value1',
|
||||
'': 'value2', # 全角空格被删除后键为空字符串
|
||||
'nested': {
|
||||
'key': {
|
||||
'subkey': 'updated_subvalue'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试键仅包含空格和点号失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试键仅包含空格和点号通过。")
|
||||
|
||||
|
||||
# 测试用例8:更新覆盖整个嵌套结构
|
||||
def test_overwrite_nested_structure():
|
||||
original_data = {
|
||||
'settings': {
|
||||
'display': {
|
||||
'brightness': 70,
|
||||
'contrast': 50
|
||||
},
|
||||
'sound': {
|
||||
'volume': 80
|
||||
}
|
||||
}
|
||||
}
|
||||
updates = {
|
||||
'settings.display': 'default', # 覆盖整个display字典
|
||||
'settings.sound.volume': 90 # 更新音量
|
||||
}
|
||||
expected_result = {
|
||||
'settings': {
|
||||
'display': 'default',
|
||||
'sound': {
|
||||
'volume': 90
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试覆盖整个嵌套结构失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试覆盖整个嵌套结构通过。")
|
||||
|
||||
|
||||
# 测试用例9:更新包含列表中的字典
|
||||
def test_update_with_list_of_dicts():
|
||||
original_data = {
|
||||
'users': [
|
||||
{'name': 'Alice', 'age': 30},
|
||||
{'name': 'Bob', 'age': 25}
|
||||
],
|
||||
'settings': {
|
||||
'theme': 'dark'
|
||||
}
|
||||
}
|
||||
updates = {
|
||||
'users': [
|
||||
{'name': 'Alice', 'age': 31}, # 更新第一个用户的年龄
|
||||
{'name': 'Bob', 'age': 26}, # 更新第二个用户的年龄
|
||||
{'name': 'Charlie', 'age': 22} # 添加新用户
|
||||
],
|
||||
'settings.theme': 'light' # 更新主题
|
||||
}
|
||||
expected_result = {
|
||||
'users': [
|
||||
{'name': 'Alice', 'age': 31},
|
||||
{'name': 'Bob', 'age': 26},
|
||||
{'name': 'Charlie', 'age': 22}
|
||||
],
|
||||
'settings': {
|
||||
'theme': 'light'
|
||||
}
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试更新包含列表中的字典失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试更新包含列表中的字典通过。")
|
||||
|
||||
|
||||
# 测试用例10:键重复但规范化后不同
|
||||
def test_duplicate_keys_after_normalization():
|
||||
original_data = {
|
||||
'key': {
|
||||
'one':'value1',
|
||||
'two':'value2'
|
||||
}
|
||||
}
|
||||
updates = {
|
||||
'key.one': 'updated_value',
|
||||
'keytwo': 'updated_value3'
|
||||
}
|
||||
expected_result = {
|
||||
'keyone': 'updated_value', # 'key.one' 和 'key.one ' 规范化后合并为 'keyone'
|
||||
'keytwo': 'updated_value3' # 'key two' 规范化后为 'keytwo'
|
||||
}
|
||||
|
||||
result = combine_and_update_results(original_data, updates)
|
||||
assert result == expected_result, f"测试键重复但规范化后不同失败。\n预期: {expected_result}\n实际: {result}"
|
||||
print("测试键重复但规范化后不同通过。")
|
||||
|
||||
|
||||
# 主函数,运行所有测试用例
|
||||
def run_all_tests():
|
||||
test_functions = [
|
||||
# test_complex_case,
|
||||
# test_empty_updates,
|
||||
# test_empty_original,
|
||||
# test_non_dict_values,
|
||||
# test_full_width_spaces,
|
||||
# test_nested_and_various_types,
|
||||
# test_keys_only_spaces_and_dots,
|
||||
# test_overwrite_nested_structure,
|
||||
# test_update_with_list_of_dicts,
|
||||
test_duplicate_keys_after_normalization
|
||||
]
|
||||
|
||||
for test_func in test_functions:
|
||||
try:
|
||||
test_func()
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
except Exception as ex:
|
||||
print(f"{test_func.__name__} 运行时发生错误: {ex}")
|
||||
|
||||
print("所有测试用例已执行完毕。")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_all_tests()
|
92
flask_app/test_case/test_extract_matching_keys.py
Normal file
92
flask_app/test_case/test_extract_matching_keys.py
Normal file
@ -0,0 +1,92 @@
|
||||
from flask_app.货物标.技术参数要求提取后处理函数 import extract_matching_keys
|
||||
|
||||
|
||||
def test_extract_matching_keys():
|
||||
# 定义测试数据
|
||||
data = {
|
||||
"fruits": ["apple", "banana"],
|
||||
"fruits-1": ["orange"],
|
||||
"vegetables": ["carrot"],
|
||||
"系统功能": ["feature1"], # 特殊键,应被特殊处理或排除
|
||||
"grains": {
|
||||
"whole": ["rice", "wheat"],
|
||||
"whole-1": ["barley"],
|
||||
"refined": ["white rice"]
|
||||
},
|
||||
"misc": {
|
||||
"fruits": ["strawberry"],
|
||||
"fruits-1": ["blueberry"],
|
||||
"系统 功能": ["feature2"], # 特殊键,带空格
|
||||
"dairy": ["milk", "cheese"]
|
||||
},
|
||||
"beverages": {
|
||||
"alcoholic": {
|
||||
"beer": ["lager", "ale"],
|
||||
"wine": ["red", "white"]
|
||||
},
|
||||
"non-alcoholic": ["juice", "soda"]
|
||||
},
|
||||
"snacks": [
|
||||
{
|
||||
"chips": ["potato", "tortilla"],
|
||||
"nuts": ["almonds", "cashews"]
|
||||
},
|
||||
{
|
||||
"chips-1": ["kettle", "baked"],
|
||||
"candies": ["chocolate", "gummy"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
good_list = ["fruits", "vegetables", "grains", "chips"]
|
||||
|
||||
special_keys = ["系统功能", "dairy"] # 假设 'dairy' 也是特殊键
|
||||
|
||||
# 预期输出
|
||||
expected_output = {
|
||||
"fruits-a": ["apple", "banana"],
|
||||
"fruits-b": ["orange"],
|
||||
"vegetables": ["carrot"],
|
||||
"grains-a": ["rice", "wheat"],
|
||||
"grains-b": ["barley"],
|
||||
"grains": {"refined": ["white rice"]},
|
||||
"misc": {}, # 'fruits' and 'fruits-1' inside 'misc' should be processed
|
||||
"chips-a": ["potato", "tortilla"],
|
||||
"chips-b": ["kettle", "baked"]
|
||||
}
|
||||
|
||||
# 注意:根据您的函数逻辑,特殊键会被排除,且嵌套的 'fruits' 会被处理
|
||||
# 这里我们需要根据函数实际行为调整预期输出
|
||||
# 让我们根据函数逻辑重新定义预期输出
|
||||
|
||||
# 函数会生成新的键名,对于重复的 'fruits' 会添加后缀
|
||||
# 'grains' 内的 'whole' 和 'whole-1' 也会被处理为 'whole-a', 'whole-b'
|
||||
# 'chips' 和 'chips-1' 会被处理为 'chips-a', 'chips-b'
|
||||
# 'dairy' 是特殊键,应被排除
|
||||
# '系统功能' 和 '系统 功能' 是特殊键,应被排除
|
||||
|
||||
expected_output_correct = {
|
||||
"fruits-a": ["apple", "banana"],
|
||||
"fruits-b": ["orange"],
|
||||
"vegetables": ["carrot"],
|
||||
"grains-a": ["rice", "wheat"],
|
||||
"grains-b": ["barley"],
|
||||
"chips-a": ["potato", "tortilla"],
|
||||
"chips-b": ["kettle", "baked"]
|
||||
}
|
||||
|
||||
# 运行函数
|
||||
result = extract_matching_keys(data, good_list, special_keys)
|
||||
|
||||
# 打印结果
|
||||
print("测试用例: 提取匹配键并处理各种情况")
|
||||
print("输入数据:", data)
|
||||
print("good_list:", good_list)
|
||||
print("special_keys:", special_keys)
|
||||
print("\n预期输出:", expected_output_correct)
|
||||
print("实际输出:", result)
|
||||
print("\n测试通过:", result == expected_output_correct)
|
||||
|
||||
# 运行测试
|
||||
if __name__ == "__main__":
|
||||
test_extract_matching_keys()
|
352
flask_app/test_case/test_generate_key_paths.py
Normal file
352
flask_app/test_case/test_generate_key_paths.py
Normal file
@ -0,0 +1,352 @@
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def generate_key_paths(data):
|
||||
"""
|
||||
处理输入的字典,生成 key_paths, grouped_paths 和 good_list,并根据条件修改原始字典。
|
||||
|
||||
参数:
|
||||
data (dict): 输入的嵌套字典。
|
||||
|
||||
返回:
|
||||
tuple: 包含 key_paths, grouped_paths 和 good_list 的元组。
|
||||
"""
|
||||
# 编译用于匹配后缀的正则表达式模式
|
||||
pattern = re.compile(r'(.+)-\d+$')
|
||||
|
||||
# 初始化结果列表和字典
|
||||
key_paths = []
|
||||
grouped_counts = defaultdict(int) # 用于记录每个 grouped_path 的数量
|
||||
good_list = []
|
||||
|
||||
def recurse(current_dict, path):
|
||||
"""
|
||||
递归遍历字典,处理 key_paths 和 grouped_paths,并收集 good_list 和 grouped_counts。
|
||||
|
||||
参数:
|
||||
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:
|
||||
base = key
|
||||
base_names[key] = base
|
||||
base_name_count[base] = base_name_count.get(base, 0) + 1
|
||||
|
||||
# 第二遍遍历,根据基名的出现次数分类
|
||||
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_path = '.'.join(path + [base])
|
||||
grouped_counts[grouped_path] += 1
|
||||
|
||||
# 执行键名的重命名,同时保持原有顺序
|
||||
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, [])
|
||||
|
||||
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_counts 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 转换为包含数量的字典列表
|
||||
grouped_paths = [{path: grouped_counts[path]} for path in 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
|
||||
|
||||
# 对整个数据结构进行递归重命名
|
||||
return rename_keys_recursive(data)
|
||||
def test_generate_key_paths():
|
||||
data0={
|
||||
"显示系统": {
|
||||
"LED全彩显示屏": [],
|
||||
"控制盒及电源": [],
|
||||
"大屏播控系统": [],
|
||||
"配电柜(含PLC)": [],
|
||||
"钢结构底座及铝型材支架": [],
|
||||
"电缆及信号线缆": [],
|
||||
"控制终端": [],
|
||||
"50寸液晶电视机": [],
|
||||
"50寸电视机地面推车": [],
|
||||
"高清监视器": []
|
||||
},
|
||||
"摄像系统": {
|
||||
"高清摄像机": [],
|
||||
"摄像机三脚架": [],
|
||||
"摄像机壁装架": [],
|
||||
"摄像机控制键盘": []
|
||||
},
|
||||
"视频处理系统": {
|
||||
"高清视频拼控矩阵(16*16)": [],
|
||||
"分量信号接口器": [],
|
||||
"高清四画面分割器": []
|
||||
},
|
||||
"发言系统": {
|
||||
"数字会议发言主机": [],
|
||||
"方形短杆代表话筒": [],
|
||||
"专用连接线缆": [],
|
||||
"手持无线话筒": []
|
||||
},
|
||||
"视频会议系统": {
|
||||
"多点控制器": [],
|
||||
"多串口控制服务器": [],
|
||||
"综合会议管理调度平台": [],
|
||||
"高清会议终端(主会场)": [],
|
||||
"高清会议终端(分会场)": [],
|
||||
"65寸电视机移动推车(9楼)": [],
|
||||
"65寸液晶电视机(分会场)": [],
|
||||
"控制平板及软件": [],
|
||||
"鹅颈话筒": []
|
||||
},
|
||||
"辅助系统": {
|
||||
"时序电源": [],
|
||||
"多媒体地插盒": [],
|
||||
"线材辅料": [],
|
||||
"墙体拆除及修复": []
|
||||
}
|
||||
}
|
||||
# 定义测试数据
|
||||
data = {
|
||||
"fruits": [],
|
||||
"fruits-1": [],
|
||||
"vegetables": {
|
||||
"root": [],
|
||||
"root-1": [],
|
||||
"leafy": []
|
||||
},
|
||||
"grains": {
|
||||
"交换机":[],
|
||||
"whole grains": [], # 键名包含空格
|
||||
"whole-grains-1": [] # 同一基名,带后缀
|
||||
},
|
||||
"misc": {
|
||||
"system functions": [], # 特殊键,包含空格
|
||||
"system functions-1": [], # 特殊键,带后缀
|
||||
"fruits-1": [],
|
||||
"fruits-2": [],
|
||||
"fruits-3": []
|
||||
},
|
||||
"beverages": []
|
||||
}
|
||||
# 运行函数
|
||||
key_paths, grouped_paths, good_list, data_copy = generate_key_paths(data0)
|
||||
# 打印结果
|
||||
print("实际 data_copy:")
|
||||
print(json.dumps(data_copy,ensure_ascii=False,indent=4))
|
||||
modi=rename_keys(data_copy)
|
||||
print(json.dumps(modi, ensure_ascii=False, indent=4))
|
||||
|
||||
print(key_paths)
|
||||
print(grouped_paths)
|
||||
print(good_list)
|
||||
|
||||
user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增删内容,尤其是不可擅自添加序号。
|
||||
|
||||
要求与指南:
|
||||
1. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。
|
||||
2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。
|
||||
3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。
|
||||
4. 你的键值应该全面,对于同一个单元格内的数据,尽量全面,不要遗漏,对于单元格内以序号分隔的各条参数要求,请分别添加进键值(即字符串列表)中。
|
||||
|
||||
### 示例输出1如下:
|
||||
{{
|
||||
"摄像机控制键盘": [
|
||||
"1、▲支持串行 RS232/RS422 和 IP 混合控制,允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;",
|
||||
"2、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机。",
|
||||
"★能够自动对焦,提供检测报告"
|
||||
]
|
||||
}}
|
||||
|
||||
### 示例输出2如下(包含嵌套结构):
|
||||
{{
|
||||
"摄像机": [
|
||||
"摄像机:有效像素:≥900W像素",
|
||||
"摄像机:最低照度:彩色≤0.001lx",
|
||||
"协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK"
|
||||
]
|
||||
}}
|
||||
"""
|
||||
|
||||
user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增添内容。
|
||||
请注意以下特殊情况:
|
||||
要求与指南:
|
||||
1. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表。
|
||||
2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。
|
||||
3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。
|
||||
4. 你的键值应该全面,对于同一个单元格内的数据,尽量全面,不要遗漏,对于单元格内以序号分隔的各条参数要求,请分别添加进键值(即字符串列表)中。
|
||||
|
||||
### 示例输出1如下:
|
||||
{{
|
||||
"交换机-1": [
|
||||
"★1、支持固化千兆电口≥8 个,固化千兆光口≥2 个,桌面型设备;",
|
||||
"2、支持静态链路聚合"
|
||||
],
|
||||
"交换机-2": [
|
||||
"1、交换容量≥52Gbps,包转发率≥38.69Mpps,",
|
||||
"2、提供国家强制性产品认证证书及测试报告(3C)",
|
||||
"★能实现信号控制独立传输"
|
||||
]
|
||||
}}
|
||||
|
||||
### 示例输出2如下(包含嵌套结构):
|
||||
{{
|
||||
"摄像机-1": [
|
||||
"摄像机:有效像素:≥900W像素",
|
||||
"摄像机:最低照度:彩色≤0.001lx",
|
||||
"协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK"
|
||||
],
|
||||
"摄像机-2": [
|
||||
"支持夜视", "支持云存储"
|
||||
]
|
||||
}}
|
||||
"""
|
||||
def test_generate_ques_two(grouped_paths):
|
||||
queries=[]
|
||||
for grouped_dict in grouped_paths:
|
||||
for grouped_key, grouped_key_cnt in grouped_dict.items():
|
||||
# 将键中的 '.' 替换为 '下的'
|
||||
modified_grouped_key = grouped_key.replace('.', '下的')
|
||||
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
|
||||
# 如果需要使用 full_text,可以取消注释并提供相应的实现
|
||||
# full_text = read_txt_to_string(processed_filepath)
|
||||
# new_query = user_query_template_two.format(modified_grouped_key, grouped_key, modified_grouped_key, full_text)
|
||||
|
||||
# 根据您的需求,生成新的查询字符串
|
||||
new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt,grouped_key, modified_grouped_key)
|
||||
queries.append(new_query)
|
||||
print(new_query)
|
||||
|
||||
def test_generate_ques_one(key_paths):
|
||||
queries=[]
|
||||
for key in key_paths:
|
||||
# 将键中的 '.' 替换为 '下的'
|
||||
modified_key = key.replace('.', '下的')
|
||||
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
|
||||
# full_text = read_txt_to_string(processed_filepath)
|
||||
# new_query = user_query_template.format(modified_key, key, modified_key,full_text) #转豆包后取消注释
|
||||
new_query = user_query_template.format(modified_key, key, modified_key)
|
||||
queries.append(new_query)
|
||||
print(new_query)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test_generate_key_paths()
|
||||
# grouped_paths=[{'fruits': 2}, {'vegetables.root': 2}, {'misc.system functions': 2}, {'misc.fruits': 3}]
|
||||
# key_paths=['显示系统.LED全彩显示屏', '显示系统.控制盒及电源', '显示系统.大屏播控系统', '显示系统.配电柜(含PLC)', '显示系统.钢结构底座及铝型材支架', '显示系统.电缆及信号线缆', '显示系统.控制终端', '显示系统.50寸液晶电视机', '显示系统.50寸电视机地面推车', '显示系统.高清监视器', '摄像系统.高清摄像机', '摄像系统.摄像机三脚架', '摄像系统.摄像机壁装架', '摄像系统.摄像机控制键盘', '视频处理系统.高清视频拼控矩阵(16*16)', '视频处理系统.分量信号接口器', '视频处理系统.高清四画面分割器', '发言系统.数字会议发言主机', '发言系统.方形短杆代表话筒', '发言系统.专用连接线缆', '发言系统.手持无线话筒', '视频会议系统.多点控制器', '视频会议系统.多串口控制服务器', '视频会议系统.综合会议管理调度平台', '视频会议系统.高清会议终端(主会场)', '视频会议系统.高清会议终端(分会场)', '视频会议系统.65寸电视机移动推车(9楼)', '视频会议系统.65寸液晶电视机(分会场)', '视频会议系统.控制平板及软件', '视频会议系统.鹅颈话筒', '辅助系统.时序电源', '辅助系统.多媒体地插盒', '辅助系统.线材辅料', '辅助系统.墙体拆除及修复']
|
||||
key_paths=['显示系统.LED全彩显示屏', '显示系统.控制盒及电源', '显示系统.大屏播控系统']
|
||||
# test_generate_ques(grouped_paths)
|
||||
test_generate_ques_one(key_paths)
|
213
flask_app/test_case/test_restructure_data.py
Normal file
213
flask_app/test_case/test_restructure_data.py
Normal file
@ -0,0 +1,213 @@
|
||||
from flask_app.货物标.技术参数要求提取后处理函数 import restructure_data
|
||||
|
||||
#注意!这里的期待输出有误
|
||||
def test_restructure_data():
|
||||
print("开始测试 restructure_data 函数...\n")
|
||||
|
||||
# 测试用例 1: 所有顶层键的值都是列表(二层结构)
|
||||
data1 = {
|
||||
"fruits": ["apple", "banana", "cherry"],
|
||||
"vegetables": ["carrot", "lettuce", "spinach"],
|
||||
"grains": ["rice", "wheat", "barley"]
|
||||
}
|
||||
expected1 = data1.copy()
|
||||
result1 = restructure_data(data1)
|
||||
print("测试用例 1: 所有顶层键的值都是列表(二层结构)")
|
||||
print("输入:", data1)
|
||||
print("预期输出:", expected1)
|
||||
print("实际输出:", result1)
|
||||
print("测试通过:", result1 == expected1, "\n")
|
||||
|
||||
# 测试用例 2: 所有顶层键的值都是字典,且已经是三层结构
|
||||
data2 = {
|
||||
"fruits": {
|
||||
"citrus": ["orange", "lemon"],
|
||||
"berries": ["strawberry", "blueberry"]
|
||||
},
|
||||
"vegetables": {
|
||||
"root": ["carrot", "beet"],
|
||||
"leafy": ["lettuce", "spinach"]
|
||||
}
|
||||
}
|
||||
expected2 = data2.copy()
|
||||
result2 = restructure_data(data2)
|
||||
print("测试用例 2: 所有顶层键的值都是字典,且已经是三层结构")
|
||||
print("输入:", data2)
|
||||
print("预期输出:", expected2)
|
||||
print("实际输出:", result2)
|
||||
print("测试通过:", result2 == expected2, "\n")
|
||||
|
||||
# 测试用例 3: 混合的两层和三层结构
|
||||
data3 = {
|
||||
"fruits": ["apple", "banana"],
|
||||
"vegetables": {
|
||||
"root": ["carrot", "beet"],
|
||||
"leafy": ["lettuce", "spinach"]
|
||||
},
|
||||
"grains": ["rice", "wheat"]
|
||||
}
|
||||
expected3 = {
|
||||
"fruits": {"fruits": ["apple", "banana"]},
|
||||
"vegetables": {
|
||||
"root": ["carrot", "beet"],
|
||||
"leafy": ["lettuce", "spinach"]
|
||||
},
|
||||
"grains": {"grains": ["rice", "wheat"]}
|
||||
}
|
||||
result3 = restructure_data(data3)
|
||||
print("测试用例 3: 混合的两层和三层结构")
|
||||
print("输入:", data3)
|
||||
print("预期输出:", expected3)
|
||||
print("实际输出:", result3)
|
||||
print("测试通过:", result3 == expected3, "\n")
|
||||
|
||||
# 测试用例 4: 超过三层嵌套
|
||||
data4 = {
|
||||
"fruits": {
|
||||
"citrus": {
|
||||
"tropical": ["orange", "lemon"]
|
||||
},
|
||||
"berries": ["strawberry", "blueberry"]
|
||||
},
|
||||
"vegetables": ["carrot", "lettuce"]
|
||||
}
|
||||
expected4 = {
|
||||
"fruits.citrus": {
|
||||
"tropical": ["orange", "lemon"]
|
||||
},
|
||||
"fruits.berries": {
|
||||
"berries": ["strawberry", "blueberry"]
|
||||
},
|
||||
"vegetables": {
|
||||
"vegetables": ["carrot", "lettuce"]
|
||||
}
|
||||
}
|
||||
result4 = restructure_data(data4)
|
||||
print("测试用例 4: 超过三层嵌套")
|
||||
print("输入:", data4)
|
||||
print("预期输出:", expected4)
|
||||
print("实际输出:", result4)
|
||||
print("测试通过:", result4 == expected4, "\n")
|
||||
|
||||
# 测试用例 5: 超过四层嵌套
|
||||
data5 = {
|
||||
"animals": {
|
||||
"mammals": {
|
||||
"primates": {
|
||||
"humans": ["Alice", "Bob"],
|
||||
"monkeys": ["Charlie", "Dave"]
|
||||
},
|
||||
"carnivores": ["Lion", "Tiger"]
|
||||
},
|
||||
"birds": ["Eagle", "Parrot"]
|
||||
},
|
||||
"plants": ["Oak", "Pine"]
|
||||
}
|
||||
expected5 = {
|
||||
"animals.mammals.primates": {
|
||||
"humans": ["Alice", "Bob"],
|
||||
"monkeys": ["Charlie", "Dave"]
|
||||
},
|
||||
"animals.mammals.carnivores": ["Lion", "Tiger"],
|
||||
"animals.birds": ["Eagle", "Parrot"],
|
||||
"plants": {"plants": ["Oak", "Pine"]}
|
||||
}
|
||||
result5 = restructure_data(data5)
|
||||
print("测试用例 5: 超过四层嵌套")
|
||||
print("输入:", data5)
|
||||
print("预期输出:", expected5)
|
||||
print("实际输出:", result5)
|
||||
print("测试通过:", result5 == expected5, "\n")
|
||||
|
||||
# 测试用例 6: 空字典
|
||||
data6 = {}
|
||||
expected6 = {}
|
||||
result6 = restructure_data(data6)
|
||||
print("测试用例 6: 空字典")
|
||||
print("输入:", data6)
|
||||
print("预期输出:", expected6)
|
||||
print("实际输出:", result6)
|
||||
print("测试通过:", result6 == expected6, "\n")
|
||||
|
||||
# 测试用例 7: 顶层键的值类型无效
|
||||
data7 = {
|
||||
"fruits": "apple",
|
||||
"vegetables": ["carrot", "lettuce"]
|
||||
}
|
||||
print("测试用例 7: 顶层键的值类型无效")
|
||||
print("输入:", data7)
|
||||
try:
|
||||
restructure_data(data7)
|
||||
print("预期结果: 引发 ValueError")
|
||||
print("实际输出: 无异常")
|
||||
print("测试通过: False\n")
|
||||
except ValueError as ve:
|
||||
print("预期结果: 引发 ValueError")
|
||||
print("实际输出: 引发 ValueError:", ve)
|
||||
print("测试通过:", "数据格式异常" in str(ve), "\n")
|
||||
|
||||
# 测试用例 8: 嵌套字典中存在无效的值类型
|
||||
data8 = {
|
||||
"fruits": {
|
||||
"citrus": "orange",
|
||||
"berries": ["strawberry", "blueberry"]
|
||||
},
|
||||
"vegetables": ["carrot", "lettuce"]
|
||||
}
|
||||
print("测试用例 8: 嵌套字典中存在无效的值类型")
|
||||
print("输入:", data8)
|
||||
try:
|
||||
restructure_data(data8)
|
||||
print("预期结果: 引发 ValueError")
|
||||
print("实际输出: 无异常")
|
||||
print("测试通过: False\n")
|
||||
except ValueError as ve:
|
||||
print("预期结果: 引发 ValueError")
|
||||
print("实际输出: 引发 ValueError:", ve)
|
||||
print("测试通过:", "数据格式异常" in str(ve), "\n")
|
||||
|
||||
# 测试用例 9: 复杂的嵌套结构
|
||||
data9 = {
|
||||
"fruits": {
|
||||
"citrus": ["orange", "lemon"],
|
||||
"berries": {
|
||||
"summer": ["strawberry", "blueberry"],
|
||||
"winter": ["cranberry"]
|
||||
}
|
||||
},
|
||||
"vegetables": ["carrot", "lettuce"],
|
||||
"grains": {
|
||||
"whole": ["rice", "wheat"],
|
||||
"refined": ["white rice"]
|
||||
}
|
||||
}
|
||||
expected9 = {
|
||||
"fruits.berries": {
|
||||
"summer": ["strawberry", "blueberry"],
|
||||
"winter": ["cranberry"]
|
||||
},
|
||||
"fruits.citrus": {
|
||||
"citrus": ["orange", "lemon"]
|
||||
},
|
||||
"vegetables": {
|
||||
"vegetables": ["carrot", "lettuce"]
|
||||
},
|
||||
"grains.whole": {
|
||||
"whole": ["rice", "wheat"]
|
||||
},
|
||||
"grains.refined": {
|
||||
"refined": ["white rice"]
|
||||
}
|
||||
}
|
||||
result9 = restructure_data(data9)
|
||||
print("测试用例 9: 复杂的嵌套结构")
|
||||
print("输入:", data9)
|
||||
print("预期输出:", expected9)
|
||||
print("实际输出:", result9)
|
||||
print("测试通过:", result9 == expected9, "\n")
|
||||
|
||||
print("所有测试用例已完成。")
|
||||
|
||||
# 运行测试
|
||||
if __name__ == "__main__":
|
||||
test_restructure_data()
|
342
flask_app/test_case/采购要求数据.py
Normal file
342
flask_app/test_case/采购要求数据.py
Normal file
@ -0,0 +1,342 @@
|
||||
data1={
|
||||
"采购需求": {
|
||||
"显示系统": {
|
||||
"★LED全彩显示屏": [],
|
||||
"控制盒及电源": [],
|
||||
"大屏播控系统": [],
|
||||
"配电柜(含PLC)": [],
|
||||
"钢结构底座及铝型材支架": [],
|
||||
"电缆及信号线缆": [],
|
||||
"控制终端": [],
|
||||
"50寸液晶电视机": [],
|
||||
"50寸电视机地面推车": [],
|
||||
"高清监视器": []
|
||||
},
|
||||
"摄像系统": {
|
||||
"★高清摄像机": [],
|
||||
"摄像机三角架": [],
|
||||
"摄像机壁装架": [],
|
||||
"摄像机控制键盘": []
|
||||
},
|
||||
"视频处理系统": {
|
||||
"★高清视频拼控矩阵(16*16)": [],
|
||||
"分量信号接口器": [],
|
||||
"高清四画面分割器": []
|
||||
},
|
||||
"发言系统": {
|
||||
"数字会议发言主机": [],
|
||||
"方形短杆代表话筒": [],
|
||||
"专用连接线缆": [],
|
||||
"手持无线话筒": []
|
||||
},
|
||||
"视频会议系统": {
|
||||
"★多点控制器": [],
|
||||
"★多串口控制服务器": [],
|
||||
"★综合会议管理调度平台": [],
|
||||
"★高清会议终端(主会场)": [],
|
||||
"★高清会议终端(分会场)": [],
|
||||
"65寸电视机移动推车(9楼)": [],
|
||||
"65寸液晶电视机(分会场)": [],
|
||||
"控制平板及软件": [],
|
||||
"鹅颈话筒": []
|
||||
},
|
||||
"辅助系统": {
|
||||
"时序电源": [],
|
||||
"多媒体地插盒": [],
|
||||
"线材辅料": [],
|
||||
"墙体拆除及修复": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data2={
|
||||
"采购需求": {
|
||||
"显示系统": {
|
||||
"★LED全彩显示屏": [
|
||||
"1、显示尺寸:6m±0.3(W)× 1.5m±0.2(H),单屏分辨率≥3744 × 1040;",
|
||||
"2 、像素间距≤1.53mm;",
|
||||
"3 、亮度≥450nits, 色温: 3000-15000K 可调, 对比度: 5000:1;",
|
||||
"4 、峰值功耗≤440W, 平均功耗≤146W, 带有智能(黑屏) 节电功 能, 开启智能节电功能比没开启节能 40%以上;",
|
||||
"5 、水平可视角度≥160 ° , 垂直可视角度≥140 ° ;",
|
||||
"6 、亮度均匀性≥97%, 刷新率≥3840 Hz , 发光点中心距偏差<3%;",
|
||||
"7 、色域覆盖率≥100%, 低亮高灰: 100%亮度时, 16 bits 灰度,20% 亮度时, 12bits 灰度;",
|
||||
"8 、铝底壳材质, 无风扇散热结构;",
|
||||
"9 、模组电源接口采用4P 接插头, 免工具维护, 同时有防呆设计, 预防接错电源线短路而导致的烧毁模组行为,采用集成 HUB 接收卡 控制, 支持通讯状态监控;",
|
||||
"10 、冗余备份, 支持双电网供电, 当其中一路交流电网跳闸后, 另 外一路电网继续供电, 实现不间断供电, 支持热备份, 当其中一块 电源失效后, 另一块电源继续工作, 从而实现不间断供电;",
|
||||
"11 、屏体发光模组采用 4.5 VDC 的安全电压供电;",
|
||||
"12 、彩色信号处理位数≥16bit;",
|
||||
"13 、具备故障自诊及排查功能;",
|
||||
"14 、 图像有降噪 、增强 、运动补偿 、色坐标变换处理 、钝 化处理无 几何失真和线性失真现象 、消除鬼影拖尾, 无“毛毛虫 ”“鬼影 ” 跟随现象;",
|
||||
"15 、防护等级符合 IP6X, 显示屏具有防潮 、防尘 、防腐蚀 、防电磁 干扰 、防静电等功能, 并具有过流、短路 、过压 、欠压保护等功能;",
|
||||
"16 、工作噪音声压等级一米处≤7.8 dB (A),盐雾符合 10 级要求, PCB 阻燃等级达到 UL 94 V-0 级要求, 通过 9 级烈度地震模拟实验。"
|
||||
],
|
||||
"控制盒及电源": [
|
||||
"高清发送盒 2 台 、接收卡 27 张 、 电源 26 台。"
|
||||
],
|
||||
"大屏播控系统": [
|
||||
"1、具有多用户多权限管理功能,支持多用户同时登录客户端,每个用户根据自身不同权限管理显示屏;",
|
||||
"2、系统对输入信号源进行预监视,实现在播控前预先查看的功能。"
|
||||
],
|
||||
"配电柜(含PLC)": [
|
||||
"1 、10 KW 容量,施耐德元器件,含 PLC 控制系统,可实时获取屏幕背部烟雾及温度数据, 可执行远程开关机操作; 数字量 输入: 12 路 24 V DC 输入;数字量输出: 10 路继电输出;模拟量输 入:4 个电压 输入,4 个电流输入;通讯口: 1 个 RS232,2 个 RS485, 1 个以太网; 程序容量: 256K 片内 Flash 内存; 功能: 远程监控 、 电话监视 、温 度监控 、消防监控;",
|
||||
"2、本配电柜具备过压、过流、欠压、短路、断路以及漏电保护措施。"
|
||||
],
|
||||
"钢结构底座及铝型材支架": [
|
||||
"1 、主体钢架结构及定制型材;",
|
||||
"2 、确保楼层承受力许可,按需加固楼层地面;",
|
||||
"3 、钢结构。"
|
||||
],
|
||||
"电缆及信号线缆": [
|
||||
"配套所需控制网线 、高清视频线缆 、 电源线缆等适配。"
|
||||
],
|
||||
"控制终端": [
|
||||
"1 、处理器: 八核心 16 线程;",
|
||||
"2 、显卡: 8G/DDR6/PCI Express 4.0 16X;",
|
||||
"3 、 内存: ≥8G DDR4 内存;",
|
||||
"4 、硬盘: SSD 固态硬盘(容量≥480G);",
|
||||
"5 、接口: 音频/网络/HDMI 接口/9 针串口;",
|
||||
"6 、显示器: 21.5 英寸。"
|
||||
],
|
||||
"50寸液晶电视机": [
|
||||
"1 、屏幕尺寸: 50 英寸;",
|
||||
"2 、背光类型: LED;",
|
||||
"3 、屏幕分辨率: 超高清 4K (3840 ×2 160);",
|
||||
"4 、支持 HDR 显示;",
|
||||
"5 、接口: USB2.0 ×2 、HDMI2.0 ×3;",
|
||||
"6 、 网络连接方式: 无线/网线。"
|
||||
],
|
||||
"50寸电视机地面推车": [
|
||||
"1 、全钢结构, 满足 50 寸显示屏承重安装要求;",
|
||||
"2 、承载: 50Kg;",
|
||||
"3 、高度: 650mm;",
|
||||
"4 、可以调节观看角度。"
|
||||
],
|
||||
"高清监视器": [
|
||||
"1 、27 寸全屏高清监视器;",
|
||||
"2 、 IPS 技术炫彩硬屏, 4K 高清分辨率;",
|
||||
"3 、 内置音箱;",
|
||||
"4 、亮度: 350cd/m2;",
|
||||
"5 、接口: DP × 1, HDMI ×2, USB ×2, 音频/耳机 × 1。"
|
||||
]
|
||||
},
|
||||
"摄像系统": {
|
||||
"★高清摄像机": [
|
||||
"1 、成像器件: 1/2.8 Exmor CMOS;",
|
||||
"2 、镜头: 30 倍光学(f=4.3mm to 129mm );",
|
||||
"3 、水平视角: 63.7 ° ;",
|
||||
"4、视频输出格式:1080P/60,1 080P/50,1080P/30,1080P/25,1080i/60, 720p/60;",
|
||||
"5 、视频输出: 3G-SDI, HDMI, CVBS,IP (可同步输出 );",
|
||||
"6 、真双输出: IP 和 SDI 视频格式可以独立设置;",
|
||||
"7 、控制方式: RS232 / RS422 / RS485, IP/Onvif/Visca-over-IP, IP 控制软件, 红外遥控器;",
|
||||
"8 、IP 最高 1080p60, 支持 H.264/H.265/MJPEG;",
|
||||
"9 、支持 Tally 灯;",
|
||||
"10 、支持独立 PoE+(IEEE 802.3 at) 和 DC 12V 电源;",
|
||||
"11 、扩展存储: Micro SD,最高支持 128GB。"
|
||||
],
|
||||
"摄像机三角架": [
|
||||
"1 、铝合金材质, 承重 2-10Kg;",
|
||||
"2 、满足高清摄像机承重 、尺寸要求。"
|
||||
],
|
||||
"摄像机壁装架": [
|
||||
"1 、钣金 2.0mm 优质冷轧钢板;",
|
||||
"2 、满足高清摄像机壁挂安装承重 、尺寸要求;",
|
||||
"3 、具有放置摄像机电源空间。"
|
||||
],
|
||||
"摄像机控制键盘": [
|
||||
"1 、支持串行 RS232/RS422 和 IP 混合控制, 允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;",
|
||||
"2 、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机;",
|
||||
"3 、通过手柄或单独的 Seesaw 控制杆控制变焦,通过专用旋钮及按钮, 可直接设置摄像机的光圈 、快门 、增益 、 白平 衡 、 自动曝光等 级等参数, 无需通过菜单设置;",
|
||||
"4 、使用 IP 控制,控制器可自动搜索系统中的 IP 摄像机, 快速分配 摄像机 IP 地址 。支持 Onvif 、CGI 、VISCA over IP;",
|
||||
"5 、单个网络上不限制控制器, 控制 255 台 IP 摄像机;",
|
||||
"6 、多达 256 个预置位,带有图像参数记忆功能及 8 条 轨迹记忆存储, 方便快速调用摄像机的运动 (需摄像机支持 );",
|
||||
"7 、6 个可选 ASSIGN 功能键, 可以为 ASSIGN 按钮分配附加功能;",
|
||||
"8 、多色按键/旋钮照明指示摄像机当前状态;",
|
||||
"9 、独立双电源供电: DC 12V ,POE。"
|
||||
]
|
||||
},
|
||||
"视频处理系统": {
|
||||
"★高清视频拼控矩阵(16*16)": [
|
||||
"1 、8U 切换主箱体, 支持输入 13 槽, 输出 4.5 槽, 支持 8 路高分采集, 支持冗余电源, 标配 1 个电源模块; 本项目配置输入接 口 16 路和 1 张字幕卡, 输出接口 16 路;",
|
||||
"2 、设备应为纯硬件 FPGA 架构, CrossPoint 全总线交换技术, 背板 等效带宽;",
|
||||
"3 、单张板卡支持 4 通道输入或输出, 紧凑型机箱,模拟视频单板卡 支持 16 路同时输入, 单卡支持 2 种信号源任意组合;",
|
||||
"4、输入输出板卡可热插拔,输入板卡热插拔恢复时间 <2s,输出板 卡热插拔恢复时间<8s;",
|
||||
"5 、开机时间≤10s, 启动电源至输出最总画面的时间间隔;",
|
||||
"6 、平均故障时间间隔 ( MTBF ) 不小于 96000 小时, 保证设备能够 稳定运行;",
|
||||
"7 、最大单机背板信号处理带宽不小于 720Gbps;",
|
||||
"8、对各个输入通道采用纯硬件处理技术,采用独享带宽方式为每个 输入通道分配带宽, 切换过程中对其他信号无影响, 实现了对输入 通道的实时处理;",
|
||||
"9、支持集中采集 DVI、VGA、CVBS、HDBaseT、HDMI、SDI、YPbPr 、 光纤等 2K 信号,Dual-link DVI、HDMI 1.4、DisplayPort 等 4K 信号;",
|
||||
"10 、支持 DVI 、HDBaseT 、HDMI 、SDI 、光纤 、CVBS 、Ypbp r 等常见的 2K 信号输出, Dual-link DVI 、HDMI 1.4 等 4K 信号输出;",
|
||||
"11、设备可实现任意一路画面的任意比例缩放、漫游、 跨屏 、叠加、 开窗;",
|
||||
"12 、设备支持图像无缝实时切换功能, 无缝切换时间<20 ms ;",
|
||||
"13 、支持场景保存及快速调用, 支持场景轮巡, 适应于不同的应用 场景;",
|
||||
"14 、支持信号源预监功能, 支持浏览所有输入信号源的实时预览画 面;",
|
||||
"15 、支持大屏图像回显, 可显示整面拼接墙的显示图像;",
|
||||
"16 、支持设置拼接屏的拼缝补偿, 可精确到 1 个像素;",
|
||||
"17 、支持 RRTA 分辨率实时全兼容技术, 单台 设备应支持同时控制 4 组不同分辨率的大屏幕显示;",
|
||||
"18 、设备具备静态底图功能, 设备支持超大分辨率底图显示, 横纵 分辨率最大 65535 像素。"
|
||||
],
|
||||
"分量信号接口器": [
|
||||
"用于现有视频会议专业对接高清矩阵接口器"
|
||||
],
|
||||
"高清四画面分割器": [
|
||||
"画面预览使用, 具有画中画 、独立单画面放大功能。"
|
||||
]
|
||||
},
|
||||
"发言系统": {
|
||||
"数字会议发言主机": [
|
||||
"1 、标准挂载单元数量: 4 路总线接口, 单路可连接 32 个 , 最多系统可挂载 128 个会议单元, 且最远线路长度可高达 100 米;",
|
||||
"2、主机面板彩屏显示系统菜单,通过设置可设 定 1/2/4/6 发言数量;",
|
||||
"3 、支持先入先出模式, 后入后出模式, 限制模式, 电脑/主席允许 模式, 自由讨论模式;",
|
||||
"4 、可直接控制最多三个摄像球, 完成视频会议功能;",
|
||||
"5、多种输入输出接口主输入、卡座输入和前置输出、辅助输出及录音输出接口;",
|
||||
"6 、带有 RS 232 视频控制输出 口, 可以直接输出派尔高-P, 派尔高 -D, VISCA 控制协议, 控制最大 3 个摄像机, 完成摄像自动跟踪;",
|
||||
"7 、 内置 4 切 1 视频切换器, 用于摄像机的视频接连;",
|
||||
"8 、可以响应处理话筒的会议中服务的请求;",
|
||||
"9 、 内置签到表决功能, 可以配合话筒进行签到表决;",
|
||||
"10 、 内置 DSP 自适应音频处理器,可以最大可能的抑制声回输。"
|
||||
],
|
||||
"方形短杆代表话筒": [
|
||||
"1 、超大静音开关设计;",
|
||||
"2 、会议操作系统,全新的触摸操控技术, 2.8 英寸的彩色触摸屏幕;",
|
||||
"3 、超短全金属短咪杆设计;",
|
||||
"4 、高灵敏度咪芯设计,拾音距离≥80 cm ;",
|
||||
"5 、红色雾面指示灯设计, 指示发言状态;",
|
||||
"6 、支持视像跟踪;",
|
||||
"7、配合主机, 可以实现先入先出,后入后出, 限制模式,主席允许模式, 自由讨论模式;",
|
||||
"8、话筒的身份可以自行设定,可以通过主机设置改变话筒身份,在 代表, 主席, VIP 自由切换, 让使用更灵活多样, 满足 高端需求;",
|
||||
"9 、长距离传输对音质不会有影响; 具备超强的抗手机 RF 干扰性。"
|
||||
],
|
||||
"专用连接线缆": [
|
||||
"主机与话筒专用连接线缆, 长度≥30m。"
|
||||
],
|
||||
"手持无线话筒": [
|
||||
"1 、含一台接收机, 两个无线手持话筒发射器;",
|
||||
"2 、频率响应: 50Hz-18KHz;",
|
||||
"3 、有效使用距离≥100 米;",
|
||||
"4 、信噪比≥105dB(1KHz-A);",
|
||||
"5 、灵敏度: -105dBm(12dB S/N AD)。"
|
||||
]
|
||||
},
|
||||
"视频会议系统": {
|
||||
"★多点控制器": [
|
||||
"1 、遵循 H.323 、H.320 、SIP 标准协议;",
|
||||
"2 、支持 H.265, H.264 HP, H.264 编解码标准。 3 、支持不低于 25 分屏高清多画面;",
|
||||
"4 、最大线路速率: 8M;",
|
||||
"5、视频抗丢包能力:支持高至 60%丢包率情况下 , 图像流畅无马赛 克;音频抗 IP 网络丢包能力:支持高至 75%丢包 率情况下,声音清 晰流畅; 会议抗 IP 网络丢包能力:支持高至 70%丢包率情况下,会 议仍可正常召开。 以上 5 项参数需提供第三方检测机构检验 报告。"
|
||||
],
|
||||
"★多串口控制服务器": [
|
||||
"1 、具有高速数据处理能力, 内嵌高速嵌入式 CPU ;",
|
||||
"2 、提供 16 路一控多 、多控一;",
|
||||
"3 、具有多种转发机制, 支持 IP 、串口间双向转发机制;",
|
||||
"4、控制会议矩阵、会议摄像机外围设备串口设备,实现对会议系统设备的控制;"
|
||||
],
|
||||
"★综合会议管理调度平台": [
|
||||
"1、含硬件终端和视频会议专用软件,用于控制会议、矩阵、会议摄像机,实现与省厅、随州市综合会议管理调度平台对接、融合,互联互通;",
|
||||
"2、统一调度管理平台,根据业务需要,可互为控制、互为备份;",
|
||||
"3、可以与原有的主控平台互为操作、实现控制备份,保证会议正常召开,需在设计方案中详细阐明如何实现;",
|
||||
"4、实现对会议设备的整合控制,采用一键拖拉式操作,软件界面友好、操作管理简易、直观;",
|
||||
"5、可在综合会议管理平台实现四画面预览各分会场及中心视频信号;",
|
||||
"6、提供软件著作权证书。"
|
||||
],
|
||||
"★高清会议终端(主会场)": [
|
||||
"1 、体系标准: ITU-T H.323 、H.320 、IETF SIP/专有平台协议;",
|
||||
"2 、 网 络 传 输 协 议: TCP/IP, UDP, RTP/RTC P/ RTSP, HTTP, DHCP/PPPOE/ NAT, IGMP;",
|
||||
"3 、视频编解码协议: H.265, H.264 HP, H.264 、H.2 63 、MPEG-4;",
|
||||
"4 、音频编解码协议: G.711a/G.711u 、G.722/G.723/G.7 19/AAC-LC;",
|
||||
"5 、视频码率: 128Kbps~ 16Mbps, 支持 16M( E1*8 );",
|
||||
"6 、音频码率: 24Kbps~384Kbps;",
|
||||
"7 、 内 容 分 辨 率 : 典 型 : 动 态 (HDMI/VGA) 1 080P60 , 静 态 (VGA)1600*1200@60 Up to 60fps;",
|
||||
"8 、音频指标: AEC/ANS/AGC 等;",
|
||||
"9 、音频特性: 唇音同步, 静音与哑音控制;",
|
||||
"10 、接口指标: 遥控接口 NEC 码制标准红外接口,支持遥控器直接 控制终端;",
|
||||
"11 、通信控制接 口: RJ45 × 2(RS232 x 1, RS48 5 x1), 摄像机控制 RJ45-232 接口通用路由器己置线;另预留 RS232 x 2;",
|
||||
"12 、 网 络 接 口 : LAN , RJ45 x 4 , E1 (4x E1) , CC4/miniBNC x 810/100/1000Mbps2/4/6/8Mbps;",
|
||||
"13、视频输出接口:3G-SDIx1、HDMIx1,DVI-Ix1,CVBSx1,VGAx1, DVI -I 可以配置选择 DVI / HDMI / VGA / YPrPb 任何一种接口模式输入 图像, 高标 、清接口可同时输出;",
|
||||
"14、音频接口:音频输出输出采用“凤凰接口 ”, LINE IN, MAIN (3pin) × 1,XLR IN (3pin) × 1,XLR 用于 MIC 输入时支持,+48V 幻象供 电, LINE OUT,MAIN (3pin) × 1;",
|
||||
"15 、遵循 H.323 、H.320 、SIP 协议标准;",
|
||||
"16 、视频抗丢包能力: 支持高至 60%丢包率情况下, 图像流畅 、无 卡顿、无马赛克;音频抗 IP 网络丢包能力:支 持高至 75%丢包率情 况下,声音清晰流畅; 会议抗 IP 网络丢包能力:支持高至 70%丢包 率情况下, 会议可正常召开。"
|
||||
],
|
||||
"★高清会议终端(分会场)": [
|
||||
"内置 1080P 高清摄像机, 12 倍光学变焦,72 度广角镜头,DVI-I 接口辅流输入,2 路 1080P@30fps 高清视频, 6M 速率,双 HDMI 接口输出,双屏双显、单屏双显、单屏三显、单屏四显 ,含终端控制软件。"
|
||||
],
|
||||
"65寸电视机移动推车(9楼)": [
|
||||
"1 、全钢结构, 满足 70 寸电视承重安装要求;",
|
||||
"2 、承载: 200Kg;",
|
||||
"3 、轮子带自锁刹车功能。"
|
||||
],
|
||||
"65寸液晶电视机(分会场)": [
|
||||
"1 、屏幕尺寸: 65 英寸; 含挂架及安装;",
|
||||
"2 、背光类型: LED;",
|
||||
"3 、屏幕分辨率: 超高清 4K (3840 ×2 160);",
|
||||
"4 、支持 HDR 显示;",
|
||||
"5 、CPU: Cortex A55 四核;",
|
||||
"6 、接口: USB2.0 ×2 、HDMI2.0 ×2;",
|
||||
"7 、 网络连接方式: 无线/网线。"
|
||||
],
|
||||
"控制平板及软件": [
|
||||
"10.2 寸无线触摸屏, 含控制软件, 实现远程一键式控制 、视频会议调度。"
|
||||
],
|
||||
"鹅颈话筒": [
|
||||
"1 、采样率: 48kHz;",
|
||||
"2 、频响: 20Hz – 20kHz;",
|
||||
"3 、灵敏度: 38 ±2dB;",
|
||||
"4 、拾音距离: 20-50CM;含接头 、线缆, 线缆 长度≥3.5m;",
|
||||
"5 、支持终端远程供电, 无需外接电源。"
|
||||
]
|
||||
},
|
||||
"辅助系统": {
|
||||
"时序电源": [
|
||||
"1 、具有 12 路 1KW 电源;",
|
||||
"2 、具有电压表指示, 支持串口控制;",
|
||||
"3 、采用触点闭合控制功能;",
|
||||
"4 、具有过压 、过流保护。"
|
||||
],
|
||||
"多媒体地插盒": [
|
||||
"1 、具有至少 1 路 HDMI 、 1 路电源 、2 路网络接口模块;",
|
||||
"2 、采用优质接插件。"
|
||||
],
|
||||
"线材辅料": [
|
||||
"采用专用线材、材料、接口、各种辅料等。"
|
||||
],
|
||||
"墙体拆除及修复": [
|
||||
"对大屏安装区域墙体 、天花进行拆除及修复。"
|
||||
]
|
||||
},
|
||||
"货物列表": [
|
||||
"★LED全彩显示屏",
|
||||
"控制盒及电源",
|
||||
"大屏播控系统",
|
||||
"配电柜(含PLC)",
|
||||
"钢结构底座及铝型材支架",
|
||||
"电缆及信号线缆",
|
||||
"控制终端",
|
||||
"50寸液晶电视机",
|
||||
"50寸电视机地面推车",
|
||||
"高清监视器",
|
||||
"★高清摄像机",
|
||||
"摄像机三角架",
|
||||
"摄像机壁装架",
|
||||
"摄像机控制键盘",
|
||||
"★高清视频拼控矩阵(16*16)",
|
||||
"分量信号接口器",
|
||||
"高清四画面分割器",
|
||||
"数字会议发言主机",
|
||||
"方形短杆代表话筒",
|
||||
"专用连接线缆",
|
||||
"手持无线话筒",
|
||||
"★多点控制器",
|
||||
"★多串口控制服务器",
|
||||
"★综合会议管理调度平台",
|
||||
"★高清会议终端(主会场)",
|
||||
"★高清会议终端(分会场)",
|
||||
"65寸电视机移动推车(9楼)",
|
||||
"65寸液晶电视机(分会场)",
|
||||
"控制平板及软件",
|
||||
"鹅颈话筒",
|
||||
"时序电源",
|
||||
"多媒体地插盒",
|
||||
"线材辅料",
|
||||
"墙体拆除及修复"
|
||||
]
|
||||
}
|
||||
}
|
@ -65,14 +65,14 @@ def generate_key_paths(data):
|
||||
# 编译用于匹配后缀的正则表达式模式
|
||||
pattern = re.compile(r'(.+)-\d+$')
|
||||
|
||||
# 初始化结果列表
|
||||
# 初始化结果列表和字典
|
||||
key_paths = []
|
||||
grouped_set = set() # 使用集合来避免重复
|
||||
grouped_counts = defaultdict(int) # 用于记录每个 grouped_path 的数量
|
||||
good_list = []
|
||||
|
||||
def recurse(current_dict, path):
|
||||
"""
|
||||
递归遍历字典,处理 key_paths 和 grouped_paths,并收集 good_list。
|
||||
递归遍历字典,处理 key_paths 和 grouped_paths,并收集 good_list 和 grouped_counts。
|
||||
|
||||
参数:
|
||||
current_dict (dict): 当前遍历的字典。
|
||||
@ -109,8 +109,9 @@ def generate_key_paths(data):
|
||||
# 继续递归处理
|
||||
recurse(value, path + [base])
|
||||
else:
|
||||
# 记录分组路径,确保唯一性
|
||||
grouped_set.add('.'.join(path + [base]))
|
||||
# 记录分组路径,并统计数量
|
||||
grouped_path = '.'.join(path + [base])
|
||||
grouped_counts[grouped_path] += 1
|
||||
|
||||
# 执行键名的重命名,同时保持原有顺序
|
||||
if keys_to_rename:
|
||||
@ -141,16 +142,6 @@ def generate_key_paths(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)
|
||||
@ -159,7 +150,7 @@ def generate_key_paths(data):
|
||||
else:
|
||||
base = key
|
||||
current_path = '.'.join(path + [base])
|
||||
if current_path in grouped_set and current_path not in collected:
|
||||
if current_path in grouped_counts and current_path not in collected:
|
||||
collected.append(current_path)
|
||||
value = current_dict[key]
|
||||
if isinstance(value, dict):
|
||||
@ -167,7 +158,9 @@ def generate_key_paths(data):
|
||||
|
||||
collected_grouped_paths = []
|
||||
collect_grouped_paths(data_copy, [], collected_grouped_paths)
|
||||
grouped_paths = collected_grouped_paths
|
||||
|
||||
# 将 grouped_paths 转换为包含数量的字典列表
|
||||
grouped_paths = [{path: grouped_counts[path]} for path in collected_grouped_paths]
|
||||
|
||||
return key_paths, grouped_paths, good_list, data_copy
|
||||
def rename_keys(data):
|
||||
@ -177,7 +170,7 @@ def rename_keys(data):
|
||||
|
||||
def rename_keys_recursive(current_dict):
|
||||
"""
|
||||
递归地重命名字典中的键,确保同一层级下具有相同基名的键被正确编号。
|
||||
递归地重名字典中的键,确保同一层级下具有相同基名的键被正确编号。
|
||||
"""
|
||||
if not isinstance(current_dict, dict):
|
||||
return current_dict
|
||||
@ -222,13 +215,15 @@ def rename_keys(data):
|
||||
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
|
||||
|
||||
# 对整个数据结构进行递归重命名
|
||||
return rename_keys_recursive(data)
|
||||
|
||||
|
||||
def combine_and_update_results(original_data, updates):
|
||||
"""
|
||||
先规范化original和updates中的字典,防止空格的情况导致匹配不上无法更新
|
||||
"""
|
||||
def normalize_key(key):
|
||||
"""
|
||||
规范化键名:
|
||||
@ -364,6 +359,9 @@ def get_technical_requirements(file_path,invalid_path,processed_filepath):
|
||||
5. 系统功能:若采购的某系统提及总体系统功能,则在系统值中添加'系统功能'二级键,不展开具体内容。
|
||||
6. 完整性:确保不遗漏系统内的货物,也不添加未提及的内容。若'采购清单'中未提取的货物(或系统)名称在形如'主要设备功能指标'的标题下有详细参数指标要求,请将该货物名也添加至返回中。
|
||||
|
||||
特殊情况:
|
||||
若采购的货物或系统或模块名称前存在三角▲、五角★,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲或★符号,如'★高清摄像机'。
|
||||
|
||||
输出格式:
|
||||
1.JSON格式,最外层键名为'采购需求'。
|
||||
2.层次关系用嵌套键值对表示。
|
||||
@ -428,8 +426,8 @@ def get_technical_requirements(file_path,invalid_path,processed_filepath):
|
||||
processed_data=truncate_system_keys(cleaned_res['采购需求'])
|
||||
key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 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像素"。
|
||||
@ -454,8 +452,8 @@ def get_technical_requirements(file_path,invalid_path,processed_filepath):
|
||||
]
|
||||
}}
|
||||
"""
|
||||
user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在多种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增添内容。
|
||||
请注意以下特殊情况:
|
||||
user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
|
||||
|
||||
要求与指南:
|
||||
1. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表。
|
||||
2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。
|
||||
@ -498,14 +496,17 @@ def get_technical_requirements(file_path,invalid_path,processed_filepath):
|
||||
queries.append(new_query)
|
||||
|
||||
# 处理 grouped_paths 中的项,应用 user_query_template_two
|
||||
for grouped_key in grouped_paths:
|
||||
# 将键中的 '.' 替换为 '下的'
|
||||
modified_grouped_key = grouped_key.replace('.', '下的')
|
||||
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
|
||||
# full_text = read_txt_to_string(processed_filepath)
|
||||
# new_query = user_query_template_two.format(modified_grouped_key, grouped_key, modified_grouped_key, full_text)
|
||||
new_query = user_query_template_two.format(modified_grouped_key, grouped_key, modified_grouped_key)
|
||||
queries.append(new_query)
|
||||
for grouped_dict in grouped_paths:
|
||||
for grouped_key, grouped_key_cnt in grouped_dict.items():
|
||||
# 将键中的 '.' 替换为 '下的'
|
||||
modified_grouped_key = grouped_key.replace('.', '下的')
|
||||
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
|
||||
# 如果需要使用 full_text,可以取消注释并提供相应的实现
|
||||
# full_text = read_txt_to_string(processed_filepath)
|
||||
# new_query = user_query_template_two.format(modified_grouped_key, grouped_key, modified_grouped_key, full_text)
|
||||
# 根据您的需求,生成新的查询字符串
|
||||
new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt,grouped_key, modified_grouped_key)
|
||||
queries.append(new_query)
|
||||
results = multi_threading(queries, "", file_id, 2) #通义
|
||||
# results = multi_threading(queries, "", "", 3) #豆包
|
||||
technical_requirements = []
|
||||
@ -553,8 +554,8 @@ def test_all_files_in_folder(input_folder, output_folder):
|
||||
if __name__ == "__main__":
|
||||
start_time=time.time()
|
||||
# truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx"
|
||||
truncate_docfile=r"C:\Users\Administrator\Desktop\货物标\output1\招标文件(107国道)_procurement.docx"
|
||||
truncate_file=r'C:\Users\Administrator\Desktop\fsdownload\e702f1e6-095d-443d-bb7d-ef2e42037cb1\ztbfile_procurement.pdf'
|
||||
truncate_docfile=r"C:\Users\Administrator\Desktop\货物标\output1\6_2定版视频会议磋商文件_procurement.docx"
|
||||
truncate_file=r'C:\Users\Administrator\Desktop\货物标\output1\6.2定版视频会议磋商文件_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"
|
||||
|
@ -133,6 +133,14 @@ def restructure_data(data):
|
||||
- 如果所有顶层键的值都是列表(两层结构),直接返回原数据。
|
||||
- 如果存在混合的两层和三层结构,或更深层级,则将所有数据统一为三层结构。
|
||||
"""
|
||||
def get_max_depth(d, current_depth=1):
|
||||
"""
|
||||
计算字典的最大嵌套深度。
|
||||
"""
|
||||
if not isinstance(d, dict) or not d:
|
||||
return current_depth
|
||||
return max(get_max_depth(v, current_depth + 1) for v in d.values())
|
||||
|
||||
# 检查是否所有顶层键的值都是列表(即两层结构)
|
||||
all_two_layers = all(isinstance(value, list) for value in data.values())
|
||||
|
||||
@ -150,7 +158,7 @@ def restructure_data(data):
|
||||
# 如果存在更深层级,展开至三层
|
||||
for sub_key, sub_value in value.items():
|
||||
if isinstance(sub_value, dict):
|
||||
# 这里假设需要展开到三层,将其保留为三层
|
||||
# 保留为三层
|
||||
structured_data[sub_key] = sub_value
|
||||
elif isinstance(sub_value, list):
|
||||
# 将两层结构转换为三层结构
|
||||
@ -165,7 +173,14 @@ def restructure_data(data):
|
||||
structured_data[key] = {key: value}
|
||||
else:
|
||||
raise ValueError(f"键'{key}'的数据格式异常: {type(value)}")
|
||||
return structured_data
|
||||
|
||||
# 检查重构后的数据深度
|
||||
max_depth = get_max_depth(structured_data)
|
||||
if max_depth > 3:
|
||||
# 递归调用以进一步重构
|
||||
return restructure_data(structured_data)
|
||||
else:
|
||||
return structured_data
|
||||
|
||||
|
||||
# 定义获取所有以':'结尾的前缀的函数
|
||||
@ -202,7 +217,7 @@ def remove_common_prefixes(string_list):
|
||||
else:
|
||||
new_string_list.append(s)
|
||||
return new_string_list
|
||||
#TODO:目前对于超过四层的数据,无法平坦化为3层
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 示例数据
|
||||
sample_data = {
|
||||
|
@ -62,7 +62,6 @@ def fetch_procurement_reqs(procurement_path, invalid_path):
|
||||
|
||||
|
||||
# TODO:技术要求可以在技术参数之后执行,把完整的技术参数输入,问大模型,除了上述内容还有哪些,这样的话把技术标和其他的区分开。
|
||||
# TODO:0362
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_time = time.time()
|
||||
|
Loading…
x
Reference in New Issue
Block a user