915 lines
35 KiB
Python
915 lines
35 KiB
Python
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)
|