1use graph_sp::Graph;
10use std::collections::HashMap;
11
12fn main() {
13 println!("═══════════════════════════════════════════════════════════");
14 println!(" Per-Node and Per-Branch Output Access Demo");
15 println!(" Track execution results at every level");
16 println!("═══════════════════════════════════════════════════════════\n");
17
18 demo_per_node_access();
19 demo_per_branch_access();
20 demo_variant_per_node_access();
21 demo_execution_history_tracking();
22}
23
24fn demo_per_node_access() {
25 println!("─────────────────────────────────────────────────────────");
26 println!("Demo 1: Per-Node Output Access");
27 println!("─────────────────────────────────────────────────────────\n");
28
29 let mut graph = Graph::new();
30
31 graph.add(
33 |_: &HashMap<String, String>, _| {
34 let mut result = HashMap::new();
35 result.insert("value".to_string(), "10".to_string());
36 result
37 },
38 Some("Source"),
39 None,
40 Some(vec![("value", "initial_data")])
41 );
42
43 graph.add(
45 |inputs: &HashMap<String, String>, _| {
46 let value = inputs.get("in").unwrap().parse::<i32>().unwrap();
47 let mut result = HashMap::new();
48 result.insert("doubled".to_string(), (value * 2).to_string());
49 result
50 },
51 Some("Double"),
52 Some(vec![("initial_data", "in")]),
53 Some(vec![("doubled", "doubled_data")])
54 );
55
56 graph.add(
58 |inputs: &HashMap<String, String>, _| {
59 let value = inputs.get("in").unwrap().parse::<i32>().unwrap();
60 let mut result = HashMap::new();
61 result.insert("added".to_string(), (value + 10).to_string());
62 result
63 },
64 Some("AddTen"),
65 Some(vec![("doubled_data", "in")]),
66 Some(vec![("added", "final_result")])
67 );
68
69 let dag = graph.build();
71 let result = dag.execute_detailed(false, None);
72
73 println!("🌍 Global Context (all variables):");
74 for (key, value) in &result.context {
75 println!(" {} = {}", key, value);
76 }
77
78 println!("\n📦 Per-Node Outputs:");
79 println!("\nNode 0 (Source) outputs:");
80 if let Some(outputs) = result.get_node_outputs(0) {
81 for (key, value) in outputs {
82 println!(" {} = {}", key, value);
83 }
84 }
85
86 println!("\nNode 1 (Double) outputs:");
87 if let Some(outputs) = result.get_node_outputs(1) {
88 for (key, value) in outputs {
89 println!(" {} = {}", key, value);
90 }
91 }
92
93 println!("\nNode 2 (AddTen) outputs:");
94 if let Some(outputs) = result.get_node_outputs(2) {
95 for (key, value) in outputs {
96 println!(" {} = {}", key, value);
97 }
98 }
99
100 println!("\n🎯 Accessing specific node outputs:");
101 if let Some(value) = result.get_from_node(0, "initial_data") {
102 println!(" Node 0 'initial_data': {}", value);
103 }
104 if let Some(value) = result.get_from_node(1, "doubled_data") {
105 println!(" Node 1 'doubled_data': {}", value);
106 }
107 if let Some(value) = result.get_from_node(2, "final_result") {
108 println!(" Node 2 'final_result': {}", value);
109 }
110
111 println!();
112}
113
114fn demo_per_branch_access() {
115 println!("─────────────────────────────────────────────────────────");
116 println!("Demo 2: Per-Branch Output Access");
117 println!("─────────────────────────────────────────────────────────\n");
118
119 let mut graph = Graph::new();
120
121 graph.add(
123 |_: &HashMap<String, String>, _| {
124 let mut result = HashMap::new();
125 result.insert("dataset".to_string(), "100".to_string());
126 result
127 },
128 Some("Source"),
129 None,
130 Some(vec![("dataset", "data")])
131 );
132
133 let mut branch_a = Graph::new();
135 branch_a.add(
136 |inputs: &HashMap<String, String>, _| {
137 let value = inputs.get("input").unwrap();
138 let mut result = HashMap::new();
139 result.insert("stat_result".to_string(), format!("Mean of {}", value));
140 result
141 },
142 Some("Statistics"),
143 Some(vec![("data", "input")]),
144 Some(vec![("stat_result", "statistics")])
145 );
146
147 let mut branch_b = Graph::new();
149 branch_b.add(
150 |inputs: &HashMap<String, String>, _| {
151 let value = inputs.get("input").unwrap();
152 let mut result = HashMap::new();
153 result.insert("model_result".to_string(), format!("Model trained on {}", value));
154 result
155 },
156 Some("ModelTraining"),
157 Some(vec![("data", "input")]),
158 Some(vec![("model_result", "trained_model")])
159 );
160
161 let mut branch_c = Graph::new();
163 branch_c.add(
164 |inputs: &HashMap<String, String>, _| {
165 let value = inputs.get("input").unwrap();
166 let mut result = HashMap::new();
167 result.insert("viz_result".to_string(), format!("Plot of {}", value));
168 result
169 },
170 Some("Visualization"),
171 Some(vec![("data", "input")]),
172 Some(vec![("viz_result", "plot")])
173 );
174
175 let branch_a_id = graph.branch(branch_a);
176 let branch_b_id = graph.branch(branch_b);
177 let branch_c_id = graph.branch(branch_c);
178
179 let dag = graph.build();
181 let result = dag.execute_detailed(false, None);
182
183 println!("🌍 Global Context:");
184 for (key, value) in &result.context {
185 println!(" {} = {}", key, value);
186 }
187
188 println!("\n🌿 Per-Branch Outputs:");
189
190 println!("\nBranch {} (Statistics) outputs:", branch_a_id);
191 if let Some(outputs) = result.get_branch_outputs(branch_a_id) {
192 for (key, value) in outputs {
193 println!(" {} = {}", key, value);
194 }
195 }
196
197 println!("\nBranch {} (Model Training) outputs:", branch_b_id);
198 if let Some(outputs) = result.get_branch_outputs(branch_b_id) {
199 for (key, value) in outputs {
200 println!(" {} = {}", key, value);
201 }
202 }
203
204 println!("\nBranch {} (Visualization) outputs:", branch_c_id);
205 if let Some(outputs) = result.get_branch_outputs(branch_c_id) {
206 for (key, value) in outputs {
207 println!(" {} = {}", key, value);
208 }
209 }
210
211 println!("\n🎯 Accessing specific branch outputs:");
212 if let Some(value) = result.get_from_branch(branch_a_id, "statistics") {
213 println!(" Branch {} 'statistics': {}", branch_a_id, value);
214 }
215 if let Some(value) = result.get_from_branch(branch_b_id, "trained_model") {
216 println!(" Branch {} 'trained_model': {}", branch_b_id, value);
217 }
218 if let Some(value) = result.get_from_branch(branch_c_id, "plot") {
219 println!(" Branch {} 'plot': {}", branch_c_id, value);
220 }
221
222 println!();
223}
224
225fn demo_variant_per_node_access() {
226 println!("─────────────────────────────────────────────────────────");
227 println!("Demo 3: Variant Outputs with Per-Node Tracking");
228 println!("─────────────────────────────────────────────────────────\n");
229
230 let mut graph = Graph::new();
231
232 graph.add(
234 |_: &HashMap<String, String>, _| {
235 let mut result = HashMap::new();
236 result.insert("base_value".to_string(), "10".to_string());
237 result
238 },
239 Some("Source"),
240 None,
241 Some(vec![("base_value", "data")])
242 );
243
244 fn make_scaler(factor: f64) -> impl Fn(&HashMap<String, String>, &HashMap<String, String>) -> HashMap<String, String> {
246 move |inputs: &HashMap<String, String>, _| {
247 let value = inputs.get("input_data").unwrap().parse::<f64>().unwrap();
248 let mut result = HashMap::new();
249 result.insert("scaled_value".to_string(), (value * factor).to_string());
250 result
251 }
252 }
253
254 graph.variant(
256 make_scaler,
257 vec![2.0, 3.0, 5.0],
258 Some("Scale"),
259 Some(vec![("data", "input_data")]),
260 Some(vec![("scaled_value", "result")]) );
262
263 let dag = graph.build();
264 let result = dag.execute_detailed(false, None);
265
266 println!("🌍 Global Context (note: 'result' contains last variant's output):");
267 for (key, value) in &result.context {
268 println!(" {} = {}", key, value);
269 }
270
271 println!("\n📦 Per-Node Outputs (each variant tracked separately):");
272
273 for node_id in 1..=3 {
276 println!("\nNode {} (Variant Scaler) outputs:", node_id);
277 if let Some(outputs) = result.get_node_outputs(node_id) {
278 for (key, value) in outputs {
279 println!(" {} = {}", key, value);
280 }
281 }
282 }
283
284 println!("\n💡 Key Insight:");
285 println!(" - Global context has 'result' = {} (last variant overwrites)", result.get("result").unwrap());
286 println!(" - But per-node outputs preserve ALL variant results:");
287 println!(" Node 1 (2x): result = {}", result.get_from_node(1, "result").unwrap());
288 println!(" Node 2 (3x): result = {}", result.get_from_node(2, "result").unwrap());
289 println!(" Node 3 (5x): result = {}", result.get_from_node(3, "result").unwrap());
290
291 println!();
292}
293
294fn demo_execution_history_tracking() {
295 println!("─────────────────────────────────────────────────────────");
296 println!("Demo 4: Execution History Tracking");
297 println!("─────────────────────────────────────────────────────────\n");
298
299 let mut graph = Graph::new();
300
301 graph.add(
303 |_: &HashMap<String, String>, _| {
304 let mut result = HashMap::new();
305 result.insert("raw".to_string(), "5".to_string());
306 result
307 },
308 Some("Load"),
309 None,
310 Some(vec![("raw", "input")])
311 );
312
313 graph.add(
314 |inputs: &HashMap<String, String>, _| {
315 let value = inputs.get("x").unwrap().parse::<i32>().unwrap();
316 let mut result = HashMap::new();
317 result.insert("cleaned".to_string(), (value + 1).to_string());
318 result
319 },
320 Some("Clean"),
321 Some(vec![("input", "x")]),
322 Some(vec![("cleaned", "clean_data")])
323 );
324
325 graph.add(
326 |inputs: &HashMap<String, String>, _| {
327 let value = inputs.get("x").unwrap().parse::<i32>().unwrap();
328 let mut result = HashMap::new();
329 result.insert("normalized".to_string(), (value * 10).to_string());
330 result
331 },
332 Some("Normalize"),
333 Some(vec![("clean_data", "x")]),
334 Some(vec![("normalized", "norm_data")])
335 );
336
337 graph.add(
338 |inputs: &HashMap<String, String>, _| {
339 let value = inputs.get("x").unwrap().parse::<i32>().unwrap();
340 let mut result = HashMap::new();
341 result.insert("transformed".to_string(), format!("FINAL_{}", value));
342 result
343 },
344 Some("Transform"),
345 Some(vec![("norm_data", "x")]),
346 Some(vec![("transformed", "output")])
347 );
348
349 let dag = graph.build();
350 let result = dag.execute_detailed(false, None);
351
352 println!("📊 Execution History (Data Flow Tracking):");
353 println!();
354 println!("Step-by-step transformation:");
355 println!(" 1. Load: input = {}", result.get_from_node(0, "input").unwrap());
356 println!(" 2. Clean: clean_data = {}", result.get_from_node(1, "clean_data").unwrap());
357 println!(" 3. Normalize: norm_data = {}", result.get_from_node(2, "norm_data").unwrap());
358 println!(" 4. Transform: output = {}", result.get_from_node(3, "output").unwrap());
359
360 println!("\n🔍 Debugging: Inspect any intermediate result:");
361 println!(" Need to debug the normalization step?");
362 println!(" Just check Node 2: {}", result.get_from_node(2, "norm_data").unwrap());
363
364 println!("\n✅ Benefits of Per-Node Access:");
365 println!(" ✓ Track data transformations step-by-step");
366 println!(" ✓ Debug issues by inspecting intermediate values");
367 println!(" ✓ Validate each processing stage independently");
368 println!(" ✓ Preserve all variant outputs even with name collisions");
369
370 println!();
371}