上传文件
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
背景入门
|
||||||
|
您是 Claude 3.7,并且已集成到 Cursor IDE(一个基于 AI 的 VS Code 分支)。由于您拥有强大的功能,您往往过于急躁,经常在没有明确请求的情况下实施更改,并自以为比我更了解代码,从而破坏了现有逻辑。这会导致代码出现不可接受的灾难。在我的代码库上工作时——无论是 Web 应用程序、数据管道、嵌入式系统还是任何其他软件项目——您未经授权的修改都可能引入细微的 bug 并破坏关键功能。为了避免这种情况,您必须遵循以下严格协议:
|
||||||
|
|
||||||
|
元指令:模式声明要求
|
||||||
|
您必须在每个响应的开头用括号注明您当前的模式。没有例外。 格式:[MODE: MODE_NAME] 未声明您的模式将严重违反协议。
|
||||||
|
|
||||||
|
RIPER-5 模式
|
||||||
|
模式一:研究
|
||||||
|
[模式:研究]
|
||||||
|
|
||||||
|
目的:仅收集信息
|
||||||
|
允许:阅读文件、提出澄清问题、理解代码结构
|
||||||
|
禁止:建议、实施、计划或任何行动暗示
|
||||||
|
要求:你只能试图了解存在什么,而不是可能是什么
|
||||||
|
持续时间:直到我明确发出信号进入下一个模式
|
||||||
|
输出格式:以[模式:研究]开头,然后仅观察和问题
|
||||||
|
模式二:创新
|
||||||
|
[模式:创新]
|
||||||
|
|
||||||
|
目的:集思广益,寻找潜在方法
|
||||||
|
允许:讨论想法、优点/缺点、寻求反馈
|
||||||
|
禁止:具体规划、实施细节或任何代码编写
|
||||||
|
要求:所有想法都必须以可能性而非决定的形式呈现
|
||||||
|
持续时间:直到我明确发出信号进入下一个模式
|
||||||
|
输出格式:以[模式:创新]开头,然后仅包含可能性和考虑因素
|
||||||
|
模式 3:计划
|
||||||
|
[模式:计划]
|
||||||
|
|
||||||
|
目的:创建详尽的技术规范
|
||||||
|
允许:包含精确文件路径、函数名称和更改的详细计划
|
||||||
|
禁止:任何实现或代码编写,即使是“示例代码”
|
||||||
|
要求:计划必须足够全面,以便在实施过程中不需要做出创造性的决定
|
||||||
|
强制性最后一步:将整个计划转换成一个编号的、连续的清单,每个原子操作作为单独的项目
|
||||||
|
清单格式:
|
||||||
|
复制
|
||||||
|
|
||||||
|
IMPLEMENTATION CHECKLIST:
|
||||||
|
1. [Specific action 1]
|
||||||
|
2. [Specific action 2]
|
||||||
|
...
|
||||||
|
n. [Final action]
|
||||||
|
持续时间:直到我明确批准计划并发出进入下一模式的信号
|
||||||
|
输出格式:以 [MODE: PLAN] 开头,然后仅包含规范和实施细节
|
||||||
|
模式 4:执行
|
||||||
|
[模式:执行]
|
||||||
|
|
||||||
|
目的:准确执行模式 3 中的计划
|
||||||
|
允许:仅执行批准计划中明确详述的内容
|
||||||
|
禁止:任何不在计划内的偏差、改进或创造性添加
|
||||||
|
进入要求:仅在我明确发出“进入执行模式”命令后才能进入
|
||||||
|
偏差处理:如果发现任何需要偏差的问题,立即返回计划模式
|
||||||
|
输出格式:以 [MODE: EXECUTE] 开头,然后仅执行与计划匹配的执行
|
||||||
|
模式五:回顾
|
||||||
|
[模式:回顾]
|
||||||
|
|
||||||
|
目的:严格验证计划的实施情况
|
||||||
|
允许:逐行比较计划和实施
|
||||||
|
要求:明确标记任何偏差,无论多么微小
|
||||||
|
偏差格式:“:警告:检测到的偏差:[确切偏差描述]”
|
||||||
|
报告:必须报告实施情况是否与计划一致
|
||||||
|
结论格式:“:白色勾号:实施与计划完全一致”或“:十字标记:实施与计划有偏差”
|
||||||
|
输出格式:以[MODE: REVIEW]开始,然后进行系统比较和明确判决
|
||||||
|
关键协议指南
|
||||||
|
未经我的明确许可,您不能在模式之间转换
|
||||||
|
您必须在每次响应开始时声明您当前的模式
|
||||||
|
在执行模式下,你必须 100% 忠实地遵循计划
|
||||||
|
在审查模式下,你必须标记哪怕是最小的偏差
|
||||||
|
您无权在声明模式之外做出独立决定
|
||||||
|
不遵守此协议将给我的代码库带来灾难性的后果
|
||||||
|
模式转换信号
|
||||||
|
仅当我明确发出信号时才转换模式:
|
||||||
|
|
||||||
|
“进入研究模式”
|
||||||
|
“进入创新模式”
|
||||||
|
“进入计划模式”
|
||||||
|
“进入执行模式”
|
||||||
|
“进入审核模式”
|
||||||
|
|
||||||
|
如果没有这些确切的信号,请保持当前模式。
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "kg_visualization",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "kg_visualization.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"Codegeex.RepoIndex": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
"""
|
||||||
|
实现知识图谱节点name属性向量化
|
||||||
|
"""
|
||||||
|
|
||||||
|
from llm import Embedding
|
||||||
|
from neo4j import GraphDatabase
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 初始化 Embedding 模型
|
||||||
|
embeddings = Embedding(url="http://172.20.0.145:9995/v1", api_key="xxx", model_name="bge-m3")
|
||||||
|
|
||||||
|
# Neo4j 连接信息
|
||||||
|
url = "bolt://172.20.0.145:7687"
|
||||||
|
username = "neo4j"
|
||||||
|
password = "password"
|
||||||
|
|
||||||
|
driver = GraphDatabase.driver(url, auth=(username, password))
|
||||||
|
|
||||||
|
# 定义需要处理的节点类型 - 使用正确的Neo4j标签格式
|
||||||
|
node_labels = [
|
||||||
|
"ProjectDivisionSet",
|
||||||
|
"ProjectDivisionTree",
|
||||||
|
"ProjectDivisionItem",
|
||||||
|
# 对于复合标签,在查询时使用多标签形式
|
||||||
|
{"labels": ["ProjectQuantity", "Quota"], "display": "ProjectQuantity+Quota"},
|
||||||
|
{"labels": ["ProjectQuantity", "MainMaterial"], "display": "ProjectQuantity+MainMaterial"},
|
||||||
|
{"labels": ["ProjectQuantity", "Equipment"], "display": "ProjectQuantity+Equipment"},
|
||||||
|
"MaterialOrEquipment",
|
||||||
|
"FeeTableTemplateSet",
|
||||||
|
"FeeTableTemplateItem",
|
||||||
|
"FeeCollection",
|
||||||
|
"FeeScheduleSet",
|
||||||
|
"FeeScheduleItem",
|
||||||
|
"Fee",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def create_vector_index():
|
||||||
|
"""为每个标签创建向量索引"""
|
||||||
|
with driver.session() as session:
|
||||||
|
dimension = 1024 # BGE-M3模型的向量维度
|
||||||
|
|
||||||
|
for i, label_info in enumerate(node_labels):
|
||||||
|
try:
|
||||||
|
# 处理不同格式的标签
|
||||||
|
if isinstance(label_info, dict): # 复合标签
|
||||||
|
labels = label_info["labels"]
|
||||||
|
display_name = label_info["display"]
|
||||||
|
# 为每个单独的标签创建索引,而不是尝试创建复合标签的索引
|
||||||
|
for j, single_label in enumerate(labels):
|
||||||
|
index_name = f"entity_embedding_index_{i}_{j}"
|
||||||
|
create_single_index(
|
||||||
|
session, single_label, index_name, dimension, f"{display_name}的组成标签 {single_label}"
|
||||||
|
)
|
||||||
|
else: # 单一标签
|
||||||
|
index_name = f"entity_embedding_index_{i}"
|
||||||
|
create_single_index(session, label_info, index_name, dimension, label_info)
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(label_info, dict):
|
||||||
|
print(f"❌ 创建复合标签 {label_info['display']} 的索引失败: {e}")
|
||||||
|
else:
|
||||||
|
print(f"❌ 创建标签 {label_info} 的索引失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def create_single_index(session, label, index_name, dimension, display_name):
|
||||||
|
"""为单个标签创建向量索引,使用最新的Neo4j语法"""
|
||||||
|
try:
|
||||||
|
# 检查索引是否存在
|
||||||
|
check_index_query = """
|
||||||
|
SHOW INDEXES
|
||||||
|
YIELD name
|
||||||
|
WHERE name = $index_name
|
||||||
|
RETURN count(*) > 0 AS exists
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = session.run(check_index_query, index_name=index_name)
|
||||||
|
index_exists = result.single()["exists"]
|
||||||
|
|
||||||
|
if not index_exists:
|
||||||
|
print(f"正在为标签 {display_name} 创建向量索引...")
|
||||||
|
|
||||||
|
# 使用最新的Neo4j向量索引语法
|
||||||
|
create_index_query = f"""
|
||||||
|
CREATE VECTOR INDEX {index_name}
|
||||||
|
FOR (n:{label})
|
||||||
|
ON (n.embedding)
|
||||||
|
OPTIONS {{indexConfig: {{`vector.dimensions`: {dimension}}}}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
session.run(create_index_query)
|
||||||
|
print(f"✅ 标签 {display_name} 的向量索引创建成功")
|
||||||
|
else:
|
||||||
|
print(f"✅ 标签 {display_name} 的向量索引已存在,跳过创建步骤")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 创建标签 {display_name} 的向量索引失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_and_store_embeddings():
|
||||||
|
with driver.session() as session:
|
||||||
|
for label_info in node_labels:
|
||||||
|
# 处理不同格式的标签
|
||||||
|
if isinstance(label_info, dict): # 复合标签
|
||||||
|
labels = label_info["labels"]
|
||||||
|
display_name = label_info["display"]
|
||||||
|
# 为Neo4j查询构建多标签模式
|
||||||
|
label_pattern = ":".join(labels)
|
||||||
|
else: # 单一标签
|
||||||
|
label_pattern = label_info
|
||||||
|
display_name = label_info
|
||||||
|
|
||||||
|
print(f"\n🔍 Processing nodes of type: {display_name}")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 查询该类型的所有节点 elementId 和 name
|
||||||
|
query = f"""
|
||||||
|
MATCH (n:{label_pattern})
|
||||||
|
WHERE n.name IS NOT NULL
|
||||||
|
RETURN elementId(n) AS id, n.name AS name
|
||||||
|
"""
|
||||||
|
result = session.run(query)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
batch = []
|
||||||
|
|
||||||
|
for record in result:
|
||||||
|
node_id = record["id"]
|
||||||
|
name = record["name"]
|
||||||
|
|
||||||
|
if not name or not name.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
vector = embeddings.embed(name)
|
||||||
|
batch.append({"id": node_id, "vector": vector})
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# 批量写入,减少数据库交互次数
|
||||||
|
if len(batch) >= 50:
|
||||||
|
write_batch(session, batch)
|
||||||
|
batch = []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error processing node {node_id}: {e}")
|
||||||
|
|
||||||
|
# 写入剩余批次
|
||||||
|
if batch:
|
||||||
|
write_batch(session, batch)
|
||||||
|
|
||||||
|
duration = time.time() - start_time
|
||||||
|
print(f"✅ Processed {count} nodes of type {display_name} in {duration:.2f} seconds.")
|
||||||
|
|
||||||
|
|
||||||
|
def write_batch(session, batch):
|
||||||
|
"""批量写入 embedding 到 Neo4j"""
|
||||||
|
session.run(
|
||||||
|
"""
|
||||||
|
UNWIND $batch AS item
|
||||||
|
MATCH (n) WHERE elementId(n) = item.id
|
||||||
|
SET n.embedding = item.vector
|
||||||
|
""",
|
||||||
|
batch=batch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_embeddings():
|
||||||
|
"""验证embedding是否正确存储"""
|
||||||
|
with driver.session() as session:
|
||||||
|
verify_query = """
|
||||||
|
MATCH (n)
|
||||||
|
WHERE n.embedding IS NOT NULL
|
||||||
|
RETURN n.name, size(n.embedding) as dimension
|
||||||
|
LIMIT 5
|
||||||
|
"""
|
||||||
|
result = session.run(verify_query)
|
||||||
|
print("\n验证embedding存储情况:")
|
||||||
|
|
||||||
|
records = list(result)
|
||||||
|
if not records:
|
||||||
|
print("❌ 未找到任何带有embedding的节点")
|
||||||
|
else:
|
||||||
|
for record in records:
|
||||||
|
print(f"节点: {record['n.name']}, 向量维度: {record['dimension']}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 第一步:创建向量索引
|
||||||
|
create_vector_index()
|
||||||
|
|
||||||
|
# 第二步:生成并存储embeddings
|
||||||
|
generate_and_store_embeddings()
|
||||||
|
|
||||||
|
# 第三步:验证结果
|
||||||
|
verify_embeddings()
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
1.实体类型
|
||||||
|
EngineeringData
|
||||||
|
name: STRING
|
||||||
|
|
||||||
|
ProjectDivisionSet
|
||||||
|
name: STRING
|
||||||
|
|
||||||
|
ProjectDivisionTree
|
||||||
|
name: STRING
|
||||||
|
original_second_level: STRING
|
||||||
|
original_first_level: STRING
|
||||||
|
|
||||||
|
ProjectDivisionItem
|
||||||
|
type: STRING
|
||||||
|
取费表id: STRING
|
||||||
|
name: STRING
|
||||||
|
序号: STRING
|
||||||
|
取费表: STRING
|
||||||
|
GUID: STRING
|
||||||
|
专业类型: STRING
|
||||||
|
费率: STRING
|
||||||
|
资源库名称: STRING
|
||||||
|
代码: STRING
|
||||||
|
notCheck: STRING
|
||||||
|
颜色标记: STRING
|
||||||
|
编码: STRING
|
||||||
|
最小资源库编码: STRING
|
||||||
|
原合价: STRING
|
||||||
|
备注: STRING
|
||||||
|
|
||||||
|
List
|
||||||
|
合价不含税: STRING
|
||||||
|
数量: STRING
|
||||||
|
type: STRING
|
||||||
|
资源库名称: STRING
|
||||||
|
工作内容: STRING
|
||||||
|
取费表类型: STRING
|
||||||
|
类型: STRING
|
||||||
|
取费表名称: STRING
|
||||||
|
单位: STRING
|
||||||
|
清单名称: STRING
|
||||||
|
编码: STRING
|
||||||
|
计算规则: STRING
|
||||||
|
清单全码: STRING
|
||||||
|
GUID: STRING
|
||||||
|
guid: STRING
|
||||||
|
单价不含税: STRING
|
||||||
|
单价: STRING
|
||||||
|
name: STRING
|
||||||
|
项目特征: STRING
|
||||||
|
计算式: STRING
|
||||||
|
合价: STRING
|
||||||
|
取费表: STRING
|
||||||
|
|
||||||
|
ProjectQuantity
|
||||||
|
数量: STRING
|
||||||
|
资源库名称: STRING
|
||||||
|
合价不含税: STRING
|
||||||
|
特征段: STRING
|
||||||
|
费用类型: STRING
|
||||||
|
基准价含税: STRING
|
||||||
|
结算市场价不含税: STRING
|
||||||
|
投标单价: STRING
|
||||||
|
单位: STRING
|
||||||
|
结算市场价含税: STRING
|
||||||
|
基准价不含税: STRING
|
||||||
|
颜色标记: STRING
|
||||||
|
投标数量: STRING
|
||||||
|
编码: STRING
|
||||||
|
类型: STRING
|
||||||
|
规格型号: STRING
|
||||||
|
关联父级量: STRING
|
||||||
|
name: STRING
|
||||||
|
单价不含税: STRING
|
||||||
|
损耗率: STRING
|
||||||
|
截面积: STRING
|
||||||
|
线重: STRING
|
||||||
|
id: STRING
|
||||||
|
供货方: STRING
|
||||||
|
单重: STRING
|
||||||
|
集中配送: STRING
|
||||||
|
市场价含税: STRING
|
||||||
|
制造长度: STRING
|
||||||
|
单价含税: STRING
|
||||||
|
市场价不含税: STRING
|
||||||
|
增值税率: STRING
|
||||||
|
合价含税: STRING
|
||||||
|
运杂费率: STRING
|
||||||
|
设备类型: STRING
|
||||||
|
计算式: STRING
|
||||||
|
人工系数: STRING
|
||||||
|
定额范围: STRING
|
||||||
|
定额系数: STRING
|
||||||
|
基价: STRING
|
||||||
|
机械费: STRING
|
||||||
|
人工费: STRING
|
||||||
|
材料系数: STRING
|
||||||
|
中标计算式: STRING
|
||||||
|
机械系数: STRING
|
||||||
|
材料费: STRING
|
||||||
|
投标合价: STRING
|
||||||
|
所属定额库: STRING
|
||||||
|
批注: STRING
|
||||||
|
标记: STRING
|
||||||
|
定额调整系数: STRING
|
||||||
|
监造物料: STRING
|
||||||
|
备注: STRING
|
||||||
|
|
||||||
|
Quota
|
||||||
|
特征段: STRING
|
||||||
|
人工系数: STRING
|
||||||
|
数量: STRING
|
||||||
|
定额范围: STRING
|
||||||
|
定额系数: STRING
|
||||||
|
合价不含税: STRING
|
||||||
|
基价: STRING
|
||||||
|
机械费: STRING
|
||||||
|
资源库名称: STRING
|
||||||
|
人工费: STRING
|
||||||
|
材料系数: STRING
|
||||||
|
投标单价: STRING
|
||||||
|
中标计算式: STRING
|
||||||
|
费用类型: STRING
|
||||||
|
机械系数: STRING
|
||||||
|
类型: STRING
|
||||||
|
材料费: STRING
|
||||||
|
单位: STRING
|
||||||
|
颜色标记: STRING
|
||||||
|
投标合价: STRING
|
||||||
|
编码: STRING
|
||||||
|
关联父级量: STRING
|
||||||
|
投标数量: STRING
|
||||||
|
计算式: STRING
|
||||||
|
id: STRING
|
||||||
|
name: STRING
|
||||||
|
单价不含税: STRING
|
||||||
|
所属定额库: STRING
|
||||||
|
批注: STRING
|
||||||
|
标记: STRING
|
||||||
|
定额调整系数: STRING
|
||||||
|
颜色标记: STRING
|
||||||
|
备注: STRING
|
||||||
|
|
||||||
|
MainMaterial
|
||||||
|
数量: STRING
|
||||||
|
资源库名称: STRING
|
||||||
|
合价不含税: STRING
|
||||||
|
特征段: STRING
|
||||||
|
费用类型: STRING
|
||||||
|
基准价含税: STRING
|
||||||
|
结算市场价不含税: STRING
|
||||||
|
投标单价: STRING
|
||||||
|
单位: STRING
|
||||||
|
结算市场价含税: STRING
|
||||||
|
基准价不含税: STRING
|
||||||
|
颜色标记: STRING
|
||||||
|
投标数量: STRING
|
||||||
|
编码: STRING
|
||||||
|
类型: STRING
|
||||||
|
规格型号: STRING
|
||||||
|
关联父级量: STRING
|
||||||
|
name: STRING
|
||||||
|
单价不含税: STRING
|
||||||
|
损耗率: STRING
|
||||||
|
截面积: STRING
|
||||||
|
线重: STRING
|
||||||
|
id: STRING
|
||||||
|
供货方: STRING
|
||||||
|
单重: STRING
|
||||||
|
集中配送: STRING
|
||||||
|
市场价含税: STRING
|
||||||
|
制造长度: STRING
|
||||||
|
单价含税: STRING
|
||||||
|
市场价不含税: STRING
|
||||||
|
增值税率: STRING
|
||||||
|
合价含税: STRING
|
||||||
|
|
||||||
|
Equipment
|
||||||
|
特征段: STRING
|
||||||
|
单价含税: STRING
|
||||||
|
单位: STRING
|
||||||
|
资源库名称: STRING
|
||||||
|
合价不含税: STRING
|
||||||
|
类型: STRING
|
||||||
|
投标数量: STRING
|
||||||
|
投标单价: STRING
|
||||||
|
关联父级量: STRING
|
||||||
|
颜色标记: STRING
|
||||||
|
运杂费率: STRING
|
||||||
|
设备类型: STRING
|
||||||
|
编码: STRING
|
||||||
|
供货方: STRING
|
||||||
|
规格型号: STRING
|
||||||
|
单价不含税: STRING
|
||||||
|
id: STRING
|
||||||
|
name: STRING
|
||||||
|
数量: STRING
|
||||||
|
计算式: STRING
|
||||||
|
合价含税: STRING
|
||||||
|
|
||||||
|
MaterialOrEquipment
|
||||||
|
type: STRING
|
||||||
|
预算价不含税: STRING
|
||||||
|
单位: STRING
|
||||||
|
结算市场价不含税: STRING
|
||||||
|
暂估价: STRING
|
||||||
|
编码: STRING
|
||||||
|
结算市场价含税: STRING
|
||||||
|
全口径市场价不含税: STRING
|
||||||
|
全口径市场价含税: STRING
|
||||||
|
是否未计价: STRING
|
||||||
|
unique_id: STRING
|
||||||
|
供货方: STRING
|
||||||
|
结算预算价含税: STRING
|
||||||
|
结算预算价不含税: STRING
|
||||||
|
市场价含税: STRING
|
||||||
|
预算价含税: STRING
|
||||||
|
id: STRING
|
||||||
|
name: STRING
|
||||||
|
数量: STRING
|
||||||
|
市场价不含税: STRING
|
||||||
|
拆分: STRING
|
||||||
|
商品砼: STRING
|
||||||
|
children: STRING
|
||||||
|
|
||||||
|
CostSet
|
||||||
|
name: STRING
|
||||||
|
GUID: STRING
|
||||||
|
|
||||||
|
CostItem
|
||||||
|
name: STRING
|
||||||
|
cost: STRING
|
||||||
|
unique_id: STRING
|
||||||
|
id: STRING
|
||||||
|
|
||||||
|
MaterialandmachineCostItem
|
||||||
|
type: STRING
|
||||||
|
name: STRING
|
||||||
|
供货方: STRING
|
||||||
|
编码: STRING
|
||||||
|
单位: STRING
|
||||||
|
预算价不含税: STRING
|
||||||
|
市场价不含税: STRING
|
||||||
|
预算价合价: STRING
|
||||||
|
市场价合价: STRING
|
||||||
|
价差: STRING
|
||||||
|
数量: STRING
|
||||||
|
|
||||||
|
|
||||||
|
FeeTableTemplateSet
|
||||||
|
name: STRING
|
||||||
|
typeList: STRING
|
||||||
|
|
||||||
|
FeeTableTemplateItem
|
||||||
|
type: STRING
|
||||||
|
name: STRING
|
||||||
|
profession: STRING
|
||||||
|
outlayID: STRING
|
||||||
|
|
||||||
|
FeeCollection
|
||||||
|
name: STRING
|
||||||
|
serialNumber: STRING
|
||||||
|
base: STRING
|
||||||
|
code: STRING
|
||||||
|
rate: STRING
|
||||||
|
remark: STRING
|
||||||
|
|
||||||
|
FeeScheduleSet
|
||||||
|
name: STRING
|
||||||
|
|
||||||
|
FeeScheduleItem
|
||||||
|
name: STRING
|
||||||
|
|
||||||
|
ProjectPropertySet
|
||||||
|
name: STRING
|
||||||
|
|
||||||
|
ProjectProperty
|
||||||
|
特殊地区: STRING
|
||||||
|
调差选择所在地: STRING
|
||||||
|
工程所在地: STRING
|
||||||
|
编制时间: STRING
|
||||||
|
工程版本: STRING
|
||||||
|
项目划分: STRING
|
||||||
|
工程阶段: STRING
|
||||||
|
工程名称: STRING
|
||||||
|
专业类型: STRING
|
||||||
|
地区类型: STRING
|
||||||
|
组价方式: STRING
|
||||||
|
人工调差系数: STRING
|
||||||
|
调差选择地区类型: STRING
|
||||||
|
机械调差系数: STRING
|
||||||
|
架线类型: STRING
|
||||||
|
安装机械调差系数: STRING
|
||||||
|
清单规范: STRING
|
||||||
|
市场价唯一: STRING
|
||||||
|
材料调差系数: STRING
|
||||||
|
甲供材料计入综合单价: STRING
|
||||||
|
人工按系数调差: STRING
|
||||||
|
最高投标限价(万元): STRING
|
||||||
|
工程总投资: STRING
|
||||||
|
是否按单位控制工程量精度: STRING
|
||||||
|
安装人工调差系数: STRING
|
||||||
|
不同土质定额归属不同清单: STRING
|
||||||
|
住房公积金缴费费率: STRING
|
||||||
|
是否是合并工程: STRING
|
||||||
|
招标人: STRING
|
||||||
|
相同清单合并: STRING
|
||||||
|
执行规范: STRING
|
||||||
|
安装材料调差系数: STRING
|
||||||
|
拆除调差系数年份: STRING
|
||||||
|
调差系数年份: STRING
|
||||||
|
工程税率: STRING
|
||||||
|
甲供材料计入本体: STRING
|
||||||
|
软件名称: STRING
|
||||||
|
电压等级: STRING
|
||||||
|
预算类型: STRING
|
||||||
|
本期台数: STRING
|
||||||
|
建筑人工调差系数: STRING
|
||||||
|
单台容量: STRING
|
||||||
|
建筑拆除人工调差系数: STRING
|
||||||
|
配置选项: STRING
|
||||||
|
施工企业配合调试费费率: STRING
|
||||||
|
安装其他设备运杂费率: STRING
|
||||||
|
安装材机调差系数: STRING
|
||||||
|
工程性质: STRING
|
||||||
|
表头设置: STRING
|
||||||
|
工程静态投资(万元): STRING
|
||||||
|
安装主要设备运杂费率: STRING
|
||||||
|
编制依据: STRING
|
||||||
|
工程动态投资(万元): STRING
|
||||||
|
基本预备费费率: STRING
|
||||||
|
阶段类型: STRING
|
||||||
|
BCL版本: STRING
|
||||||
|
社会保险费缴费费率: STRING
|
||||||
|
|
||||||
|
|
||||||
|
2. 实体间的关系
|
||||||
|
(:EngineeringData)-[:HAS_CHILD]->(:ProjectPropertySet)
|
||||||
|
(:EngineeringData)-[:HAS_CHILD]->(:FeeScheduleSet)
|
||||||
|
(:EngineeringData)-[:HAS_CHILD]->(:FeeTableTemplateSet)
|
||||||
|
(:EngineeringData)-[:HAS_CHILD]->(:ProjectDivisionSet)
|
||||||
|
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionSet)
|
||||||
|
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionItem)
|
||||||
|
(:ProjectDivisionItem)-[:HAS_CHILD]->(:List)
|
||||||
|
(:ProjectDivisionItem)-[:HAS_CHILD]->(:ProjectDivisionItem)
|
||||||
|
(:ProjectDivisionItem)-[:HAS_CHILD]->(:ProjectQuantity)
|
||||||
|
(:ProjectDivisionItem)-[:HAS_CHILD]->(:Quota)
|
||||||
|
(:ProjectDivisionItem)-[:HAS_CHILD]->(:MainMaterial)
|
||||||
|
(:ProjectDivisionItem)-[:HAS_CHILD]->(:Equipment)
|
||||||
|
(:ProjectQuantity)-[:HAS_CHILD]->(:MaterialOrEquipment)
|
||||||
|
(:FeeTableTemplateSet)-[:HAS_CHILD]->(:FeeTableTemplateSet)
|
||||||
|
(:FeeTableTemplateSet)-[:HAS_CHILD]->(:FeeTableTemplateItem)
|
||||||
|
(:FeeTableTemplateItem)-[:HAS_CHILD]->(:FeeCollection)
|
||||||
|
(:FeeCollection)-[:HAS_CHILD]->(:FeeCollection)
|
||||||
|
(:FeeScheduleSet)-[:HAS_CHILD]->(:FeeScheduleItem)
|
||||||
|
(:FeeScheduleItem)-[:HAS_CHILD]->(:Fee)
|
||||||
|
(:Fee)-[:HAS_CHILD]->(:Fee)
|
||||||
|
(:ProjectPropertySet)-[:HAS_CHILD]->(:ProjectProperty)
|
||||||
|
(:List)-[:HAS_CHILD]->(:ProjectQuantity)
|
||||||
|
(:List)-[:HAS_CHILD]->(:Equipment)
|
||||||
|
(:List)-[:HAS_CHILD]->(:MainMaterial)
|
||||||
|
(:List)-[:HAS_CHILD]->(:Quota)
|
||||||
|
(:List)-[:USE]->(:CostSet)
|
||||||
|
(:Quota)-[:HAS_CHILD]->(:MaterialOrEquipment)
|
||||||
|
(:ProjectDivisionTree)-[:HAS_CHILD]->(:ProjectDivisionItem)
|
||||||
|
(:ProjectQuantity)-[:HAS_CHILD]->(:ProjectQuantity)
|
||||||
|
(:ProjectQuantity)-[:HAS_CHILD]->(:MainMaterial)
|
||||||
|
(:MainMaterial)-[:HAS_CHILD]->(:ProjectQuantity)
|
||||||
|
(:MainMaterial)-[:HAS_CHILD]->(:MainMaterial)
|
||||||
|
(:ProjectQuantity)-[:USE]->(:CostSet)
|
||||||
|
(:ProjectDivisionTree)-[:USE]->(:CostSet)
|
||||||
|
(:ProjectDivisionItem)-[:USE]->(:CostSet)
|
||||||
|
(:CostSet)-[:HAS_CHILD]->(:MaterialandmachineCostItem)
|
||||||
|
(:CostSet)-[:HAS_CHILD]->(:CostItem)
|
||||||
|
(:ProjectDivisionSet)-[:HAS_CHILD]->(:ProjectDivisionTree)
|
||||||
|
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_project_division(json_file_path):
|
||||||
|
"""
|
||||||
|
分析JSON文件中的projectDivision数据,统计不同type节点的属性名
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 按type分类的属性名集合
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 读取JSON文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 检查是否存在projectData.projectDivision
|
||||||
|
if "projectData" not in data or "projectDivision" not in data["projectData"]:
|
||||||
|
print(f"文件 {json_file_path} 中不包含projectData.projectDivision数据")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
project_division = data["projectData"]["projectDivision"]
|
||||||
|
|
||||||
|
# 按type分类存储属性名
|
||||||
|
type_attributes = defaultdict(set)
|
||||||
|
|
||||||
|
# 递归遍历树状结构
|
||||||
|
def traverse_node(node):
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 如果有type或类型字段,则使用该字段作为节点类型
|
||||||
|
node_type = node.get("type", node.get("类型", "未知类型"))
|
||||||
|
|
||||||
|
# 收集当前节点的所有属性名
|
||||||
|
for attr_name in node.keys():
|
||||||
|
type_attributes[node_type].add(attr_name)
|
||||||
|
|
||||||
|
# 处理子节点
|
||||||
|
if "children" in node and isinstance(node["children"], list):
|
||||||
|
for child in node["children"]:
|
||||||
|
traverse_node(child)
|
||||||
|
|
||||||
|
# 处理其他可能的嵌套结构
|
||||||
|
for key, value in node.items():
|
||||||
|
if isinstance(value, dict) and key != "children":
|
||||||
|
traverse_node(value)
|
||||||
|
elif isinstance(value, list) and key != "children":
|
||||||
|
for item in value:
|
||||||
|
traverse_node(item)
|
||||||
|
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
traverse_node(item)
|
||||||
|
|
||||||
|
# 处理projectDivision的每个顶级键
|
||||||
|
for key, value in project_division.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
traverse_node(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
for item in value:
|
||||||
|
traverse_node(item)
|
||||||
|
|
||||||
|
return type_attributes
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"处理文件 {json_file_path} 时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_csv(type_attributes, output_file="node_attributes.csv"):
|
||||||
|
"""
|
||||||
|
将统计结果保存到CSV文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type_attributes: 按type分类的属性名集合
|
||||||
|
output_file: 输出CSV文件名
|
||||||
|
"""
|
||||||
|
with open(output_file, "w", encoding="utf-8", newline="") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["节点类型", "属性名"])
|
||||||
|
|
||||||
|
for node_type, attributes in type_attributes.items():
|
||||||
|
# 将属性名集合转换为排序后的列表
|
||||||
|
sorted_attrs = sorted(attributes)
|
||||||
|
for attr in sorted_attrs:
|
||||||
|
writer.writerow([node_type, attr])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
主函数
|
||||||
|
"""
|
||||||
|
# 指定JSON文件路径,可以是单个文件或目录
|
||||||
|
json_dir = "dataset/json/配网清单/2022行业招标3.1.12_readable.json" # 可以根据实际情况修改
|
||||||
|
|
||||||
|
# 存储所有文件的统计结果
|
||||||
|
all_type_attributes = defaultdict(set)
|
||||||
|
|
||||||
|
# 如果是目录,则遍历所有JSON文件
|
||||||
|
if os.path.isdir(json_dir):
|
||||||
|
for root, _, files in os.walk(json_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".json"):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
print(f"正在处理: {file_path}")
|
||||||
|
|
||||||
|
# 获取当前文件的统计结果
|
||||||
|
current_type_attrs = analyze_project_division(file_path)
|
||||||
|
|
||||||
|
# 合并结果
|
||||||
|
for node_type, attrs in current_type_attrs.items():
|
||||||
|
all_type_attributes[node_type].update(attrs)
|
||||||
|
else:
|
||||||
|
# 单个文件
|
||||||
|
print(f"正在处理: {json_dir}")
|
||||||
|
all_type_attributes = analyze_project_division(json_dir)
|
||||||
|
|
||||||
|
# 保存结果到CSV
|
||||||
|
save_to_csv(all_type_attributes)
|
||||||
|
print(f"统计结果已保存到 node_attributes.csv")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+1067
File diff suppressed because it is too large
Load Diff
+1488
File diff suppressed because it is too large
Load Diff
+206
@@ -0,0 +1,206 @@
|
|||||||
|
"""
|
||||||
|
构建知识图谱
|
||||||
|
清洗节点属性数据规范化
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def load_template(template_path):
|
||||||
|
"""
|
||||||
|
加载模板CSV文件,获取每种类型允许的属性列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_path: 模板CSV文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 按类型分类的属性名集合
|
||||||
|
"""
|
||||||
|
allowed_attributes = defaultdict(set)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(template_path, "r", encoding="utf-8") as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
next(reader) # 跳过表头
|
||||||
|
|
||||||
|
for row in reader:
|
||||||
|
if len(row) >= 2:
|
||||||
|
node_type = row[0].strip()
|
||||||
|
attr_name = row[1].strip()
|
||||||
|
if node_type and attr_name:
|
||||||
|
allowed_attributes[node_type].add(attr_name)
|
||||||
|
|
||||||
|
return allowed_attributes
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取模板文件出错: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_mapped_type(original_type):
|
||||||
|
"""
|
||||||
|
将数字类型映射为对应的字符串类型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
original_type: 原始类型值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 映射后的类型值
|
||||||
|
"""
|
||||||
|
type_mapping = {
|
||||||
|
"0": "定额",
|
||||||
|
"1": "主材",
|
||||||
|
"2": "人工",
|
||||||
|
"3": "材料",
|
||||||
|
"4": "机械",
|
||||||
|
"5": "设备",
|
||||||
|
"配件": "设备",
|
||||||
|
"8": "清单",
|
||||||
|
}
|
||||||
|
|
||||||
|
return type_mapping.get(original_type, original_type)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_attributes(json_file_path, template_path, output_file_path):
|
||||||
|
"""
|
||||||
|
根据模板筛选JSON文件中的属性
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
template_path: 模板CSV文件路径
|
||||||
|
output_file_path: 输出JSON文件路径
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 加载模板
|
||||||
|
allowed_attributes = load_template(template_path)
|
||||||
|
if not allowed_attributes:
|
||||||
|
print("模板加载失败或为空")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 读取JSON文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 检查是否存在projectData.projectDivision
|
||||||
|
if "projectData" not in data or "projectDivision" not in data["projectData"]:
|
||||||
|
print(f"文件 {json_file_path} 中不包含projectData.projectDivision数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取projectDivision
|
||||||
|
project_division = data["projectData"]["projectDivision"]
|
||||||
|
|
||||||
|
# 递归筛选节点属性
|
||||||
|
def filter_node(node):
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 处理类型映射和GUID大小写
|
||||||
|
processed_node = {}
|
||||||
|
for key, value in node.items():
|
||||||
|
# 处理GUID大小写
|
||||||
|
if key.lower() == "guid":
|
||||||
|
processed_node["GUID"] = value
|
||||||
|
else:
|
||||||
|
processed_node[key] = value
|
||||||
|
|
||||||
|
# 处理类型映射
|
||||||
|
if "类型" in processed_node:
|
||||||
|
processed_node["类型"] = get_mapped_type(processed_node["类型"])
|
||||||
|
if "type" in processed_node:
|
||||||
|
processed_node["type"] = get_mapped_type(processed_node["type"])
|
||||||
|
|
||||||
|
# 获取节点类型,优先使用type字段,如果没有则使用"类型"字段
|
||||||
|
node_type = processed_node.get("type", processed_node.get("类型", "未知类型"))
|
||||||
|
node_type = get_mapped_type(node_type) # 确保类型已映射
|
||||||
|
|
||||||
|
# 如果模板中有该类型的定义
|
||||||
|
if node_type in allowed_attributes:
|
||||||
|
# 筛选属性
|
||||||
|
filtered_node = {}
|
||||||
|
for attr_name, attr_value in processed_node.items():
|
||||||
|
# 处理属性名大小写
|
||||||
|
template_attr_name = attr_name
|
||||||
|
if attr_name.lower() == "guid":
|
||||||
|
template_attr_name = "GUID"
|
||||||
|
|
||||||
|
# 如果属性在模板中定义,或者是children或材机列表属性,则保留
|
||||||
|
if (
|
||||||
|
template_attr_name in allowed_attributes[node_type]
|
||||||
|
or attr_name == "children"
|
||||||
|
or attr_name == "材机列表"
|
||||||
|
):
|
||||||
|
# 如果是children或材机列表属性,递归处理子节点
|
||||||
|
if (attr_name == "children" or attr_name == "材机列表") and isinstance(attr_value, list):
|
||||||
|
filtered_node[attr_name] = [filter_node(child) for child in attr_value]
|
||||||
|
else:
|
||||||
|
# 如果是GUID属性,统一使用大写
|
||||||
|
if attr_name.lower() == "guid":
|
||||||
|
filtered_node["GUID"] = attr_value
|
||||||
|
else:
|
||||||
|
filtered_node[attr_name] = attr_value
|
||||||
|
return filtered_node
|
||||||
|
else:
|
||||||
|
# 如果模板中没有该类型的定义,则递归处理所有可能的子结构
|
||||||
|
result = {}
|
||||||
|
for key, value in processed_node.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
result[key] = filter_node(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
result[key] = [filter_node(item) for item in value]
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 深度遍历整个projectDivision结构
|
||||||
|
def deep_traverse(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
# 检查是否是一个有类型的节点
|
||||||
|
if "type" in obj or "类型" in obj:
|
||||||
|
return filter_node(obj)
|
||||||
|
else:
|
||||||
|
# 不是有类型的节点,递归处理所有字段
|
||||||
|
result = {}
|
||||||
|
for key, value in obj.items():
|
||||||
|
result[key] = deep_traverse(value)
|
||||||
|
return result
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
return [deep_traverse(item) for item in obj]
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# 处理整个projectDivision
|
||||||
|
filtered_project_division = deep_traverse(project_division)
|
||||||
|
|
||||||
|
# 更新数据
|
||||||
|
data["projectData"]["projectDivision"] = filtered_project_division
|
||||||
|
|
||||||
|
# 保存到新文件
|
||||||
|
with open(output_file_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
print(f"筛选完成,结果已保存到 {output_file_path}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"处理文件时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
主函数
|
||||||
|
"""
|
||||||
|
# 指定文件路径
|
||||||
|
json_file_path = "KG_generation/dataset/json/主网清单/架空.json"
|
||||||
|
template_path = "KG_generation/节点属性模板.csv"
|
||||||
|
output_file_path = "KG_generation/dataset/json/主网清单/架空_clean.json"
|
||||||
|
|
||||||
|
# 执行筛选
|
||||||
|
filter_attributes(json_file_path, template_path, output_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[neo4j]
|
||||||
|
uri = bolt://10.1.6.34:7687
|
||||||
|
user = neo4j
|
||||||
|
password = password
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def convert_json_to_readable(input_file, output_file=None):
|
||||||
|
"""
|
||||||
|
将JSON文件转换为可读格式并保存
|
||||||
|
|
||||||
|
参数:
|
||||||
|
input_file: 输入JSON文件路径
|
||||||
|
output_file: 输出文件路径,如果为None则自动生成
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 读取JSON文件
|
||||||
|
with open(input_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 如果未指定输出文件,则自动生成
|
||||||
|
if output_file is None:
|
||||||
|
base_name = os.path.splitext(input_file)[0]
|
||||||
|
output_file = f"{base_name}_readable.json"
|
||||||
|
|
||||||
|
# 以美化格式写入新文件
|
||||||
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
print(f"转换成功!可读格式的文件已保存为: {output_file}")
|
||||||
|
return output_file
|
||||||
|
except Exception as e:
|
||||||
|
print(f"转换过程中出现错误: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 指定输入文件路径
|
||||||
|
input_file = r"project2json/outputs/json/220kV变电站工程.json"
|
||||||
|
|
||||||
|
# 调用转换函数
|
||||||
|
convert_json_to_readable(input_file)
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from difflib import SequenceMatcher
|
||||||
|
|
||||||
|
|
||||||
|
def extract_guid_from_filename(filename):
|
||||||
|
"""从文件名中提取 GUID"""
|
||||||
|
# 匹配 8-4-4-4-12 格式的 GUID
|
||||||
|
pattern = r"([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})"
|
||||||
|
match = re.search(pattern, filename, re.IGNORECASE)
|
||||||
|
return match.group(1).upper() if match else None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_cost_name(name):
|
||||||
|
"""标准化费用名称,去除特殊符号、后缀等,便于匹配"""
|
||||||
|
# 去除 ID 后缀如 "_GJJ", "_BZHF" 等
|
||||||
|
name = re.sub(r"_\w+$", "", name)
|
||||||
|
# 去除常见符号
|
||||||
|
name = re.sub(r"[^\w]", "", name)
|
||||||
|
# 统一转小写
|
||||||
|
return name.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_similarity(a, b):
|
||||||
|
"""计算两个字符串的相似度"""
|
||||||
|
return SequenceMatcher(None, a, b).ratio()
|
||||||
|
|
||||||
|
|
||||||
|
def load_calculation_results(json_file_path):
|
||||||
|
"""读取计算结果 JSON,返回费用总和字典"""
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
total_costs = {}
|
||||||
|
for node_name, cost_dict in data.items():
|
||||||
|
for cost_name, value in cost_dict.items():
|
||||||
|
total_costs[cost_name] = total_costs.get(cost_name, 0) + value
|
||||||
|
return total_costs
|
||||||
|
|
||||||
|
|
||||||
|
def find_node_by_guid(expense_preview, target_guid):
|
||||||
|
"""
|
||||||
|
在 expensePreview 中递归查找 GUID 对应的节点
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def search_recursive(items):
|
||||||
|
"""在列表中递归查找 GUID"""
|
||||||
|
for item in items:
|
||||||
|
current_guid = item.get("GUID", "").strip("{}").upper()
|
||||||
|
if current_guid == target_guid:
|
||||||
|
return item.get("children", [])
|
||||||
|
if "children" in item:
|
||||||
|
result = search_recursive(item["children"])
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 遍历每个大类(如 "建筑工程")
|
||||||
|
for category_name, category_data in expense_preview.items():
|
||||||
|
if not isinstance(category_data, dict):
|
||||||
|
continue
|
||||||
|
# 遍历每个子类(如 "建筑"、"安装")
|
||||||
|
for subcategory_name, items in category_data.items():
|
||||||
|
if isinstance(items, list):
|
||||||
|
result = search_recursive(items)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_project_data_and_find_costs(project_json_path, target_guid):
|
||||||
|
"""读取 project_data.json 并查找对应 GUID 的费用列表"""
|
||||||
|
with open(project_json_path, "r", encoding="utf-8") as f:
|
||||||
|
project_data = json.load(f)
|
||||||
|
|
||||||
|
expense_preview = project_data.get("projectData", {}).get("expensePreview", {})
|
||||||
|
children = find_node_by_guid(expense_preview, target_guid)
|
||||||
|
|
||||||
|
if not children:
|
||||||
|
print(f"未找到 GUID 为 {target_guid} 的节点")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
external_costs = {}
|
||||||
|
for item in children:
|
||||||
|
# 有些节点只有 cost,没有 id(可能是合计)
|
||||||
|
item_id = item.get("id", "")
|
||||||
|
cost_str = item.get("cost", "0")
|
||||||
|
try:
|
||||||
|
cost_val = float(cost_str)
|
||||||
|
except ValueError:
|
||||||
|
cost_val = 0.0
|
||||||
|
|
||||||
|
if item_id:
|
||||||
|
# 只提取有 id 的项
|
||||||
|
clean_id = re.sub(r"_\w+$", "", item_id) # 去掉 _GJJ 等后缀
|
||||||
|
external_costs[clean_id] = cost_val
|
||||||
|
# 如果没有 id,可以考虑用其他方式标记,这里先忽略
|
||||||
|
return external_costs
|
||||||
|
|
||||||
|
|
||||||
|
def match_and_compare_costs(calc_costs, ext_costs, similarity_threshold=0.6):
|
||||||
|
"""匹配两个费用列表并对比"""
|
||||||
|
comparison = []
|
||||||
|
matched_ext = set()
|
||||||
|
|
||||||
|
for calc_name, calc_value in calc_costs.items():
|
||||||
|
best_match = None
|
||||||
|
best_score = 0
|
||||||
|
|
||||||
|
for ext_name in ext_costs:
|
||||||
|
if ext_name in matched_ext:
|
||||||
|
continue
|
||||||
|
score = calculate_similarity(normalize_cost_name(calc_name), normalize_cost_name(ext_name))
|
||||||
|
if score > best_score:
|
||||||
|
best_score = score
|
||||||
|
best_match = ext_name
|
||||||
|
|
||||||
|
# 判断是否足够相似
|
||||||
|
if best_match and best_score >= similarity_threshold:
|
||||||
|
ext_value = ext_costs[best_match]
|
||||||
|
difference = calc_value - ext_value
|
||||||
|
comparison.append(
|
||||||
|
{
|
||||||
|
"项目": calc_name,
|
||||||
|
"计算值": calc_value,
|
||||||
|
"参考值": ext_value,
|
||||||
|
"差异": difference,
|
||||||
|
"匹配项": best_match,
|
||||||
|
"相似度": best_score,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
matched_ext.add(best_match)
|
||||||
|
else:
|
||||||
|
comparison.append(
|
||||||
|
{
|
||||||
|
"项目": calc_name,
|
||||||
|
"计算值": calc_value,
|
||||||
|
"参考值": None,
|
||||||
|
"差异": None,
|
||||||
|
"匹配项": None,
|
||||||
|
"相似度": best_score,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加未匹配的参考项
|
||||||
|
for ext_name, ext_value in ext_costs.items():
|
||||||
|
if ext_name not in matched_ext:
|
||||||
|
comparison.append(
|
||||||
|
{"项目": None, "计算值": None, "参考值": ext_value, "差异": None, "匹配项": ext_name, "相似度": None}
|
||||||
|
)
|
||||||
|
|
||||||
|
return comparison
|
||||||
|
|
||||||
|
|
||||||
|
def save_comparison_to_txt(comparison, output_txt_path):
|
||||||
|
"""保存对比结果到 TXT 文件"""
|
||||||
|
with open(output_txt_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(f"{'项目':<20} {'计算值':<15} {'参考值':<15} {'差异':<15} {'匹配项':<20} {'相似度':<8}\n")
|
||||||
|
f.write("-" * 100 + "\n")
|
||||||
|
for item in comparison:
|
||||||
|
project = item["项目"] or ""
|
||||||
|
calc = f"{item['计算值']:.2f}" if item["计算值"] is not None else ""
|
||||||
|
ref = f"{item['参考值']:.2f}" if item["参考值"] is not None else ""
|
||||||
|
diff = f"{item['差异']:.2f}" if item["差异"] is not None else ""
|
||||||
|
match = item["匹配项"] or ""
|
||||||
|
sim = f"{item['相似度']:.3f}" if item["相似度"] is not None else ""
|
||||||
|
f.write(f"{project:<20} {calc:<15} {ref:<15} {diff:<15} {match:<20} {sim:<8}\n")
|
||||||
|
print(f"对比结果已保存至: {output_txt_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 配置路径
|
||||||
|
calculation_json_path = "project2json/outputs/bclresult/基础工程材料工地运输_496A54BB-8A38-4BE1-B116-AD4780E6874A_预算工程_calculation_results.json"
|
||||||
|
project_data_json_path = "project2json/outputs/json/220kV变电站工程_readable.json" # 你要提供这个文件
|
||||||
|
|
||||||
|
# 1. 提取 GUID
|
||||||
|
guid = extract_guid_from_filename(calculation_json_path)
|
||||||
|
if not guid:
|
||||||
|
raise ValueError("无法从文件名中提取 GUID")
|
||||||
|
print(f"提取到 GUID: {guid}")
|
||||||
|
|
||||||
|
# 2. 读取计算结果并汇总
|
||||||
|
calc_costs = load_calculation_results(calculation_json_path)
|
||||||
|
print(f"共加载 {len(calc_costs)} 个费用项")
|
||||||
|
|
||||||
|
# 3. 从 project_data.json 中查找对应 GUID 的费用项
|
||||||
|
ext_costs = load_project_data_and_find_costs(project_data_json_path, guid)
|
||||||
|
print(f"从 project_data 中找到 {len(ext_costs)} 个参考费用项")
|
||||||
|
|
||||||
|
# 4. 匹配并对比
|
||||||
|
comparison = match_and_compare_costs(calc_costs, ext_costs, similarity_threshold=0.6)
|
||||||
|
|
||||||
|
# 5. 输出到同名 .txt 文件
|
||||||
|
base_name = os.path.splitext(calculation_json_path)[0]
|
||||||
|
output_txt_path = base_name + ".txt"
|
||||||
|
save_comparison_to_txt(comparison, output_txt_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,758 @@
|
|||||||
|
import json
|
||||||
|
from typing import Dict, List, Any, Optional, Tuple
|
||||||
|
from bcl_calculator import (
|
||||||
|
BCLCalculator,
|
||||||
|
BCLContext,
|
||||||
|
BCLVariant,
|
||||||
|
BCLVariantType,
|
||||||
|
BCLPrefixContext,
|
||||||
|
BCLPrefixPrevContext,
|
||||||
|
BCLDataSourceItem,
|
||||||
|
BCLDataSourceContext,
|
||||||
|
)
|
||||||
|
from project import Ration, MaterialOrEquipment, Material, Equipment, bill_node
|
||||||
|
from copy import deepcopy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 全局变量
|
||||||
|
calculator = BCLCalculator()
|
||||||
|
|
||||||
|
|
||||||
|
def load_json_data(json_file_path: str, json_path: str = None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从JSON文件中加载指定路径的数据
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
json_path: JSON路径表达式,格式如 "projectData.projectInfo",None表示返回整个JSON
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 加载的数据
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
if not json_path:
|
||||||
|
return data
|
||||||
|
|
||||||
|
# 按路径逐级获取数据
|
||||||
|
parts = json_path.split(".")
|
||||||
|
result = data
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(result, dict) and part in result:
|
||||||
|
result = result.get(part, {})
|
||||||
|
else:
|
||||||
|
print(f"JSON路径 '{json_path}' 中的部分 '{part}' 不存在")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"加载JSON数据时出错: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def create_node_from_type(node: dict[str, any]):
|
||||||
|
"""
|
||||||
|
根据项目划分子节点类型动态创建对应类型的对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 项目划分子节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
对应类型的对象:Ration、Material或Equipment
|
||||||
|
"""
|
||||||
|
# 获取节点类型
|
||||||
|
node_type = node.get("类型", "")
|
||||||
|
type_code = node.get("type", "")
|
||||||
|
|
||||||
|
# 判断是否为“定额”类型: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 ["设备", "配件"]:
|
||||||
|
return create_equipment_from_node(node)
|
||||||
|
else:
|
||||||
|
logging.warning(f"未知节点类型: 类型={node_type}, type={type_code},默认创建Ration对象")
|
||||||
|
return create_ration_from_node(node)
|
||||||
|
|
||||||
|
|
||||||
|
def create_list_from_node(node: dict[str, any]) -> bill_node:
|
||||||
|
"""
|
||||||
|
创建定额对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 项目划分子节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 创建的清单对象
|
||||||
|
"""
|
||||||
|
bill = bill_node()
|
||||||
|
|
||||||
|
# 设置定额相关属性
|
||||||
|
bill.清单名称 = node.get("清单名称")
|
||||||
|
bill.清单全码 = node.get("清单全码")
|
||||||
|
bill.编码 = node.get("编码")
|
||||||
|
bill.单位 = node.get("单位")
|
||||||
|
bill.计算式 = node.get("计算式")
|
||||||
|
bill.数量 = node.get("数量")
|
||||||
|
bill.取费表 = node.get("取费表")
|
||||||
|
bill.合价 = node.get("合价")
|
||||||
|
bill.工作内容 = node.get("工作内容")
|
||||||
|
bill.类型 = node.get("类型")
|
||||||
|
bill.type = node.get("type")
|
||||||
|
bill.项目特征 = node.get("项目特征")
|
||||||
|
bill.资源库名称 = node.get("资源库名称")
|
||||||
|
bill.计算规则 = node.get("计算规则")
|
||||||
|
bill.单价 = node.get("单价")
|
||||||
|
bill.一次性费用 = node.get("一次性费用")
|
||||||
|
bill.取费表名称 = node.get("取费表名称")
|
||||||
|
bill.取费表类型 = node.get("取费表类型")
|
||||||
|
bill.单价不含税 = node.get("单价不含税")
|
||||||
|
bill.合价不含税 = node.get("合价不含税")
|
||||||
|
bill.专业类型 = node.get("专业类型")
|
||||||
|
bill.关联父级量 = node.get("关联父级量")
|
||||||
|
bill.含量 = node.get("含量")
|
||||||
|
|
||||||
|
# 遍历节点的所有属性,确保所有可能的字段都被设置
|
||||||
|
for key, value in node.items():
|
||||||
|
if hasattr(bill, key) and not key.startswith("_"):
|
||||||
|
if value is not None:
|
||||||
|
setattr(bill, key, str(value) if value != "" else "")
|
||||||
|
|
||||||
|
return bill
|
||||||
|
|
||||||
|
|
||||||
|
def create_ration_from_node(node: dict[str, any]) -> Ration:
|
||||||
|
"""
|
||||||
|
创建定额对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 项目划分子节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Ration: 创建的定额对象
|
||||||
|
"""
|
||||||
|
ration = Ration()
|
||||||
|
|
||||||
|
# 设置定额相关属性
|
||||||
|
ration.id = node.get("id")
|
||||||
|
ration.type = node.get("类型")
|
||||||
|
ration.name = node.get("项目名称")
|
||||||
|
ration.编码 = node.get("编码")
|
||||||
|
ration.单位 = node.get("单位")
|
||||||
|
ration.数量 = node.get("数量")
|
||||||
|
ration.资源库名称 = node.get("资源库名称")
|
||||||
|
ration.计算式 = node.get("计算式")
|
||||||
|
ration.人工费 = node.get("人工费")
|
||||||
|
ration.机械费 = node.get("机械费")
|
||||||
|
ration.甲供材料费不含税 = node.get("甲供材料费不含税")
|
||||||
|
ration.材料费 = node.get("材料费")
|
||||||
|
ration.定额系数 = node.get("定额系数")
|
||||||
|
ration.人工系数 = node.get("人工系数")
|
||||||
|
ration.材料系数 = node.get("材料系数")
|
||||||
|
ration.机械系数 = node.get("机械系数")
|
||||||
|
ration.定额范围 = node.get("定额范围")
|
||||||
|
ration.定额章节名称 = node.get("定额章节名称")
|
||||||
|
ration.费用类型 = node.get("费用类型")
|
||||||
|
ration.甲供材料费含税 = node.get("甲供材料费含税")
|
||||||
|
ration.投标合价 = node.get("投标合价")
|
||||||
|
ration.其中甲供材料费 = node.get("其中:甲供材料费")
|
||||||
|
ration.合价不含税 = node.get("合价不含税")
|
||||||
|
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 value is not None:
|
||||||
|
setattr(ration, key, str(value) if value != "" else "")
|
||||||
|
|
||||||
|
ration.乙供材料费不含税 = node.get("材料费")
|
||||||
|
return ration
|
||||||
|
|
||||||
|
|
||||||
|
def create_material_from_node(node: dict[str, any]) -> Material:
|
||||||
|
"""
|
||||||
|
创建主材对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 项目划分子节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Material: 创建的主材对象
|
||||||
|
"""
|
||||||
|
material = Material()
|
||||||
|
|
||||||
|
# 设置主材相关属性
|
||||||
|
material.id = node.get("id")
|
||||||
|
material.name = node.get("项目名称")
|
||||||
|
material.type = 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("规格型号")
|
||||||
|
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("特征段")
|
||||||
|
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("合价不含税")
|
||||||
|
material.保管 = node.get("保管")
|
||||||
|
material.卸车 = node.get("卸车")
|
||||||
|
material.暂估价 = node.get("暂估价")
|
||||||
|
material.每件重 = node.get("每件重")
|
||||||
|
material.设备性材料 = node.get("设备性材料")
|
||||||
|
material.调差类型 = node.get("调差类型")
|
||||||
|
material.运输类型 = node.get("运输类型")
|
||||||
|
material.拆分 = node.get("拆分")
|
||||||
|
|
||||||
|
# 遍历节点的所有属性,确保所有可能的字段都被设置
|
||||||
|
for key, value in node.items():
|
||||||
|
if hasattr(material, key) and not key.startswith("_"):
|
||||||
|
if value is not None:
|
||||||
|
setattr(material, key, str(value) if value != "" else "")
|
||||||
|
|
||||||
|
material.预算价不含税 = material.单价不含税
|
||||||
|
|
||||||
|
return material
|
||||||
|
|
||||||
|
|
||||||
|
def create_equipment_from_node(node: dict[str, any]) -> Equipment:
|
||||||
|
"""
|
||||||
|
创建设备对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 项目划分子节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Equipment: 创建的设备对象
|
||||||
|
"""
|
||||||
|
equipment = Equipment()
|
||||||
|
|
||||||
|
# 设置设备相关属性
|
||||||
|
equipment.id = node.get("id")
|
||||||
|
equipment.name = node.get("项目名称")
|
||||||
|
equipment.type = node.get("类型", "设备")
|
||||||
|
equipment.供货方 = node.get("供货方")
|
||||||
|
equipment.关联父级量 = node.get("关联父级量")
|
||||||
|
equipment.制造长度 = node.get("制造长度")
|
||||||
|
equipment.单位 = node.get("单位")
|
||||||
|
equipment.单重 = node.get("单重")
|
||||||
|
equipment.增值税率 = node.get("增值税率")
|
||||||
|
equipment.数量 = node.get("数量")
|
||||||
|
equipment.损耗率 = node.get("损耗率")
|
||||||
|
equipment.规格型号 = node.get("规格型号")
|
||||||
|
equipment.线重 = node.get("线重")
|
||||||
|
equipment.市场价不含税 = node.get("市场价不含税")
|
||||||
|
equipment.市场价含税 = node.get("市场价含税")
|
||||||
|
equipment.编码 = node.get("编码")
|
||||||
|
equipment.设备类型 = node.get("设备类型")
|
||||||
|
equipment.资源库名称 = node.get("资源库名称")
|
||||||
|
equipment.集中配送 = node.get("集中配送")
|
||||||
|
equipment.运杂费率 = node.get("运杂费率")
|
||||||
|
equipment.投标数量 = node.get("投标数量")
|
||||||
|
equipment.投标单价 = node.get("投标单价")
|
||||||
|
equipment.特征段 = node.get("特征段")
|
||||||
|
equipment.颜色标记 = node.get("颜色标记")
|
||||||
|
equipment.单价不含税 = node.get("单价不含税")
|
||||||
|
equipment.单价含税 = node.get("单价含税")
|
||||||
|
equipment.合价含税 = node.get("合价含税")
|
||||||
|
equipment.合价不含税 = node.get("合价不含税")
|
||||||
|
equipment.保管 = node.get("保管")
|
||||||
|
equipment.卸车 = node.get("卸车")
|
||||||
|
equipment.工程量索引 = node.get("工程量索引")
|
||||||
|
equipment.截面积 = node.get("截面积")
|
||||||
|
equipment.拆分 = node.get("拆分")
|
||||||
|
equipment.暂估价 = node.get("暂估价")
|
||||||
|
equipment.计算式 = node.get("计算式")
|
||||||
|
equipment.设备性材料 = node.get("设备性材料")
|
||||||
|
equipment.调差类型 = node.get("调差类型")
|
||||||
|
|
||||||
|
# 遍历节点的所有属性,确保所有可能的字段都被设置
|
||||||
|
for key, value in node.items():
|
||||||
|
if hasattr(equipment, key) and not key.startswith("_"):
|
||||||
|
if value is not None:
|
||||||
|
setattr(equipment, key, str(value) if value != "" else "")
|
||||||
|
|
||||||
|
return equipment
|
||||||
|
|
||||||
|
|
||||||
|
def init_bcl_calculator(software_category, engineering_type, calculation_type):
|
||||||
|
"""
|
||||||
|
初始化BCL计算器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
software_category: 软件类别(主网/配网/技改)
|
||||||
|
engineering_type: 工程类型(预算/清单)
|
||||||
|
calculation_type: 计算类型(工程量/人材机)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功初始化
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
# 构建计算配置路径
|
||||||
|
# 格式:计算配置/软件类型(主网,配网,技改)/计算类型(工程量,人材机)/工程类型(预算,清单)
|
||||||
|
config_path = f"计算配置/{software_category}/{calculation_type}/{engineering_type}"
|
||||||
|
|
||||||
|
print(f"加载计算配置: {config_path}")
|
||||||
|
|
||||||
|
# 检查目录是否存在
|
||||||
|
import os
|
||||||
|
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
print(f"计算配置目录不存在: {config_path}")
|
||||||
|
# 尝试创建目录
|
||||||
|
try:
|
||||||
|
os.makedirs(config_path, exist_ok=True)
|
||||||
|
print(f"已创建计算配置目录: {config_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"创建计算配置目录失败: {e}")
|
||||||
|
|
||||||
|
# 如果目录不存在,使用默认配置
|
||||||
|
default_path = "计算配置/主网/工程量/预算"
|
||||||
|
print(f"使用默认计算配置: {default_path}")
|
||||||
|
|
||||||
|
# 检查默认配置目录是否存在
|
||||||
|
if not os.path.exists(default_path):
|
||||||
|
print(f"默认计算配置目录不存在: {default_path}")
|
||||||
|
# 尝试使用原始默认配置
|
||||||
|
original_default_path = "计算配置/主网/主网预算"
|
||||||
|
print(f"尝试使用原始默认计算配置: {original_default_path}")
|
||||||
|
|
||||||
|
if os.path.exists(original_default_path):
|
||||||
|
config_path = original_default_path
|
||||||
|
else:
|
||||||
|
print(f"原始默认计算配置目录不存在: {original_default_path}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
config_path = default_path
|
||||||
|
|
||||||
|
# 加载脚本
|
||||||
|
result = calculator.load_scripts_dir(config_path)
|
||||||
|
if False == result:
|
||||||
|
print(f"加载脚本错误: {calculator.get_last_error()}")
|
||||||
|
# 尝试使用默认配置
|
||||||
|
default_path = "计算配置/主网/工程量/预算"
|
||||||
|
print(f"尝试使用默认计算配置: {default_path}")
|
||||||
|
|
||||||
|
# 检查默认配置目录是否存在
|
||||||
|
if not os.path.exists(default_path):
|
||||||
|
print(f"默认计算配置目录不存在: {default_path}")
|
||||||
|
# 尝试使用原始默认配置
|
||||||
|
original_default_path = "计算配置/主网/主网预算"
|
||||||
|
print(f"尝试使用原始默认计算配置: {original_default_path}")
|
||||||
|
|
||||||
|
if os.path.exists(original_default_path):
|
||||||
|
result = calculator.load_scripts_dir(original_default_path)
|
||||||
|
if False == result:
|
||||||
|
print(f"加载原始默认脚本错误: {calculator.get_last_error()}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"成功加载原始默认脚本: {original_default_path}")
|
||||||
|
else:
|
||||||
|
print(f"原始默认计算配置目录不存在: {original_default_path}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
result = calculator.load_scripts_dir(default_path)
|
||||||
|
if False == result:
|
||||||
|
print(f"加载默认脚本错误: {calculator.get_last_error()}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"成功加载默认脚本: {default_path}")
|
||||||
|
else:
|
||||||
|
print(f"成功加载脚本: {config_path}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"初始化BCL计算器错误: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_material_or_equipment_from_node(node: dict[str, any]) -> MaterialOrEquipment:
|
||||||
|
"""
|
||||||
|
根据项目划分子节点动态创建 MaterialOrEquipment 对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 人材机子节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MaterialOrEquipment: 创建的人材机对象
|
||||||
|
"""
|
||||||
|
me = MaterialOrEquipment()
|
||||||
|
|
||||||
|
# 设置基本属性
|
||||||
|
me.id = node.get("id")
|
||||||
|
me.编码 = node.get("编码")
|
||||||
|
me.名称 = node.get("名称")
|
||||||
|
me.单位 = node.get("单位")
|
||||||
|
me.type = node.get("类型")
|
||||||
|
me.供货方 = node.get("供货方", "")
|
||||||
|
me.预算价不含税 = node.get("预算价不含税")
|
||||||
|
me.市场价不含税 = node.get("市场价不含税")
|
||||||
|
me.预算价含税 = node.get("预算价含税")
|
||||||
|
me.市场价含税 = node.get("市场价含税")
|
||||||
|
me.结算预算价不含税 = node.get("结算预算价不含税")
|
||||||
|
me.结算市场价不含税 = node.get("结算市场价不含税")
|
||||||
|
me.结算预算价含税 = node.get("结算预算价含税")
|
||||||
|
me.结算市场价含税 = node.get("结算市场价含税")
|
||||||
|
me.暂估价 = node.get("暂估价", "")
|
||||||
|
me.拆分 = node.get("拆分", "")
|
||||||
|
me.全口径市场价不含税 = node.get("全口径市场价不含税")
|
||||||
|
me.全口径市场价含税 = node.get("全口径市场价含税")
|
||||||
|
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))
|
||||||
|
|
||||||
|
return me
|
||||||
|
|
||||||
|
|
||||||
|
# 不需要派生,写一个函数去生成上下文
|
||||||
|
class ZjProjectBCLContext(BCLPrefixPrevContext):
|
||||||
|
"""
|
||||||
|
工程信息上下文类,用于从JSON文件中读取工程信息并提供给BCL计算器
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, prefix="@工程信息", valueDict=None, prevContext=None, project_division_name=None, **kwargs):
|
||||||
|
# 创建空字典作为valueDict参数
|
||||||
|
super().__init__(prefix=prefix, valueDict=valueDict or {}, prevContext=prevContext)
|
||||||
|
self.project_division_name = project_division_name
|
||||||
|
|
||||||
|
def add_variable(self, name, value):
|
||||||
|
"""
|
||||||
|
添加变量到上下文中
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 变量名
|
||||||
|
value: 变量值
|
||||||
|
"""
|
||||||
|
self._childContext.variables[name] = BCLVariant(value)
|
||||||
|
|
||||||
|
def add_variables_from_dict(self, data_dict: Dict[str, Any]):
|
||||||
|
"""
|
||||||
|
从字典批量添加变量到上下文中
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dict: 数据字典或列表
|
||||||
|
"""
|
||||||
|
# 如果是列表,直接添加为"属性"变量
|
||||||
|
if isinstance(data_dict, list):
|
||||||
|
self.add_variable("属性", data_dict)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 原有的字典处理逻辑
|
||||||
|
for key, value in data_dict.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
continue # 跳过嵌套字典
|
||||||
|
|
||||||
|
# 尝试将字符串转换为数字
|
||||||
|
if isinstance(value, str) and value.replace(".", "", 1).isdigit():
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.add_variable(key, value)
|
||||||
|
|
||||||
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
||||||
|
"""
|
||||||
|
获取变量值,支持直接查找价差系数属性
|
||||||
|
|
||||||
|
Args:
|
||||||
|
var_name: 变量名,格式为'对象.属性'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BCLVariant: 变量值
|
||||||
|
"""
|
||||||
|
# 首先尝试使用父类的方法获取变量
|
||||||
|
result = super()._get_variable_value(var_name)
|
||||||
|
|
||||||
|
# 如果是价差系数相关的变量
|
||||||
|
if var_name.startswith("@价差系数."):
|
||||||
|
# 提取属性名称
|
||||||
|
attr_name = var_name.split(".", 1)[1]
|
||||||
|
|
||||||
|
# 获取价差系数数据
|
||||||
|
price_diff_data = None
|
||||||
|
if (
|
||||||
|
self._prefix == "@价差系数"
|
||||||
|
and hasattr(self, "_childContext")
|
||||||
|
and hasattr(self._childContext, "variables")
|
||||||
|
):
|
||||||
|
if "属性" in self._childContext.variables:
|
||||||
|
price_diff_data = self._childContext.variables["属性"].value
|
||||||
|
|
||||||
|
# 如果没有在当前上下文找到,尝试从父上下文查找
|
||||||
|
if price_diff_data is None and self._prevContext is not None:
|
||||||
|
prev_ctx = self._prevContext
|
||||||
|
while prev_ctx is not None:
|
||||||
|
if isinstance(prev_ctx, ZjProjectBCLContext) and prev_ctx._prefix == "@价差系数":
|
||||||
|
if hasattr(prev_ctx, "_childContext") and hasattr(prev_ctx._childContext, "variables"):
|
||||||
|
if "属性" in prev_ctx._childContext.variables:
|
||||||
|
price_diff_data = prev_ctx._childContext.variables["属性"].value
|
||||||
|
break
|
||||||
|
prev_ctx = prev_ctx._prevContext
|
||||||
|
|
||||||
|
# 如果找到了价差系数数据,在属性列表中查找匹配项
|
||||||
|
if price_diff_data is not None and isinstance(price_diff_data, list):
|
||||||
|
for item in price_diff_data:
|
||||||
|
if isinstance(item, dict) and item.get("名称") == attr_name:
|
||||||
|
return BCLVariant(item.get("值", ""))
|
||||||
|
|
||||||
|
# 如果找不到匹配项,返回空值
|
||||||
|
print(f"警告: 未找到价差系数属性 '{attr_name}'")
|
||||||
|
return BCLVariant("")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# 前缀配置定义
|
||||||
|
class PrefixConfig:
|
||||||
|
def __init__(self, prefix: str, json_path: str = None, field_mappings: Dict[str, str] = None):
|
||||||
|
"""
|
||||||
|
前缀配置类
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix: 前缀名称,如 "@工程信息"
|
||||||
|
json_path: JSON路径表达式,如 "projectData.projectInfo"
|
||||||
|
field_mappings: 字段映射,键为JSON字段名,值为BCL变量名
|
||||||
|
"""
|
||||||
|
self.prefix = prefix
|
||||||
|
self.json_path = json_path
|
||||||
|
self.field_mappings = field_mappings or {}
|
||||||
|
|
||||||
|
|
||||||
|
def create_project_contexts(json_file_path: str, prefix_configs: List[PrefixConfig] = None) -> BCLContext:
|
||||||
|
"""
|
||||||
|
从JSON文件创建多个前缀上下文,并将它们链接在一起
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
prefix_configs: 前缀配置列表,None时使用默认配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BCLContext: 链接好的上下文对象
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 使用默认配置(如果未提供)
|
||||||
|
if prefix_configs is None:
|
||||||
|
prefix_configs = [
|
||||||
|
# 工程信息前缀
|
||||||
|
PrefixConfig(prefix="@工程信息", json_path="projectData.projectInfo"),
|
||||||
|
# 调差系数前缀
|
||||||
|
PrefixConfig(
|
||||||
|
prefix="@价差系数",
|
||||||
|
json_path="projectData.价差系数",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 创建根上下文
|
||||||
|
root_context = None
|
||||||
|
|
||||||
|
# 为每个前缀创建上下文并链接
|
||||||
|
for config in prefix_configs:
|
||||||
|
# 加载数据
|
||||||
|
data = load_json_data(json_file_path, config.json_path)
|
||||||
|
|
||||||
|
# 创建上下文
|
||||||
|
context = ZjProjectBCLContext(prefix=config.prefix, prevContext=root_context)
|
||||||
|
|
||||||
|
# 如果有字段映射,应用映射
|
||||||
|
if config.field_mappings:
|
||||||
|
mapped_data = {}
|
||||||
|
for bcl_name, json_name in config.field_mappings.items():
|
||||||
|
if json_name in data:
|
||||||
|
mapped_data[bcl_name] = data[json_name]
|
||||||
|
context.add_variables_from_dict(mapped_data)
|
||||||
|
else:
|
||||||
|
# 直接添加所有数据
|
||||||
|
context.add_variables_from_dict(data)
|
||||||
|
|
||||||
|
# 更新根上下文
|
||||||
|
root_context = context
|
||||||
|
|
||||||
|
return root_context
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"创建工程上下文时出错: {e}")
|
||||||
|
# 返回一个空的上下文
|
||||||
|
return ZjProjectBCLContext()
|
||||||
|
|
||||||
|
|
||||||
|
class ZjBillBCLContext(BCLPrefixPrevContext):
|
||||||
|
def __init__(self, prefix=None, valueDict=None, prevContext=None, **kwargs):
|
||||||
|
# 正确调用父类构造函数,提供必要的参数
|
||||||
|
super().__init__(prefix=prefix, valueDict=valueDict or {}, prevContext=prevContext)
|
||||||
|
|
||||||
|
|
||||||
|
class ZjQuantityBCLContext(BCLPrefixPrevContext):
|
||||||
|
def __init__(self, node_data=None, json_file_path=None, prevContext=None, **kwargs):
|
||||||
|
# 只传递BCLPrefixContext需要的参数
|
||||||
|
super().__init__(prefix="node", valueDict={}, prevContext=prevContext)
|
||||||
|
|
||||||
|
# 保存json_file_path作为实例变量
|
||||||
|
self.json_file_path = json_file_path
|
||||||
|
|
||||||
|
if node_data:
|
||||||
|
node = create_node_from_type(node_data)
|
||||||
|
# 添加项目特定变量
|
||||||
|
self.variables["source"] = BCLVariant([node])
|
||||||
|
|
||||||
|
|
||||||
|
def create_parent_ration(parent_node=None, project_node=None):
|
||||||
|
"""
|
||||||
|
创建父级定额对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent_node: 父级节点数据
|
||||||
|
project_node: 项目节点数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Ration: 创建的父级定额对象
|
||||||
|
"""
|
||||||
|
if parent_node:
|
||||||
|
parent_ration = create_ration_from_node(parent_node)
|
||||||
|
elif project_node:
|
||||||
|
parent_ration = create_ration_from_node(project_node)
|
||||||
|
else:
|
||||||
|
parent_ration = create_ration_from_node({})
|
||||||
|
|
||||||
|
# 确保父级节点中有必要的系数
|
||||||
|
if not hasattr(parent_ration, "定额系数") or not parent_ration.定额系数:
|
||||||
|
parent_ration.定额系数 = "1.0"
|
||||||
|
if not hasattr(parent_ration, "人工系数") or not parent_ration.人工系数:
|
||||||
|
parent_ration.人工系数 = "1.0"
|
||||||
|
if not hasattr(parent_ration, "材料系数") or not parent_ration.材料系数:
|
||||||
|
parent_ration.材料系数 = "1.0"
|
||||||
|
if not hasattr(parent_ration, "机械系数") or not parent_ration.机械系数:
|
||||||
|
parent_ration.机械系数 = "1.0"
|
||||||
|
|
||||||
|
# 确保父级节点中的数量是有效的数值
|
||||||
|
if not hasattr(parent_ration, "数量") or not parent_ration.数量:
|
||||||
|
parent_ration.数量 = "0.0"
|
||||||
|
|
||||||
|
return parent_ration
|
||||||
|
|
||||||
|
|
||||||
|
class ZjMaterialOrEquipmentBCLContext(BCLPrefixContext):
|
||||||
|
def __init__(self, node_data=None, parent_node=None, prevContext=None, **kwargs):
|
||||||
|
"""
|
||||||
|
初始化人材机上下文
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node_data: 人材机节点,可以是单个节点或节点列表
|
||||||
|
parent_node: 父级定额节点
|
||||||
|
prevContext: 上一级上下文(定额上下文)
|
||||||
|
**kwargs: 其他参数
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 调用父类构造函数
|
||||||
|
super().__init__(prefix="node", valueDict={}, prevContext=prevContext, **kwargs)
|
||||||
|
|
||||||
|
# 创建父级定额
|
||||||
|
self.parent_ration = create_parent_ration(parent_node)
|
||||||
|
|
||||||
|
# 创建人材机对象列表
|
||||||
|
self.items = []
|
||||||
|
if node_data:
|
||||||
|
if isinstance(node_data, list):
|
||||||
|
# 如果传入的是列表,处理每个人材机节点
|
||||||
|
self.items = [create_material_or_equipment_from_node(node) for node in node_data]
|
||||||
|
else:
|
||||||
|
# 如果传入的是单个节点
|
||||||
|
self.items = [create_material_or_equipment_from_node(node_data)]
|
||||||
|
|
||||||
|
# 初始化 parent_variables(用于支持 parent.xxx 表达式)
|
||||||
|
self.parent_variables = {}
|
||||||
|
for key, value in self.parent_ration.__dict__.items():
|
||||||
|
if value is None:
|
||||||
|
value = ""
|
||||||
|
self.parent_variables[key] = BCLVariant(value)
|
||||||
|
|
||||||
|
# 设置上下文变量
|
||||||
|
self.variables["items"] = BCLVariant(self.items)
|
||||||
|
self.variables["curnode"] = BCLVariant(self.items)
|
||||||
|
self.variables["source"] = BCLVariant(self.items)
|
||||||
|
self.variables["parent"] = BCLVariant([self.parent_ration])
|
||||||
|
|
||||||
|
# 添加调试信息
|
||||||
|
print(f"父级节点数量: {self.parent_ration.数量}")
|
||||||
|
print(f"父级节点定额系数: {self.parent_ration.定额系数}")
|
||||||
|
print(f"父级节点人工系数: {self.parent_ration.人工系数}")
|
||||||
|
print(f"父级节点材料系数: {self.parent_ration.材料系数}")
|
||||||
|
print(f"父级节点机械系数: {self.parent_ration.机械系数}")
|
||||||
|
|
||||||
|
if self.items:
|
||||||
|
print(f"人材机节点类型: {self.items[0].type}")
|
||||||
|
print(f"人材机节点数量: {self.items[0].数量}")
|
||||||
|
|
||||||
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
||||||
|
# 添加调试信息
|
||||||
|
print(f"获取变量: {var_name}")
|
||||||
|
|
||||||
|
if var_name.startswith("@"):
|
||||||
|
# 对于全局变量,尝试从上下文链中获取
|
||||||
|
result = super()._get_variable_value(var_name)
|
||||||
|
print(f" 全局变量 {var_name} = {result.value if result else None}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
if "." in var_name:
|
||||||
|
obj_name, attr = var_name.split(".", maxsplit=1)
|
||||||
|
if obj_name == "parent":
|
||||||
|
# 确保父级变量存在
|
||||||
|
if attr not in self.parent_variables:
|
||||||
|
print(f" 警告: 父级变量 '{attr}' 不存在,返回默认值")
|
||||||
|
if attr in ["数量", "定额系数", "人工系数", "材料系数", "机械系数"]:
|
||||||
|
return BCLVariant("1.0")
|
||||||
|
return BCLVariant("")
|
||||||
|
result = self.parent_variables.get(attr)
|
||||||
|
print(f" 父级变量 {attr} = {result.value if result else None}")
|
||||||
|
return result
|
||||||
|
elif obj_name in ["curnode", "items"]:
|
||||||
|
items = self.variables[obj_name].value
|
||||||
|
if isinstance(items, list) and len(items) > 0:
|
||||||
|
item = items[0]
|
||||||
|
val = getattr(item, attr, None)
|
||||||
|
result = BCLVariant(val)
|
||||||
|
print(f" 项目变量 {obj_name}.{attr} = {result.value if result else None}")
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
print(f" 警告: {obj_name} 为空或不是列表")
|
||||||
|
return BCLVariant("")
|
||||||
|
else:
|
||||||
|
if var_name in self.variables:
|
||||||
|
result = self.variables[var_name]
|
||||||
|
print(f" 上下文变量 {var_name} = {result.value if result else None}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 如果在本地找不到变量,尝试从上下文链中获取
|
||||||
|
print(f" 尝试从上下文链中获取变量 {var_name}")
|
||||||
|
return super()._get_variable_value(var_name)
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, List, Any, Optional, Set, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class CalculationStrategy(ABC):
|
||||||
|
"""
|
||||||
|
计算策略接口
|
||||||
|
|
||||||
|
定义了计算过程中可以自定义的各种方法。
|
||||||
|
每种软件类型可以提供自己的实现,以修改计算过程中的任何规则。
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate_fee_base(
|
||||||
|
self,
|
||||||
|
fee_base: str,
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
context: Any,
|
||||||
|
in_calculation: Optional[Set[str]] = None,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算取费基数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fee_base: 取费基数表达式
|
||||||
|
cost_table: 取费表
|
||||||
|
project_node: 项目节点
|
||||||
|
context: 计算上下文
|
||||||
|
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 计算结果
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate_internal_fee(
|
||||||
|
self,
|
||||||
|
fee_code: str,
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
context: Any,
|
||||||
|
in_calculation: Optional[Set[str]] = None,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算表内费用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fee_code: 费用代码
|
||||||
|
cost_table: 取费表
|
||||||
|
project_node: 项目节点
|
||||||
|
context: 计算上下文
|
||||||
|
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 计算结果
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate_external_variable(self, var_name: str, context: Any) -> float:
|
||||||
|
"""
|
||||||
|
计算表外变量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
var_name: 变量名
|
||||||
|
context: 计算上下文
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 计算结果
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_fee_item_by_code(self, cost_table: Dict[str, Any], code: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
在取费表中查找指定代码的费用项
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cost_table: 取费表
|
||||||
|
code: 费用代码
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Dict[str, Any]]: 找到的费用项,如果未找到则返回None
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_fee_base_nodes(self, node: Dict[str, Any], result: Optional[List] = None) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
递归查找取费表中包含"取费基数"的节点或有子节点的费用项
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 取费表节点
|
||||||
|
result: 结果列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 包含"取费基数"的节点或有子节点的费用项列表
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate_all_fees(
|
||||||
|
self,
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
json_file_path: Optional[str] = None,
|
||||||
|
engineering_type: Optional[str] = None,
|
||||||
|
) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
计算所有费用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_node: 项目节点
|
||||||
|
cost_table: 取费表
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
engineering_type: 工程类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, float]: 计算结果,键为费用名称,值为计算结果
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate_rcj_count(
|
||||||
|
self,
|
||||||
|
rcj_nodes: List[Tuple[Dict[str, Any], str]],
|
||||||
|
project_children: List[Dict[str, Any]],
|
||||||
|
json_file_path: Optional[str] = None,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
计算人材机节点的数量,考虑父级消耗量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rcj_nodes: 人材机节点列表,每个元素是(节点, 父级ID)元组
|
||||||
|
project_children: 项目划分级别下的所有工程量节点
|
||||||
|
json_file_path: JSON文件路径,用于获取工程信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 计算数量后的人材机节点列表
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def cat_rcj_count(self, rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
汇总人材机节点的数量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rcj_nodes: 人材机节点列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 汇总后的人材机节点列表
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calc_rcj_fee(self, rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
计算人材机节点的费用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rcj_nodes: 人材机节点列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 计算费用后的人材机节点列表
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def format_rcj_output(self, rcj_nodes: List[Dict[str, Any]], node_type: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
格式化人材机节点输出
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rcj_nodes: 人材机节点列表
|
||||||
|
node_type: 节点类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 格式化后的人材机节点列表
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate_rcj_fees(
|
||||||
|
self,
|
||||||
|
json_file_path: str,
|
||||||
|
project_name: str,
|
||||||
|
engineering_type: str,
|
||||||
|
project_guid: str = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
计算人材机合价
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
engineering_type: 工程类型
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 计算结果
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""
|
||||||
|
对工程量取费表进行后处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_file: 输出文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def preprocess_project_nodes(self, project_nodes: List[Dict[str, Any]]) -> None:
|
||||||
|
"""
|
||||||
|
对工程量节点进行预处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_nodes: 工程量节点列表
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultCalculationStrategy(CalculationStrategy):
|
||||||
|
"""
|
||||||
|
默认计算策略实现
|
||||||
|
|
||||||
|
实现了所有计算方法的默认行为,与原有代码逻辑相同。
|
||||||
|
其他计算策略可以继承此类,只重写需要修改的方法。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 导入必要的模块
|
||||||
|
from expressioncalculator import ExpressionCalculator
|
||||||
|
from bcl_utils import calculator
|
||||||
|
from item_acquisition import get_quantity_nodes, get_classified_resource_nodes
|
||||||
|
|
||||||
|
# 缓存已计算过的费用
|
||||||
|
self.calculated_fees = {}
|
||||||
|
|
||||||
|
def calculate_fee_base(
|
||||||
|
self,
|
||||||
|
fee_base: str,
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
context: Any,
|
||||||
|
in_calculation: Optional[Set[str]] = None,
|
||||||
|
) -> float:
|
||||||
|
"""计算取费基数"""
|
||||||
|
from quantity_fee_calculator import calculate_fee_base as original_calculate_fee_base
|
||||||
|
|
||||||
|
# 传递自身作为计算策略
|
||||||
|
return original_calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation, self)
|
||||||
|
|
||||||
|
def calculate_internal_fee(
|
||||||
|
self,
|
||||||
|
fee_code: str,
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
context: Any,
|
||||||
|
in_calculation: Optional[Set[str]] = None,
|
||||||
|
) -> float:
|
||||||
|
"""计算表内费用"""
|
||||||
|
from quantity_fee_calculator import calculate_internal_fee as original_calculate_internal_fee
|
||||||
|
|
||||||
|
# 传递自身作为计算策略
|
||||||
|
return original_calculate_internal_fee(fee_code, cost_table, project_node, context, in_calculation, self)
|
||||||
|
|
||||||
|
def calculate_external_variable(self, var_name: str, context: Any) -> float:
|
||||||
|
"""计算表外变量"""
|
||||||
|
# 直接使用 calculator.calculate,而不是调用 quantity_fee_calculator.py 中的函数
|
||||||
|
from bcl_utils import calculator
|
||||||
|
|
||||||
|
result = calculator.calculate(var_name, context)
|
||||||
|
return float(result) if result is not None else 0.0
|
||||||
|
|
||||||
|
def find_fee_item_by_code(self, cost_table: Dict[str, Any], code: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""在取费表中查找指定代码的费用项"""
|
||||||
|
from quantity_fee_calculator import find_fee_item_by_code as original_find_fee_item_by_code
|
||||||
|
|
||||||
|
return original_find_fee_item_by_code(cost_table, code)
|
||||||
|
|
||||||
|
def find_fee_base_nodes(self, node: Dict[str, Any], result: Optional[List] = None) -> List[Dict[str, Any]]:
|
||||||
|
"""递归查找取费表中包含"取费基数"的节点或有子节点的费用项"""
|
||||||
|
from quantity_fee_calculator import find_fee_base_nodes as original_find_fee_base_nodes
|
||||||
|
|
||||||
|
return original_find_fee_base_nodes(node, result)
|
||||||
|
|
||||||
|
def calculate_all_fees(
|
||||||
|
self,
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
json_file_path: Optional[str] = None,
|
||||||
|
engineering_type: Optional[str] = None,
|
||||||
|
) -> Dict[str, float]:
|
||||||
|
"""计算所有费用"""
|
||||||
|
from 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)
|
||||||
|
|
||||||
|
def calculate_rcj_count(
|
||||||
|
self,
|
||||||
|
rcj_nodes: List[Tuple[Dict[str, Any], str]],
|
||||||
|
project_children: List[Dict[str, Any]],
|
||||||
|
json_file_path: Optional[str] = None,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""计算人材机节点的数量"""
|
||||||
|
from resource_fee_calculator import calc_rcj_count as original_calc_rcj_count
|
||||||
|
|
||||||
|
return original_calc_rcj_count(rcj_nodes, project_children, json_file_path)
|
||||||
|
|
||||||
|
def cat_rcj_count(self, rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
"""汇总人材机节点的数量"""
|
||||||
|
from resource_fee_calculator import cat_rcj_count as original_cat_rcj_count
|
||||||
|
|
||||||
|
return original_cat_rcj_count(rcj_nodes)
|
||||||
|
|
||||||
|
def calc_rcj_fee(self, rcj_nodes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
"""计算人材机节点的费用"""
|
||||||
|
from resource_fee_calculator import calc_rcj_fee as original_calc_rcj_fee
|
||||||
|
|
||||||
|
return original_calc_rcj_fee(rcj_nodes)
|
||||||
|
|
||||||
|
def format_rcj_output(self, rcj_nodes: List[Dict[str, Any]], node_type: str) -> List[Dict[str, Any]]:
|
||||||
|
"""格式化人材机节点输出"""
|
||||||
|
from resource_fee_calculator import format_rcj_output as original_format_rcj_output
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
计算项目划分节点下所有人材机节点的合价
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 计算结果
|
||||||
|
"""
|
||||||
|
# 导入原始函数
|
||||||
|
from resource_fee_calculator import calculate_rcj_fees as original_calculate_rcj_fees
|
||||||
|
|
||||||
|
# 调用原始函数
|
||||||
|
return original_calculate_rcj_fees(json_file_path, project_name, project_guid)
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""对工程量取费表进行后处理"""
|
||||||
|
# 默认实现不做任何操作
|
||||||
|
pass
|
||||||
|
|
||||||
|
def preprocess_project_nodes(self, project_nodes: List[Dict[str, Any]]) -> None:
|
||||||
|
"""
|
||||||
|
默认实现不做任何预处理
|
||||||
|
"""
|
||||||
|
pass
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import os
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, List, Any, Optional, Tuple
|
||||||
|
from software_types import SoftwareType
|
||||||
|
from find_project_nodes import get_project_divisions_list
|
||||||
|
from quantity_fee_calculator import calculate_quantity_fees as base_calculate_quantity_fees
|
||||||
|
from resource_fee_calculator import calculate_resource_fees as base_calculate_resource_fees
|
||||||
|
from calculation_strategy import CalculationStrategy, DefaultCalculationStrategy
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class CalculatorBase(ABC):
|
||||||
|
"""计算器基类"""
|
||||||
|
|
||||||
|
def __init__(self, software_type: SoftwareType):
|
||||||
|
"""
|
||||||
|
初始化计算器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
software_type: 软件类型
|
||||||
|
"""
|
||||||
|
self.software_type = software_type
|
||||||
|
self.config_dir = software_type.config_dir
|
||||||
|
# 使用默认计算策略
|
||||||
|
self.calculation_strategy = self.create_calculation_strategy()
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""
|
||||||
|
创建计算策略
|
||||||
|
|
||||||
|
子类可以重写此方法,返回特定的计算策略实现
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CalculationStrategy: 计算策略实例
|
||||||
|
"""
|
||||||
|
return DefaultCalculationStrategy()
|
||||||
|
|
||||||
|
def get_default_input_file(self) -> str:
|
||||||
|
"""获取默认输入文件路径"""
|
||||||
|
return os.path.join(self.software_type.test_dir)
|
||||||
|
|
||||||
|
def get_output_dir(self) -> str:
|
||||||
|
"""获取输出目录路径"""
|
||||||
|
base_dir = "计算结果"
|
||||||
|
return os.path.join(base_dir, self.software_type.name)
|
||||||
|
|
||||||
|
def calculate_quantity_fee_tables(
|
||||||
|
self,
|
||||||
|
json_file_path: str,
|
||||||
|
project_name: str = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
计算工程量取费表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称,如果为None则处理所有项目
|
||||||
|
"""
|
||||||
|
engineering_type = self.software_type.engineering_type.value + "工程"
|
||||||
|
|
||||||
|
# 在计算前应用软件特定的规则
|
||||||
|
preprocess_func = self.apply_quantity_fee_rules()
|
||||||
|
if callable(preprocess_func): # 检查返回值是否可调用
|
||||||
|
preprocess_func(json_file_path) # 调用预处理函数
|
||||||
|
|
||||||
|
if project_name:
|
||||||
|
# 处理单个项目划分
|
||||||
|
print(f"处理单个项目划分: {project_name}")
|
||||||
|
output_file = self._calculate_quantity_fees(json_file_path, project_name, engineering_type)
|
||||||
|
if output_file:
|
||||||
|
print(f"已完成 {project_name} 的工程量取费表计算,结果保存在 {output_file}")
|
||||||
|
else:
|
||||||
|
# 处理所有项目划分
|
||||||
|
project_divisions = get_project_divisions_list(json_file_path)
|
||||||
|
print(f"找到 {len(project_divisions)} 个项目划分节点,开始处理工程量取费表...")
|
||||||
|
|
||||||
|
for i, (proj_name, proj_guid) in enumerate(project_divisions, 1):
|
||||||
|
# 使用命令行参数中指定的调差类型,而不是从JSON文件中获取的调差类型
|
||||||
|
print(f"处理 {i}/{len(project_divisions)}: {proj_name} (GUID: {proj_guid})")
|
||||||
|
output_file = self._calculate_quantity_fees(json_file_path, proj_name, engineering_type, proj_guid)
|
||||||
|
if output_file:
|
||||||
|
print(f"已完成 {proj_name} (GUID: {proj_guid}) 的工程量取费表计算")
|
||||||
|
|
||||||
|
print(f"所有项目划分节点的工程量取费表计算完成,结果保存在 {self.get_output_dir()} 目录")
|
||||||
|
|
||||||
|
def calculate_resource_fee_tables(self, json_file_path: str, project_name: str = None) -> None:
|
||||||
|
"""
|
||||||
|
计算人材机合价
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称,如果为None则处理所有项目
|
||||||
|
"""
|
||||||
|
# 获取工程类型
|
||||||
|
engineering_type = self.software_type.engineering_type.value + "工程"
|
||||||
|
|
||||||
|
# 在计算前应用软件特定的规则
|
||||||
|
self.apply_resource_fee_rules()
|
||||||
|
|
||||||
|
if project_name:
|
||||||
|
# 处理单个项目划分
|
||||||
|
output_file = self._calculate_resource_fees(
|
||||||
|
json_file_path,
|
||||||
|
project_name,
|
||||||
|
)
|
||||||
|
if output_file:
|
||||||
|
print(f"已完成 {project_name} 的人材机合价计算,结果保存在 {output_file}")
|
||||||
|
else:
|
||||||
|
# 处理所有项目划分
|
||||||
|
project_divisions = get_project_divisions_list(json_file_path)
|
||||||
|
print(f"找到 {len(project_divisions)} 个项目划分节点,开始处理人材机合价...")
|
||||||
|
|
||||||
|
for i, (proj_name, proj_guid) in enumerate(project_divisions, 1):
|
||||||
|
print(f"处理 {i}/{len(project_divisions)}: {proj_name} (GUID: {proj_guid})")
|
||||||
|
output_file = self._calculate_resource_fees(json_file_path, proj_name, proj_guid)
|
||||||
|
if output_file:
|
||||||
|
print(f"已完成 {proj_name} (GUID: {proj_guid}) 的人材机合价计算")
|
||||||
|
|
||||||
|
print(f"所有项目划分节点的人材机合价计算完成,结果保存在 {self.get_output_dir()} 目录")
|
||||||
|
|
||||||
|
def _calculate_quantity_fees(
|
||||||
|
self,
|
||||||
|
json_file_path: str,
|
||||||
|
project_name: str,
|
||||||
|
engineering_type: str,
|
||||||
|
project_guid: str = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
计算工程量取费表,并应用软件特定的后处理规则
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
engineering_type: 工程类型
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 输出文件路径
|
||||||
|
"""
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(self.get_output_dir(), exist_ok=True)
|
||||||
|
|
||||||
|
# 初始化BCL计算器,传递软件类型和计算类型
|
||||||
|
from 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__}")
|
||||||
|
|
||||||
|
# 调用基础计算函数,传入计算策略和项目GUID
|
||||||
|
output_file = base_calculate_quantity_fees(
|
||||||
|
json_file_path,
|
||||||
|
project_name,
|
||||||
|
engineering_type,
|
||||||
|
project_guid=project_guid,
|
||||||
|
calculation_strategy=self.calculation_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 应用软件特定的后处理规则
|
||||||
|
if output_file:
|
||||||
|
self.post_process_quantity_fees(output_file, project_name)
|
||||||
|
|
||||||
|
return output_file
|
||||||
|
|
||||||
|
def _calculate_resource_fees(
|
||||||
|
self,
|
||||||
|
json_file_path: str,
|
||||||
|
project_name: str,
|
||||||
|
project_guid: str = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
计算人材机合价,并应用软件特定的后处理规则
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
engineering_type: 工程类型
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 输出文件路径
|
||||||
|
"""
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(self.get_output_dir(), exist_ok=True)
|
||||||
|
|
||||||
|
# 初始化BCL计算器,传递软件类型和计算类型
|
||||||
|
from bcl_utils import init_bcl_calculator
|
||||||
|
|
||||||
|
init_bcl_calculator(
|
||||||
|
software_category=self.software_type.category.value,
|
||||||
|
engineering_type=self.software_type.engineering_type.value,
|
||||||
|
calculation_type="人材机",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 调用基础计算函数,传入计算策略和项目GUID
|
||||||
|
output_file = base_calculate_resource_fees(
|
||||||
|
json_file_path,
|
||||||
|
project_name,
|
||||||
|
project_guid=project_guid,
|
||||||
|
calculation_strategy=self.calculation_strategy,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 应用软件特定的后处理规则
|
||||||
|
if output_file:
|
||||||
|
self.post_process_resource_fees(output_file, project_name)
|
||||||
|
|
||||||
|
return output_file
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用软件特定的工程量取费规则(在计算前)"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用软件特定的人材机合价规则(在计算前)"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""
|
||||||
|
应用软件特定的工程量取费后处理规则
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_file: 输出文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""
|
||||||
|
应用软件特定的人材机合价后处理规则
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_file: 输出文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
"""
|
||||||
|
pass
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def equipment_calculation(json_file_path):
|
||||||
|
|
||||||
|
# 读取JSON文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 获取projectDivision
|
||||||
|
project_division = data["projectData"]["projectDivision"]
|
||||||
|
|
||||||
|
def collect_nodes_by_type(parent_name):
|
||||||
|
# 初始化五个列表,分别存储不同类型的节点
|
||||||
|
zhucai_nodes = [] # 主材
|
||||||
|
shebei_nodes = [] # 设备
|
||||||
|
rengong_nodes = [] # 人工
|
||||||
|
cailiao_nodes = [] # 材料
|
||||||
|
jixie_nodes = [] # 机械
|
||||||
|
|
||||||
|
found_parent = [False] # 使用列表作为可变引用
|
||||||
|
|
||||||
|
# 递归查找函数 - 处理嵌套结构
|
||||||
|
def find_parent_and_collect(node, target_name):
|
||||||
|
# 如果节点是字典
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 检查当前节点是否为目标父节点
|
||||||
|
node_name = node.get("项目名称", node.get("名称", ""))
|
||||||
|
if node_name == target_name:
|
||||||
|
print(f"找到目标节点: {target_name}")
|
||||||
|
# 找到目标父节点,开始收集子节点
|
||||||
|
collect_children(node)
|
||||||
|
found_parent[0] = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 处理所有可能的子节点路径
|
||||||
|
for key, value in node.items():
|
||||||
|
if find_parent_and_collect(value, target_name):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 如果节点是列表
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
if find_parent_and_collect(item, target_name):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 递归收集子节点中的不同类型节点
|
||||||
|
def collect_children(node, parent_quantity=1.0):
|
||||||
|
# 检查当前节点类型并添加到对应列表
|
||||||
|
node_type = node.get("类型")
|
||||||
|
node_name = node.get("项目名称", node.get("名称", "未知"))
|
||||||
|
node_quantity = float(node.get("数量", 1.0))
|
||||||
|
|
||||||
|
# 只有当前节点是五类特定节点之一时,才考虑其父节点数量
|
||||||
|
if node_type:
|
||||||
|
print(f"发现节点: {node_name}, 类型: {node_type}, 数量: {node_quantity}, 父节点数量: {parent_quantity}")
|
||||||
|
|
||||||
|
# 创建节点的副本,避免修改原始数据
|
||||||
|
node_copy = node.copy()
|
||||||
|
# 保存原始数量
|
||||||
|
node_copy["原始数量"] = node_quantity
|
||||||
|
# 保存父节点数量,用于后续计算
|
||||||
|
node_copy["父节点数量"] = parent_quantity
|
||||||
|
|
||||||
|
if node_type == "主材":
|
||||||
|
zhucai_nodes.append(node_copy)
|
||||||
|
elif node_type == "设备":
|
||||||
|
shebei_nodes.append(node_copy)
|
||||||
|
elif node_type == "人工":
|
||||||
|
rengong_nodes.append(node_copy)
|
||||||
|
elif node_type == "材料":
|
||||||
|
cailiao_nodes.append(node_copy)
|
||||||
|
elif node_type == "机械":
|
||||||
|
jixie_nodes.append(node_copy)
|
||||||
|
|
||||||
|
# 计算当前节点传递给子节点的数量
|
||||||
|
# 如果当前节点不是特定五类节点之一,则将其数量与父节点数量相乘传递给子节点
|
||||||
|
current_parent_quantity = (
|
||||||
|
node_quantity * parent_quantity
|
||||||
|
if node_type not in ["主材", "设备", "人工", "材料", "机械"]
|
||||||
|
else parent_quantity
|
||||||
|
)
|
||||||
|
|
||||||
|
# 递归处理children子节点
|
||||||
|
children = node.get("children", [])
|
||||||
|
for child in children:
|
||||||
|
collect_children(child, current_parent_quantity)
|
||||||
|
|
||||||
|
# 递归处理材机列表子节点
|
||||||
|
caiji_list = node.get("材机列表", [])
|
||||||
|
for item in caiji_list:
|
||||||
|
collect_children(item, current_parent_quantity)
|
||||||
|
|
||||||
|
# 从根节点开始查找目标父节点
|
||||||
|
find_parent_and_collect(project_division, parent_name)
|
||||||
|
|
||||||
|
if not found_parent[0]:
|
||||||
|
print(f"警告: 未找到名为 '{parent_name}' 的节点")
|
||||||
|
|
||||||
|
# 尝试查找相似名称的节点
|
||||||
|
all_names = []
|
||||||
|
|
||||||
|
def collect_names(node):
|
||||||
|
if isinstance(node, dict):
|
||||||
|
if "项目名称" in node:
|
||||||
|
all_names.append(node["项目名称"])
|
||||||
|
if "名称" in node:
|
||||||
|
all_names.append(node["名称"])
|
||||||
|
|
||||||
|
# 遍历所有子节点
|
||||||
|
for key, value in node.items():
|
||||||
|
collect_names(value)
|
||||||
|
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
collect_names(item)
|
||||||
|
|
||||||
|
collect_names(project_division)
|
||||||
|
|
||||||
|
# 打印一些可能的节点名称
|
||||||
|
print("可能的节点名称示例:")
|
||||||
|
unique_names = sorted(set(all_names))
|
||||||
|
for name in unique_names[:20]: # 只显示前20个,去重并排序
|
||||||
|
print(f"- {name}")
|
||||||
|
|
||||||
|
# 返回五个分类列表
|
||||||
|
return {
|
||||||
|
"主材": zhucai_nodes,
|
||||||
|
"设备": shebei_nodes,
|
||||||
|
"人工": rengong_nodes,
|
||||||
|
"材料": cailiao_nodes,
|
||||||
|
"机械": jixie_nodes,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 合并同类型节点并计算价格差异
|
||||||
|
def merge_same_nodes(nodes_dict):
|
||||||
|
merged_results = {"主材": [], "设备": [], "人工": [], "材料": [], "机械": []}
|
||||||
|
|
||||||
|
# 遍历每种类型的节点
|
||||||
|
for node_type, nodes in nodes_dict.items():
|
||||||
|
# 用于存储合并的节点
|
||||||
|
merged_nodes = {}
|
||||||
|
|
||||||
|
# 遍历每个节点
|
||||||
|
for node in nodes:
|
||||||
|
# 获取节点属性
|
||||||
|
name = node.get("名称", node.get("项目名称", "未知"))
|
||||||
|
budget_price = str(node.get("预算价不含税", "0"))
|
||||||
|
market_price = str(node.get("市场价不含税", "0"))
|
||||||
|
|
||||||
|
# 获取当前节点的数量和父节点数量
|
||||||
|
node_quantity = float(node.get("原始数量", node.get("数量", "0")))
|
||||||
|
parent_quantity = float(node.get("父节点数量", 1.0))
|
||||||
|
|
||||||
|
# 计算实际数量 = 节点数量 * 父节点数量
|
||||||
|
actual_quantity = node_quantity * parent_quantity
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"合并节点: {name}, 节点数量: {node_quantity}, 父节点数量: {parent_quantity}, 实际数量: {actual_quantity}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建唯一键以识别相同节点 - 使用名称、预算价和市场价作为键
|
||||||
|
key = f"{name}_{budget_price}_{market_price}"
|
||||||
|
|
||||||
|
# 如果已存在相同键的节点,则合并数量
|
||||||
|
if key in merged_nodes:
|
||||||
|
merged_nodes[key]["数量"] += actual_quantity
|
||||||
|
else:
|
||||||
|
# 深拷贝节点,避免修改原始数据
|
||||||
|
merged_node = node.copy()
|
||||||
|
merged_node["数量"] = actual_quantity
|
||||||
|
# 移除辅助字段
|
||||||
|
if "原始数量" in merged_node:
|
||||||
|
del merged_node["原始数量"]
|
||||||
|
if "父节点数量" in merged_node:
|
||||||
|
del merged_node["父节点数量"]
|
||||||
|
merged_nodes[key] = merged_node
|
||||||
|
|
||||||
|
# 计算合并后的预算价合价、市场价合价和价差
|
||||||
|
for key, merged_node in merged_nodes.items():
|
||||||
|
quantity = float(merged_node["数量"])
|
||||||
|
budget_price = float(merged_node.get("预算价不含税", 0))
|
||||||
|
market_price = float(merged_node.get("市场价不含税", 0))
|
||||||
|
|
||||||
|
# 计算合价
|
||||||
|
budget_total = budget_price * quantity
|
||||||
|
market_total = market_price * quantity
|
||||||
|
price_diff = market_total - budget_total
|
||||||
|
|
||||||
|
# 添加计算结果到节点属性
|
||||||
|
merged_node["预算价合价"] = round(budget_total, 2)
|
||||||
|
merged_node["市场价合价"] = round(market_total, 2)
|
||||||
|
merged_node["价差"] = round(price_diff, 2)
|
||||||
|
|
||||||
|
# 将合并后的节点添加到结果中
|
||||||
|
merged_results[node_type].append(merged_node)
|
||||||
|
|
||||||
|
return merged_results
|
||||||
|
|
||||||
|
# 打印合并结果的统计信息
|
||||||
|
def print_merge_stats(merged_results):
|
||||||
|
for node_type, nodes in merged_results.items():
|
||||||
|
total_budget = sum(node.get("预算价合价", 0) for node in nodes)
|
||||||
|
total_market = sum(node.get("市场价合价", 0) for node in nodes)
|
||||||
|
total_diff = sum(node.get("价差", 0) for node in nodes)
|
||||||
|
|
||||||
|
print(f"{node_type}节点数量: {len(nodes)}")
|
||||||
|
print(f"{node_type}总预算价合价: {round(total_budget, 2)}")
|
||||||
|
print(f"{node_type}总市场价合价: {round(total_market, 2)}")
|
||||||
|
print(f"{node_type}总价差: {round(total_diff, 2)}")
|
||||||
|
print("-" * 30)
|
||||||
|
|
||||||
|
# 主函数流程
|
||||||
|
parent_name = "基础工程材料工地运输"
|
||||||
|
result = collect_nodes_by_type(parent_name)
|
||||||
|
|
||||||
|
print("\n原始节点统计:")
|
||||||
|
print(f"主材节点数量: {len(result['主材'])}")
|
||||||
|
print(f"设备节点数量: {len(result['设备'])}")
|
||||||
|
print(f"人工节点数量: {len(result['人工'])}")
|
||||||
|
print(f"材料节点数量: {len(result['材料'])}")
|
||||||
|
print(f"机械节点数量: {len(result['机械'])}")
|
||||||
|
|
||||||
|
print("\n开始合并相同节点...")
|
||||||
|
merged_results = merge_same_nodes(result)
|
||||||
|
|
||||||
|
print("\n合并后节点统计及价格信息:")
|
||||||
|
print_merge_stats(merged_results)
|
||||||
|
|
||||||
|
# 输出样例节点详情
|
||||||
|
for node_type, nodes in merged_results.items():
|
||||||
|
if nodes:
|
||||||
|
sample_node = nodes[0]
|
||||||
|
print(f"\n{node_type}节点样例:")
|
||||||
|
for key, value in sample_node.items():
|
||||||
|
if key in [
|
||||||
|
"名称",
|
||||||
|
"项目名称",
|
||||||
|
"数量",
|
||||||
|
"预算价不含税",
|
||||||
|
"市场价不含税",
|
||||||
|
"预算价合价",
|
||||||
|
"市场价合价",
|
||||||
|
"价差",
|
||||||
|
]:
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
break
|
||||||
|
|
||||||
|
return merged_results
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
json_file_path = "主网预算/架空_clean.json"
|
||||||
|
|
||||||
|
equipment_calculation(json_file_path)
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
import re
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
class ExpressionCalculator:
|
||||||
|
def __init__(self):
|
||||||
|
self.expression = None
|
||||||
|
self.tokens = None
|
||||||
|
self.variables = None
|
||||||
|
self.ast = None
|
||||||
|
self.last_error = None
|
||||||
|
|
||||||
|
def _tokenize(self, expr):
|
||||||
|
"""将表达式字符串分解为标记列表"""
|
||||||
|
token_spec = [
|
||||||
|
("NUMBER", r"\d+(\.\d*)?"), # 整数或小数
|
||||||
|
("VARIABLE", r"@?[a-zA-Z_\u4e00-\u9fa5][\w.@\u4e00-\u9fa5]+"), # 支持带点的复合变量
|
||||||
|
("OPERATOR", r"[+\-*/()]"), # 运算符
|
||||||
|
("SKIP", r"\s+"), # 跳过空格
|
||||||
|
("MISMATCH", r"."), # 其他字符
|
||||||
|
]
|
||||||
|
|
||||||
|
tokens = []
|
||||||
|
pos = 0
|
||||||
|
while pos < len(expr):
|
||||||
|
match = None
|
||||||
|
for token_name, pattern in token_spec:
|
||||||
|
regex = re.compile(pattern)
|
||||||
|
match = regex.match(expr, pos)
|
||||||
|
if match:
|
||||||
|
value = match.group(0)
|
||||||
|
if token_name == "NUMBER":
|
||||||
|
tokens.append(("NUMBER", float(value)))
|
||||||
|
elif token_name == "VARIABLE":
|
||||||
|
tokens.append(("VARIABLE", value))
|
||||||
|
elif token_name == "OPERATOR":
|
||||||
|
tokens.append(("OPERATOR", value))
|
||||||
|
elif token_name == "SKIP":
|
||||||
|
pass # 跳过空格
|
||||||
|
else:
|
||||||
|
raise ValueError(f"非法字符: {value}")
|
||||||
|
pos = match.end()
|
||||||
|
break
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f"无法解析的字符: {expr[pos]}")
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
def _extract_variables(self):
|
||||||
|
"""提取表达式中的所有变量"""
|
||||||
|
variables = OrderedDict()
|
||||||
|
for token_type, value in self.tokens:
|
||||||
|
if token_type == "VARIABLE" and value not in variables:
|
||||||
|
variables[value] = None
|
||||||
|
return list(variables.keys())
|
||||||
|
|
||||||
|
def parse_expression(self, expression):
|
||||||
|
"""解析表达式并返回是否成功
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 解析成功返回True,失败返回False
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.expression = expression
|
||||||
|
self.last_error = None
|
||||||
|
try:
|
||||||
|
self.tokens = self._tokenize(expression)
|
||||||
|
self.variables = self._extract_variables()
|
||||||
|
self.ast = self._parse_expression()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.last_error = str(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_last_error(self):
|
||||||
|
"""获取最后发生的错误信息"""
|
||||||
|
return self.last_error
|
||||||
|
|
||||||
|
def _parse_expression(self):
|
||||||
|
"""解析表达式并构建抽象语法树(AST)"""
|
||||||
|
self.current_token = 0
|
||||||
|
return self._parse_exp()
|
||||||
|
|
||||||
|
def _parse_exp(self):
|
||||||
|
"""解析加减表达式: exp -> term { ('+'|'-') term }"""
|
||||||
|
node = self._parse_term()
|
||||||
|
while self.current_token < len(self.tokens):
|
||||||
|
token_type, op = self.tokens[self.current_token]
|
||||||
|
if token_type == "OPERATOR" and op in ("+", "-"):
|
||||||
|
self.current_token += 1
|
||||||
|
right = self._parse_term()
|
||||||
|
node = ("BINOP", op, node, right)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return node
|
||||||
|
|
||||||
|
def _parse_term(self):
|
||||||
|
"""解析乘除表达式: term -> factor { ('*'|'/') factor }"""
|
||||||
|
node = self._parse_factor()
|
||||||
|
while self.current_token < len(self.tokens):
|
||||||
|
token_type, op = self.tokens[self.current_token]
|
||||||
|
if token_type == "OPERATOR" and op in ("*", "/"):
|
||||||
|
self.current_token += 1
|
||||||
|
right = self._parse_factor()
|
||||||
|
node = ("BINOP", op, node, right)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return node
|
||||||
|
|
||||||
|
def _parse_factor(self):
|
||||||
|
"""解析基本因子: factor -> NUMBER | VARIABLE | '(' exp ')' | ('+'|'-') factor"""
|
||||||
|
if self.current_token >= len(self.tokens):
|
||||||
|
raise ValueError("表达式不完整")
|
||||||
|
|
||||||
|
token_type, value = self.tokens[self.current_token]
|
||||||
|
|
||||||
|
# 处理括号表达式
|
||||||
|
if token_type == "OPERATOR" and value == "(":
|
||||||
|
self.current_token += 1
|
||||||
|
node = self._parse_exp()
|
||||||
|
if self.current_token >= len(self.tokens) or self.tokens[self.current_token][1] != ")":
|
||||||
|
raise ValueError("缺少右括号")
|
||||||
|
self.current_token += 1
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 处理一元运算符
|
||||||
|
if token_type == "OPERATOR" and value in ("+", "-"):
|
||||||
|
self.current_token += 1
|
||||||
|
node = self.parse_factor()
|
||||||
|
return ("UNARYOP", value, node)
|
||||||
|
|
||||||
|
# 处理变量
|
||||||
|
if token_type == "VARIABLE":
|
||||||
|
self.current_token += 1
|
||||||
|
return ("VARIABLE", value)
|
||||||
|
|
||||||
|
# 处理数字
|
||||||
|
if token_type == "NUMBER":
|
||||||
|
self.current_token += 1
|
||||||
|
return ("NUMBER", value)
|
||||||
|
|
||||||
|
raise ValueError(f"语法错误: {value}")
|
||||||
|
|
||||||
|
def get_last_error(self):
|
||||||
|
"""获取最后一次解析错误信息
|
||||||
|
Returns:
|
||||||
|
str: 错误信息,如果没有错误则返回None
|
||||||
|
"""
|
||||||
|
return self.last_error
|
||||||
|
|
||||||
|
def evaluate(self, variables):
|
||||||
|
"""计算表达式的值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (bool, float) - 第一个元素表示计算是否成功,第二个元素是计算结果(失败时为None)
|
||||||
|
"""
|
||||||
|
# 确保表达式已解析
|
||||||
|
if self.ast is None:
|
||||||
|
self.last_error = "表达式尚未解析"
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# 检查变量是否齐全
|
||||||
|
missing = [var for var in self.variables if var not in variables]
|
||||||
|
if missing:
|
||||||
|
self.last_error = f"缺少变量值: {', '.join(missing)}"
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# 执行计算
|
||||||
|
try:
|
||||||
|
result = self._evaluate_node(self.ast, variables)
|
||||||
|
self.last_error = None
|
||||||
|
return True, result
|
||||||
|
except Exception as e:
|
||||||
|
self.last_error = str(e)
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def _evaluate_node(self, node, variables):
|
||||||
|
"""递归计算AST节点的值"""
|
||||||
|
node_type = node[0]
|
||||||
|
|
||||||
|
if node_type == "NUMBER":
|
||||||
|
return node[1]
|
||||||
|
|
||||||
|
if node_type == "VARIABLE":
|
||||||
|
return variables[node[1]]
|
||||||
|
|
||||||
|
if node_type == "UNARYOP":
|
||||||
|
op, child = node[1], node[2]
|
||||||
|
value = self._evaluate_node(child, variables)
|
||||||
|
return value if op == "+" else -value
|
||||||
|
|
||||||
|
if node_type == "BINOP":
|
||||||
|
_, op, left, right = node
|
||||||
|
left_val = self._evaluate_node(left, variables)
|
||||||
|
right_val = self._evaluate_node(right, variables)
|
||||||
|
|
||||||
|
if op == "+":
|
||||||
|
return left_val + right_val
|
||||||
|
if op == "-":
|
||||||
|
return left_val - right_val
|
||||||
|
if op == "*":
|
||||||
|
return left_val * right_val
|
||||||
|
if op == "/":
|
||||||
|
if right_val == 0:
|
||||||
|
raise ValueError("除数不能为零")
|
||||||
|
return left_val / right_val
|
||||||
|
|
||||||
|
raise ValueError("未知节点类型")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""返回表达式的字符串表示"""
|
||||||
|
return self.expression
|
||||||
|
|
||||||
|
|
||||||
|
# 测试代码
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 示例表达式
|
||||||
|
expr_str = "工程量.数量*工程量.人工费*工程量.人工系数*工程量.定额系数"
|
||||||
|
|
||||||
|
# 创建计算器实例
|
||||||
|
calculator = ExpressionCalculator()
|
||||||
|
parse_success = calculator.parse_expression(expr_str)
|
||||||
|
if not parse_success:
|
||||||
|
raise ValueError(f"解析表达式失败: {calculator.get_last_error()}")
|
||||||
|
success, result = calculator.evaluate()
|
||||||
|
if not success:
|
||||||
|
raise ValueError(f"计算表达式失败: {calculator.get_last_error()}")
|
||||||
|
|
||||||
|
# 获取变量列表
|
||||||
|
variables = calculator.variables
|
||||||
|
print("表达式中的变量:", variables)
|
||||||
|
|
||||||
|
# 为变量赋值
|
||||||
|
values = {"工程量.数量": 10, "工程量.人工费": 100, "工程量.人工系数": 1.5, "工程量.定额系数": 0.8}
|
||||||
|
|
||||||
|
# 计算结果
|
||||||
|
success, result = calculator.evaluate(values)
|
||||||
|
if success:
|
||||||
|
print(f"计算结果: {result}")
|
||||||
|
else:
|
||||||
|
print(f"计算失败: {calculator.get_last_error()}")
|
||||||
|
|
||||||
|
# 测试带括号和混合运算的表达式
|
||||||
|
complex_expr = "(工程量.数量 + 5) * (工程量.人工费 - 20) / 2"
|
||||||
|
complex_calc = ExpressionCalculator()
|
||||||
|
parse_success = complex_calc.parse_expression(complex_expr)
|
||||||
|
if not parse_success:
|
||||||
|
raise ValueError(f"解析复杂表达式失败: {complex_calc.get_last_error()}")
|
||||||
|
success, result = complex_calc.evaluate()
|
||||||
|
if not success:
|
||||||
|
raise ValueError(f"计算复杂表达式失败: {complex_calc.get_last_error()}")
|
||||||
|
print("\n复杂表达式:", complex_expr)
|
||||||
|
print("变量列表:", complex_calc.variables)
|
||||||
|
|
||||||
|
success, complex_result = complex_calc.evaluate({"工程量.数量": 15, "工程量.人工费": 120})
|
||||||
|
if success:
|
||||||
|
print(f"计算结果: {complex_result}")
|
||||||
|
else:
|
||||||
|
print(f"计算失败: {complex_calc.get_last_error()}")
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Dict, List, Any, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
def read_json_file(file_path: str) -> Dict:
|
||||||
|
"""读取JSON文件"""
|
||||||
|
try:
|
||||||
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取JSON文件时出错: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def find_leaf_project_divisions(
|
||||||
|
node: Any, project_name: str = None, path: List = None
|
||||||
|
) -> List[Tuple[str, str, str, List[str]]]:
|
||||||
|
"""
|
||||||
|
递归查找最子集的项目划分节点
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 当前节点
|
||||||
|
project_name: 当前工程名称
|
||||||
|
path: 当前路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[str, str, str, List[str]]]: 项目划分节点名称、调差类型、GUID和路径的列表
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
path = []
|
||||||
|
|
||||||
|
# 如果节点是字典
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 判断是否是项目划分节点
|
||||||
|
is_project_division = node.get("type") == "项目划分"
|
||||||
|
has_children = "children" in node and isinstance(node["children"], list) and node["children"]
|
||||||
|
|
||||||
|
if is_project_division:
|
||||||
|
# 获取GUID
|
||||||
|
node_guid = node.get("GUID") or node.get("guid", "")
|
||||||
|
# 保存当前节点的名称到路径
|
||||||
|
current_path = path + [node.get("项目名称", "未命名")]
|
||||||
|
|
||||||
|
# 如果没有子节点或子节点不是项目划分节点,则认为是最子集的项目划分节点
|
||||||
|
if not has_children or not any(child.get("type") == "项目划分" for child in node["children"]):
|
||||||
|
results.append((node.get("项目名称", "未命名"), node_guid, current_path))
|
||||||
|
|
||||||
|
# 递归处理子节点
|
||||||
|
if has_children:
|
||||||
|
for child in node["children"]:
|
||||||
|
results.extend(find_leaf_project_divisions(child, project_name, current_path))
|
||||||
|
else:
|
||||||
|
# 遍历所有键值对
|
||||||
|
for key, value in node.items():
|
||||||
|
# 如果是工程名称
|
||||||
|
if isinstance(value, dict) and key != "children":
|
||||||
|
new_project_name = key
|
||||||
|
# 遍历调差类型
|
||||||
|
for adj_type, adj_value in value.items():
|
||||||
|
if isinstance(adj_value, list):
|
||||||
|
for item in adj_value:
|
||||||
|
results.extend(
|
||||||
|
find_leaf_project_divisions(item, new_project_name, path + [new_project_name])
|
||||||
|
)
|
||||||
|
# 处理其他可能的字典或列表
|
||||||
|
elif isinstance(value, (dict, list)):
|
||||||
|
results.extend(find_leaf_project_divisions(value, project_name, path))
|
||||||
|
|
||||||
|
# 如果节点是列表
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
results.extend(find_leaf_project_divisions(item, project_name, path))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def find_all_project_divisions(json_file_path: str) -> List[Tuple[str, str, List[str]]]:
|
||||||
|
"""
|
||||||
|
查找JSON文件中所有最子集的项目划分节点和对应的调差类型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[str, str, List[str]]]: 项目划分节点名称、调差类型和路径的列表
|
||||||
|
"""
|
||||||
|
# 读取JSON文件
|
||||||
|
data = read_json_file(json_file_path)
|
||||||
|
|
||||||
|
# 获取projectDivision
|
||||||
|
project_division = data.get("projectData", {}).get("projectDivision", {})
|
||||||
|
|
||||||
|
# 查找所有最子集的项目划分节点
|
||||||
|
results = find_leaf_project_divisions(project_division)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def process_project_and_save(json_file_path: str, project_name: str, output_dir: str = "计算结果") -> str:
|
||||||
|
"""
|
||||||
|
处理指定的项目划分和调差类型,计算费用并保存结果
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目划分名称
|
||||||
|
output_dir: 输出目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 输出文件路径
|
||||||
|
"""
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 设置输出文件名
|
||||||
|
output_file = os.path.join(output_dir, f"{project_name}.json")
|
||||||
|
|
||||||
|
# 返回输出文件路径
|
||||||
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
|
def print_project_divisions(results: List[Tuple[str, str, List[str]]]):
|
||||||
|
"""打印项目划分节点和调差类型"""
|
||||||
|
print(f"找到 {len(results)} 个最子集项目划分节点:")
|
||||||
|
for i, (name, adj_type, path) in enumerate(results, 1):
|
||||||
|
path_str = " > ".join(path)
|
||||||
|
print(f"{i}. 项目划分: {name}")
|
||||||
|
print(f" 调差类型: {adj_type}")
|
||||||
|
print(f" 路径: {path_str}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_divisions_list(json_file_path: str) -> List[Tuple[str, str, str]]:
|
||||||
|
"""
|
||||||
|
获取JSON文件中所有项目划分节点和调差类型的列表,返回名称、调差类型和GUID
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[str, str, str]]: 项目划分节点名称、调差类型和GUID的列表
|
||||||
|
"""
|
||||||
|
results = find_all_project_divisions(json_file_path)
|
||||||
|
# 返回项目名称、调差类型和GUID,不包括路径
|
||||||
|
return [(name, guid) for name, guid, _ in results]
|
||||||
@@ -0,0 +1,779 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from copy import deepcopy
|
||||||
|
from typing import Dict, Any, Optional, Set
|
||||||
|
|
||||||
|
# 添加全局缓存
|
||||||
|
_bill_node_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def find_project_division_node(node, project_name, project_guid=None, result=None):
|
||||||
|
"""递归查找指定项目名称的项目划分节点,可选匹配GUID"""
|
||||||
|
if result is not None and result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 检查是否是项目划分节点
|
||||||
|
is_project_division = node.get("type") == "项目划分"
|
||||||
|
# 检查项目名称是否匹配
|
||||||
|
name_matches = node.get("项目名称") == project_name
|
||||||
|
# 检查GUID是否匹配(如果提供了GUID)
|
||||||
|
guid_matches = True
|
||||||
|
if project_guid:
|
||||||
|
node_guid = node.get("GUID") or node.get("guid")
|
||||||
|
guid_matches = node_guid == project_guid
|
||||||
|
|
||||||
|
# 如果是项目划分节点,且名称和GUID都匹配(或没有提供GUID)
|
||||||
|
if is_project_division and name_matches and guid_matches:
|
||||||
|
return [node]
|
||||||
|
|
||||||
|
# 递归查找子节点
|
||||||
|
for key, value in node.items():
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
result = find_project_division_node(value, project_name, project_guid, result)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
if isinstance(item, (dict, list)):
|
||||||
|
result = find_project_division_node(item, project_name, project_guid, result)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return result or []
|
||||||
|
|
||||||
|
|
||||||
|
def find_cost_table(cost_setting, table_name):
|
||||||
|
"""在costSetting中查找指定名称的取费表"""
|
||||||
|
if isinstance(cost_setting, dict):
|
||||||
|
if cost_setting.get("name") == table_name:
|
||||||
|
return cost_setting
|
||||||
|
|
||||||
|
for key, value in cost_setting.items():
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
result = find_cost_table(value, table_name)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif isinstance(cost_setting, list):
|
||||||
|
for item in cost_setting:
|
||||||
|
if isinstance(item, (dict, list)):
|
||||||
|
result = find_cost_table(item, table_name)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def map_quantity_node_types(node):
|
||||||
|
"""映射工程量节点的类型和费用类型"""
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 复制节点以避免修改原始数据
|
||||||
|
node_copy = deepcopy(node)
|
||||||
|
|
||||||
|
# 映射类型字段
|
||||||
|
if "类型" in node_copy:
|
||||||
|
type_mapping = {"0": "定额", "1": "主材", "5": "设备"}
|
||||||
|
if node_copy["类型"] in type_mapping:
|
||||||
|
node_copy["类型"] = type_mapping[node_copy["类型"]]
|
||||||
|
|
||||||
|
# 映射费用类型字段
|
||||||
|
if "费用类型" in node_copy:
|
||||||
|
if node_copy["费用类型"] == "0":
|
||||||
|
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"]]
|
||||||
|
|
||||||
|
return node_copy
|
||||||
|
|
||||||
|
|
||||||
|
def map_resource_node_types(node):
|
||||||
|
"""映射人材机节点的类型"""
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return 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["类型"]]
|
||||||
|
|
||||||
|
# 递归处理子节点
|
||||||
|
if "children" in node_copy and node_copy["children"]:
|
||||||
|
node_copy["children"] = [map_resource_node_types(child) for child in node_copy["children"]]
|
||||||
|
|
||||||
|
return node_copy
|
||||||
|
|
||||||
|
|
||||||
|
def extract_resource_nodes(node, parent_id=None):
|
||||||
|
"""
|
||||||
|
提取人材机节点并保持父子结构
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 节点
|
||||||
|
parent_id: 父级节点ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 提取的人材机节点列表,每个节点包含parent_id字段
|
||||||
|
"""
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return [] # 返回空列表而不是None,便于后续处理
|
||||||
|
|
||||||
|
# 获取当前节点ID
|
||||||
|
current_id = node.get("id")
|
||||||
|
resource_nodes = []
|
||||||
|
|
||||||
|
# 检查是否是人材机节点(支持字符串和数字类型)
|
||||||
|
node_type = node.get("类型")
|
||||||
|
is_resource_node = False
|
||||||
|
|
||||||
|
# 同时支持数字类型和字符串类型
|
||||||
|
if node_type in ["人工", "材料", "机械", "2", "3", "4"]:
|
||||||
|
is_resource_node = True
|
||||||
|
|
||||||
|
# 复制节点但不包含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
|
||||||
|
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)
|
||||||
|
|
||||||
|
return resource_nodes
|
||||||
|
|
||||||
|
|
||||||
|
def process_project_children(children):
|
||||||
|
"""处理项目子节点,分离工程量节点和人材机节点,支持嵌套的工程量节点"""
|
||||||
|
if not children:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
quantity_nodes = []
|
||||||
|
resource_nodes = []
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
# 对于工程量节点,深拷贝用于工程量树
|
||||||
|
quantity_node = deepcopy(child)
|
||||||
|
|
||||||
|
# 应用工程量节点类型映射
|
||||||
|
quantity_node = map_quantity_node_types(quantity_node)
|
||||||
|
|
||||||
|
# 提取人材机节点 - extract_resource_nodes会递归提取所有层级的人材机节点
|
||||||
|
resources = extract_resource_nodes(child)
|
||||||
|
if resources:
|
||||||
|
# 应用人材机节点类型映射
|
||||||
|
resources = [map_resource_node_types(resource) for resource in resources]
|
||||||
|
resource_nodes.extend(resources)
|
||||||
|
|
||||||
|
# 从工程量节点中移除材机列表
|
||||||
|
if "材机列表" in quantity_node:
|
||||||
|
del quantity_node["材机列表"]
|
||||||
|
|
||||||
|
# 清理子节点中的人材机节点
|
||||||
|
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"])
|
||||||
|
|
||||||
|
# 更新quantity_node的children
|
||||||
|
if sub_quantity_nodes:
|
||||||
|
quantity_node["children"] = sub_quantity_nodes
|
||||||
|
else:
|
||||||
|
quantity_node.pop("children", None) # 使用pop安全删除
|
||||||
|
|
||||||
|
# 合并子节点中提取的资源节点
|
||||||
|
if sub_resource_nodes:
|
||||||
|
resource_nodes.extend(sub_resource_nodes)
|
||||||
|
|
||||||
|
# 添加到工程量节点列表
|
||||||
|
quantity_nodes.append(quantity_node)
|
||||||
|
|
||||||
|
return (quantity_nodes if quantity_nodes else None, resource_nodes if resource_nodes else None)
|
||||||
|
|
||||||
|
|
||||||
|
def clean_resource_nodes_from_quantity(node):
|
||||||
|
"""递归清理工程量节点中的人材机节点"""
|
||||||
|
if not isinstance(node, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 清理材机列表
|
||||||
|
if "材机列表" in node:
|
||||||
|
del node["材机列表"]
|
||||||
|
|
||||||
|
# 清理子节点中的人材机节点
|
||||||
|
if "children" in node and node["children"]:
|
||||||
|
# 过滤掉人材机类型的子节点
|
||||||
|
node["children"] = [
|
||||||
|
c for c in node["children"] if not (isinstance(c, dict) and c.get("类型") in ["人工", "材料", "机械"])
|
||||||
|
]
|
||||||
|
|
||||||
|
# 递归清理剩余子节点
|
||||||
|
for child in node["children"]:
|
||||||
|
clean_resource_nodes_from_quantity(child)
|
||||||
|
|
||||||
|
# 如果没有子节点了,删除children属性
|
||||||
|
if not node["children"]:
|
||||||
|
del node["children"]
|
||||||
|
|
||||||
|
|
||||||
|
def load_project_data(json_file_path, project_name, project_guid=None):
|
||||||
|
"""加载JSON数据并获取目标项目节点"""
|
||||||
|
try:
|
||||||
|
# 读取JSON文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 获取projectData中的costSetting和projectDivision
|
||||||
|
project_data = data.get("projectData", {})
|
||||||
|
cost_setting = project_data.get("costSetting", {})
|
||||||
|
project_division = project_data.get("projectDivision", {})
|
||||||
|
|
||||||
|
# 查找指定项目名称和GUID的节点
|
||||||
|
target_nodes = find_project_division_node(project_division, project_name, project_guid)
|
||||||
|
|
||||||
|
if not target_nodes:
|
||||||
|
if project_guid:
|
||||||
|
print(f"未找到项目名称为 '{project_name}' 且GUID为 '{project_guid}' 的节点")
|
||||||
|
else:
|
||||||
|
print(f"未找到项目名称为 '{project_name}' 的节点")
|
||||||
|
return None, None, None, None, None
|
||||||
|
|
||||||
|
# 获取找到的节点
|
||||||
|
target_node = target_nodes[0]
|
||||||
|
|
||||||
|
return data, project_data, cost_setting, project_division, target_node
|
||||||
|
except Exception as e:
|
||||||
|
print(f"加载项目数据时出错: {e}")
|
||||||
|
return None, None, None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_cost_table_children(json_file_path, project_name, project_guid=None):
|
||||||
|
"""获取取费表子节点"""
|
||||||
|
try:
|
||||||
|
# 加载项目数据,传递project_guid参数
|
||||||
|
data, project_data, cost_setting, project_division, target_node = load_project_data(
|
||||||
|
json_file_path, project_name, project_guid
|
||||||
|
)
|
||||||
|
|
||||||
|
if not target_node:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 获取取费表名称
|
||||||
|
fee_table_name = target_node.get("取费表")
|
||||||
|
if not fee_table_name:
|
||||||
|
print(f"目标项目划分节点中没有'取费表'字段")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 查找对应的取费表
|
||||||
|
cost_table = find_cost_table(cost_setting, fee_table_name)
|
||||||
|
cost_table_children = cost_table.get("children", None) if cost_table else None
|
||||||
|
|
||||||
|
return cost_table_children
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取取费表子节点时出错: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid=None):
|
||||||
|
"""
|
||||||
|
获取工程量节点
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
engineering_type: 工程类型
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 工程量节点列表,如果没有找到项目划分节点则返回None,如果找到项目划分节点但没有工程量节点则返回空列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 加载项目数据,传递project_guid参数
|
||||||
|
data, project_data, cost_setting, project_division, target_node = load_project_data(
|
||||||
|
json_file_path, project_name, project_guid
|
||||||
|
)
|
||||||
|
|
||||||
|
if not target_node:
|
||||||
|
return None # 没有找到项目划分节点
|
||||||
|
|
||||||
|
# 处理项目子节点
|
||||||
|
project_children = target_node.get("children", None)
|
||||||
|
if not project_children:
|
||||||
|
print(f"项目 '{project_name}' (GUID: {project_guid}) 没有子节点(工程量节点)")
|
||||||
|
return [] # 找到项目划分节点,但没有子节点(工程量节点)
|
||||||
|
|
||||||
|
# 如果是清单工程,需要先找到清单节点,然后获取其下的工程量节点
|
||||||
|
if engineering_type == "清单工程":
|
||||||
|
quantity_nodes = []
|
||||||
|
|
||||||
|
print(f"开始处理清单工程,项目名称: {project_name}")
|
||||||
|
|
||||||
|
# 递归查找真正的清单节点
|
||||||
|
def find_true_bill_nodes(node_list, parent_path=""):
|
||||||
|
result = []
|
||||||
|
for node in node_list:
|
||||||
|
# 检查是否是清单节点
|
||||||
|
is_bill_node = (
|
||||||
|
node.get("类型") == "8"
|
||||||
|
or node.get("类型") == "清单"
|
||||||
|
or node.get("type") == "8"
|
||||||
|
or node.get("type") == "清单"
|
||||||
|
or "清单名称" in node
|
||||||
|
or "清单全码" in node
|
||||||
|
)
|
||||||
|
|
||||||
|
node_name = node.get("清单名称", node.get("项目名称", "未命名"))
|
||||||
|
node_id = node.get("id", node.get("GUID", "未知ID"))
|
||||||
|
current_path = f"{parent_path}/{node_name}" if parent_path else node_name
|
||||||
|
|
||||||
|
if is_bill_node:
|
||||||
|
print(f"找到清单节点: ID={node_id}, 名称={node_name}, 路径={current_path}")
|
||||||
|
# 检查清单节点是否有取费表名称
|
||||||
|
has_fee_table = "取费表名称" in node or "取费表" in node
|
||||||
|
if has_fee_table:
|
||||||
|
fee_table_name = node.get("取费表名称", node.get("取费表", "未知"))
|
||||||
|
print(f"清单节点有取费表: {fee_table_name}")
|
||||||
|
# 添加到缓存
|
||||||
|
if node_id != "未知ID":
|
||||||
|
_bill_node_cache[node_id] = node
|
||||||
|
print(f"缓存清单节点: ID={node_id}")
|
||||||
|
result.append(node)
|
||||||
|
else:
|
||||||
|
print(f"警告:清单节点没有取费表名称: {node_name}")
|
||||||
|
|
||||||
|
# 递归处理子节点
|
||||||
|
if "children" in node and node["children"]:
|
||||||
|
result.extend(find_true_bill_nodes(node["children"], current_path))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 查找所有真正的清单节点
|
||||||
|
bill_nodes = find_true_bill_nodes(project_children)
|
||||||
|
print(f"找到 {len(bill_nodes)} 个有效清单节点")
|
||||||
|
|
||||||
|
# 处理每个清单节点
|
||||||
|
for bill_node in bill_nodes:
|
||||||
|
bill_id = bill_node.get("id") or bill_node.get("GUID")
|
||||||
|
bill_name = bill_node.get("清单名称", bill_node.get("项目名称", "未命名清单"))
|
||||||
|
bill_children = bill_node.get("children", [])
|
||||||
|
|
||||||
|
if bill_children:
|
||||||
|
# 处理清单节点的子节点,获取工程量节点
|
||||||
|
bill_quantity_nodes, _ = process_project_children(bill_children)
|
||||||
|
|
||||||
|
if bill_quantity_nodes:
|
||||||
|
print(f"清单节点 '{bill_name}' 下找到 {len(bill_quantity_nodes)} 个工程量节点")
|
||||||
|
|
||||||
|
# 为每个工程量节点设置清单节点信息
|
||||||
|
for node in bill_quantity_nodes:
|
||||||
|
# 存储关键信息而不是整个对象
|
||||||
|
node["bill_id"] = bill_id
|
||||||
|
node["bill_name"] = bill_name
|
||||||
|
node["取费表名称"] = bill_node.get("取费表名称", bill_node.get("取费表", ""))
|
||||||
|
# 设置parent_id以保持兼容性
|
||||||
|
node["parent_id"] = bill_id
|
||||||
|
print(f"设置工程量节点 '{node.get('项目名称', '未命名')}' 的清单节点信息")
|
||||||
|
|
||||||
|
# 添加这一行,将完整的清单节点添加到工程量节点中
|
||||||
|
node["bill_node"] = bill_node
|
||||||
|
|
||||||
|
quantity_nodes.extend(bill_quantity_nodes)
|
||||||
|
else:
|
||||||
|
print(f"清单节点 '{bill_name}' 下未找到工程量节点")
|
||||||
|
else:
|
||||||
|
print(f"清单节点 '{bill_name}' 没有子节点")
|
||||||
|
|
||||||
|
# 如果没有找到任何工程量节点,尝试直接获取定额节点
|
||||||
|
if not quantity_nodes:
|
||||||
|
print("未找到清单节点下的工程量节点,尝试直接获取定额节点...")
|
||||||
|
|
||||||
|
def find_quota_nodes(node_list):
|
||||||
|
result = []
|
||||||
|
for node in node_list:
|
||||||
|
# 检查是否是定额节点
|
||||||
|
is_quota_node = node.get("类型") == "定额" or node.get("type") == "定额"
|
||||||
|
|
||||||
|
if is_quota_node:
|
||||||
|
result.append(node)
|
||||||
|
|
||||||
|
# 递归处理子节点
|
||||||
|
if "children" in node and node["children"]:
|
||||||
|
result.extend(find_quota_nodes(node["children"]))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 查找所有定额节点
|
||||||
|
quota_nodes = find_quota_nodes(project_children)
|
||||||
|
if quota_nodes:
|
||||||
|
print(f"找到 {len(quota_nodes)} 个定额节点")
|
||||||
|
|
||||||
|
# 检查这些定额节点是否有清单节点信息
|
||||||
|
for node in quota_nodes:
|
||||||
|
parent_node = None
|
||||||
|
parent_id = node.get("parent_id")
|
||||||
|
|
||||||
|
# 在项目中查找父节点
|
||||||
|
def find_node_by_id(node_list, node_id):
|
||||||
|
for n in node_list:
|
||||||
|
if n.get("id") == node_id or n.get("GUID") == node_id:
|
||||||
|
return n
|
||||||
|
if "children" in n and n["children"]:
|
||||||
|
found = find_node_by_id(n["children"], node_id)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
if parent_id:
|
||||||
|
parent_node = find_node_by_id(project_children, parent_id)
|
||||||
|
|
||||||
|
# 如果找到父节点且是清单节点,使用其信息
|
||||||
|
if parent_node:
|
||||||
|
is_bill_node = (
|
||||||
|
parent_node.get("类型") == "8"
|
||||||
|
or parent_node.get("类型") == "清单"
|
||||||
|
or parent_node.get("type") == "8"
|
||||||
|
or parent_node.get("type") == "清单"
|
||||||
|
or "清单名称" in parent_node
|
||||||
|
or "清单全码" in parent_node
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_bill_node:
|
||||||
|
bill_id = parent_node.get("id") or parent_node.get("GUID")
|
||||||
|
bill_name = parent_node.get("清单名称", parent_node.get("项目名称", "未命名清单"))
|
||||||
|
|
||||||
|
node["bill_id"] = bill_id
|
||||||
|
node["bill_name"] = bill_name
|
||||||
|
node["取费表名称"] = parent_node.get("取费表名称", parent_node.get("取费表", ""))
|
||||||
|
# 设置parent_id以保持兼容性
|
||||||
|
node["parent_id"] = bill_id
|
||||||
|
print(f"为定额节点 '{node.get('项目名称', '未命名')}' 设置清单节点信息")
|
||||||
|
|
||||||
|
quantity_nodes.extend(quota_nodes)
|
||||||
|
|
||||||
|
return quantity_nodes
|
||||||
|
else:
|
||||||
|
# 预算工程 - 直接获取工程量节点
|
||||||
|
quantity_nodes, _ = process_project_children(project_children)
|
||||||
|
|
||||||
|
# 如果没有工程量节点,返回空列表而不是None
|
||||||
|
if not quantity_nodes:
|
||||||
|
print(f"项目 '{project_name}' (GUID: {project_guid}) 没有工程量节点")
|
||||||
|
return []
|
||||||
|
|
||||||
|
return quantity_nodes
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取工程量节点时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc() # 打印详细错误堆栈
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_resource_nodes(json_file_path, project_name, project_guid=None):
|
||||||
|
"""
|
||||||
|
获取人材机节点
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 人材机节点列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 加载项目数据,传递project_guid参数
|
||||||
|
data, project_data, cost_setting, project_division, target_node = load_project_data(
|
||||||
|
json_file_path, project_name, project_guid
|
||||||
|
)
|
||||||
|
|
||||||
|
if not target_node:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 获取项目子节点
|
||||||
|
project_children = target_node.get("children", None)
|
||||||
|
if not project_children:
|
||||||
|
print(f"项目 '{project_name}' (GUID: {project_guid}) 没有子节点")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 提取人材机节点
|
||||||
|
resource_nodes = []
|
||||||
|
for child in project_children:
|
||||||
|
# 提取人材机节点并保持父子结构
|
||||||
|
nodes = extract_resource_nodes(child)
|
||||||
|
if nodes:
|
||||||
|
resource_nodes.extend(nodes)
|
||||||
|
|
||||||
|
# 映射节点类型
|
||||||
|
resource_nodes = [map_resource_node_types(node) for node in resource_nodes]
|
||||||
|
|
||||||
|
return resource_nodes
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取人材机节点时出错: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_classified_resource_nodes(json_file_path, project_name, project_guid=None):
|
||||||
|
"""
|
||||||
|
获取分类后的人材机节点
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (人工节点列表, 材料节点列表, 机械节点列表),每个列表包含(节点, 父级ID)元组
|
||||||
|
"""
|
||||||
|
# 获取所有人材机节点,传递project_guid参数
|
||||||
|
resource_nodes = get_resource_nodes(json_file_path, project_name, project_guid)
|
||||||
|
if not resource_nodes:
|
||||||
|
return [], [], []
|
||||||
|
|
||||||
|
# 分类存储
|
||||||
|
labor_nodes = [] # 人工节点
|
||||||
|
material_nodes = [] # 材料节点
|
||||||
|
machine_nodes = [] # 机械节点
|
||||||
|
|
||||||
|
# 递归函数,用于处理节点及其子节点
|
||||||
|
def process_node(node):
|
||||||
|
node_type = node.get("类型")
|
||||||
|
parent_id = node.get("parent_id")
|
||||||
|
|
||||||
|
# 同时支持数字类型和字符串类型
|
||||||
|
if node_type in ["人工", "2"]:
|
||||||
|
node_type = "人工" # 统一转换为字符串类型
|
||||||
|
labor_nodes.append((node, parent_id))
|
||||||
|
elif node_type in ["材料", "3"]:
|
||||||
|
node_type = "材料" # 统一转换为字符串类型
|
||||||
|
material_nodes.append((node, parent_id))
|
||||||
|
elif node_type in ["机械", "4"]:
|
||||||
|
node_type = "机械" # 统一转换为字符串类型
|
||||||
|
machine_nodes.append((node, parent_id))
|
||||||
|
|
||||||
|
# 更新类型字段
|
||||||
|
node["类型"] = node_type
|
||||||
|
|
||||||
|
# 处理子节点
|
||||||
|
if "children" in node and node["children"]:
|
||||||
|
for child in node["children"]:
|
||||||
|
# 确保子节点有父级ID
|
||||||
|
if "parent_id" not in child:
|
||||||
|
child["parent_id"] = node.get("id")
|
||||||
|
process_node(child)
|
||||||
|
|
||||||
|
# 处理所有节点
|
||||||
|
for node in resource_nodes:
|
||||||
|
process_node(node)
|
||||||
|
|
||||||
|
return labor_nodes, material_nodes, machine_nodes
|
||||||
|
|
||||||
|
|
||||||
|
def find_bill_node_by_id(node, bill_id, result=None):
|
||||||
|
"""递归查找指定ID的清单节点"""
|
||||||
|
if result is not None and result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 检查当前节点是否是目标清单节点
|
||||||
|
node_id_matches = node.get("id") == bill_id or node.get("GUID") == bill_id
|
||||||
|
|
||||||
|
if node_id_matches:
|
||||||
|
# 检查是否是清单节点
|
||||||
|
is_bill_node = (
|
||||||
|
node.get("类型") == "8"
|
||||||
|
or node.get("类型") == "清单"
|
||||||
|
or node.get("type") == "8"
|
||||||
|
or node.get("type") == "清单"
|
||||||
|
or "清单名称" in node
|
||||||
|
or "清单全码" in node
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_bill_node:
|
||||||
|
print(f"找到清单节点 ID={bill_id}, 类型={node.get('类型', node.get('type', '未知'))}")
|
||||||
|
return [node]
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"找到ID匹配但不是清单节点的节点: ID={bill_id}, 类型={node.get('类型', node.get('type', '未知'))}"
|
||||||
|
)
|
||||||
|
# 不返回非清单节点
|
||||||
|
|
||||||
|
# 递归查找子节点
|
||||||
|
if "children" in node and isinstance(node["children"], list):
|
||||||
|
for child in node["children"]:
|
||||||
|
result = find_bill_node_by_id(child, bill_id, result)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 检查其他字段
|
||||||
|
for key, value in node.items():
|
||||||
|
if key != "children" and isinstance(value, (dict, list)):
|
||||||
|
result = find_bill_node_by_id(value, bill_id, result)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
if isinstance(item, (dict, list)):
|
||||||
|
result = find_bill_node_by_id(item, bill_id, result)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return result or []
|
||||||
|
|
||||||
|
|
||||||
|
def get_bill_cost_table(json_file_path, bill_id):
|
||||||
|
"""获取清单节点的取费表子节点"""
|
||||||
|
try:
|
||||||
|
# 读取JSON文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 获取projectData中的costSetting和projectDivision
|
||||||
|
project_data = data.get("projectData", {})
|
||||||
|
cost_setting = project_data.get("costSetting", {})
|
||||||
|
project_division = project_data.get("projectDivision", {})
|
||||||
|
|
||||||
|
print(f"正在查找清单节点ID: {bill_id}")
|
||||||
|
|
||||||
|
# 查找指定ID的清单节点
|
||||||
|
bill_nodes = find_bill_node_by_id(project_division, bill_id)
|
||||||
|
|
||||||
|
if not bill_nodes:
|
||||||
|
print(f"未找到ID为 '{bill_id}' 的清单节点")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 获取找到的节点
|
||||||
|
bill_node = bill_nodes[0]
|
||||||
|
|
||||||
|
# 获取取费表名称或ID - 尝试多种可能的字段名
|
||||||
|
fee_table_name = bill_node.get("取费表名称") or bill_node.get("取费表") or bill_node.get("费率表")
|
||||||
|
|
||||||
|
if not fee_table_name:
|
||||||
|
print(f"清单节点中没有'取费表名称'字段")
|
||||||
|
# 打印节点信息以便调试
|
||||||
|
print(f"清单节点字段: {', '.join(bill_node.keys())}")
|
||||||
|
|
||||||
|
# 尝试从父级节点获取取费表名称
|
||||||
|
if "parent_id" in bill_node:
|
||||||
|
parent_nodes = find_bill_node_by_id(project_division, bill_node["parent_id"])
|
||||||
|
if parent_nodes:
|
||||||
|
parent_node = parent_nodes[0]
|
||||||
|
fee_table_name = (
|
||||||
|
parent_node.get("取费表名称") or parent_node.get("取费表") or parent_node.get("费率表")
|
||||||
|
)
|
||||||
|
if fee_table_name:
|
||||||
|
print(f"从父级节点获取到取费表名称: {fee_table_name}")
|
||||||
|
|
||||||
|
if not fee_table_name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f"找到取费表名称: {fee_table_name}")
|
||||||
|
|
||||||
|
# 使用相同的函数查找取费表 - 与预算工程保持一致
|
||||||
|
cost_table = find_cost_table(cost_setting, fee_table_name)
|
||||||
|
|
||||||
|
if not cost_table:
|
||||||
|
print(f"未找到取费表 '{fee_table_name}'")
|
||||||
|
return None
|
||||||
|
|
||||||
|
cost_table_children = cost_table.get("children", None)
|
||||||
|
return cost_table_children
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取清单节点取费表时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc() # 打印详细错误堆栈
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_bill_node_by_id(bill_id: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
根据ID从缓存中获取清单节点
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bill_id: 清单节点ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 清单节点数据
|
||||||
|
"""
|
||||||
|
# 清理ID格式
|
||||||
|
clean_bill_id = str(bill_id).strip("{}").upper()
|
||||||
|
|
||||||
|
# 在缓存中查找
|
||||||
|
for cached_id, node in _bill_node_cache.items():
|
||||||
|
if str(cached_id).strip("{}").upper() == clean_bill_id:
|
||||||
|
print(f"从缓存中获取清单节点: {node.get('清单名称', '未命名')}")
|
||||||
|
return node
|
||||||
|
|
||||||
|
print(f"在缓存中未找到ID为 {bill_id} 的清单节点")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# # 测试代码
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# json_file_path = os.path.join("技改预算", "架线.json")
|
||||||
|
# project_name = "基础工程材料工地运输"
|
||||||
|
# adjustment_type = "拆除"
|
||||||
|
|
||||||
|
# # 获取并输出取费表子节点
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# # 获取并输出工程量节点
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# # 获取并输出分类后的人材机节点
|
||||||
|
# labor_nodes, material_nodes, machine_nodes = get_classified_resource_nodes(
|
||||||
|
# json_file_path, project_name, adjustment_type
|
||||||
|
# )
|
||||||
|
|
||||||
|
# print("\n人工节点列表:")
|
||||||
|
# for node, parent_id in labor_nodes:
|
||||||
|
# print(f"节点名称: {node.get('名称')}, 父级ID: {parent_id}")
|
||||||
|
|
||||||
|
# print("\n材料节点列表:")
|
||||||
|
# for node, parent_id in material_nodes:
|
||||||
|
# print(f"节点名称: {node.get('名称')}, 父级ID: {parent_id}")
|
||||||
|
|
||||||
|
# print("\n机械节点列表:")
|
||||||
|
# for node, parent_id in machine_nodes:
|
||||||
|
# print(f"节点名称: {node.get('名称')}, 父级ID: {parent_id}")
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
"""
|
||||||
|
第二步:计算bcl结果
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
from typing import Dict, List, Any, Optional, Tuple
|
||||||
|
from equipment_calculation.software_types import (
|
||||||
|
get_software_type,
|
||||||
|
SoftwareCategory,
|
||||||
|
EngineeringType,
|
||||||
|
ALL_SOFTWARE_TYPES,
|
||||||
|
)
|
||||||
|
from equipment_calculation.software_calculators import get_calculator
|
||||||
|
|
||||||
|
|
||||||
|
def parse_arguments():
|
||||||
|
"""解析命令行参数"""
|
||||||
|
parser = argparse.ArgumentParser(description="工程量取费和人材机合价计算程序")
|
||||||
|
|
||||||
|
# 软件类型参数
|
||||||
|
parser.add_argument(
|
||||||
|
"--category", choices=["主网", "配网", "技改"], default="主网", help="软件类别(主网/配网/技改)"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("--engineering-type", choices=["预算", "清单"], default="清单", help="工程类型(预算/清单)")
|
||||||
|
|
||||||
|
# 计算类型参数
|
||||||
|
parser.add_argument(
|
||||||
|
"--calculate",
|
||||||
|
choices=["all", "quantity", "resource"],
|
||||||
|
default="quantity",
|
||||||
|
help="计算类型(all: 全部, quantity: 工程量取费, resource: 人材机合价)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 项目参数
|
||||||
|
parser.add_argument("--project", help="项目名称,如果不指定则处理所有项目")
|
||||||
|
|
||||||
|
parser.add_argument("--adjustment-type", default="调差", help="调差类型,默认为'调差'")
|
||||||
|
|
||||||
|
# 输入文件参数
|
||||||
|
parser.add_argument(
|
||||||
|
"--input-file",
|
||||||
|
default="测试案例/主网清单变电.json",
|
||||||
|
help="输入JSON文件路径,如果不指定则使用默认路径",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加输入和输出文件夹参数
|
||||||
|
parser.add_argument("--input-folder", help="输入文件夹路径,包含要处理的JSON文件")
|
||||||
|
parser.add_argument("--output-folder", default="计算结果", help="输出文件夹路径,用于保存计算结果")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_filename(filename: str) -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
从文件名中解析软件类别和工程类型(备用方法)
|
||||||
|
|
||||||
|
:param filename: JSON文件名,例如"主网预算变电.json"
|
||||||
|
:return: (category, engineering_type) 元组,例如 ("主网", "预算")
|
||||||
|
"""
|
||||||
|
# 移除扩展名
|
||||||
|
basename = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
|
# 查找类别(主网/配网/技改)
|
||||||
|
category = None
|
||||||
|
for cat in ["主网", "配网", "技改"]:
|
||||||
|
if cat in basename:
|
||||||
|
category = cat
|
||||||
|
break
|
||||||
|
|
||||||
|
# 查找工程类型(预算/清单)
|
||||||
|
engineering_type = None
|
||||||
|
for eng_type in ["预算", "清单"]:
|
||||||
|
if eng_type in basename:
|
||||||
|
engineering_type = eng_type
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果未找到,使用默认值
|
||||||
|
if not category:
|
||||||
|
print(f"警告: 无法从文件名 '{filename}' 中解析软件类别,使用默认值 '主网'")
|
||||||
|
category = "主网"
|
||||||
|
|
||||||
|
if not engineering_type:
|
||||||
|
print(f"警告: 无法从文件名 '{filename}' 中解析工程类型,使用默认值 '清单'")
|
||||||
|
engineering_type = "清单"
|
||||||
|
|
||||||
|
return category, engineering_type
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_content(json_file_path: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""
|
||||||
|
从JSON文件内容中解析软件类别和工程类型
|
||||||
|
|
||||||
|
:param json_file_path: JSON文件路径
|
||||||
|
:return: (category, engineering_type) 元组,如果解析失败则返回 (None, 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()
|
||||||
|
|
||||||
|
# 验证软件类别
|
||||||
|
if category not in ["主网", "配网", "技改"]:
|
||||||
|
print(f"警告: division中的软件名称 '{category}' 不是有效值,将使用默认值 '主网'")
|
||||||
|
category = "主网"
|
||||||
|
|
||||||
|
# 映射阶段类型
|
||||||
|
if any(budget_type in stage_type for budget_type in budget_types):
|
||||||
|
engineering_type = "预算"
|
||||||
|
elif any(list_type in stage_type for list_type in list_types):
|
||||||
|
engineering_type = "清单"
|
||||||
|
else:
|
||||||
|
print(f"警告: division中的阶段类型 '{stage_type}' 无法映射到预算或清单,将使用默认值 '清单'")
|
||||||
|
engineering_type = "清单"
|
||||||
|
|
||||||
|
print(f"从division解析: 软件名称={category}, 阶段类型={engineering_type} (原始值: {stage_type})")
|
||||||
|
return category, engineering_type
|
||||||
|
else:
|
||||||
|
print(f"警告: division字段 '{division}' 格式不正确,无法分割")
|
||||||
|
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:
|
||||||
|
# 确保category是有效值
|
||||||
|
if category not in ["主网", "配网", "技改"]:
|
||||||
|
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
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"解析JSON文件内容时出错: {str(e)}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def process_json_file(
|
||||||
|
input_file: str,
|
||||||
|
base_output_dir: str = "计算结果",
|
||||||
|
category: Optional[str] = None,
|
||||||
|
engineering_type: Optional[str] = None,
|
||||||
|
project: Optional[str] = None,
|
||||||
|
adjustment_type: str = "调差",
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
处理单个JSON文件
|
||||||
|
|
||||||
|
:param input_file: 输入JSON文件路径
|
||||||
|
:param base_output_dir: 基础输出目录路径,实际结果会保存在此目录下的子文件夹中
|
||||||
|
:param category: 软件类别,如果为None则从JSON内容中解析
|
||||||
|
:param engineering_type: 工程类型,如果为None则从JSON内容中解析
|
||||||
|
:param project: 项目名称,如果为None则处理所有项目
|
||||||
|
:param adjustment_type: 调差类型
|
||||||
|
:return: 处理是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取文件名(不含扩展名)作为输出子文件夹的名称
|
||||||
|
filename = os.path.basename(input_file)
|
||||||
|
file_basename = os.path.splitext(filename)[0]
|
||||||
|
output_dir = os.path.join(base_output_dir, file_basename)
|
||||||
|
|
||||||
|
# 创建输出子文件夹
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
print(f"创建输出目录: {output_dir}")
|
||||||
|
|
||||||
|
# 如果未指定category或engineering_type,从JSON内容中解析
|
||||||
|
if category is None or engineering_type is None:
|
||||||
|
# 首先尝试从JSON内容中解析
|
||||||
|
json_category, json_engineering_type = parse_json_content(input_file)
|
||||||
|
|
||||||
|
if category is None:
|
||||||
|
if json_category:
|
||||||
|
category = json_category
|
||||||
|
else:
|
||||||
|
# 如果从JSON内容中解析失败,尝试从文件名中解析(作为备用)
|
||||||
|
print("从JSON内容解析软件名称失败,尝试从文件名解析...")
|
||||||
|
parsed_category, _ = parse_filename(filename)
|
||||||
|
category = parsed_category
|
||||||
|
|
||||||
|
if engineering_type is None:
|
||||||
|
if json_engineering_type:
|
||||||
|
engineering_type = json_engineering_type
|
||||||
|
else:
|
||||||
|
# 如果从JSON内容中解析失败,尝试从文件名中解析(作为备用)
|
||||||
|
print("从JSON内容解析阶段类型失败,尝试从文件名解析...")
|
||||||
|
_, parsed_engineering_type = parse_filename(filename)
|
||||||
|
engineering_type = parsed_engineering_type
|
||||||
|
|
||||||
|
print(f"处理文件: {input_file}")
|
||||||
|
print(f" 软件类别: {category}")
|
||||||
|
print(f" 工程类型: {engineering_type}")
|
||||||
|
|
||||||
|
# 获取软件类型
|
||||||
|
software_type = get_software_type(category, engineering_type)
|
||||||
|
print(f" 使用软件类型: {software_type.name}")
|
||||||
|
|
||||||
|
# 获取计算器
|
||||||
|
calculator = get_calculator(software_type)
|
||||||
|
if not calculator:
|
||||||
|
print(f" 错误: 未找到软件类型 {software_type.name} 的计算器")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 设置输出目录
|
||||||
|
calculator.set_output_dir(output_dir)
|
||||||
|
|
||||||
|
# 执行计算
|
||||||
|
print(" 开始计算工程量取费表...")
|
||||||
|
calculator.calculate_quantity_fee_tables(
|
||||||
|
json_file_path=input_file, project_name=project, adjustment_type=adjustment_type
|
||||||
|
)
|
||||||
|
|
||||||
|
print(" 开始计算人材机合价...")
|
||||||
|
calculator.calculate_resource_fee_tables(
|
||||||
|
json_file_path=input_file, project_name=project, adjustment_type=adjustment_type
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f" 处理完成: {input_file}")
|
||||||
|
print(f" 结果保存在: {output_dir}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" 处理文件 {input_file} 时出错: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def process_BCL_calculate(input_folder: str, output_folder: str) -> List[Tuple[str, bool]]:
|
||||||
|
"""
|
||||||
|
处理指定文件夹中的所有JSON文件
|
||||||
|
|
||||||
|
:param input_folder: 输入文件夹路径,包含要处理的JSON文件
|
||||||
|
:param output_folder: 输出文件夹路径,用于保存计算结果
|
||||||
|
:return: 处理结果列表,格式为 [(文件路径, 是否成功), ...]
|
||||||
|
"""
|
||||||
|
# 确保基础输出目录存在
|
||||||
|
os.makedirs(output_folder, exist_ok=True)
|
||||||
|
|
||||||
|
# 查找所有JSON文件
|
||||||
|
json_files = []
|
||||||
|
for file in os.listdir(input_folder):
|
||||||
|
if file.lower().endswith(".json"):
|
||||||
|
json_files.append(os.path.join(input_folder, file))
|
||||||
|
|
||||||
|
if not json_files:
|
||||||
|
print(f"警告: 在目录 {input_folder} 中没有找到JSON文件")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 处理每个JSON文件
|
||||||
|
results = []
|
||||||
|
for input_file in json_files:
|
||||||
|
success = process_json_file(
|
||||||
|
input_file=input_file,
|
||||||
|
base_output_dir=output_folder, # 传递基础输出目录
|
||||||
|
category=None, # 从JSON内容中解析
|
||||||
|
engineering_type=None, # 从JSON内容中解析
|
||||||
|
project=None, # 处理所有项目
|
||||||
|
adjustment_type="调差",
|
||||||
|
)
|
||||||
|
|
||||||
|
results.append((input_file, success))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""程序入口"""
|
||||||
|
|
||||||
|
input_folder = "project2json/outputs"
|
||||||
|
output_folder = "outputs-2"
|
||||||
|
|
||||||
|
results = process_BCL_calculate(input_folder, output_folder)
|
||||||
|
|
||||||
|
# 显示处理结果
|
||||||
|
success_count = sum(1 for _, success in results if success)
|
||||||
|
print(f"\n处理完成: 成功 {success_count}/{len(results)} 个文件")
|
||||||
|
|
||||||
|
if success_count < len(results):
|
||||||
|
print("处理失败的文件:")
|
||||||
|
for file_path, success in results:
|
||||||
|
if not success:
|
||||||
|
print(f" - {os.path.basename(file_path)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectQuantity:
|
||||||
|
"""
|
||||||
|
工程量
|
||||||
|
描述: 代表项目划分项(ProjectDivisionItem)下的具体工作单元或物料项,是定额、主材、设备的父类型
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id = None # xsd:string
|
||||||
|
self.类型 = None # xsd:string ("0"为定额,"1"为主材,"5"为设备)
|
||||||
|
self.name = None # xsd:string
|
||||||
|
self.编码 = None # xsd:string
|
||||||
|
self.单位 = None # xsd:string
|
||||||
|
self.数量 = None # xsd:string
|
||||||
|
self.资源库名称 = None # xsd:string
|
||||||
|
self.投标数量 = None # xsd:string (可选)
|
||||||
|
self.投标单价 = None # xsd:string (可选)
|
||||||
|
self.特征段 = None # xsd:string (可选)
|
||||||
|
self.关联父级量 = None # xsd:string (可选)
|
||||||
|
self.颜色标记 = None # xsd:string (可选)
|
||||||
|
self.单价不含税 = None
|
||||||
|
self.cost_set = None # xsd:CostSet
|
||||||
|
self.设备类型 = None # xsd:string
|
||||||
|
self.供货方 = None # xsd:string
|
||||||
|
|
||||||
|
# (定额)
|
||||||
|
self.综合地形类型 = None # xsd:string
|
||||||
|
self.乙供材料费不含税 = None # xsd:string
|
||||||
|
self.调差类型 = None # xsd:string
|
||||||
|
self.机械费不含税 = None # xsd:string
|
||||||
|
self.地形费计算方式 = None # xsd:string
|
||||||
|
self.专业属性 = None # xsd:string
|
||||||
|
|
||||||
|
# (主材)
|
||||||
|
self.拆分 = None # xsd:string
|
||||||
|
self.设备性材料 = None # xsd:string
|
||||||
|
self.预算价不含税 = None # xsd:string
|
||||||
|
self.损耗 = None # xsd:string
|
||||||
|
self.预算价含税 = None # xsd:string
|
||||||
|
|
||||||
|
# 技改预算_定额
|
||||||
|
self.脚手架计取 = None # xsd:string
|
||||||
|
self.甲供材料费_含税 = None # xsd:string
|
||||||
|
self.配件类型 = None # xsd:string
|
||||||
|
self.专业类型 = None # xsd:string
|
||||||
|
self.调试费计取 = None # xsd:string
|
||||||
|
self.甲供材料费 = None # xsd:string
|
||||||
|
self.费用类型 = None # xsd:string
|
||||||
|
|
||||||
|
|
||||||
|
class Ration(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
|
||||||
|
self.定额系数 = None # xsd:string
|
||||||
|
self.人工系数 = None # xsd:string
|
||||||
|
self.材料系数 = None # xsd:string
|
||||||
|
self.机械系数 = None # xsd:string
|
||||||
|
self.定额范围 = None # xsd:string
|
||||||
|
self.定额章节名称 = None # xsd:string
|
||||||
|
self.费用类型 = None # xsd:string
|
||||||
|
self.甲供材料费含税 = None # xsd:string
|
||||||
|
self.投标合价 = None # xsd:string
|
||||||
|
self.其中甲供材料费 = None # xsd:string
|
||||||
|
self.合价不含税 = None # xsd:string
|
||||||
|
self.基价 = None # xsd:string
|
||||||
|
self.所属定额库 = None # xsd:string
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""返回定额的字符串表示"""
|
||||||
|
attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
return json.dumps(attrs, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
self.市场价含税 = None # xsd:string
|
||||||
|
self.单价含税 = None # xsd:string
|
||||||
|
self.结算市场价不含税 = None # xsd:string (可选)
|
||||||
|
self.结算市场价含税 = None # xsd:string (可选)
|
||||||
|
self.基准价不含税 = None # xsd:string (可选)
|
||||||
|
self.基准价含税 = None # xsd:string (可选)
|
||||||
|
self.费用类型 = None # xsd:string
|
||||||
|
self.增值税率 = None # xsd:string (可选)
|
||||||
|
self.合价含税 = None # xsd:string
|
||||||
|
self.合价不含税 = None # xsd:string
|
||||||
|
self.线重 = None # xsd:string (可选)
|
||||||
|
self.制造长度 = None # xsd:string (可选)
|
||||||
|
self.截面积 = None # xsd:string (可选)
|
||||||
|
self.拆分 = None # xsd:string (可选)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""返回材料的字符串表示"""
|
||||||
|
attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
return json.dumps(attrs, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
class Equipment(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 (可选)
|
||||||
|
self.合价含税 = None # xsd:string
|
||||||
|
self.合价不含税 = None # xsd:string
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialOrEquipment:
|
||||||
|
"""
|
||||||
|
材机
|
||||||
|
描述: 代表DetailedWorkItem中所列出的具体材料、人工或机械设备及其详细信息
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id = None # xsd:string
|
||||||
|
self.编码 = None # xsd:string
|
||||||
|
self.名称 = None # xsd:string
|
||||||
|
self.单位 = None # xsd:string
|
||||||
|
self.type = None # xsd:string
|
||||||
|
self.供货方 = None # xsd:string
|
||||||
|
self.预算价不含税 = None # xsd:string
|
||||||
|
self.市场价不含税 = None # xsd:string
|
||||||
|
self.预算价含税 = None # xsd:string
|
||||||
|
self.市场价含税 = None # xsd:string
|
||||||
|
self.结算预算价不含税 = None # xsd:string
|
||||||
|
self.结算市场价不含税 = None # xsd:string
|
||||||
|
self.结算预算价含税 = None # xsd:string
|
||||||
|
self.结算市场价含税 = None # xsd:string
|
||||||
|
self.暂估价 = None # xsd:string
|
||||||
|
self.拆分 = None # xsd:string
|
||||||
|
self.全口径市场价不含税 = None # xsd:string
|
||||||
|
self.全口径市场价含税 = None # xsd:string
|
||||||
|
self.商品砼 = None # xsd:string
|
||||||
|
self.数量 = None # xsd:string
|
||||||
|
self.是否未计价 = None # xsd:string
|
||||||
|
|
||||||
|
|
||||||
|
class bill_node:
|
||||||
|
"""
|
||||||
|
清单
|
||||||
|
描述: 代表清单项,通常包含多个定额
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.清单名称 = None # xsd:string
|
||||||
|
self.清单全码 = None # xsd:string
|
||||||
|
self.编码 = None # xsd:string
|
||||||
|
self.单位 = None # xsd:string
|
||||||
|
self.计算式 = None # xsd:string
|
||||||
|
self.数量 = None # xsd:string
|
||||||
|
self.取费表 = None # xsd:string
|
||||||
|
self.合价 = None # xsd:string
|
||||||
|
self.工作内容 = None # xsd:string
|
||||||
|
self.类型 = None # xsd:string
|
||||||
|
self.type = None # xsd:string
|
||||||
|
self.项目特征 = None # xsd:string
|
||||||
|
self.资源库名称 = None # xsd:string
|
||||||
|
self.计算规则 = None # xsd:string
|
||||||
|
self.单价 = None # xsd:string
|
||||||
|
self.一次性费用 = None # xsd:string
|
||||||
|
self.取费表名称 = None # xsd:string
|
||||||
|
self.取费表类型 = None # xsd:string
|
||||||
|
self.单价不含税 = None # xsd:string
|
||||||
|
self.合价不含税 = None # xsd:string
|
||||||
|
self.专业类型 = None # xsd:string
|
||||||
|
self.关联父级量 = None # xsd:string
|
||||||
|
self.含量 = None # xsd:string
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""返回清单的字符串表示"""
|
||||||
|
attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||||
|
return json.dumps(attrs, ensure_ascii=False, indent=2)
|
||||||
@@ -0,0 +1,972 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Dict, List, Any, Optional, Tuple, Set
|
||||||
|
from expressioncalculator import ExpressionCalculator
|
||||||
|
from bcl_utils import (
|
||||||
|
ZjQuantityBCLContext,
|
||||||
|
ZjProjectBCLContext,
|
||||||
|
ZjBillBCLContext,
|
||||||
|
init_bcl_calculator,
|
||||||
|
BCLVariant,
|
||||||
|
calculator,
|
||||||
|
create_project_contexts,
|
||||||
|
BCLDataSourceItem,
|
||||||
|
BCLDataSourceContext,
|
||||||
|
create_list_from_node,
|
||||||
|
create_node_from_type,
|
||||||
|
)
|
||||||
|
from item_acquisition import (
|
||||||
|
get_cost_table_children,
|
||||||
|
get_quantity_nodes,
|
||||||
|
get_bill_cost_table,
|
||||||
|
find_cost_table,
|
||||||
|
get_bill_node_by_id,
|
||||||
|
load_project_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 缓存已计算过的费用
|
||||||
|
calculated_fees = {}
|
||||||
|
|
||||||
|
# 添加一个全局变量和一个缓存字典来存储清单数量
|
||||||
|
_BILL_QUANTITY = 1.0
|
||||||
|
bill_quantity_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def process_DXdata(json_data):
|
||||||
|
"""
|
||||||
|
处理 projectData 中的线路特征段数据,计算每条 bpBillZhXsTable 记录的加权值,
|
||||||
|
并返回一个字典列表,每个字典包含 '特征段' 和对应的 ItemName: 计算结果。
|
||||||
|
|
||||||
|
:param json_data: 解析后的 JSON 数据(字典格式)
|
||||||
|
:return: 字典列表,例如 [{"特征段": "1", "工地运输...": "21.8"}, ...]
|
||||||
|
"""
|
||||||
|
result_list = []
|
||||||
|
|
||||||
|
# 遍历每个线路特征段
|
||||||
|
for segment in json_data.get("projectData", {}).get("线路特征段", []):
|
||||||
|
seg_name = segment.get("Name", "")
|
||||||
|
# 提取特征段中的数字,例如 "特征段1" -> "1"
|
||||||
|
seg_number = "".join(filter(str.isdigit, seg_name))
|
||||||
|
if not seg_number:
|
||||||
|
seg_number = "0" # 默认值
|
||||||
|
|
||||||
|
# 获取 bpBillZhXsTable 列表
|
||||||
|
table_list = segment.get("bpBillZhXsTable", [])
|
||||||
|
for item in table_list:
|
||||||
|
try:
|
||||||
|
# 提取字段并转换为浮点数
|
||||||
|
KnapScale = float(item.get("KnapScale", 0))
|
||||||
|
Knap = float(item.get("Knap", 0))
|
||||||
|
HillScale = float(item.get("HillScale", 0))
|
||||||
|
Hill = float(item.get("Hill", 0))
|
||||||
|
EdelweissScale = float(item.get("EdelweissScale", 0))
|
||||||
|
edelweiss = float(item.get("edelweiss", 0))
|
||||||
|
MountainScale = float(item.get("MountainScale", 0))
|
||||||
|
Mountain = float(item.get("Mountain", 0))
|
||||||
|
SloughScale = float(item.get("SloughScale", 0))
|
||||||
|
Slough = float(item.get("Slough", 0))
|
||||||
|
RiverScale = float(item.get("RiverScale", 0))
|
||||||
|
River = float(item.get("River", 0))
|
||||||
|
DesertScale = float(item.get("DesertScale", 0))
|
||||||
|
Desert = float(item.get("Desert", 0))
|
||||||
|
|
||||||
|
# 执行计算
|
||||||
|
total = (
|
||||||
|
(KnapScale * Knap)
|
||||||
|
+ (HillScale * Hill)
|
||||||
|
+ (EdelweissScale * edelweiss)
|
||||||
|
+ (MountainScale * Mountain)
|
||||||
|
+ (SloughScale * Slough)
|
||||||
|
+ (RiverScale * River)
|
||||||
|
+ (DesertScale * Desert)
|
||||||
|
) * 0.01
|
||||||
|
|
||||||
|
# 保留一位小数,格式化为字符串
|
||||||
|
calculated_value = f"{total:.1f}"
|
||||||
|
|
||||||
|
# 获取 ItemName
|
||||||
|
item_name = item.get("ItemName", "未知项目")
|
||||||
|
|
||||||
|
# 构建结果字典
|
||||||
|
item_dict = {"特征段": seg_number, item_name: calculated_value}
|
||||||
|
|
||||||
|
# 添加到结果列表
|
||||||
|
result_list.append(item_dict)
|
||||||
|
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(f"数据转换错误,跳过该项: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
return result_list
|
||||||
|
|
||||||
|
|
||||||
|
# 在create_list_from_node函数后添加一个包装函数
|
||||||
|
def create_list_from_node_with_quantity(node, quantity=None):
|
||||||
|
"""创建清单对象并设置数量"""
|
||||||
|
from bcl_utils import create_list_from_node
|
||||||
|
|
||||||
|
bill_obj = create_list_from_node(node)
|
||||||
|
|
||||||
|
# 设置清单数量
|
||||||
|
bill_quantity = quantity if quantity is not None else node.get("数量")
|
||||||
|
|
||||||
|
# 设置到对象属性
|
||||||
|
bill_obj.quantity = bill_quantity
|
||||||
|
|
||||||
|
# 保存到全局变量和缓存
|
||||||
|
global _BILL_QUANTITY
|
||||||
|
_BILL_QUANTITY = bill_quantity
|
||||||
|
|
||||||
|
# 如果节点有ID,保存到缓存
|
||||||
|
bill_id = node.get("id") or node.get("GUID")
|
||||||
|
if bill_id:
|
||||||
|
bill_quantity_cache[bill_id] = bill_quantity
|
||||||
|
|
||||||
|
# 设置环境变量作为后备方案
|
||||||
|
os.environ["BILL_QUANTITY"] = str(bill_quantity)
|
||||||
|
|
||||||
|
print(f"设置清单对象数量: {bill_quantity}")
|
||||||
|
print(f"全局变量_BILL_QUANTITY: {_BILL_QUANTITY}")
|
||||||
|
if bill_id:
|
||||||
|
print(f"缓存清单{bill_id}的数量: {bill_quantity_cache[bill_id]}")
|
||||||
|
|
||||||
|
return bill_obj
|
||||||
|
|
||||||
|
|
||||||
|
# 递归查找取费表中包含"取费基数"的节点
|
||||||
|
def find_fee_base_nodes(node: Dict[str, Any], result: List = None) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
递归查找取费表中包含"取费基数"的节点或有子节点的费用项
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: 取费表节点
|
||||||
|
result: 结果列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 包含"取费基数"的节点或有子节点的费用项列表
|
||||||
|
"""
|
||||||
|
if result is None:
|
||||||
|
result = []
|
||||||
|
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 检查当前节点是否包含"取费基数"字段或有子节点
|
||||||
|
has_children = "children" in node and isinstance(node["children"], list) and node["children"]
|
||||||
|
has_code = "代码" in node
|
||||||
|
|
||||||
|
if "取费基数" in node or (has_children and has_code):
|
||||||
|
result.append(node)
|
||||||
|
|
||||||
|
# 递归检查子节点
|
||||||
|
for key, value in node.items():
|
||||||
|
if key == "children" and isinstance(value, list):
|
||||||
|
for child in value:
|
||||||
|
find_fee_base_nodes(child, result)
|
||||||
|
elif isinstance(value, (dict, list)):
|
||||||
|
find_fee_base_nodes(value, result)
|
||||||
|
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
find_fee_base_nodes(item, result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# 查找取费表中的费用项
|
||||||
|
def find_fee_item_by_code(cost_table: Dict[str, Any], code: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
在取费表中查找指定代码的费用项
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cost_table: 取费表
|
||||||
|
code: 费用代码
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Dict[str, Any]]: 找到的费用项,如果未找到则返回None
|
||||||
|
"""
|
||||||
|
|
||||||
|
def search_in_node(node):
|
||||||
|
if isinstance(node, dict):
|
||||||
|
if node.get("代码") == code:
|
||||||
|
return node
|
||||||
|
|
||||||
|
for key, value in node.items():
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
result = search_in_node(value)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
elif isinstance(node, list):
|
||||||
|
for item in node:
|
||||||
|
result = search_in_node(item)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
return search_in_node(cost_table)
|
||||||
|
|
||||||
|
|
||||||
|
# 计算表外变量
|
||||||
|
def calculate_external_variable(var_name: str, context: ZjQuantityBCLContext, calculation_strategy=None) -> float:
|
||||||
|
"""
|
||||||
|
使用BCL计算器计算表外变量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
var_name: 变量名
|
||||||
|
context: BCL上下文
|
||||||
|
calculation_strategy: 计算策略对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 计算结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 如果有计算策略,使用计算策略的方法
|
||||||
|
if calculation_strategy:
|
||||||
|
print(f"使用计算策略 {calculation_strategy.__class__.__name__} 计算表外变量: {var_name}")
|
||||||
|
# 直接调用计算策略的方法,而不是递归调用自己
|
||||||
|
return calculation_strategy.calculate_external_variable(var_name, context)
|
||||||
|
|
||||||
|
# 否则使用默认实现
|
||||||
|
print(f"使用默认方法计算表外变量: {var_name}")
|
||||||
|
result = calculator.calculate(var_name, context)
|
||||||
|
|
||||||
|
# 检查结果类型,如果是 BCLVariant,尝试转换为浮点数
|
||||||
|
if hasattr(result, "__class__") and result.__class__.__name__ == "BCLVariant":
|
||||||
|
# 根据错误信息,BCLVariant 格式似乎是 Variant(BCLVariantType.FLOAT, 0.0)
|
||||||
|
# 尝试访问 value 属性或使用适当的方法获取值
|
||||||
|
try:
|
||||||
|
# 尝试不同的方法获取值
|
||||||
|
if hasattr(result, "value"):
|
||||||
|
return float(result.value)
|
||||||
|
elif hasattr(result, "get_value"):
|
||||||
|
return float(result.get_value())
|
||||||
|
elif hasattr(result, "__getitem__"):
|
||||||
|
# 如果 BCLVariant 支持索引访问,尝试获取第二个元素
|
||||||
|
return float(result[1])
|
||||||
|
else:
|
||||||
|
# 尝试直接转换为字符串,然后解析值
|
||||||
|
str_result = str(result)
|
||||||
|
# 假设格式是 Variant(BCLVariantType.FLOAT, 0.0)
|
||||||
|
# 尝试提取括号中的第二个值
|
||||||
|
import re
|
||||||
|
|
||||||
|
match = re.search(r"Variant\([^,]+,\s*([^)]+)\)", str_result)
|
||||||
|
if match:
|
||||||
|
return float(match.group(1))
|
||||||
|
|
||||||
|
# 如果上述方法都失败,尝试直接转换
|
||||||
|
return float(result)
|
||||||
|
except (ValueError, TypeError, AttributeError, IndexError) as e:
|
||||||
|
print(f"无法将 BCLVariant 类型的结果转换为浮点数: {result}, 错误: {e}")
|
||||||
|
# 打印更多调试信息
|
||||||
|
print(f"BCLVariant 类型: {type(result)}")
|
||||||
|
print(f"BCLVariant 属性: {dir(result)}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# 如果不是 BCLVariant,尝试直接转换为浮点数
|
||||||
|
return float(result) if result is not None else 0.0
|
||||||
|
except Exception as e:
|
||||||
|
print(f"计算表外变量 '{var_name}' 时出错: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
# 计算表内费用
|
||||||
|
def calculate_internal_fee(
|
||||||
|
fee_code: str,
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
context: ZjQuantityBCLContext,
|
||||||
|
in_calculation: set = None,
|
||||||
|
calculation_strategy=None,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算表内费用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fee_code: 费用代码
|
||||||
|
cost_table: 取费表
|
||||||
|
project_node: 项目划分节点
|
||||||
|
context: BCL上下文
|
||||||
|
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
|
||||||
|
calculation_strategy: 计算策略对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 计算结果
|
||||||
|
"""
|
||||||
|
# 初始化正在计算的集合
|
||||||
|
if in_calculation is None:
|
||||||
|
in_calculation = set()
|
||||||
|
|
||||||
|
# 检查是否存在循环依赖
|
||||||
|
if fee_code in in_calculation:
|
||||||
|
print(f"检测到循环依赖: {fee_code}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# 将当前费用代码添加到正在计算的集合中
|
||||||
|
in_calculation.add(fee_code)
|
||||||
|
|
||||||
|
# 检查缓存中是否已有计算结果
|
||||||
|
cache_key = f"{fee_code}_{project_node.get('id', '')}"
|
||||||
|
if cache_key in calculated_fees:
|
||||||
|
# 从正在计算的集合中移除当前费用代码
|
||||||
|
in_calculation.remove(fee_code)
|
||||||
|
return calculated_fees[cache_key]
|
||||||
|
|
||||||
|
# 查找费用项
|
||||||
|
fee_item = find_fee_item_by_code(cost_table, fee_code)
|
||||||
|
if not fee_item:
|
||||||
|
print(f"未找到代码为 '{fee_code}' 的费用项")
|
||||||
|
# 从正在计算的集合中移除当前费用代码
|
||||||
|
in_calculation.remove(fee_code)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# 获取取费基数和费率
|
||||||
|
fee_base = fee_item.get("取费基数")
|
||||||
|
fee_rate = fee_item.get("费率(%)") or fee_item.get("费率")
|
||||||
|
|
||||||
|
# 尝试转换费率为浮点数
|
||||||
|
try:
|
||||||
|
if fee_rate is None or (isinstance(fee_rate, str) and fee_rate.strip() == ""):
|
||||||
|
rate = 1.0
|
||||||
|
else:
|
||||||
|
rate = float(fee_rate) / 100.0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
print(f"无法将费率 '{fee_rate}' 转换为浮点数,使用默认值1.0")
|
||||||
|
rate = 1.0
|
||||||
|
|
||||||
|
# 如果有取费基数,直接计算
|
||||||
|
if fee_base:
|
||||||
|
base_value = calculate_fee_base(
|
||||||
|
fee_base, cost_table, project_node, context, in_calculation, calculation_strategy
|
||||||
|
)
|
||||||
|
# 确保 base_value 不为 None
|
||||||
|
base_value = base_value if base_value is not None else 0.0
|
||||||
|
result = base_value * rate
|
||||||
|
else:
|
||||||
|
# 如果没有取费基数,检查是否有子节点
|
||||||
|
children = fee_item.get("children", [])
|
||||||
|
if children:
|
||||||
|
# 计算所有子节点的费用之和
|
||||||
|
result = 0.0
|
||||||
|
for child in children:
|
||||||
|
child_code = child.get("代码", "")
|
||||||
|
if child_code:
|
||||||
|
child_result = calculate_internal_fee(
|
||||||
|
child_code, cost_table, project_node, context, in_calculation, calculation_strategy
|
||||||
|
)
|
||||||
|
# 确保 child_result 不为 None
|
||||||
|
child_result = child_result if child_result is not None else 0.0
|
||||||
|
result += child_result
|
||||||
|
else:
|
||||||
|
print(f"费用项 '{fee_code}' 没有取费基数且没有子节点")
|
||||||
|
result = 0.0
|
||||||
|
|
||||||
|
# 缓存计算结果
|
||||||
|
calculated_fees[cache_key] = result
|
||||||
|
|
||||||
|
# 从正在计算的集合中移除当前费用代码
|
||||||
|
in_calculation.remove(fee_code)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# 计算取费基数
|
||||||
|
def calculate_fee_base(
|
||||||
|
fee_base: str,
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
context: ZjQuantityBCLContext,
|
||||||
|
in_calculation: set = None,
|
||||||
|
calculation_strategy=None,
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
计算取费基数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fee_base: 取费基数表达式
|
||||||
|
cost_table: 取费表
|
||||||
|
project_node: 项目划分节点
|
||||||
|
context: BCL上下文
|
||||||
|
in_calculation: 正在计算中的费用代码集合,用于检测循环依赖
|
||||||
|
calculation_strategy: 计算策略对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 计算结果
|
||||||
|
"""
|
||||||
|
# 初始化正在计算的集合
|
||||||
|
if in_calculation is None:
|
||||||
|
in_calculation = set()
|
||||||
|
|
||||||
|
# 检查是否是直接引用另一个费用项的代码
|
||||||
|
if fee_base in calculated_fees:
|
||||||
|
return calculated_fees[fee_base]
|
||||||
|
|
||||||
|
# 创建表达式计算器
|
||||||
|
expr_calculator = ExpressionCalculator()
|
||||||
|
parse_success = expr_calculator.parse_expression(fee_base)
|
||||||
|
if not parse_success:
|
||||||
|
print(f"测试表达式解析失败: {expr_calculator.get_last_error()}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 获取表达式中的变量
|
||||||
|
variables = expr_calculator.variables
|
||||||
|
variable_values = {}
|
||||||
|
|
||||||
|
# 处理每个变量
|
||||||
|
for var in variables:
|
||||||
|
# 检查是否为表内费用代码
|
||||||
|
if var in calculated_fees:
|
||||||
|
# 使用缓存的计算结果
|
||||||
|
variable_values[var] = calculated_fees[var]
|
||||||
|
elif find_fee_item_by_code(cost_table, var):
|
||||||
|
# 计算表内费用
|
||||||
|
value = calculate_internal_fee(var, cost_table, project_node, context, in_calculation, calculation_strategy)
|
||||||
|
# 确保值不为 None
|
||||||
|
variable_values[var] = value if value is not None else 0.0
|
||||||
|
else:
|
||||||
|
# 处理表外变量
|
||||||
|
value = calculate_external_variable(var, context, calculation_strategy)
|
||||||
|
# 确保值不为 None
|
||||||
|
variable_values[var] = value if value is not None else 0.0
|
||||||
|
|
||||||
|
# 计算最终结果
|
||||||
|
try:
|
||||||
|
result, value = expr_calculator.evaluate(variable_values)
|
||||||
|
# 确保结果不为 None
|
||||||
|
return value if result else 0.0
|
||||||
|
except Exception as e:
|
||||||
|
print(f"计算表达式 '{fee_base}' 时出错: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
# 计算项目节点的所有费用
|
||||||
|
def calculate_all_fees(
|
||||||
|
project_node: Dict[str, Any],
|
||||||
|
cost_table: Dict[str, Any],
|
||||||
|
json_file_path: str = None,
|
||||||
|
engineering_type: str = None,
|
||||||
|
calculation_strategy=None,
|
||||||
|
) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
计算项目节点的所有费用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_node: 工程量节点
|
||||||
|
cost_table: 取费表
|
||||||
|
json_file_path: JSON文件路径,用于获取工程信息
|
||||||
|
engineering_type: 工程类型,如"清单工程"或"预算工程"
|
||||||
|
calculation_strategy: 计算策略,如果为None则使用默认策略
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, float]: 费用计算结果
|
||||||
|
"""
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
# 1. 工程信息上下文 ,在这里暂时先将参数项目划分名称固定
|
||||||
|
project_context = create_project_contexts(json_file_path=json_file_path)
|
||||||
|
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as file:
|
||||||
|
data = json.load(file)
|
||||||
|
|
||||||
|
processed_data = process_DXdata(data)
|
||||||
|
|
||||||
|
DXITEM = [BCLDataSourceItem(item) for item in processed_data]
|
||||||
|
|
||||||
|
# 2. 构建数据源链式上下文
|
||||||
|
if engineering_type == "清单工程":
|
||||||
|
# 获取清单节点
|
||||||
|
bill_id = project_node.get("bill_id")
|
||||||
|
bill_name = project_node.get("bill_name")
|
||||||
|
bill_node = project_node.get("bill_node")
|
||||||
|
if not bill_node:
|
||||||
|
bill_node = {
|
||||||
|
"id": bill_id,
|
||||||
|
"GUID": bill_id,
|
||||||
|
"清单名称": bill_name,
|
||||||
|
"取费表名称": project_node.get("取费表名称"),
|
||||||
|
"清单全码": project_node.get("清单全码", ""),
|
||||||
|
"编码": project_node.get("清单编码", ""),
|
||||||
|
"单位": project_node.get("单位", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 使用包装函数创建清单对象
|
||||||
|
# bill_obj = create_list_from_node_with_quantity(bill_node)
|
||||||
|
bill_obj = create_list_from_node(bill_node)
|
||||||
|
# 如果清单节点有数量,确保它被保存到bill_obj
|
||||||
|
if "数量" in bill_node:
|
||||||
|
bill_obj.quantity = float(bill_node["数量"])
|
||||||
|
print(f"设置清单对象数量: {bill_obj.quantity}")
|
||||||
|
|
||||||
|
# 创建工程量对象
|
||||||
|
from bcl_utils import create_node_from_type
|
||||||
|
|
||||||
|
project_obj = create_node_from_type(project_node)
|
||||||
|
|
||||||
|
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
|
||||||
|
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
|
||||||
|
|
||||||
|
# 递归数据源链式上下文
|
||||||
|
billItem = BCLDataSourceItem(bill_obj)
|
||||||
|
# 设置一个显式的数量属性
|
||||||
|
# billItem.bill_quantity = float(bill_node.get("数量", 1.0))
|
||||||
|
# print(f"设置清单数据源项数量: {billItem.bill_quantity}")
|
||||||
|
|
||||||
|
QuantityItem = BCLDataSourceItem(project_obj, billItem)
|
||||||
|
bill_context = BCLDataSourceContext([billItem], dxitem_context)
|
||||||
|
context = BCLDataSourceContext([QuantityItem], bill_context)
|
||||||
|
else:
|
||||||
|
# 非清单工程 - 工程信息 -> 工程量节点
|
||||||
|
from bcl_utils import create_node_from_type
|
||||||
|
|
||||||
|
project_obj = create_node_from_type(project_node)
|
||||||
|
|
||||||
|
dxitem_context = BCLDataSourceContext(DXITEM, project_context)
|
||||||
|
dxitem_context.variables["@特征段地形系数"] = BCLVariant(DXITEM)
|
||||||
|
|
||||||
|
QuantityItem = BCLDataSourceItem(project_obj)
|
||||||
|
context = BCLDataSourceContext([QuantityItem], dxitem_context)
|
||||||
|
|
||||||
|
# 打印项目节点信息,用于调试
|
||||||
|
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||||
|
print(f"\n处理项目节点: {node_name}")
|
||||||
|
|
||||||
|
# 查找包含取费基数的节点
|
||||||
|
fee_base_nodes = find_fee_base_nodes(cost_table)
|
||||||
|
|
||||||
|
# 计算每个费用项
|
||||||
|
# 先计算基本费用项,再计算复合费用项
|
||||||
|
# 这里简单按照节点在列表中的顺序计算,可能需要更复杂的依赖解析
|
||||||
|
for node in fee_base_nodes:
|
||||||
|
fee_name = node.get("费用名称", node.get("name", "未知费用"))
|
||||||
|
fee_code = node.get("代码", "")
|
||||||
|
fee_base = node.get("取费基数", "")
|
||||||
|
fee_rate = node.get("费率(%)") or node.get("费率")
|
||||||
|
|
||||||
|
# 尝试转换费率为浮点数
|
||||||
|
try:
|
||||||
|
if fee_rate is None or (isinstance(fee_rate, str) and fee_rate.strip() == ""):
|
||||||
|
rate = 1.0
|
||||||
|
else:
|
||||||
|
rate = float(fee_rate) / 100.0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
print(f"无法将费率 '{fee_rate}' 转换为浮点数,使用默认值1.0")
|
||||||
|
rate = 1.0
|
||||||
|
|
||||||
|
if fee_base:
|
||||||
|
# 计算取费基数
|
||||||
|
base_value = calculate_fee_base(fee_base, cost_table, project_node, context, None, calculation_strategy)
|
||||||
|
# 应用费率
|
||||||
|
result = base_value * rate
|
||||||
|
results[fee_name] = result
|
||||||
|
|
||||||
|
# 缓存计算结果
|
||||||
|
if fee_code:
|
||||||
|
calculated_fees[fee_code] = result
|
||||||
|
elif fee_code:
|
||||||
|
# 如果没有取费基数但有代码,尝试计算
|
||||||
|
result = calculate_internal_fee(fee_code, cost_table, project_node, context, None, calculation_strategy)
|
||||||
|
results[fee_name] = result
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# 计算工程量取费表
|
||||||
|
##原始代码
|
||||||
|
# def calculate_quantity_fees(
|
||||||
|
# json_file_path: str, project_name: str, adjustment_type: str = None, engineering_type: str = None
|
||||||
|
# ) -> str:
|
||||||
|
# """
|
||||||
|
# 计算工程量取费表
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# json_file_path: JSON文件路径
|
||||||
|
# project_name: 项目名称
|
||||||
|
# adjustment_type: 调差类型
|
||||||
|
# engineering_type: 工程类型
|
||||||
|
# Returns:
|
||||||
|
# str: 输出文件路径
|
||||||
|
# """
|
||||||
|
# # 设置输出目录和文件名
|
||||||
|
# output_dir = "计算结果"
|
||||||
|
# os.makedirs(output_dir, exist_ok=True)
|
||||||
|
# output_file = os.path.join(
|
||||||
|
# output_dir, f"{project_name}_{adjustment_type}_{engineering_type}_calculation_results.json"
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # 初始化BCL计算器
|
||||||
|
# if not init_bcl_calculator():
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# # 根据工程类型获取取费表和项目节点
|
||||||
|
# if engineering_type == "预算工程":
|
||||||
|
# # 预算工程 - 获取项目划分节点的取费表
|
||||||
|
# cost_table_children = get_cost_table_children(json_file_path, project_name)
|
||||||
|
# project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type)
|
||||||
|
|
||||||
|
# if not cost_table_children or not project_children:
|
||||||
|
# print(f"未找到项目 '{project_name}' 的取费表或项目划分节点")
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# elif engineering_type == "清单工程":
|
||||||
|
# # 清单工程 - 获取清单节点的取费表
|
||||||
|
# project_children = get_quantity_nodes(json_file_path, project_name, adjustment_type, engineering_type)
|
||||||
|
# if not project_children:
|
||||||
|
# print(f"未找到项目 '{project_name}' 的项目划分节点")
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# # 为每个工程量节点找到对应的清单节点取费表
|
||||||
|
# cost_tables = {}
|
||||||
|
|
||||||
|
# for project_node in project_children:
|
||||||
|
# node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||||
|
|
||||||
|
# # 获取清单节点信息
|
||||||
|
# bill_id = project_node.get("bill_id")
|
||||||
|
# fee_table_name = project_node.get("取费表名称")
|
||||||
|
|
||||||
|
# if not bill_id:
|
||||||
|
# print(f"工程量节点 '{node_name}' 缺少清单节点ID")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# if not fee_table_name:
|
||||||
|
# print(f"工程量节点 '{node_name}' 没有取费表名称")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# # 使用清单节点的取费表名称直接查找取费表
|
||||||
|
# if bill_id not in cost_tables:
|
||||||
|
# print(f"查找清单节点 '{project_node.get('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)
|
||||||
|
|
||||||
|
# if not cost_table:
|
||||||
|
# print(f"未找到取费表 '{fee_table_name}'")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# cost_table_children = cost_table.get("children", None)
|
||||||
|
# if not cost_table_children:
|
||||||
|
# print(f"取费表 '{fee_table_name}' 没有子节点")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# cost_tables[bill_id] = cost_table_children
|
||||||
|
# print(f"成功获取清单节点 '{project_node.get('bill_name', '未命名清单')}' 的取费表")
|
||||||
|
# else:
|
||||||
|
# print(f"不支持的工程类型: {engineering_type}")
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# # 初始化缓存
|
||||||
|
# calculated_fees.clear()
|
||||||
|
|
||||||
|
# # 计算每个项目节点的费用
|
||||||
|
# all_results = {}
|
||||||
|
|
||||||
|
# for project_node in project_children:
|
||||||
|
# # 为每个节点清空缓存,确保计算独立
|
||||||
|
# calculated_fees.clear()
|
||||||
|
|
||||||
|
# node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||||
|
|
||||||
|
# # 根据工程类型获取对应的取费表
|
||||||
|
# if engineering_type == "预算工程":
|
||||||
|
# # 预算工程 - 使用项目划分节点的取费表
|
||||||
|
# node_results = calculate_all_fees(
|
||||||
|
# project_node, {"children": cost_table_children}, json_file_path, engineering_type
|
||||||
|
# )
|
||||||
|
# else: # 清单工程
|
||||||
|
# # 清单工程 - 使用清单节点的取费表
|
||||||
|
# parent_id = project_node.get("parent_id")
|
||||||
|
# if parent_id and parent_id in cost_tables:
|
||||||
|
# node_results = calculate_all_fees(
|
||||||
|
# project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# print(f"无法获取工程量节点 '{node_name}' 的取费表")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# all_results[node_name] = node_results
|
||||||
|
|
||||||
|
# # 输出计算结果
|
||||||
|
# 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:
|
||||||
|
# json.dump(all_results, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
# print(f"\n计算结果已保存到 {output_file}")
|
||||||
|
|
||||||
|
# return output_file
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_quantity_fees(
|
||||||
|
json_file_path: str,
|
||||||
|
project_name: str,
|
||||||
|
engineering_type: str = None,
|
||||||
|
project_guid: str = None,
|
||||||
|
calculation_strategy=None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
计算工程量取费表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_file_path: JSON文件路径
|
||||||
|
project_name: 项目名称
|
||||||
|
engineering_type: 工程类型
|
||||||
|
project_guid: 项目GUID,用于区分同名项目
|
||||||
|
calculation_strategy: 计算策略,如果为None则使用默认策略
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 输出文件路径
|
||||||
|
"""
|
||||||
|
# 如果没有提供计算策略,使用默认策略
|
||||||
|
if calculation_strategy is None:
|
||||||
|
from calculation_strategy import DefaultCalculationStrategy
|
||||||
|
|
||||||
|
calculation_strategy = DefaultCalculationStrategy()
|
||||||
|
|
||||||
|
# 设置输出目录和文件名
|
||||||
|
output_dir = "计算结果"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 获取项目划分节点的GUID
|
||||||
|
_, _, _, _, target_node = load_project_data(json_file_path, project_name, project_guid)
|
||||||
|
if target_node:
|
||||||
|
node_guid = target_node.get("GUID") or target_node.get("guid", "")
|
||||||
|
# 如果传入了GUID但与节点的GUID不一致,优先使用传入的GUID
|
||||||
|
project_guid = project_guid if project_guid else node_guid
|
||||||
|
|
||||||
|
# 将文件名中的非法字符替换为下划线
|
||||||
|
safe_project_name = (
|
||||||
|
project_name.replace("/", "_")
|
||||||
|
.replace("\\", "_")
|
||||||
|
.replace(":", "_")
|
||||||
|
.replace("*", "_")
|
||||||
|
.replace("?", "_")
|
||||||
|
.replace('"', "_")
|
||||||
|
.replace("<", "_")
|
||||||
|
.replace(">", "_")
|
||||||
|
.replace("|", "_")
|
||||||
|
)
|
||||||
|
|
||||||
|
# 将GUID中的非法字符替换为下划线
|
||||||
|
safe_project_guid = ""
|
||||||
|
if project_guid:
|
||||||
|
safe_project_guid = (
|
||||||
|
project_guid.replace("/", "_")
|
||||||
|
.replace("\\", "_")
|
||||||
|
.replace(":", "_")
|
||||||
|
.replace("*", "_")
|
||||||
|
.replace("?", "_")
|
||||||
|
.replace('"', "_")
|
||||||
|
.replace("<", "_")
|
||||||
|
.replace(">", "_")
|
||||||
|
.replace("|", "_")
|
||||||
|
.replace("{", "")
|
||||||
|
.replace("}", "")
|
||||||
|
)
|
||||||
|
# 添加下划线作为分隔符
|
||||||
|
safe_project_guid = f"_{safe_project_guid}"
|
||||||
|
|
||||||
|
output_file = os.path.join(
|
||||||
|
output_dir,
|
||||||
|
f"{safe_project_name}{safe_project_guid}_{engineering_type}_calculation_results.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 在处理清单工程时,保存清单数量供后续使用
|
||||||
|
bill_quantities = {}
|
||||||
|
|
||||||
|
# 根据工程类型获取取费表和项目节点
|
||||||
|
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)
|
||||||
|
|
||||||
|
if not cost_table_children or not project_children:
|
||||||
|
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的取费表或项目划分节点")
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif engineering_type == "清单工程":
|
||||||
|
# 清单工程 - 获取清单节点的取费表,传递project_guid参数
|
||||||
|
project_children = get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid)
|
||||||
|
if not project_children:
|
||||||
|
print(f"未找到项目 '{project_name}' (GUID: {project_guid}) 的项目划分节点")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 为每个工程量节点找到对应的清单节点取费表
|
||||||
|
cost_tables = {}
|
||||||
|
|
||||||
|
for project_node in project_children:
|
||||||
|
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||||
|
|
||||||
|
# 获取清单节点信息
|
||||||
|
bill_id = project_node.get("bill_id")
|
||||||
|
fee_table_name = project_node.get("取费表名称")
|
||||||
|
|
||||||
|
if not bill_id:
|
||||||
|
print(f"工程量节点 '{node_name}' 缺少清单节点ID")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not fee_table_name:
|
||||||
|
print(f"工程量节点 '{node_name}' 没有取费表名称")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 使用清单节点的取费表名称直接查找取费表
|
||||||
|
if bill_id not in cost_tables:
|
||||||
|
print(f"查找清单节点 '{project_node.get('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)
|
||||||
|
|
||||||
|
if not cost_table:
|
||||||
|
print(f"未找到取费表 '{fee_table_name}'")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cost_table_children = cost_table.get("children", None)
|
||||||
|
if not cost_table_children:
|
||||||
|
print(f"取费表 '{fee_table_name}' 没有子节点")
|
||||||
|
continue
|
||||||
|
|
||||||
|
cost_tables[bill_id] = cost_table_children
|
||||||
|
print(f"成功获取清单节点 '{project_node.get('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", {})
|
||||||
|
|
||||||
|
# 递归函数获取所有清单节点
|
||||||
|
def get_bill_nodes(node, results=None):
|
||||||
|
if results is None:
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if isinstance(node, dict):
|
||||||
|
# 检查是否是清单节点
|
||||||
|
is_bill = (
|
||||||
|
node.get("类型") == "8" or node.get("类型") == "清单" or "清单名称" in node or "清单编码" in node
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_bill and ("id" in node or "GUID" in node):
|
||||||
|
bill_id = node.get("id") or node.get("GUID")
|
||||||
|
if "数量" in node:
|
||||||
|
try:
|
||||||
|
quantity = float(node["数量"])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
results.append((bill_id, quantity))
|
||||||
|
|
||||||
|
# 直接设置到全局变量和缓存
|
||||||
|
global _BILL_QUANTITY
|
||||||
|
_BILL_QUANTITY = quantity
|
||||||
|
bill_quantity_cache[bill_id] = quantity
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
os.environ["BILL_QUANTITY"] = str(quantity)
|
||||||
|
|
||||||
|
print(f"设置清单 {bill_id} 的数量为全局变量: {quantity}")
|
||||||
|
|
||||||
|
# 递归处理子节点
|
||||||
|
if "children" in node and isinstance(node["children"], list):
|
||||||
|
for child in node["children"]:
|
||||||
|
get_bill_nodes(child, results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
# 获取和设置清单数量
|
||||||
|
get_bill_nodes(bill_data)
|
||||||
|
|
||||||
|
# 如果有计算策略,设置清单数量
|
||||||
|
if calculation_strategy and hasattr(calculation_strategy, "set_bill_quantity"):
|
||||||
|
calculation_strategy.set_bill_quantity(_BILL_QUANTITY)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"不支持的工程类型: {engineering_type}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 在获取 project_children 后添加预处理代码
|
||||||
|
if project_children and calculation_strategy and hasattr(calculation_strategy, "preprocess_quantity_fee_node"):
|
||||||
|
print(f"对项目节点进行预处理...")
|
||||||
|
|
||||||
|
# 递归处理所有节点
|
||||||
|
def process_node(node):
|
||||||
|
calculation_strategy.preprocess_quantity_fee_node(node)
|
||||||
|
if "children" in node and isinstance(node["children"], list):
|
||||||
|
for child in node["children"]:
|
||||||
|
process_node(child)
|
||||||
|
|
||||||
|
# 处理每个项目节点
|
||||||
|
for node in project_children:
|
||||||
|
process_node(node)
|
||||||
|
|
||||||
|
# 初始化缓存
|
||||||
|
calculated_fees.clear() # 使用全局缓存,而不是计算策略中的缓存
|
||||||
|
|
||||||
|
# 计算每个项目节点的费用
|
||||||
|
all_results = {}
|
||||||
|
|
||||||
|
for project_node in project_children:
|
||||||
|
# 为每个节点清空缓存,确保计算独立
|
||||||
|
calculated_fees.clear()
|
||||||
|
|
||||||
|
node_name = project_node.get("项目名称", project_node.get("name", "未知节点"))
|
||||||
|
|
||||||
|
# 添加清单数量信息
|
||||||
|
if engineering_type == "清单工程":
|
||||||
|
bill_id = project_node.get("bill_id")
|
||||||
|
if bill_id in bill_quantities:
|
||||||
|
project_node["bill_quantity"] = bill_quantities[bill_id]
|
||||||
|
print(f"为工程量节点 '{node_name}' 设置清单数量: {bill_quantities[bill_id]}")
|
||||||
|
|
||||||
|
# 根据工程类型获取对应的取费表
|
||||||
|
if engineering_type == "预算工程":
|
||||||
|
# 预算工程 - 使用项目划分节点的取费表
|
||||||
|
node_results = calculation_strategy.calculate_all_fees(
|
||||||
|
project_node, {"children": cost_table_children}, json_file_path, engineering_type
|
||||||
|
)
|
||||||
|
else: # 清单工程
|
||||||
|
# 清单工程 - 使用清单节点的取费表
|
||||||
|
parent_id = project_node.get("parent_id")
|
||||||
|
if parent_id and parent_id in cost_tables:
|
||||||
|
node_results = calculation_strategy.calculate_all_fees(
|
||||||
|
project_node, {"children": cost_tables[parent_id]}, json_file_path, engineering_type
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"无法获取工程量节点 '{node_name}' 的取费表")
|
||||||
|
continue
|
||||||
|
|
||||||
|
all_results[node_name] = node_results
|
||||||
|
|
||||||
|
# 输出计算结果
|
||||||
|
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:
|
||||||
|
json.dump(all_results, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"\n计算结果已保存到 {output_file}")
|
||||||
|
|
||||||
|
# 如果有计算策略,应用后处理规则
|
||||||
|
if calculation_strategy:
|
||||||
|
calculation_strategy.post_process_quantity_fees(output_file, project_name)
|
||||||
|
|
||||||
|
return output_file
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,655 @@
|
|||||||
|
from calculator_base import CalculatorBase
|
||||||
|
from software_types import (
|
||||||
|
SoftwareType,
|
||||||
|
MAIN_GRID_BUDGET,
|
||||||
|
MAIN_GRID_BILL,
|
||||||
|
DISTRIBUTION_BUDGET,
|
||||||
|
DISTRIBUTION_BILL,
|
||||||
|
TECHNICAL_RENOVATION_BUDGET,
|
||||||
|
TECHNICAL_RENOVATION_BILL,
|
||||||
|
)
|
||||||
|
from calculation_strategy import CalculationStrategy, DefaultCalculationStrategy
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Any, Dict, Optional, Set
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
# 添加一个全局变量和一个缓存字典来存储清单数量
|
||||||
|
_BILL_QUANTITY = 1.0
|
||||||
|
bill_quantity_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
# 为每种软件类型定义特定的计算策略
|
||||||
|
class MainGridBudgetCalculationStrategy(DefaultCalculationStrategy):
|
||||||
|
"""主网预算计算策略"""
|
||||||
|
|
||||||
|
def preprocess_quantity_fee_node(self, node: dict) -> None:
|
||||||
|
"""
|
||||||
|
工程量取费表节点预处理规则:
|
||||||
|
- 类型为"1"或"主材"时,供货方映射,主材属性补全
|
||||||
|
- 类型为"5"或"设备"时,供货方映射,设备类型映射
|
||||||
|
"""
|
||||||
|
node_type = str(node.get("类型", ""))
|
||||||
|
# 定额处理
|
||||||
|
if node_type == "0" or node_type == "定额":
|
||||||
|
supplier = str(node.get("费用类型", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["费用类型"] = "不取费"
|
||||||
|
elif supplier == "0":
|
||||||
|
node["费用类型"] = "取费"
|
||||||
|
|
||||||
|
# 处理特征段
|
||||||
|
seg_val = node.get("特征段", "")
|
||||||
|
if isinstance(seg_val, str):
|
||||||
|
num = re.search(r"\d+", seg_val)
|
||||||
|
node["特征段"] = num.group() if num else ""
|
||||||
|
|
||||||
|
# 主材处理
|
||||||
|
if node_type == "1" or node_type == "主材":
|
||||||
|
supplier = str(node.get("供货方", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["供货方"] = "甲供"
|
||||||
|
elif supplier == "2":
|
||||||
|
node["供货方"] = "乙供"
|
||||||
|
# 补全主材属性
|
||||||
|
for key in ["拆分", "设备性材料"]:
|
||||||
|
if key not in node:
|
||||||
|
node[key] = 0
|
||||||
|
# 设备处理
|
||||||
|
if node_type == "5" or node_type == "设备":
|
||||||
|
supplier = str(node.get("供货方", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["供货方"] = "甲供"
|
||||||
|
elif supplier == "2":
|
||||||
|
node["供货方"] = "乙供"
|
||||||
|
# 设备类型映射
|
||||||
|
if str(node.get("设备类型", "")) == "0":
|
||||||
|
node["设备类型"] = "普通设备"
|
||||||
|
|
||||||
|
|
||||||
|
class MainGridBillCalculationStrategy(DefaultCalculationStrategy):
|
||||||
|
"""主网清单计算策略"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.bill_quantity = 1.0 # 默认值
|
||||||
|
|
||||||
|
def set_bill_quantity(self, quantity):
|
||||||
|
"""设置清单数量"""
|
||||||
|
try:
|
||||||
|
self.bill_quantity = float(quantity)
|
||||||
|
print(f"设置计算策略的清单数量: {self.bill_quantity}")
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
print(f"无法将 {quantity} 转换为浮点数")
|
||||||
|
|
||||||
|
def calculate_external_variable(self, var_name: str, context: Any) -> float:
|
||||||
|
"""计算表外变量,并乘以父级清单的数量"""
|
||||||
|
# 使用默认实现计算表外变量
|
||||||
|
from bcl_utils import calculator
|
||||||
|
|
||||||
|
result = calculator.calculate(var_name, context)
|
||||||
|
|
||||||
|
# 获取清单数量 - 首先从全局变量获取
|
||||||
|
global _BILL_QUANTITY # 在模块级别定义一个全局变量
|
||||||
|
parent_quantity = _BILL_QUANTITY if "_BILL_QUANTITY" in globals() else 1.0
|
||||||
|
|
||||||
|
print(f"\n===== 获取清单数量 =====")
|
||||||
|
print(f"从全局变量获取清单数量: {parent_quantity}")
|
||||||
|
|
||||||
|
# 如果全局变量没有设置正确的值,尝试从bill_quantity_cache获取
|
||||||
|
if parent_quantity == 1.0 and hasattr(context, "__dict__"):
|
||||||
|
module = sys.modules.get("quantity_fee_calculator")
|
||||||
|
if module and hasattr(module, "bill_quantity_cache"):
|
||||||
|
bill_cache = getattr(module, "bill_quantity_cache")
|
||||||
|
bill_id = None
|
||||||
|
|
||||||
|
# 尝试从上下文中获取bill_id
|
||||||
|
if hasattr(context, "bill_id"):
|
||||||
|
bill_id = context.bill_id
|
||||||
|
elif hasattr(context, "datasource") and context.datasource:
|
||||||
|
for item in context.datasource:
|
||||||
|
if hasattr(item, "object") and hasattr(item.object, "bill_id"):
|
||||||
|
bill_id = item.object.bill_id
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果找到bill_id并且在缓存中,使用缓存的数量
|
||||||
|
if bill_id and bill_id in bill_cache:
|
||||||
|
parent_quantity = bill_cache[bill_id]
|
||||||
|
print(f"从bill_quantity_cache获取清单{bill_id}的数量: {parent_quantity}")
|
||||||
|
|
||||||
|
# 设置一个临时环境变量作为最后的手段
|
||||||
|
if parent_quantity == 1.0:
|
||||||
|
import os
|
||||||
|
|
||||||
|
env_quantity = os.environ.get("BILL_QUANTITY")
|
||||||
|
if env_quantity:
|
||||||
|
try:
|
||||||
|
parent_quantity = float(env_quantity)
|
||||||
|
print(f"从环境变量获取清单数量: {parent_quantity}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"===== 获取清单数量结束 =====\n")
|
||||||
|
|
||||||
|
# 将结果乘以父级清单的数量
|
||||||
|
final_result = float(result) * parent_quantity if result is not None else 0.0
|
||||||
|
print(f"表外变量 '{var_name}' 的值为 {result},父级清单数量为 {parent_quantity},最终结果为 {final_result}")
|
||||||
|
return final_result
|
||||||
|
|
||||||
|
def preprocess_quantity_fee_node(self, node: dict) -> None:
|
||||||
|
"""
|
||||||
|
工程量取费表节点预处理规则:
|
||||||
|
- 类型为"1"或"主材"时,供货方映射,主材属性补全
|
||||||
|
- 类型为"5"或"设备"时,供货方映射,设备类型映射
|
||||||
|
"""
|
||||||
|
node_type = str(node.get("类型", ""))
|
||||||
|
# 主材处理
|
||||||
|
if node_type == "1" or node_type == "主材":
|
||||||
|
supplier = str(node.get("供货方", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["供货方"] = "甲供"
|
||||||
|
elif supplier == "2":
|
||||||
|
node["供货方"] = "乙供"
|
||||||
|
# 补全主材属性
|
||||||
|
for key in ["拆分", "设备性材料", "损耗", "预算价含税"]:
|
||||||
|
if key not in node:
|
||||||
|
node[key] = 0
|
||||||
|
# 设备处理
|
||||||
|
if node_type == "5" or node_type == "设备":
|
||||||
|
supplier = str(node.get("供货方", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["供货方"] = "甲供"
|
||||||
|
elif supplier == "2":
|
||||||
|
node["供货方"] = "乙供"
|
||||||
|
# 设备类型映射
|
||||||
|
if str(node.get("设备类型", "")) == "0":
|
||||||
|
node["设备类型"] = "普通设备"
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""将取费表中的'综合单价'替换为'合价'"""
|
||||||
|
try:
|
||||||
|
# 读取输出文件
|
||||||
|
with open(output_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 递归查找并替换"综合单价"为"合价"
|
||||||
|
def replace_key(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
new_obj = {}
|
||||||
|
for key, value in obj.items():
|
||||||
|
# 替换键名
|
||||||
|
new_key = "合价" if key == "综合单价" else key
|
||||||
|
# 递归处理值
|
||||||
|
new_obj[new_key] = replace_key(value)
|
||||||
|
return new_obj
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
return [replace_key(item) for item in obj]
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# 替换所有出现的"综合单价"
|
||||||
|
new_data = replace_key(data)
|
||||||
|
|
||||||
|
# 写回文件
|
||||||
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(new_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"已将取费表中的'综合单价'替换为'合价'")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"修改取费表时出错: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionBudgetCalculationStrategy(DefaultCalculationStrategy):
|
||||||
|
"""配网预算计算策略"""
|
||||||
|
|
||||||
|
# def calculate_fee_base(
|
||||||
|
# self, fee_base: str, cost_table: dict, project_node: dict, context, in_calculation=None
|
||||||
|
# ) -> float:
|
||||||
|
# """计算取费基数,可以重写以添加配网预算特定的计算规则"""
|
||||||
|
# # 示例:如果取费基数是特定值,使用特定的计算规则
|
||||||
|
# if fee_base == "材料费":
|
||||||
|
# print(f"应用配网预算特定的取费基数计算规则: {fee_base}")
|
||||||
|
# # 这里可以添加特定的计算逻辑
|
||||||
|
|
||||||
|
# # 暂时仍然使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
# # 对于其他取费基数,使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionBillCalculationStrategy(DefaultCalculationStrategy):
|
||||||
|
"""配网清单计算策略"""
|
||||||
|
|
||||||
|
# def calculate_fee_base(
|
||||||
|
# self, fee_base: str, cost_table: dict, project_node: dict, context, in_calculation=None
|
||||||
|
# ) -> float:
|
||||||
|
# """计算取费基数,可以重写以添加配网清单特定的计算规则"""
|
||||||
|
# # 示例:如果取费基数是特定值,使用特定的计算规则
|
||||||
|
# if fee_base == "机械费":
|
||||||
|
# print(f"应用配网清单特定的取费基数计算规则: {fee_base}")
|
||||||
|
# # 这里可以添加特定的计算逻辑
|
||||||
|
|
||||||
|
# # 暂时仍然使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
# # 对于其他取费基数,使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalRenovationBudgetCalculationStrategy(DefaultCalculationStrategy):
|
||||||
|
"""技改预算计算策略"""
|
||||||
|
|
||||||
|
# def calculate_fee_base(
|
||||||
|
# self, fee_base: str, cost_table: dict, project_node: dict, context, in_calculation=None
|
||||||
|
# ) -> float:
|
||||||
|
# """计算取费基数,可以重写以添加技改预算特定的计算规则"""
|
||||||
|
# # 示例:如果取费基数是特定值,使用特定的计算规则
|
||||||
|
# if fee_base == "人工费+材料费":
|
||||||
|
# print(f"应用技改预算特定的取费基数计算规则: {fee_base}")
|
||||||
|
# # 这里可以添加特定的计算逻辑
|
||||||
|
|
||||||
|
# # 暂时仍然使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
# # 对于其他取费基数,使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalRenovationBillCalculationStrategy(DefaultCalculationStrategy):
|
||||||
|
"""技改清单计算策略"""
|
||||||
|
|
||||||
|
# def calculate_fee_base(
|
||||||
|
# self, fee_base: str, cost_table: dict, project_node: dict, context, in_calculation=None
|
||||||
|
# ) -> float:
|
||||||
|
# """计算取费基数,可以重写以添加技改清单特定的计算规则"""
|
||||||
|
# # 示例:如果取费基数是特定值,使用特定的计算规则
|
||||||
|
# if fee_base == "人工费+材料费+机械费":
|
||||||
|
# print(f"应用技改清单特定的取费基数计算规则: {fee_base}")
|
||||||
|
# # 这里可以添加特定的计算逻辑
|
||||||
|
|
||||||
|
# # 暂时仍然使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
|
||||||
|
# # 对于其他取费基数,使用默认实现
|
||||||
|
# return super().calculate_fee_base(fee_base, cost_table, project_node, context, in_calculation)
|
||||||
|
def calculate_external_variable(self, var_name: str, context: Any) -> float:
|
||||||
|
"""计算表外变量,并乘以父级清单的数量"""
|
||||||
|
# 使用默认实现计算表外变量
|
||||||
|
from bcl_utils import calculator
|
||||||
|
|
||||||
|
result = calculator.calculate(var_name, context)
|
||||||
|
|
||||||
|
# 获取清单数量 - 首先从全局变量获取
|
||||||
|
global _BILL_QUANTITY # 在模块级别定义一个全局变量
|
||||||
|
parent_quantity = _BILL_QUANTITY if "_BILL_QUANTITY" in globals() else 1.0
|
||||||
|
|
||||||
|
print(f"\n===== 获取清单数量 =====")
|
||||||
|
print(f"从全局变量获取清单数量: {parent_quantity}")
|
||||||
|
|
||||||
|
# 如果全局变量没有设置正确的值,尝试从bill_quantity_cache获取
|
||||||
|
if parent_quantity == 1.0 and hasattr(context, "__dict__"):
|
||||||
|
module = sys.modules.get("quantity_fee_calculator")
|
||||||
|
if module and hasattr(module, "bill_quantity_cache"):
|
||||||
|
bill_cache = getattr(module, "bill_quantity_cache")
|
||||||
|
bill_id = None
|
||||||
|
|
||||||
|
# 尝试从上下文中获取bill_id
|
||||||
|
if hasattr(context, "bill_id"):
|
||||||
|
bill_id = context.bill_id
|
||||||
|
elif hasattr(context, "datasource") and context.datasource:
|
||||||
|
for item in context.datasource:
|
||||||
|
if hasattr(item, "object") and hasattr(item.object, "bill_id"):
|
||||||
|
bill_id = item.object.bill_id
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果找到bill_id并且在缓存中,使用缓存的数量
|
||||||
|
if bill_id and bill_id in bill_cache:
|
||||||
|
parent_quantity = bill_cache[bill_id]
|
||||||
|
print(f"从bill_quantity_cache获取清单{bill_id}的数量: {parent_quantity}")
|
||||||
|
|
||||||
|
# 设置一个临时环境变量作为最后的手段
|
||||||
|
if parent_quantity == 1.0:
|
||||||
|
import os
|
||||||
|
|
||||||
|
env_quantity = os.environ.get("BILL_QUANTITY")
|
||||||
|
if env_quantity:
|
||||||
|
try:
|
||||||
|
parent_quantity = float(env_quantity)
|
||||||
|
print(f"从环境变量获取清单数量: {parent_quantity}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"===== 获取清单数量结束 =====\n")
|
||||||
|
|
||||||
|
# 将结果乘以父级清单的数量
|
||||||
|
final_result = float(result) * parent_quantity if result is not None else 0.0
|
||||||
|
print(f"表外变量 '{var_name}' 的值为 {result},父级清单数量为 {parent_quantity},最终结果为 {final_result}")
|
||||||
|
return final_result
|
||||||
|
|
||||||
|
def preprocess_quantity_fee_node(self, node: dict) -> None:
|
||||||
|
"""
|
||||||
|
工程量取费表节点预处理规则:
|
||||||
|
- 类型为"1"或"主材"时,供货方映射,主材属性补全
|
||||||
|
- 类型为"5"或"设备"时,供货方映射,设备类型映射
|
||||||
|
"""
|
||||||
|
node_type = str(node.get("类型", ""))
|
||||||
|
# 主材处理
|
||||||
|
if node_type == "1" or node_type == "主材":
|
||||||
|
supplier = str(node.get("供货方", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["供货方"] = "甲供"
|
||||||
|
elif supplier == "2":
|
||||||
|
node["供货方"] = "乙供"
|
||||||
|
# 补全主材属性
|
||||||
|
for key in ["拆分", "设备性材料", "损耗", "预算价含税"]:
|
||||||
|
if key not in node:
|
||||||
|
node[key] = 0
|
||||||
|
# 设备处理
|
||||||
|
if node_type == "5" or node_type == "设备":
|
||||||
|
supplier = str(node.get("供货方", ""))
|
||||||
|
if supplier == "1":
|
||||||
|
node["供货方"] = "甲供"
|
||||||
|
elif supplier == "2":
|
||||||
|
node["供货方"] = "乙供"
|
||||||
|
# 设备类型映射
|
||||||
|
if str(node.get("设备类型", "")) == "0":
|
||||||
|
node["设备类型"] = "普通设备"
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""将取费表中的'综合单价'替换为'合价'"""
|
||||||
|
try:
|
||||||
|
# 读取输出文件
|
||||||
|
with open(output_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 递归查找并替换"综合单价"为"合价"
|
||||||
|
def replace_key(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
new_obj = {}
|
||||||
|
for key, value in obj.items():
|
||||||
|
# 替换键名
|
||||||
|
new_key = "合价" if key == "综合单价" else key
|
||||||
|
# 递归处理值
|
||||||
|
new_obj[new_key] = replace_key(value)
|
||||||
|
return new_obj
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
return [replace_key(item) for item in obj]
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# 替换所有出现的"综合单价"
|
||||||
|
new_data = replace_key(data)
|
||||||
|
|
||||||
|
# 写回文件
|
||||||
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(new_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"已将取费表中的'综合单价'替换为'合价'")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"修改取费表时出错: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class MainGridBudgetCalculator(CalculatorBase):
|
||||||
|
"""主网预算计算器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(MAIN_GRID_BUDGET)
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""创建主网预算计算策略"""
|
||||||
|
return MainGridBudgetCalculationStrategy()
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用主网预算特定的工程量取费规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费规则 - 对节点进行预处理")
|
||||||
|
|
||||||
|
# 创建预处理函数
|
||||||
|
def preprocess_nodes_recursive(json_file_path):
|
||||||
|
try:
|
||||||
|
# 读取 JSON 文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 获取工程量数据
|
||||||
|
project_data = data.get("projectData", {})
|
||||||
|
quantity_data = project_data.get("quantity", {})
|
||||||
|
|
||||||
|
# 递归处理所有节点
|
||||||
|
def process_node(node):
|
||||||
|
# 调用计算策略的预处理方法
|
||||||
|
if isinstance(node, dict):
|
||||||
|
self.calculation_strategy.preprocess_quantity_fee_node(node)
|
||||||
|
# 处理子节点
|
||||||
|
if "children" in node and isinstance(node["children"], list):
|
||||||
|
for child in node["children"]:
|
||||||
|
process_node(child)
|
||||||
|
|
||||||
|
# 处理所有工程量节点
|
||||||
|
process_node(quantity_data)
|
||||||
|
|
||||||
|
# 写回 JSON 文件
|
||||||
|
with open(json_file_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"已完成对 {json_file_path} 中所有节点的预处理")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"预处理节点时出错: {e}")
|
||||||
|
|
||||||
|
# 不在这里调用预处理函数,因为我们还不知道 JSON 文件路径
|
||||||
|
# 这个函数会在 calculate_quantity_fee_tables 中调用
|
||||||
|
# preprocess_nodes_recursive(json_file_path)
|
||||||
|
|
||||||
|
# 只需实现一个空函数,返回预处理函数以便后续调用
|
||||||
|
return preprocess_nodes_recursive
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用主网预算特定的人材机合价规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价规则")
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用主网预算特定的工程量取费后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费后处理规则")
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用主网预算特定的人材机合价后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价后处理规则")
|
||||||
|
|
||||||
|
|
||||||
|
class MainGridBillCalculator(CalculatorBase):
|
||||||
|
"""主网清单计算器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(MAIN_GRID_BILL)
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""创建主网清单计算策略"""
|
||||||
|
return MainGridBillCalculationStrategy()
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用主网清单特定的工程量取费规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费规则 - 对节点进行预处理")
|
||||||
|
|
||||||
|
# 创建预处理函数,这个函数会处理JSON文件中的节点
|
||||||
|
def preprocess_nodes_recursive(json_file_path):
|
||||||
|
try:
|
||||||
|
# 读取 JSON 文件
|
||||||
|
with open(json_file_path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 获取工程量数据
|
||||||
|
project_data = data.get("projectData", {})
|
||||||
|
quantity_data = project_data.get("quantity", {})
|
||||||
|
bill_data = project_data.get("bill", {})
|
||||||
|
|
||||||
|
print(f"开始预处理工程量和清单节点...")
|
||||||
|
|
||||||
|
# 递归处理所有节点
|
||||||
|
def process_node(node):
|
||||||
|
if isinstance(node, dict):
|
||||||
|
self.calculation_strategy.preprocess_quantity_fee_node(node)
|
||||||
|
if "children" in node and isinstance(node["children"], list):
|
||||||
|
for child in node["children"]:
|
||||||
|
process_node(child)
|
||||||
|
|
||||||
|
# 处理工程量节点
|
||||||
|
process_node(quantity_data)
|
||||||
|
# 也处理清单节点,因为对于清单工程,清单节点信息也很重要
|
||||||
|
process_node(bill_data)
|
||||||
|
|
||||||
|
# 写回文件
|
||||||
|
with open(json_file_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"已完成对 {json_file_path} 中所有节点的预处理")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"预处理节点时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# 返回预处理函数,供 calculate_quantity_fee_tables 调用
|
||||||
|
return preprocess_nodes_recursive
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用主网清单特定的人材机合价规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价规则")
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用主网清单特定的工程量取费后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费后处理规则")
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用主网清单特定的人材机合价后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价后处理规则")
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionBudgetCalculator(CalculatorBase):
|
||||||
|
"""配网预算计算器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(DISTRIBUTION_BUDGET)
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""创建配网预算计算策略"""
|
||||||
|
return DistributionBudgetCalculationStrategy()
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用配网预算特定的工程量取费规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用配网预算特定的人材机合价规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价规则")
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用配网预算特定的工程量取费后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费后处理规则")
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用配网预算特定的人材机合价后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价后处理规则")
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionBillCalculator(CalculatorBase):
|
||||||
|
"""配网清单计算器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(DISTRIBUTION_BILL)
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""创建配网清单计算策略"""
|
||||||
|
return DistributionBillCalculationStrategy()
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用配网清单特定的工程量取费规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用配网清单特定的人材机合价规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价规则")
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用配网清单特定的工程量取费后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费后处理规则")
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用配网清单特定的人材机合价后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价后处理规则")
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalRenovationBudgetCalculator(CalculatorBase):
|
||||||
|
"""技改预算计算器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(TECHNICAL_RENOVATION_BUDGET)
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""创建技改预算计算策略"""
|
||||||
|
return TechnicalRenovationBudgetCalculationStrategy()
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用技改预算特定的工程量取费规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用技改预算特定的人材机合价规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价规则")
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用技改预算特定的工程量取费后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费后处理规则")
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用技改预算特定的人材机合价后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价后处理规则")
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalRenovationBillCalculator(CalculatorBase):
|
||||||
|
"""技改清单计算器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(TECHNICAL_RENOVATION_BILL)
|
||||||
|
|
||||||
|
def create_calculation_strategy(self) -> CalculationStrategy:
|
||||||
|
"""创建技改清单计算策略"""
|
||||||
|
return TechnicalRenovationBillCalculationStrategy()
|
||||||
|
|
||||||
|
def apply_quantity_fee_rules(self) -> None:
|
||||||
|
"""应用技改清单特定的工程量取费规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费规则")
|
||||||
|
|
||||||
|
def apply_resource_fee_rules(self) -> None:
|
||||||
|
"""应用技改清单特定的人材机合价规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价规则")
|
||||||
|
|
||||||
|
def post_process_quantity_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用技改清单特定的工程量取费后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的工程量取费后处理规则")
|
||||||
|
|
||||||
|
def post_process_resource_fees(self, output_file: str, project_name: str) -> None:
|
||||||
|
"""应用技改清单特定的人材机合价后处理规则"""
|
||||||
|
print(f"应用{self.software_type.name}特定的人材机合价后处理规则")
|
||||||
|
|
||||||
|
|
||||||
|
def get_calculator(software_type: SoftwareType) -> CalculatorBase:
|
||||||
|
"""
|
||||||
|
根据软件类型获取对应的计算器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
software_type: 软件类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CalculatorBase: 计算器实例
|
||||||
|
"""
|
||||||
|
calculators = {
|
||||||
|
MAIN_GRID_BUDGET.name: MainGridBudgetCalculator(),
|
||||||
|
MAIN_GRID_BILL.name: MainGridBillCalculator(),
|
||||||
|
DISTRIBUTION_BUDGET.name: DistributionBudgetCalculator(),
|
||||||
|
DISTRIBUTION_BILL.name: DistributionBillCalculator(),
|
||||||
|
TECHNICAL_RENOVATION_BUDGET.name: TechnicalRenovationBudgetCalculator(),
|
||||||
|
TECHNICAL_RENOVATION_BILL.name: TechnicalRenovationBillCalculator(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculators.get(software_type.name)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
from enum import Enum, auto
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareCategory(Enum):
|
||||||
|
"""软件大类"""
|
||||||
|
|
||||||
|
MAIN_GRID = "主网" # 主网
|
||||||
|
DISTRIBUTION = "配网" # 配网
|
||||||
|
TECHNICAL_RENOVATION = "技改" # 技改
|
||||||
|
|
||||||
|
|
||||||
|
class EngineeringType(Enum):
|
||||||
|
"""工程类型"""
|
||||||
|
|
||||||
|
BUDGET = "预算" # 预算工程
|
||||||
|
BILL = "清单" # 清单工程
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareType:
|
||||||
|
"""软件类型"""
|
||||||
|
|
||||||
|
def __init__(self, category: SoftwareCategory, engineering_type: EngineeringType):
|
||||||
|
self.category = category
|
||||||
|
self.engineering_type = engineering_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""获取软件类型名称"""
|
||||||
|
return f"{self.category.value}{self.engineering_type.value}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config_dir(self) -> str:
|
||||||
|
"""获取配置目录"""
|
||||||
|
return f"计算配置/{self.category.value}/{self.name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test_dir(self) -> str:
|
||||||
|
"""获取测试案例目录"""
|
||||||
|
return f"测试案例/{self.category.value}/{self.engineering_type.value}"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"SoftwareType({self.category.value}, {self.engineering_type.value})"
|
||||||
|
|
||||||
|
|
||||||
|
# 预定义的六种软件类型
|
||||||
|
MAIN_GRID_BUDGET = SoftwareType(SoftwareCategory.MAIN_GRID, EngineeringType.BUDGET)
|
||||||
|
MAIN_GRID_BILL = SoftwareType(SoftwareCategory.MAIN_GRID, EngineeringType.BILL)
|
||||||
|
DISTRIBUTION_BUDGET = SoftwareType(SoftwareCategory.DISTRIBUTION, EngineeringType.BUDGET)
|
||||||
|
DISTRIBUTION_BILL = SoftwareType(SoftwareCategory.DISTRIBUTION, EngineeringType.BILL)
|
||||||
|
TECHNICAL_RENOVATION_BUDGET = SoftwareType(SoftwareCategory.TECHNICAL_RENOVATION, EngineeringType.BUDGET)
|
||||||
|
TECHNICAL_RENOVATION_BILL = SoftwareType(SoftwareCategory.TECHNICAL_RENOVATION, EngineeringType.BILL)
|
||||||
|
|
||||||
|
# 所有软件类型列表
|
||||||
|
ALL_SOFTWARE_TYPES = [
|
||||||
|
MAIN_GRID_BUDGET,
|
||||||
|
MAIN_GRID_BILL,
|
||||||
|
DISTRIBUTION_BUDGET,
|
||||||
|
DISTRIBUTION_BILL,
|
||||||
|
TECHNICAL_RENOVATION_BUDGET,
|
||||||
|
TECHNICAL_RENOVATION_BILL,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_software_type(category_name: str, engineering_type_name: str) -> SoftwareType:
|
||||||
|
"""
|
||||||
|
根据类别名称和工程类型名称获取软件类型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
category_name: 类别名称(主网/配网/技改)
|
||||||
|
engineering_type_name: 工程类型名称(预算/清单)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SoftwareType: 软件类型
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
category = SoftwareCategory(category_name)
|
||||||
|
eng_type = EngineeringType(engineering_type_name)
|
||||||
|
return SoftwareType(category, eng_type)
|
||||||
|
except ValueError:
|
||||||
|
valid_categories = [c.value for c in SoftwareCategory]
|
||||||
|
valid_eng_types = [t.value for t in EngineeringType]
|
||||||
|
raise ValueError(
|
||||||
|
f"无效的软件类型: {category_name}/{engineering_type_name}. "
|
||||||
|
f"有效的类别: {valid_categories}, 有效的工程类型: {valid_eng_types}"
|
||||||
|
)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import json
|
||||||
|
from bcl_calculator import BCLDataSourceItem
|
||||||
|
|
||||||
|
|
||||||
|
def process_project_data(json_data):
|
||||||
|
"""
|
||||||
|
处理 projectData 中的线路特征段数据,计算每条 bpBillZhXsTable 记录的加权值,
|
||||||
|
并返回一个字典列表,每个字典包含 '特征段' 和对应的 ItemName: 计算结果。
|
||||||
|
|
||||||
|
:param json_data: 解析后的 JSON 数据(字典格式)
|
||||||
|
:return: 字典列表,例如 [{"特征段": "1", "工地运输...": "21.8"}, ...]
|
||||||
|
"""
|
||||||
|
result_list = []
|
||||||
|
|
||||||
|
# 遍历每个线路特征段
|
||||||
|
for segment in json_data.get("projectData", {}).get("线路特征段", []):
|
||||||
|
seg_name = segment.get("Name", "")
|
||||||
|
# 提取特征段中的数字,例如 "特征段1" -> "1"
|
||||||
|
seg_number = "".join(filter(str.isdigit, seg_name))
|
||||||
|
if not seg_number:
|
||||||
|
seg_number = "0" # 默认值
|
||||||
|
|
||||||
|
# 获取 bpBillZhXsTable 列表
|
||||||
|
table_list = segment.get("bpBillZhXsTable", [])
|
||||||
|
for item in table_list:
|
||||||
|
try:
|
||||||
|
# 提取字段并转换为浮点数
|
||||||
|
KnapScale = float(item.get("KnapScale", 0))
|
||||||
|
Knap = float(item.get("Knap", 0))
|
||||||
|
HillScale = float(item.get("HillScale", 0))
|
||||||
|
Hill = float(item.get("Hill", 0))
|
||||||
|
EdelweissScale = float(item.get("EdelweissScale", 0))
|
||||||
|
edelweiss = float(item.get("edelweiss", 0))
|
||||||
|
MountainScale = float(item.get("MountainScale", 0))
|
||||||
|
Mountain = float(item.get("Mountain", 0))
|
||||||
|
SloughScale = float(item.get("SloughScale", 0))
|
||||||
|
Slough = float(item.get("Slough", 0))
|
||||||
|
RiverScale = float(item.get("RiverScale", 0))
|
||||||
|
River = float(item.get("River", 0))
|
||||||
|
DesertScale = float(item.get("DesertScale", 0))
|
||||||
|
Desert = float(item.get("Desert", 0))
|
||||||
|
|
||||||
|
# 执行计算
|
||||||
|
total = (
|
||||||
|
(KnapScale * Knap)
|
||||||
|
+ (HillScale * Hill)
|
||||||
|
+ (EdelweissScale * edelweiss)
|
||||||
|
+ (MountainScale * Mountain)
|
||||||
|
+ (SloughScale * Slough)
|
||||||
|
+ (RiverScale * River)
|
||||||
|
+ (DesertScale * Desert)
|
||||||
|
) * 0.01
|
||||||
|
|
||||||
|
# 保留一位小数,格式化为字符串
|
||||||
|
calculated_value = f"{total:.1f}"
|
||||||
|
|
||||||
|
# 获取 ItemName
|
||||||
|
item_name = item.get("ItemName", "未知项目")
|
||||||
|
|
||||||
|
# 构建结果字典
|
||||||
|
item_dict = {"特征段": seg_number, item_name: calculated_value}
|
||||||
|
|
||||||
|
# 添加到结果列表
|
||||||
|
result_list.append(item_dict)
|
||||||
|
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(f"数据转换错误,跳过该项: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
return result_list
|
||||||
|
|
||||||
|
|
||||||
|
# ============ 使用示例 ============
|
||||||
|
|
||||||
|
# 假设你已经通过 json.loads() 解析了你的 JSON 字符串
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 正确读取 JSON 文件的方式
|
||||||
|
with open("测试案例/主网预算线路.json", "r", encoding="utf-8") as file:
|
||||||
|
data = json.load(file)
|
||||||
|
|
||||||
|
# 调用函数处理数据
|
||||||
|
processed_data = process_project_data(data)
|
||||||
|
|
||||||
|
# 现在 processed_data 是一个字典列表,你可以用它来创建 BCLDataSourceItem
|
||||||
|
DXITEM = [BCLDataSourceItem(item) for item in processed_data]
|
||||||
|
|
||||||
|
print(processed_data)
|
||||||
|
print(DXITEM)
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
|||||||
|
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 + _地形材料系数()));
|
||||||
|
}
|
||||||
|
}
|
||||||
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.
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.
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.
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.
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user