上传文件

This commit is contained in:
chentianrui
2025-08-01 15:31:56 +08:00
commit 9609bb67b4
805 changed files with 982256 additions and 0 deletions
+758
View File
@@ -0,0 +1,758 @@
import json
from typing import Dict, List, Any, Optional, Tuple
from bcl_calculator import (
BCLCalculator,
BCLContext,
BCLVariant,
BCLVariantType,
BCLPrefixContext,
BCLPrefixPrevContext,
BCLDataSourceItem,
BCLDataSourceContext,
)
from 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"计算配置/{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 = "计算配置/主网/工程量/预算"
print(f"使用默认计算配置: {default_path}")
# 检查默认配置目录是否存在
if not os.path.exists(default_path):
print(f"默认计算配置目录不存在: {default_path}")
# 尝试使用原始默认配置
original_default_path = "计算配置/主网/主网预算"
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 = "计算配置/主网/工程量/预算"
print(f"尝试使用默认计算配置: {default_path}")
# 检查默认配置目录是否存在
if not os.path.exists(default_path):
print(f"默认计算配置目录不存在: {default_path}")
# 尝试使用原始默认配置
original_default_path = "计算配置/主网/主网预算"
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)