增加了知识图谱导出excel
This commit is contained in:
@@ -10,7 +10,7 @@ import copy
|
||||
|
||||
# 配置logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
handlers=[logging.FileHandler("bcl_calculator.log"), logging.StreamHandler()],
|
||||
)
|
||||
|
||||
@@ -10,6 +10,16 @@ class CalculationStrategy(ABC):
|
||||
每种软件类型可以提供自己的实现,以修改计算过程中的任何规则。
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_output_dir(self) -> str:
|
||||
"""
|
||||
获取输出目录路径
|
||||
|
||||
Returns:
|
||||
str: 输出目录路径
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def calculate_fee_base(
|
||||
self,
|
||||
@@ -243,6 +253,17 @@ class DefaultCalculationStrategy(CalculationStrategy):
|
||||
# 缓存已计算过的费用
|
||||
self.calculated_fees = {}
|
||||
|
||||
# 默认输出目录
|
||||
self.output_dir = "计算结果"
|
||||
|
||||
def get_output_dir(self) -> str:
|
||||
"""获取输出目录路径"""
|
||||
return self.output_dir
|
||||
|
||||
def set_output_dir(self, output_dir: str) -> None:
|
||||
"""设置输出目录路径"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def calculate_fee_base(
|
||||
self,
|
||||
fee_base: str,
|
||||
|
||||
@@ -23,6 +23,8 @@ class CalculatorBase(ABC):
|
||||
self.config_dir = software_type.config_dir
|
||||
# 使用默认计算策略
|
||||
self.calculation_strategy = self.create_calculation_strategy()
|
||||
# 默认输出目录
|
||||
self.output_dir = os.path.join("计算结果", self.software_type.name)
|
||||
|
||||
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||
"""
|
||||
@@ -41,13 +43,24 @@ class CalculatorBase(ABC):
|
||||
|
||||
def get_output_dir(self) -> str:
|
||||
"""获取输出目录路径"""
|
||||
base_dir = "计算结果"
|
||||
return os.path.join(base_dir, self.software_type.name)
|
||||
return self.output_dir
|
||||
|
||||
def set_output_dir(self, output_dir: str) -> None:
|
||||
"""
|
||||
设置输出目录路径
|
||||
|
||||
Args:
|
||||
output_dir: 输出目录路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
# 如果计算策略支持设置输出目录,也设置计算策略的输出目录
|
||||
if hasattr(self.calculation_strategy, "set_output_dir"):
|
||||
self.calculation_strategy.set_output_dir(output_dir)
|
||||
|
||||
def calculate_quantity_fee_tables(
|
||||
self,
|
||||
json_file_path: str,
|
||||
project_name: str,
|
||||
project_name: str = None,
|
||||
) -> None:
|
||||
"""
|
||||
计算工程量取费表
|
||||
|
||||
@@ -356,7 +356,8 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g
|
||||
)
|
||||
|
||||
node_name = node.get("清单名称", node.get("项目名称", "未命名"))
|
||||
node_id = node.get("id", node.get("GUID", "未知ID"))
|
||||
# 强制使用GUID而不是ID
|
||||
node_id = node.get("GUID") or node.get("guid") or node.get("id", "未知ID")
|
||||
current_path = f"{parent_path}/{node_name}" if parent_path else node_name
|
||||
|
||||
if is_bill_node:
|
||||
@@ -386,7 +387,8 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g
|
||||
|
||||
# 处理每个清单节点
|
||||
for bill_node in bill_nodes:
|
||||
bill_id = bill_node.get("id") or bill_node.get("GUID")
|
||||
# 强制使用GUID而不是ID
|
||||
bill_id = bill_node.get("GUID") or bill_node.get("guid") or bill_node.get("id")
|
||||
bill_name = bill_node.get("清单名称", bill_node.get("项目名称", "未命名清单"))
|
||||
bill_children = bill_node.get("children", [])
|
||||
|
||||
@@ -400,7 +402,9 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g
|
||||
# 为每个工程量节点设置清单节点信息
|
||||
for node in bill_quantity_nodes:
|
||||
# 存储关键信息而不是整个对象
|
||||
node["bill_id"] = bill_id
|
||||
# 强制使用GUID而不是ID
|
||||
node["bill_guid"] = bill_id # 新增GUID字段
|
||||
node["bill_id"] = bill_id # 保持兼容性
|
||||
node["bill_name"] = bill_name
|
||||
node["取费表名称"] = bill_node.get("取费表名称", bill_node.get("取费表", ""))
|
||||
# 设置parent_id以保持兼容性
|
||||
@@ -448,7 +452,9 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g
|
||||
# 在项目中查找父节点
|
||||
def find_node_by_id(node_list, node_id):
|
||||
for n in node_list:
|
||||
if n.get("id") == node_id or n.get("GUID") == node_id:
|
||||
# 强制使用GUID而不是ID进行匹配
|
||||
node_guid = n.get("GUID") or n.get("guid") or n.get("id")
|
||||
if node_guid == node_id:
|
||||
return n
|
||||
if "children" in n and n["children"]:
|
||||
found = find_node_by_id(n["children"], node_id)
|
||||
@@ -471,10 +477,13 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g
|
||||
)
|
||||
|
||||
if is_bill_node:
|
||||
bill_id = parent_node.get("id") or parent_node.get("GUID")
|
||||
# 强制使用GUID而不是ID
|
||||
bill_id = parent_node.get("GUID") or parent_node.get("guid") or parent_node.get("id")
|
||||
bill_name = parent_node.get("清单名称", parent_node.get("项目名称", "未命名清单"))
|
||||
|
||||
node["bill_id"] = bill_id
|
||||
# 强制使用GUID而不是ID
|
||||
node["bill_guid"] = bill_id # 新增GUID字段
|
||||
node["bill_id"] = bill_id # 保持兼容性
|
||||
node["bill_name"] = bill_name
|
||||
node["取费表名称"] = parent_node.get("取费表名称", parent_node.get("取费表", ""))
|
||||
# 设置parent_id以保持兼容性
|
||||
|
||||
+174
-202
@@ -1,10 +1,4 @@
|
||||
"""
|
||||
第二步:计算bcl结果
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import re
|
||||
import json
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from equipment_calculation.software_types import (
|
||||
@@ -16,78 +10,23 @@ from equipment_calculation.software_types import (
|
||||
from equipment_calculation.software_calculators import get_calculator
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="工程量取费和人材机合价计算程序")
|
||||
|
||||
# 软件类型参数
|
||||
parser.add_argument(
|
||||
"--category", choices=["主网", "配网", "技改"], default="主网", help="软件类别(主网/配网/技改)"
|
||||
)
|
||||
|
||||
parser.add_argument("--engineering-type", choices=["预算", "清单"], default="清单", help="工程类型(预算/清单)")
|
||||
|
||||
# 计算类型参数
|
||||
parser.add_argument(
|
||||
"--calculate",
|
||||
choices=["all", "quantity", "resource"],
|
||||
default="quantity",
|
||||
help="计算类型(all: 全部, quantity: 工程量取费, resource: 人材机合价)",
|
||||
)
|
||||
|
||||
# 项目参数
|
||||
parser.add_argument("--project", help="项目名称,如果不指定则处理所有项目")
|
||||
|
||||
parser.add_argument("--adjustment-type", default="调差", help="调差类型,默认为'调差'")
|
||||
|
||||
# 输入文件参数
|
||||
parser.add_argument(
|
||||
"--input-file",
|
||||
default="测试案例/主网清单变电.json",
|
||||
help="输入JSON文件路径,如果不指定则使用默认路径",
|
||||
)
|
||||
|
||||
# 添加输入和输出文件夹参数
|
||||
parser.add_argument("--input-folder", help="输入文件夹路径,包含要处理的JSON文件")
|
||||
parser.add_argument("--output-folder", default="计算结果", help="输出文件夹路径,用于保存计算结果")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def parse_filename(filename: str) -> Tuple[str, str]:
|
||||
"""
|
||||
从文件名中解析软件类别和工程类型(备用方法)
|
||||
|
||||
:param filename: JSON文件名,例如"主网预算变电.json"
|
||||
:return: (category, engineering_type) 元组,例如 ("主网", "预算")
|
||||
"""
|
||||
# 移除扩展名
|
||||
basename = os.path.splitext(filename)[0]
|
||||
|
||||
# 查找类别(主网/配网/技改)
|
||||
category = None
|
||||
for cat in ["主网", "配网", "技改"]:
|
||||
if cat in basename:
|
||||
category = cat
|
||||
break
|
||||
|
||||
# 查找工程类型(预算/清单)
|
||||
engineering_type = None
|
||||
for eng_type in ["预算", "清单"]:
|
||||
if eng_type in basename:
|
||||
engineering_type = eng_type
|
||||
break
|
||||
|
||||
# 如果未找到,使用默认值
|
||||
if not category:
|
||||
print(f"警告: 无法从文件名 '{filename}' 中解析软件类别,使用默认值 '主网'")
|
||||
category = "主网"
|
||||
|
||||
if not engineering_type:
|
||||
print(f"警告: 无法从文件名 '{filename}' 中解析工程类型,使用默认值 '清单'")
|
||||
engineering_type = "清单"
|
||||
|
||||
return category, engineering_type
|
||||
# 软件类别名称映射字典,将各种变体映射到标准类别
|
||||
CATEGORY_MAPPING = {
|
||||
# 主网及其变体
|
||||
"主网": "主网",
|
||||
"主网工程": "主网",
|
||||
"主网项目": "主网",
|
||||
# 配网及其变体
|
||||
"配网": "配网",
|
||||
"配网造价": "配网",
|
||||
"配网清单": "配网",
|
||||
# 技改及其变体
|
||||
"技改": "技改",
|
||||
"技改工程": "技改",
|
||||
"技改项目": "技改",
|
||||
"技改造价": "技改",
|
||||
"技改清单": "技改",
|
||||
}
|
||||
|
||||
|
||||
def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
@@ -102,8 +41,8 @@ def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str
|
||||
data = json.load(f)
|
||||
|
||||
# 定义阶段类型映射表
|
||||
budget_types = ["概预算", "定额", "估算", "概算"]
|
||||
list_types = ["清单", "结算", "招标控制价", "招投标工程"]
|
||||
budget_types = ["概预算", "定额", "定额计价", "概算", "概预算工程"]
|
||||
list_types = ["清单", "结算", "招标控制价", "招投标工程", "清单计价"]
|
||||
|
||||
# 从division字段获取软件名称和阶段类型
|
||||
if "division" in data:
|
||||
@@ -112,28 +51,39 @@ def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str
|
||||
|
||||
# 使用-分割division字段
|
||||
parts = division.split("-")
|
||||
if len(parts) >= 2:
|
||||
if len(parts) == 2:
|
||||
category = parts[0].strip()
|
||||
stage_type = parts[1].strip()
|
||||
|
||||
# 验证软件类别
|
||||
if category not in ["主网", "配网", "技改"]:
|
||||
print(f"警告: division中的软件名称 '{category}' 不是有效值,将使用默认值 '主网'")
|
||||
category = "主网"
|
||||
|
||||
# 映射阶段类型
|
||||
if any(budget_type in stage_type for budget_type in budget_types):
|
||||
engineering_type = "预算"
|
||||
elif any(list_type in stage_type for list_type in list_types):
|
||||
engineering_type = "清单"
|
||||
else:
|
||||
print(f"警告: division中的阶段类型 '{stage_type}' 无法映射到预算或清单,将使用默认值 '清单'")
|
||||
engineering_type = "清单"
|
||||
|
||||
print(f"从division解析: 软件名称={category}, 阶段类型={engineering_type} (原始值: {stage_type})")
|
||||
return category, engineering_type
|
||||
elif len(parts) == 3:
|
||||
category = parts[0].strip()
|
||||
stage_type = parts[2].strip()
|
||||
else:
|
||||
print(f"警告: division字段 '{division}' 格式不正确,无法分割")
|
||||
# 可以选择返回默认值或抛出异常,这里以返回默认值为例
|
||||
category = "主网"
|
||||
engineering_type = "清单"
|
||||
print(f"使用默认值: 软件名称={category}, 阶段类型={engineering_type}")
|
||||
return category, engineering_type
|
||||
|
||||
# 使用映射字典规范化软件类别
|
||||
if category in CATEGORY_MAPPING:
|
||||
category = CATEGORY_MAPPING[category]
|
||||
else:
|
||||
print(f"警告: division中的软件名称 '{category}' 不是有效值,将使用默认值 '主网'")
|
||||
category = "主网"
|
||||
|
||||
# 映射阶段类型
|
||||
if any(budget_type in stage_type for budget_type in budget_types):
|
||||
engineering_type = "预算"
|
||||
elif any(list_type in stage_type for list_type in list_types):
|
||||
engineering_type = "清单"
|
||||
else:
|
||||
print(f"警告: division中的阶段类型 '{stage_type}' 无法映射到预算或清单,将使用默认值 '清单'")
|
||||
engineering_type = "清单"
|
||||
|
||||
print(f"从division解析: 软件名称={category}, 阶段类型={engineering_type} (原始值: {stage_type})")
|
||||
return category, engineering_type
|
||||
|
||||
else:
|
||||
print(f"警告: JSON文件中未找到division字段,尝试从basicData中解析")
|
||||
|
||||
@@ -145,8 +95,10 @@ def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str
|
||||
|
||||
# 验证解析结果
|
||||
if category and engineering_type:
|
||||
# 确保category是有效值
|
||||
if category not in ["主网", "配网", "技改"]:
|
||||
# 使用映射字典规范化软件类别
|
||||
if category in CATEGORY_MAPPING:
|
||||
category = CATEGORY_MAPPING[category]
|
||||
else:
|
||||
print(f"警告: basicData中的软件名称 '{category}' 不是有效值,将使用默认值 '主网'")
|
||||
category = "主网"
|
||||
|
||||
@@ -170,146 +122,166 @@ def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str
|
||||
|
||||
|
||||
def process_json_file(
|
||||
input_file: str,
|
||||
base_output_dir: str = "计算结果",
|
||||
category: Optional[str] = None,
|
||||
engineering_type: Optional[str] = None,
|
||||
project: Optional[str] = None,
|
||||
adjustment_type: str = "调差",
|
||||
json_file_path: str, output_dir: str, calculate_type: str = "all", project_name: str = None
|
||||
) -> bool:
|
||||
"""
|
||||
处理单个JSON文件
|
||||
|
||||
:param input_file: 输入JSON文件路径
|
||||
:param base_output_dir: 基础输出目录路径,实际结果会保存在此目录下的子文件夹中
|
||||
:param category: 软件类别,如果为None则从JSON内容中解析
|
||||
:param engineering_type: 工程类型,如果为None则从JSON内容中解析
|
||||
:param project: 项目名称,如果为None则处理所有项目
|
||||
:param adjustment_type: 调差类型
|
||||
:return: 处理是否成功
|
||||
Args:
|
||||
json_file_path: JSON文件路径
|
||||
output_dir: 输出目录
|
||||
calculate_type: 计算类型(all: 全部, quantity: 工程量取费, resource: 人材机合价)
|
||||
project_name: 项目名称,如果不指定则处理所有项目
|
||||
|
||||
Returns:
|
||||
bool: 处理是否成功
|
||||
"""
|
||||
try:
|
||||
# 获取文件名(不含扩展名)作为输出子文件夹的名称
|
||||
filename = os.path.basename(input_file)
|
||||
file_basename = os.path.splitext(filename)[0]
|
||||
output_dir = os.path.join(base_output_dir, file_basename)
|
||||
# 从JSON文件内容中解析软件类别和工程类型
|
||||
category, engineering_type = parse_json_content(json_file_path)
|
||||
|
||||
# 创建输出子文件夹
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
print(f"创建输出目录: {output_dir}")
|
||||
# 如果解析失败,使用默认值
|
||||
if category is None:
|
||||
category = "主网"
|
||||
print(f"无法从文件中解析软件类别,使用默认值: {category}")
|
||||
|
||||
# 如果未指定category或engineering_type,从JSON内容中解析
|
||||
if category is None or engineering_type is None:
|
||||
# 首先尝试从JSON内容中解析
|
||||
json_category, json_engineering_type = parse_json_content(input_file)
|
||||
|
||||
if category is None:
|
||||
if json_category:
|
||||
category = json_category
|
||||
else:
|
||||
# 如果从JSON内容中解析失败,尝试从文件名中解析(作为备用)
|
||||
print("从JSON内容解析软件名称失败,尝试从文件名解析...")
|
||||
parsed_category, _ = parse_filename(filename)
|
||||
category = parsed_category
|
||||
|
||||
if engineering_type is None:
|
||||
if json_engineering_type:
|
||||
engineering_type = json_engineering_type
|
||||
else:
|
||||
# 如果从JSON内容中解析失败,尝试从文件名中解析(作为备用)
|
||||
print("从JSON内容解析阶段类型失败,尝试从文件名解析...")
|
||||
_, parsed_engineering_type = parse_filename(filename)
|
||||
engineering_type = parsed_engineering_type
|
||||
|
||||
print(f"处理文件: {input_file}")
|
||||
print(f" 软件类别: {category}")
|
||||
print(f" 工程类型: {engineering_type}")
|
||||
if engineering_type is None:
|
||||
engineering_type = "预算"
|
||||
print(f"无法从文件中解析工程类型,使用默认值: {engineering_type}")
|
||||
|
||||
# 获取软件类型
|
||||
software_type = get_software_type(category, engineering_type)
|
||||
print(f" 使用软件类型: {software_type.name}")
|
||||
print(f"使用软件类型: {software_type.name}")
|
||||
|
||||
# 获取计算器
|
||||
calculator = get_calculator(software_type)
|
||||
if not calculator:
|
||||
print(f" 错误: 未找到软件类型 {software_type.name} 的计算器")
|
||||
print(f"错误: 未找到软件类型 {software_type.name} 的计算器")
|
||||
return False
|
||||
|
||||
# 设置输出目录
|
||||
calculator.set_output_dir(output_dir)
|
||||
# 获取文件名(不含扩展名),并替换空格和特殊字符
|
||||
base_filename = os.path.basename(json_file_path).replace(".json", "")
|
||||
# 创建安全的目录名(替换空格和特殊字符)
|
||||
safe_dirname = base_filename.replace(" ", "_").replace("\\", "_").replace("/", "_")
|
||||
|
||||
# 执行计算
|
||||
print(" 开始计算工程量取费表...")
|
||||
calculator.calculate_quantity_fee_tables(json_file_path=input_file, project_name=project)
|
||||
# 设置自定义输出目录
|
||||
custom_output_dir = os.path.join(output_dir, safe_dirname)
|
||||
|
||||
print(" 开始计算人材机合价...")
|
||||
calculator.calculate_resource_fee_tables(json_file_path=input_file, project_name=project)
|
||||
# 确保输出目录存在
|
||||
try:
|
||||
os.makedirs(custom_output_dir, exist_ok=True)
|
||||
print(f"创建输出目录: {custom_output_dir}")
|
||||
except Exception as e:
|
||||
print(f"创建输出目录失败: {str(e)}")
|
||||
# 尝试使用更安全的目录名
|
||||
safe_dirname = f"project_{hash(base_filename) % 10000}"
|
||||
custom_output_dir = os.path.join(output_dir, safe_dirname)
|
||||
os.makedirs(custom_output_dir, exist_ok=True)
|
||||
print(f"使用备用输出目录: {custom_output_dir}")
|
||||
|
||||
print(f" 处理完成: {input_file}")
|
||||
print(f" 结果保存在: {output_dir}")
|
||||
# 检查计算器是否有set_output_dir方法,如果有则直接设置输出目录
|
||||
if hasattr(calculator, "set_output_dir"):
|
||||
calculator.set_output_dir(custom_output_dir)
|
||||
print(f"已设置计算器输出目录: {custom_output_dir}")
|
||||
|
||||
# 根据计算类型执行计算
|
||||
if calculate_type in ["all", "quantity"]:
|
||||
print("开始计算工程量取费表...")
|
||||
calculator.calculate_quantity_fee_tables(
|
||||
json_file_path=json_file_path,
|
||||
project_name=project_name,
|
||||
)
|
||||
|
||||
if calculate_type in ["all", "resource"]:
|
||||
print("开始计算人材机合价...")
|
||||
calculator.calculate_resource_fee_tables(json_file_path=json_file_path, project_name=project_name)
|
||||
else:
|
||||
# 创建一个新的计算器,复制原始计算器的属性
|
||||
class CustomOutputCalculator:
|
||||
def __init__(self, original_calculator, custom_dir):
|
||||
self.original_calculator = original_calculator
|
||||
self.custom_dir = custom_dir
|
||||
|
||||
def get_output_dir(self):
|
||||
return self.custom_dir
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.original_calculator, name)
|
||||
|
||||
# 创建自定义输出目录的计算器
|
||||
custom_calculator = CustomOutputCalculator(calculator, custom_output_dir)
|
||||
|
||||
# 根据计算类型执行计算
|
||||
if calculate_type in ["all", "quantity"]:
|
||||
print("开始计算工程量取费表...")
|
||||
custom_calculator.calculate_quantity_fee_tables(
|
||||
json_file_path=json_file_path,
|
||||
project_name=project_name,
|
||||
)
|
||||
|
||||
if calculate_type in ["all", "resource"]:
|
||||
print("开始计算人材机合价...")
|
||||
custom_calculator.calculate_resource_fee_tables(
|
||||
json_file_path=json_file_path, project_name=project_name
|
||||
)
|
||||
|
||||
print(f"文件 {json_file_path} 处理完成")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" 处理文件 {input_file} 时出错: {str(e)}")
|
||||
print(f"处理文件 {json_file_path} 时出错: {str(e)}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def process_BCL_calculate(input_folder: str, output_folder: str) -> List[Tuple[str, bool]]:
|
||||
def process_directory(input_dir: str, output_dir: str, calculate_type: str = "all") -> None:
|
||||
"""
|
||||
处理指定文件夹中的所有JSON文件
|
||||
批量处理目录中的JSON文件
|
||||
|
||||
:param input_folder: 输入文件夹路径,包含要处理的JSON文件
|
||||
:param output_folder: 输出文件夹路径,用于保存计算结果
|
||||
:return: 处理结果列表,格式为 [(文件路径, 是否成功), ...]
|
||||
Args:
|
||||
input_dir: 输入目录路径
|
||||
output_dir: 输出目录路径
|
||||
calculate_type: 计算类型(all: 全部, quantity: 工程量取费, resource: 人材机合价)
|
||||
"""
|
||||
# 确保基础输出目录存在
|
||||
os.makedirs(output_folder, exist_ok=True)
|
||||
# 确保输出目录存在
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 查找所有JSON文件
|
||||
json_files = []
|
||||
for file in os.listdir(input_folder):
|
||||
if file.lower().endswith(".json"):
|
||||
json_files.append(os.path.join(input_folder, file))
|
||||
# 获取目录中的所有JSON文件
|
||||
json_files = [f for f in os.listdir(input_dir) if f.lower().endswith(".json")]
|
||||
|
||||
if not json_files:
|
||||
print(f"警告: 在目录 {input_folder} 中没有找到JSON文件")
|
||||
return []
|
||||
print(f"警告: 在目录 {input_dir} 中未找到JSON文件")
|
||||
return
|
||||
|
||||
print(f"找到 {len(json_files)} 个JSON文件,开始处理...")
|
||||
|
||||
# 处理每个JSON文件
|
||||
results = []
|
||||
for input_file in json_files:
|
||||
success = process_json_file(
|
||||
input_file=input_file,
|
||||
base_output_dir=output_folder, # 传递基础输出目录
|
||||
category=None, # 从JSON内容中解析
|
||||
engineering_type=None, # 从JSON内容中解析
|
||||
project=None, # 处理所有项目
|
||||
adjustment_type="调差",
|
||||
)
|
||||
success_count = 0
|
||||
for i, json_file in enumerate(json_files, 1):
|
||||
json_file_path = os.path.join(input_dir, json_file)
|
||||
print(f"处理文件 {i}/{len(json_files)}: {json_file}")
|
||||
|
||||
results.append((input_file, success))
|
||||
if process_json_file(json_file_path, output_dir, calculate_type):
|
||||
success_count += 1
|
||||
|
||||
return results
|
||||
print(f"处理完成,成功: {success_count}/{len(json_files)}")
|
||||
|
||||
|
||||
def main():
|
||||
"""程序入口"""
|
||||
def bcl_calculate(input_dir: str, output_dir: str, calculate_type: str = "all") -> None:
|
||||
"""
|
||||
主函数,处理指定目录中的所有JSON文件
|
||||
|
||||
input_folder = "project2json/outputs/json"
|
||||
output_folder = "project2json/outputs/bcl_results"
|
||||
Args:
|
||||
input_dir: 输入目录路径
|
||||
output_dir: 输出目录路径
|
||||
calculate_type: 计算类型(all: 全部, quantity: 工程量取费, resource: 人材机合价)
|
||||
"""
|
||||
print(f"开始处理目录: {input_dir}")
|
||||
print(f"输出目录: {output_dir}")
|
||||
print(f"计算类型: {calculate_type}")
|
||||
|
||||
results = process_BCL_calculate(input_folder, output_folder)
|
||||
process_directory(input_dir, output_dir, calculate_type)
|
||||
|
||||
# 显示处理结果
|
||||
success_count = sum(1 for _, success in results if success)
|
||||
print(f"\n处理完成: 成功 {success_count}/{len(results)} 个文件")
|
||||
|
||||
if success_count < len(results):
|
||||
print("处理失败的文件:")
|
||||
for file_path, success in results:
|
||||
if not success:
|
||||
print(f" - {os.path.basename(file_path)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
print("所有文件处理完成")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,11 +28,12 @@ from equipment_calculation.item_acquisition import (
|
||||
# 缓存已计算过的费用
|
||||
calculated_fees = {}
|
||||
|
||||
# 添加一个全局变量和一个缓存字典来存储清单数量
|
||||
_BILL_QUANTITY = 1.0
|
||||
bill_quantity_cache = {}
|
||||
# # 添加一个全局变量和一个缓存字典来存储清单数量
|
||||
# _BILL_QUANTITY = 1.0
|
||||
# bill_quantity_cache = {}
|
||||
|
||||
|
||||
# 主网处理地形系数
|
||||
def process_DXdata(json_data):
|
||||
"""
|
||||
处理 projectData 中的线路特征段数据,计算每条 bpBillZhXsTable 记录的加权值,
|
||||
@@ -102,40 +103,39 @@ def process_DXdata(json_data):
|
||||
|
||||
|
||||
# 在create_list_from_node函数后添加一个包装函数
|
||||
def create_list_from_node_with_quantity(node, quantity=None):
|
||||
"""创建清单对象并设置数量"""
|
||||
def create_list_from_node_with_bill_quantity(node, quantity=None):
|
||||
"""创建清单对象并设置数量,不使用全局变量"""
|
||||
from equipment_calculation.bcl_utils import create_list_from_node
|
||||
|
||||
bill_obj = create_list_from_node(node)
|
||||
|
||||
# 设置清单数量
|
||||
bill_quantity = quantity if quantity is not None else node.get("数量")
|
||||
if quantity is not None:
|
||||
bill_quantity = quantity
|
||||
elif "数量" in node:
|
||||
try:
|
||||
bill_quantity = float(node["数量"])
|
||||
except (ValueError, TypeError):
|
||||
bill_quantity = 1.0
|
||||
else:
|
||||
bill_quantity = 1.0
|
||||
|
||||
# 设置到对象属性
|
||||
bill_obj.quantity = bill_quantity
|
||||
|
||||
# 保存到全局变量和缓存
|
||||
global _BILL_QUANTITY
|
||||
_BILL_QUANTITY = bill_quantity
|
||||
|
||||
# 如果节点有ID,保存到缓存
|
||||
bill_id = node.get("id") or node.get("GUID")
|
||||
if bill_id:
|
||||
bill_quantity_cache[bill_id] = bill_quantity
|
||||
|
||||
# 设置环境变量作为后备方案
|
||||
os.environ["BILL_QUANTITY"] = str(bill_quantity)
|
||||
|
||||
if hasattr(bill_obj, "quantity"):
|
||||
bill_obj.quantity = bill_quantity
|
||||
else:
|
||||
# 如果对象没有quantity属性,尝试设置到其他可能的属性
|
||||
try:
|
||||
setattr(bill_obj, "quantity", bill_quantity)
|
||||
except:
|
||||
pass
|
||||
print(f"设置清单对象数量: {bill_quantity}")
|
||||
print(f"全局变量_BILL_QUANTITY: {_BILL_QUANTITY}")
|
||||
if bill_id:
|
||||
print(f"缓存清单{bill_id}的数量: {bill_quantity_cache[bill_id]}")
|
||||
|
||||
return bill_obj
|
||||
|
||||
|
||||
# 递归查找取费表中包含"取费基数"的节点
|
||||
def find_fee_base_nodes(node: Dict[str, Any], result: List = None) -> List[Dict[str, Any]]:
|
||||
def find_fee_base_nodes(node: Dict[str, Any], result: List[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
递归查找取费表中包含"取费基数"的节点或有子节点的费用项
|
||||
|
||||
@@ -449,21 +449,11 @@ def calculate_all_fees(
|
||||
calculation_strategy=None,
|
||||
) -> Dict[str, float]:
|
||||
"""
|
||||
计算项目节点的所有费用
|
||||
|
||||
Args:
|
||||
project_node: 工程量节点
|
||||
cost_table: 取费表
|
||||
json_file_path: JSON文件路径,用于获取工程信息
|
||||
engineering_type: 工程类型,如"清单工程"或"预算工程"
|
||||
calculation_strategy: 计算策略,如果为None则使用默认策略
|
||||
|
||||
Returns:
|
||||
Dict[str, float]: 费用计算结果
|
||||
计算项目节点的所有费用,确保清单数量正确传递
|
||||
"""
|
||||
results = {}
|
||||
|
||||
# 1. 工程信息上下文 ,在这里暂时先将参数项目划分名称固定
|
||||
# 1. 工程信息上下文
|
||||
project_context = create_project_contexts(json_file_path=json_file_path)
|
||||
|
||||
with open(json_file_path, "r", encoding="utf-8") as file:
|
||||
@@ -478,9 +468,9 @@ def calculate_all_fees(
|
||||
# 获取清单节点
|
||||
bill_id = project_node.get("bill_id")
|
||||
bill_name = project_node.get("bill_name")
|
||||
bill_node = project_node.get("bill_node")
|
||||
if not bill_node:
|
||||
bill_node = {
|
||||
bill_node_data = project_node.get("bill_node")
|
||||
if not bill_node_data:
|
||||
bill_node_data = {
|
||||
"id": bill_id,
|
||||
"GUID": bill_id,
|
||||
"清单名称": bill_name,
|
||||
@@ -491,16 +481,9 @@ def calculate_all_fees(
|
||||
}
|
||||
|
||||
# 使用包装函数创建清单对象
|
||||
# bill_obj = create_list_from_node_with_quantity(bill_node)
|
||||
bill_obj = create_list_from_node(bill_node)
|
||||
# 如果清单节点有数量,确保它被保存到bill_obj
|
||||
if "数量" in bill_node:
|
||||
bill_obj.quantity = float(bill_node["数量"])
|
||||
print(f"设置清单对象数量: {bill_obj.quantity}")
|
||||
bill_obj = create_list_from_node_with_bill_quantity(bill_node_data)
|
||||
|
||||
# 创建工程量对象
|
||||
from equipment_calculation.bcl_utils import create_node_from_type
|
||||
|
||||
project_obj = create_node_from_type(project_node)
|
||||
|
||||
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
|
||||
@@ -508,17 +491,23 @@ def calculate_all_fees(
|
||||
|
||||
# 递归数据源链式上下文
|
||||
billItem = BCLDataSourceItem(bill_obj)
|
||||
# 设置一个显式的数量属性
|
||||
# billItem.bill_quantity = float(bill_node.get("数量", 1.0))
|
||||
# print(f"设置清单数据源项数量: {billItem.bill_quantity}")
|
||||
|
||||
# 将清单数量直接设置到billItem对象上
|
||||
if hasattr(bill_obj, "quantity"):
|
||||
billItem.bill_quantity = bill_obj.quantity
|
||||
print(f"设置清单数据源项数量: {billItem.bill_quantity}")
|
||||
|
||||
QuantityItem = BCLDataSourceItem(project_obj, billItem)
|
||||
bill_context = BCLDataSourceContext([billItem], dxitem_context)
|
||||
context = BCLDataSourceContext([QuantityItem], bill_context)
|
||||
|
||||
# 如果计算策略存在,设置清单数量
|
||||
if calculation_strategy and hasattr(calculation_strategy, "set_bill_quantity"):
|
||||
if hasattr(bill_obj, "quantity"):
|
||||
calculation_strategy.set_bill_quantity(bill_obj.quantity)
|
||||
print(f"设置计算策略的清单数量: {bill_obj.quantity}")
|
||||
else:
|
||||
# 非清单工程 - 工程信息 -> 工程量节点
|
||||
from equipment_calculation.bcl_utils import create_node_from_type
|
||||
|
||||
project_obj = create_node_from_type(project_node)
|
||||
|
||||
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
|
||||
@@ -527,16 +516,10 @@ def calculate_all_fees(
|
||||
QuantityItem = BCLDataSourceItem(project_obj)
|
||||
context = BCLDataSourceContext([QuantityItem], dxitem_context)
|
||||
|
||||
# 打印项目节点信息,用于调试
|
||||
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||
print(f"\n处理项目节点: {node_name}")
|
||||
|
||||
# 查找包含取费基数的节点
|
||||
fee_base_nodes = find_fee_base_nodes(cost_table)
|
||||
|
||||
# 计算每个费用项
|
||||
# 先计算基本费用项,再计算复合费用项
|
||||
# 这里简单按照节点在列表中的顺序计算,可能需要更复杂的依赖解析
|
||||
for node in fee_base_nodes:
|
||||
fee_name = node.get("费用名称", node.get("name", "未知费用"))
|
||||
fee_code = node.get("代码", "")
|
||||
@@ -715,17 +698,7 @@ def calculate_quantity_fees(
|
||||
calculation_strategy=None,
|
||||
) -> str:
|
||||
"""
|
||||
计算工程量取费表
|
||||
|
||||
Args:
|
||||
json_file_path: JSON文件路径
|
||||
project_name: 项目名称
|
||||
engineering_type: 工程类型
|
||||
project_guid: 项目GUID,用于区分同名项目
|
||||
calculation_strategy: 计算策略,如果为None则使用默认策略
|
||||
|
||||
Returns:
|
||||
str: 输出文件路径
|
||||
计算工程量取费表,不使用全局变量,并按清单节点组织结果
|
||||
"""
|
||||
# 如果没有提供计算策略,使用默认策略
|
||||
if calculation_strategy is None:
|
||||
@@ -734,7 +707,13 @@ def calculate_quantity_fees(
|
||||
calculation_strategy = DefaultCalculationStrategy()
|
||||
|
||||
# 设置输出目录和文件名
|
||||
output_dir = "计算结果"
|
||||
# 检查calculation_strategy是否有get_output_dir方法
|
||||
if hasattr(calculation_strategy, "get_output_dir"):
|
||||
output_dir = calculation_strategy.get_output_dir()
|
||||
else:
|
||||
# 兼容旧代码,使用默认目录
|
||||
output_dir = "计算结果"
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 获取项目划分节点的GUID
|
||||
@@ -781,8 +760,10 @@ def calculate_quantity_fees(
|
||||
f"{safe_project_name}{safe_project_guid}_{engineering_type}_calculation_results.json",
|
||||
)
|
||||
|
||||
# 在处理清单工程时,保存清单数量供后续使用
|
||||
# 创建一个字典来存储清单数量
|
||||
bill_quantities = {}
|
||||
# 创建一个字典来存储清单信息
|
||||
bill_info = {}
|
||||
|
||||
# 根据工程类型获取取费表和项目节点
|
||||
if engineering_type == "预算工程":
|
||||
@@ -809,6 +790,7 @@ def calculate_quantity_fees(
|
||||
|
||||
# 获取清单节点信息
|
||||
bill_id = project_node.get("bill_id")
|
||||
bill_name = project_node.get("bill_name", "未命名清单")
|
||||
fee_table_name = project_node.get("取费表名称")
|
||||
|
||||
if not bill_id:
|
||||
@@ -819,9 +801,17 @@ def calculate_quantity_fees(
|
||||
print(f"工程量节点 '{node_name}' 没有取费表名称")
|
||||
continue
|
||||
|
||||
# 保存清单信息
|
||||
if bill_id not in bill_info:
|
||||
bill_info[bill_id] = {
|
||||
"name": bill_name,
|
||||
"fee_table_name": fee_table_name,
|
||||
"guid": bill_id.replace("{", "").replace("}", "") if bill_id else "",
|
||||
}
|
||||
|
||||
# 使用清单节点的取费表名称直接查找取费表
|
||||
if bill_id not in cost_tables:
|
||||
print(f"查找清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表: {fee_table_name}")
|
||||
print(f"查找清单节点 '{bill_name}' 的取费表: {fee_table_name}")
|
||||
|
||||
# 查找取费表
|
||||
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||
@@ -843,7 +833,7 @@ def calculate_quantity_fees(
|
||||
continue
|
||||
|
||||
cost_tables[bill_id] = cost_table_children
|
||||
print(f"成功获取清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表")
|
||||
print(f"成功获取清单节点 '{bill_name}' 的取费表")
|
||||
|
||||
# 在读取JSON文件后,遍历项目中的清单节点,获取并保存清单数量
|
||||
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||
@@ -853,10 +843,7 @@ def calculate_quantity_fees(
|
||||
bill_data = project_data.get("bill", {})
|
||||
|
||||
# 递归函数获取所有清单节点
|
||||
def get_bill_nodes(node, results=None):
|
||||
if results is None:
|
||||
results = []
|
||||
|
||||
def get_bill_nodes(node):
|
||||
if isinstance(node, dict):
|
||||
# 检查是否是清单节点
|
||||
is_bill = (
|
||||
@@ -864,39 +851,36 @@ def calculate_quantity_fees(
|
||||
)
|
||||
|
||||
if is_bill and ("id" in node or "GUID" in node):
|
||||
bill_id = node.get("id") or node.get("GUID")
|
||||
# 强制使用GUID而不是ID
|
||||
bill_id = node.get("GUID") or node.get("guid") or node.get("id")
|
||||
quantity = 1.0 # 默认值
|
||||
if "数量" in node:
|
||||
try:
|
||||
quantity = float(node["数量"])
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
results.append((bill_id, quantity))
|
||||
# 保存到字典
|
||||
bill_quantities[bill_id] = quantity
|
||||
|
||||
# 直接设置到全局变量和缓存
|
||||
global _BILL_QUANTITY
|
||||
_BILL_QUANTITY = quantity
|
||||
bill_quantity_cache[bill_id] = quantity
|
||||
# 补充清单信息
|
||||
if bill_id not in bill_info:
|
||||
bill_info[bill_id] = {
|
||||
"name": node.get("清单名称", "未命名清单"),
|
||||
"fee_table_name": node.get("取费表名称", ""),
|
||||
"guid": bill_id.replace("{", "").replace("}", "") if bill_id else "",
|
||||
}
|
||||
|
||||
# 设置环境变量
|
||||
os.environ["BILL_QUANTITY"] = str(quantity)
|
||||
|
||||
print(f"设置清单 {bill_id} 的数量为全局变量: {quantity}")
|
||||
print(f"保存清单 {bill_id} 的数量: {quantity}")
|
||||
|
||||
# 递归处理子节点
|
||||
if "children" in node and isinstance(node["children"], list):
|
||||
for child in node["children"]:
|
||||
get_bill_nodes(child, results)
|
||||
|
||||
return results
|
||||
get_bill_nodes(child)
|
||||
|
||||
# 获取和设置清单数量
|
||||
get_bill_nodes(bill_data)
|
||||
|
||||
# 如果有计算策略,设置清单数量
|
||||
if calculation_strategy and hasattr(calculation_strategy, "set_bill_quantity"):
|
||||
calculation_strategy.set_bill_quantity(_BILL_QUANTITY)
|
||||
|
||||
else:
|
||||
print(f"不支持的工程类型: {engineering_type}")
|
||||
return None
|
||||
@@ -919,41 +903,89 @@ def calculate_quantity_fees(
|
||||
# 初始化缓存
|
||||
calculated_fees.clear() # 使用全局缓存,而不是计算策略中的缓存
|
||||
|
||||
# 计算每个项目节点的费用
|
||||
all_results = {}
|
||||
# 创建一个新的结果结构,按清单节点组织
|
||||
structured_results = {}
|
||||
|
||||
# 辅助函数:处理ID,移除花括号并保留完整ID
|
||||
def process_id(id_str):
|
||||
if not id_str:
|
||||
return ""
|
||||
# 移除花括号
|
||||
return id_str.replace("{", "").replace("}", "")
|
||||
|
||||
# 计算每个项目节点的费用
|
||||
for project_node in project_children:
|
||||
# 为每个节点清空缓存,确保计算独立
|
||||
calculated_fees.clear()
|
||||
|
||||
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||
# 强制使用GUID而不是ID
|
||||
node_id = project_node.get("GUID") or project_node.get("guid") or project_node.get("id", "")
|
||||
|
||||
# 添加清单数量信息
|
||||
# 处理节点ID
|
||||
processed_node_id = process_id(node_id)
|
||||
|
||||
# 创建唯一的节点键
|
||||
node_key = node_name
|
||||
if processed_node_id:
|
||||
node_key = f"{node_name}_{processed_node_id}"
|
||||
|
||||
# 添加清单数量信息到项目节点
|
||||
if engineering_type == "清单工程":
|
||||
bill_id = project_node.get("bill_id")
|
||||
# 强制使用GUID而不是ID
|
||||
bill_id = project_node.get("bill_guid") or project_node.get("bill_id")
|
||||
if bill_id in bill_quantities:
|
||||
project_node["bill_quantity"] = bill_quantities[bill_id]
|
||||
print(f"为工程量节点 '{node_name}' 设置清单数量: {bill_quantities[bill_id]}")
|
||||
|
||||
# 如果计算策略支持,设置清单数量
|
||||
if calculation_strategy and hasattr(calculation_strategy, "set_bill_quantity"):
|
||||
calculation_strategy.set_bill_quantity(bill_quantities[bill_id])
|
||||
|
||||
# 根据工程类型获取对应的取费表
|
||||
if engineering_type == "预算工程":
|
||||
# 预算工程 - 使用项目划分节点的取费表
|
||||
node_results = calculation_strategy.calculate_all_fees(
|
||||
project_node, {"children": cost_table_children}, json_file_path, engineering_type
|
||||
)
|
||||
|
||||
# 直接使用节点键作为结果键
|
||||
structured_results[node_key] = node_results
|
||||
|
||||
else: # 清单工程
|
||||
# 清单工程 - 使用清单节点的取费表
|
||||
parent_id = project_node.get("parent_id")
|
||||
if parent_id and parent_id in cost_tables:
|
||||
# 强制使用GUID而不是ID
|
||||
bill_id = project_node.get("bill_guid") or project_node.get("bill_id")
|
||||
if bill_id and bill_id in cost_tables:
|
||||
node_results = calculation_strategy.calculate_all_fees(
|
||||
project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
|
||||
project_node, {"children": cost_tables[bill_id]}, json_file_path, engineering_type
|
||||
)
|
||||
|
||||
# 获取清单信息
|
||||
bill_name = bill_info.get(bill_id, {}).get("name", "未命名清单")
|
||||
|
||||
# 处理清单ID - 强制使用GUID
|
||||
processed_bill_id = process_id(bill_id)
|
||||
|
||||
# 创建清单的唯一键 - 强制使用GUID
|
||||
bill_key = bill_name
|
||||
if processed_bill_id:
|
||||
# 获取清单的guid而不是id
|
||||
bill_guid = bill_info.get(bill_id, {}).get("guid", processed_bill_id)
|
||||
if not bill_guid:
|
||||
bill_guid = processed_bill_id
|
||||
bill_key = f"{bill_name}_{bill_guid}"
|
||||
|
||||
# 如果清单节点不存在于结果中,创建它
|
||||
if bill_key not in structured_results:
|
||||
structured_results[bill_key] = {}
|
||||
|
||||
# 将工程量节点的计算结果添加到对应的清单节点下
|
||||
structured_results[bill_key][node_key] = node_results
|
||||
else:
|
||||
print(f"无法获取工程量节点 '{node_name}' 的取费表")
|
||||
continue
|
||||
|
||||
all_results[node_name] = node_results
|
||||
|
||||
# 输出计算结果
|
||||
print(f"费用计算结果:")
|
||||
for fee_name, fee_value in node_results.items():
|
||||
@@ -961,7 +993,7 @@ def calculate_quantity_fees(
|
||||
|
||||
# 保存计算结果到JSON文件
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(all_results, f, ensure_ascii=False, indent=2)
|
||||
json.dump(structured_results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n计算结果已保存到 {output_file}")
|
||||
|
||||
|
||||
@@ -280,12 +280,12 @@ def calc_rcj_count(
|
||||
context = BCLDataSourceContext([QuantityItem, moeItem], project_context)
|
||||
|
||||
# 根据节点类型选择不同的计算表达式
|
||||
node_type = node.get("类型", "")
|
||||
if node_type == "人工":
|
||||
node_type = node.get("类型", node.get("type", ""))
|
||||
if node_type == "人工" or node_type == "2":
|
||||
calc_expr = "_材机合并人工数量"
|
||||
elif node_type == "材料":
|
||||
elif node_type == "材料" or node_type == "3":
|
||||
calc_expr = "_材机合并材料数量"
|
||||
elif node_type == "机械":
|
||||
elif node_type == "机械" or node_type == "4":
|
||||
calc_expr = "_材机合并机械数量"
|
||||
else:
|
||||
# 未知类型,使用默认数量
|
||||
@@ -730,7 +730,7 @@ def calculate_rcj_fees(
|
||||
f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 有材机列表,数量: {len(node['材机列表'])}"
|
||||
)
|
||||
for rcj_node in node["材机列表"]:
|
||||
node_type = rcj_node.get("类型")
|
||||
node_type = rcj_node.get("类型", rcj_node.get("type"))
|
||||
# 同时支持数字类型和字符串类型
|
||||
if node_type in ["人工", "2"]:
|
||||
labor_nodes.append((rcj_node, node))
|
||||
@@ -756,7 +756,7 @@ def calculate_rcj_fees(
|
||||
f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 有子节点,数量: {len(node['children'])}"
|
||||
)
|
||||
for child in node["children"]:
|
||||
child_type = child.get("类型")
|
||||
child_type = child.get("类型", child.get("type"))
|
||||
if child_type in ["人工", "2"]:
|
||||
labor_nodes.append((child, node))
|
||||
print(
|
||||
@@ -893,7 +893,7 @@ def calculate_rcj_fees(
|
||||
context = BCLDataSourceContext([moeItem], dxitem_context)
|
||||
|
||||
# 根据节点类型选择不同的计算表达式
|
||||
node_type = node.get("类型", "")
|
||||
node_type = node.get("类型", node.get("type", ""))
|
||||
if node_type in ["人工", "2"]:
|
||||
calc_expr = "_材机合并人工数量"
|
||||
elif node_type in ["材料", "3"]:
|
||||
@@ -1042,7 +1042,13 @@ def calculate_resource_fees(
|
||||
safe_project_guid = f"_{safe_project_guid}"
|
||||
|
||||
# 设置输出目录和文件名
|
||||
output_dir = "计算结果"
|
||||
# 检查calculation_strategy是否有get_output_dir方法
|
||||
if hasattr(calculation_strategy, "get_output_dir"):
|
||||
output_dir = calculation_strategy.get_output_dir()
|
||||
else:
|
||||
# 兼容旧代码,使用默认目录
|
||||
output_dir = "计算结果"
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
rcj_output_file = os.path.join(output_dir, f"{safe_project_name}{safe_project_guid}_rcj_fees.json")
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ from typing import Any, Dict, Optional, Set
|
||||
import sys
|
||||
import re
|
||||
|
||||
# 添加一个全局变量和一个缓存字典来存储清单数量
|
||||
_BILL_QUANTITY = 1.0
|
||||
bill_quantity_cache = {}
|
||||
# 移除全局变量声明
|
||||
# _BILL_QUANTITY = 1.0
|
||||
# bill_quantity_cache = {}
|
||||
|
||||
|
||||
# 为每种软件类型定义特定的计算策略
|
||||
@@ -90,46 +90,42 @@ class MainGridBillCalculationStrategy(DefaultCalculationStrategy):
|
||||
|
||||
result = calculator.calculate(var_name, context)
|
||||
|
||||
# 获取清单数量 - 首先从全局变量获取
|
||||
global _BILL_QUANTITY # 在模块级别定义一个全局变量
|
||||
parent_quantity = _BILL_QUANTITY if "_BILL_QUANTITY" in globals() else 1.0
|
||||
# 获取清单数量 - 首先尝试从上下文中获取
|
||||
parent_quantity = 1.0 # 默认值
|
||||
|
||||
print(f"\n===== 获取清单数量 =====")
|
||||
print(f"从全局变量获取清单数量: {parent_quantity}")
|
||||
|
||||
# 如果全局变量没有设置正确的值,尝试从bill_quantity_cache获取
|
||||
if parent_quantity == 1.0 and hasattr(context, "__dict__"):
|
||||
module = sys.modules.get("quantity_fee_calculator")
|
||||
if module and hasattr(module, "bill_quantity_cache"):
|
||||
bill_cache = getattr(module, "bill_quantity_cache")
|
||||
bill_id = None
|
||||
# 1. 首先尝试从计算策略中获取
|
||||
if hasattr(self, "bill_quantity"):
|
||||
parent_quantity = self.bill_quantity
|
||||
print(f"从计算策略获取清单数量: {parent_quantity}")
|
||||
|
||||
# 尝试从上下文中获取bill_id
|
||||
if hasattr(context, "bill_id"):
|
||||
bill_id = context.bill_id
|
||||
elif hasattr(context, "datasource") and context.datasource:
|
||||
for item in context.datasource:
|
||||
if hasattr(item, "object") and hasattr(item.object, "bill_id"):
|
||||
bill_id = item.object.bill_id
|
||||
# 2. 如果计算策略中没有,尝试从上下文中获取
|
||||
if parent_quantity == 1.0 and context:
|
||||
# 尝试从上下文的数据源中获取
|
||||
if hasattr(context, "datasource") and context.datasource:
|
||||
for item in context.datasource:
|
||||
# 检查是否有bill_quantity属性
|
||||
if hasattr(item, "bill_quantity"):
|
||||
parent_quantity = item.bill_quantity
|
||||
print(f"从数据源项获取清单数量: {parent_quantity}")
|
||||
break
|
||||
# 检查是否有object属性,且object有quantity属性
|
||||
if hasattr(item, "object") and hasattr(item.object, "quantity"):
|
||||
parent_quantity = item.object.quantity
|
||||
print(f"从数据源项对象获取清单数量: {parent_quantity}")
|
||||
break
|
||||
|
||||
# 尝试从上下文的父级上下文中获取
|
||||
if parent_quantity == 1.0 and hasattr(context, "prevContext"):
|
||||
prev_context = context.prevContext
|
||||
if prev_context and hasattr(prev_context, "datasource") and prev_context.datasource:
|
||||
for item in prev_context.datasource:
|
||||
if hasattr(item, "object") and hasattr(item.object, "quantity"):
|
||||
parent_quantity = item.object.quantity
|
||||
print(f"从父级上下文获取清单数量: {parent_quantity}")
|
||||
break
|
||||
|
||||
# 如果找到bill_id并且在缓存中,使用缓存的数量
|
||||
if bill_id and bill_id in bill_cache:
|
||||
parent_quantity = bill_cache[bill_id]
|
||||
print(f"从bill_quantity_cache获取清单{bill_id}的数量: {parent_quantity}")
|
||||
|
||||
# 设置一个临时环境变量作为最后的手段
|
||||
if parent_quantity == 1.0:
|
||||
import os
|
||||
|
||||
env_quantity = os.environ.get("BILL_QUANTITY")
|
||||
if env_quantity:
|
||||
try:
|
||||
parent_quantity = float(env_quantity)
|
||||
print(f"从环境变量获取清单数量: {parent_quantity}")
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"===== 获取清单数量结束 =====\n")
|
||||
|
||||
# 将结果乘以父级清单的数量
|
||||
@@ -260,20 +256,18 @@ class TechnicalRenovationBudgetCalculationStrategy(DefaultCalculationStrategy):
|
||||
class TechnicalRenovationBillCalculationStrategy(DefaultCalculationStrategy):
|
||||
"""技改清单计算策略"""
|
||||
|
||||
# def calculate_fee_base(
|
||||
# self, fee_base: str, cost_table: dict, project_node: dict, context, in_calculation=None
|
||||
# ) -> float:
|
||||
# """计算取费基数,可以重写以添加技改清单特定的计算规则"""
|
||||
# # 示例:如果取费基数是特定值,使用特定的计算规则
|
||||
# if fee_base == "人工费+材料费+机械费":
|
||||
# print(f"应用技改清单特定的取费基数计算规则: {fee_base}")
|
||||
# # 这里可以添加特定的计算逻辑
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.bill_quantity = 1.0 # 默认值
|
||||
|
||||
# # 暂时仍然使用默认实现
|
||||
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||
def set_bill_quantity(self, quantity):
|
||||
"""设置清单数量"""
|
||||
try:
|
||||
self.bill_quantity = float(quantity)
|
||||
print(f"设置计算策略的清单数量: {self.bill_quantity}")
|
||||
except (TypeError, ValueError):
|
||||
print(f"无法将 {quantity} 转换为浮点数")
|
||||
|
||||
# # 对于其他取费基数,使用默认实现
|
||||
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||
def calculate_external_variable(self, var_name: str, context: Any) -> float:
|
||||
"""计算表外变量,并乘以父级清单的数量"""
|
||||
# 使用默认实现计算表外变量
|
||||
@@ -281,46 +275,42 @@ class TechnicalRenovationBillCalculationStrategy(DefaultCalculationStrategy):
|
||||
|
||||
result = calculator.calculate(var_name, context)
|
||||
|
||||
# 获取清单数量 - 首先从全局变量获取
|
||||
global _BILL_QUANTITY # 在模块级别定义一个全局变量
|
||||
parent_quantity = _BILL_QUANTITY if "_BILL_QUANTITY" in globals() else 1.0
|
||||
# 获取清单数量 - 首先尝试从上下文中获取
|
||||
parent_quantity = 1.0 # 默认值
|
||||
|
||||
print(f"\n===== 获取清单数量 =====")
|
||||
print(f"从全局变量获取清单数量: {parent_quantity}")
|
||||
|
||||
# 如果全局变量没有设置正确的值,尝试从bill_quantity_cache获取
|
||||
if parent_quantity == 1.0 and hasattr(context, "__dict__"):
|
||||
module = sys.modules.get("quantity_fee_calculator")
|
||||
if module and hasattr(module, "bill_quantity_cache"):
|
||||
bill_cache = getattr(module, "bill_quantity_cache")
|
||||
bill_id = None
|
||||
# 1. 首先尝试从计算策略中获取
|
||||
if hasattr(self, "bill_quantity"):
|
||||
parent_quantity = self.bill_quantity
|
||||
print(f"从计算策略获取清单数量: {parent_quantity}")
|
||||
|
||||
# 尝试从上下文中获取bill_id
|
||||
if hasattr(context, "bill_id"):
|
||||
bill_id = context.bill_id
|
||||
elif hasattr(context, "datasource") and context.datasource:
|
||||
for item in context.datasource:
|
||||
if hasattr(item, "object") and hasattr(item.object, "bill_id"):
|
||||
bill_id = item.object.bill_id
|
||||
# 2. 如果计算策略中没有,尝试从上下文中获取
|
||||
if parent_quantity == 1.0 and context:
|
||||
# 尝试从上下文的数据源中获取
|
||||
if hasattr(context, "datasource") and context.datasource:
|
||||
for item in context.datasource:
|
||||
# 检查是否有bill_quantity属性
|
||||
if hasattr(item, "bill_quantity"):
|
||||
parent_quantity = item.bill_quantity
|
||||
print(f"从数据源项获取清单数量: {parent_quantity}")
|
||||
break
|
||||
# 检查是否有object属性,且object有quantity属性
|
||||
if hasattr(item, "object") and hasattr(item.object, "quantity"):
|
||||
parent_quantity = item.object.quantity
|
||||
print(f"从数据源项对象获取清单数量: {parent_quantity}")
|
||||
break
|
||||
|
||||
# 尝试从上下文的父级上下文中获取
|
||||
if parent_quantity == 1.0 and hasattr(context, "prevContext"):
|
||||
prev_context = context.prevContext
|
||||
if prev_context and hasattr(prev_context, "datasource") and prev_context.datasource:
|
||||
for item in prev_context.datasource:
|
||||
if hasattr(item, "object") and hasattr(item.object, "quantity"):
|
||||
parent_quantity = item.object.quantity
|
||||
print(f"从父级上下文获取清单数量: {parent_quantity}")
|
||||
break
|
||||
|
||||
# 如果找到bill_id并且在缓存中,使用缓存的数量
|
||||
if bill_id and bill_id in bill_cache:
|
||||
parent_quantity = bill_cache[bill_id]
|
||||
print(f"从bill_quantity_cache获取清单{bill_id}的数量: {parent_quantity}")
|
||||
|
||||
# 设置一个临时环境变量作为最后的手段
|
||||
if parent_quantity == 1.0:
|
||||
import os
|
||||
|
||||
env_quantity = os.environ.get("BILL_QUANTITY")
|
||||
if env_quantity:
|
||||
try:
|
||||
parent_quantity = float(env_quantity)
|
||||
print(f"从环境变量获取清单数量: {parent_quantity}")
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"===== 获取清单数量结束 =====\n")
|
||||
|
||||
# 将结果乘以父级清单的数量
|
||||
@@ -401,14 +391,6 @@ class MainGridBudgetCalculator(CalculatorBase):
|
||||
"""创建主网预算计算策略"""
|
||||
return MainGridBudgetCalculationStrategy()
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
"""
|
||||
设置计算结果的输出目录
|
||||
|
||||
:param output_dir: 输出目录的路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def apply_quantity_fee_rules(self) -> None:
|
||||
"""应用主网预算特定的工程量取费规则"""
|
||||
print(f"应用{self.software_type.name}特定的工程量取费规则 - 对节点进行预处理")
|
||||
@@ -475,14 +457,6 @@ class MainGridBillCalculator(CalculatorBase):
|
||||
"""创建主网清单计算策略"""
|
||||
return MainGridBillCalculationStrategy()
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
"""
|
||||
设置计算结果的输出目录
|
||||
|
||||
:param output_dir: 输出目录的路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def apply_quantity_fee_rules(self) -> None:
|
||||
"""应用主网清单特定的工程量取费规则"""
|
||||
print(f"应用{self.software_type.name}特定的工程量取费规则 - 对节点进行预处理")
|
||||
@@ -547,14 +521,6 @@ class DistributionBudgetCalculator(CalculatorBase):
|
||||
def __init__(self):
|
||||
super().__init__(DISTRIBUTION_BUDGET)
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
"""
|
||||
设置计算结果的输出目录
|
||||
|
||||
:param output_dir: 输出目录的路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||
"""创建配网预算计算策略"""
|
||||
return DistributionBudgetCalculationStrategy()
|
||||
@@ -586,14 +552,6 @@ class DistributionBillCalculator(CalculatorBase):
|
||||
"""创建配网清单计算策略"""
|
||||
return DistributionBillCalculationStrategy()
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
"""
|
||||
设置计算结果的输出目录
|
||||
|
||||
:param output_dir: 输出目录的路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def apply_quantity_fee_rules(self) -> None:
|
||||
"""应用配网清单特定的工程量取费规则"""
|
||||
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||
@@ -621,14 +579,6 @@ class TechnicalRenovationBudgetCalculator(CalculatorBase):
|
||||
"""创建技改预算计算策略"""
|
||||
return TechnicalRenovationBudgetCalculationStrategy()
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
"""
|
||||
设置计算结果的输出目录
|
||||
|
||||
:param output_dir: 输出目录的路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def apply_quantity_fee_rules(self) -> None:
|
||||
"""应用技改预算特定的工程量取费规则"""
|
||||
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||
@@ -656,14 +606,6 @@ class TechnicalRenovationBillCalculator(CalculatorBase):
|
||||
"""创建技改清单计算策略"""
|
||||
return TechnicalRenovationBillCalculationStrategy()
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
"""
|
||||
设置计算结果的输出目录
|
||||
|
||||
:param output_dir: 输出目录的路径
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
|
||||
def apply_quantity_fee_rules(self) -> None:
|
||||
"""应用技改清单特定的工程量取费规则"""
|
||||
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,718 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import math
|
||||
from typing import Dict, List, Any, Tuple
|
||||
from copy import deepcopy
|
||||
|
||||
from equipment_calculation.item_acquisition import get_quantity_nodes, get_classified_resource_nodes, load_project_data
|
||||
|
||||
from bcl_utils import (
|
||||
ZjMaterialOrEquipmentBCLContext,
|
||||
ZjProjectBCLContext,
|
||||
ZjBillBCLContext,
|
||||
init_bcl_calculator,
|
||||
calculator,
|
||||
)
|
||||
from item_acquisition import get_quantity_nodes, get_classified_resource_nodes
|
||||
|
||||
# 人材机节点定义合并条件常量
|
||||
LABOR_MERGE_CONDITIONS = [
|
||||
"编码",
|
||||
"名称",
|
||||
"单位",
|
||||
"预算价含税",
|
||||
"预算价不含税",
|
||||
"市场价不含税",
|
||||
"市场价含税",
|
||||
"调差类型",
|
||||
"专业属性",
|
||||
"供货方",
|
||||
"物料类材料",
|
||||
]
|
||||
|
||||
MATERIAL_MERGE_CONDITIONS = [
|
||||
"类型",
|
||||
"编码",
|
||||
"名称",
|
||||
"单位",
|
||||
"预算价不含税",
|
||||
"预算价含税",
|
||||
"市场价不含税",
|
||||
"市场价含税",
|
||||
"调差类型",
|
||||
"专业属性",
|
||||
"供货方",
|
||||
"集中配送",
|
||||
"卸车",
|
||||
"保管",
|
||||
"物料类材料",
|
||||
]
|
||||
|
||||
MACHINE_MERGE_CONDITIONS = [
|
||||
"编码",
|
||||
"名称",
|
||||
"单位",
|
||||
"预算价不含税",
|
||||
"预算价含税",
|
||||
"市场价不含税",
|
||||
"市场价含税",
|
||||
"调差类型",
|
||||
"专业属性",
|
||||
"供货方",
|
||||
"物料类材料",
|
||||
]
|
||||
|
||||
|
||||
from bcl_utils import (
|
||||
ZjMaterialOrEquipmentBCLContext,
|
||||
ZjProjectBCLContext,
|
||||
ZjBillBCLContext,
|
||||
init_bcl_calculator,
|
||||
calculator,
|
||||
)
|
||||
|
||||
|
||||
def calc_rcj_count(
|
||||
rcj_nodes: List[Tuple[Dict[str, Any], str]], project_children: List[Dict[str, Any]], json_file_path: str = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
计算人材机节点的数量,考虑父级消耗量
|
||||
|
||||
Args:
|
||||
rcj_nodes: 人材机节点列表,每个元素是(节点, 父级ID)元组
|
||||
project_children: 项目划分级别下的所有工程量节点
|
||||
json_file_path: JSON文件路径,用于获取工程信息
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 计算数量后的人材机节点列表
|
||||
"""
|
||||
result_nodes = []
|
||||
|
||||
# 检查rcj_nodes是否为空
|
||||
if not rcj_nodes:
|
||||
print("没有找到人材机节点")
|
||||
return result_nodes
|
||||
|
||||
# 检查project_children是否为None
|
||||
if project_children is None:
|
||||
print("没有找到工程量节点,使用默认数量")
|
||||
# 使用默认数量处理所有人材机节点
|
||||
for node, parent_id in rcj_nodes:
|
||||
node_copy = deepcopy(node)
|
||||
node_copy["计算数量"] = node_copy.get("数量", "1.0")
|
||||
# 保存父级ID
|
||||
node_copy["parent_id"] = parent_id
|
||||
# 由于没有找到父级节点,使用默认名称
|
||||
node_copy["parent_name"] = "未知工程量节点"
|
||||
result_nodes.append(node_copy)
|
||||
return result_nodes
|
||||
|
||||
# 创建父级ID到工程量节点的映射
|
||||
parent_nodes = {}
|
||||
for node in project_children:
|
||||
if "id" in node:
|
||||
parent_nodes[node["id"]] = node
|
||||
|
||||
print(f"找到 {len(parent_nodes)} 个父级工程量节点")
|
||||
|
||||
# 初始化BCL计算器
|
||||
if not init_bcl_calculator():
|
||||
print("初始化BCL计算器失败,使用默认数量")
|
||||
# 使用默认数量处理所有人材机节点
|
||||
for node, parent_id in rcj_nodes:
|
||||
node_copy = deepcopy(node)
|
||||
node_copy["计算数量"] = node_copy.get("数量", "1.0")
|
||||
# 保存父级ID
|
||||
node_copy["parent_id"] = parent_id
|
||||
# 由于没有找到父级节点,使用默认名称
|
||||
node_copy["parent_name"] = "未知工程量节点"
|
||||
result_nodes.append(node_copy)
|
||||
return result_nodes
|
||||
|
||||
# 创建工程信息上下文(顶层上下文)
|
||||
project_context = ZjProjectBCLContext(json_file_path=json_file_path)
|
||||
|
||||
# 遍历所有节点,计算数量
|
||||
for node, parent_id in rcj_nodes:
|
||||
node_copy = deepcopy(node)
|
||||
|
||||
# 保存父级ID
|
||||
node_copy["parent_id"] = parent_id
|
||||
|
||||
# 获取父级工程量节点
|
||||
parent_node = parent_nodes.get(parent_id)
|
||||
if not parent_node:
|
||||
print(f"未找到ID为 {parent_id} 的父级工程量节点,使用默认数量")
|
||||
# 使用默认数量
|
||||
node_copy["计算数量"] = node_copy.get("数量", "1.0")
|
||||
# 由于没有找到父级节点,使用默认名称
|
||||
node_copy["parent_name"] = "未知工程量节点"
|
||||
result_nodes.append(node_copy)
|
||||
continue
|
||||
|
||||
# 保存父级节点名称
|
||||
parent_name = parent_node.get("项目名称", parent_node.get("name", "未命名工程量"))
|
||||
node_copy["parent_name"] = parent_name
|
||||
|
||||
# 确保父级节点有数量字段
|
||||
if "数量" not in parent_node or not parent_node["数量"]:
|
||||
print(f"父级节点 {parent_id} 没有数量字段,使用默认数量")
|
||||
parent_node["数量"] = "1.0"
|
||||
|
||||
# 打印父级节点信息
|
||||
print(f"父级节点 {parent_id} ({parent_name}) 的数量: {parent_node.get('数量', '1.0')}")
|
||||
|
||||
# 创建定额上下文(中间层上下文)
|
||||
ration_context = ZjBillBCLContext(prefix="定额", valueDict=parent_node, prevContext=project_context)
|
||||
|
||||
# 创建人材机上下文(底层上下文)
|
||||
context = ZjMaterialOrEquipmentBCLContext(node_data=node, parent_node=parent_node, prevContext=ration_context)
|
||||
|
||||
# 根据节点类型选择不同的计算表达式
|
||||
node_type = node.get("类型", "")
|
||||
if node_type == "人工":
|
||||
calc_expr = "_材机合并人工数量"
|
||||
elif node_type == "材料":
|
||||
calc_expr = "_材机合并材料数量"
|
||||
elif node_type == "机械":
|
||||
calc_expr = "_材机合并机械数量"
|
||||
else:
|
||||
# 未知类型,使用默认数量
|
||||
print(f"未知节点类型 {node_type},使用默认数量")
|
||||
node_copy["计算数量"] = node_copy.get("数量", "1.0")
|
||||
result_nodes.append(node_copy)
|
||||
continue
|
||||
|
||||
try:
|
||||
# 使用BCL计算器计算数量
|
||||
result = calculator.calculate(calc_expr, context)
|
||||
|
||||
# 打印调试信息
|
||||
print(f"计算 {node.get('名称', '未知节点')} 的数量,表达式: {calc_expr}, 结果: {result}")
|
||||
|
||||
# 处理计算结果
|
||||
if hasattr(result, "value"):
|
||||
calculated_quantity = result.value
|
||||
elif isinstance(result, (int, float)):
|
||||
calculated_quantity = result
|
||||
else:
|
||||
# 尝试转换为浮点数
|
||||
try:
|
||||
calculated_quantity = float(result)
|
||||
except (ValueError, TypeError):
|
||||
print(f"无法将计算结果转换为浮点数: {result}")
|
||||
calculated_quantity = float(node_copy.get("数量", "1.0") or "1.0")
|
||||
|
||||
# 如果计算结果为0,尝试使用原始数量
|
||||
if calculated_quantity == 0:
|
||||
orig_quantity = float(node_copy.get("数量", "0.0") or "0.0")
|
||||
if orig_quantity > 0:
|
||||
print(f"计算结果为0,使用原始数量: {orig_quantity}")
|
||||
calculated_quantity = orig_quantity
|
||||
|
||||
# 设置计算数量
|
||||
node_copy["计算数量"] = str(calculated_quantity)
|
||||
except Exception as e:
|
||||
print(f"计算节点 '{node.get('名称', '未知节点')}' 的数量时出错: {e}")
|
||||
# 使用默认数量
|
||||
node_copy["计算数量"] = node_copy.get("数量", "1.0")
|
||||
|
||||
result_nodes.append(node_copy)
|
||||
|
||||
return result_nodes
|
||||
|
||||
|
||||
def generate_node_key(node: Dict[str, Any], conditions: List[str]) -> str:
|
||||
"""
|
||||
根据合并条件生成节点的唯一键,增加parent_id作为条件
|
||||
|
||||
Args:
|
||||
node: 节点
|
||||
conditions: 合并条件列表
|
||||
|
||||
Returns:
|
||||
str: 节点的唯一键
|
||||
"""
|
||||
key_parts = []
|
||||
|
||||
# 添加parent_id作为合并条件的第一个元素
|
||||
parent_id = node.get("parent_id", "")
|
||||
key_parts.append(f"parent_id:{parent_id}")
|
||||
|
||||
# 添加其他合并条件
|
||||
for condition in conditions:
|
||||
value = node.get(condition, "")
|
||||
# 确保值是字符串
|
||||
if value is None:
|
||||
value = ""
|
||||
elif not isinstance(value, str):
|
||||
value = str(value)
|
||||
key_parts.append(f"{condition}:{value}")
|
||||
|
||||
return "|".join(key_parts)
|
||||
|
||||
|
||||
def merge_similar_nodes(nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
合并相似的节点
|
||||
|
||||
Args:
|
||||
nodes: 节点列表
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 合并后的节点列表
|
||||
"""
|
||||
if not nodes:
|
||||
return []
|
||||
|
||||
# 按节点类型分组
|
||||
nodes_by_type = {}
|
||||
for node in nodes:
|
||||
node_type = node.get("类型", "")
|
||||
if node_type not in nodes_by_type:
|
||||
nodes_by_type[node_type] = []
|
||||
nodes_by_type[node_type].append(node)
|
||||
|
||||
merged_nodes = []
|
||||
|
||||
# 处理每种类型的节点
|
||||
for node_type, type_nodes in nodes_by_type.items():
|
||||
# 选择合并条件
|
||||
if node_type == "人工":
|
||||
conditions = LABOR_MERGE_CONDITIONS
|
||||
elif node_type == "材料":
|
||||
conditions = MATERIAL_MERGE_CONDITIONS
|
||||
elif node_type == "机械":
|
||||
conditions = MACHINE_MERGE_CONDITIONS
|
||||
else:
|
||||
# 未知类型,不合并
|
||||
merged_nodes.extend(type_nodes)
|
||||
continue
|
||||
|
||||
# 使用字典存储合并后的节点,键是根据合并条件生成的唯一键
|
||||
merged_dict = {}
|
||||
|
||||
for node in type_nodes:
|
||||
key = generate_node_key(node, conditions)
|
||||
|
||||
if key in merged_dict:
|
||||
# 合并节点
|
||||
merged_node = merged_dict[key]
|
||||
|
||||
# 合并数量
|
||||
merged_quantity = float(merged_node.get("取整数量", "0.0") or "0.0")
|
||||
node_quantity = float(node.get("取整数量", "0.0") or "0.0")
|
||||
merged_node["取整数量"] = str(merged_quantity + node_quantity)
|
||||
|
||||
# 合并计算数量
|
||||
merged_calc_quantity = float(merged_node.get("计算数量", "0.0") or "0.0")
|
||||
node_calc_quantity = float(node.get("计算数量", "0.0") or "0.0")
|
||||
merged_node["计算数量"] = str(merged_calc_quantity + node_calc_quantity)
|
||||
|
||||
# 更新原始数量
|
||||
merged_orig_quantity = float(merged_node.get("数量", "0.0") or "0.0")
|
||||
node_orig_quantity = float(node.get("数量", "0.0") or "0.0")
|
||||
merged_node["数量"] = str(merged_orig_quantity + node_orig_quantity)
|
||||
|
||||
# 添加合并信息
|
||||
if "合并来源" not in merged_node:
|
||||
# 使用唯一标识符,如果没有id字段则使用节点本身的索引
|
||||
merged_node_id = merged_node.get("id", f"node_{id(merged_node)}")
|
||||
merged_node["合并来源"] = [merged_node_id]
|
||||
|
||||
# 添加当前节点ID到合并来源
|
||||
node_id = node.get("id", f"node_{id(node)}")
|
||||
merged_node["合并来源"].append(node_id)
|
||||
|
||||
# 更新合并数量计数
|
||||
merged_node["合并数量"] = str(len(merged_node["合并来源"]))
|
||||
else:
|
||||
# 创建新节点
|
||||
merged_dict[key] = deepcopy(node)
|
||||
|
||||
# 添加合并信息
|
||||
node_id = node.get("id", f"node_{id(node)}")
|
||||
merged_dict[key]["合并来源"] = [node_id]
|
||||
merged_dict[key]["合并数量"] = "1"
|
||||
|
||||
# 将合并后的节点添加到结果列表
|
||||
merged_nodes.extend(merged_dict.values())
|
||||
|
||||
return merged_nodes
|
||||
|
||||
|
||||
def cat_rcj_count(rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
处理人材机节点数量的变化并合并相似节点
|
||||
|
||||
Args:
|
||||
rcj_nodes: 人材机节点列表
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 处理数量变化后的人材机节点列表
|
||||
"""
|
||||
# 使用计算数量,不进行向上取整
|
||||
processed_nodes = []
|
||||
|
||||
for node in rcj_nodes:
|
||||
node_copy = deepcopy(node)
|
||||
|
||||
# 获取计算数量,直接使用,不再向上取整
|
||||
calc_quantity = float(node_copy.get("计算数量", "0.0") or "0.0")
|
||||
node_copy["取整数量"] = str(calc_quantity)
|
||||
|
||||
processed_nodes.append(node_copy)
|
||||
|
||||
# 合并相似节点
|
||||
merged_nodes = merge_similar_nodes(processed_nodes)
|
||||
|
||||
return merged_nodes
|
||||
|
||||
|
||||
def calc_rcj_fee(rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
计算人材机节点的合价
|
||||
|
||||
Args:
|
||||
rcj_nodes: 人材机节点列表
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 计算合价后的人材机节点列表
|
||||
"""
|
||||
result_nodes = []
|
||||
|
||||
for node in rcj_nodes:
|
||||
node_copy = deepcopy(node)
|
||||
|
||||
# 获取取整数量
|
||||
quantity = float(node_copy.get("取整数量", "0.0") or "0.0")
|
||||
|
||||
# 获取预算价不含税和市场价不含税
|
||||
budget_price = float(node_copy.get("预算价不含税", "0.0") or "0.0")
|
||||
market_price = float(node_copy.get("市场价不含税", "0.0") or "0.0")
|
||||
|
||||
# 计算预算价合价和市场价合价
|
||||
budget_total = quantity * budget_price
|
||||
market_total = quantity * market_price
|
||||
|
||||
# 计算价差
|
||||
price_diff = market_total - budget_total
|
||||
|
||||
# 设置合价信息
|
||||
node_copy["预算价合价"] = str(budget_total)
|
||||
node_copy["市场价合价"] = str(market_total)
|
||||
node_copy["价差"] = str(price_diff)
|
||||
|
||||
result_nodes.append(node_copy)
|
||||
|
||||
return result_nodes
|
||||
|
||||
|
||||
def format_rcj_output(rcj_nodes: List[Dict[str, Any]], node_type: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
格式化人材机节点输出,只保留需要的字段
|
||||
|
||||
Args:
|
||||
rcj_nodes: 人材机节点列表
|
||||
node_type: 节点类型("人工"、"材料"、"机械")
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 格式化后的节点列表
|
||||
"""
|
||||
formatted_nodes = []
|
||||
|
||||
for node in rcj_nodes:
|
||||
formatted_node = {}
|
||||
|
||||
# 保留父级工程量节点信息
|
||||
formatted_node["parent_id"] = node.get("parent_id", "")
|
||||
formatted_node["parent_name"] = node.get("parent_name", "未知工程量节点")
|
||||
|
||||
# 根据节点类型保留特定字段
|
||||
if node_type == "人工":
|
||||
# 人工节点保存:编码,名称,单位,数量,预算价不含税,市场价不含税,预算价合价,市场价合价,价差
|
||||
fields = [
|
||||
"编码",
|
||||
"名称",
|
||||
"单位",
|
||||
"取整数量",
|
||||
"预算价不含税",
|
||||
"市场价不含税",
|
||||
"预算价合价",
|
||||
"市场价合价",
|
||||
"价差",
|
||||
]
|
||||
for field in fields:
|
||||
formatted_node[field] = node.get(field, "")
|
||||
elif node_type == "材料":
|
||||
# 材料节点保存:供货方,编码,名称,单位,数量,预算价不含税,市场价不含税,预算价合价,市场价合价,价差
|
||||
fields = [
|
||||
"供货方",
|
||||
"编码",
|
||||
"名称",
|
||||
"单位",
|
||||
"取整数量",
|
||||
"预算价不含税",
|
||||
"市场价不含税",
|
||||
"预算价合价",
|
||||
"市场价合价",
|
||||
"价差",
|
||||
]
|
||||
for field in fields:
|
||||
formatted_node[field] = node.get(field, "")
|
||||
elif node_type == "机械":
|
||||
# 机械节点保存:编码,名称,单位,数量,预算价不含税,市场价不含税,预算价合价,市场价合价,价差
|
||||
fields = [
|
||||
"编码",
|
||||
"名称",
|
||||
"单位",
|
||||
"取整数量",
|
||||
"预算价不含税",
|
||||
"市场价不含税",
|
||||
"预算价合价",
|
||||
"市场价合价",
|
||||
"价差",
|
||||
]
|
||||
for field in fields:
|
||||
formatted_node[field] = node.get(field, "")
|
||||
|
||||
# 重命名取整数量为数量
|
||||
if "取整数量" in formatted_node:
|
||||
formatted_node["数量"] = formatted_node.pop("取整数量")
|
||||
|
||||
formatted_nodes.append(formatted_node)
|
||||
|
||||
return formatted_nodes
|
||||
|
||||
|
||||
def calculate_rcj_fees(
|
||||
json_file_path: str,
|
||||
project_name: str,
|
||||
adjustment_type: str = "拆除",
|
||||
engineering_type: str = "预算工程",
|
||||
project_guid: str = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
计算项目划分节点下所有人材机节点的合价
|
||||
|
||||
Args:
|
||||
json_file_path: JSON文件路径
|
||||
project_name: 项目名称
|
||||
adjustment_type: 调差类型,默认为"拆除"
|
||||
engineering_type: 工程类型,默认为"预算工程"
|
||||
project_guid: 项目GUID,用于区分同名项目
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 计算结果,按工程量节点分组
|
||||
"""
|
||||
# 获取工程量节点,传递project_guid参数
|
||||
project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type, project_guid)
|
||||
|
||||
# 如果没有找到项目划分节点
|
||||
if project_children is None:
|
||||
print(f"警告: 未找到项目 '{project_name}' 的项目划分节点")
|
||||
return {}
|
||||
|
||||
# 如果找到项目划分节点但没有工程量节点
|
||||
if len(project_children) == 0:
|
||||
print(f"警告: 项目 '{project_name}' (GUID: {project_guid}) 没有工程量节点,跳过计算")
|
||||
return {}
|
||||
|
||||
# 获取分类后的人材机节点,传递project_guid参数
|
||||
labor_nodes, material_nodes, machine_nodes = get_classified_resource_nodes(
|
||||
json_file_path, project_name, adjustment_type, project_guid
|
||||
)
|
||||
|
||||
# 打印调试信息
|
||||
print(f"找到 {len(labor_nodes)} 个人工节点, {len(material_nodes)} 个材料节点, {len(machine_nodes)} 个机械节点")
|
||||
|
||||
# 计算人工节点的数量和合价
|
||||
labor_with_count = calc_rcj_count(labor_nodes, project_children, json_file_path)
|
||||
labor_with_cat = cat_rcj_count(labor_with_count)
|
||||
labor_with_fee = calc_rcj_fee(labor_with_cat)
|
||||
labor_formatted = format_rcj_output(labor_with_fee, "人工")
|
||||
|
||||
# 计算材料节点的数量和合价
|
||||
material_with_count = calc_rcj_count(material_nodes, project_children, json_file_path)
|
||||
material_with_cat = cat_rcj_count(material_with_count)
|
||||
material_with_fee = calc_rcj_fee(material_with_cat)
|
||||
material_formatted = format_rcj_output(material_with_fee, "材料")
|
||||
|
||||
# 计算机械节点的数量和合价
|
||||
machine_with_count = calc_rcj_count(machine_nodes, project_children, json_file_path)
|
||||
machine_with_cat = cat_rcj_count(machine_with_count)
|
||||
machine_with_fee = calc_rcj_fee(machine_with_cat)
|
||||
machine_formatted = format_rcj_output(machine_with_fee, "机械")
|
||||
|
||||
# 创建工程量节点ID到名称的映射
|
||||
node_id_to_name = {}
|
||||
for node in project_children:
|
||||
if "id" in node:
|
||||
node_name = node.get("项目名称", node.get("name", f"工程量节点_{node['id']}"))
|
||||
node_id_to_name[node["id"]] = node_name
|
||||
|
||||
# 按工程量节点分组结果
|
||||
result_by_parent = {}
|
||||
|
||||
# 处理人工节点
|
||||
for node in labor_formatted:
|
||||
parent_id = node.pop("parent_id", "")
|
||||
parent_name = node.pop("parent_name", "未知工程量节点")
|
||||
|
||||
# 使用父级节点名称作为键
|
||||
parent_key = node_id_to_name.get(parent_id, parent_name)
|
||||
|
||||
if parent_key not in result_by_parent:
|
||||
result_by_parent[parent_key] = {"人工节点": [], "材料节点": [], "机械节点": []}
|
||||
|
||||
result_by_parent[parent_key]["人工节点"].append(node)
|
||||
|
||||
# 处理材料节点
|
||||
for node in material_formatted:
|
||||
parent_id = node.pop("parent_id", "")
|
||||
parent_name = node.pop("parent_name", "未知工程量节点")
|
||||
|
||||
# 使用父级节点名称作为键
|
||||
parent_key = node_id_to_name.get(parent_id, parent_name)
|
||||
|
||||
if parent_key not in result_by_parent:
|
||||
result_by_parent[parent_key] = {"人工节点": [], "材料节点": [], "机械节点": []}
|
||||
|
||||
result_by_parent[parent_key]["材料节点"].append(node)
|
||||
|
||||
# 处理机械节点
|
||||
for node in machine_formatted:
|
||||
parent_id = node.pop("parent_id", "")
|
||||
parent_name = node.pop("parent_name", "未知工程量节点")
|
||||
|
||||
# 使用父级节点名称作为键
|
||||
parent_key = node_id_to_name.get(parent_id, parent_name)
|
||||
|
||||
if parent_key not in result_by_parent:
|
||||
result_by_parent[parent_key] = {"人工节点": [], "材料节点": [], "机械节点": []}
|
||||
|
||||
result_by_parent[parent_key]["机械节点"].append(node)
|
||||
|
||||
# 计算合价只用于调试显示
|
||||
for parent_key, parent_data in result_by_parent.items():
|
||||
labor_fee = sum(float(node.get("预算价合价", "0.0") or "0.0") for node in parent_data["人工节点"])
|
||||
material_fee = sum(float(node.get("预算价合价", "0.0") or "0.0") for node in parent_data["材料节点"])
|
||||
machine_fee = sum(float(node.get("预算价合价", "0.0") or "0.0") for node in parent_data["机械节点"])
|
||||
print(f"工程量节点 '{parent_key}': 人工合价={labor_fee}, 材料合价={material_fee}, 机械合价={machine_fee}")
|
||||
|
||||
return result_by_parent
|
||||
|
||||
|
||||
def calculate_resource_fees(
|
||||
json_file_path: str,
|
||||
project_name: str,
|
||||
adjustment_type: str,
|
||||
engineering_type: str,
|
||||
project_guid: str = None,
|
||||
calculation_strategy=None,
|
||||
) -> str:
|
||||
"""
|
||||
计算人材机合价
|
||||
|
||||
Args:
|
||||
json_file_path: JSON文件路径
|
||||
project_name: 项目名称
|
||||
adjustment_type: 调差类型
|
||||
engineering_type: 工程类型
|
||||
project_guid: 项目GUID,用于区分同名项目
|
||||
calculation_strategy: 计算策略,如果为None则使用默认策略
|
||||
|
||||
Returns:
|
||||
str: 输出文件路径,如果没有工程量节点则返回None
|
||||
"""
|
||||
# 如果没有提供计算策略,使用默认策略
|
||||
if calculation_strategy is None:
|
||||
from calculation_strategy import DefaultCalculationStrategy
|
||||
|
||||
calculation_strategy = DefaultCalculationStrategy()
|
||||
|
||||
# 获取项目划分节点的GUID
|
||||
_, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid)
|
||||
# 如果传入了GUID但与节点的GUID不一致,优先使用传入的GUID
|
||||
node_guid = target_node.get("GUID") or target_node.get("guid", "") if target_node else ""
|
||||
project_guid = project_guid if project_guid else node_guid
|
||||
|
||||
# 将文件名中的非法字符替换为下划线
|
||||
safe_project_name = (
|
||||
project_name.replace("/", "_")
|
||||
.replace("\\", "_")
|
||||
.replace(":", "_")
|
||||
.replace("*", "_")
|
||||
.replace("?", "_")
|
||||
.replace('"', "_")
|
||||
.replace("<", "_")
|
||||
.replace(">", "_")
|
||||
.replace("|", "_")
|
||||
)
|
||||
|
||||
# 将GUID中的非法字符替换为下划线
|
||||
safe_project_guid = ""
|
||||
if project_guid:
|
||||
safe_project_guid = (
|
||||
project_guid.replace("/", "_")
|
||||
.replace("\\", "_")
|
||||
.replace(":", "_")
|
||||
.replace("*", "_")
|
||||
.replace("?", "_")
|
||||
.replace('"', "_")
|
||||
.replace("<", "_")
|
||||
.replace(">", "_")
|
||||
.replace("|", "_")
|
||||
.replace("{", "")
|
||||
.replace("}", "")
|
||||
)
|
||||
# 添加下划线作为分隔符
|
||||
safe_project_guid = f"_{safe_project_guid}"
|
||||
|
||||
# 设置输出目录和文件名
|
||||
output_dir = "计算结果"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
rcj_output_file = os.path.join(
|
||||
output_dir, f"{safe_project_name}{safe_project_guid}_{adjustment_type}_rcj_fees.json"
|
||||
)
|
||||
|
||||
# 计算人材机节点的合价,传递project_guid参数
|
||||
# 这里使用 calculation_strategy 的 calculate_rcj_fees 方法
|
||||
if hasattr(calculation_strategy, "calculate_rcj_fees"):
|
||||
rcj_results = calculation_strategy.calculate_rcj_fees(
|
||||
json_file_path, project_name, adjustment_type, engineering_type, project_guid
|
||||
)
|
||||
else:
|
||||
# 如果计算策略没有实现 calculate_rcj_fees 方法,使用原始函数
|
||||
rcj_results = calculate_rcj_fees(json_file_path, project_name, adjustment_type, engineering_type, project_guid)
|
||||
|
||||
# 检查是否有人材机数据 - 适应新的按工程量节点分组的结构
|
||||
has_data = False
|
||||
|
||||
# 如果是旧结构(直接包含人工节点、材料节点、机械节点)
|
||||
if any(key in rcj_results for key in ["人工节点", "材料节点", "机械节点"]):
|
||||
for node_type in ["人工节点", "材料节点", "机械节点"]:
|
||||
if rcj_results.get(node_type) and len(rcj_results[node_type]) > 0:
|
||||
has_data = True
|
||||
break
|
||||
# 如果是新结构(按工程量节点分组)
|
||||
else:
|
||||
for parent_name, parent_data in rcj_results.items():
|
||||
for node_type in ["人工节点", "材料节点", "机械节点"]:
|
||||
if parent_data.get(node_type) and len(parent_data[node_type]) > 0:
|
||||
has_data = True
|
||||
break
|
||||
if has_data:
|
||||
break
|
||||
|
||||
if not has_data:
|
||||
print(f"项目 '{project_name}' (GUID: {project_guid}) 没有人材机数据,不保存结果")
|
||||
return None
|
||||
|
||||
# 保存人材机合价计算结果到JSON文件
|
||||
with open(rcj_output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(rcj_results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n人材机合价计算结果已保存到 {rcj_output_file}")
|
||||
|
||||
return rcj_output_file
|
||||
Reference in New Issue
Block a user