Files
KG_generation/equipment_calculation/quantity_fee_calculator.py
T
chentianrui 9609bb67b4 上传文件
2025-08-01 15:31:56 +08:00

973 lines
35 KiB
Python

import json
import os
import sys
from typing import Dict, List, Any, Optional, Tuple, Set
from expressioncalculator import ExpressionCalculator
from bcl_utils import (
ZjQuantityBCLContext,
ZjProjectBCLContext,
ZjBillBCLContext,
init_bcl_calculator,
BCLVariant,
calculator,
create_project_contexts,
BCLDataSourceItem,
BCLDataSourceContext,
create_list_from_node,
create_node_from_type,
)
from item_acquisition import (
get_cost_table_children,
get_quantity_nodes,
get_bill_cost_table,
find_cost_table,
get_bill_node_by_id,
load_project_data,
)
# 缓存已计算过的费用
calculated_fees = {}
# 添加一个全局变量和一个缓存字典来存储清单数量
_BILL_QUANTITY = 1.0
bill_quantity_cache = {}
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
# 在create_list_from_node函数后添加一个包装函数
def create_list_from_node_with_quantity(node, quantity=None):
"""创建清单对象并设置数量"""
from bcl_utils import create_list_from_node
bill_obj = create_list_from_node(node)
# 设置清单数量
bill_quantity = quantity if quantity is not None else node.get("数量")
# 设置到对象属性
bill_obj.quantity = bill_quantity
# 保存到全局变量和缓存
global _BILL_QUANTITY
_BILL_QUANTITY = bill_quantity
# 如果节点有ID,保存到缓存
bill_id = node.get("id") or node.get("GUID")
if bill_id:
bill_quantity_cache[bill_id] = bill_quantity
# 设置环境变量作为后备方案
os.environ["BILL_QUANTITY"] = str(bill_quantity)
print(f"设置清单对象数量: {bill_quantity}")
print(f"全局变量_BILL_QUANTITY: {_BILL_QUANTITY}")
if bill_id:
print(f"缓存清单{bill_id}的数量: {bill_quantity_cache[bill_id]}")
return bill_obj
# 递归查找取费表中包含"取费基数"的节点
def find_fee_base_nodes(node: Dict[str, Any], result: List = None) -> List[Dict[str, Any]]:
"""
递归查找取费表中包含"取费基数"的节点或有子节点的费用项
Args:
node: 取费表节点
result: 结果列表
Returns:
List[Dict[str, Any]]: 包含"取费基数"的节点或有子节点的费用项列表
"""
if result is None:
result = []
if isinstance(node, dict):
# 检查当前节点是否包含"取费基数"字段或有子节点
has_children = "children" in node and isinstance(node["children"], list) and node["children"]
has_code = "代码" in node
if "取费基数" in node or (has_children and has_code):
result.append(node)
# 递归检查子节点
for key, value in node.items():
if key == "children" and isinstance(value, list):
for child in value:
find_fee_base_nodes(child, result)
elif isinstance(value, (dict, list)):
find_fee_base_nodes(value, result)
elif isinstance(node, list):
for item in node:
find_fee_base_nodes(item, result)
return result
# 查找取费表中的费用项
def find_fee_item_by_code(cost_table: Dict[str, Any], code: str) -> Optional[Dict[str, Any]]:
"""
在取费表中查找指定代码的费用项
Args:
cost_table: 取费表
code: 费用代码
Returns:
Optional[Dict[str, Any]]: 找到的费用项,如果未找到则返回None
"""
def search_in_node(node):
if isinstance(node, dict):
if node.get("代码") == code:
return node
for key, value in node.items():
if isinstance(value, (dict, list)):
result = search_in_node(value)
if result:
return result
elif isinstance(node, list):
for item in node:
result = search_in_node(item)
if result:
return result
return None
return search_in_node(cost_table)
# 计算表外变量
def calculate_external_variable(var_name: str, context: ZjQuantityBCLContext, calculation_strategy=None) -> float:
"""
使用BCL计算器计算表外变量
Args:
var_name: 变量名
context: BCL上下文
calculation_strategy: 计算策略对象
Returns:
float: 计算结果
"""
try:
# 如果有计算策略,使用计算策略的方法
if calculation_strategy:
print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}")
# 直接调用计算策略的方法,而不是递归调用自己
return calculation_strategy.calculate_external_variable(var_name, context)
# 否则使用默认实现
print(f"使用默认方法计算表外变量: {var_name}")
result = calculator.calculate(var_name, context)
# 检查结果类型,如果是 BCLVariant,尝试转换为浮点数
if hasattr(result, "__class__") and result.__class__.__name__ == "BCLVariant":
# 根据错误信息,BCLVariant 格式似乎是 Variant(BCLVariantType.FLOAT, 0.0)
# 尝试访问 value 属性或使用适当的方法获取值
try:
# 尝试不同的方法获取值
if hasattr(result, "value"):
return float(result.value)
elif hasattr(result, "get_value"):
return float(result.get_value())
elif hasattr(result, "__getitem__"):
# 如果 BCLVariant 支持索引访问,尝试获取第二个元素
return float(result[1])
else:
# 尝试直接转换为字符串,然后解析值
str_result = str(result)
# 假设格式是 Variant(BCLVariantType.FLOAT, 0.0)
# 尝试提取括号中的第二个值
import re
match = re.search(r"Variant\([^,]+,\s*([^)]+)\)", str_result)
if match:
return float(match.group(1))
# 如果上述方法都失败,尝试直接转换
return float(result)
except (ValueError, TypeError, AttributeError, IndexError) as e:
print(f"无法将 BCLVariant 类型的结果转换为浮点数: {result}, 错误: {e}")
# 打印更多调试信息
print(f"BCLVariant 类型: {type(result)}")
print(f"BCLVariant 属性: {dir(result)}")
return 0.0
# 如果不是 BCLVariant,尝试直接转换为浮点数
return float(result) if result is not None else 0.0
except Exception as e:
print(f"计算表外变量 '{var_name}' 时出错: {e}")
return 0.0
# 计算表内费用
def calculate_internal_fee(
fee_code: str,
cost_table: Dict[str, Any],
project_node: Dict[str, Any],
context: ZjQuantityBCLContext,
in_calculation: set = None,
calculation_strategy=None,
) -> float:
"""
计算表内费用
Args:
fee_code: 费用代码
cost_table: 取费表
project_node: 项目划分节点
context: BCL上下文
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
calculation_strategy: 计算策略对象
Returns:
float: 计算结果
"""
# 初始化正在计算的集合
if in_calculation is None:
in_calculation = set()
# 检查是否存在循环依赖
if fee_code in in_calculation:
print(f"检测到循环依赖: {fee_code}")
return 0.0
# 将当前费用代码添加到正在计算的集合中
in_calculation.add(fee_code)
# 检查缓存中是否已有计算结果
cache_key = f"{fee_code}_{project_node.get('id', '')}"
if cache_key in calculated_fees:
# 从正在计算的集合中移除当前费用代码
in_calculation.remove(fee_code)
return calculated_fees[cache_key]
# 查找费用项
fee_item = find_fee_item_by_code(cost_table, fee_code)
if not fee_item:
print(f"未找到代码为 '{fee_code}' 的费用项")
# 从正在计算的集合中移除当前费用代码
in_calculation.remove(fee_code)
return 0.0
# 获取取费基数和费率
fee_base = fee_item.get("取费基数")
fee_rate = fee_item.get("费率(%)") or fee_item.get("费率")
# 尝试转换费率为浮点数
try:
if fee_rate is None or (isinstance(fee_rate, str) and fee_rate.strip() == ""):
rate = 1.0
else:
rate = float(fee_rate) / 100.0
except (ValueError, TypeError):
print(f"无法将费率 '{fee_rate}' 转换为浮点数,使用默认值1.0")
rate = 1.0
# 如果有取费基数,直接计算
if fee_base:
base_value = calculate_fee_base(
fee_base, cost_table, project_node, context, in_calculation, calculation_strategy
)
# 确保 base_value 不为 None
base_value = base_value if base_value is not None else 0.0
result = base_value * rate
else:
# 如果没有取费基数,检查是否有子节点
children = fee_item.get("children", [])
if children:
# 计算所有子节点的费用之和
result = 0.0
for child in children:
child_code = child.get("代码", "")
if child_code:
child_result = calculate_internal_fee(
child_code, cost_table, project_node, context, in_calculation, calculation_strategy
)
# 确保 child_result 不为 None
child_result = child_result if child_result is not None else 0.0
result += child_result
else:
print(f"费用项 '{fee_code}' 没有取费基数且没有子节点")
result = 0.0
# 缓存计算结果
calculated_fees[cache_key] = result
# 从正在计算的集合中移除当前费用代码
in_calculation.remove(fee_code)
return result
# 计算取费基数
def calculate_fee_base(
fee_base: str,
cost_table: Dict[str, Any],
project_node: Dict[str, Any],
context: ZjQuantityBCLContext,
in_calculation: set = None,
calculation_strategy=None,
) -> float:
"""
计算取费基数
Args:
fee_base: 取费基数表达式
cost_table: 取费表
project_node: 项目划分节点
context: BCL上下文
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
calculation_strategy: 计算策略对象
Returns:
float: 计算结果
"""
# 初始化正在计算的集合
if in_calculation is None:
in_calculation = set()
# 检查是否是直接引用另一个费用项的代码
if fee_base in calculated_fees:
return calculated_fees[fee_base]
# 创建表达式计算器
expr_calculator = ExpressionCalculator()
parse_success = expr_calculator.parse_expression(fee_base)
if not parse_success:
print(f"测试表达式解析失败: {expr_calculator.get_last_error()}")
return None
# 获取表达式中的变量
variables = expr_calculator.variables
variable_values = {}
# 处理每个变量
for var in variables:
# 检查是否为表内费用代码
if var in calculated_fees:
# 使用缓存的计算结果
variable_values[var] = calculated_fees[var]
elif find_fee_item_by_code(cost_table, var):
# 计算表内费用
value = calculate_internal_fee(var, cost_table, project_node, context, in_calculation, calculation_strategy)
# 确保值不为 None
variable_values[var] = value if value is not None else 0.0
else:
# 处理表外变量
value = calculate_external_variable(var, context, calculation_strategy)
# 确保值不为 None
variable_values[var] = value if value is not None else 0.0
# 计算最终结果
try:
result, value = expr_calculator.evaluate(variable_values)
# 确保结果不为 None
return value if result else 0.0
except Exception as e:
print(f"计算表达式 '{fee_base}' 时出错: {e}")
return 0.0
# 计算项目节点的所有费用
def calculate_all_fees(
project_node: Dict[str, Any],
cost_table: Dict[str, Any],
json_file_path: str = None,
engineering_type: str = None,
calculation_strategy=None,
) -> Dict[str, float]:
"""
计算项目节点的所有费用
Args:
project_node: 工程量节点
cost_table: 取费表
json_file_path: JSON文件路径,用于获取工程信息
engineering_type: 工程类型,如"清单工程"或"预算工程"
calculation_strategy: 计算策略,如果为None则使用默认策略
Returns:
Dict[str, float]: 费用计算结果
"""
results = {}
# 1. 工程信息上下文 ,在这里暂时先将参数项目划分名称固定
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]
# 2. 构建数据源链式上下文
if engineering_type == "清单工程":
# 获取清单节点
bill_id = project_node.get("bill_id")
bill_name = project_node.get("bill_name")
bill_node = project_node.get("bill_node")
if not bill_node:
bill_node = {
"id": bill_id,
"GUID": bill_id,
"清单名称": bill_name,
"取费表名称": project_node.get("取费表名称"),
"清单全码": project_node.get("清单全码", ""),
"编码": project_node.get("清单编码", ""),
"单位": project_node.get("单位", ""),
}
# 使用包装函数创建清单对象
# bill_obj = create_list_from_node_with_quantity(bill_node)
bill_obj = create_list_from_node(bill_node)
# 如果清单节点有数量,确保它被保存到bill_obj
if "数量" in bill_node:
bill_obj.quantity = float(bill_node["数量"])
print(f"设置清单对象数量: {bill_obj.quantity}")
# 创建工程量对象
from bcl_utils import create_node_from_type
project_obj = create_node_from_type(project_node)
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
# 递归数据源链式上下文
billItem = BCLDataSourceItem(bill_obj)
# 设置一个显式的数量属性
# billItem.bill_quantity = float(bill_node.get("数量", 1.0))
# print(f"设置清单数据源项数量: {billItem.bill_quantity}")
QuantityItem = BCLDataSourceItem(project_obj, billItem)
bill_context = BCLDataSourceContext([billItem], dxitem_context)
context = BCLDataSourceContext([QuantityItem], bill_context)
else:
# 非清单工程 - 工程信息 -> 工程量节点
from bcl_utils import create_node_from_type
project_obj = create_node_from_type(project_node)
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
QuantityItem = BCLDataSourceItem(project_obj)
context = BCLDataSourceContext([QuantityItem], dxitem_context)
# 打印项目节点信息,用于调试
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
print(f"\n处理项目节点: {node_name}")
# 查找包含取费基数的节点
fee_base_nodes = find_fee_base_nodes(cost_table)
# 计算每个费用项
# 先计算基本费用项,再计算复合费用项
# 这里简单按照节点在列表中的顺序计算,可能需要更复杂的依赖解析
for node in fee_base_nodes:
fee_name = node.get("费用名称", node.get("name", "未知费用"))
fee_code = node.get("代码", "")
fee_base = node.get("取费基数", "")
fee_rate = node.get("费率(%)") or node.get("费率")
# 尝试转换费率为浮点数
try:
if fee_rate is None or (isinstance(fee_rate, str) and fee_rate.strip() == ""):
rate = 1.0
else:
rate = float(fee_rate) / 100.0
except (ValueError, TypeError):
print(f"无法将费率 '{fee_rate}' 转换为浮点数,使用默认值1.0")
rate = 1.0
if fee_base:
# 计算取费基数
base_value = calculate_fee_base(fee_base, cost_table, project_node, context, None, calculation_strategy)
# 应用费率
result = base_value * rate
results[fee_name] = result
# 缓存计算结果
if fee_code:
calculated_fees[fee_code] = result
elif fee_code:
# 如果没有取费基数但有代码,尝试计算
result = calculate_internal_fee(fee_code, cost_table, project_node, context, None, calculation_strategy)
results[fee_name] = result
return results
# 计算工程量取费表
##原始代码
# def calculate_quantity_fees(
# json_file_path: str, project_name: str, adjustment_type: str = None, engineering_type: str = None
# ) -> str:
# """
# 计算工程量取费表
# Args:
# json_file_path: JSON文件路径
# project_name: 项目名称
# adjustment_type: 调差类型
# engineering_type: 工程类型
# Returns:
# str: 输出文件路径
# """
# # 设置输出目录和文件名
# output_dir = "计算结果"
# os.makedirs(output_dir, exist_ok=True)
# output_file = os.path.join(
# output_dir, f"{project_name}_{adjustment_type}_{engineering_type}_calculation_results.json"
# )
# # 初始化BCL计算器
# if not init_bcl_calculator():
# return None
# # 根据工程类型获取取费表和项目节点
# if engineering_type == "预算工程":
# # 预算工程 - 获取项目划分节点的取费表
# cost_table_children = get_cost_table_children(json_file_path, project_name)
# project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type)
# if not cost_table_children or not project_children:
# print(f"未找到项目 '{project_name}' 的取费表或项目划分节点")
# return None
# elif engineering_type == "清单工程":
# # 清单工程 - 获取清单节点的取费表
# project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type)
# if not project_children:
# print(f"未找到项目 '{project_name}' 的项目划分节点")
# return None
# # 为每个工程量节点找到对应的清单节点取费表
# cost_tables = {}
# for project_node in project_children:
# node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
# # 获取清单节点信息
# bill_id = project_node.get("bill_id")
# fee_table_name = project_node.get("取费表名称")
# if not bill_id:
# print(f"工程量节点 '{node_name}' 缺少清单节点ID")
# continue
# if not fee_table_name:
# print(f"工程量节点 '{node_name}' 没有取费表名称")
# continue
# # 使用清单节点的取费表名称直接查找取费表
# if bill_id not in cost_tables:
# print(f"查找清单节点 '{project_node.get('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)
# if not cost_table:
# print(f"未找到取费表 '{fee_table_name}'")
# continue
# cost_table_children = cost_table.get("children", None)
# if not cost_table_children:
# print(f"取费表 '{fee_table_name}' 没有子节点")
# continue
# cost_tables[bill_id] = cost_table_children
# print(f"成功获取清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表")
# else:
# print(f"不支持的工程类型: {engineering_type}")
# return None
# # 初始化缓存
# calculated_fees.clear()
# # 计算每个项目节点的费用
# all_results = {}
# for project_node in project_children:
# # 为每个节点清空缓存,确保计算独立
# calculated_fees.clear()
# node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
# # 根据工程类型获取对应的取费表
# if engineering_type == "预算工程":
# # 预算工程 - 使用项目划分节点的取费表
# node_results = calculate_all_fees(
# project_node, {"children": cost_table_children}, json_file_path, engineering_type
# )
# else: # 清单工程
# # 清单工程 - 使用清单节点的取费表
# parent_id = project_node.get("parent_id")
# if parent_id and parent_id in cost_tables:
# node_results = calculate_all_fees(
# project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
# )
# else:
# print(f"无法获取工程量节点 '{node_name}' 的取费表")
# continue
# all_results[node_name] = node_results
# # 输出计算结果
# 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:
# json.dump(all_results, f, ensure_ascii=False, indent=2)
# print(f"\n计算结果已保存到 {output_file}")
# return output_file
def calculate_quantity_fees(
json_file_path: str,
project_name: str,
engineering_type: str = None,
project_guid: str = None,
calculation_strategy=None,
) -> str:
"""
计算工程量取费表
Args:
json_file_path: JSON文件路径
project_name: 项目名称
engineering_type: 工程类型
project_guid: 项目GUID,用于区分同名项目
calculation_strategy: 计算策略,如果为None则使用默认策略
Returns:
str: 输出文件路径
"""
# 如果没有提供计算策略,使用默认策略
if calculation_strategy is None:
from calculation_strategy import DefaultCalculationStrategy
calculation_strategy = DefaultCalculationStrategy()
# 设置输出目录和文件名
output_dir = "计算结果"
os.makedirs(output_dir, exist_ok=True)
# 获取项目划分节点的GUID
_, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid)
if target_node:
node_guid = target_node.get("GUID") or target_node.get("guid", "")
# 如果传入了GUID但与节点的GUID不一致,优先使用传入的GUID
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_file = os.path.join(
output_dir,
f"{safe_project_name}{safe_project_guid}_{engineering_type}_calculation_results.json",
)
# 在处理清单工程时,保存清单数量供后续使用
bill_quantities = {}
# 根据工程类型获取取费表和项目节点
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)
if not cost_table_children or not project_children:
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的取费表或项目划分节点")
return None
elif engineering_type == "清单工程":
# 清单工程 - 获取清单节点的取费表,传递project_guid参数
project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid)
if not project_children:
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的项目划分节点")
return None
# 为每个工程量节点找到对应的清单节点取费表
cost_tables = {}
for project_node in project_children:
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
# 获取清单节点信息
bill_id = project_node.get("bill_id")
fee_table_name = project_node.get("取费表名称")
if not bill_id:
print(f"工程量节点 '{node_name}' 缺少清单节点ID")
continue
if not fee_table_name:
print(f"工程量节点 '{node_name}' 没有取费表名称")
continue
# 使用清单节点的取费表名称直接查找取费表
if bill_id not in cost_tables:
print(f"查找清单节点 '{project_node.get('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)
if not cost_table:
print(f"未找到取费表 '{fee_table_name}'")
continue
cost_table_children = cost_table.get("children", None)
if not cost_table_children:
print(f"取费表 '{fee_table_name}' 没有子节点")
continue
cost_tables[bill_id] = cost_table_children
print(f"成功获取清单节点 '{project_node.get('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", {})
# 递归函数获取所有清单节点
def get_bill_nodes(node, results=None):
if results is None:
results = []
if isinstance(node, dict):
# 检查是否是清单节点
is_bill = (
node.get("类型") == "8" or node.get("类型") == "清单" or "清单名称" in node or "清单编码" in node
)
if is_bill and ("id" in node or "GUID" in node):
bill_id = node.get("id") or node.get("GUID")
if "数量" in node:
try:
quantity = float(node["数量"])
except (TypeError, ValueError):
pass
results.append((bill_id, quantity))
# 直接设置到全局变量和缓存
global _BILL_QUANTITY
_BILL_QUANTITY = quantity
bill_quantity_cache[bill_id] = quantity
# 设置环境变量
os.environ["BILL_QUANTITY"] = str(quantity)
print(f"设置清单 {bill_id} 的数量为全局变量: {quantity}")
# 递归处理子节点
if "children" in node and isinstance(node["children"], list):
for child in node["children"]:
get_bill_nodes(child, results)
return results
# 获取和设置清单数量
get_bill_nodes(bill_data)
# 如果有计算策略,设置清单数量
if calculation_strategy and hasattr(calculation_strategy, "set_bill_quantity"):
calculation_strategy.set_bill_quantity(_BILL_QUANTITY)
else:
print(f"不支持的工程类型: {engineering_type}")
return None
# 在获取 project_children 后添加预处理代码
if project_children and calculation_strategy and hasattr(calculation_strategy, "preprocess_quantity_fee_node"):
print(f"对项目节点进行预处理...")
# 递归处理所有节点
def process_node(node):
calculation_strategy.preprocess_quantity_fee_node(node)
if "children" in node and isinstance(node["children"], list):
for child in node["children"]:
process_node(child)
# 处理每个项目节点
for node in project_children:
process_node(node)
# 初始化缓存
calculated_fees.clear() # 使用全局缓存,而不是计算策略中的缓存
# 计算每个项目节点的费用
all_results = {}
for project_node in project_children:
# 为每个节点清空缓存,确保计算独立
calculated_fees.clear()
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
# 添加清单数量信息
if engineering_type == "清单工程":
bill_id = project_node.get("bill_id")
if bill_id in bill_quantities:
project_node["bill_quantity"] = bill_quantities[bill_id]
print(f"为工程量节点 '{node_name}' 设置清单数量: {bill_quantities[bill_id]}")
# 根据工程类型获取对应的取费表
if engineering_type == "预算工程":
# 预算工程 - 使用项目划分节点的取费表
node_results = calculation_strategy.calculate_all_fees(
project_node, {"children": cost_table_children}, json_file_path, engineering_type
)
else: # 清单工程
# 清单工程 - 使用清单节点的取费表
parent_id = project_node.get("parent_id")
if parent_id and parent_id in cost_tables:
node_results = calculation_strategy.calculate_all_fees(
project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
)
else:
print(f"无法获取工程量节点 '{node_name}' 的取费表")
continue
all_results[node_name] = node_results
# 输出计算结果
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:
json.dump(all_results, f, ensure_ascii=False, indent=2)
print(f"\n计算结果已保存到 {output_file}")
# 如果有计算策略,应用后处理规则
if calculation_strategy:
calculation_strategy.post_process_quantity_fees(output_file, project_name)
return output_file