更新代码

This commit is contained in:
chentianrui
2025-10-14 16:13:18 +08:00
parent f5f26c5cf8
commit 0a4dedda1c
230 changed files with 7029 additions and 855114 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+207 -7
View File
@@ -1,3 +1,7 @@
"""
统计费用
"""
import os
import shutil
import time
@@ -6,13 +10,13 @@ import traceback
import uuid
import random
import string
import argparse
import csv
# 导入各个步骤需要的函数
from project2json.project_converter import convert_project_to_json
from transform_expense_preview import process_directory
from supplement_kg import costsummary_upwards
from equipment_calculation.main import bcl_calculate
from equipment_calculation.main import bcl_calculate, parse_json_content
from cost_comparison import compare_costs_batch
import tempfile
@@ -136,9 +140,9 @@ def convert_all_steps(files):
# ========= 批处理与命令行入口(文件末尾追加) =========
def create_named_workdirs(output_base_dir: str, base_name: str):
"""在输出根目录下创建 '<工程名>+<GUID>' 的工作目录结构。"""
"""在输出根目录下创建 '<工程名>_<GUID>' 的工作目录结构。"""
guid = uuid.uuid4().hex
root_name = f"{base_name}+{guid}"
root_name = f"{base_name}_{guid}"
root = os.path.join(output_base_dir, root_name)
dirs = {
"root": root,
@@ -219,14 +223,210 @@ def run_batch_with_io(input_dir: str, output_dir: str) -> int:
return total_success
# ========= 新增:嵌套目录遍历与结果抽查/汇总 =========
def _find_leaf_dirs(root_dir: str):
"""找到 root_dir 下的所有最深层级子文件夹(没有进一步子目录的文件夹)。"""
leaf_dirs = []
for current, dirs, _files in os.walk(root_dir):
# 只要该目录没有子目录,则视为叶子目录
if not dirs:
leaf_dirs.append(current)
return leaf_dirs
def _iter_comparison_results_dirs(bcl_results_dir: str):
"""在 bcl_results_dir 下递归查找名为 'comparison_results' 的目录,返回其绝对路径列表。"""
found = []
for current, dirs, _files in os.walk(bcl_results_dir):
for d in dirs:
if d == "comparison_results":
found.append(os.path.join(current, d))
return found
def _parse_diff_value_from_line(line: str) -> float | None:
"""从一行对齐文本中解析“差异”列的值。
文本列宽(在 cost_comparison.save_comparison_to_txt 中定义):
- 项目: 20
- 参考值: 25
- 计算值: 25
- 差异: 25 <-- 我们需要的列
- 原数据项: 30
"""
try:
# 按固定宽度切片
start = 20 + 25 + 25
end = start + 25
cell = line[start:end].strip()
if not cell:
return None
# 去掉可能的对齐空格,再解析为 float
val = float(cell)
return val
except Exception:
return None
def _evaluate_comparison_folder(comp_dir: str, sample_k: int = 10) -> str:
"""随机抽查 comp_dir 下的 TXT 文件,若任意“差异”列存在非 0/-0 值则判定为“有问题”,否则为“正确”。"""
txt_files = [os.path.join(comp_dir, f) for f in os.listdir(comp_dir) if f.lower().endswith(".txt")]
if not txt_files:
# 无文件视为有问题(或按需 "正确"),这里更稳妥地标记为有问题
return "有问题"
random.shuffle(txt_files)
subset = txt_files[: min(sample_k, len(txt_files))]
for path in subset:
try:
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
# 跳过前两行(表头和分隔线)
for line in lines[2:]:
diff_val = _parse_diff_value_from_line(line)
if diff_val is None:
continue
# 允许 -0.0 视为 0
if abs(diff_val) > 0.0:
return "有问题"
except Exception:
return "有问题"
return "正确"
def _append_results_to_csv(records: list[tuple[str, str, str, str]], csv_path: str):
"""将 (文件路径, 软件类别, 项目类型, 计算结果) 记录写入 CSV(追加/创建)。"""
header = ["文件路径", "软件类别", "项目类型", "计算结果"]
file_exists = os.path.exists(csv_path)
with open(csv_path, "a", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(header)
for row in records:
writer.writerow(list(row))
def run_nested_batch_with_io(
input_root: str, output_root: str, sample_k: int = 10, summary_csv_path: str | None = None
) -> int:
"""遍历嵌套目录中每个最深层级的子文件夹,按现有流程处理其中所有工程文件。
并在完成每个工程的第 8 步(费用对比)后,定位 bclresults 下的所有 comparison_results 文件夹,随机抽查 10 个 TXT,
将判定结果汇总到一个 CSV(列:文件路径、工程名称、计算结果)。
"""
input_root = os.path.abspath(input_root)
output_root = os.path.abspath(output_root)
os.makedirs(output_root, exist_ok=True)
# 提前确定并创建汇总 CSV 路径,后续逐条写入,避免中途异常导致整体丢失
# 需求:多次运行不要覆盖历史数据 -> 当未显式指定时,采用固定文件名并使用追加写入
if summary_csv_path is None:
summary_csv_path = os.path.join(output_root, "comparison_validation_summary.csv")
else:
# 若显式指定了路径,确保其父目录存在
csv_dir = os.path.dirname(summary_csv_path)
if csv_dir:
os.makedirs(csv_dir, exist_ok=True)
leaf_dirs = _find_leaf_dirs(input_root)
if not leaf_dirs:
print(f"未找到任何叶子目录: {input_root}")
return 0
print(f"共发现 {len(leaf_dirs)} 个最深层级子文件夹,开始逐一处理……")
total_success = 0
for didx, leaf in enumerate(leaf_dirs, start=1):
# 收集该叶子目录下的所有文件
files = [os.path.join(leaf, f) for f in os.listdir(leaf) if os.path.isfile(os.path.join(leaf, f))]
if not files:
print(f"[{didx}/{len(leaf_dirs)}] 叶子目录无文件,跳过: {leaf}")
continue
print(f"[{didx}/{len(leaf_dirs)}] 处理叶子目录: {leaf}{len(files)} 个文件)")
for fidx, file_path in enumerate(files, start=1):
base_name = os.path.basename(file_path)
stem = os.path.splitext(base_name)[0]
print(f" - ({fidx}/{len(files)}) 处理文件: {base_name}")
try:
# 为该工程创建独立工作区
fdirs = create_named_workdirs(output_root, stem)
upload_dir = fdirs["upload_dir"]
json_dir = fdirs["json_dir"]
merged_dir = fdirs["merged_dir"]
bcl_results_dir = fdirs["bcl_results_dir"]
bcl_dir = fdirs["bcl_dir"]
# 保存上传
save_path = os.path.join(upload_dir, base_name)
shutil.copy(file_path, save_path)
# 转 JSON
success, _file_num = convert_project_to_json(upload_dir, json_dir, bcl_dir)
if not success:
raise RuntimeError("转换为JSON失败")
# 处理 JSON
process_directory(json_dir)
# 费用向上汇总
_ = costsummary_upwards(json_dir, merged_dir)
# BCL 计算
bcl_calculate(merged_dir, bcl_results_dir, bcl_dir_path=bcl_dir)
# 选择一个项目 JSON 进行费用对比
merged_jsons = [
os.path.join(merged_dir, nf) for nf in os.listdir(merged_dir) if nf.lower().endswith(".json")
]
if not merged_jsons:
raise FileNotFoundError("在 merged_dir 中未找到项目 JSON")
project_data_json_path = merged_jsons[0]
# 费用对比
compare_costs_batch(bcl_results_dir, project_data_json_path)
# 从项目 JSON 中解析 软件类别/项目类型
try:
category, project_type, engineering_type = parse_json_content(project_data_json_path)
except Exception:
category, project_type = "未知", "未知"
# 对比完成后:在 bclresults 下寻找 comparison_results 目录,并进行抽查
comp_dirs = _iter_comparison_results_dirs(bcl_results_dir)
if not comp_dirs:
# 若未找到任何 comparison_results,则记录为有问题(立即写入 CSV)
_append_results_to_csv([(bcl_results_dir, category, project_type, "有问题")], summary_csv_path)
else:
for comp_dir in comp_dirs:
parent_dir = os.path.dirname(comp_dir)
result = _evaluate_comparison_folder(comp_dir, sample_k=sample_k)
_append_results_to_csv([(parent_dir, category, project_type, result)], summary_csv_path)
total_success += 1
except Exception as fe:
print(f" ❌ 处理文件 {base_name} 失败: {fe}\n{traceback.format_exc()}")
print(f"\n📄 抽查结果 CSV 路径: {summary_csv_path}")
print(f"\n✅ 嵌套目录批处理完成。成功处理工程数: {total_success}")
return total_success
if __name__ == "__main__":
input_dir = r"data/input" # 请修改为你的实际输入路径
output_dir = r"data/output" # 请修改为你的实际输出路径
input_path = r"E:\文件\LLM_model\RAG\code\Engineering_data_KG-1\KG_generation\data\input\uploads" # 请修改为你的实际输入嵌套目录根
output_path = r"data/input" # 请修改为你的实际输出根目录
summary_csv = None # 可选:指定汇总 CSV 路径;为 None 时自动生成
t0 = time.time()
try:
count = run_batch_with_io(input_dir, output_dir)
count = run_nested_batch_with_io(input_path, output_path, sample_k=10, summary_csv_path=summary_csv)
print(f"\n🎉 ✅ 处理完成,共成功 {count} 个。耗时: {int(time.time() - t0)}")
except Exception as e:
print(f"\n❌ 执行失败: {e}\n{traceback.format_exc()}")
+1 -1
View File
@@ -1,4 +1,4 @@
[neo4j]
uri: bolt://10.1.6.34:7687
uri: bolt://10.1.6.34:7787
user: neo4j
password: password
+1 -1
View File
@@ -97,6 +97,6 @@ def convert_json_to_readable(input_folder, output_folder=None):
if __name__ == "__main__":
input_folder = r"project2json/outputs/json"
input_folder = r"E:\文件\LLM_model\RAG\code\Engineering_data_KG-1\4、模版指标库\test"
convert_json_to_readable(input_folder)
+2 -5
View File
@@ -346,15 +346,12 @@ def compare_costs_batch(calc_results_folder: str, project_data_json_path: str):
# --------------------------
# 测试入口:直接运行本文件
# --------------------------
# --------------------------
# 测试入口:直接运行本文件(简化版)
# --------------------------
def _main():
"""直接运行费用对比,无需命令行或输入"""
# ✅ 在这里直接填写你要测试的路径(可自行修改)
calc_dir = r"data/output/bclresults/电缆检修国网"
proj_json = r"data/output/merged/电缆检修国网.json"
calc_dir = r"data/input/bclresults/国网投标变电全费用(报表数据)1.1.0.46(沙悦)_-_副本"
proj_json = r"data/input/merged/国网投标变电全费用(报表数据)1.1.0.46(沙悦) - 副本.json"
# 检查路径是否存在
if not os.path.exists(calc_dir):
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
+126
View File
@@ -0,0 +1,126 @@
import os
import csv
from typing import Dict, Tuple, List, Optional
# 固定宽度列配置(与 cost_comparison.save_comparison_to_txt 保持一致)
# 项目:20, 参考值:25, 计算值:25, 差异:25, 原数据项:30
COL_PROJECT = (0, 20)
COL_REF = (20, 45)
COL_CALC = (45, 70)
COL_DIFF = (70, 95)
COL_ORIG = (95, 125)
def _slice(line: str, span: Tuple[int, int]) -> str:
return line[span[0] : span[1]].strip() if len(line) >= span[0] else ""
def parse_txt_line(line: str) -> Tuple[Optional[str], Optional[float]]:
"""解析一行对齐文本,提取 '项目''差异'
返回 (项目名, 差异值);若无法解析,返回 (None, None)。
"""
try:
project = _slice(line, COL_PROJECT)
diff_str = _slice(line, COL_DIFF)
if not project:
return None, None
if not diff_str:
return project, None
diff_val = float(diff_str)
return project, diff_val
except Exception:
return None, None
def scan_comparison_results_folder(comp_dir: str) -> Dict[str, Tuple[float, str]]:
"""遍历一个工程的 comparison_results 目录下的所有 txt 文件,
对每个 '项目' 取绝对值最大的 '差异' 值,并记录来源的 txt 文件名。
返回: { 项目名: (差异值(保留正负), 来源txt文件名) }
仅包含差异非0的项目。
"""
max_diff_by_project: Dict[str, Tuple[float, str]] = {}
txt_files = [f for f in os.listdir(comp_dir) if f.lower().endswith(".txt")]
for fname in txt_files:
fpath = os.path.join(comp_dir, fname)
try:
with open(fpath, "r", encoding="utf-8") as f:
lines = f.readlines()
# 跳过前两行(表头和分隔线)
for line in lines[2:]:
project, diff_val = parse_txt_line(line)
if project is None or diff_val is None:
continue
# 过滤 0/-0
if abs(diff_val) == 0.0:
continue
# 更新为绝对值更大的差异
if project not in max_diff_by_project or abs(diff_val) > abs(max_diff_by_project[project][0]):
max_diff_by_project[project] = (diff_val, fname)
except Exception:
# 单个文件解析失败,忽略即可
continue
return max_diff_by_project
def find_all_projects_with_comparison_results(root: str) -> List[Tuple[str, str]]:
"""
在 root 下递归查找所有工程的 comparison_results 目录。
返回列表: [(工程名称, comparison_results绝对路径)]
工程名称采用 bclresults 的父目录名(通常是 <工程名>_<GUID>)。
如果目录结构为 <工程目录>/bclresults/comparison_results,则工程名称取 <工程目录> 名称。
"""
results: List[Tuple[str, str]] = []
for current, dirs, _files in os.walk(root):
if "comparison_results" in dirs:
comp_dir = os.path.join(current, "comparison_results")
# 工程目录通常是 bclresults 的父级目录
# current 可能是 bclresults 目录
if os.path.basename(current) == "bclresults":
project_dir = os.path.dirname(current)
else:
# 兼容其它层级结构
project_dir = os.path.dirname(current)
project_name = os.path.basename(project_dir)
results.append((project_name, comp_dir))
return results
def aggregate_to_csv(root_of_projects: str, output_csv_path: str) -> None:
"""从根目录递归查找所有 comparison_results,提取每个工程各费用项的最大差异,并按顺序写入(无表头、无固定列)。
每行结构:
[工程名称, 费用项1, 差异1, 文件1, 费用项2, 差异2, 文件2, ...]
注意:不写任何表头;不对不同工程对齐列数,按该工程出现的有差异费用项依次写入三元组。
"""
os.makedirs(os.path.dirname(output_csv_path) or ".", exist_ok=True)
projects = find_all_projects_with_comparison_results(root_of_projects)
with open(output_csv_path, "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
for project_name, comp_dir in projects:
diff_map = scan_comparison_results_folder(comp_dir)
row: List[str] = [project_name]
if diff_map:
for fee in sorted(diff_map.keys()):
val, fname = diff_map[fee]
row.extend([fee, str(val), fname])
# 若无差异,也写入仅含工程名称的一行
writer.writerow(row)
def main():
root_of_projects = r"data/output"
output_csv_path = r"data/output/engineering_diffs_summary.csv"
aggregate_to_csv(root_of_projects, output_csv_path)
print(f"✅ 汇总完成,输出 CSV: {output_csv_path}")
if __name__ == "__main__":
main()
+27 -4
View File
@@ -409,7 +409,6 @@ class BCLCalcItemContext(BCLPrevContext):
result = BCLVariant(item.get_value(value_name))
else:
result = BCLVariant("")
elif prefix_name == self._data_name or prefix_name == "curnode":
result = BCLVariant(self._data_source[self._index - 1].get_value(parts[1]))
elif (
@@ -417,6 +416,8 @@ class BCLCalcItemContext(BCLPrevContext):
and prefix_name == self._data_source[self._index - 1].get_parent().get_dataname()
):
result = BCLVariant(self._data_source[self._index - 1].get_parent().get_value(parts[1]))
else:
result = super()._get_variable_value(var_name)
except ValueError:
pass
@@ -1167,12 +1168,22 @@ class BCLCalculator:
self.register_function("getparam", getparam_func)
self.register_function("strFind", strfind_func)
def reset(self):
"""
重置计算器的可变状态,确保跨工程运行时不复用上一次的脚本与表达式。
"""
# logging.info("[BCLCalculator.reset] 清空已加载状态: expressions/loaded_scripts/last_error")
self.expressions.clear()
self.loaded_scripts.clear()
self.last_error = None
def __copy__(self):
# 创建新实例,复制基本属性
new_instance = self.__class__()
new_instance.loaded_scripts = self.loaded_scripts # 直接引用
new_instance.expressions = self.expressions # 直接引用
new_instance.functions = self.functions # 直接引用
# 改为独立拷贝,避免实例之间共享状态导致跨工程串扰
new_instance.loaded_scripts = list(self.loaded_scripts)
new_instance.expressions = dict(self.expressions)
new_instance.functions = dict(self.functions)
return new_instance
def load_scripts_dir(self, dir_path: str) -> bool:
@@ -1206,6 +1217,13 @@ class BCLCalculator:
self.last_error = f"文件'{xml_path}'不存在\n"
return False
# 日志:当前读取的XML路径与文件名,及self的内存地址
try:
_xml_name = os.path.basename(xml_path)
except Exception:
_xml_name = "<unknown>"
# logging.info(f"[load_script] 正在读取XML | 路径: {xml_path} | 文件名: {_xml_name} | self地址: {hex(id(self))}")
# 读取并清理XML文件内容
import chardet
@@ -1232,6 +1250,11 @@ class BCLCalculator:
relative_path = os.path.relpath(xml_path, start=os.getcwd())
self.loaded_scripts.append(relative_path)
# 日志:累计已加载的XML数量
# logging.info(
# f"[load_script] 已记录XML脚本 | 当前累计数量: {len(self.loaded_scripts)} | 最近一次: {relative_path}"
# )
# 验证根节点 - 兼容BclDocument作为根节点或在Document-DataDefs下
if root.tag == "Document":
data_defs = root.find("DataDefs")
+19 -10
View File
@@ -231,16 +231,17 @@ 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():
if hasattr(ration, key) and not key.startswith("_"):
# 不再限制为类上已有的属性,允许把节点上的所有属性同步到对象上
if not key.startswith("_"):
if value is not None:
setattr(ration, key, str(value) if value != "" else "")
ration.乙供材料费不含税 = node.get("材料费")
ration.children = node.get("children")
ration.children = node.get("children") or node.get("材机列表")
ration.专业类型 = node.get("调差类型")
ration.乙供材料费不含税 = node.get("乙供材料费") or node.get("乙供材料费不含税")
return ration
@@ -290,7 +291,6 @@ def create_material_from_node(node: dict[str, any]) -> Material:
material.单重 = node.get("单重")
material.增值税率 = node.get("增值税率")
material.数量 = node.get("数量")
material.损耗率 = node.get("损耗率")
material.规格型号 = node.get("规格型号")
material.线重 = node.get("线重")
material.截面积 = node.get("截面积")
@@ -303,8 +303,8 @@ def create_material_from_node(node: dict[str, any]) -> Material:
material.投标单价 = node.get("投标单价")
material.特征段 = node.get("特征段")
material.颜色标记 = node.get("颜色标记")
material.价不含税 = node.get("单价不含税")
material.价含税 = node.get("单价含税")
material.预算价不含税 = node.get("单价不含税")
material.预算价含税 = node.get("单价含税")
material.结算市场价不含税 = node.get("结算市场价不含税")
material.结算市场价含税 = node.get("结算市场价含税")
material.基准价不含税 = node.get("基准价不含税")
@@ -323,12 +323,13 @@ def create_material_from_node(node: dict[str, any]) -> Material:
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(material, key) and not key.startswith("_"):
# 不再限制为类上已有的属性,允许把节点上的所有属性同步到对象上
if not key.startswith("_"):
if value is not None:
setattr(material, key, str(value) if value != "" else "")
material.预算价不含税 = material.单价不含税
material.children = node.get("children")
material.损耗 = node.get("损耗率")
return material
@@ -387,7 +388,8 @@ def create_equipment_from_node(node: dict[str, any]) -> Equipment:
# 遍历节点的所有属性,确保所有可能的字段都被设置
for key, value in node.items():
if hasattr(equipment, key) and not key.startswith("_"):
# 不再限制为类上已有的属性,允许把节点上的所有属性同步到对象上
if not key.startswith("_"):
if value is not None:
setattr(equipment, key, str(value) if value != "" else "")
equipment.children = node.get("children")
@@ -474,6 +476,13 @@ def init_bcl_calculator(*args, **kwargs):
else:
config_path = default_path
# 加载脚本前,重置全局 calculator,避免跨工程复用上一次加载的状态
try:
calculator.reset()
logging.info("[init_bcl_calculator] 已重置 BCLCalculator 状态")
except Exception as _e:
logging.warning(f"[init_bcl_calculator] 重置 BCLCalculator 失败: {_e}")
# 加载脚本
result = True
use_filtered_files = False
+110 -43
View File
@@ -222,56 +222,114 @@ def process_project_children(children, software_type=None):
resource_nodes = []
# 内部工具:在人材机存在拆分时展开其子节点,删除中间父级人材机节点
def _flatten_resource_splits_in_place(node: dict):
"""
递归+迭代地展开任意深度的人材机拆分:
- 如果某个子节点是人材机(人工/材料/机械/2/3/4)且其自身有children
* 将其children上提为当前层的children
* 被上提的每个子节点的 类型/type 设为与父节点相同;
* 移除该父级人材机节点本身;
- 对非被移除的子节点递归处理;
通过 while 循环保证多层嵌套被完全展开。
递归+迭代地展开任意深度的人材机拆分,依据新规则
- 材料:仅当 "拆分"=1 时展开子项并删除父节点;否则保留父节点、删除 children
- 机械:仅当 "拆分"=0 时保留父节点、删除 children;否则展开子项并删除父节点
- 人工:始终展开(无拆分控制)
- 商品砼=1:最高优先级,保留父节点,删除 children
- 同时处理 node["children"] 和 node["材机列表"]
"""
if not isinstance(node, dict):
return
if "children" not in node or not node["children"]:
# 提前退出:无 children 且无 材机列表
if ("children" not in node or not node["children"]) and ("材机列表" not in node or not node["材机列表"]):
return
def _is_resource_type(t):
return t in ("人工", "材料", "机械", "2", "3", "4")
if t in ("人工", "材料", "机械"):
return True
return str(t) in ("2", "3", "4")
# 中英文类型映射
# 类型映射
str2code = {"人工": "2", "材料": "3", "机械": "4"}
code2str = {v: k for k, v in str2code.items()}
def _get_normalized_type(t):
"""将类型统一转为中文(用于判断材料/机械/人工)"""
if t in ("人工", "材料", "机械"):
return t
if str(t) == "2":
return "人工"
if str(t) == "3":
return "材料"
if str(t) == "4":
return "机械"
return None
def _process_node_list(node_list):
if not node_list:
return node_list, False
changed = False
new_list = []
for ch in node_list:
if isinstance(ch, dict):
t = ch.get("类型") or ch.get("type")
normalized_type = _get_normalized_type(t)
# 只处理人材机类型
if normalized_type and ch.get("children"):
# 规则1:商品砼=1 → 保留父节点,清空 children(最高优先级)
sg_concrete = ch.get("商品砼")
if sg_concrete == "1" or sg_concrete == 1:
ch.pop("children", None)
new_list.append(ch)
changed = True
continue
# 规则2:根据类型和“拆分”字段决定行为
split_flag = ch.get("拆分")
should_expand = False
if normalized_type == "人工":
# 人工:始终展开
should_expand = True
elif normalized_type == "材料":
# 材料:仅当 拆分=1 时展开
should_expand = split_flag == "1" or split_flag == 1
elif normalized_type == "机械":
# 机械:仅当 拆分≠0 时展开(即 拆分=0 时不展开)
should_expand = not (split_flag == "0" or split_flag == 0)
if should_expand:
# 展开:上提 children,丢弃父节点;保持子级原有 type/类型 不变
for gc in ch.get("children", []) or []:
new_list.append(gc)
changed = True
continue
else:
# 不展开:保留父节点,删除 children
ch.pop("children", None)
new_list.append(ch)
changed = True
continue
# 非人材机类型,或无人材机 children:递归处理并保留
_flatten_resource_splits_in_place(ch)
new_list.append(ch)
else:
new_list.append(ch)
return new_list, changed
# 迭代直到稳定
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
if "children" in node and node["children"]:
new_children, c1 = _process_node_list(node["children"])
if c1:
node["children"] = new_children
changed = True
if "材机列表" in node and node["材机列表"]:
new_materials, c2 = _process_node_list(node["材机列表"])
if c2:
node["材机列表"] = new_materials
changed = True
for child in children:
# 先复制两份:一份用于工程量树(quantity_node),一份用于资源提取(resource_node_src)
@@ -428,6 +486,8 @@ def process_project_children(children, software_type=None):
if new_type:
# 不改写原始字段,直接将条目拷贝加入,类型由下游对象构造派生
new_node = deepcopy(it)
# 修正:并入前应用拆分展开逻辑,确保 商品砼=1 的资源不再携带 children
_flatten_resource_splits_in_place(new_node)
quantity_nodes.append(new_node)
try:
nm = (
@@ -467,9 +527,12 @@ def process_project_children(children, software_type=None):
return True
return False
# 主材/设备直接计入(以标准化类型入列,避免后续 BCL 过滤不到)
if current_node_type in ("主材", "设备", "1", "5"):
# 主材/设备/一笔性费用直接计入(以标准化类型入列,避免后续 BCL 过滤不到)
if current_node_type in ("主材", "设备", "一笔性费用", "1", "5"):
to_append = deepcopy(original_quantity_node)
# 修正:对即将入列的节点应用人材机拆分展开逻辑,
# 以生效“商品砼=1:保留父节点并去除其 children”的规则
_flatten_resource_splits_in_place(to_append)
quantity_nodes.append(to_append)
try:
nm = (
@@ -486,16 +549,20 @@ def process_project_children(children, software_type=None):
# 定额按条件计入
elif is_quota_node:
if _children_have_no_children(quantity_node) or _mj_has_hit(quantity_node):
quantity_nodes.append(original_quantity_node)
to_append = deepcopy(original_quantity_node)
# 修正:定额节点入列时也先应用拆分展开逻辑,
# 保证“商品砼=1 的资源子项不含 children”
_flatten_resource_splits_in_place(to_append)
quantity_nodes.append(to_append)
try:
nm = (
original_quantity_node.get("项目名称")
or original_quantity_node.get("清单名称")
or original_quantity_node.get("name")
or original_quantity_node.get("材料名称")
to_append.get("项目名称")
or to_append.get("清单名称")
or to_append.get("name")
or to_append.get("材料名称")
or "<未命名>"
)
tp = original_quantity_node.get("类型") or original_quantity_node.get("type")
tp = to_append.get("类型") or to_append.get("type")
# print(f"[DEBUG] 入列工程量节点(定额): 名称={nm} | 类型={tp}")
except Exception:
pass
+2784 -1183
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -96,7 +96,7 @@ class Material(ProjectQuantity):
def __init__(self):
super().__init__()
self.规格型号 = None # xsd:string
self.损耗 = None # xsd:string
self.损耗 = None # xsd:string
self.供货方 = None # xsd:string
self.集中配送 = None # xsd:string (可选)
self.单重 = None # xsd:string (可选)
@@ -19,6 +19,7 @@ from equipment_calculation.bcl_utils import (
create_node_from_type,
create_material_or_equipment_from_node,
)
from equipment_calculation.item_acquisition import (
get_cost_table_children,
get_quantity_nodes,
@@ -37,7 +38,7 @@ calculated_fees = {}
# bill_quantity_cache = {}
# 主网处理地形系数
# 主网架线处理地形系数
def process_DXdata(json_data):
"""
处理 projectData 中的线路特征段数据,计算每条 bpBillZhXsTable 记录的加权值,
@@ -88,13 +89,13 @@ def process_DXdata(json_data):
) * 0.01
# 保留一位小数,格式化为字符串
calculated_value = f"{total:.1f}"
# calculated_value = f"{total:.8f}"
# 获取 ItemName
item_name = item.get("ItemName", "未知项目")
# 构建结果字典
item_dict = {"特征段": seg_number, item_name: calculated_value}
item_dict = {"特征段": seg_number, item_name: total}
# 添加到结果列表
result_list.append(item_dict)
@@ -106,6 +107,10 @@ def process_DXdata(json_data):
return result_list
# # 技改处理地形系数
# def JG_process_DXdata(json_data):
# 在create_list_from_node函数后添加一个包装函数
def create_list_from_node_with_bill_quantity(node, quantity=None):
"""创建清单对象并设置数量,不使用全局变量"""
@@ -1,23 +0,0 @@
BEGIN:_材机合并机械数量
${
?#{
->@工程信息.工程类型=="变电":sum(source, "机械", ?parent.数量 * 机械.数量 * parent.机械系数 * parent.定额系数);
->@工程信息.工程类型=="送电":sum(source, "机械", ?parent.数量 * 机械.数量 * parent.机械系数 * parent.定额系数 * (1 + _地形机械系数()));
}
}
BEGIN:_材机合并人工数量
${
?#{
->@工程信息.工程类型=="变电":sum(source, "人工", ?parent.数量 * 人工.数量 * parent.人工系数 * parent.定额系数);
->@工程信息.工程类型=="送电":sum(source, "人工", ?parent.数量 * 人工.数量 * parent.人工系数 * parent.定额系数 * (1 + _地形人工系数()));
}
}
BEGIN:_材机合并材料数量
${
?#{
->@工程信息.工程类型=="变电":sum(source, "材料", ?parent.数量 * 材料.数量 * parent.材料系数 * parent.定额系数);
->@工程信息.工程类型=="送电":sum(source, "材料", ?parent.数量 * 材料.数量 * parent.材料系数 * parent.定额系数 * (1 + _地形材料系数()));
}
}

Some files were not shown because too many files have changed in this diff Show More