Files
KG_generation/equipment_calculation/resource_fee_calculator.py
T
2025-08-01 17:37:40 +08:00

1075 lines
43 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 os
import math
from typing import Dict, List, Any, Tuple
from copy import deepcopy
from equipment_calculation.item_acquisition import get_quantity_nodes, get_classified_resource_nodes, load_project_data
# 修改后的代码:先将字典转换为对象
from equipment_calculation.bcl_utils import create_node_from_type, create_material_or_equipment_from_node
from equipment_calculation.bcl_utils import (
ZjMaterialOrEquipmentBCLContext,
ZjProjectBCLContext,
BCLVariant,
ZjBillBCLContext,
init_bcl_calculator,
create_project_contexts,
calculator,
)
from equipment_calculation.bcl_utils import BCLDataSourceItem, BCLDataSourceContext
from equipment_calculation.item_acquisition import get_quantity_nodes, get_classified_resource_nodes
# 人材机节点定义合并条件常量
LABOR_MERGE_CONDITIONS = [
"编码",
"名称",
"单位",
"预算价含税",
"预算价不含税",
"市场价不含税",
"市场价含税",
"调差类型",
"专业属性",
"供货方",
"物料类材料",
]
MATERIAL_MERGE_CONDITIONS = [
"类型",
"编码",
"名称",
"单位",
"预算价不含税",
"预算价含税",
"市场价不含税",
"市场价含税",
"调差类型",
"专业属性",
"供货方",
"集中配送",
"卸车",
"保管",
"物料类材料",
]
MACHINE_MERGE_CONDITIONS = [
"编码",
"名称",
"单位",
"预算价不含税",
"预算价含税",
"市场价不含税",
"市场价含税",
"调差类型",
"专业属性",
"供货方",
"物料类材料",
]
def process_DXdata(json_data):
"""
处理 projectData 中的线路特征段数据,计算每条 bpBillZhXsTable 记录的加权值,
并返回一个字典列表,每个字典包含 '特征段' 和对应的 ItemName: 计算结果。
:param json_data: 解析后的 JSON 数据(字典格式)
:return: 字典列表,例如 [{"特征段": "1", "工地运输...": "21.8"}, ...]
"""
result_list = []
# 遍历每个线路特征段
for segment in json_data.get("projectData", {}).get("线路特征段", []):
seg_name = segment.get("Name", "")
# 提取特征段中的数字,例如 "特征段1" -> "1"
seg_number = "".join(filter(str.isdigit, seg_name))
if not seg_number:
seg_number = "0" # 默认值
# 获取 bpBillZhXsTable 列表
table_list = segment.get("bpBillZhXsTable", [])
for item in table_list:
try:
# 提取字段并转换为浮点数
KnapScale = float(item.get("KnapScale", 0))
Knap = float(item.get("Knap", 0))
HillScale = float(item.get("HillScale", 0))
Hill = float(item.get("Hill", 0))
EdelweissScale = float(item.get("EdelweissScale", 0))
edelweiss = float(item.get("edelweiss", 0))
MountainScale = float(item.get("MountainScale", 0))
Mountain = float(item.get("Mountain", 0))
SloughScale = float(item.get("SloughScale", 0))
Slough = float(item.get("Slough", 0))
RiverScale = float(item.get("RiverScale", 0))
River = float(item.get("River", 0))
DesertScale = float(item.get("DesertScale", 0))
Desert = float(item.get("Desert", 0))
# 执行计算
total = (
(KnapScale * Knap)
+ (HillScale * Hill)
+ (EdelweissScale * edelweiss)
+ (MountainScale * Mountain)
+ (SloughScale * Slough)
+ (RiverScale * River)
+ (DesertScale * Desert)
) * 0.01
# 保留一位小数,格式化为字符串
calculated_value = f"{total:.1f}"
# 获取 ItemName
item_name = item.get("ItemName", "未知项目")
# 构建结果字典
item_dict = {"特征段": seg_number, item_name: calculated_value}
# 添加到结果列表
result_list.append(item_dict)
except (ValueError, TypeError) as e:
print(f"数据转换错误,跳过该项: {e}")
continue
return result_list
# 在 resource_fee_calculator.py 文件中修改 calc_rcj_count 函数中的相关代码
def calc_rcj_count(
rcj_nodes: List[Tuple[Dict[str, Any], str]], project_children: List[Dict[str, Any]], json_file_path: str = None
) -> List[Dict[str, Any]]:
"""
计算人材机节点的数量,考虑父级消耗量
Args:
rcj_nodes: 人材机节点列表,每个元素是(节点, 父级ID)元组
project_children: 项目划分级别下的所有工程量节点
json_file_path: JSON文件路径,用于获取工程信息
Returns:
List[Dict[str, Any]]: 计算数量后的人材机节点列表
"""
result_nodes = []
# 检查rcj_nodes是否为空
if not rcj_nodes:
print("没有找到人材机节点")
return result_nodes
# 检查project_children是否为None
if project_children is None:
print("没有找到工程量节点,使用默认数量")
# 使用默认数量处理所有人材机节点
for node, parent_id in rcj_nodes:
node_copy = deepcopy(node)
node_copy["计算数量"] = node_copy.get("数量", "1.0")
# 保存父级ID
node_copy["parent_id"] = parent_id
# 由于没有找到父级节点,使用默认名称
node_copy["parent_name"] = "未知工程量节点"
result_nodes.append(node_copy)
return result_nodes
# 创建父级ID到工程量节点的映射
parent_nodes = {}
for node in project_children:
# 如果节点没有id,使用节点的名称作为id
node_id = node.get("id")
if not node_id:
# 使用节点名称或其他唯一标识符作为ID
node_id = node.get("项目名称", node.get("name", ""))
# 如果还是没有,使用内存地址作为唯一标识符
if not node_id:
node_id = f"node_{id(node)}"
# 将生成的ID添加到节点中
node["id"] = node_id
print(f"为工程量节点 '{node.get('项目名称', node.get('name', '未命名'))}' 分配ID: {node_id}")
parent_nodes[node_id] = node
node_name = node.get("项目名称", node.get("name", "未命名"))
print(f"工程量节点: ID={node_id}, 名称={node_name}")
print(f"找到 {len(parent_nodes)} 个父级工程量节点")
# 如果只有一个工程量节点,为所有没有父级ID的人材机节点分配这个工程量节点作为父级
default_parent_node = None
default_parent_id = None
if len(parent_nodes) == 1:
default_parent_id = list(parent_nodes.keys())[0]
default_parent_node = list(parent_nodes.values())[0]
print(
f"只有一个工程量节点,将用作默认父级: ID={default_parent_id}, 名称={default_parent_node.get('项目名称', default_parent_node.get('name', '未命名'))}"
)
# 添加调试信息,打印人材机节点的父级ID
for i, (node, parent_id) in enumerate(rcj_nodes):
node_name = node.get("名称", "未命名")
# 如果人材机节点没有父级ID且有默认父级,分配默认父级
if parent_id is None and default_parent_id is not None:
rcj_nodes[i] = (node, default_parent_id)
parent_id = default_parent_id
print(f"人材机节点: 名称={node_name}, 分配默认父级ID={parent_id}")
else:
print(f"人材机节点: 名称={node_name}, 父级ID={parent_id}")
# 初始化BCL计算器
if not init_bcl_calculator():
print("初始化BCL计算器失败,使用默认数量")
# 使用默认数量处理所有人材机节点
for node, parent_id in rcj_nodes:
node_copy = deepcopy(node)
node_copy["计算数量"] = node_copy.get("数量", "1.0")
# 保存父级ID
node_copy["parent_id"] = parent_id
# 由于没有找到父级节点,使用默认名称
node_copy["parent_name"] = "未知工程量节点"
result_nodes.append(node_copy)
return result_nodes
# 创建工程信息上下文(顶层上下文)
project_context = create_project_contexts(json_file_path=json_file_path)
# 遍历所有节点,计算数量
for node, parent_id in rcj_nodes:
node_copy = deepcopy(node)
# 保存父级ID
node_copy["parent_id"] = parent_id
# 获取父级工程量节点
parent_node = parent_nodes.get(parent_id)
if not parent_node:
print(f"未找到ID为 {parent_id} 的父级工程量节点,使用默认数量")
# 使用默认数量
node_copy["计算数量"] = node_copy.get("数量", "1.0")
# 由于没有找到父级节点,使用默认名称
node_copy["parent_name"] = "未知工程量节点"
result_nodes.append(node_copy)
continue
# 保存父级节点名称
parent_name = parent_node.get("项目名称", parent_node.get("name", "未命名工程量"))
node_copy["parent_name"] = parent_name
# 确保父级节点有数量字段
if "数量" not in parent_node or not parent_node["数量"]:
print(f"父级节点 {parent_id} 没有数量字段,使用默认数量")
parent_node["数量"] = "1.0"
# 打印父级节点信息
print(f"父级节点 {parent_id} ({parent_name}) 的数量: {parent_node.get('数量', '1.0')}")
# 原始代码
# ration_context = ZjBillBCLContext(prefix="定额", valueDict=parent_node, prevContext=project_context)
# context = ZjMaterialOrEquipmentBCLContext(node_data=node, parent_node=parent_node, prevContext=ration_context)
# 将父级节点转换为对象
parent_obj = create_node_from_type(parent_node)
# 将人材机节点转换为对象
node_obj = create_material_or_equipment_from_node(node)
# 使用对象创建数据源项
QuantityItem = BCLDataSourceItem(parent_obj)
moeItem = BCLDataSourceItem(node_obj)
context = BCLDataSourceContext([QuantityItem, moeItem], project_context)
# 根据节点类型选择不同的计算表达式
node_type = node.get("类型", "")
if node_type == "人工":
calc_expr = "_材机合并人工数量"
elif node_type == "材料":
calc_expr = "_材机合并材料数量"
elif node_type == "机械":
calc_expr = "_材机合并机械数量"
else:
# 未知类型,使用默认数量
print(f"未知节点类型 {node_type},使用默认数量")
node_copy["计算数量"] = node_copy.get("数量", "1.0")
result_nodes.append(node_copy)
continue
try:
# 使用BCL计算器计算数量
result = calculator.calculate(calc_expr, context)
# 打印调试信息
print(f"计算 {node.get('名称', '未知节点')} 的数量,表达式: {calc_expr}, 结果: {result}")
# 处理计算结果
if hasattr(result, "value"):
calculated_quantity = result.value
elif isinstance(result, (int, float)):
calculated_quantity = result
else:
# 尝试转换为浮点数
try:
calculated_quantity = float(result)
except (ValueError, TypeError):
print(f"无法将计算结果转换为浮点数: {result}")
calculated_quantity = float(node_copy.get("数量", "1.0") or "1.0")
# 如果计算结果为0,尝试使用原始数量
if calculated_quantity == 0:
orig_quantity = float(node_copy.get("数量", "0.0") or "0.0")
if orig_quantity > 0:
print(f"计算结果为0")
calculated_quantity = 0
# 设置计算数量
node_copy["计算数量"] = str(calculated_quantity)
except Exception as e:
print(f"计算节点 '{node.get('名称', '未知节点')}' 的数量时出错: {e}")
# 使用默认数量
node_copy["计算数量"] = node_copy.get("数量", "1.0")
result_nodes.append(node_copy)
return result_nodes
def generate_node_key(node: Dict[str, Any], conditions: List[str]) -> str:
"""
根据合并条件生成节点的唯一键,不再使用parent_id作为条件
"""
key_parts = []
# 添加合并条件
for condition in conditions:
# 排除与父节点相关的字段
if condition in ["parent_id", "parent_name", "id", "合并来源", "合并数量", "计算数量", "取整数量"]:
print(f" 排除字段: {condition}")
continue
value = node.get(condition, "")
# 确保值是字符串并进行标准化处理
if value is None:
value = ""
print(f" 字段 {condition} 的值为None,设置为空字符串")
elif isinstance(value, (int, float)):
# 对数值进行标准化处理,保留固定小数位
value = f"{float(value):.4f}"
print(f" 字段 {condition} 的值是数值,标准化为: {value}")
elif isinstance(value, str):
# 对字符串进行标准化处理,去除空格
original = value
value = value.strip()
if original != value:
print(f" 字段 {condition} 的值是字符串,去除空格: '{original}' -> '{value}'")
else:
# 其他类型转换为字符串
original = value
value = str(value).strip()
print(f" 字段 {condition} 的值是其他类型,转换为字符串: {type(original)} -> '{value}'")
key_parts.append(f"{condition}:{value}")
result = "|".join(key_parts)
return result
# 测试代码**************************
def debug_node_differences(node1, node2, conditions):
"""比较两个节点在合并条件上的差异"""
print(f"\n比较节点: {node1.get('名称', '未知')}{node2.get('名称', '未知')}")
for condition in conditions:
val1 = node1.get(condition, "")
val2 = node2.get(condition, "")
if val1 != val2:
print(f" 条件 '{condition}' 不同: '{val1}' != '{val2}'")
# 进一步分析差异类型
if isinstance(val1, str) and isinstance(val2, str):
if val1.strip() == val2.strip():
print(f" 差异仅在空格")
elif val1.lower() == val2.lower():
print(f" 差异仅在大小写")
# **************************
def merge_similar_nodes(nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
合并相似的节点
"""
print(f"\n进入merge_similar_nodes函数,节点数量: {len(nodes)}")
if not nodes:
return []
# 按节点类型分组
nodes_by_type = {}
for node in nodes:
node_type = node.get("类型", "")
if node_type not in nodes_by_type:
nodes_by_type[node_type] = []
nodes_by_type[node_type].append(node)
print(f"节点类型分组: {list(nodes_by_type.keys())}")
merged_nodes = []
# 处理每种类型的节点
for node_type, type_nodes in nodes_by_type.items():
print(f"\n处理节点类型: {node_type}, 数量: {len(type_nodes)}")
# 选择合并条件
if node_type == "人工" or node_type == "2":
conditions = LABOR_MERGE_CONDITIONS
elif node_type == "材料" or node_type == "3":
conditions = MATERIAL_MERGE_CONDITIONS
elif node_type == "机械" or node_type == "4":
conditions = MACHINE_MERGE_CONDITIONS
else:
# 未知类型,不合并
print(f"未知节点类型: {node_type}, 不合并")
merged_nodes.extend(type_nodes)
continue
print(f"使用合并条件: {conditions}")
# 检查节点是否有所有合并条件字段
for i, node in enumerate(type_nodes):
print(f"\n节点 {i+1}: {node.get('名称')}")
missing_fields = []
for condition in conditions:
if condition not in node:
missing_fields.append(condition)
if missing_fields:
print(f" 缺少字段: {missing_fields}")
# 打印节点的所有字段
print(" 节点字段:")
for key, value in node.items():
print(f" {key}: {value}")
# 使用字典存储合并后的节点,键是根据合并条件生成的唯一键
merged_dict = {}
# 检查是否有parent_id或parent_name字段
has_parent_fields = False
for node in type_nodes:
if "parent_id" in node or "parent_name" in node:
has_parent_fields = True
print(
f"发现parent字段: parent_id={node.get('parent_id', '无')}, parent_name={node.get('parent_name', '无')}"
)
break
if has_parent_fields:
print("警告: 节点包含parent字段,这可能导致合并失败。考虑从合并键中排除这些字段。")
for i, node in enumerate(type_nodes):
print(f"\n处理节点 {i+1}: {node.get('名称')}")
# 生成唯一键前,打印节点的合并条件字段
print(" 合并条件字段值:")
for condition in conditions:
value = node.get(condition, "")
print(f" {condition}: '{value}' (类型: {type(value)})")
key = generate_node_key(node, conditions)
print(f" 生成的唯一键: {key}")
# 检查键是否在字典中
if key in merged_dict:
print(f" 键已存在,合并节点")
# 合并节点
merged_node = merged_dict[key]
# 打印合并前的数量
merged_quantity = float(merged_node.get("取整数量", "0.0") or "0.0")
node_quantity = float(node.get("取整数量", "0.0") or "0.0")
print(f" 合并数量: {merged_quantity} + {node_quantity} = {merged_quantity + node_quantity}")
# 合并数量
merged_node["取整数量"] = str(merged_quantity + node_quantity)
# 合并计算数量
merged_calc_quantity = float(merged_node.get("计算数量", "0.0") or "0.0")
node_calc_quantity = float(node.get("计算数量", "0.0") or "0.0")
merged_node["计算数量"] = str(merged_calc_quantity + node_calc_quantity)
# 更新原始数量
merged_orig_quantity = float(merged_node.get("数量", "0.0") or "0.0")
node_orig_quantity = float(node.get("数量", "0.0") or "0.0")
merged_node["数量"] = str(merged_orig_quantity + node_orig_quantity)
# 添加合并信息
if "合并来源" not in merged_node:
# 使用唯一标识符,如果没有id字段则使用节点本身的索引
merged_node_id = merged_node.get("id", f"node_{id(merged_node)}")
merged_node["合并来源"] = [merged_node_id]
# 添加当前节点ID到合并来源
node_id = node.get("id", f"node_{id(node)}")
merged_node["合并来源"].append(node_id)
# 更新合并数量计数
merged_node["合并数量"] = str(len(merged_node["合并来源"]))
else:
print(f" 键不存在,创建新节点")
# 创建新节点
merged_dict[key] = deepcopy(node)
# 添加合并信息
node_id = node.get("id", f"node_{id(node)}")
merged_dict[key]["合并来源"] = [node_id]
merged_dict[key]["合并数量"] = "1"
# 检查合并结果
print(f"\n合并后节点数量: {len(merged_dict)}")
for key, node in merged_dict.items():
print(f" 键: {key}")
print(f" 节点: {node.get('名称')}, 取整数量: {node.get('取整数量')}")
if "合并来源" in node and len(node["合并来源"]) > 1:
print(f" 合并来源数量: {len(node['合并来源'])}")
# 将合并后的节点添加到结果列表
merged_nodes.extend(merged_dict.values())
return merged_nodes
def cat_rcj_count(rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
处理人材机节点数量的变化并合并相似节点
"""
print(f"进入cat_rcj_count函数,节点数量: {len(rcj_nodes)}")
for node in rcj_nodes:
print(f"节点: {node.get('名称')}, 计算数量: {node.get('计算数量')}")
# 使用计算数量,不进行向上取整
processed_nodes = []
for node in rcj_nodes:
node_copy = deepcopy(node)
# 获取计算数量,直接使用,不再向上取整
calc_quantity = float(node_copy.get("计算数量", "0.0") or "0.0")
node_copy["取整数量"] = str(calc_quantity)
processed_nodes.append(node_copy)
print(f"处理后节点数量: {len(processed_nodes)}")
# 合并相似节点
merged_nodes = merge_similar_nodes(processed_nodes)
print(f"合并后节点数量: {len(merged_nodes)}")
for node in merged_nodes:
print(f"合并后节点: {node.get('名称')}, 取整数量: {node.get('取整数量')}")
return merged_nodes
def calc_rcj_fee(rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
计算人材机节点的合价
Args:
rcj_nodes: 人材机节点列表
Returns:
List[Dict[str, Any]]: 计算合价后的人材机节点列表
"""
result_nodes = []
for node in rcj_nodes:
node_copy = deepcopy(node)
# 获取取整数量
quantity = float(node_copy.get("取整数量", "0.0") or "0.0")
# 获取预算价不含税和市场价不含税
budget_price = float(node_copy.get("预算价不含税", "0.0") or "0.0")
market_price = float(node_copy.get("市场价不含税", "0.0") or "0.0")
# 计算预算价合价和市场价合价
budget_total = quantity * budget_price
market_total = quantity * market_price
# 计算价差
price_diff = market_total - budget_total
# 设置合价信息
node_copy["预算价合价"] = str(budget_total)
node_copy["市场价合价"] = str(market_total)
node_copy["价差"] = str(price_diff)
result_nodes.append(node_copy)
return result_nodes
def format_rcj_output(rcj_nodes: List[Dict[str, Any]], node_type: str) -> List[Dict[str, Any]]:
"""
格式化人材机节点输出,只保留需要的字段,不再保留父级节点信息
Args:
rcj_nodes: 人材机节点列表
node_type: 节点类型("人工"、"材料"、"机械"
Returns:
List[Dict[str, Any]]: 格式化后的节点列表
"""
formatted_nodes = []
for node in rcj_nodes:
formatted_node = {}
# 根据节点类型保留特定字段
if node_type == "人工":
# 人工节点保存:编码,名称,单位,数量,预算价不含税,市场价不含税,预算价合价,市场价合价,价差
fields = [
"编码",
"名称",
"单位",
"取整数量",
"预算价不含税",
"市场价不含税",
"预算价合价",
"市场价合价",
"价差",
]
for field in fields:
formatted_node[field] = node.get(field, "")
elif node_type == "材料":
# 材料节点保存:供货方,编码,名称,单位,数量,预算价不含税,市场价不含税,预算价合价,市场价合价,价差
fields = [
"供货方",
"编码",
"名称",
"单位",
"取整数量",
"预算价不含税",
"市场价不含税",
"预算价合价",
"市场价合价",
"价差",
]
for field in fields:
formatted_node[field] = node.get(field, "")
elif node_type == "机械":
# 机械节点保存:编码,名称,单位,数量,预算价不含税,市场价不含税,预算价合价,市场价合价,价差
fields = [
"编码",
"名称",
"单位",
"取整数量",
"预算价不含税",
"市场价不含税",
"预算价合价",
"市场价合价",
"价差",
]
for field in fields:
formatted_node[field] = node.get(field, "")
# 重命名取整数量为数量
if "取整数量" in formatted_node:
formatted_node["数量"] = formatted_node.pop("取整数量")
formatted_nodes.append(formatted_node)
return formatted_nodes
def calculate_rcj_fees(
json_file_path: str,
project_name: str,
project_guid: str = None,
) -> Dict[str, Any]:
"""
计算项目划分节点下所有人材机节点的合价
Args:
json_file_path: JSON文件路径
project_name: 项目名称
project_guid: 项目GUID,用于区分同名项目
Returns:
Dict[str, Any]: 计算结果,包含人工节点、材料节点和机械节点
"""
# 加载项目数据
data, project_data, cost_setting, project_division, target_node = load_project_data(
json_file_path, project_name, project_guid
)
# 如果没有找到项目划分节点
if not target_node:
print(f"警告: 未找到项目 '{project_name}' 的项目划分节点")
return {}
# 获取项目子节点
project_children = target_node.get("children", [])
if not project_children:
print(f"警告: 项目 '{project_name}' (GUID: {project_guid}) 没有工程量节点,跳过计算")
return {}
# 直接从工程量节点中提取人材机节点,保持父子关系
labor_nodes = [] # 人工节点 (节点, 父级节点) 元组列表
material_nodes = [] # 材料节点 (节点, 父级节点) 元组列表
machine_nodes = [] # 机械节点 (节点, 父级节点) 元组列表
# 递归处理工程量节点,提取人材机节点
def process_nodes(nodes):
for node in nodes:
# 如果是定额节点,处理其材机列表和children
if node.get("类型") == "定额" or node.get("类型") == "0":
print(f"找到定额节点: {node.get('项目名称', node.get('name', '未命名'))}")
# 处理材机列表
if "材机列表" in node and node["材机列表"]:
print(
f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 有材机列表,数量: {len(node['材机列表'])}"
)
for rcj_node in node["材机列表"]:
node_type = rcj_node.get("类型")
# 同时支持数字类型和字符串类型
if node_type in ["人工", "2"]:
labor_nodes.append((rcj_node, node))
print(
f"从材机列表找到人工节点: {rcj_node.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}"
)
elif node_type in ["材料", "3"]:
material_nodes.append((rcj_node, node))
print(
f"从材机列表找到材料节点: {rcj_node.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}"
)
elif node_type in ["机械", "4"]:
machine_nodes.append((rcj_node, node))
print(
f"从材机列表找到机械节点: {rcj_node.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}"
)
else:
print(f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 没有材机列表")
# 处理children中的人材机节点
if "children" in node and node["children"]:
print(
f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 有子节点,数量: {len(node['children'])}"
)
for child in node["children"]:
child_type = child.get("类型")
if child_type in ["人工", "2"]:
labor_nodes.append((child, node))
print(
f"从children找到人工节点: {child.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}"
)
elif child_type in ["材料", "3"]:
material_nodes.append((child, node))
print(
f"从children找到材料节点: {child.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}"
)
elif child_type in ["机械", "4"]:
machine_nodes.append((child, node))
print(
f"从children找到机械节点: {child.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}"
)
else:
print(f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 没有子节点")
# 递归处理子节点
if "children" in node and node["children"]:
process_nodes(node["children"])
# 处理所有工程量节点
process_nodes(project_children)
print(f"找到 {len(labor_nodes)} 个人工节点, {len(material_nodes)} 个材料节点, {len(machine_nodes)} 个机械节点")
# 如果仍然没有找到人材机节点,返回空结果
if not labor_nodes and not material_nodes and not machine_nodes:
print(f"项目 '{project_name}' (GUID: {project_guid}) 没有人材机数据,不保存结果")
return {}
# 修改calc_rcj_count函数调用,直接传递父级节点对象而不是ID
def calc_rcj_count_with_parent(rcj_nodes):
"""
计算人材机节点的数量,考虑父级消耗量
Args:
rcj_nodes: 人材机节点列表,每个元素是(节点, 父级节点)元组
Returns:
List[Dict[str, Any]]: 计算数量后的人材机节点列表
"""
result_nodes = []
# 检查rcj_nodes是否为空
if not rcj_nodes:
print("没有找到人材机节点")
return result_nodes
# # 初始化BCL计算器
# if not init_bcl_calculator():
# print("初始化BCL计算器失败,使用默认数量")
# # 使用默认数量处理所有人材机节点
# for node, parent_node in rcj_nodes:
# node_copy = deepcopy(node)
# node_copy["计算数量"] = node_copy.get("数量", "1.0")
# # 保存父级信息
# node_copy["parent_id"] = parent_node.get("id", "")
# node_copy["parent_name"] = parent_node.get("项目名称", parent_node.get("name", "未命名工程量"))
# result_nodes.append(node_copy)
# return result_nodes
# 创建工程信息上下文(顶层上下文)
project_context = create_project_contexts(json_file_path=json_file_path)
with open(json_file_path, "r", encoding="utf-8") as file:
data = json.load(file)
processed_data = process_DXdata(data)
DXITEM = [BCLDataSourceItem(item) for item in processed_data]
# 遍历所有节点,计算数量
for node, parent_node in rcj_nodes:
node_copy = deepcopy(node)
# 保存父级信息
parent_id = parent_node.get("id", "")
parent_name = parent_node.get("项目名称", parent_node.get("name", "未命名工程量"))
node_copy["parent_id"] = parent_id
node_copy["parent_name"] = parent_name
# 确保父级节点有数量字段
if "数量" not in parent_node or not parent_node["数量"]:
print(f"父级节点 {parent_name} 没有数量字段,使用默认数量")
parent_node["数量"] = "1.0"
# 打印父级节点信息
print(f"父级节点 {parent_name} 的数量: {parent_node.get('数量', '1.0')}")
# 确保父级节点的类型字段正确设置
if "类型" in parent_node:
parent_type = parent_node["类型"]
print(f"父级节点 {parent_name} 的类型: {parent_type}")
else:
# 如果父级节点没有类型字段,默认设置为"定额"
parent_node["类型"] = "定额"
print(f"父级节点 {parent_name} 没有类型字段,设置为默认类型: 定额")
# 处理 parent_node 的 "特征段" 字段
import re
segment_value = parent_node.get("特征段", "")
if isinstance(segment_value, str):
match = re.search(r"\d+", segment_value) # 提取第一个连续数字
if match:
parent_node["特征段"] = match.group() # 例如 "特征1" -> "1"
else:
parent_node["特征段"] = "" # 无法提取数字则清空或设为默认值
# 将父级节点转换为对象
parent_obj = create_node_from_type(parent_node)
# 确保父级对象的type属性正确设置
if hasattr(parent_obj, "type") and not parent_obj.type:
parent_obj.type = parent_node.get("类型", "定额")
print(f"设置父级对象 {parent_name} 的type属性为: {parent_obj.type}")
# 将人材机节点转换为对象
node_obj = create_material_or_equipment_from_node(node)
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
# 使用对象创建数据源项
QuantityItem = BCLDataSourceItem(parent_obj, None, "定额")
moeItem = BCLDataSourceItem(node_obj, QuantityItem)
if hasattr(parent_obj, "type"):
print(f"父级对象type属性值: {parent_obj.type}")
# 创建数据源上下文
context = BCLDataSourceContext([moeItem], dxitem_context)
# 根据节点类型选择不同的计算表达式
node_type = node.get("类型", "")
if node_type in ["人工", "2"]:
calc_expr = "_材机合并人工数量"
elif node_type in ["材料", "3"]:
calc_expr = "_材机合并材料数量"
elif node_type in ["机械", "4"]:
calc_expr = "_材机合并机械数量"
else:
# 未知类型,使用默认数量
print(f"未知节点类型 {node_type},使用默认数量")
node_copy["计算数量"] = node_copy.get("数量", "1.0")
result_nodes.append(node_copy)
continue
try:
# 使用BCL计算器计算数量
result = calculator.calculate(calc_expr, context)
# 打印调试信息
print(f"计算 {node.get('名称', '未知节点')} 的数量,表达式: {calc_expr}, 结果: {result}")
# 处理计算结果
if hasattr(result, "value"):
calculated_quantity = result.value
elif isinstance(result, (int, float)):
calculated_quantity = result
else:
# 尝试转换为浮点数
try:
calculated_quantity = float(result)
except (ValueError, TypeError):
print(f"无法将计算结果转换为浮点数: {result}")
calculated_quantity = float(node_copy.get("数量", "1.0") or "1.0")
# 如果计算结果为0,使用原始数量
if calculated_quantity == 0:
orig_quantity = float(node_copy.get("数量", "0.0") or "0.0")
if orig_quantity > 0:
print(f"计算结果为0,使用原始数量: {orig_quantity}")
calculated_quantity = orig_quantity
# 设置计算数量
node_copy["计算数量"] = str(calculated_quantity)
except Exception as e:
print(f"计算节点 '{node.get('名称', '未知节点')}' 的数量时出错: {e}")
# 使用默认数量
node_copy["计算数量"] = node_copy.get("数量", "1.0")
result_nodes.append(node_copy)
return result_nodes
# 计算人工节点的数量和合价
labor_with_count = calc_rcj_count_with_parent(labor_nodes)
print(f"calc_rcj_count_with_parent 返回节点数量: {len(labor_with_count)}")
labor_with_cat = cat_rcj_count(labor_with_count)
print(f"cat_rcj_count 返回节点数量: {len(labor_with_cat)}")
labor_with_fee = calc_rcj_fee(labor_with_cat)
print(f"calc_rcj_fee 返回节点数量: {len(labor_with_fee)}")
labor_formatted = format_rcj_output(labor_with_fee, "人工")
print(f"format_rcj_output 返回节点数量: {len(labor_formatted)}")
# 计算材料节点的数量和合价
material_with_count = calc_rcj_count_with_parent(material_nodes)
material_with_cat = cat_rcj_count(material_with_count)
material_with_fee = calc_rcj_fee(material_with_cat)
material_formatted = format_rcj_output(material_with_fee, "材料")
# 计算机械节点的数量和合价
machine_with_count = calc_rcj_count_with_parent(machine_nodes)
machine_with_cat = cat_rcj_count(machine_with_count)
machine_with_fee = calc_rcj_fee(machine_with_cat)
machine_formatted = format_rcj_output(machine_with_fee, "机械")
# 直接返回合并后的结果,不再按工程量节点分组
result = {"人工节点": labor_formatted, "材料节点": material_formatted, "机械节点": machine_formatted}
# 计算合价只用于调试显示
labor_fee = sum(float(node.get("预算价合价", "0.0") or "0.0") for node in labor_formatted)
material_fee = sum(float(node.get("预算价合价", "0.0") or "0.0") for node in material_formatted)
machine_fee = sum(float(node.get("预算价合价", "0.0") or "0.0") for node in machine_formatted)
print(f"项目 '{project_name}': 人工合价={labor_fee}, 材料合价={material_fee}, 机械合价={machine_fee}")
return result
def calculate_resource_fees(
json_file_path: str,
project_name: str,
project_guid: str = None,
calculation_strategy=None,
) -> str:
"""
计算人材机合价
Args:
json_file_path: JSON文件路径
project_name: 项目名称
project_guid: 项目GUID,用于区分同名项目
calculation_strategy: 计算策略,如果为None则使用默认策略
Returns:
str: 输出文件路径,如果没有工程量节点则返回None
"""
# 如果没有提供计算策略,使用默认策略
if calculation_strategy is None:
from equipment_calculation.calculation_strategy import DefaultCalculationStrategy
calculation_strategy = DefaultCalculationStrategy()
# 获取项目划分节点的GUID
_, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid)
# 如果传入了GUID但与节点的GUID不一致,优先使用传入的GUID
node_guid = target_node.get("GUID") or target_node.get("guid", "") if target_node else ""
project_guid = project_guid if project_guid else node_guid
# 将文件名中的非法字符替换为下划线
safe_project_name = (
project_name.replace("/", "_")
.replace("\\", "_")
.replace(":", "_")
.replace("*", "_")
.replace("?", "_")
.replace('"', "_")
.replace("<", "_")
.replace(">", "_")
.replace("|", "_")
)
# 将GUID中的非法字符替换为下划线
safe_project_guid = ""
if project_guid:
safe_project_guid = (
project_guid.replace("/", "_")
.replace("\\", "_")
.replace(":", "_")
.replace("*", "_")
.replace("?", "_")
.replace('"', "_")
.replace("<", "_")
.replace(">", "_")
.replace("|", "_")
.replace("{", "")
.replace("}", "")
)
# 添加下划线作为分隔符
safe_project_guid = f"_{safe_project_guid}"
# 设置输出目录和文件名
output_dir = "计算结果"
os.makedirs(output_dir, exist_ok=True)
rcj_output_file = os.path.join(output_dir, f"{safe_project_name}{safe_project_guid}_rcj_fees.json")
# 计算人材机节点的合价,传递project_guid参数
# 这里使用 calculation_strategy 的 calculate_rcj_fees 方法
if hasattr(calculation_strategy, "calculate_rcj_fees"):
rcj_results = calculation_strategy.calculate_rcj_fees(json_file_path, project_name, project_guid)
else:
# 如果计算策略没有实现 calculate_rcj_fees 方法,使用原始函数
rcj_results = calculate_rcj_fees(json_file_path, project_name, project_guid)
# 检查是否有人材机数据
has_data = False
for node_type in ["人工节点", "材料节点", "机械节点"]:
if rcj_results.get(node_type) and len(rcj_results[node_type]) > 0:
has_data = True
break
if not has_data:
print(f"项目 '{project_name}' (GUID: {project_guid}) 没有人材机数据,不保存结果")
return None
# 保存人材机合价计算结果到JSON文件
with open(rcj_output_file, "w", encoding="utf-8") as f:
json.dump(rcj_results, f, ensure_ascii=False, indent=2)
print(f"\n人材机合价计算结果已保存到 {rcj_output_file}")
return rcj_output_file