From be848c3e7861b65a36c35218550daebe72a740aa Mon Sep 17 00:00:00 2001 From: chentianrui Date: Fri, 22 Aug 2025 18:13:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B4=B9=E7=94=A8=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tgitconfig | 0 .vscode/settings.json | 3 +- config.ini | 4 - convert_json.py | 4 +- cost_comparison.py | 4 +- equipment_calculation/bcl_calculator.py | 91 +- equipment_calculation/bcl_utils.py | 186 ++- equipment_calculation/calculation_strategy.py | 26 +- equipment_calculation/calculator_base.py | 100 +- equipment_calculation/expressioncalculator.py | 2 +- equipment_calculation/item_acquisition.py | 502 +++++- equipment_calculation/main.py | 136 +- equipment_calculation/new.py | 1456 ++++++++++++++++- equipment_calculation/project.py | 3 + .../quantity_fee_calculator.py | 181 +- .../resource_fee_calculator.py | 123 +- .../工程量/预算/{bcl => }/变量计算配置(线路).xml | Bin .../工程量/预算/{bcl => }/变量计算配置(配网).xml | Bin extract_errors.py | 46 +- transform_expense_preview.py | 62 + 20 files changed, 2569 insertions(+), 360 deletions(-) delete mode 100644 .tgitconfig delete mode 100644 config.ini rename equipment_calculation/计算配置/技改/工程量/预算/{bcl => }/变量计算配置(线路).xml (100%) rename equipment_calculation/计算配置/技改/工程量/预算/{bcl => }/变量计算配置(配网).xml (100%) diff --git a/.tgitconfig b/.tgitconfig deleted file mode 100644 index e69de29..0000000 diff --git a/.vscode/settings.json b/.vscode/settings.json index bb14cf9..7042fa3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "Codegeex.RepoIndex": true + "Codegeex.RepoIndex": true, + "python.languageServer": "None" } \ No newline at end of file diff --git a/config.ini b/config.ini deleted file mode 100644 index e65d727..0000000 --- a/config.ini +++ /dev/null @@ -1,4 +0,0 @@ -[neo4j] -uri = bolt://10.1.6.34:7687 -user = neo4j -password = password \ No newline at end of file diff --git a/convert_json.py b/convert_json.py index 758a1ac..9395767 100644 --- a/convert_json.py +++ b/convert_json.py @@ -33,7 +33,9 @@ def convert_json_to_readable(input_file, output_file=None): if __name__ == "__main__": # 指定输入文件路径 - input_file = r"project2json/outputs/json/变电技改国网.json" + input_file = ( + r"E:/文件/LLM_model/RAG/code/Engineering_data_KG-1/equipment_dataset/数据工程/技改/预算/通信线路检修国网.json" + ) # 调用转换函数 convert_json_to_readable(input_file) diff --git a/cost_comparison.py b/cost_comparison.py index 0caa342..34ba520 100644 --- a/cost_comparison.py +++ b/cost_comparison.py @@ -170,10 +170,10 @@ def save_comparison_to_txt(comparison, output_txt_path): def main(): # ================== 配置路径 ================== # 存放所有 calculation_results.json 的文件夹 - calc_results_folder = "project2json/outputs/bclresults/变电技改国网" + calc_results_folder = "project2json/outputs/bclresults/变电检修国网" # 主 project_data.json 路径(参考数据源) - project_data_json_path = "project2json/outputs/json/变电技改国网.json" + project_data_json_path = "project2json/outputs/json/变电检修国网.json" # 输出对比结果的文件夹 output_folder = "project2json/outputs/comparison_results" diff --git a/equipment_calculation/bcl_calculator.py b/equipment_calculation/bcl_calculator.py index bf3256c..14bff0c 100644 --- a/equipment_calculation/bcl_calculator.py +++ b/equipment_calculation/bcl_calculator.py @@ -1,16 +1,19 @@ from __future__ import annotations import logging +from memory_profiler import profile import xml.etree.ElementTree as ET from typing import Dict, Callable, Any, Optional import os from enum import Enum, auto from equipment_calculation.expressioncalculator import ExpressionCalculator import copy +import re +from xml.dom import minidom # 配置logging logging.basicConfig( - level=logging.DEBUG, + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[logging.FileHandler("bcl_calculator.log"), logging.StreamHandler()], ) @@ -744,6 +747,86 @@ def strfind_func(funcname: str, claculate, context: BCLContext, *args): return BCLVariant(position) +def _is_in_range(value, range_expression): + """判断值是否在范围表达式中 + + Args: + value (str): 要检查的值 + range_expression (str): 范围表达式,支持以下格式: + - 单个值: "JYT19-71" + - 范围: "JYT17-1~189" + - 范围(后半省略前缀且带前导零): "C09032001~09032315" + - 多个范围用逗号分隔: "JYT17-1~189,JYT18-1~100" + + Returns: + bool: 如果值在范围中返回True,否则返回False + """ + # 统一转为字符串 + if value is None or range_expression is None: + return False + value = str(value).strip() + range_expression = str(range_expression).strip() + + # 拆分为(前缀, 尾部数字)——前缀为末尾数字前的全部,数字为末尾一段数字 + def split_prefix_num(s: str) -> tuple[str, Optional[int]]: + s = str(s) + m = re.match(r"^(.*?)(\d+)$", s) + if not m: + return s, None + prefix, num_str = m.group(1), m.group(2) + try: + return prefix, int(num_str) + except ValueError: + return prefix, None + + value_prefix, value_num = split_prefix_num(value) + + # 处理多个范围表达式(用逗号分隔) + for seg in range_expression.split(","): + seg = seg.strip() + if not seg: + continue + + # 范围:start~end + if "~" in seg: + try: + start, end = [x.strip() for x in seg.split("~", 1)] + s_pref, s_num = split_prefix_num(start) + e_pref, e_num = split_prefix_num(end) + + # 允许 end 省略前缀(如 JYT17-1~189 或 C09032001~09032315), + # 即只要 end 全为数字,则继承 start 的前缀,并按数值比较(忽略前导零差异) + if e_pref == "" and end.isdigit(): + e_pref = s_pref + try: + e_num = int(end) + except ValueError: + e_num = None + + # 要求前缀完全一致 + if s_pref != e_pref: + continue + + # 值必须与范围前缀完全一致 + if value_prefix != s_pref: + continue + + # 三者数字齐备方可比较 + if value_num is None or s_num is None or e_num is None: + continue + + if s_num <= value_num <= e_num: + return True + except Exception: + continue + else: + # 单值:要求完全相等 + if value == seg: + return True + + return False + + def in_func(funcname: str, claculate, context: BCLContext, *args): # 参数数量校验 if len(args) != 2: @@ -780,8 +863,8 @@ def in_func(funcname: str, claculate, context: BCLContext, *args): else param2.value ) - # 判断value1是否在value2中 - result = value1 in value2 + # 使用自定义的范围判断函数 + result = _is_in_range(value1, value2) # 返回布尔类型的BCLVariant对象 return BCLVariant(result) @@ -1173,6 +1256,8 @@ class BCLCalculator: return False name = expr.get("name") + if name in self.expressions: + logging.warning(f"BCLExpression名称重复: {name},后来的定义将覆盖之前的。") self.expressions[name] = expr return True diff --git a/equipment_calculation/bcl_utils.py b/equipment_calculation/bcl_utils.py index 190102a..fc1b45e 100644 --- a/equipment_calculation/bcl_utils.py +++ b/equipment_calculation/bcl_utils.py @@ -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) diff --git a/equipment_calculation/calculation_strategy.py b/equipment_calculation/calculation_strategy.py index 93733fa..efc2d65 100644 --- a/equipment_calculation/calculation_strategy.py +++ b/equipment_calculation/calculation_strategy.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from memory_profiler import profile from typing import Dict, List, Any, Optional, Set, Tuple @@ -117,6 +118,7 @@ class CalculationStrategy(ABC): cost_table: Dict[str, Any], json_file_path: Optional[str] = None, engineering_type: Optional[str] = None, + json_data: Optional[Dict[str, Any]] = None, ) -> Dict[str, float]: """ 计算所有费用 @@ -138,6 +140,7 @@ class CalculationStrategy(ABC): rcj_nodes: List[Tuple[Dict[str, Any], str]], project_children: List[Dict[str, Any]], json_file_path: Optional[str] = None, + json_data: Optional[Dict[str, Any]] = None, ) -> List[Dict[str, Any]]: """ 计算人材机节点的数量,考虑父级消耗量 @@ -322,12 +325,20 @@ class DefaultCalculationStrategy(CalculationStrategy): cost_table: Dict[str, Any], json_file_path: Optional[str] = None, engineering_type: Optional[str] = None, + json_data: Optional[Dict[str, Any]] = None, ) -> Dict[str, float]: """计算所有费用""" from equipment_calculation.quantity_fee_calculator import calculate_all_fees as original_calculate_all_fees # 传递自身作为计算策略 - return original_calculate_all_fees(project_node, cost_table, json_file_path, engineering_type, self) + return original_calculate_all_fees( + project_node, + cost_table, + json_file_path, + engineering_type, + self, + json_data=json_data, + ) def calculate_rcj_count( self, @@ -338,7 +349,7 @@ class DefaultCalculationStrategy(CalculationStrategy): """计算人材机节点的数量""" from equipment_calculation.resource_fee_calculator import calc_rcj_count as original_calc_rcj_count - return original_calc_rcj_count(rcj_nodes, project_children, json_file_path) + return original_calc_rcj_count(rcj_nodes, project_children, json_file_path, json_data=json_data) def cat_rcj_count(self, rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """汇总人材机节点的数量""" @@ -359,7 +370,14 @@ class DefaultCalculationStrategy(CalculationStrategy): return original_format_rcj_output(rcj_nodes, node_type) # 修复 calculate_rcj_fees 未定义的错误 - def calculate_rcj_fees(self, json_file_path, project_name, project_guid=None): + def calculate_rcj_fees( + self, + json_file_path, + project_name, + project_guid=None, + json_data: Optional[Dict[str, Any]] = None, + engineering_type: Optional[str] = None, + ): """ 计算项目划分节点下所有人材机节点的合价 @@ -375,7 +393,7 @@ class DefaultCalculationStrategy(CalculationStrategy): from equipment_calculation.resource_fee_calculator import calculate_rcj_fees as original_calculate_rcj_fees # 调用原始函数 - return original_calculate_rcj_fees(json_file_path, project_name, project_guid) + return original_calculate_rcj_fees(json_file_path, project_name, project_guid, json_data=json_data) def post_process_quantity_fees(self, output_file: str, project_name: str) -> None: """对工程量取费表进行后处理""" diff --git a/equipment_calculation/calculator_base.py b/equipment_calculation/calculator_base.py index 22d8d55..f537cb0 100644 --- a/equipment_calculation/calculator_base.py +++ b/equipment_calculation/calculator_base.py @@ -1,5 +1,6 @@ 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 @@ -7,6 +8,8 @@ from equipment_calculation.quantity_fee_calculator import calculate_quantity_fee 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): @@ -57,10 +60,45 @@ class CalculatorBase(ABC): if hasattr(self.calculation_strategy, "set_output_dir"): self.calculation_strategy.set_output_dir(output_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: """ 计算工程量取费表 @@ -76,12 +114,30 @@ class CalculatorBase(ABC): 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 + + 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) + 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) @@ -90,9 +146,13 @@ class CalculatorBase(ABC): 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) + 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()} 目录") @@ -110,14 +170,30 @@ class CalculatorBase(ABC): # 在计算前应用软件特定的规则 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 + + 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) @@ -125,9 +201,11 @@ class CalculatorBase(ABC): 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) + 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()} 目录") @@ -137,6 +215,7 @@ class CalculatorBase(ABC): project_name: str, engineering_type: str, project_guid: str = None, + json_data: dict | None = None, ) -> str: """ 计算工程量取费表,并应用软件特定的后处理规则 @@ -153,17 +232,8 @@ class CalculatorBase(ABC): # 确保输出目录存在 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="工程量", - ) - # 打印计算策略信息 - print(f"使用计算策略: {self.calculation_strategy.__class__.__name__}") + # print(f"使用计算策略: {self.calculation_strategy.__class__.__name__}") # 调用基础计算函数,传入计算策略和项目GUID output_file = base_calculate_quantity_fees( @@ -172,6 +242,8 @@ class CalculatorBase(ABC): engineering_type, project_guid=project_guid, calculation_strategy=self.calculation_strategy, + software_type=self.software_type.category.value, + json_data=json_data, ) # 应用软件特定的后处理规则 @@ -185,6 +257,7 @@ class CalculatorBase(ABC): json_file_path: str, project_name: str, project_guid: str = None, + json_data: dict | None = None, ) -> str: """ 计算人材机合价,并应用软件特定的后处理规则 @@ -216,6 +289,7 @@ class CalculatorBase(ABC): project_name, project_guid=project_guid, calculation_strategy=self.calculation_strategy, + json_data=json_data, ) # 应用软件特定的后处理规则 diff --git a/equipment_calculation/expressioncalculator.py b/equipment_calculation/expressioncalculator.py index 37e9114..08e82af 100644 --- a/equipment_calculation/expressioncalculator.py +++ b/equipment_calculation/expressioncalculator.py @@ -125,7 +125,7 @@ class ExpressionCalculator: # 处理一元运算符 if token_type == "OPERATOR" and value in ("+", "-"): self.current_token += 1 - node = self.parse_factor() + node = self._parse_factor() return ("UNARYOP", value, node) # 处理变量 diff --git a/equipment_calculation/item_acquisition.py b/equipment_calculation/item_acquisition.py index 03a9e8b..2af7426 100644 --- a/equipment_calculation/item_acquisition.py +++ b/equipment_calculation/item_acquisition.py @@ -74,6 +74,22 @@ def map_quantity_node_types(node): # 复制节点以避免修改原始数据 node_copy = deepcopy(node) + # 先根据“配件判定规则”标准化类型: + # 若 节点的 type/类型 == "配件" 且 配型 == "配件",则检查 配件类型: + # - 配件类型 == "主材" -> 作为 主材 节点 + # - 配件类型 == "配件" -> 作为 设备 节点 + raw_type = node_copy.get("类型") if node_copy.get("类型") is not None else node_copy.get("type") + raw_peixing = node_copy.get("配型") + # 放宽条件:当 类型/type 为“配件”,且(配型为“配件”或缺省),并且存在“配件类型”时,按配件类型映射 + if str(raw_type) == "配件" and (raw_peixing in (None, "", "配件")): + pj_type = node_copy.get("配件类型") + if pj_type == "主材": + node_copy["类型"] = "主材" + node_copy["type"] = "主材" + elif pj_type == "配件": + node_copy["类型"] = "设备" + node_copy["type"] = "设备" + # 映射类型字段 if "类型" in node_copy: type_mapping = {"0": "定额", "1": "主材", "5": "设备"} @@ -86,9 +102,9 @@ def map_quantity_node_types(node): node_copy["费用类型"] = "取费" # 如果已经是"取费"则保持不变 - # 递归处理子节点 - if "children" in node_copy and node_copy["children"]: - node_copy["children"] = [map_quantity_node_types(child) for child in node_copy["children"]] + # 不在此处递归 children,避免对子节点进行类型标准化,交由各层独立处理 + # if isinstance(node_copy.get("children"), list): + # node_copy["children"] = [map_quantity_node_types(child) for child in node_copy["children"]] return node_copy @@ -101,15 +117,15 @@ def map_resource_node_types(node): # 复制节点以避免修改原始数据 node_copy = deepcopy(node) - # 映射类型字段 - if "类型" in node_copy: - type_mapping = { - "2": "人工", - "3": "材料", - "4": "机械", - } - if node_copy["类型"] in type_mapping: - node_copy["类型"] = type_mapping[node_copy["类型"]] + # 统一标准化类型字段:同时规范化 '类型' 与 'type',输出为中文(人工/材料/机械) + type_mapping = {"2": "人工", "3": "材料", "4": "机械"} + # 取原始值(可能存在任一字段) + raw_t = node_copy.get("类型") if node_copy.get("类型") is not None else node_copy.get("type") + if raw_t is not None: + # 若为代码,转换为中文;若已为中文则保持 + normalized = type_mapping.get(str(raw_t), raw_t) + node_copy["类型"] = normalized + node_copy["type"] = normalized # 递归处理子节点 if "children" in node_copy and node_copy["children"]: @@ -132,50 +148,72 @@ def extract_resource_nodes(node, parent_id=None): if not isinstance(node, dict): return [] # 返回空列表而不是None,便于后续处理 + # 类型工具 + def _is_resource_type(t): + return t in ("人工", "材料", "机械", "2", "3", "4") + + str2code = {"人工": "2", "材料": "3", "机械": "4"} + code2str = {v: k for k, v in str2code.items()} + # 获取当前节点ID - current_id = node.get("id") + current_id = node.get("id") or node.get("GUID") or node.get("guid") resource_nodes = [] - # 检查是否是人材机节点(支持字符串和数字类型) - node_type = node.get("类型") - is_resource_node = False + # 检查是否是人材机节点(支持字符串和数字类型、双字段) + node_type = node.get("类型") or node.get("type") + is_resource_node = _is_resource_type(node_type) - # 同时支持数字类型和字符串类型 - if node_type in ["人工", "材料", "机械", "2", "3", "4"]: - is_resource_node = True + # 合并处理材机列表和children字段 # 处理逻辑: + # - 若为资源节点且存在children:不返回父节点;其children继承父类型后继续递归提取 + # - 若为资源节点且无children:返回该节点 + # - 若非资源节点:常规递归处理children/材机列表 - # 复制节点但不包含children和材机列表 - node_copy = {k: v for k, v in node.items() if k not in ["children", "材机列表"]} - - # 添加父级ID - if parent_id: - node_copy["parent_id"] = parent_id - - # 添加到资源列表 - resource_nodes.append(node_copy) - - # 合并处理材机列表和children字段 - 只处理一种来源的子节点,优先处理材机列表 + # 优先处理“材机列表” child_nodes = [] if "材机列表" in node and node["材机列表"]: child_nodes = node["材机列表"] - elif "children" in node and node["children"] and not is_resource_node: - # 只有在不是资源节点时才处理children + elif "children" in node and node["children"]: child_nodes = node["children"] - # 处理子节点 - for child in child_nodes: - child_copy = deepcopy(child) - child_copy["parent_id"] = current_id - - # 递归处理 - sub_resources = extract_resource_nodes(child_copy, current_id) - if sub_resources: - resource_nodes.extend(sub_resources) + if is_resource_node: + if child_nodes: + # 父为资源且有子:上提子项,子项继承父类型(同时设置中英文字段),并递归 + parent_code = str2code.get(node_type, node_type) + parent_str = code2str.get(node_type, node_type) + for child in child_nodes: + child_copy = deepcopy(child) + if isinstance(child_copy, dict): + child_copy["parent_id"] = current_id + child_copy["type"] = parent_code + child_copy["类型"] = parent_str + sub_resources = extract_resource_nodes(child_copy, current_id) + if sub_resources: + resource_nodes.extend(sub_resources) + else: + # 基本类型直接忽略 + continue + else: + # 父为资源且无子:直接计入 + node_copy = deepcopy(node) + node_copy["parent_id"] = parent_id + # 统一双字段 + node_copy["type"] = str2code.get(node_type, node_type) + node_copy["类型"] = code2str.get(node_type, node_type) + resource_nodes.append(node_copy) + else: + # 非资源节点:递归其子节点 + for child in child_nodes: + child_copy = deepcopy(child) + if isinstance(child_copy, dict): + child_copy["parent_id"] = current_id + sub_resources = extract_resource_nodes(child_copy, current_id) + if sub_resources: + resource_nodes.extend(sub_resources) return resource_nodes -def process_project_children(children): +def process_project_children(children, software_type=None): """处理项目子节点,分离工程量节点和人材机节点,支持嵌套的工程量节点""" if not children: return None, None @@ -183,15 +221,135 @@ def process_project_children(children): quantity_nodes = [] resource_nodes = [] + # 内部工具:在人材机存在拆分时展开其子节点,删除中间父级人材机节点 + def _flatten_resource_splits_in_place(node: dict): + """ + 递归+迭代地展开任意深度的人材机拆分: + - 如果某个子节点是人材机(人工/材料/机械/2/3/4)且其自身有children: + * 将其children上提为当前层的children; + * 被上提的每个子节点的 类型/type 设为与父节点相同; + * 移除该父级人材机节点本身; + - 对非被移除的子节点递归处理; + 通过 while 循环保证多层嵌套被完全展开。 + """ + if not isinstance(node, dict): + return + if "children" not in node or not node["children"]: + return + + def _is_resource_type(t): + return t in ("人工", "材料", "机械", "2", "3", "4") + + # 中英文类型映射 + str2code = {"人工": "2", "材料": "3", "机械": "4"} + code2str = {v: k for k, v in str2code.items()} + + changed = True + while changed: + changed = False + new_children = [] + for ch in node["children"]: + if isinstance(ch, dict): + t = ch.get("类型") or ch.get("type") + # 命中“人材机且存在children”,则将其子节点上提并继承类型 + if _is_resource_type(t) and ch.get("children"): + parent_code = str2code.get(t, t) # 若 t 已是代码则保持 + parent_str = code2str.get(t, t) # 若 t 已是中文则保持 + for gc in ch.get("children", []) or []: + if isinstance(gc, dict): + gc["type"] = parent_code + gc["类型"] = parent_str + new_children.append(gc) + else: + new_children.append(gc) + # 丢弃父级人材机节点 + changed = True + continue + else: + # 对未被移除的子节点继续递归展开 + _flatten_resource_splits_in_place(ch) + new_children.append(ch) + else: + new_children.append(ch) + node["children"] = new_children + for child in children: - # 对于工程量节点,深拷贝用于工程量树 + # 先复制两份:一份用于工程量树(quantity_node),一份用于资源提取(resource_node_src) quantity_node = deepcopy(child) + resource_node_src = deepcopy(child) + # 额外保留一份原始工程量节点用于最终输出,保持原样不作修改 + original_quantity_node = deepcopy(child) + + # 在两份结构中都先执行“人材机拆分展开”,以便: + # 1) 工程量树不保留中间的人材机父节点 + # 2) 资源提取时直接提取到展开后的子资源,且不包含中间父节点 + _flatten_resource_splits_in_place(quantity_node) + _flatten_resource_splits_in_place(resource_node_src) # 应用工程量节点类型映射 quantity_node = map_quantity_node_types(quantity_node) - # 提取人材机节点 - extract_resource_nodes会递归提取所有层级的人材机节点 - resources = extract_resource_nodes(child) + # 在“技改/主网”下,对定额节点做嵌套检测(children 或 材机列表 中包含 定额/主材/设备 或 配件类型为 主材/配件) + if software_type in ("技改", "主网"): + + def _type_is_quota_material_equipment(t): + # 支持代码与中文类型名 + return t in ("定额", "主材", "设备", "0", "1", "5") + + def _collect_nested_matches(node): + matches = [] + # 检查 children + for ch in node.get("children", []) or []: + if isinstance(ch, dict): + t = ch.get("类型") or ch.get("type") + pj_t = ch.get("配件类型") + if _type_is_quota_material_equipment(t) or pj_t in ("主材", "配件"): + matches.append( + { + "来源": "children", + "名称": ch.get("项目名称") or ch.get("清单名称") or ch.get("name"), + "类型": t, + "配件类型": pj_t, + "id": ch.get("GUID") or ch.get("guid") or ch.get("id"), + } + ) + # 递归继续检查更深层的嵌套 + sub = _collect_nested_matches(ch) + if sub: + matches.extend(sub) + + # 检查 材机列表(如存在) + for it in node.get("材机列表", []) or []: + if isinstance(it, dict): + t = it.get("类型") or it.get("type") + pj_t = it.get("配件类型") + if _type_is_quota_material_equipment(t) or pj_t in ("主材", "配件"): + matches.append( + { + "来源": "材机列表", + "名称": it.get("项目名称") + or it.get("清单名称") + or it.get("name") + or it.get("材料名称"), + "类型": t, + "配件类型": pj_t, + "id": it.get("GUID") or it.get("guid") or it.get("id"), + } + ) + return matches + + node_type = quantity_node.get("类型") or quantity_node.get("type") + if node_type in ("定额", "0"): + nested_matches = _collect_nested_matches(quantity_node) + if nested_matches: + quantity_node["嵌套检测"] = { + "存在嵌套": True, + "命中数量": len(nested_matches), + "命中项": nested_matches, + } + + # 提取人材机节点 - 基于已展开的 resource_node_src 提取 + resources = extract_resource_nodes(resource_node_src) if resources: # 应用人材机节点类型映射 resources = [map_resource_node_types(resource) for resource in resources] @@ -204,24 +362,146 @@ def process_project_children(children): # 清理子节点中的人材机节点 clean_resource_nodes_from_quantity(quantity_node) - # 递归处理剩余的工程量子节点 - if "children" in quantity_node and quantity_node["children"]: - sub_quantity_nodes, sub_resource_nodes = process_project_children(quantity_node["children"]) + # 🔥 核心修改:仅当当前节点是"定额"时,才递归处理其children并提取嵌套的工程量节点 + current_node_type = quantity_node.get("类型") or quantity_node.get("type") + is_quota_node = current_node_type in ("定额", "0") - # 更新quantity_node的children + sub_quantity_nodes = None + sub_resource_nodes = None + + if is_quota_node and "children" in quantity_node and quantity_node["children"]: + # 处理定额的人材机节点 + # 关键:递归输入改为原始副本的 children,避免上一层标准化对子层“原始类型字段”的影响 + sub_quantity_nodes, sub_resource_nodes = process_project_children( + (original_quantity_node.get("children") or []), software_type + ) + + # 更新当前节点的 children(保留结构) if sub_quantity_nodes: quantity_node["children"] = sub_quantity_nodes + # 关键:将子层的工程量节点(如主材/设备)直接并入当前层输出, + # 并确保类型为标准化后的中文值(主材/设备),避免后续 BCL 过滤不到 + # 不修改原始子节点类型,仅做深拷贝并并入 + normalized_children = [deepcopy(n) for n in sub_quantity_nodes] + quantity_nodes.extend(normalized_children) + # 调试:打印并入的子工程量节点类型 + try: + for ch in normalized_children: + nm = ( + ch.get("项目名称") + or ch.get("清单名称") + or ch.get("name") + or ch.get("材料名称") + or "<未命名>" + ) + tp = ch.get("类型") or ch.get("type") + # print(f"[DEBUG] 并入子工程量节点: 名称={nm} | 类型={tp}") + except Exception: + pass else: - quantity_node.pop("children", None) # 使用pop安全删除 + quantity_node.pop("children", None) - # 合并子节点中提取的资源节点 + # 合并资源节点 if sub_resource_nodes: resource_nodes.extend(sub_resource_nodes) + # else: + # 非定额节点:不递归提取子节点作为工程量,不处理 sub_quantity_nodes - # 添加到工程量节点列表 - quantity_nodes.append(quantity_node) + # 对于定额节点:将“材机列表”里符合条件的项(主材/设备或配件类型为主材/配件)也作为工程量节点并入 + if is_quota_node: + mj_items = (quantity_node.get("材机列表") or []) if isinstance(quantity_node, dict) else [] + for it in mj_items: + if not isinstance(it, dict): + continue + t = it.get("类型") or it.get("type") + pj_t = it.get("配件类型") + # 类型代码转中文 + if t in ("1", "5"): + t = {"1": "主材", "5": "设备"}[t] + new_type = None + if t in ("主材", "设备"): + new_type = t + elif pj_t == "主材": + new_type = "主材" + elif pj_t == "配件": + new_type = "设备" + if new_type: + # 不改写原始字段,直接将条目拷贝加入,类型由下游对象构造派生 + new_node = deepcopy(it) + quantity_nodes.append(new_node) + try: + nm = ( + new_node.get("项目名称") + or new_node.get("清单名称") + or new_node.get("name") + or new_node.get("材料名称") + or "<未命名>" + ) + # print(f"[DEBUG] 从材机列表并入工程量节点: 名称={nm} | 类型={t} | 派生类型={new_type}") + except Exception: + pass - return (quantity_nodes if quantity_nodes else None, resource_nodes if resource_nodes else None) + # 🔥 最后:将当前节点本身加入 quantity_nodes + # 为满足“工程量节点输出保持原样”,此处使用 original_quantity_node 代替处理后的 quantity_node + # 规则: + # - 若当前节点为“主材/设备(1/5)”,直接计入工程量节点; + # - 若为“定额(0)”,沿用既有两个条件之一成立时计入; + def _children_have_no_children(n: dict) -> bool: + ch = (n.get("children") or []) if isinstance(n, dict) else [] + # 若无 children,则视作“没有更深层 children” + if not ch: + return True + for c in ch: + if isinstance(c, dict) and (c.get("children") or []): + return False + return True + + def _mj_has_hit(n: dict) -> bool: + items = (n.get("材机列表") or []) if isinstance(n, dict) else [] + for it in items: + if not isinstance(it, dict): + continue + t = it.get("类型") or it.get("type") + pj_t = it.get("配件类型") + if t in ("定额", "主材", "设备", "0", "1", "5") or pj_t in ("主材", "配件"): + return True + return False + + # 主材/设备直接计入(以标准化类型入列,避免后续 BCL 过滤不到) + if current_node_type in ("主材", "设备", "1", "5"): + to_append = deepcopy(original_quantity_node) + quantity_nodes.append(to_append) + try: + nm = ( + to_append.get("项目名称") + or to_append.get("清单名称") + or to_append.get("name") + or to_append.get("材料名称") + or "<未命名>" + ) + tp = to_append.get("类型") or to_append.get("type") + # print(f"[DEBUG] 入列工程量节点(主材/设备): 名称={nm} | 类型={tp}") + except Exception: + pass + # 定额按条件计入 + elif is_quota_node: + if _children_have_no_children(quantity_node) or _mj_has_hit(quantity_node): + quantity_nodes.append(original_quantity_node) + try: + nm = ( + original_quantity_node.get("项目名称") + or original_quantity_node.get("清单名称") + or original_quantity_node.get("name") + or original_quantity_node.get("材料名称") + or "<未命名>" + ) + tp = original_quantity_node.get("类型") or original_quantity_node.get("type") + # print(f"[DEBUG] 入列工程量节点(定额): 名称={nm} | 类型={tp}") + except Exception: + pass + + # 始终返回列表,避免上层出现 None 导致判定困难 + return (quantity_nodes or [], resource_nodes or []) def clean_resource_nodes_from_quantity(node): @@ -249,12 +529,16 @@ def clean_resource_nodes_from_quantity(node): del node["children"] -def load_project_data(json_file_path, project_name, project_guid=None): +def load_project_data(json_file_path, project_name, project_guid=None, json_data=None): """加载JSON数据并获取目标项目节点""" try: - # 读取JSON文件 - with open(json_file_path, "r", encoding="utf-8") as f: - data = json.load(f) + # 支持透传已加载的 JSON 数据,避免重复读取 + if json_data is not None: + data = json_data + else: + # 读取JSON文件 + with open(json_file_path, "r", encoding="utf-8") as f: + data = json.load(f) # 获取projectData中的costSetting和projectDivision project_data = data.get("projectData", {}) @@ -280,12 +564,12 @@ def load_project_data(json_file_path, project_name, project_guid=None): return None, None, None, None, None -def get_cost_table_children(json_file_path, project_name, project_guid=None): +def get_cost_table_children(json_file_path, project_name, project_guid=None, json_data=None): """获取取费表子节点""" try: # 加载项目数据,传递project_guid参数 data, project_data, cost_setting, project_division, target_node = load_project_data( - json_file_path, project_name, project_guid + json_file_path, project_name, project_guid, json_data=json_data ) if not target_node: @@ -307,7 +591,7 @@ def get_cost_table_children(json_file_path, project_name, project_guid=None): return None -def get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid=None): +def get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid=None, software_type=None): """ 获取工程量节点 @@ -394,7 +678,7 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g if bill_children: # 处理清单节点的子节点,获取工程量节点 - bill_quantity_nodes, _ = process_project_children(bill_children) + bill_quantity_nodes, _ = process_project_children(bill_children, software_type) if bill_quantity_nodes: print(f"清单节点 '{bill_name}' 下找到 {len(bill_quantity_nodes)} 个工程量节点") @@ -495,7 +779,7 @@ def get_quantity_nodes(json_file_path, project_name, engineering_type, project_g return quantity_nodes else: # 预算工程 - 直接获取工程量节点 - quantity_nodes, _ = process_project_children(project_children) + quantity_nodes, _ = process_project_children(project_children, software_type) # 如果没有工程量节点,返回空列表而不是None if not quantity_nodes: @@ -754,35 +1038,77 @@ def get_bill_node_by_id(bill_id: str) -> Dict[str, Any]: return {} -# # 测试代码 # if __name__ == "__main__": -# json_file_path = os.path.join("技改预算", "架线.json") -# project_name = "基础工程材料工地运输" -# adjustment_type = "拆除" +# # 测试参数(使用新合并的JSON文件) +# json_file_path = "project2json/outputs/merged/变电检修国网-副本.json" +# project_name = "1.12新增卸车保管" +# project_guid = "{0952D46D-E5C7-4412-88FF-93517EF2633C}" -# # 获取并输出取费表子节点 -# cost_children = get_cost_table_children(json_file_path, project_name) -# print("\n取费表子节点:") -# print(json.dumps(cost_children, ensure_ascii=False, indent=2) if cost_children else None) +# def _node_name(n: dict): +# # 尽可能稳健地取名称字段 +# for k in ("名称", "name", "材料名称", "定额名称", "项目名称", "title"): +# if isinstance(n, dict) and n.get(k): +# return n.get(k) +# return n.get("newGuid") or n.get("GUID") or n.get("id") or "<无名称>" -# # 获取并输出工程量节点 -# quantity_nodes = get_quantity_nodes(json_file_path, project_name, adjustment_type) -# print("\n工程量节点:") -# print(json.dumps(quantity_nodes, ensure_ascii=False, indent=2) if quantity_nodes else None) +# def _node_type(n: dict): +# return n.get("类型") or n.get("type") or "<无类型>" -# # 获取并输出分类后的人材机节点 -# labor_nodes, material_nodes, machine_nodes = get_classified_resource_nodes( -# json_file_path, project_name, adjustment_type -# ) +# def _node_id(n: dict): +# return n.get("GUID") or n.get("guid") or n.get("id") or n.get("parent_id") or "<无ID>" -# print("\n人工节点列表:") -# for node, parent_id in labor_nodes: -# print(f"节点名称: {node.get('名称')}, 父级ID: {parent_id}") +# def _print_nodes(nodes, title): +# print(f"{title} 共 {len(nodes) if nodes else 0} 个:") +# if not nodes: +# return +# for i, item in enumerate(nodes, 1): +# n = item[0] if isinstance(item, tuple) and item and isinstance(item[0], dict) else item +# try: +# print(f" {i:02d}. 名称={_node_name(n)} | 类型={_node_type(n)} | ID={_node_id(n)}") +# except Exception: +# print(f" {i:02d}. {item}") -# print("\n材料节点列表:") -# for node, parent_id in material_nodes: -# print(f"节点名称: {node.get('名称')}, 父级ID: {parent_id}") +# def _check_resource_parents_with_children(nodes): +# """统计是否存在类型为人/材/机且仍带children的父节点""" -# print("\n机械节点列表:") -# for node, parent_id in machine_nodes: -# print(f"节点名称: {node.get('名称')}, 父级ID: {parent_id}") +# def _is_res(t): +# return t in ("人工", "材料", "机械", "2", "3", "4") + +# bad = [] +# for item in nodes or []: +# n = item[0] if isinstance(item, tuple) and item and isinstance(item[0], dict) else item +# if isinstance(n, dict) and _is_res(n.get("类型") or n.get("type")) and n.get("children"): +# bad.append(n) +# if bad: +# print("[警告] 发现仍存在带children的人材机父节点,这些父节点应被跳过,子节点应上提:") +# _print_nodes(bad, "异常父级人材机节点") +# else: +# print("[校验] 未发现带children的人材机父节点,扁平化正常。") + +# print(f"测试项目: {project_name}") +# print(f"文件路径: {json_file_path}") +# print(f"项目GUID: {project_guid}") +# print("-" * 50) + +# # 获取工程量节点 +# print("获取工程量节点...") +# quantity_nodes = get_quantity_nodes(json_file_path, project_name, "预算工程", project_guid, software_type="技改") +# _print_nodes(quantity_nodes or [], "工程量节点") + +# # 获取人材机节点(应为:父为人材机且有children时,父不计,子上提并继承父类型) +# print("获取人材机节点...") +# resource_nodes = get_resource_nodes(json_file_path, project_name, project_guid) +# _print_nodes(resource_nodes or [], "人材机节点") +# _check_resource_parents_with_children(resource_nodes or []) + +# # 获取分类后的人材机节点 +# print("获取分类后的人材机节点...") +# labor_nodes, material_nodes, machinery_nodes = get_classified_resource_nodes( +# json_file_path, project_name, project_guid +# ) +# _print_nodes(labor_nodes or [], "人工节点") +# _print_nodes(material_nodes or [], "材料节点") +# _print_nodes(machinery_nodes or [], "机械节点") + +# print("-" * 50) +# print("测试完成!") diff --git a/equipment_calculation/main.py b/equipment_calculation/main.py index 5b94c4e..7a9e5dc 100644 --- a/equipment_calculation/main.py +++ b/equipment_calculation/main.py @@ -1,5 +1,6 @@ import os import json +from memory_profiler import profile from typing import Dict, List, Any, Optional, Tuple from equipment_calculation.software_types import ( get_software_type, @@ -29,101 +30,65 @@ CATEGORY_MAPPING = { } -def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str]]: +# 项目类型名称映射字典,将各种变体映射到标准类型(预算/清单) +PROJECT_TYPE_MAPPING = { + # 预算类变体 + "概预算工程": "预算", +} + + +####应该是加在json后没释放 +def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str], Optional[str]]: """ - 从JSON文件内容中解析软件类别和工程类型 + 从JSON文件内容中解析: + - 软件类别: 来自 basicData["软件类别"](若无则尝试 basicData["软件名称"] 作为兜底) + - 项目类型: 来自 basicData["项目类型"](期望为 "预算" 或 "清单") + - 工程类型: 来自 projectData.projectInfo["工程类型"] :param json_file_path: JSON文件路径 - :return: (category, engineering_type) 元组,如果解析失败则返回 (None, None) + :return: (category, project_type, engineering_type) 元组,解析失败对应位置返回 None """ try: with open(json_file_path, "r", encoding="utf-8") as f: data = json.load(f) - # 定义阶段类型映射表 - budget_types = ["概预算", "定额", "定额计价", "概算", "概预算工程"] - list_types = ["清单", "结算", "招标控制价", "招投标工程", "清单计价"] - - # 从division字段获取软件名称和阶段类型 - if "division" in data: - division = data["division"] - print(f"找到division字段: {division}") - - # 使用-分割division字段 - parts = division.split("-") - if len(parts) == 2: - category = parts[0].strip() - stage_type = parts[1].strip() - elif len(parts) == 3: - category = parts[0].strip() - stage_type = parts[2].strip() + # 提取 basicData + basic_data = data.get("basicData", {}) if isinstance(data, dict) else {} + # 软件类别(优先 软件类别,其次 软件名称) + category = basic_data.get("软件名称") or basic_data.get("软件名称") + engineering_type = basic_data.get("项目类型") + # 规范化项目类型为 预算/清单 + if engineering_type: + mapped_pt = PROJECT_TYPE_MAPPING.get(engineering_type) + if mapped_pt: + engineering_type = mapped_pt else: - print(f"警告: division字段 '{division}' 格式不正确,无法分割") - # 可以选择返回默认值或抛出异常,这里以返回默认值为例 - category = "主网" + print(f"警告: basicData.项目类型 '{engineering_type}' 不是有效值,将使用默认值 '清单'") engineering_type = "清单" - print(f"使用默认值: 软件名称={category}, 阶段类型={engineering_type}") - return category, engineering_type - # 使用映射字典规范化软件类别 + # 规范化软件类别 + if category: if category in CATEGORY_MAPPING: category = CATEGORY_MAPPING[category] else: - print(f"警告: division中的软件名称 '{category}' 不是有效值,将使用默认值 '主网'") + print(f"警告: basicData中的软件名称/名称 '{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 = "清单" + # 提取工程类型:projectData.projectInfo.工程类型 + project_info = data.get("projectData", {}).get("projectInfo", {}) if isinstance(data, dict) else {} + project_type = project_info.get("工程类型") - print(f"从division解析: 软件名称={category}, 阶段类型={engineering_type} (原始值: {stage_type})") - return category, engineering_type + # 打印解析结果(便于调试) + print(f"解析完成: 软件类别={category}, 项目类型={engineering_type}, 工程类型={project_type}") - else: - print(f"警告: JSON文件中未找到division字段,尝试从basicData中解析") - - # 作为备选,尝试从basicData中获取 - if "basicData" in data: - basic_data = data["basicData"] - category = basic_data.get("软件名称") - engineering_type = basic_data.get("阶段类型") - - # 验证解析结果 - if category and engineering_type: - # 使用映射字典规范化软件类别 - if category in CATEGORY_MAPPING: - category = CATEGORY_MAPPING[category] - else: - print(f"警告: basicData中的软件名称 '{category}' 不是有效值,将使用默认值 '主网'") - category = "主网" - - # 确保engineering_type是有效值 - if engineering_type not in ["预算", "清单"]: - print(f"警告: basicData中的阶段类型 '{engineering_type}' 不是有效值,将使用默认值 '清单'") - engineering_type = "清单" - - print(f"从basicData解析: 软件名称={category}, 阶段类型={engineering_type}") - return category, engineering_type - else: - print(f"警告: basicData中未找到软件名称或阶段类型") - else: - print(f"警告: JSON文件中未找到basicData部分") - - return None, None + return category, engineering_type, project_type except Exception as e: print(f"解析JSON文件内容时出错: {str(e)}") - return None, None + return None, None, None -def process_json_file( - json_file_path: str, output_dir: str, calculate_type: str = "all", project_name: str = None -) -> bool: +def process_json_file(json_file_path: str, output_dir: str, calculate_type: str, project_name: str = None) -> bool: """ 处理单个JSON文件 @@ -137,20 +102,27 @@ def process_json_file( bool: 处理是否成功 """ try: - # 从JSON文件内容中解析软件类别和工程类型 - category, engineering_type = parse_json_content(json_file_path) + # 从JSON文件内容中解析软件类别、项目类型(预算/清单) 和 工程类型(如 变电/线路/配网) + # parse_json_content 返回顺序为: (category, project_type, engineering_type) + category, project_type, engineering_type = parse_json_content(json_file_path) # 如果解析失败,使用默认值 if category is None: category = "主网" print(f"无法从文件中解析软件类别,使用默认值: {category}") - if engineering_type is None: - engineering_type = "预算" - print(f"无法从文件中解析工程类型,使用默认值: {engineering_type}") + # 项目类型(预算/清单) 兜底 + if project_type is None: + project_type = "预算" + print(f"无法从文件中解析项目类型(预算/清单),使用默认值: {project_type}") + + # 可选:记录工程类型 + if engineering_type: + print(f"工程类型: {engineering_type}") # 获取软件类型 - software_type = get_software_type(category, engineering_type) + # 这里的第二个参数应为项目类型(预算/清单) + software_type = get_software_type(category, project_type) print(f"使用软件类型: {software_type.name}") # 获取计算器 @@ -190,6 +162,8 @@ def process_json_file( calculator.calculate_quantity_fee_tables( json_file_path=json_file_path, project_name=project_name, + # 这里的 project_type 传递给计算器用于BCL筛选,应为 工程类型,例如"变电/线路/配网" + project_type=engineering_type, ) if calculate_type in ["all", "resource"]: @@ -217,6 +191,8 @@ def process_json_file( custom_calculator.calculate_quantity_fee_tables( json_file_path=json_file_path, project_name=project_name, + # 这里的 project_type 传递给计算器用于BCL筛选,应为 工程类型,例如"变电/线路/配网" + project_type=engineering_type, ) if calculate_type in ["all", "resource"]: @@ -236,7 +212,7 @@ def process_json_file( return False -def process_directory(input_dir: str, output_dir: str, calculate_type: str = "all") -> None: +def process_directory(input_dir: str, output_dir: str, calculate_type: str = "quantity") -> None: """ 批量处理目录中的JSON文件 @@ -269,7 +245,7 @@ def process_directory(input_dir: str, output_dir: str, calculate_type: str = "al print(f"处理完成,成功: {success_count}/{len(json_files)}") -def bcl_calculate(input_dir: str, output_dir: str, calculate_type: str = "all") -> None: +def bcl_calculate(input_dir: str, output_dir: str, calculate_type: str = "quantity") -> None: """ 主函数,处理指定目录中的所有JSON文件 diff --git a/equipment_calculation/new.py b/equipment_calculation/new.py index 43f5548..a307224 100644 --- a/equipment_calculation/new.py +++ b/equipment_calculation/new.py @@ -304,7 +304,7 @@ def create_equipment_from_node(node: dict[str, any]) -> Equipment: def init_bcl_calculator(): """初始化BCL计算器""" try: - result = calculator.load_scripts_dir("计算配置/主网/人材机/预算") + result = calculator.load_scripts_dir("equipment_calculation/计算配置/技改/工程量/预算") if False == result: print(f"加载脚本错误: {calculator.get_last_error()}") else: @@ -985,63 +985,1367 @@ bill_node = { } ration = { - "颜色标记": "标记:16777215;", - "type": "定额", - "关联父级量": "0", - "id": "8611", - "项目名称": "人力运输 金具、绝缘子、零星钢材1", - "编码": "YX1-17", - "单位": "t.km", - "计算式": "(2.951359)+(21.773142)+(0.621326)", - "数量": "25.345827", - "中标计算式": "(2.951359)+(21.773142)+(0.621326)", - "人工费": "123", - "机械费": "8.28", - "甲供材料费不含税": "0", - "材料费": "0", + "数量": "12", + "guid": "{800B71DC-CC56-4AE8-965B-85F7DB47E059}", + "id": "{800B71DC-CC56-4AE8-965B-85F7DB47E059}", + "标准资源": "0", "定额系数": "1", - "人工系数": "1", - "材料系数": "1", + "配合比": "1", + "工程量索引": "0", + "统计": "0", + "关联父级量": "0", + "人工费": "236.66", + "单价": "852.85", + "定额费": "0", + "标记": "换", "机械系数": "1", - "定额范围": "1", - "定额章节名称": "金具、绝缘子、零星钢材", - "资源库名称": "7", - "费用类型": "取费", - "甲供材料费含税": "0", - "投标数量": "0", - "投标单价": "0", - "投标合价": "0", - "其中:甲供材料费": "0", - "单价不含税": "131.28", - "合价不含税": "3327.400169", - "特征段": "1", - "基价": "131.28", - "所属定额库": "预算 第四册 架空输电线路工程(2018年版)", + "含量": "3*0.01*400", + "项目名称": "钢筋混凝土底板与墙 钢筋混凝土墙 厚度25cm以内(现浇混凝土 C25-40 集中搅拌 替换为 水工 预制混凝土 C40-40 集中搅拌)", + "type": "定额", + "类型": "定额", + "机械费": "43.79", + "调试费计取": "不计取", + "人工系数": "1", + "浇捣方式": "现场搅拌机+泵车", + "计算式": "含量", + "定额范围": "概算", + "基价": "852.85", + "摊销材料费计取": "不计取", + "甲供材料费": "4.03", + "调差类型": "建筑修缮", + "编码": "JGT7-8", + "单价不含税": "852.85", + "资源库名称": "技改概算 第一册 建筑工程", + "人工工日": "0", + "脚手架计取": "不计取", + "材料系数": "1", + "甲供材料费_含税": "10.42", + "删除": "0", + "单位": "m³", + "StatSrcInfo": "{34CAB6C7-8453-40CE-AA40-3F0B92389DF0}", + "多行表达式": '\r\n\r\n\r\n', + "材料费_含税": "11.01", + "材料费": "572.4", + "审前数量": "0", + "审前人工系数": "0", + "审前机械费": "0", + "审前材料系数": "0", + "审前单价不含税": "0", + "审前基价": "0", + "审前材料费": "0", + "审前定额系数": "0", + "荐标记": "0", + "审前人工费": "0", + "审前机械系数": "0", + "合价不含税": "10234.2", + "合价": "10234.2", } -moe = { - "ID": "3", - "编码": "9101110", - "名称": "输电普通工", - "单位": "工日", - "类型": "2", - "供货方": "乙供", - "预算价不含税": "70", - "市场价不含税": "70", - "预算价含税": "0", - "市场价含税": "0", - "结算预算价不含税": "0", - "结算市场价不含税": "0", - "结算预算价含税": "0", - "结算市场价含税": "0", - "暂估价": "0", - "拆分": "0", - "全口径市场价不含税": "0", - "全口径市场价含税": "0", - "商品砼": "0", - "数量": "1.492", - "是否未计价": "0", -} +moes = [ + { + "数量": "1.7561", + "si": "589731982", + "type": "人工", + "类型": "人工", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "工日", + "编码": "9101106", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "75", + "补差": "0", + "名称": "普通工", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "75", + }, + { + "数量": "1.0092", + "si": "-64452061", + "type": "人工", + "类型": "人工", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "工日", + "编码": "9102102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "104", + "补差": "0", + "名称": "建筑技术工", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "104", + }, + { + "数量": "0.22", + "si": "-1042462405", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "甲供", + "单位": "kg", + "编码": "C01020701", + "商品砼": "0", + "市场价含税": "3", + "市场价不含税": "5", + "补差": "0", + "名称": "铁件 钢筋", + "预算价含税": "1.1", + "单重": "0", + "拆分": "0", + "预算价不含税": "3.4", + }, + { + "数量": "0.88", + "si": "-202836307", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "甲供", + "单位": "kg", + "编码": "C01020702", + "商品砼": "0", + "市场价含税": "22", + "市场价不含税": "10", + "补差": "0", + "名称": "铁件 型钢", + "预算价含税": "11", + "单重": "0", + "拆分": "0", + "预算价不含税": "3.46", + }, + { + "数量": "1.009", + "si": "-741847927", + "原始名称": "现浇混凝土 C25-40 集中搅拌", + "替换": "1", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C09032315", + "商品砼": "0", + "市场价含税": "0.47", + "市场价不含税": "339.91", + "补差": "0", + "名称": "水工 预制混凝土 C40-40 集中搅拌", + "预算价含税": "0.47", + "单重": "0", + "拆分": "1", + "预算价不含税": "339.56", + "children": [ + { + "数量": "0.0149", + "si": "-713466896", + "type": "人工", + "类型": "人工", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "工日", + "编码": "9101106", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "75", + "补差": "0", + "名称": "普通工", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "75", + }, + { + "数量": "0.0347", + "si": "-154743675", + "type": "人工", + "类型": "人工", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "工日", + "编码": "9102102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "104", + "补差": "0", + "名称": "建筑技术工", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "104", + }, + { + "数量": "0.425", + "si": "-446510056", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "t", + "编码": "C09010103", + "商品砼": "0", + "市场价含税": "1.1", + "市场价不含税": "460.428", + "补差": "0", + "名称": "普通硅酸盐水泥 52.5", + "预算价含税": "1.1", + "单重": "0", + "拆分": "0", + "预算价不含税": "460.428", + }, + { + "数量": "0.55", + "si": "-10409727", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C10010101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "69.487", + "补差": "0", + "名称": "中砂", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "69.487", + }, + { + "数量": "0.77", + "si": "889235358", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C10020103", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "83.366", + "补差": "0", + "名称": "碎石 40", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "83.366", + }, + { + "数量": "0.4", + "si": "842443366", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "t", + "编码": "C21010101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.1", + "补差": "0", + "名称": "水", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.1", + }, + { + "数量": "0.017", + "si": "1763161267", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "台班", + "编码": "J06-01-030", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "1136.86", + "补差": "0", + "名称": "混凝土搅拌输送车 搅动容量 6m³", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "1136.86", + }, + { + "数量": "0.007", + "si": "-1547727318", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "台班", + "编码": "J06-01-049", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "2253.176", + "补差": "0", + "名称": "混凝土搅拌站 生产率 50m³/h", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "2253.176", + }, + ], + }, + { + "数量": "0.4551", + "si": "-112284958", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C09041201", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "2.16", + "补差": "0", + "名称": "隔离剂", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "2.16", + }, + { + "数量": "0.3672", + "si": "-1118374709", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C10080102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.061", + "补差": "0", + "名称": "石油沥青 30号", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.061", + }, + { + "数量": "0.0008", + "si": "362628524", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C10080201", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4566.04", + "补差": "0", + "名称": "石油沥青玛蹄脂", + "预算价含税": "0", + "单重": "0", + "拆分": "1", + "预算价不含税": "4564.67", + "children": [ + { + "数量": "0.1822", + "si": "-713466896", + "type": "人工", + "类型": "人工", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "工日", + "编码": "9101106", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "75", + "补差": "0", + "名称": "普通工", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "75", + }, + { + "数量": "0.0456", + "si": "-154743675", + "type": "人工", + "类型": "人工", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "工日", + "编码": "9102102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "104", + "补差": "0", + "名称": "建筑技术工", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "104", + }, + { + "数量": "255", + "si": "260114790", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C10050201", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "0.899", + "补差": "0", + "名称": "滑石粉", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "0.899", + }, + { + "数量": "1058", + "si": "-1118374709", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C10080102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.061", + "补差": "0", + "名称": "石油沥青 30号", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.061", + }, + { + "数量": "0.137", + "si": "-1564276573", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "台班", + "编码": "J06-01-027", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "149.47", + "补差": "0", + "名称": "灰浆搅拌机 拌筒容量 200L", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "149.47", + }, + ], + }, + { + "数量": "0.0472", + "si": "-1626020189", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C12010100", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "5.355", + "补差": "0", + "名称": "电焊条 J422 综合", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "5.355", + }, + { + "数量": "0.4428", + "si": "-1975415116", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C13050101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "5.94", + "补差": "0", + "名称": "圆钉", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "5.94", + }, + { + "数量": "2.6858", + "si": "1452117355", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C14010100", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.674", + "补差": "0", + "名称": "镀锌铁丝", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.674", + }, + { + "数量": "0.7217", + "si": "1747130154", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m²", + "编码": "C18040802", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "0.701", + "补差": "0", + "名称": "聚氯乙烯塑料薄膜 0.5mm", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "0.701", + }, + { + "数量": "0.0022", + "si": "2062330344", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C19020601", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "6.5", + "补差": "0", + "名称": "松节油", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "6.5", + }, + { + "数量": "0.0004", + "si": "179573430", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C19031101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "2.623", + "补差": "0", + "名称": "清洗剂", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "2.623", + }, + { + "数量": "0.0001", + "si": "-1596635134", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C19070221", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "6.09", + "补差": "0", + "名称": "催干剂", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "6.09", + }, + { + "数量": "0.0064", + "si": "-1152716259", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C19110101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.77", + "补差": "0", + "名称": "氧气", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.77", + }, + { + "数量": "0.0028", + "si": "1436204340", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C19110201", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "11.05", + "补差": "0", + "名称": "乙炔气", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "11.05", + }, + { + "数量": "0.0061", + "si": "-894527259", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C20010101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "9.87", + "补差": "0", + "名称": "防锈漆", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "9.87", + }, + { + "数量": "0.0086", + "si": "27096461", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C20030301", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "11.714", + "补差": "0", + "名称": "酚醛调和漆", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "11.714", + }, + { + "数量": "0.318", + "si": "842443366", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "t", + "编码": "C21010101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.1", + "补差": "0", + "名称": "水", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.1", + }, + { + "数量": "3.7654", + "si": "-1955570465", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C22010101", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.706", + "补差": "0", + "名称": "钢管脚手架 包括扣件", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.706", + }, + { + "数量": "3.456", + "si": "908509220", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C22010102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.518", + "补差": "0", + "名称": "支撑钢管及扣件", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.518", + }, + { + "数量": "0.0012", + "si": "-2116431727", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "块", + "编码": "C22010131", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "43.325", + "补差": "0", + "名称": "钢脚手板 50×250×4000", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "43.325", + }, + { + "数量": "0.0089", + "si": "-21956691", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m²", + "编码": "C22010321", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "3.745", + "补差": "0", + "名称": "尼龙编织布", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "3.745", + }, + { + "数量": "5.8547", + "si": "-239431756", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C22010401", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "4.712", + "补差": "0", + "名称": "通用钢模板", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "4.712", + }, + { + "数量": "2.4366", + "si": "387925781", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m²", + "编码": "C22010431", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "42.491", + "补差": "0", + "名称": "复合木模板", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "42.491", + }, + { + "数量": "0.0162", + "si": "362721934", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "m³", + "编码": "C22010432", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "1649.989", + "补差": "0", + "名称": "木模板", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "1649.989", + }, + { + "数量": "0.003", + "si": "-237212855", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "张", + "编码": "C22040102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "0.579", + "补差": "0", + "名称": "砂纸", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "0.579", + }, + { + "数量": "0.0983", + "si": "1524607229", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "kg", + "编码": "C22040511", + "商品砼": "0", + "市场价含税": "1.1", + "市场价不含税": "6.61", + "补差": "0", + "名称": "麻丝", + "预算价含税": "1.1", + "单重": "0", + "拆分": "0", + "预算价不含税": "6.61", + }, + { + "数量": "0.456", + "si": "1417560998", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "甲供", + "单位": "kg", + "编码": "C22040821", + "商品砼": "0", + "市场价含税": "1.1", + "市场价不含税": "0.526", + "补差": "0", + "名称": "木柴", + "预算价含税": "1.1", + "单重": "0", + "拆分": "0", + "预算价不含税": "0.526", + }, + { + "数量": "10.2192", + "si": "-1558622683", + "type": "材料", + "类型": "材料", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "供货方": "乙供", + "单位": "元", + "编码": "C99010102", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "1", + "补差": "0", + "名称": "其他材料费", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "1", + }, + { + "数量": "0.035", + "si": "76469325", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J03-01-033", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "11", + "补差": "0", + "名称": "汽车式起重机 起重量 5t", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "564.811", + }, + { + "数量": "0.0001", + "si": "-1598035019", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J03-01-038", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "1125.35", + "补差": "0", + "名称": "汽车式起重机 起重量 25t", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "1125.35", + }, + { + "数量": "0.0009", + "si": "-2084005593", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J03-01-077-2", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "22", + "补差": "0", + "名称": "塔式起重机 起重力矩 2500kN·m", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "5054.94", + }, + { + "数量": "0.0416", + "si": "-450509515", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J04-01-002", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "33", + "补差": "0", + "名称": "载重汽车 5t", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "375.731", + }, + { + "数量": "0.0001", + "si": "869032009", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J04-01-003", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "390.913", + "补差": "0", + "名称": "载重汽车 6t", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "390.913", + }, + { + "数量": "0.004", + "si": "825623446", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J05-01-001", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "174.577", + "补差": "0", + "名称": "电动单筒快速卷扬机 10kN", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "174.577", + }, + { + "数量": "0.0008", + "si": "-1168576166", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J05-01-023", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "291.849", + "补差": "0", + "名称": "单笼施工电梯 提升质量(t)1 提升高度75m", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "291.849", + }, + { + "数量": "0.004", + "si": "-1214705068", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J05-01-034", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "19.9", + "补差": "0", + "名称": "卷扬机架(单笼5t以内) 架高 40m以内", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "19.9", + }, + { + "数量": "0.113", + "si": "1440877872", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J06-01-052", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "13.67", + "补差": "0", + "名称": "混凝土振捣器(插入式)", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "13.67", + }, + { + "数量": "0.02", + "si": "-1339492320", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J08-01-024", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "28.21", + "补差": "0", + "名称": "木工圆锯机 直径 φ500", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "28.21", + }, + { + "数量": "0.0001", + "si": "-1581108338", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J08-01-058", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "29.255", + "补差": "0", + "名称": "摇臂钻床 钻孔直径 φ50", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "29.255", + }, + { + "数量": "0.0086", + "si": "845179494", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J10-01-001", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "64.589", + "补差": "0", + "名称": "交流弧焊机 容量 21kVA", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "64.589", + }, + { + "数量": "0.0001", + "si": "-590459590", + "type": "机械", + "类型": "机械", + "调差类型": "建筑修缮", + "审前市场价不含税": "0", + "审前市场价含税": "0", + "审前数量": "0", + "审前预算价不含税": "0", + "单位": "台班", + "编码": "J11-01-018", + "商品砼": "0", + "市场价含税": "0", + "市场价不含税": "131.09", + "补差": "0", + "名称": "电动空气压缩机 排气量 3m³/min", + "预算价含税": "0", + "单重": "0", + "拆分": "0", + "预算价不含税": "131.09", + }, +] + # json_file_path = "测试案例/技改预算线路.json" @@ -1071,9 +2375,9 @@ moe = { # context = BCLDataSourceContext([QuantityItem], bill_context) ############################## -# # 预算示例 -# json_file_path = "测试案例/主网预算线路.json" -# project_context = create_project_contexts(json_file_path=json_file_path) +# 预算示例 +json_file_path = "project2json/outputs/merged/变电检修国网-副本.json" +project_context = create_project_contexts(json_file_path=json_file_path) DXITEM = [ BCLDataSourceItem( @@ -1091,30 +2395,40 @@ DXITEM = [ ] -# QuantityItem = BCLDataSourceItem(ration) +QuantityItem = BCLDataSourceItem(ration) -# dxitem_context = BCLDataSourceContext(DXITEM, project_context) -# dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM) -# # 创建上下文 -# context = BCLDataSourceContext([QuantityItem], dxitem_context) - -##################################### -# 人材机示例 -json_file_path = "测试案例/主网预算线路.json" -project_context = create_project_contexts(json_file_path=json_file_path) dxitem_context = BCLDataSourceContext(DXITEM, project_context) dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM) -QuantityItem = BCLDataSourceItem(ration, None, "定额") +childItems = [] -moeItem = BCLDataSourceItem(moe, QuantityItem) +for moe in moes: + childItems.append(BCLDataSourceItem(moe, QuantityItem)) -context = BCLDataSourceContext([moeItem], dxitem_context) +QuantityItem.set_childs(childItems) + +# 创建上下文 +context = BCLDataSourceContext([QuantityItem], dxitem_context) + + +##################################### +# 人材机示例 +# json_file_path = "测试案例/主网预算线路.json" +# project_context = create_project_contexts(json_file_path=json_file_path) + +# dxitem_context = BCLDataSourceContext(DXITEM, project_context) +# dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM) + +# QuantityItem = BCLDataSourceItem(ration, None, "定额") + +# moeItem = BCLDataSourceItem(moe, QuantityItem) + +# context = BCLDataSourceContext([moeItem], dxitem_context) try: - result = calculator.calculate("_材机合并人工数量", context) + result = calculator.calculate("混凝土施工调整人工费", context) print(f"人工计算结果: {result}\n") except Exception as e: print(f"计算错误: {calculator.get_last_error() if hasattr(calculator, 'getLastError') else e}\n") diff --git a/equipment_calculation/project.py b/equipment_calculation/project.py index 0196967..f61f381 100644 --- a/equipment_calculation/project.py +++ b/equipment_calculation/project.py @@ -24,6 +24,7 @@ class ProjectQuantity: self.cost_set = None # xsd:CostSet self.设备类型 = None # xsd:string self.供货方 = None # xsd:string + self.children = None # (定额) self.综合地形类型 = None # xsd:string @@ -32,6 +33,8 @@ class ProjectQuantity: self.机械费不含税 = None # xsd:string self.地形费计算方式 = None # xsd:string self.专业属性 = None # xsd:string + self.专业类型 = None # xsd:string + self.浇捣方式 = None # xsd:string # (主材) self.拆分 = None # xsd:string diff --git a/equipment_calculation/quantity_fee_calculator.py b/equipment_calculation/quantity_fee_calculator.py index cd12bc6..fcbdefe 100644 --- a/equipment_calculation/quantity_fee_calculator.py +++ b/equipment_calculation/quantity_fee_calculator.py @@ -1,8 +1,10 @@ import json import os import sys +from memory_profiler import profile from typing import Dict, List, Any, Optional, Tuple, Set from equipment_calculation.expressioncalculator import ExpressionCalculator + from equipment_calculation.bcl_utils import ( ZjQuantityBCLContext, ZjProjectBCLContext, @@ -15,6 +17,7 @@ from equipment_calculation.bcl_utils import ( BCLDataSourceContext, create_list_from_node, create_node_from_type, + create_material_or_equipment_from_node, ) from equipment_calculation.item_acquisition import ( get_cost_table_children, @@ -23,6 +26,7 @@ from equipment_calculation.item_acquisition import ( find_cost_table, get_bill_node_by_id, load_project_data, + get_classified_resource_nodes, ) # 缓存已计算过的费用 @@ -223,7 +227,7 @@ def calculate_external_variable(var_name: str, context: ZjQuantityBCLContext, ca try: # 如果有计算策略,使用计算策略的方法 if calculation_strategy: - print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}") + # print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}") # 直接调用计算策略的方法,而不是递归调用自己 return calculation_strategy.calculate_external_variable(var_name, context) @@ -447,17 +451,21 @@ def calculate_all_fees( json_file_path: str = None, engineering_type: str = None, calculation_strategy=None, + json_data: dict | None = None, ) -> Dict[str, float]: """ 计算项目节点的所有费用,确保清单数量正确传递 """ results = {} - # 1. 工程信息上下文 - project_context = create_project_contexts(json_file_path=json_file_path) + # 1. 工程信息上下文(优先使用传入的 json_data) + project_context = create_project_contexts(json_file_path=json_file_path, json_data=json_data) - with open(json_file_path, "r", encoding="utf-8") as file: - data = json.load(file) + if json_data is not None: + data = json_data + else: + with open(json_file_path, "r", encoding="utf-8") as file: + data = json.load(file) processed_data = process_DXdata(data) @@ -497,8 +505,37 @@ def calculate_all_fees( billItem.bill_quantity = bill_obj.quantity print(f"设置清单数据源项数量: {billItem.bill_quantity}") + # 如果无法创建工程量对象,则跳过并返回当前已计算结果,避免未定义的 QuantityItem + if not project_obj: + print( + f"警告: 无法从项目节点创建工程量对象,跳过该节点: " + f"{project_node.get('项目名称', project_node.get('name', '未知节点'))}" + ) + return results + QuantityItem = BCLDataSourceItem(project_obj, billItem) + + childItems = [] + if project_obj.children: + for moe in project_obj.children: + childItems.append(BCLDataSourceItem(moe, QuantityItem)) + + QuantityItem.set_childs(childItems) + bill_context = BCLDataSourceContext([billItem], dxitem_context) + + # # 构建人材机数据源项(若存在)并接入上下文 + # rcj_nodes_for_parent = project_node.get("_rcj_nodes", []) + # moe_items = [] + # if rcj_nodes_for_parent: + # try: + # for rcj_node in rcj_nodes_for_parent: + # node_obj = create_material_or_equipment_from_node(rcj_node) + # moe_items.append(BCLDataSourceItem(node_obj, QuantityItem)) + # except Exception as e: + # print(f"构建人材机数据源项出错: {e}") + + # items_chain = [QuantityItem] + moe_items if moe_items else [QuantityItem] context = BCLDataSourceContext([QuantityItem], bill_context) # 如果计算策略存在,设置清单数量 @@ -513,7 +550,35 @@ def calculate_all_fees( dxitem_context = BCLDataSourceContext(DXITEM, project_context) dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM) + # 如果无法创建工程量对象,则跳过并返回当前已计算结果,避免未定义的 QuantityItem + if not project_obj: + print( + f"警告: 无法从项目节点创建工程量对象,跳过该节点: " + f"{project_node.get('项目名称', project_node.get('name', '未知节点'))}" + ) + return results + QuantityItem = BCLDataSourceItem(project_obj) + + childItems = [] + if project_obj.children: + for moe in project_obj.children: + childItems.append(BCLDataSourceItem(moe, QuantityItem)) + + QuantityItem.set_childs(childItems) + + # # 构建人材机数据源项(若存在)并接入上下文 + # rcj_nodes_for_parent = project_node.get("_rcj_nodes", []) + # moe_items = [] + # if rcj_nodes_for_parent: + # try: + # for rcj_node in rcj_nodes_for_parent: + # node_obj = create_material_or_equipment_from_node(rcj_node) + # moe_items.append(BCLDataSourceItem(node_obj, QuantityItem)) + # except Exception as e: + # print(f"构建人材机数据源项出错: {e}") + + # items_chain = [QuantityItem] + moe_items if moe_items else [QuantityItem] context = BCLDataSourceContext([QuantityItem], dxitem_context) # 查找包含取费基数的节点 @@ -696,6 +761,8 @@ def calculate_quantity_fees( engineering_type: str = None, project_guid: str = None, calculation_strategy=None, + software_type: str = None, + json_data: dict | None = None, ) -> str: """ 计算工程量取费表,不使用全局变量,并按清单节点组织结果 @@ -769,7 +836,9 @@ def calculate_quantity_fees( if engineering_type == "预算工程": # 预算工程 - 获取项目划分节点的取费表,传递project_guid参数 cost_table_children = get_cost_table_children(json_file_path, project_name, project_guid) - project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid) + project_children = get_quantity_nodes( + json_file_path, project_name, engineering_type, project_guid, software_type + ) if not cost_table_children or not project_children: print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的取费表或项目划分节点") @@ -777,7 +846,9 @@ def calculate_quantity_fees( elif engineering_type == "清单工程": # 清单工程 - 获取清单节点的取费表,传递project_guid参数 - project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid) + project_children = get_quantity_nodes( + json_file_path, project_name, engineering_type, project_guid, software_type + ) if not project_children: print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的项目划分节点") return None @@ -785,6 +856,16 @@ def calculate_quantity_fees( # 为每个工程量节点找到对应的清单节点取费表 cost_tables = {} + # 准备一次性 JSON 数据(清单工程需要从 projectData 中获取 costSetting 与 bill) + if json_data is not None: + _data_all = json_data + else: + with open(json_file_path, "r", encoding="utf-8") as f: + _data_all = json.load(f) + _project_data_all = _data_all.get("projectData", {}) + _cost_setting_all = _project_data_all.get("costSetting", {}) + _bill_data_all = _project_data_all.get("bill", {}) + for project_node in project_children: node_name = project_node.get("项目名称", project_node.get("name", "未知节点")) @@ -813,15 +894,8 @@ def calculate_quantity_fees( if bill_id not in cost_tables: print(f"查找清单节点 '{bill_name}' 的取费表: {fee_table_name}") - # 查找取费表 - with open(json_file_path, "r", encoding="utf-8") as f: - data = json.load(f) - - project_data = data.get("projectData", {}) - cost_setting = project_data.get("costSetting", {}) - - # 查找取费表 - cost_table = find_cost_table(cost_setting, fee_table_name) + # 查找取费表:使用已加载的数据 + cost_table = find_cost_table(_cost_setting_all, fee_table_name) if not cost_table: print(f"未找到取费表 '{fee_table_name}'") @@ -835,12 +909,8 @@ def calculate_quantity_fees( cost_tables[bill_id] = cost_table_children print(f"成功获取清单节点 '{bill_name}' 的取费表") - # 在读取JSON文件后,遍历项目中的清单节点,获取并保存清单数量 - with open(json_file_path, "r", encoding="utf-8") as f: - data = json.load(f) - - project_data = data.get("projectData", {}) - bill_data = project_data.get("bill", {}) + # 遍历项目中的清单节点,获取并保存清单数量(使用已加载的数据) + bill_data = _bill_data_all # 递归函数获取所有清单节点 def get_bill_nodes(node): @@ -885,6 +955,55 @@ def calculate_quantity_fees( print(f"不支持的工程类型: {engineering_type}") return None + # 预取并分派人材机节点到各工程量节点上,供后续上下文使用 + try: + labor_nodes, material_nodes, machine_nodes = get_classified_resource_nodes( + json_file_path, project_name, project_guid + ) + + # 合并三类节点,形成父ID到节点列表的映射 + rcj_all = [] + if labor_nodes: + rcj_all.extend([n for n, _ in labor_nodes]) + if material_nodes: + rcj_all.extend([n for n, _ in material_nodes]) + if machine_nodes: + rcj_all.extend([n for n, _ in machine_nodes]) + + rcj_by_parent: Dict[str, List[Dict[str, Any]]] = {} + + # 注意:get_classified_resource_nodes 返回的是 (node, parent_id) 元组列表 + def add_to_map(pairs): + if not pairs: + return + for node, parent_id in pairs: + if parent_id is None: + continue + key = str(parent_id) + rcj_by_parent.setdefault(key, []).append(node) + + add_to_map(labor_nodes) + add_to_map(material_nodes) + add_to_map(machine_nodes) + + # 将对应的人材机列表写入每个工程量节点 + if project_children: + for node in project_children: + pid_candidates = [ + node.get("GUID"), + node.get("guid"), + node.get("id"), + ] + rcj_list = [] + for pid in pid_candidates: + if pid and str(pid) in rcj_by_parent: + rcj_list = rcj_by_parent.get(str(pid), []) + break + if rcj_list: + node["_rcj_nodes"] = rcj_list + except Exception as e: + print(f"预取人材机节点失败: {e}") + # 在获取 project_children 后添加预处理代码 if project_children and calculation_strategy and hasattr(calculation_strategy, "preprocess_quantity_fee_node"): print(f"对项目节点进行预处理...") @@ -946,7 +1065,11 @@ def calculate_quantity_fees( if engineering_type == "预算工程": # 预算工程 - 使用项目划分节点的取费表 node_results = calculation_strategy.calculate_all_fees( - project_node, {"children": cost_table_children}, json_file_path, engineering_type + project_node, + {"children": cost_table_children}, + json_file_path, + engineering_type, + json_data=json_data, ) # 直接使用节点键作为结果键 @@ -958,7 +1081,11 @@ def calculate_quantity_fees( 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[bill_id]}, json_file_path, engineering_type + project_node, + {"children": cost_tables[bill_id]}, + json_file_path, + engineering_type, + json_data=json_data, ) # 获取清单信息 @@ -987,9 +1114,9 @@ def calculate_quantity_fees( continue # 输出计算结果 - print(f"费用计算结果:") - for fee_name, fee_value in node_results.items(): - print(f" {fee_name}: {fee_value}") + # print(f"费用计算结果:") + # for fee_name, fee_value in node_results.items(): + # print(f" {fee_name}: {fee_value}") # 保存计算结果到JSON文件 with open(output_file, "w", encoding="utf-8") as f: diff --git a/equipment_calculation/resource_fee_calculator.py b/equipment_calculation/resource_fee_calculator.py index d18b05b..66d5e8c 100644 --- a/equipment_calculation/resource_fee_calculator.py +++ b/equipment_calculation/resource_fee_calculator.py @@ -3,6 +3,7 @@ import os import math from typing import Dict, List, Any, Tuple from copy import deepcopy +from memory_profiler import profile from equipment_calculation.item_acquisition import get_quantity_nodes, get_classified_resource_nodes, load_project_data @@ -140,7 +141,10 @@ def process_DXdata(json_data): # 在 resource_fee_calculator.py 文件中修改 calc_rcj_count 函数中的相关代码 def calc_rcj_count( - rcj_nodes: List[Tuple[Dict[str, Any], str]], project_children: List[Dict[str, Any]], json_file_path: str = None + rcj_nodes: List[Tuple[Dict[str, Any], str]], + project_children: List[Dict[str, Any]], + json_file_path: str = None, + json_data: dict | None = None, ) -> List[Dict[str, Any]]: """ 计算人材机节点的数量,考虑父级消耗量 @@ -231,8 +235,8 @@ def calc_rcj_count( result_nodes.append(node_copy) return result_nodes - # 创建工程信息上下文(顶层上下文) - project_context = create_project_contexts(json_file_path=json_file_path) + # 创建工程信息上下文(顶层上下文,优先使用 json_data) + project_context = create_project_contexts(json_file_path=json_file_path, json_data=json_data) # 遍历所有节点,计算数量 for node, parent_id in rcj_nodes: @@ -684,6 +688,7 @@ def calculate_rcj_fees( json_file_path: str, project_name: str, project_guid: str = None, + json_data: dict | None = None, ) -> Dict[str, Any]: """ 计算项目划分节点下所有人材机节点的合价 @@ -698,7 +703,7 @@ def calculate_rcj_fees( """ # 加载项目数据 data, project_data, cost_setting, project_division, target_node = load_project_data( - json_file_path, project_name, project_guid + json_file_path, project_name, project_guid, json_data=json_data ) # 如果没有找到项目划分节点 @@ -717,7 +722,56 @@ def calculate_rcj_fees( material_nodes = [] # 材料节点 (节点, 父级节点) 元组列表 machine_nodes = [] # 机械节点 (节点, 父级节点) 元组列表 - # 递归处理工程量节点,提取人材机节点 + # 工具:类型判断与资源拆分上提 + def _is_resource_type(t): + return t in ("人工", "材料", "机械", "2", "3", "4") + + def _normalize_type(t): + return {"2": "人工", "3": "材料", "4": "机械"}.get(t, t) + + def _flatten_resource_splits(n, inherited_type=None, qty_factor: float = 1.0): + """ + 将可能带 children 的人材机节点递归拆平: + - 若是人材机且有children:不计入父,children 继承本节点类型继续拆分; + - 若是人材机且无children:作为叶子返回; + - 若非人材机:递归其children以发现资源叶子。 + 返回叶子资源节点列表(dict)。 + """ + results = [] + if not isinstance(n, dict): + return results + n_type = _normalize_type(n.get("类型") or n.get("type")) + children = n.get("children") or [] + + if _is_resource_type(n_type): + # 当前节点是人材机 + current_type = _normalize_type(inherited_type or n_type) + # 当前资源节点自身数量,默认1 + try: + self_qty = float((n.get("数量") or "1").strip()) + except Exception: + self_qty = 1.0 + new_factor = qty_factor * self_qty + if children: + for ch in children: + results.extend(_flatten_resource_splits(ch, current_type, new_factor)) + else: + leaf = deepcopy(n) + leaf["类型"] = current_type + # 叶子数量 = 父链乘积 * 自身(若叶子也有数量则已计入 new_factor) + try: + # 如果叶子本身还有数量字段,已包含在 new_factor 中,这里直接覆盖为乘积 + leaf["数量"] = str(new_factor) + except Exception: + leaf["数量"] = str(new_factor) + results.append(leaf) + else: + # 非资源,继续向下查找 + for ch in children: + results.extend(_flatten_resource_splits(ch, inherited_type, qty_factor)) + return results + + # 递归处理工程量节点,提取人材机节点(对children内的人材机执行拆分上提) def process_nodes(nodes): for node in nodes: # 如果是定额节点,处理其材机列表和children @@ -756,22 +810,25 @@ def calculate_rcj_fees( f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 有子节点,数量: {len(node['children'])}" ) for child in node["children"]: - child_type = child.get("类型", child.get("type")) - if child_type in ["人工", "2"]: - labor_nodes.append((child, node)) - print( - f"从children找到人工节点: {child.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}" - ) - elif child_type in ["材料", "3"]: - material_nodes.append((child, node)) - print( - f"从children找到材料节点: {child.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}" - ) - elif child_type in ["机械", "4"]: - machine_nodes.append((child, node)) - print( - f"从children找到机械节点: {child.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}" - ) + # 将children中的资源节点拆分为叶子资源,排除中间父级 + flattened = _flatten_resource_splits(child) + for leaf in flattened: + leaf_type = _normalize_type(leaf.get("类型") or leaf.get("type")) + if leaf_type == "人工": + labor_nodes.append((leaf, node)) + print( + f"从children(拆分后)找到人工节点: {leaf.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}" + ) + elif leaf_type == "材料": + material_nodes.append((leaf, node)) + print( + f"从children(拆分后)找到材料节点: {leaf.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}" + ) + elif leaf_type == "机械": + machine_nodes.append((leaf, node)) + print( + f"从children(拆分后)找到机械节点: {leaf.get('名称', '未命名')}, 父节点: {node.get('项目名称', node.get('name', '未命名'))}" + ) else: print(f"定额节点 '{node.get('项目名称', node.get('name', '未命名'))}' 没有子节点") @@ -820,11 +877,14 @@ def calculate_rcj_fees( # result_nodes.append(node_copy) # return result_nodes - # 创建工程信息上下文(顶层上下文) - project_context = create_project_contexts(json_file_path=json_file_path) + # 创建工程信息上下文(顶层上下文,优先使用 json_data) + project_context = create_project_contexts(json_file_path=json_file_path, json_data=json_data) - with open(json_file_path, "r", encoding="utf-8") as file: - data = json.load(file) + if json_data is not None: + data = json_data + else: + with open(json_file_path, "r", encoding="utf-8") as file: + data = json.load(file) processed_data = process_DXdata(data) @@ -984,6 +1044,7 @@ def calculate_resource_fees( project_name: str, project_guid: str = None, calculation_strategy=None, + json_data: dict | None = None, ) -> str: """ 计算人材机合价 @@ -1004,7 +1065,7 @@ def calculate_resource_fees( calculation_strategy = DefaultCalculationStrategy() # 获取项目划分节点的GUID - _, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid) + _, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid, json_data=json_data) # 如果传入了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 @@ -1055,10 +1116,16 @@ def calculate_resource_fees( # 计算人材机节点的合价,传递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, project_guid) + try: + rcj_results = calculation_strategy.calculate_rcj_fees( + json_file_path, project_name, project_guid, json_data=json_data + ) + except TypeError: + # 向后兼容:旧策略不支持 json_data 形参 + rcj_results = calculation_strategy.calculate_rcj_fees(json_file_path, project_name, project_guid) else: # 如果计算策略没有实现 calculate_rcj_fees 方法,使用原始函数 - rcj_results = calculate_rcj_fees(json_file_path, project_name, project_guid) + rcj_results = calculate_rcj_fees(json_file_path, project_name, project_guid, json_data=json_data) # 检查是否有人材机数据 has_data = False diff --git a/equipment_calculation/计算配置/技改/工程量/预算/bcl/变量计算配置(线路).xml b/equipment_calculation/计算配置/技改/工程量/预算/变量计算配置(线路).xml similarity index 100% rename from equipment_calculation/计算配置/技改/工程量/预算/bcl/变量计算配置(线路).xml rename to equipment_calculation/计算配置/技改/工程量/预算/变量计算配置(线路).xml diff --git a/equipment_calculation/计算配置/技改/工程量/预算/bcl/变量计算配置(配网).xml b/equipment_calculation/计算配置/技改/工程量/预算/变量计算配置(配网).xml similarity index 100% rename from equipment_calculation/计算配置/技改/工程量/预算/bcl/变量计算配置(配网).xml rename to equipment_calculation/计算配置/技改/工程量/预算/变量计算配置(配网).xml diff --git a/extract_errors.py b/extract_errors.py index c9f8a10..6945d6b 100644 --- a/extract_errors.py +++ b/extract_errors.py @@ -1,6 +1,7 @@ import re import codecs + def extract_errors_and_warnings(input_log_path, output_error_path, warning_stats_path="warning_statistics.txt"): """ 从日志文件中提取 WARNING 和 ERROR 及其 Traceback 堆栈信息,保存到新文件 @@ -8,28 +9,28 @@ def extract_errors_and_warnings(input_log_path, output_error_path, warning_stats 同时统计WARNING信息并输出到单独文件 """ # 正则匹配日志行开头(时间戳格式) - log_pattern = re.compile(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})') + log_pattern = re.compile(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})") # 尝试多种编码格式读取文件 - encodings = ['utf-8', 'gbk', 'gb2312', 'ascii'] + encodings = ["utf-8", "gbk", "gb2312", "ascii"] lines = [] - + for encoding in encodings: try: - with open(input_log_path, 'r', encoding=encoding) as f: + with open(input_log_path, "r", encoding=encoding) as f: lines = f.readlines() print(f"✅ 成功使用 {encoding} 编码读取文件") break except UnicodeDecodeError: continue - + # 如果所有编码都失败,则使用二进制模式读取并尝试解码 if not lines: try: - with open(input_log_path, 'rb') as f: + with open(input_log_path, "rb") as f: content = f.read() # 尝试解码,忽略错误 - lines = content.decode('utf-8', errors='ignore').splitlines(True) + lines = content.decode("utf-8", errors="ignore").splitlines(True) print("⚠️ 使用二进制模式读取文件,可能有字符丢失") except Exception as e: print(f"❌ 无法读取文件: {e}") @@ -46,10 +47,10 @@ def extract_errors_and_warnings(input_log_path, output_error_path, warning_stats if is_new_log: # 判断是否为 WARNING 或 ERROR - if ' - WARNING - ' in line or ' - ERROR - ' in line: + if " - WARNING - " in line or " - ERROR - " in line: error_lines.append(line.rstrip()) # 如果是 ERROR,捕获后续的 Traceback 信息 - if ' - ERROR - ' in line: + if " - ERROR - " in line: i += 1 # 继续读取后续行,直到遇到下一个时间戳行或文件结束 while i < len(lines): @@ -65,7 +66,7 @@ def extract_errors_and_warnings(input_log_path, output_error_path, warning_stats error_lines.append(next_line.rstrip()) i += 1 # 如果是DEBUG/INFO行,检查是否包含Traceback - elif ' - DEBUG - ' in line and i + 1 < len(lines) and 'Traceback' in lines[i + 1]: + elif " - DEBUG - " in line and i + 1 < len(lines) and "Traceback" in lines[i + 1]: # 这是一个包含Traceback的DEBUG信息,也提取 error_lines.append(line.rstrip()) i += 1 @@ -92,35 +93,36 @@ def extract_errors_and_warnings(input_log_path, output_error_path, warning_stats i += 1 # 写入输出文件 - with open(output_error_path, 'w', encoding='utf-8') as f: + with open(output_error_path, "w", encoding="utf-8") as f: for err_line in error_lines: - f.write(err_line + '\n') + f.write(err_line + "\n") # 统计WARNING信息 warning_dict = {} for line in error_lines: - if ' - WARNING - ' in line: + if " - WARNING - " in line: # 提取WARNING后的内容作为键 - warning_content = line.split(' - WARNING - ', 1)[1] + warning_content = line.split(" - WARNING - ", 1)[1] if warning_content in warning_dict: warning_dict[warning_content] += 1 else: warning_dict[warning_content] = 1 - + # 写入统计结果到文件 - with open(warning_stats_path, 'w', encoding='utf-8') as f: + with open(warning_stats_path, "w", encoding="utf-8") as f: f.write("WARNING统计结果:\n") f.write(f"共找到 {len(warning_dict)} 种不同的WARNING信息\n\n") for warning_content, count in warning_dict.items(): - f.write(f"{count}次: {warning_content}\n") - + f.write(f"{warning_content}\n") + print(f"✅ 提取完成!共找到 {len(error_lines)} 行错误/警告信息。") print(f"📁 已保存到: {output_error_path}") print(f"📊 WARNING统计已保存到: {warning_stats_path}") + # ============ 使用示例 ============ if __name__ == "__main__": - input_file = "bcl_calculator.log" # 替换为你的日志文件路径 - output_file = "error_report.txt" # 输出的错误报告文件 - warning_stats_file = "warning_statistics.txt" # WARNING统计结果文件 - extract_errors_and_warnings(input_file, output_file, warning_stats_file) \ No newline at end of file + input_file = "bcl_calculator.log" # 替换为你的日志文件路径 + output_file = "error_report.txt" # 输出的错误报告文件 + warning_stats_file = "warning_statistics.txt" # WARNING统计结果文件 + extract_errors_and_warnings(input_file, output_file, warning_stats_file) diff --git a/transform_expense_preview.py b/transform_expense_preview.py index 9a43664..7f9980e 100644 --- a/transform_expense_preview.py +++ b/transform_expense_preview.py @@ -57,6 +57,37 @@ def transform_expense_preview(input_file, output_file): print(f"原始expensePreview中的顶级分类: {list(original_expense_preview.keys())}") print(f"projectDivision中的顶级分类: {list(project_division.keys())}") + # 先清理 projectDivision:递归删除任意带有 "删除": "1" 或 1 的节点 + def _filter_deleted_nodes(obj): + # 若当前对象本身标记了删除,则直接丢弃 + if isinstance(obj, dict): + flag = obj.get("删除") + if flag == "1" or flag == 1: + return None + new_obj = {} + for k, v in obj.items(): + filtered = _filter_deleted_nodes(v) + if filtered is not None: + new_obj[k] = filtered + return new_obj + elif isinstance(obj, list): + new_list = [] + for item in obj: + filtered = _filter_deleted_nodes(item) + if filtered is not None: + new_list.append(filtered) + return new_list + else: + return obj + + cleaned_project_division = _filter_deleted_nodes(project_division) or {} + if cleaned_project_division != project_division: + print("已根据 '删除' 标记清理 projectDivision 中的节点") + project_division = cleaned_project_division + # 回写清理后的结构,确保后续流程与落盘一致 + if "projectData" in data: + data["projectData"]["projectDivision"] = project_division + # 创建新的expensePreview结构 new_expense_preview = {} @@ -478,6 +509,37 @@ def transform_json_types(input_file_path, output_file_path=None): with open(input_file_path, "r", encoding="utf-8") as f: data = json.load(f) + # 在主网流程中,同样先清理 projectDivision:递归删除任意带有 "删除": "1" 或 1 的节点 + def _filter_deleted_nodes(obj): + if isinstance(obj, dict): + flag = obj.get("删除") + if flag == "1" or flag == 1: + return None + new_obj = {} + for k, v in obj.items(): + filtered = _filter_deleted_nodes(v) + if filtered is not None: + new_obj[k] = filtered + return new_obj + elif isinstance(obj, list): + new_list = [] + for item in obj: + filtered = _filter_deleted_nodes(item) + if filtered is not None: + new_list.append(filtered) + return new_list + else: + return obj + + try: + pd = data.get("projectData", {}).get("projectDivision", {}) + cleaned_pd = _filter_deleted_nodes(pd) or {} + if cleaned_pd != pd and "projectData" in data: + data["projectData"]["projectDivision"] = cleaned_pd + print("[主网] 已根据 '删除' 标记清理 projectDivision 中的节点") + except Exception: + pass + # 递归处理函数 def traverse(obj): if isinstance(obj, dict):