Files
KG_generation/equipment_calculation/bcl_utils.py
T
2025-08-22 18:13:09 +08:00

915 lines
35 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
from memory_profiler import profile
from typing import Dict, List, Any, Optional, Tuple
from equipment_calculation.bcl_calculator import (
BCLCalculator,
BCLContext,
BCLVariant,
BCLVariantType,
BCLPrefixContext,
BCLPrefixPrevContext,
BCLDataSourceItem,
BCLDataSourceContext,
)
from equipment_calculation.project import Ration, MaterialOrEquipment, Material, Equipment, bill_node
from copy import deepcopy
import logging
# 全局变量
calculator = BCLCalculator()
# 项目类型到需加载XML文件名的映射(按软件分类预留)。
# 仅在 calculation_type == "工程量" 时生效;人材机仍按目录全部加载。
# 注意:文件名需与目录中文件名完全匹配。
PROJECT_TYPE_XML_MAP: Dict[str, Dict[str, List[str]]] = {
# 主网软件分类
"主网": {
# 示例:变电需要读取以下文件(用户后续可补充/修改)
# 其他项目类型占位
# "输电": [],
# "线路": [],
},
# 配网软件分类
"配网": {
# 预留:用户后续填充
# "配网项目类型示例": ["xxx.xml"],
},
# 技改软件分类
"技改": {
"变电": [
"变量计算配置.xml",
"变量计算配置(变电).xml",
"定额基本信息费用计算.xml",
"工程量统计配置.xml",
"材机分析配置.xml",
],
"线路": [
"变量计算配置.xml",
"变量计算配置(线路).xml",
"定额基本信息费用计算.xml",
"工程量统计配置.xml",
"材机分析配置.xml",
],
"配网": [
"变量计算配置.xml",
"变量计算配置(配网).xml",
"定额基本信息费用计算.xml",
"工程量统计配置.xml",
"材机分析配置.xml",
],
},
}
def load_json_data(json_file_path: str, json_path: str = None) -> Dict[str, Any]:
"""
从JSON文件中加载指定路径的数据
Args:
json_file_path: JSON文件路径
json_path: JSON路径表达式,格式如 "projectData.projectInfo"None表示返回整个JSON
Returns:
Dict[str, Any]: 加载的数据
"""
try:
with open(json_file_path, "r", encoding="utf-8") as f:
data = json.load(f)
if not json_path:
return data
# 按路径逐级获取数据
parts = json_path.split(".")
result = data
for part in parts:
if isinstance(result, dict) and part in result:
result = result.get(part, {})
else:
print(f"JSON路径 '{json_path}' 中的部分 '{part}' 不存在")
return {}
return result
except Exception as e:
print(f"加载JSON数据时出错: {e}")
return {}
import logging
def create_node_from_type(node: dict[str, any]):
"""
根据项目划分子节点类型动态创建对应类型的对象
Args:
node: 项目划分子节点数据
Returns:
对应类型的对象:Ration、Material或Equipment;不支持的类型返回None
"""
# 获取节点类型
node_type = node.get("类型", "")
type_code = node.get("type", "")
# 获取配件类型的附加判断字段(仅当是配件时使用)
peijian_type = node.get("配件类型", "")
# 判断是否为“定额”类型
if node_type in ["定额", "0"] or type_code in ["定额", "0"]:
return create_ration_from_node(node)
# 判断是否为“主材”类型
elif node_type in ["主材", "1"] or type_code in ["主材", "1"]:
return create_material_from_node(node)
# 判断是否为“设备”类型
elif node_type in ["设备", "5"] or type_code in ["设备", "5"]:
return create_equipment_from_node(node)
# 判断是否为“一笔性费用”类型
elif node_type in ["一笔性费用", ""] or type_code in ["一笔性费用", ""]:
return create_ration_from_node(node)
# 特殊处理:“配件”需要根据“配件类型”进一步判断
elif node_type == "配件" or type_code == "配件":
if peijian_type == "主材":
return create_material_from_node(node)
elif peijian_type == "配件":
return create_equipment_from_node(node)
else:
logging.warning(f"配件类型未识别: 配件类型={peijian_type}, 节点类型={node_type}, type={type_code},跳过")
return None
else:
logging.warning(f"未知节点类型: 类型={node_type}, type={type_code},跳过")
return None
def create_list_from_node(node: dict[str, any]) -> bill_node:
"""
创建定额对象
Args:
node: 项目划分子节点数据
Returns:
list: 创建的清单对象
"""
bill = bill_node()
# 设置定额相关属性
bill.清单名称 = node.get("清单名称")
bill.清单全码 = node.get("清单全码")
bill.编码 = node.get("编码")
bill.单位 = node.get("单位")
bill.计算式 = node.get("计算式")
bill.数量 = node.get("数量")
bill.取费表 = node.get("取费表")
bill.合价 = node.get("合价")
bill.工作内容 = node.get("工作内容")
bill.类型 = node.get("类型")
bill.type = node.get("type")
bill.项目特征 = node.get("项目特征")
bill.资源库名称 = node.get("资源库名称")
bill.计算规则 = node.get("计算规则")
bill.单价 = node.get("单价")
bill.一次性费用 = node.get("一次性费用")
bill.取费表名称 = node.get("取费表名称")
bill.取费表类型 = node.get("取费表类型")
bill.单价不含税 = node.get("单价不含税")
bill.合价不含税 = node.get("合价不含税")
bill.专业类型 = node.get("专业类型")
bill.关联父级量 = node.get("关联父级量")
bill.含量 = node.get("含量")
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(bill, key) and not key.startswith("_"):
if value is not None:
setattr(bill, key, str(value) if value != "" else "")
return bill
def create_ration_from_node(node: dict[str, any]) -> Ration:
"""
创建定额对象
Args:
node: 项目划分子节点数据
Returns:
Ration: 创建的定额对象
"""
ration = Ration()
# 设置定额相关属性
ration.id = node.get("id")
ration.type = node.get("类型")
ration.name = node.get("项目名称")
ration.编码 = node.get("编码")
ration.单位 = node.get("单位")
ration.数量 = node.get("数量")
ration.资源库名称 = node.get("资源库名称")
ration.计算式 = node.get("计算式")
ration.人工费 = node.get("人工费")
ration.机械费 = node.get("机械费")
ration.甲供材料费不含税 = node.get("甲供材料费不含税")
ration.材料费 = node.get("材料费")
ration.定额系数 = node.get("定额系数")
ration.人工系数 = node.get("人工系数")
ration.材料系数 = node.get("材料系数")
ration.机械系数 = node.get("机械系数")
ration.定额范围 = node.get("定额范围")
ration.定额章节名称 = node.get("定额章节名称")
ration.费用类型 = node.get("费用类型")
ration.甲供材料费含税 = node.get("甲供材料费含税")
ration.投标合价 = node.get("投标合价")
ration.其中甲供材料费 = node.get("其中:甲供材料费")
ration.合价不含税 = node.get("合价不含税")
ration.基价 = node.get("基价")
ration.所属定额库 = node.get("所属定额库")
ration.专业属性 = node.get("专业属性")
ration.地形费计算方式 = node.get("地形费计算方式")
ration.专业类型 = node.get("调差类型")
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(ration, key) and not key.startswith("_"):
if value is not None:
setattr(ration, key, str(value) if value != "" else "")
ration.乙供材料费不含税 = node.get("材料费")
ration.children = node.get("children")
return ration
def _derive_moe_type(node: dict[str, any]) -> str:
"""
基于节点字段推导人/材/机对象在BCL中的 type,不修改原始节点:
- 若 类型/type 为数值代码,映射到中文
- 若 类型/type 为“配件”,根据 配件类型:主材->主材;配件->设备
- 其他返回原值
"""
t = node.get("类型") or node.get("type")
pj = node.get("配件类型")
if t in ("1", "主材"):
return "主材"
if t in ("5", "设备"):
return "设备"
if t == "配件":
if pj == "主材":
return "主材"
if pj == "配件":
return "设备"
return t or ""
def create_material_from_node(node: dict[str, any]) -> Material:
"""
创建主材对象
Args:
node: 项目划分子节点数据
Returns:
Material: 创建的主材对象
"""
material = Material()
# 设置主材相关属性
material.id = node.get("id")
material.name = node.get("项目名称")
# 对象层的 type 使用派生类型,不覆盖原始字典
material.type = _derive_moe_type(node) or "主材"
material.供货方 = node.get("供货方")
material.关联父级量 = node.get("关联父级量")
material.制造长度 = node.get("制造长度")
material.单位 = node.get("单位")
material.单重 = node.get("单重")
material.增值税率 = node.get("增值税率")
material.数量 = node.get("数量")
material.损耗率 = node.get("损耗率")
material.规格型号 = node.get("规格型号")
material.线重 = node.get("线重")
material.截面积 = node.get("截面积")
material.市场价不含税 = node.get("市场价不含税")
material.市场价含税 = node.get("市场价含税")
material.资源库名称 = node.get("资源库名称")
material.编码 = node.get("编码")
material.集中配送 = node.get("集中配送")
material.投标数量 = node.get("投标数量")
material.投标单价 = node.get("投标单价")
material.特征段 = node.get("特征段")
material.颜色标记 = node.get("颜色标记")
material.单价不含税 = node.get("单价不含税")
material.单价含税 = node.get("单价含税")
material.结算市场价不含税 = node.get("结算市场价不含税")
material.结算市场价含税 = node.get("结算市场价含税")
material.基准价不含税 = node.get("基准价不含税")
material.基准价含税 = node.get("基准价含税")
material.费用类型 = node.get("费用类型")
material.合价含税 = node.get("合价含税")
material.合价不含税 = node.get("合价不含税")
material.保管 = node.get("保管")
material.卸车 = node.get("卸车")
material.暂估价 = node.get("暂估价")
material.每件重 = node.get("每件重")
material.设备性材料 = node.get("设备性材料")
material.调差类型 = node.get("调差类型")
material.运输类型 = node.get("运输类型")
material.拆分 = node.get("拆分")
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(material, key) and not key.startswith("_"):
if value is not None:
setattr(material, key, str(value) if value != "" else "")
material.预算价不含税 = material.单价不含税
material.children = node.get("children")
return material
def create_equipment_from_node(node: dict[str, any]) -> Equipment:
"""
创建设备对象
Args:
node: 项目划分子节点数据
Returns:
Equipment: 创建的设备对象
"""
equipment = Equipment()
# 设置设备相关属性
equipment.id = node.get("id")
equipment.name = node.get("项目名称")
# 对象层的 type 使用派生类型,不覆盖原始字典
equipment.type = _derive_moe_type(node) or "设备"
equipment.供货方 = node.get("供货方")
equipment.关联父级量 = node.get("关联父级量")
equipment.制造长度 = node.get("制造长度")
equipment.单位 = node.get("单位")
equipment.单重 = node.get("单重")
equipment.增值税率 = node.get("增值税率")
equipment.数量 = node.get("数量")
equipment.损耗率 = node.get("损耗率")
equipment.规格型号 = node.get("规格型号")
equipment.线重 = node.get("线重")
equipment.市场价不含税 = node.get("市场价不含税")
equipment.市场价含税 = node.get("市场价含税")
equipment.编码 = node.get("编码")
equipment.设备类型 = node.get("设备类型")
equipment.资源库名称 = node.get("资源库名称")
equipment.集中配送 = node.get("集中配送")
equipment.运杂费率 = node.get("运杂费率")
equipment.投标数量 = node.get("投标数量")
equipment.投标单价 = node.get("投标单价")
equipment.特征段 = node.get("特征段")
equipment.颜色标记 = node.get("颜色标记")
equipment.单价不含税 = node.get("单价不含税")
equipment.单价含税 = node.get("单价含税")
equipment.合价含税 = node.get("合价含税")
equipment.合价不含税 = node.get("合价不含税")
equipment.保管 = node.get("保管")
equipment.卸车 = node.get("卸车")
equipment.工程量索引 = node.get("工程量索引")
equipment.截面积 = node.get("截面积")
equipment.拆分 = node.get("拆分")
equipment.暂估价 = node.get("暂估价")
equipment.计算式 = node.get("计算式")
equipment.设备性材料 = node.get("设备性材料")
equipment.调差类型 = node.get("调差类型")
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(equipment, key) and not key.startswith("_"):
if value is not None:
setattr(equipment, key, str(value) if value != "" else "")
equipment.children = node.get("children")
return equipment
def init_bcl_calculator(software_category, engineering_type, calculation_type, project_type: Optional[str] = None):
"""
初始化BCL计算器
Args:
software_category: 软件类别(主网/配网/技改)
engineering_type: 工程类型(预算/清单)
calculation_type: 计算类型(工程量/人材机)
project_type: 项目类型(如 变电/线路 等);仅用于工程量时筛选要加载的XML文件
Returns:
bool: 是否成功初始化
"""
try:
# 构建计算配置路径
# 格式:计算配置/软件类型(主网,配网,技改)/计算类型(工程量,人材机)/工程类型(预算,清单)
config_path = f"equipment_calculation/计算配置/{software_category}/{calculation_type}/{engineering_type}"
print(f"加载计算配置: {config_path}")
# 检查目录是否存在
import os
if not os.path.exists(config_path):
print(f"计算配置目录不存在: {config_path}")
# 尝试创建目录
try:
os.makedirs(config_path, exist_ok=True)
print(f"已创建计算配置目录: {config_path}")
except Exception as e:
print(f"创建计算配置目录失败: {e}")
# 如果目录不存在,使用默认配置
default_path = "equipment_calculation/计算配置/主网/工程量/预算"
print(f"使用默认计算配置: {default_path}")
# 检查默认配置目录是否存在
if not os.path.exists(default_path):
print(f"默认计算配置目录不存在: {default_path}")
# 尝试使用原始默认配置
original_default_path = "equipment_calculation/计算配置/主网/主网预算"
print(f"尝试使用原始默认计算配置: {original_default_path}")
if os.path.exists(original_default_path):
config_path = original_default_path
else:
print(f"原始默认计算配置目录不存在: {original_default_path}")
return False
else:
config_path = default_path
# 加载脚本
# 对于工程量,若提供了project_type且存在映射,则仅加载映射中指定的XML
result = True
use_filtered_files = False
try:
import os
if calculation_type == "工程量" and project_type:
target_list = PROJECT_TYPE_XML_MAP.get(software_category, {}).get(project_type) or []
if target_list:
use_filtered_files = True
print(
f"根据项目类型筛选加载XML: 软件={software_category}, 项目类型={project_type}, 目标文件={target_list}"
)
all_ok = True
missing_files = []
for filename in target_list:
xml_path = os.path.join(config_path, filename)
if not os.path.exists(xml_path):
missing_files.append(filename)
all_ok = False
continue
if not calculator.load_script(xml_path):
print(f"加载脚本失败: {xml_path}, 错误: {calculator.get_last_error()}")
all_ok = False
else:
print(f"成功加载脚本: {xml_path}")
if missing_files:
print(f"警告: 以下映射指定的文件在目录中未找到: {missing_files}")
result = all_ok
else:
print(f"未找到映射项,按目录全部加载: 软件={software_category}, 项目类型={project_type}")
except Exception as _e:
# 映射/筛选流程异常时,退回目录加载
print(f"按项目类型筛选加载发生异常,退回目录加载: {_e}")
use_filtered_files = False
if not use_filtered_files:
result = calculator.load_scripts_dir(config_path)
if False == result:
print(f"加载脚本错误: {calculator.get_last_error()}")
# 尝试使用默认配置
default_path = "equipment_calculation/计算配置/主网/工程量/预算"
print(f"尝试使用默认计算配置: {default_path}")
# 检查默认配置目录是否存在
if not os.path.exists(default_path):
print(f"默认计算配置目录不存在: {default_path}")
# 尝试使用原始默认配置
original_default_path = "equipment_calculation/计算配置/主网/主网预算"
print(f"尝试使用原始默认计算配置: {original_default_path}")
if os.path.exists(original_default_path):
result = calculator.load_scripts_dir(original_default_path)
if False == result:
print(f"加载原始默认脚本错误: {calculator.get_last_error()}")
return False
else:
print(f"成功加载原始默认脚本: {original_default_path}")
else:
print(f"原始默认计算配置目录不存在: {original_default_path}")
return False
else:
result = calculator.load_scripts_dir(default_path)
if False == result:
print(f"加载默认脚本错误: {calculator.get_last_error()}")
return False
else:
print(f"成功加载默认脚本: {default_path}")
else:
print(f"成功加载脚本: {config_path}")
return True
except Exception as e:
print(f"初始化BCL计算器错误: {e}")
return False
def create_material_or_equipment_from_node(node: dict[str, any]) -> MaterialOrEquipment:
"""
根据项目划分子节点动态创建 MaterialOrEquipment 对象
Args:
node: 人材机子节点数据
Returns:
MaterialOrEquipment: 创建的人材机对象
"""
me = MaterialOrEquipment()
# 设置基本属性(type 使用派生值,不改写原始字典)
me.id = node.get("id")
me.编码 = node.get("编码")
me.名称 = node.get("名称")
me.单位 = node.get("单位")
me.type = _derive_moe_type(node)
me.供货方 = node.get("供货方", "")
me.预算价不含税 = node.get("预算价不含税")
me.市场价不含税 = node.get("市场价不含税")
me.预算价含税 = node.get("预算价含税")
me.市场价含税 = node.get("市场价含税")
me.结算预算价不含税 = node.get("结算预算价不含税")
me.结算市场价不含税 = node.get("结算市场价不含税")
me.结算预算价含税 = node.get("结算预算价含税")
me.结算市场价含税 = node.get("结算市场价含税")
me.暂估价 = node.get("暂估价", "")
me.拆分 = node.get("拆分", "")
me.全口径市场价不含税 = node.get("全口径市场价不含税")
me.全口径市场价含税 = node.get("全口径市场价含税")
me.商品砼 = node.get("商品砼", "")
me.数量 = node.get("数量")
me.是否未计价 = node.get("是否未计价")
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(me, key) and not key.startswith("_"):
setattr(me, key, str(value))
me.children = node.get("children")
return me
# 不需要派生,写一个函数去生成上下文
class ZjProjectBCLContext(BCLPrefixPrevContext):
"""
工程信息上下文类,用于从JSON文件中读取工程信息并提供给BCL计算器
"""
def __init__(self, prefix="@工程信息", valueDict=None, prevContext=None, project_division_name=None, **kwargs):
# 创建空字典作为valueDict参数
super().__init__(prefix=prefix, valueDict=valueDict or {}, prevContext=prevContext)
self.project_division_name = project_division_name
def add_variable(self, name, value):
"""
添加变量到上下文中
Args:
name: 变量名
value: 变量值
"""
self._childContext.variables[name] = BCLVariant(value)
def add_variables_from_dict(self, data_dict: Dict[str, Any]):
"""
从字典批量添加变量到上下文中
Args:
data_dict: 数据字典或列表
"""
# 如果是列表,直接添加为"属性"变量
if isinstance(data_dict, list):
self.add_variable("属性", data_dict)
return
# 原有的字典处理逻辑
for key, value in data_dict.items():
if isinstance(value, dict):
continue # 跳过嵌套字典
# 尝试将字符串转换为数字
if isinstance(value, str) and value.replace(".", "", 1).isdigit():
try:
value = float(value)
except (ValueError, TypeError):
pass
self.add_variable(key, value)
def _get_variable_value(self, var_name: str) -> BCLVariant:
"""
获取变量值,支持直接查找价差系数属性
Args:
var_name: 变量名,格式为'对象.属性'
Returns:
BCLVariant: 变量值
"""
# 首先尝试使用父类的方法获取变量
result = super()._get_variable_value(var_name)
# 如果是价差系数相关的变量
if var_name.startswith("@价差系数."):
# 提取属性名称
attr_name = var_name.split(".", 1)[1]
# 获取价差系数数据
price_diff_data = None
if (
self._prefix == "@价差系数"
and hasattr(self, "_childContext")
and hasattr(self._childContext, "variables")
):
if "属性" in self._childContext.variables:
price_diff_data = self._childContext.variables["属性"].value
# 如果没有在当前上下文找到,尝试从父上下文查找
if price_diff_data is None and self._prevContext is not None:
prev_ctx = self._prevContext
while prev_ctx is not None:
if isinstance(prev_ctx, ZjProjectBCLContext) and prev_ctx._prefix == "@价差系数":
if hasattr(prev_ctx, "_childContext") and hasattr(prev_ctx._childContext, "variables"):
if "属性" in prev_ctx._childContext.variables:
price_diff_data = prev_ctx._childContext.variables["属性"].value
break
prev_ctx = prev_ctx._prevContext
# 如果找到了价差系数数据,在属性列表中查找匹配项
if price_diff_data is not None and isinstance(price_diff_data, list):
for item in price_diff_data:
if isinstance(item, dict) and item.get("名称") == attr_name:
return BCLVariant(item.get("值", ""))
# 如果找不到匹配项,返回空值
print(f"警告: 未找到价差系数属性 '{attr_name}'")
return BCLVariant("")
return result
# 前缀配置定义
class PrefixConfig:
def __init__(self, prefix: str, json_path: str = None, field_mappings: Dict[str, str] = None):
"""
前缀配置类
Args:
prefix: 前缀名称,如 "@工程信息"
json_path: JSON路径表达式,如 "projectData.projectInfo"
field_mappings: 字段映射,键为JSON字段名,值为BCL变量名
"""
self.prefix = prefix
self.json_path = json_path
self.field_mappings = field_mappings or {}
def create_project_contexts(
json_file_path: str,
prefix_configs: List[PrefixConfig] = None,
json_data: Optional[Dict[str, Any]] = None,
) -> BCLContext:
"""
从JSON文件创建多个前缀上下文,并将它们链接在一起
Args:
json_file_path: JSON文件路径
prefix_configs: 前缀配置列表,None时使用默认配置
Returns:
BCLContext: 链接好的上下文对象
"""
try:
# 使用默认配置(如果未提供)
if prefix_configs is None:
prefix_configs = [
# 工程信息前缀
PrefixConfig(prefix="@工程信息", json_path="projectData.projectInfo"),
# 调差系数前缀
PrefixConfig(
prefix="@价差系数",
json_path="projectData.价差系数",
),
]
# 创建根上下文
root_context = None
# 简单路径解析器:从已有 json_data 中按路径提取
def _get_from_data(data_src: Dict[str, Any], path: Optional[str]):
if not path:
return data_src or {}
parts = path.split(".")
cur = data_src or {}
for p in parts:
if isinstance(cur, dict) and p in cur:
cur = cur.get(p, {})
else:
return {}
return cur
# 为每个前缀创建上下文并链接
for config in prefix_configs:
# 加载数据:优先使用传入的 json_data,否则从文件按路径读取
if json_data is not None:
data = _get_from_data(json_data, config.json_path)
else:
data = load_json_data(json_file_path, config.json_path)
# 创建上下文
context = ZjProjectBCLContext(prefix=config.prefix, prevContext=root_context)
# 如果有字段映射,应用映射
if config.field_mappings:
mapped_data = {}
for bcl_name, json_name in config.field_mappings.items():
if json_name in data:
mapped_data[bcl_name] = data[json_name]
context.add_variables_from_dict(mapped_data)
else:
# 直接添加所有数据
context.add_variables_from_dict(data)
# 更新根上下文
root_context = context
return root_context
except Exception as e:
print(f"创建工程上下文时出错: {e}")
# 返回一个空的上下文
return ZjProjectBCLContext()
class ZjBillBCLContext(BCLPrefixPrevContext):
def __init__(self, prefix=None, valueDict=None, prevContext=None, **kwargs):
# 正确调用父类构造函数,提供必要的参数
super().__init__(prefix=prefix, valueDict=valueDict or {}, prevContext=prevContext)
class ZjQuantityBCLContext(BCLPrefixPrevContext):
def __init__(self, node_data=None, json_file_path=None, prevContext=None, **kwargs):
# 只传递BCLPrefixContext需要的参数
super().__init__(prefix="node", valueDict={}, prevContext=prevContext)
# 保存json_file_path作为实例变量
self.json_file_path = json_file_path
if node_data:
node = create_node_from_type(node_data)
# 添加项目特定变量
self.variables["source"] = BCLVariant([node])
def create_parent_ration(parent_node=None, project_node=None):
"""
创建父级定额对象
Args:
parent_node: 父级节点数据
project_node: 项目节点数据
Returns:
Ration: 创建的父级定额对象
"""
if parent_node:
parent_ration = create_ration_from_node(parent_node)
elif project_node:
parent_ration = create_ration_from_node(project_node)
else:
parent_ration = create_ration_from_node({})
# 确保父级节点中有必要的系数
if not hasattr(parent_ration, "定额系数") or not parent_ration.定额系数:
parent_ration.定额系数 = "1.0"
if not hasattr(parent_ration, "人工系数") or not parent_ration.人工系数:
parent_ration.人工系数 = "1.0"
if not hasattr(parent_ration, "材料系数") or not parent_ration.材料系数:
parent_ration.材料系数 = "1.0"
if not hasattr(parent_ration, "机械系数") or not parent_ration.机械系数:
parent_ration.机械系数 = "1.0"
# 确保父级节点中的数量是有效的数值
if not hasattr(parent_ration, "数量") or not parent_ration.数量:
parent_ration.数量 = "0.0"
return parent_ration
class ZjMaterialOrEquipmentBCLContext(BCLPrefixContext):
def __init__(self, node_data=None, parent_node=None, prevContext=None, **kwargs):
"""
初始化人材机上下文
Args:
node_data: 人材机节点,可以是单个节点或节点列表
parent_node: 父级定额节点
prevContext: 上一级上下文(定额上下文)
**kwargs: 其他参数
"""
# 调用父类构造函数
super().__init__(prefix="node", valueDict={}, prevContext=prevContext, **kwargs)
# 创建父级定额
self.parent_ration = create_parent_ration(parent_node)
# 创建人材机对象列表
self.items = []
if node_data:
if isinstance(node_data, list):
# 如果传入的是列表,处理每个人材机节点
self.items = [create_material_or_equipment_from_node(node) for node in node_data]
else:
# 如果传入的是单个节点
self.items = [create_material_or_equipment_from_node(node_data)]
# 初始化 parent_variables(用于支持 parent.xxx 表达式)
self.parent_variables = {}
for key, value in self.parent_ration.__dict__.items():
if value is None:
value = ""
self.parent_variables[key] = BCLVariant(value)
# 设置上下文变量
self.variables["items"] = BCLVariant(self.items)
self.variables["curnode"] = BCLVariant(self.items)
self.variables["source"] = BCLVariant(self.items)
self.variables["parent"] = BCLVariant([self.parent_ration])
# 添加调试信息
print(f"父级节点数量: {self.parent_ration.数量}")
print(f"父级节点定额系数: {self.parent_ration.定额系数}")
print(f"父级节点人工系数: {self.parent_ration.人工系数}")
print(f"父级节点材料系数: {self.parent_ration.材料系数}")
print(f"父级节点机械系数: {self.parent_ration.机械系数}")
if self.items:
print(f"人材机节点类型: {self.items[0].type}")
print(f"人材机节点数量: {self.items[0].数量}")
def _get_variable_value(self, var_name: str) -> BCLVariant:
# 添加调试信息
print(f"获取变量: {var_name}")
if var_name.startswith("@"):
# 对于全局变量,尝试从上下文链中获取
result = super()._get_variable_value(var_name)
print(f" 全局变量 {var_name} = {result.value if result else None}")
return result
if "." in var_name:
obj_name, attr = var_name.split(".", maxsplit=1)
if obj_name == "parent":
# 确保父级变量存在
if attr not in self.parent_variables:
print(f" 警告: 父级变量 '{attr}' 不存在,返回默认值")
if attr in ["数量", "定额系数", "人工系数", "材料系数", "机械系数"]:
return BCLVariant("1.0")
return BCLVariant("")
result = self.parent_variables.get(attr)
print(f" 父级变量 {attr} = {result.value if result else None}")
return result
elif obj_name in ["curnode", "items"]:
items = self.variables[obj_name].value
if isinstance(items, list) and len(items) > 0:
item = items[0]
val = getattr(item, attr, None)
result = BCLVariant(val)
print(f" 项目变量 {obj_name}.{attr} = {result.value if result else None}")
return result
else:
print(f" 警告: {obj_name} 为空或不是列表")
return BCLVariant("")
else:
if var_name in self.variables:
result = self.variables[var_name]
print(f" 上下文变量 {var_name} = {result.value if result else None}")
return result
# 如果在本地找不到变量,尝试从上下文链中获取
print(f" 尝试从上下文链中获取变量 {var_name}")
return super()._get_variable_value(var_name)