Supply Chain Management with Uni (Rust)¶
BOM explosion, cost rollup, and supplier risk analysis using Uni's native Rust API.
In [ ]:
Copied!
:dep uni-db = { path = "../../../crates/uni" }
:dep tokio = { version = "1", features = ["full"] }
:dep serde_json = "1"
:dep uni-db = { path = "../../../crates/uni" }
:dep tokio = { version = "1", features = ["full"] }
:dep serde_json = "1"
In [ ]:
Copied!
use uni::{Uni, DataType, IndexType, ScalarType, VectorMetric, VectorAlgo, VectorIndexCfg};
use std::collections::HashMap;
use serde_json::json;
// Helper macro to run async code in evcxr
macro_rules! run {
($e:expr) => {
tokio::runtime::Runtime::new().unwrap().block_on($e)
};
}
use uni::{Uni, DataType, IndexType, ScalarType, VectorMetric, VectorAlgo, VectorIndexCfg};
use std::collections::HashMap;
use serde_json::json;
// Helper macro to run async code in evcxr
macro_rules! run {
($e:expr) => {
tokio::runtime::Runtime::new().unwrap().block_on($e)
};
}
In [ ]:
Copied!
let db_path = "./supply_chain_db";
// Clean up any existing database
if std::path::Path::new(db_path).exists() {
std::fs::remove_dir_all(db_path).unwrap();
}
let db = run!(Uni::open(db_path).build()).unwrap();
println!("Opened database at {}", db_path);
let db_path = "./supply_chain_db";
// Clean up any existing database
if std::path::Path::new(db_path).exists() {
std::fs::remove_dir_all(db_path).unwrap();
}
let db = run!(Uni::open(db_path).build()).unwrap();
println!("Opened database at {}", db_path);
1. Define Schema¶
Parts (with name), Suppliers, and Products, along with ASSEMBLED_FROM and SUPPLIED_BY relationships.
In [ ]:
Copied!
run!(async {
db.schema()
.label("Part")
.property("name", DataType::String)
.property("sku", DataType::String)
.property("cost", DataType::Float64)
.index("sku", IndexType::Scalar(ScalarType::Hash))
.label("Supplier")
.property("name", DataType::String)
.label("Product")
.property("name", DataType::String)
.property("price", DataType::Float64)
.edge_type("ASSEMBLED_FROM", &["Product", "Part"], &["Part"])
.edge_type("SUPPLIED_BY", &["Part"], &["Supplier"])
.apply()
.await
}).unwrap();
println!("Schema created successfully");
run!(async {
db.schema()
.label("Part")
.property("name", DataType::String)
.property("sku", DataType::String)
.property("cost", DataType::Float64)
.index("sku", IndexType::Scalar(ScalarType::Hash))
.label("Supplier")
.property("name", DataType::String)
.label("Product")
.property("name", DataType::String)
.property("price", DataType::Float64)
.edge_type("ASSEMBLED_FROM", &["Product", "Part"], &["Part"])
.edge_type("SUPPLIED_BY", &["Part"], &["Supplier"])
.apply()
.await
}).unwrap();
println!("Schema created successfully");
2. Ingest Data¶
7 parts, 3 suppliers, 2 products — same topology as the Python notebook.
In [ ]:
Copied!
// 7 parts
let part_props = vec![
HashMap::from([("name".to_string(), json!("Resistor 10K")), ("sku".to_string(), json!("RES-10K")), ("cost".to_string(), json!(0.05))]),
HashMap::from([("name".to_string(), json!("Capacitor 100uF")), ("sku".to_string(), json!("CAP-100UF")), ("cost".to_string(), json!(0.08))]),
HashMap::from([("name".to_string(), json!("Motherboard X1")), ("sku".to_string(), json!("MB-X1")), ("cost".to_string(), json!(50.0))]),
HashMap::from([("name".to_string(), json!("OLED Screen")), ("sku".to_string(), json!("SCR-OLED")), ("cost".to_string(), json!(30.0))]),
HashMap::from([("name".to_string(), json!("Battery 4000mAh")), ("sku".to_string(), json!("BAT-4000")), ("cost".to_string(), json!(15.0))]),
HashMap::from([("name".to_string(), json!("ARM Processor")), ("sku".to_string(), json!("PROC-ARM")), ("cost".to_string(), json!(80.0))]),
HashMap::from([("name".to_string(), json!("LCD Screen")), ("sku".to_string(), json!("SCR-LCD")), ("cost".to_string(), json!(20.0))]),
];
let part_vids = run!(db.bulk_insert_vertices("Part", part_props)).unwrap();
let (res10k, cap100uf, mbx1, scr_oled, bat4000, proc_arm, scr_lcd) =
(part_vids[0], part_vids[1], part_vids[2], part_vids[3], part_vids[4], part_vids[5], part_vids[6]);
// 3 suppliers
let sup_props = vec![
HashMap::from([("name".to_string(), json!("ResistorWorld"))]),
HashMap::from([("name".to_string(), json!("ScreenTech"))]),
HashMap::from([("name".to_string(), json!("CoreComponents"))]),
];
let sup_vids = run!(db.bulk_insert_vertices("Supplier", sup_props)).unwrap();
let (resistor_world, screen_tech, core_components) = (sup_vids[0], sup_vids[1], sup_vids[2]);
// 2 products
let prod_props = vec![
HashMap::from([("name".to_string(), json!("Smartphone X")), ("price".to_string(), json!(599.0))]),
HashMap::from([("name".to_string(), json!("TabletPro 10")), ("price".to_string(), json!(799.0))]),
];
let prod_vids = run!(db.bulk_insert_vertices("Product", prod_props)).unwrap();
let (smartphone, tablet) = (prod_vids[0], prod_vids[1]);
// Smartphone X assembly
run!(db.bulk_insert_edges("ASSEMBLED_FROM", vec![
(smartphone, mbx1, HashMap::new()),
(smartphone, scr_oled, HashMap::new()),
(smartphone, bat4000, HashMap::new()),
(smartphone, proc_arm, HashMap::new()),
(mbx1, res10k, HashMap::new()),
(mbx1, cap100uf, HashMap::new()),
])).unwrap();
// TabletPro 10 assembly
run!(db.bulk_insert_edges("ASSEMBLED_FROM", vec![
(tablet, mbx1, HashMap::new()),
(tablet, scr_lcd, HashMap::new()),
(tablet, bat4000, HashMap::new()),
(tablet, proc_arm, HashMap::new()),
])).unwrap();
// Supply relationships
run!(db.bulk_insert_edges("SUPPLIED_BY", vec![
(res10k, resistor_world, HashMap::new()),
(cap100uf, resistor_world, HashMap::new()),
(scr_oled, screen_tech, HashMap::new()),
(scr_lcd, screen_tech, HashMap::new()),
(mbx1, core_components, HashMap::new()),
(bat4000, core_components, HashMap::new()),
(proc_arm, core_components, HashMap::new()),
])).unwrap();
run!(db.flush()).unwrap();
println!("Data ingested and flushed");
// 7 parts
let part_props = vec![
HashMap::from([("name".to_string(), json!("Resistor 10K")), ("sku".to_string(), json!("RES-10K")), ("cost".to_string(), json!(0.05))]),
HashMap::from([("name".to_string(), json!("Capacitor 100uF")), ("sku".to_string(), json!("CAP-100UF")), ("cost".to_string(), json!(0.08))]),
HashMap::from([("name".to_string(), json!("Motherboard X1")), ("sku".to_string(), json!("MB-X1")), ("cost".to_string(), json!(50.0))]),
HashMap::from([("name".to_string(), json!("OLED Screen")), ("sku".to_string(), json!("SCR-OLED")), ("cost".to_string(), json!(30.0))]),
HashMap::from([("name".to_string(), json!("Battery 4000mAh")), ("sku".to_string(), json!("BAT-4000")), ("cost".to_string(), json!(15.0))]),
HashMap::from([("name".to_string(), json!("ARM Processor")), ("sku".to_string(), json!("PROC-ARM")), ("cost".to_string(), json!(80.0))]),
HashMap::from([("name".to_string(), json!("LCD Screen")), ("sku".to_string(), json!("SCR-LCD")), ("cost".to_string(), json!(20.0))]),
];
let part_vids = run!(db.bulk_insert_vertices("Part", part_props)).unwrap();
let (res10k, cap100uf, mbx1, scr_oled, bat4000, proc_arm, scr_lcd) =
(part_vids[0], part_vids[1], part_vids[2], part_vids[3], part_vids[4], part_vids[5], part_vids[6]);
// 3 suppliers
let sup_props = vec![
HashMap::from([("name".to_string(), json!("ResistorWorld"))]),
HashMap::from([("name".to_string(), json!("ScreenTech"))]),
HashMap::from([("name".to_string(), json!("CoreComponents"))]),
];
let sup_vids = run!(db.bulk_insert_vertices("Supplier", sup_props)).unwrap();
let (resistor_world, screen_tech, core_components) = (sup_vids[0], sup_vids[1], sup_vids[2]);
// 2 products
let prod_props = vec![
HashMap::from([("name".to_string(), json!("Smartphone X")), ("price".to_string(), json!(599.0))]),
HashMap::from([("name".to_string(), json!("TabletPro 10")), ("price".to_string(), json!(799.0))]),
];
let prod_vids = run!(db.bulk_insert_vertices("Product", prod_props)).unwrap();
let (smartphone, tablet) = (prod_vids[0], prod_vids[1]);
// Smartphone X assembly
run!(db.bulk_insert_edges("ASSEMBLED_FROM", vec![
(smartphone, mbx1, HashMap::new()),
(smartphone, scr_oled, HashMap::new()),
(smartphone, bat4000, HashMap::new()),
(smartphone, proc_arm, HashMap::new()),
(mbx1, res10k, HashMap::new()),
(mbx1, cap100uf, HashMap::new()),
])).unwrap();
// TabletPro 10 assembly
run!(db.bulk_insert_edges("ASSEMBLED_FROM", vec![
(tablet, mbx1, HashMap::new()),
(tablet, scr_lcd, HashMap::new()),
(tablet, bat4000, HashMap::new()),
(tablet, proc_arm, HashMap::new()),
])).unwrap();
// Supply relationships
run!(db.bulk_insert_edges("SUPPLIED_BY", vec![
(res10k, resistor_world, HashMap::new()),
(cap100uf, resistor_world, HashMap::new()),
(scr_oled, screen_tech, HashMap::new()),
(scr_lcd, screen_tech, HashMap::new()),
(mbx1, core_components, HashMap::new()),
(bat4000, core_components, HashMap::new()),
(proc_arm, core_components, HashMap::new()),
])).unwrap();
run!(db.flush()).unwrap();
println!("Data ingested and flushed");
3. BOM Explosion¶
Which products are affected if RES-10K is defective? Traverses the assembly hierarchy upward.
In [ ]:
Copied!
let query = r#"
MATCH (defective:Part {sku: 'RES-10K'})
MATCH (product:Product)-[:ASSEMBLED_FROM*1..5]->(defective)
RETURN product.name AS name, product.price AS price
ORDER BY product.price DESC
"#;
let results = run!(db.query(query)).unwrap();
println!("Products affected by defective RES-10K:");
for row in &results.rows {
println!(" {:?}", row);
}
assert!(results.rows.len() == 2, "Expected 2 affected products, got {}", results.rows.len());
let query = r#"
MATCH (defective:Part {sku: 'RES-10K'})
MATCH (product:Product)-[:ASSEMBLED_FROM*1..5]->(defective)
RETURN product.name AS name, product.price AS price
ORDER BY product.price DESC
"#;
let results = run!(db.query(query)).unwrap();
println!("Products affected by defective RES-10K:");
for row in &results.rows {
println!(" {:?}", row);
}
assert!(results.rows.len() == 2, "Expected 2 affected products, got {}", results.rows.len());
4. Full BOM Listing¶
Every part in Smartphone X with its cost, ordered by cost descending.
In [ ]:
Copied!
let query_parts = r#"
MATCH (p:Product {name: 'Smartphone X'})-[:ASSEMBLED_FROM*1..5]->(part:Part)
RETURN part.name AS part_name, part.sku AS sku, part.cost AS cost
ORDER BY cost DESC
"#;
let results = run!(db.query(query_parts)).unwrap();
println!("Smartphone X BOM:");
for row in &results.rows {
println!(" {:?}", row);
}
let query_parts = r#"
MATCH (p:Product {name: 'Smartphone X'})-[:ASSEMBLED_FROM*1..5]->(part:Part)
RETURN part.name AS part_name, part.sku AS sku, part.cost AS cost
ORDER BY cost DESC
"#;
let results = run!(db.query(query_parts)).unwrap();
println!("Smartphone X BOM:");
for row in &results.rows {
println!(" {:?}", row);
}
5. Cost Rollup¶
Total BOM cost per product — GROUP BY product with SUM of part costs.
In [ ]:
Copied!
let query_rollup = r#"
MATCH (p:Product)-[:ASSEMBLED_FROM*1..5]->(part:Part)
RETURN p.name AS product, SUM(part.cost) AS total_bom_cost
ORDER BY total_bom_cost DESC
"#;
let results = run!(db.query(query_rollup)).unwrap();
println!("BOM cost rollup per product:");
for row in &results.rows {
println!(" {:?}", row);
}
assert!(results.rows.len() == 2, "Expected 2 rows, got {}", results.rows.len());
let query_rollup = r#"
MATCH (p:Product)-[:ASSEMBLED_FROM*1..5]->(part:Part)
RETURN p.name AS product, SUM(part.cost) AS total_bom_cost
ORDER BY total_bom_cost DESC
"#;
let results = run!(db.query(query_rollup)).unwrap();
println!("BOM cost rollup per product:");
for row in &results.rows {
println!(" {:?}", row);
}
assert!(results.rows.len() == 2, "Expected 2 rows, got {}", results.rows.len());
6. Supply Chain Risk¶
Which supplier is critical to the most products?
In [ ]:
Copied!
let query_risk = r#"
MATCH (p:Product)-[:ASSEMBLED_FROM*1..5]->(part:Part)-[:SUPPLIED_BY]->(s:Supplier)
RETURN s.name AS supplier, COUNT(DISTINCT p) AS products_at_risk
ORDER BY products_at_risk DESC
"#;
let results = run!(db.query(query_risk)).unwrap();
println!("Supplier risk analysis:");
for row in &results.rows {
println!(" {:?}", row);
}
// CoreComponents supplies mbx1, bat4000, proc_arm — used by both products
println!("Top supplier: {:?}", results.rows[0]);
let query_risk = r#"
MATCH (p:Product)-[:ASSEMBLED_FROM*1..5]->(part:Part)-[:SUPPLIED_BY]->(s:Supplier)
RETURN s.name AS supplier, COUNT(DISTINCT p) AS products_at_risk
ORDER BY products_at_risk DESC
"#;
let results = run!(db.query(query_risk)).unwrap();
println!("Supplier risk analysis:");
for row in &results.rows {
println!(" {:?}", row);
}
// CoreComponents supplies mbx1, bat4000, proc_arm — used by both products
println!("Top supplier: {:?}", results.rows[0]);