347 lines
13 KiB
Python
347 lines
13 KiB
Python
import os
|
||
from abc import ABC, abstractmethod
|
||
from memory_profiler import profile
|
||
from typing import Dict, List, Any, Optional, Tuple
|
||
from equipment_calculation.software_types import SoftwareType
|
||
from equipment_calculation.find_project_nodes import get_project_divisions_list
|
||
from equipment_calculation.quantity_fee_calculator import calculate_quantity_fees as base_calculate_quantity_fees
|
||
from equipment_calculation.resource_fee_calculator import calculate_resource_fees as base_calculate_resource_fees
|
||
from equipment_calculation.calculation_strategy import CalculationStrategy, DefaultCalculationStrategy
|
||
import json
|
||
import psutil
|
||
from datetime import datetime
|
||
|
||
|
||
class CalculatorBase(ABC):
|
||
"""计算器基类"""
|
||
|
||
def __init__(self, software_type: SoftwareType):
|
||
"""
|
||
初始化计算器
|
||
|
||
Args:
|
||
software_type: 软件类型
|
||
"""
|
||
self.software_type = software_type
|
||
self.config_dir = software_type.config_dir
|
||
# 使用默认计算策略
|
||
self.calculation_strategy = self.create_calculation_strategy()
|
||
# 默认输出目录
|
||
self.output_dir = os.path.join("计算结果", self.software_type.name)
|
||
# 新增:BCL 脚本目录(优先使用新接口)
|
||
self.bcl_dir: str | None = None
|
||
|
||
def create_calculation_strategy(self) -> CalculationStrategy:
|
||
"""
|
||
创建计算策略
|
||
|
||
子类可以重写此方法,返回特定的计算策略实现
|
||
|
||
Returns:
|
||
CalculationStrategy: 计算策略实例
|
||
"""
|
||
return DefaultCalculationStrategy()
|
||
|
||
def get_default_input_file(self) -> str:
|
||
"""获取默认输入文件路径"""
|
||
return os.path.join(self.software_type.test_dir)
|
||
|
||
def get_output_dir(self) -> str:
|
||
"""获取输出目录路径"""
|
||
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 set_bcl_dir(self, bcl_dir: str) -> None:
|
||
"""
|
||
设置 BCL 脚本目录路径(新接口)。
|
||
|
||
Args:
|
||
bcl_dir: BCL 目录路径
|
||
"""
|
||
self.bcl_dir = bcl_dir
|
||
|
||
def _append_log(self, text: str, filename: str = "performance_memory_log.txt") -> str:
|
||
"""将文本即时追加写入输出目录下的日志文件,并返回日志路径。"""
|
||
try:
|
||
out_dir = self.get_output_dir()
|
||
os.makedirs(out_dir, exist_ok=True)
|
||
log_path = os.path.join(out_dir, filename)
|
||
with open(log_path, "a", encoding="utf-8") as f:
|
||
f.write(text + "\n")
|
||
f.flush()
|
||
return log_path
|
||
except Exception as e:
|
||
# 即使写日志失败,也不要影响主流程
|
||
print(f"写入性能日志失败: {e}")
|
||
return ""
|
||
|
||
def _print_memory_usage(self, prefix: str = "") -> None:
|
||
"""记录当前进程的内存占用情况(MB)到输出目录日志文件。"""
|
||
try:
|
||
process = psutil.Process(os.getpid())
|
||
mem_info = process.memory_info()
|
||
rss = mem_info.rss / (1024**2) # 物理内存(MB)
|
||
vms = mem_info.vms / (1024**2) # 虚拟内存(MB)
|
||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
header = f"[{ts}]" + (f" [{prefix}]" if prefix else "")
|
||
lines = [
|
||
f"{header} 当前进程占用的物理内存: {rss:.2f} MB",
|
||
f"{header} 当前进程占用的虚拟内存: {vms:.2f} MB",
|
||
]
|
||
for line in lines:
|
||
self._append_log(line)
|
||
except Exception as e:
|
||
# 日志失败不影响主流程
|
||
print(f"记录内存信息失败: {e}")
|
||
|
||
def calculate_quantity_fee_tables(
|
||
self,
|
||
json_file_path: str,
|
||
project_name: str = None,
|
||
project_type: Optional[str] = None,
|
||
) -> None:
|
||
"""
|
||
计算工程量取费表
|
||
|
||
Args:
|
||
json_file_path: JSON文件路径
|
||
project_name: 项目名称,如果为None则处理所有项目
|
||
"""
|
||
engineering_type = self.software_type.engineering_type.value + "工程"
|
||
|
||
# 在计算前应用软件特定的规则
|
||
preprocess_func = self.apply_quantity_fee_rules()
|
||
if callable(preprocess_func): # 检查返回值是否可调用
|
||
preprocess_func(json_file_path) # 调用预处理函数
|
||
|
||
# 外层只读取一次 JSON
|
||
with open(json_file_path, "r", encoding="utf-8") as _f:
|
||
_json_data = json.load(_f)
|
||
|
||
# 外层只初始化一次 BCL 计算器
|
||
from equipment_calculation.bcl_utils import init_bcl_calculator
|
||
|
||
if getattr(self, "bcl_dir", None):
|
||
init_bcl_calculator(self.bcl_dir)
|
||
# else:
|
||
# # 兼容老接口
|
||
# init_bcl_calculator(
|
||
# software_category=self.software_type.category.value,
|
||
# engineering_type=self.software_type.engineering_type.value,
|
||
# calculation_type="工程量",
|
||
# project_type=project_type,
|
||
# )
|
||
|
||
if project_name:
|
||
# 处理单个项目划分
|
||
print(f"处理单个项目划分: {project_name}")
|
||
output_file = self._calculate_quantity_fees(
|
||
json_file_path, project_name, engineering_type, project_guid=None, json_data=_json_data
|
||
)
|
||
if output_file:
|
||
print(f"已完成 {project_name} 的工程量取费表计算,结果保存在 {output_file}")
|
||
# 性能打印:每个项目划分完成后打印内存占用
|
||
self._print_memory_usage(prefix=f"工程量-{project_name}")
|
||
else:
|
||
# 处理所有项目划分
|
||
project_divisions = get_project_divisions_list(json_file_path)
|
||
print(f"找到 {len(project_divisions)} 个项目划分节点,开始处理工程量取费表...")
|
||
|
||
for i, (proj_name, proj_guid) in enumerate(project_divisions, 1):
|
||
# 使用命令行参数中指定的调差类型,而不是从JSON文件中获取的调差类型
|
||
print(f"处理 {i}/{len(project_divisions)}: {proj_name} (GUID: {proj_guid})")
|
||
output_file = self._calculate_quantity_fees(
|
||
json_file_path, proj_name, engineering_type, proj_guid, json_data=_json_data
|
||
)
|
||
if output_file:
|
||
print(f"已完成 {proj_name} (GUID: {proj_guid}) 的工程量取费表计算")
|
||
# 性能打印:每个项目划分完成后打印内存占用
|
||
self._print_memory_usage(prefix=f"工程量-{proj_name}")
|
||
|
||
print(f"所有项目划分节点的工程量取费表计算完成,结果保存在 {self.get_output_dir()} 目录")
|
||
|
||
def calculate_resource_fee_tables(self, json_file_path: str, project_name: str = None) -> None:
|
||
"""
|
||
计算人材机合价
|
||
|
||
Args:
|
||
json_file_path: JSON文件路径
|
||
project_name: 项目名称,如果为None则处理所有项目
|
||
"""
|
||
# 获取工程类型
|
||
engineering_type = self.software_type.engineering_type.value + "工程"
|
||
|
||
# 在计算前应用软件特定的规则
|
||
self.apply_resource_fee_rules()
|
||
|
||
# 外层只读取一次 JSON
|
||
with open(json_file_path, "r", encoding="utf-8") as _f:
|
||
_json_data = json.load(_f)
|
||
|
||
# 外层只初始化一次 BCL 计算器(人材机)
|
||
from equipment_calculation.bcl_utils import init_bcl_calculator
|
||
|
||
if getattr(self, "bcl_dir", None):
|
||
init_bcl_calculator(self.bcl_dir)
|
||
# else:
|
||
# # 兼容老接口
|
||
# init_bcl_calculator(
|
||
# software_category=self.software_type.category.value,
|
||
# engineering_type=self.software_type.engineering_type.value,
|
||
# calculation_type="人材机",
|
||
# )
|
||
|
||
if project_name:
|
||
# 处理单个项目划分
|
||
output_file = self._calculate_resource_fees(
|
||
json_file_path,
|
||
project_name,
|
||
json_data=_json_data,
|
||
)
|
||
if output_file:
|
||
print(f"已完成 {project_name} 的人材机合价计算,结果保存在 {output_file}")
|
||
# 性能打印:每个项目划分完成后打印内存占用
|
||
self._print_memory_usage(prefix=f"人材机-{project_name}")
|
||
else:
|
||
# 处理所有项目划分
|
||
project_divisions = get_project_divisions_list(json_file_path)
|
||
print(f"找到 {len(project_divisions)} 个项目划分节点,开始处理人材机合价...")
|
||
|
||
for i, (proj_name, proj_guid) in enumerate(project_divisions, 1):
|
||
print(f"处理 {i}/{len(project_divisions)}: {proj_name} (GUID: {proj_guid})")
|
||
output_file = self._calculate_resource_fees(json_file_path, proj_name, proj_guid, json_data=_json_data)
|
||
if output_file:
|
||
print(f"已完成 {proj_name} (GUID: {proj_guid}) 的人材机合价计算")
|
||
# 性能打印:每个项目划分完成后打印内存占用
|
||
self._print_memory_usage(prefix=f"人材机-{proj_name}")
|
||
|
||
print(f"所有项目划分节点的人材机合价计算完成,结果保存在 {self.get_output_dir()} 目录")
|
||
|
||
def _calculate_quantity_fees(
|
||
self,
|
||
json_file_path: str,
|
||
project_name: str,
|
||
engineering_type: str,
|
||
project_guid: str = None,
|
||
json_data: dict | None = None,
|
||
) -> str:
|
||
"""
|
||
计算工程量取费表,并应用软件特定的后处理规则
|
||
|
||
Args:
|
||
json_file_path: JSON文件路径
|
||
project_name: 项目名称
|
||
engineering_type: 工程类型
|
||
project_guid: 项目GUID,用于区分同名项目
|
||
|
||
Returns:
|
||
str: 输出文件路径
|
||
"""
|
||
# 确保输出目录存在
|
||
os.makedirs(self.get_output_dir(), exist_ok=True)
|
||
|
||
# 打印计算策略信息
|
||
# print(f"使用计算策略: {self.calculation_strategy.__class__.__name__}")
|
||
|
||
# 调用基础计算函数,传入计算策略和项目GUID
|
||
output_file = base_calculate_quantity_fees(
|
||
json_file_path,
|
||
project_name,
|
||
engineering_type,
|
||
project_guid=project_guid,
|
||
calculation_strategy=self.calculation_strategy,
|
||
software_type=self.software_type.category.value,
|
||
json_data=json_data,
|
||
)
|
||
|
||
# 应用软件特定的后处理规则
|
||
if output_file:
|
||
self.post_process_quantity_fees(output_file, project_name)
|
||
|
||
return output_file
|
||
|
||
def _calculate_resource_fees(
|
||
self,
|
||
json_file_path: str,
|
||
project_name: str,
|
||
project_guid: str = None,
|
||
json_data: dict | None = None,
|
||
) -> str:
|
||
"""
|
||
计算人材机合价,并应用软件特定的后处理规则
|
||
|
||
Args:
|
||
json_file_path: JSON文件路径
|
||
project_name: 项目名称
|
||
engineering_type: 工程类型
|
||
project_guid: 项目GUID,用于区分同名项目
|
||
|
||
Returns:
|
||
str: 输出文件路径
|
||
"""
|
||
# 确保输出目录存在
|
||
os.makedirs(self.get_output_dir(), exist_ok=True)
|
||
|
||
# 初始化BCL计算器,传递软件类型和计算类型
|
||
from equipment_calculation.bcl_utils import init_bcl_calculator
|
||
|
||
init_bcl_calculator(
|
||
software_category=self.software_type.category.value,
|
||
engineering_type=self.software_type.engineering_type.value,
|
||
calculation_type="人材机",
|
||
)
|
||
|
||
# 调用基础计算函数,传入计算策略和项目GUID
|
||
output_file = base_calculate_resource_fees(
|
||
json_file_path,
|
||
project_name,
|
||
project_guid=project_guid,
|
||
calculation_strategy=self.calculation_strategy,
|
||
json_data=json_data,
|
||
)
|
||
|
||
# 应用软件特定的后处理规则
|
||
if output_file:
|
||
self.post_process_resource_fees(output_file, project_name)
|
||
|
||
return output_file
|
||
|
||
def apply_quantity_fee_rules(self) -> None:
|
||
"""应用软件特定的工程量取费规则(在计算前)"""
|
||
pass
|
||
|
||
def apply_resource_fee_rules(self) -> None:
|
||
"""应用软件特定的人材机合价规则(在计算前)"""
|
||
pass
|
||
|
||
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||
"""
|
||
应用软件特定的工程量取费后处理规则
|
||
|
||
Args:
|
||
output_file: 输出文件路径
|
||
project_name: 项目名称
|
||
"""
|
||
pass
|
||
|
||
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||
"""
|
||
应用软件特定的人材机合价后处理规则
|
||
|
||
Args:
|
||
output_file: 输出文件路径
|
||
project_name: 项目名称
|
||
"""
|
||
pass
|