import json 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() 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 {} def create_node_from_type(node: dict[str, any]): """ 根据项目划分子节点类型动态创建对应类型的对象 Args: node: 项目划分子节点数据 Returns: 对应类型的对象:Ration、Material或Equipment """ # 获取节点类型 node_type = node.get("类型", "") type_code = node.get("type", "") # 判断是否为“定额”类型:node_type 或 type_code 是 "定额" 或 "0" 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 ["设备", "配件"] or type_code in ["设备", "配件"]: return create_equipment_from_node(node) else: logging.warning(f"未知节点类型: 类型={node_type}, type={type_code},默认创建Ration对象") return create_ration_from_node(node) 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("地形费计算方式") # 遍历节点的所有属性,确保所有可能的字段都被设置 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("材料费") return ration 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("项目名称") material.type = 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("运输类型") 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.单价不含税 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("项目名称") equipment.type = 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("设备性材料") 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 "") return equipment def init_bcl_calculator(software_category, engineering_type, calculation_type): """ 初始化BCL计算器 Args: software_category: 软件类别(主网/配网/技改) engineering_type: 工程类型(预算/清单) calculation_type: 计算类型(工程量/人材机) 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 # 加载脚本 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() # 设置基本属性 me.id = node.get("id") me.编码 = node.get("编码") me.名称 = node.get("名称") me.单位 = node.get("单位") me.type = 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("数量") me.是否未计价 = node.get("是否未计价") # 遍历节点的所有属性,确保所有可能的字段都被设置 for key, value in node.items(): if hasattr(me, key) and not key.startswith("_"): setattr(me, key, str(value)) 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) -> 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 # 为每个前缀创建上下文并链接 for config in prefix_configs: # 加载数据 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)