更新代码

This commit is contained in:
chentianrui
2025-10-14 16:13:18 +08:00
parent f5f26c5cf8
commit 0a4dedda1c
230 changed files with 7029 additions and 855114 deletions
+224 -6
View File
@@ -47,6 +47,8 @@ def _determine_project_type(data):
PROJECT_INFO_ADDITIONS = {
# 主网:如需新增字段,请在此处补充,示例:"示例字段": "" 或默认值
"主网": {
"阶段类型": "",
"是否结算量差工程": "",
# 在此处按需添加主网专属字段,例如:
# "主网示例字段": ""
},
@@ -58,6 +60,15 @@ PROJECT_INFO_ADDITIONS = {
"技改": {
"建筑材机按系数调差": "",
"建筑修缮材机按系数调差": "",
"建筑拆除材机按系数调差": "",
"建筑拆除人工调差系数": "",
"安装拆除机械调差系数": "",
"安装拆除人工调差系数": "",
"安装拆除材料调差系数": "",
"安装人工调差系数": "",
"安装材料调差系数": "",
"安装机械调差系数": "",
"建筑人工调差系数": "",
},
}
@@ -91,6 +102,141 @@ def add_project_info_fields(data):
project_info[k] = v
def add_adjustment_type_to_engineering_nodes(data):
"""
为工程量节点(定额、主材、设备)新增属性字段 "调差类型"
判定规则基于 projectData.projectDivision 的上级分类与子分类:
- 上级分类:"建筑工程" => 前缀为 "建筑""安装工程" => 前缀为 "安装"
- 子分类:
- "拆除" => 后缀 "拆除"(例:建筑拆除、安装拆除)
- "建筑""安装" => 无后缀(例:建筑、安装)
- "清理项目" => 后缀 "清理"(例:建筑清理、安装清理)
不覆盖已有的 "调差类型",仅在缺失时补充。
"""
try:
project_data = data.get("projectData", {})
pd = project_data.get("projectDivision")
if not isinstance(pd, dict):
return
# 映射:上级分类 -> 前缀
base_prefix_map = {"建筑工程": "建筑", "安装工程": "安装"}
# 映射:子分类 -> 后缀
sub_suffix_map = {"拆除": "拆除", "建筑": "", "安装": "", "清理项目": ""}
# 判断是否是工程量节点(定额、主材、设备)
def is_engineering_node(obj: dict) -> bool:
if not isinstance(obj, dict):
return False
t = obj.get("type")
if t in ("定额", "主材", "设备"):
return True
t2 = obj.get("类型")
if t2 in ("定额", "主材", "设备"):
return True
# 数字编码兼容(0:定额,1:主材,5:设备)
if str(t2) in ("0", "1", "5"):
return True
return False
# 递归遍历,携带当前上级分类前缀和子分类键名
def traverse(node, parent_key=None, base_prefix=None, sub_key=None):
# 更新当前上下文
if parent_key in base_prefix_map:
base_prefix = base_prefix_map[parent_key]
# 子分类只关心我们映射表里的几个键
if parent_key in sub_suffix_map:
sub_key = parent_key
if isinstance(node, dict):
# 命中工程量节点则补充“调差类型”
if is_engineering_node(node) and base_prefix:
if "调差类型" not in node:
suffix = sub_suffix_map.get(sub_key, None)
if suffix is None:
# 未识别子分类时,不写入,保持安全
pass
else:
# 特殊规则:拆除类需要前缀为“拆除”+ 基础前缀,如“拆除建筑/拆除安装”
if suffix == "拆除":
node["调差类型"] = "拆除" + base_prefix
else:
node["调差类型"] = base_prefix + (suffix or "")
for k, v in list(node.items()):
traverse(v, k, base_prefix, sub_key)
elif isinstance(node, list):
for item in node:
traverse(item, parent_key, base_prefix, sub_key)
# 从 projectDivision 根开始遍历
traverse(pd)
except Exception:
# 保守失败,不影响主流程
pass
def _fix_split_flag_without_children(root_node):
"""
遍历节点树,将属性中存在 "拆分": "1"(或数值1)且不包含 "children" 键的节点,修正为 "拆分": "0"
若存在 "children" 键,则不变(无论 children 是否为空均视为存在)。
"""
def _recurse(node):
if isinstance(node, dict):
try:
if "拆分" in node and "children" not in node:
val = node.get("拆分")
if str(val) == "1":
node["拆分"] = "0"
except Exception:
pass
# 递归子项
for _, v in list(node.items()):
_recurse(v)
elif isinstance(node, list):
for item in node:
_recurse(item)
try:
_recurse(root_node)
except Exception:
pass
def _normalize_project_division_guid_keys(project_division_root):
"""
仅在 projectDivision 下,将任意节点属性中的键 "guid" 规范化为 "GUID"
- 若同时存在 "guid""GUID",不做任何修改;
- 若只存在 "guid",则改名为 "GUID"(保留原值)。
- 仅处理 projectDivision 这颗子树,不影响其他位置。
"""
def _recurse(node):
if isinstance(node, dict):
if "guid" in node and "GUID" not in node:
try:
node["GUID"] = node["guid"]
del node["guid"]
except Exception:
# 安全回退:若修改失败则忽略该键
pass
# 继续递归所有子项
for k, v in list(node.items()):
_recurse(v)
elif isinstance(node, list):
for item in node:
_recurse(item)
try:
_recurse(project_division_root)
except Exception:
# 保守失败,不影响主流程
pass
def transform_expense_preview(input_file, output_file):
"""
转换技改预算线路.json中的expensePreview结构,使其与主网预算线路.json中的结构一致
@@ -141,7 +287,9 @@ def transform_expense_preview(input_file, output_file):
if cleaned_project_division != project_division:
print("已根据 '删除' 标记清理 projectDivision 中的节点")
project_division = cleaned_project_division
# 回写清理后的结构,确保后续流程与落盘一致
# 在 projectDivision 中规范化 guid->GUID 键(若无 GUID 才改名)
_normalize_project_division_guid_keys(project_division)
# 回写清理与规范化后的结构,确保后续流程与落盘一致
if "projectData" in data:
data["projectData"]["projectDivision"] = project_division
@@ -292,9 +440,19 @@ def transform_expense_preview(input_file, output_file):
# 更新data中的expensePreview
data["projectData"]["expensePreview"] = new_expense_preview
# 清洗:修正没有 children 却标记为 "拆分": "1" 的节点
try:
_fix_split_flag_without_children(data.get("projectData", {}).get("projectDivision", {}))
_fix_split_flag_without_children(data.get("projectData", {}).get("expensePreview", {}))
except Exception:
pass
# 新增:按工程类型为 projectInfo 补充字段
add_project_info_fields(data)
# 新增:为工程量节点补充“调差类型”
add_adjustment_type_to_engineering_nodes(data)
# 保存转换后的文件
print(f"正在保存文件: {output_file}")
with open(output_file, "w", encoding="utf-8") as f:
@@ -554,7 +712,16 @@ def transform_json_types(input_file_path, output_file_path=None):
dict: 转换后的JSON数据
"""
# 定义类型映射关系
type_mapping = {"8": "清单", "0": "定额", "1": "主材", "5": "设备", "2": "人工", "3": "材料", "4": "机械"}
type_mapping = {
"8": "清单",
"0": "定额",
"1": "主材",
"5": "设备",
"2": "人工",
"3": "材料",
"4": "机械",
"16": "一笔性费用",
}
# 定义设备类型映射关系
device_type_mapping = {"0": "普通设备"}
@@ -594,9 +761,12 @@ def transform_json_types(input_file_path, output_file_path=None):
try:
pd = data.get("projectData", {}).get("projectDivision", {})
cleaned_pd = _filter_deleted_nodes(pd) or {}
if cleaned_pd != pd and "projectData" in data:
data["projectData"]["projectDivision"] = cleaned_pd
if cleaned_pd != pd:
print("[主网] 已根据 '删除' 标记清理 projectDivision 中的节点")
# 在 projectDivision 中规范化 guid->GUID 键(若无 GUID 才改名)
_normalize_project_division_guid_keys(cleaned_pd)
if "projectData" in data:
data["projectData"]["projectDivision"] = cleaned_pd
except Exception:
pass
@@ -634,7 +804,45 @@ def transform_json_types(input_file_path, output_file_path=None):
# 若节点存在“类型”但没有“type”,则补充一个“type”属性,其值等于当前“类型”的值
if "类型" in obj and "type" not in obj:
obj["type"] = obj["类型"]
if obj["类型"] == "材料":
obj["type"] = "消材"
else:
obj["type"] = obj["类型"]
# 转换“定额范围”字段:1 -> 预算,0 -> 概算
if "定额范围" in obj:
try:
scope_val = str(obj["定额范围"]).strip()
if scope_val == "1":
obj["定额范围"] = "预算"
elif scope_val == "0":
obj["定额范围"] = "概算"
except Exception:
pass
if "脚手架计取" in obj:
try:
scope_val = str(obj["脚手架计取"]).strip()
if scope_val == "1":
obj["脚手架计取"] = "计取"
elif scope_val == "0":
obj["脚手架计取"] = "不计取"
except Exception:
pass
# 工程量节点的“特征段”字段规范化,例如“特征1”或“特征段1”-> 1
try:
if "特征段" in obj and isinstance(obj.get("特征段"), str):
val = obj.get("特征段", "").strip()
# 匹配以“特征”或“特征段”开头并带有数字的形式
m = re.match(r"^\s*特征(?:段)?\s*(\d+)\s*$", val)
if m:
# 统一以字符串形式写回,确保JSON中带双引号
obj["特征段"] = m.group(1)
except Exception:
# 保守失败,不中断整体转换
pass
# 递归处理所有值
for value in obj.values():
@@ -646,9 +854,19 @@ def transform_json_types(input_file_path, output_file_path=None):
# 执行转换
traverse(data)
# 清洗:修正没有 children 却标记为 "拆分": "1" 的节点(仅限关键树)
try:
_fix_split_flag_without_children(data.get("projectData", {}).get("projectDivision", {}))
_fix_split_flag_without_children(data.get("projectData", {}).get("expensePreview", {}))
except Exception:
pass
# 新增:按工程类型为 projectInfo 补充字段
add_project_info_fields(data)
# 新增:为工程量节点补充“调差类型”
add_adjustment_type_to_engineering_nodes(data)
# 确定输出路径
if output_file_path is None:
output_file_path = input_file_path
@@ -798,5 +1016,5 @@ if __name__ == "__main__":
# traceback.print_exc()
# 批量处理目录
json_directory = "data/output/json"
json_directory = "data/input/json"
process_directory(json_directory)