Appearance
ModuForge Node 模型到建筑预算的精确映射
📊 ModuForge Node 模型分析
核心结构
rust
// ModuForge 的 Node 定义
pub struct Node {
pub id: NodeId, // 节点唯一标识
pub r#type: String, // 节点类型名称
pub attrs: Attrs, // 节点属性(键值对)
pub content: Vec<NodeId>, // 子节点ID列表
pub marks: Vec<Mark>, // 节点标记列表
}
// 节点类型规范
pub struct NodeSpec {
pub content: Option<String>, // 子节点约束表达式 "item*"
pub marks: Option<String>, // 标记约束
pub attrs: Option<HashMap<String, AttributeSpec>>, // 属性规范
pub desc: Option<String>, // 描述
}
🏗️ 建筑预算业务映射
1. 预算项目层级结构映射
预算文档 (Document)
├── 工程项目 (Project)
│ ├── 单位工程 (Unit Engineering)
│ │ ├── 分部工程 (Division)
│ │ │ ├── 分项工程 (Subdivision)
│ │ │ │ ├── 清单项目 (Item)
│ │ │ │ └── 清单项目 (Item)
│ │ │ └── 分项工程 (Subdivision)
│ │ └── 分部工程 (Division)
│ └── 单位工程 (Unit Engineering)
└── 材料库 (Material Library)
├── 材料分类 (Material Category)
└── 材料项 (Material Item)
2. Node 类型定义
rust
// 预算业务的 NodeSpec 定义
use moduforge_model::node_type::NodeSpec;
use std::collections::HashMap;
pub fn create_budget_schema() -> HashMap<String, NodeSpec> {
let mut nodes = HashMap::new();
// 1. 预算文档 (顶级节点)
nodes.insert("budget_doc".to_string(), NodeSpec {
content: Some("project+ material_lib?".to_string()), // 1个或多个项目,可选材料库
marks: None,
attrs: Some(HashMap::from([
("doc_name".to_string(), AttributeSpec { default: None }),
("doc_code".to_string(), AttributeSpec { default: None }),
("region".to_string(), AttributeSpec { default: Some(json!("beijing")) }),
("quota_standard".to_string(), AttributeSpec { default: Some(json!("2012")) }),
("created_at".to_string(), AttributeSpec { default: None }),
])),
desc: Some("预算文档根节点".to_string()),
});
// 2. 工程项目
nodes.insert("project".to_string(), NodeSpec {
content: Some("unit_engineering+".to_string()), // 1个或多个单位工程
marks: Some("priority cost_type".to_string()), // 支持优先级和费用类型标记
attrs: Some(HashMap::from([
("project_name".to_string(), AttributeSpec { default: None }),
("project_code".to_string(), AttributeSpec { default: None }),
("total_amount".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("status".to_string(), AttributeSpec { default: Some(json!("draft")) }),
])),
desc: Some("工程项目".to_string()),
});
// 3. 单位工程
nodes.insert("unit_engineering".to_string(), NodeSpec {
content: Some("division+".to_string()), // 1个或多个分部工程
marks: None,
attrs: Some(HashMap::from([
("unit_name".to_string(), AttributeSpec { default: None }),
("unit_code".to_string(), AttributeSpec { default: None }),
("engineering_type".to_string(), AttributeSpec {
default: Some(json!("construction")) // construction, installation, decoration
}),
])),
desc: Some("单位工程".to_string()),
});
// 4. 分部工程
nodes.insert("division".to_string(), NodeSpec {
content: Some("subdivision+".to_string()), // 1个或多个分项工程
marks: None,
attrs: Some(HashMap::from([
("division_name".to_string(), AttributeSpec { default: None }),
("division_code".to_string(), AttributeSpec { default: None }),
])),
desc: Some("分部工程".to_string()),
});
// 5. 分项工程
nodes.insert("subdivision".to_string(), NodeSpec {
content: Some("budget_item+".to_string()), // 1个或多个清单项目
marks: None,
attrs: Some(HashMap::from([
("subdivision_name".to_string(), AttributeSpec { default: None }),
("subdivision_code".to_string(), AttributeSpec { default: None }),
])),
desc: Some("分项工程".to_string()),
});
// 6. 清单项目 (叶子节点 - 实际的计价单元)
nodes.insert("budget_item".to_string(), NodeSpec {
content: None, // 叶子节点,无子节点
marks: Some("calculated price_locked quota_applied".to_string()),
attrs: Some(HashMap::from([
("item_code".to_string(), AttributeSpec { default: None }),
("item_name".to_string(), AttributeSpec { default: None }),
("unit".to_string(), AttributeSpec { default: Some(json!("m³")) }),
("quantity".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("unit_price".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("amount".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("quota_code".to_string(), AttributeSpec { default: None }),
("material_cost".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("labor_cost".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("machine_cost".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("measure_fee_rate".to_string(), AttributeSpec { default: Some(json!(0.05)) }),
])),
desc: Some("清单项目".to_string()),
});
// 7. 材料库
nodes.insert("material_lib".to_string(), NodeSpec {
content: Some("material_category+ material_item*".to_string()),
marks: None,
attrs: Some(HashMap::from([
("lib_name".to_string(), AttributeSpec { default: Some(json!("默认材料库")) }),
("version".to_string(), AttributeSpec { default: Some(json!("1.0")) }),
])),
desc: Some("材料库".to_string()),
});
// 8. 材料分类
nodes.insert("material_category".to_string(), NodeSpec {
content: Some("material_item+".to_string()),
marks: None,
attrs: Some(HashMap::from([
("category_name".to_string(), AttributeSpec { default: None }),
("category_code".to_string(), AttributeSpec { default: None }),
])),
desc: Some("材料分类".to_string()),
});
// 9. 材料项目
nodes.insert("material_item".to_string(), NodeSpec {
content: None, // 叶子节点
marks: Some("price_updated regional".to_string()),
attrs: Some(HashMap::from([
("material_code".to_string(), AttributeSpec { default: None }),
("material_name".to_string(), AttributeSpec { default: None }),
("unit".to_string(), AttributeSpec { default: None }),
("market_price".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("budget_price".to_string(), AttributeSpec { default: Some(json!(0.0)) }),
("supplier".to_string(), AttributeSpec { default: None }),
("region".to_string(), AttributeSpec { default: None }),
("price_date".to_string(), AttributeSpec { default: None }),
])),
desc: Some("材料项目".to_string()),
});
nodes
}
3. 标记 (Mark) 系统映射
rust
use moduforge_model::mark_type::MarkSpec;
pub fn create_budget_marks() -> HashMap<String, MarkSpec> {
let mut marks = HashMap::new();
// 计算状态标记
marks.insert("calculated".to_string(), MarkSpec {
attrs: Some(HashMap::from([
("last_calculated".to_string(), AttributeSpec { default: None }),
("calculation_version".to_string(), AttributeSpec { default: Some(json!("1.0")) }),
])),
excludes: None,
inclusive: Some(false),
spanning: Some(false),
});
// 价格锁定标记
marks.insert("price_locked".to_string(), MarkSpec {
attrs: Some(HashMap::from([
("locked_by".to_string(), AttributeSpec { default: None }),
("locked_at".to_string(), AttributeSpec { default: None }),
("reason".to_string(), AttributeSpec { default: None }),
])),
excludes: None,
inclusive: Some(false),
spanning: Some(false),
});
// 定额应用标记
marks.insert("quota_applied".to_string(), MarkSpec {
attrs: Some(HashMap::from([
("quota_code".to_string(), AttributeSpec { default: None }),
("applied_at".to_string(), AttributeSpec { default: None }),
("conversion_factor".to_string(), AttributeSpec { default: Some(json!(1.0)) }),
])),
excludes: None,
inclusive: Some(false),
spanning: Some(false),
});
// 优先级标记
marks.insert("priority".to_string(), MarkSpec {
attrs: Some(HashMap::from([
("level".to_string(), AttributeSpec {
default: Some(json!("normal")) // high, normal, low
}),
])),
excludes: None,
inclusive: Some(false),
spanning: Some(false),
});
marks
}
4. 实际使用示例
rust
use moduforge_model::node::Node;
use moduforge_model::attrs::Attrs;
use moduforge_model::mark::Mark;
// 创建一个清单项目节点
pub fn create_budget_item_node() -> Node {
let mut attrs = im::HashMap::new();
attrs.insert("item_code".to_string(), json!("010101001001"));
attrs.insert("item_name".to_string(), json!("挖基础土方"));
attrs.insert("unit".to_string(), json!("m³"));
attrs.insert("quantity".to_string(), json!(1000.0));
attrs.insert("unit_price".to_string(), json!(45.50));
attrs.insert("amount".to_string(), json!(45500.0));
attrs.insert("quota_code".to_string(), json!("A1-1"));
// 添加计算标记
let calculated_mark = Mark {
r#type: "calculated".to_string(),
attrs: {
let mut mark_attrs = im::HashMap::new();
mark_attrs.insert("last_calculated".to_string(), json!("2024-01-20T10:00:00Z"));
mark_attrs.insert("calculation_version".to_string(), json!("1.0"));
Attrs::from(mark_attrs)
},
};
Node::new(
"item_001",
"budget_item".to_string(),
Attrs::from(attrs),
vec![], // 无子节点
vec![calculated_mark],
)
}
// 创建项目结构
pub fn create_project_structure() -> Node {
let mut project_attrs = im::HashMap::new();
project_attrs.insert("project_name".to_string(), json!("住宅楼工程"));
project_attrs.insert("project_code".to_string(), json!("BJ2024001"));
project_attrs.insert("total_amount".to_string(), json!(5000000.0));
// 创建项目节点,包含子节点ID
Node::new(
"project_001",
"project".to_string(),
Attrs::from(project_attrs),
vec!["unit_001".to_string()], // 包含一个单位工程
vec![],
)
}
5. 业务操作映射
5.1 数据变更操作
rust
// ModuForge 的事务操作映射到预算业务
use moduforge_state::transaction::Transaction;
impl BudgetEditor {
// 更新工程量
pub async fn update_quantity(&mut self, item_id: &str, quantity: f64) -> EditorResult<()> {
let mut tr = self.get_tr();
// 使用 ModuForge 的属性更新机制
tr.set_node_markup(item_id, "quantity", json!(quantity))?;
// 触发重新计算 (通过中间件自动处理)
self.dispatch(tr).await
}
// 应用定额
pub async fn apply_quota(&mut self, item_id: &str, quota_code: &str) -> EditorResult<()> {
let mut tr = self.get_tr();
// 更新定额代码属性
tr.set_node_markup(item_id, "quota_code", json!(quota_code))?;
// 添加定额应用标记
let quota_mark = Mark {
r#type: "quota_applied".to_string(),
attrs: {
let mut attrs = im::HashMap::new();
attrs.insert("quota_code".to_string(), json!(quota_code));
attrs.insert("applied_at".to_string(), json!(chrono::Utc::now().to_rfc3339()));
Attrs::from(attrs)
},
};
tr.add_mark(item_id, quota_mark)?;
self.dispatch(tr).await
}
}
5.2 查询操作
rust
// 利用 ModuForge 的查询能力
impl BudgetEditor {
// 查询所有清单项目
pub fn get_all_budget_items(&self) -> Vec<Arc<Node>> {
self.doc()
.query()
.by_type("budget_item")
.execute()
}
// 查询特定分部工程下的项目
pub fn get_items_by_division(&self, division_code: &str) -> Vec<Arc<Node>> {
self.doc()
.query()
.by_type("budget_item")
.by_ancestor_attr("division_code", &json!(division_code))
.execute()
}
// 查询已应用定额的项目
pub fn get_quota_applied_items(&self) -> Vec<Arc<Node>> {
self.doc()
.query()
.by_type("budget_item")
.by_mark("quota_applied")
.execute()
}
// 计算项目总金额
pub fn calculate_total_amount(&self) -> f64 {
self.get_all_budget_items()
.iter()
.map(|item| {
item.attrs.get("amount")
.and_then(|v| v.as_f64())
.unwrap_or(0.0)
})
.sum()
}
}
🎯 核心优势
1. 结构化数据管理
- Node 的层级结构完美映射预算的分级结构
- 属性系统承载业务数据(价格、数量等)
- 标记系统记录业务状态(计算状态、锁定状态等)
2. 强类型约束
- NodeSpec 的 content 约束确保结构合法性
- 属性规范保证数据完整性
- 标记规范控制业务状态流转
3. 事务性操作
- 所有业务变更通过 Transaction 进行
- 保证数据一致性和可回滚
- 支持批量操作的原子性
4. 高效查询
- 基于类型、属性、标记的多维度查询
- 支持层级关系查询
- 内置索引优化性能
5. 事件驱动
- 数据变更自动触发计算
- 支持复杂的业务逻辑响应
- 实现松耦合的模块通信
这种映射方式充分利用了 ModuForge 的技术优势,同时完美适配了建筑预算的业务特点!