修改费用计算代码

This commit is contained in:
chentianrui
2025-08-22 18:13:09 +08:00
parent 8d595b339c
commit be848c3e78
20 changed files with 2569 additions and 360 deletions
+154 -27
View File
@@ -1,8 +1,10 @@
import json
import os
import sys
from memory_profiler import profile
from typing import Dict, List, Any, Optional, Tuple, Set
from equipment_calculation.expressioncalculator import ExpressionCalculator
from equipment_calculation.bcl_utils import (
ZjQuantityBCLContext,
ZjProjectBCLContext,
@@ -15,6 +17,7 @@ from equipment_calculation.bcl_utils import (
BCLDataSourceContext,
create_list_from_node,
create_node_from_type,
create_material_or_equipment_from_node,
)
from equipment_calculation.item_acquisition import (
get_cost_table_children,
@@ -23,6 +26,7 @@ from equipment_calculation.item_acquisition import (
find_cost_table,
get_bill_node_by_id,
load_project_data,
get_classified_resource_nodes,
)
# 缓存已计算过的费用
@@ -223,7 +227,7 @@ def calculate_external_variable(var_name: str, context: ZjQuantityBCLContext, ca
try:
# 如果有计算策略,使用计算策略的方法
if calculation_strategy:
print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}")
# print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}")
# 直接调用计算策略的方法,而不是递归调用自己
return calculation_strategy.calculate_external_variable(var_name, context)
@@ -447,17 +451,21 @@ def calculate_all_fees(
json_file_path: str = None,
engineering_type: str = None,
calculation_strategy=None,
json_data: dict | None = None,
) -> Dict[str, float]:
"""
计算项目节点的所有费用,确保清单数量正确传递
"""
results = {}
# 1. 工程信息上下文
project_context = create_project_contexts(json_file_path=json_file_path)
# 1. 工程信息上下文(优先使用传入的 json_data
project_context = create_project_contexts(json_file_path=json_file_path, json_data=json_data)
with open(json_file_path, "r", encoding="utf-8") as file:
data = json.load(file)
if json_data is not None:
data = json_data
else:
with open(json_file_path, "r", encoding="utf-8") as file:
data = json.load(file)
processed_data = process_DXdata(data)
@@ -497,8 +505,37 @@ def calculate_all_fees(
billItem.bill_quantity = bill_obj.quantity
print(f"设置清单数据源项数量: {billItem.bill_quantity}")
# 如果无法创建工程量对象,则跳过并返回当前已计算结果,避免未定义的 QuantityItem
if not project_obj:
print(
f"警告: 无法从项目节点创建工程量对象,跳过该节点: "
f"{project_node.get('项目名称', project_node.get('name', '未知节点'))}"
)
return results
QuantityItem = BCLDataSourceItem(project_obj, billItem)
childItems = []
if project_obj.children:
for moe in project_obj.children:
childItems.append(BCLDataSourceItem(moe, QuantityItem))
QuantityItem.set_childs(childItems)
bill_context = BCLDataSourceContext([billItem], dxitem_context)
# # 构建人材机数据源项(若存在)并接入上下文
# rcj_nodes_for_parent = project_node.get("_rcj_nodes", [])
# moe_items = []
# if rcj_nodes_for_parent:
# try:
# for rcj_node in rcj_nodes_for_parent:
# node_obj = create_material_or_equipment_from_node(rcj_node)
# moe_items.append(BCLDataSourceItem(node_obj, QuantityItem))
# except Exception as e:
# print(f"构建人材机数据源项出错: {e}")
# items_chain = [QuantityItem] + moe_items if moe_items else [QuantityItem]
context = BCLDataSourceContext([QuantityItem], bill_context)
# 如果计算策略存在,设置清单数量
@@ -513,7 +550,35 @@ def calculate_all_fees(
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
# 如果无法创建工程量对象,则跳过并返回当前已计算结果,避免未定义的 QuantityItem
if not project_obj:
print(
f"警告: 无法从项目节点创建工程量对象,跳过该节点: "
f"{project_node.get('项目名称', project_node.get('name', '未知节点'))}"
)
return results
QuantityItem = BCLDataSourceItem(project_obj)
childItems = []
if project_obj.children:
for moe in project_obj.children:
childItems.append(BCLDataSourceItem(moe, QuantityItem))
QuantityItem.set_childs(childItems)
# # 构建人材机数据源项(若存在)并接入上下文
# rcj_nodes_for_parent = project_node.get("_rcj_nodes", [])
# moe_items = []
# if rcj_nodes_for_parent:
# try:
# for rcj_node in rcj_nodes_for_parent:
# node_obj = create_material_or_equipment_from_node(rcj_node)
# moe_items.append(BCLDataSourceItem(node_obj, QuantityItem))
# except Exception as e:
# print(f"构建人材机数据源项出错: {e}")
# items_chain = [QuantityItem] + moe_items if moe_items else [QuantityItem]
context = BCLDataSourceContext([QuantityItem], dxitem_context)
# 查找包含取费基数的节点
@@ -696,6 +761,8 @@ def calculate_quantity_fees(
engineering_type: str = None,
project_guid: str = None,
calculation_strategy=None,
software_type: str = None,
json_data: dict | None = None,
) -> str:
"""
计算工程量取费表,不使用全局变量,并按清单节点组织结果
@@ -769,7 +836,9 @@ def calculate_quantity_fees(
if engineering_type == "预算工程":
# 预算工程 - 获取项目划分节点的取费表,传递project_guid参数
cost_table_children = get_cost_table_children(json_file_path, project_name, project_guid)
project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid)
project_children = get_quantity_nodes(
json_file_path, project_name, engineering_type, project_guid, software_type
)
if not cost_table_children or not project_children:
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的取费表或项目划分节点")
@@ -777,7 +846,9 @@ def calculate_quantity_fees(
elif engineering_type == "清单工程":
# 清单工程 - 获取清单节点的取费表,传递project_guid参数
project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid)
project_children = get_quantity_nodes(
json_file_path, project_name, engineering_type, project_guid, software_type
)
if not project_children:
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的项目划分节点")
return None
@@ -785,6 +856,16 @@ def calculate_quantity_fees(
# 为每个工程量节点找到对应的清单节点取费表
cost_tables = {}
# 准备一次性 JSON 数据(清单工程需要从 projectData 中获取 costSetting 与 bill
if json_data is not None:
_data_all = json_data
else:
with open(json_file_path, "r", encoding="utf-8") as f:
_data_all = json.load(f)
_project_data_all = _data_all.get("projectData", {})
_cost_setting_all = _project_data_all.get("costSetting", {})
_bill_data_all = _project_data_all.get("bill", {})
for project_node in project_children:
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
@@ -813,15 +894,8 @@ def calculate_quantity_fees(
if bill_id not in cost_tables:
print(f"查找清单节点 '{bill_name}' 的取费表: {fee_table_name}")
# 查找取费表
with open(json_file_path, "r", encoding="utf-8") as f:
data = json.load(f)
project_data = data.get("projectData", {})
cost_setting = project_data.get("costSetting", {})
# 查找取费表
cost_table = find_cost_table(cost_setting, fee_table_name)
# 查找取费表:使用已加载的数据
cost_table = find_cost_table(_cost_setting_all, fee_table_name)
if not cost_table:
print(f"未找到取费表 '{fee_table_name}'")
@@ -835,12 +909,8 @@ def calculate_quantity_fees(
cost_tables[bill_id] = cost_table_children
print(f"成功获取清单节点 '{bill_name}' 的取费表")
# 在读取JSON文件后,遍历项目中的清单节点,获取并保存清单数量
with open(json_file_path, "r", encoding="utf-8") as f:
data = json.load(f)
project_data = data.get("projectData", {})
bill_data = project_data.get("bill", {})
# 遍历项目中的清单节点,获取并保存清单数量(使用已加载的数据)
bill_data = _bill_data_all
# 递归函数获取所有清单节点
def get_bill_nodes(node):
@@ -885,6 +955,55 @@ def calculate_quantity_fees(
print(f"不支持的工程类型: {engineering_type}")
return None
# 预取并分派人材机节点到各工程量节点上,供后续上下文使用
try:
labor_nodes, material_nodes, machine_nodes = get_classified_resource_nodes(
json_file_path, project_name, project_guid
)
# 合并三类节点,形成父ID到节点列表的映射
rcj_all = []
if labor_nodes:
rcj_all.extend([n for n, _ in labor_nodes])
if material_nodes:
rcj_all.extend([n for n, _ in material_nodes])
if machine_nodes:
rcj_all.extend([n for n, _ in machine_nodes])
rcj_by_parent: Dict[str, List[Dict[str, Any]]] = {}
# 注意:get_classified_resource_nodes 返回的是 (node, parent_id) 元组列表
def add_to_map(pairs):
if not pairs:
return
for node, parent_id in pairs:
if parent_id is None:
continue
key = str(parent_id)
rcj_by_parent.setdefault(key, []).append(node)
add_to_map(labor_nodes)
add_to_map(material_nodes)
add_to_map(machine_nodes)
# 将对应的人材机列表写入每个工程量节点
if project_children:
for node in project_children:
pid_candidates = [
node.get("GUID"),
node.get("guid"),
node.get("id"),
]
rcj_list = []
for pid in pid_candidates:
if pid and str(pid) in rcj_by_parent:
rcj_list = rcj_by_parent.get(str(pid), [])
break
if rcj_list:
node["_rcj_nodes"] = rcj_list
except Exception as e:
print(f"预取人材机节点失败: {e}")
# 在获取 project_children 后添加预处理代码
if project_children and calculation_strategy and hasattr(calculation_strategy, "preprocess_quantity_fee_node"):
print(f"对项目节点进行预处理...")
@@ -946,7 +1065,11 @@ def calculate_quantity_fees(
if engineering_type == "预算工程":
# 预算工程 - 使用项目划分节点的取费表
node_results = calculation_strategy.calculate_all_fees(
project_node, {"children": cost_table_children}, json_file_path, engineering_type
project_node,
{"children": cost_table_children},
json_file_path,
engineering_type,
json_data=json_data,
)
# 直接使用节点键作为结果键
@@ -958,7 +1081,11 @@ def calculate_quantity_fees(
bill_id = project_node.get("bill_guid") or project_node.get("bill_id")
if bill_id and bill_id in cost_tables:
node_results = calculation_strategy.calculate_all_fees(
project_node, {"children": cost_tables[bill_id]}, json_file_path, engineering_type
project_node,
{"children": cost_tables[bill_id]},
json_file_path,
engineering_type,
json_data=json_data,
)
# 获取清单信息
@@ -987,9 +1114,9 @@ def calculate_quantity_fees(
continue
# 输出计算结果
print(f"费用计算结果:")
for fee_name, fee_value in node_results.items():
print(f" {fee_name}: {fee_value}")
# print(f"费用计算结果:")
# for fee_name, fee_value in node_results.items():
# print(f" {fee_name}: {fee_value}")
# 保存计算结果到JSON文件
with open(output_file, "w", encoding="utf-8") as f: