///|
/// Material edge utilization at a requested production demand.
pub(all) struct EdgeUtilizationSnapshot {
edge_id : String
source_id : String
target_id : String
bandwidth_units_per_hour : Double
is_active : Bool
utilization_percent : Double
is_congested : Bool
} derive(Debug, Eq)
///|
/// Capacity report for material edges and production assets.
pub(all) struct MaterialCapacityReport {
demand_per_hour : Double
constrained_capacity_per_hour : Double
output_capacity_per_hour : Double
capacity_margin_percent : Double
edge_utilization : Array[EdgeUtilizationSnapshot]
overloaded_edge_ids : Array[String]
constrained_node_ids : Array[String]
} derive(Debug, Eq)
///|
fn is_production_asset(node : @graph.Node) -> Bool {
match node.node_type() {
@types.NodeType::Workstation
| @types.NodeType::Machine
| @types.NodeType::Conveyor
| @types.NodeType::Inspection
| @types.NodeType::RobotArm
| @types.NodeType::Diverter
| @types.NodeType::AGVTrack => true
_ => false
}
}
///|
fn requested_demand(
graph : @graph.FactoryGraph,
demand_per_hour : Double,
) -> Double {
if demand_per_hour > 0.0 {
demand_per_hour
} else {
let takt = graph.config().takt_time_target_sec
if takt > 0.0 {
3600.0 / takt
} else {
0.0
}
}
}
///|
/// Analyze material-flow congestion and the limiting production capacity.
///
/// If `demand_per_hour` is omitted, the target takt time in the graph
/// configuration is converted to units per hour. Zero or negative edge
/// bandwidth is treated as unavailable capacity instead of producing an
/// infinite utilization value.
pub fn analyze_material_capacity(
graph : @graph.FactoryGraph,
demand_per_hour? : Double = 0.0,
) -> MaterialCapacityReport {
let demand = requested_demand(graph, demand_per_hour)
let utilization = []
let overloaded = []
let mut edge_capacity = 1.0e18
let mut material_edge_count = 0
for edge in graph.get_all_edges() {
if edge.flow_type() == @types.FlowType::MaterialFlow {
material_edge_count = material_edge_count + 1
let bandwidth = edge.bandwidth()
let effective_bandwidth = if edge.is_active() && bandwidth > 0.0 {
bandwidth
} else {
0.0
}
if effective_bandwidth < edge_capacity {
edge_capacity = effective_bandwidth
}
let is_congested = demand > effective_bandwidth
let utilization_percent = if effective_bandwidth > 0.0 {
demand / effective_bandwidth * 100.0
} else if demand > 0.0 {
100.0
} else {
0.0
}
let snapshot = {
edge_id: edge.id(),
source_id: edge.source_id(),
target_id: edge.target_id(),
bandwidth_units_per_hour: bandwidth,
is_active: edge.is_active(),
utilization_percent,
is_congested,
}
utilization.push(snapshot)
if is_congested {
overloaded.push(edge.id())
}
}
}
let constrained_nodes = []
let mut node_capacity = 1.0e18
let mut production_node_count = 0
for node in graph.get_all_nodes() {
if is_production_asset(node) {
production_node_count = production_node_count + 1
let capacity = node.hourly_throughput_limit()
if capacity < node_capacity {
node_capacity = capacity
}
if demand > capacity {
constrained_nodes.push(node.id())
}
}
}
let constrained_capacity = if material_edge_count == 0 {
node_capacity
} else if production_node_count == 0 {
edge_capacity
} else if edge_capacity < node_capacity {
edge_capacity
} else {
node_capacity
}
let output_capacity = if graph.node_count() == 0 {
0.0
} else {
constrained_capacity
}
let margin = if demand > 0.0 {
(output_capacity - demand) / demand * 100.0
} else {
100.0
}
{
demand_per_hour: demand,
constrained_capacity_per_hour: constrained_capacity,
output_capacity_per_hour: output_capacity,
capacity_margin_percent: margin,
edge_utilization: utilization,
overloaded_edge_ids: overloaded,
constrained_node_ids: constrained_nodes,
}
}