Files
KG_generation/equipment_calculation/calculator_base.py
T
chentianrui f5f26c5cf8 上传代码
2025-09-08 17:58:02 +08:00

347 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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