zbparse/flask_app/货物标/技术参数要求提取后处理函数.py

303 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

import json
import re
from collections import defaultdict
def postprocess_technical_table(data, good_list, special_keys=None, parent_key=''):
"""
传输技术参数需求的时候后处理输入的data为经过main_postprocess处理的采购需求数据
"""
def get_suffix(n):
"""
根据数字n返回对应的字母后缀。
1 -> 'a', 2 -> 'b', ..., 26 -> 'z', 27 -> 'aa', 28 -> 'ab', ...
"""
suffix = ''
while n > 0:
n, r = divmod(n - 1, 26)
suffix = chr(97 + r) + suffix
return suffix
def count_matching_keys(data, patterns, special_keys, key_value_map=None):
"""
递归统计匹配键的出现次数及其对应的唯一值,仅统计值为列表的键。
对于每个键,会先去除键名中的空格,如果这个清理后的键不在 special_keys 列表中且满足至少一个patterns就把对应的值列表转换为元组添加到 key_value_map 字典中。
注意:同一个键对应的相同值(转换成元组后)只会保存一次,保证唯一性。
返回结构:{
'key1': [tuple_value1, tuple_value2, ...],
'key2': [tuple_value1, ...],
}
"""
if key_value_map is None:
key_value_map = defaultdict(list)
if isinstance(data, dict):
for key, value in data.items():
clean_key = key.replace(" ", "") # 去除键中的空格
if isinstance(value, list):
if clean_key not in special_keys and any(pattern.match(clean_key) for pattern in patterns):
value_tuple = tuple(value)
if value_tuple not in key_value_map[clean_key]: #每个键下的值若一样,则只保留一个
key_value_map[clean_key].append(value_tuple)
elif isinstance(value, dict):
count_matching_keys(value, patterns, special_keys, key_value_map)
elif isinstance(data, list):
for item in data:
if isinstance(item, (dict, list)):
count_matching_keys(item, patterns, special_keys, key_value_map)
return key_value_map
def assign_suffixes(key_value_map):
"""
如果该键只有一个唯一值,则不添加后缀-a -b..
如果有多个唯一值,则给第一个值不添加后缀,后续的值按照顺序分别添加 -a、-b、-c
返回一个字典,键为原键名,值为另一个字典,键为值元组,值为对应的后缀(如果需要)。
"""
suffix_assignment = defaultdict(dict)
for key, values in key_value_map.items():
if len(values) == 1:
suffix_assignment[key][values[0]] = '' # 只有一个唯一值,不需要后缀
else:
for idx, val in enumerate(values, start=1):
if idx == 1:
suffix = '' # 第一个唯一值不添加后缀
else:
suffix = '-' + get_suffix(idx - 1) # 从 '-a' 开始
suffix_assignment[key][val] = suffix
return suffix_assignment
def process_data(data, patterns, special_keys, suffix_assignment, filtered_data, parent_key):
"""递归处理数据并构建结果"""
if isinstance(data, dict):
for key, value in data.items():
clean_key = key.replace(" ", "") # 去除键中的空格
current_parent_key = clean_key if parent_key == '' else f"{parent_key}{clean_key}"
if isinstance(value, list):
if clean_key in special_keys:
# 处理 special_keys前缀父键路径
new_key = current_parent_key
filtered_data[new_key] = value
elif any(pattern.match(clean_key) for pattern in patterns):
# 处理普通匹配键
# 检查是否以特殊符号开头
if clean_key.startswith(('', '','','','','','','','','','#')): #货物名以特殊符号开头->移至键值(技术参数)开头
#提取符号并去除符号:
symbol = clean_key[0]
stripped_key = clean_key[1:]
value_tuple = tuple(value)
suffix = suffix_assignment.get(stripped_key, {}).get(value_tuple, '')
if suffix:
new_key = f"{stripped_key}{suffix}"
else:
new_key = stripped_key
# 将符号添加到每个字符串的开头
new_value = [symbol + item for item in value]
filtered_data[new_key] = new_value
else:
# 获取当前值的后缀
value_tuple = tuple(value)
suffix = suffix_assignment.get(clean_key, {}).get(value_tuple, '')
if suffix:
new_key = f"{clean_key}{suffix}"
else:
new_key = clean_key
filtered_data[new_key] = value
elif isinstance(value, dict):
# 继续递归处理嵌套字典
process_data(value, patterns, special_keys, suffix_assignment, filtered_data, current_parent_key)
elif isinstance(data, list):
for item in data:
if isinstance(item, (dict, list)):
process_data(item, patterns, special_keys, suffix_assignment, filtered_data, parent_key)
def generate_patterns(good_list):
"""生成匹配的正则表达式列表"""
return [re.compile(r'^' + re.escape(g) + r'(?:-\d+)?$') for g in good_list]
if special_keys is None:
special_keys = ["系统功能"] # 默认值为 ["系统功能"]
# 去除 good_list 中的空格
clean_good_list = [g.replace(" ", "") for g in good_list]
# 构建匹配的正则表达式
patterns = generate_patterns(clean_good_list)
# 先统计所有匹配键的出现次数及其对应的唯一值,仅统计值为列表的键
key_value_map = count_matching_keys(data, patterns, special_keys)
# 为每个键的唯一值分配后缀
suffix_assignment = assign_suffixes(key_value_map)
# 用于存储最终结果
filtered_data = {}
# 递归处理数据
process_data(data, patterns, special_keys, suffix_assignment, filtered_data, parent_key)
return filtered_data
def main_postprocess(data):
#解析采购要求时候的后处理,用于前端网页展示。
def recursive_process(item):
pattern = re.compile(r'(.+)-\d+$')
if isinstance(item, dict):
cleaned_dict = {
key: recursive_process(value)
for key, value in item.items()
if not (pattern.match(key) and value == []) #删除键名匹配 pattern = re.compile(r'(.+)-\d+$') 且其键值为 [] 的键值对
}
return cleaned_dict
elif isinstance(item, list):
return remove_common_prefixes(item)
else:
return item
temp = restructure_data(data)
processed_data = recursive_process(temp) #重构数据以标准化嵌套层级至三层,便于前端展示。
return processed_data
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())
if all_two_layers:
# 所有数据都是两层结构,直接返回
return data
# 否则,存在混合或更深层级,需要重构为三层结构
structured_data = {}
for key, value in data.items():
if isinstance(value, dict):
# 检查是否有子值是字典(即原始深度 >=4
has_deeper = any(isinstance(sub_value, dict) for sub_value in value.values())
if has_deeper:
# 如果存在更深层级,展开至三层
for sub_key, sub_value in value.items():
if isinstance(sub_value, dict):
# 保留为三层
structured_data[sub_key] = sub_value
elif isinstance(sub_value, list):
# 将两层结构转换为三层结构
structured_data[sub_key] = {sub_key: sub_value}
else:
raise ValueError(f"'{sub_key}'的数据格式异常: {type(sub_value)}")
else:
# 已经是三层结构,保持不变
structured_data[key] = value
elif isinstance(value, list):
# 将两层结构转换为三层结构
structured_data[key] = {key: value}
else:
raise ValueError(f"'{key}'的数据格式异常: {type(value)}")
# 检查重构后的数据深度
max_depth = get_max_depth(structured_data)
if max_depth > 3:
# 递归调用以进一步重构
return restructure_data(structured_data)
else:
return structured_data
# 定义删除公共前缀的函数
def remove_common_prefixes(string_list, min_occurrence=3):
"""
删除列表中所有满足出现次数>= min_occurrence 的公共前缀。
Args:
string_list (list): 字符串列表。
min_occurrence (int): 前缀至少出现的次数。
Returns:
list: 删除公共前缀后的字符串列表。
"""
# 定义获取所有以''结尾的前缀的函数
def get_prefixes(s):
prefixes = []
for i in range(len(s)):
if s[i] in ['', ':']:
prefixes.append(s[:i + 1])
return prefixes
if not string_list:
return string_list
# 构建前缀到字符串集合的映射
prefix_to_strings = {}
for s in string_list:
prefixes = get_prefixes(s)
unique_prefixes = set(prefixes)
for prefix in unique_prefixes:
if prefix not in prefix_to_strings:
prefix_to_strings[prefix] = set()
prefix_to_strings[prefix].add(s)
# 找出所有出现次数 >= min_occurrence 的前缀
qualifying_prefixes = [prefix for prefix, strings in prefix_to_strings.items() if len(strings) >= min_occurrence]
if not qualifying_prefixes:
# 没有满足条件的公共前缀,返回原列表
return string_list
# 为了确保较长的前缀先被匹配,按长度降序排序
qualifying_prefixes.sort(key=len, reverse=True)
# 对每个字符串,循环删除所有匹配的前缀
new_string_list = []
for s in string_list:
original_s = s
changed = True
while changed:
changed = False
for prefix in qualifying_prefixes:
if s.startswith(prefix):
s = s[len(prefix):]
changed = True
# 一旦删除一个前缀,重新开始检查,以处理可能的多个前缀
break
new_string_list.append(s)
return new_string_list
if __name__ == "__main__":
# 示例数据
sample_data = {
"★交通信号机": [
"应采用区域控制信号机,并应与广水市交通信号控制系统兼容,信号机能接入已有系统平台,实现联网优化功能。",
"1、控制功能1区域协调控制可对单个孤立交叉口、干道多个交叉口和关联性较强的交叉口群进行综合性地信号控制。",
"1、控制功能2线性协调控制可对干道多个相邻交叉口进行协调控制。",
"1、控制功能3多时段控制可根据交叉口的交通状况将每天划分为多个不同的时段每个时段配置不同的控制方案能设置至少 10个时段、10种以上不同控制方案能根据不同周日类型对方案进行调整。信号机能够根据内置时钟选择各个时段的控制方案实现交叉口的合理控制。",
"2、采集功能1信号机支持接入线圈、地磁、视频、微波、超声波检测器、RFID等多种检测方式。",
"2、采集功能2信号机支持交通信息采集与统计,并支持交通流量共享。",
"3、运维功能1信号机能够自动检测地磁故障若故障能够自动上传故障信息至监控中心。"
],
"▲高清视频抓拍像机": [
"1摄像机有效像素≥900W像素",
"1摄像机最低照度彩色≤0.001lx",
"1摄像机传感器类型≥1英寸全局曝光 COMS/GMOS/GS COMS",
"1摄像机电子快门至少满足 1/25s至 1/100,000s可调",
"2视频图像视频压缩标准至少支持 H264、H265等",
"2视频图像视频分辨率≥4096×2160向下可设置",
],
}
# 处理数据
result = main_postprocess(sample_data)
# 输出处理结果
print(json.dumps(result,ensure_ascii=False,indent=4))