修改费用计算代码
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
from memory_profiler import profile
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from equipment_calculation.bcl_calculator import (
|
||||
BCLCalculator,
|
||||
@@ -17,6 +18,48 @@ 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]:
|
||||
"""
|
||||
@@ -52,6 +95,9 @@ def load_json_data(json_file_path: str, json_path: str = None) -> Dict[str, Any]
|
||||
return {}
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
def create_node_from_type(node: dict[str, any]):
|
||||
"""
|
||||
根据项目划分子节点类型动态创建对应类型的对象
|
||||
@@ -60,23 +106,44 @@ def create_node_from_type(node: dict[str, any]):
|
||||
node: 项目划分子节点数据
|
||||
|
||||
Returns:
|
||||
对应类型的对象:Ration、Material或Equipment
|
||||
对应类型的对象:Ration、Material或Equipment;不支持的类型返回None
|
||||
"""
|
||||
# 获取节点类型
|
||||
node_type = node.get("类型", "")
|
||||
type_code = node.get("type", "")
|
||||
# 获取配件类型的附加判断字段(仅当是配件时使用)
|
||||
peijian_type = node.get("配件类型", "")
|
||||
|
||||
# 判断是否为“定额”类型: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 ["设备", "配件"]:
|
||||
|
||||
# 判断是否为“设备”类型
|
||||
elif node_type in ["设备", "5"] or type_code in ["设备", "5"]:
|
||||
return create_equipment_from_node(node)
|
||||
else:
|
||||
logging.warning(f"未知节点类型: 类型={node_type}, type={type_code},默认创建Ration对象")
|
||||
|
||||
# 判断是否为“一笔性费用”类型
|
||||
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:
|
||||
"""
|
||||
@@ -164,6 +231,7 @@ def create_ration_from_node(node: dict[str, any]) -> Ration:
|
||||
ration.所属定额库 = node.get("所属定额库")
|
||||
ration.专业属性 = node.get("专业属性")
|
||||
ration.地形费计算方式 = node.get("地形费计算方式")
|
||||
ration.专业类型 = node.get("调差类型")
|
||||
|
||||
# 遍历节点的所有属性,确保所有可能的字段都被设置
|
||||
for key, value in node.items():
|
||||
@@ -172,9 +240,32 @@ def create_ration_from_node(node: dict[str, any]) -> Ration:
|
||||
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:
|
||||
"""
|
||||
创建主材对象
|
||||
@@ -190,7 +281,8 @@ def create_material_from_node(node: dict[str, any]) -> Material:
|
||||
# 设置主材相关属性
|
||||
material.id = node.get("id")
|
||||
material.name = node.get("项目名称")
|
||||
material.type = node.get("类型", "主材")
|
||||
# 对象层的 type 使用派生类型,不覆盖原始字典
|
||||
material.type = _derive_moe_type(node) or "主材"
|
||||
material.供货方 = node.get("供货方")
|
||||
material.关联父级量 = node.get("关联父级量")
|
||||
material.制造长度 = node.get("制造长度")
|
||||
@@ -236,6 +328,7 @@ def create_material_from_node(node: dict[str, any]) -> Material:
|
||||
setattr(material, key, str(value) if value != "" else "")
|
||||
|
||||
material.预算价不含税 = material.单价不含税
|
||||
material.children = node.get("children")
|
||||
|
||||
return material
|
||||
|
||||
@@ -255,7 +348,8 @@ def create_equipment_from_node(node: dict[str, any]) -> Equipment:
|
||||
# 设置设备相关属性
|
||||
equipment.id = node.get("id")
|
||||
equipment.name = node.get("项目名称")
|
||||
equipment.type = node.get("类型", "设备")
|
||||
# 对象层的 type 使用派生类型,不覆盖原始字典
|
||||
equipment.type = _derive_moe_type(node) or "设备"
|
||||
equipment.供货方 = node.get("供货方")
|
||||
equipment.关联父级量 = node.get("关联父级量")
|
||||
equipment.制造长度 = node.get("制造长度")
|
||||
@@ -296,11 +390,12 @@ def create_equipment_from_node(node: dict[str, any]) -> Equipment:
|
||||
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):
|
||||
def init_bcl_calculator(software_category, engineering_type, calculation_type, project_type: Optional[str] = None):
|
||||
"""
|
||||
初始化BCL计算器
|
||||
|
||||
@@ -308,6 +403,7 @@ def init_bcl_calculator(software_category, engineering_type, calculation_type):
|
||||
software_category: 软件类别(主网/配网/技改)
|
||||
engineering_type: 工程类型(预算/清单)
|
||||
calculation_type: 计算类型(工程量/人材机)
|
||||
project_type: 项目类型(如 变电/线路 等);仅用于工程量时筛选要加载的XML文件
|
||||
|
||||
Returns:
|
||||
bool: 是否成功初始化
|
||||
@@ -352,7 +448,46 @@ def init_bcl_calculator(software_category, engineering_type, calculation_type):
|
||||
config_path = default_path
|
||||
|
||||
# 加载脚本
|
||||
result = calculator.load_scripts_dir(config_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()}")
|
||||
# 尝试使用默认配置
|
||||
@@ -404,12 +539,12 @@ def create_material_or_equipment_from_node(node: dict[str, any]) -> MaterialOrEq
|
||||
"""
|
||||
me = MaterialOrEquipment()
|
||||
|
||||
# 设置基本属性
|
||||
# 设置基本属性(type 使用派生值,不改写原始字典)
|
||||
me.id = node.get("id")
|
||||
me.编码 = node.get("编码")
|
||||
me.名称 = node.get("名称")
|
||||
me.单位 = node.get("单位")
|
||||
me.type = node.get("类型")
|
||||
me.type = _derive_moe_type(node)
|
||||
me.供货方 = node.get("供货方", "")
|
||||
me.预算价不含税 = node.get("预算价不含税")
|
||||
me.市场价不含税 = node.get("市场价不含税")
|
||||
@@ -426,12 +561,13 @@ def create_material_or_equipment_from_node(node: dict[str, any]) -> MaterialOrEq
|
||||
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
|
||||
|
||||
|
||||
@@ -550,7 +686,11 @@ class PrefixConfig:
|
||||
self.field_mappings = field_mappings or {}
|
||||
|
||||
|
||||
def create_project_contexts(json_file_path: str, prefix_configs: List[PrefixConfig] = None) -> BCLContext:
|
||||
def create_project_contexts(
|
||||
json_file_path: str,
|
||||
prefix_configs: List[PrefixConfig] = None,
|
||||
json_data: Optional[Dict[str, Any]] = None,
|
||||
) -> BCLContext:
|
||||
"""
|
||||
从JSON文件创建多个前缀上下文,并将它们链接在一起
|
||||
|
||||
@@ -577,10 +717,26 @@ def create_project_contexts(json_file_path: str, prefix_configs: List[PrefixConf
|
||||
# 创建根上下文
|
||||
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:
|
||||
# 加载数据
|
||||
data = load_json_data(json_file_path, config.json_path)
|
||||
# 加载数据:优先使用传入的 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)
|
||||
|
||||
Reference in New Issue
Block a user