Lesson 11 - Loop Aggregation¶
Goal¶
In this lesson we’ll learn how to aggregate output from a loop.
Get Started¶
We’ll create a new step to simulate ordering equipment. Internally it will randomly decide whether a piece of equipment is available or not. Then we’ll run that step in a loop from the main flow and record the cost of the ordered equipment and which items were unavailable. Create a new file named order.sl in the tutorials/hiring folder to house the new operation we’ll write and get the new_hire.sl file ready because we’ll need to add a step to the main flow.
Operation¶
The order
operation, as we’ll call it, looks very similar to our
check_availability
operation. It uses a random number to simulate
whether a given item is available. If the item is available, it will
return the amount spent
as one output and the not_ordered
output will be empty. If the item is unavailable, it will return 0
for the spent
output and the name of the item in the not_ordered
output.
namespace: tutorials.hiring
operation:
name: order
inputs:
- item
- price
python_action:
script: |
print 'Ordering: ' + item
import random
rand = random.randint(0, 2)
available = rand != 0
not_ordered = item + ';' if rand == 0 else ''
spent = 0 if rand == 0 else price
if rand == 0: print 'Unavailable'
outputs:
- not_ordered
- spent: ${spent}
results:
- UNAVAILABLE: ${rand == 0}
- AVAILABLE
Step¶
Now let’s go back to our flow and create a step, between
create_email_address
and print_finish
, to call our operation in
a loop. This time we’ll loop through a map of items and their prices, named,
order_map
that we’ll define at the flow level in a few moments. We use the
Python eval()
function to turn a string into a Python dictionary that we can
loop over.
- get_equipment:
loop:
for: item, price in eval(order_map)
do:
order:
- item
- price: ${str(price)}
- missing: ${all_missing}
- cost: ${total_cost}
break: []
Notice the missing
and cost
variables. These are not for inputs in the
order
operation. That operation only takes the item
and price
inputs. We will be using missing
and cost
together with some flow-level
variables to perform the loop aggregation.
Also notice how we’ve added a break
which maps to an empty list of break
results. This is necessary because the order
operation does not contain a
result of FAILURE
which is the default for breaking out of a loop.
Now let’s create those flow-level variables in the flow’s inputs
section.
Each time through the loop we want to aggregate the data that the order
operation outputs. We’ll create two variables, all_missing
and
total_cost
, for this purpose, defining them as private
and giving
them default values to start with.
Also, we’ll declare another variable called order_map
that will contain the
map we’re looping on.
inputs:
- first_name
- middle_name:
required: false
- last_name
- all_missing:
default: ""
required: false
private: true
- total_cost:
default: '0'
private: true
- order_map:
default: '{"laptop": 1000, "docking station": 200, "monitor": 500, "phone": 100}'
Now we can perform the aggregation. In the get_equipment
step’s publish
section, we’ll add the values output from the order
operation
(not_ordered
and spent
) to the step arguments we just created in
the get_equipment
step (missing
and cost
) and publish them back to
the flow-level variables (all_missing
and total_cost
). This will run for
each iteration after the operation has completed, aggregating all the
data. For example, each time through the loop the cost
is updated with the
current total_cost
. Then the order
operation runs and a spent
value
is output. That spent
value is added to the step’s cost
variable and
published back into the flow-level total_cost
for each iteration of the
get_equipment
step.
publish:
- all_missing: ${missing + not_ordered}
- total_cost: ${str(int(cost) + int(spent))}
Finally we have to rewire all the navigation logic to take into account our new step.
We need to change the create_email_address
step to forward
successful email address creations to get_equipment
.
navigate:
- CREATED: get_equipment
- UNAVAILABLE: print_fail
- FAILURE: print_fail
And we need to add navigation to the get_equipment
step. We’ll
always go to print_finish
no matter what happens.
navigate:
- AVAILABLE: print_finish
- UNAVAILABLE: print_finish
Finish¶
The last thing left to do is print out a finish message that also reflects the status of the equipment order.
- print_finish:
do:
base.print:
- text: >
${'Created address: ' + address + ' for: ' + first_name + ' ' + last_name + '\n' +
'Missing items: ' + all_missing + ' Cost of ordered items: ' + total_cost}
navigate:
- SUCCESS: SUCCESS
Run It¶
We can save the files, run the flow and see that the ordering takes place, the proper information is aggregated and then it is printed.
run --f <folder path>/tutorials/hiring/new_hire.sl --cp <folder path>/tutorials --i first_name=john,middle_name=e,last_name=doe
Download the Code¶
Up Next¶
In the next lesson we’ll see how to write a decision.
New Code - Complete¶
new_hire.sl
namespace: tutorials.hiring
imports:
base: tutorials.base
flow:
name: new_hire
inputs:
- first_name
- middle_name:
required: false
- last_name
- all_missing:
default: ""
required: false
private: true
- total_cost:
default: '0'
private: true
- order_map:
default: '{"laptop": 1000, "docking station": 200, "monitor": 500, "phone": 100}'
workflow:
- print_start:
do:
base.print:
- text: "Starting new hire process"
navigate:
- SUCCESS: create_email_address
- create_email_address:
loop:
for: attempt in range(1,5)
do:
create_user_email:
- first_name
- middle_name
- last_name
- attempt: ${str(attempt)}
publish:
- address
- password
break:
- CREATED
- FAILURE
navigate:
- CREATED: get_equipment
- UNAVAILABLE: print_fail
- FAILURE: print_fail
- get_equipment:
loop:
for: item, price in eval(order_map)
do:
order:
- item
- price: ${str(price)}
- missing: ${all_missing}
- cost: ${total_cost}
publish:
- all_missing: ${missing + not_ordered}
- total_cost: ${str(int(cost) + int(spent))}
break: []
navigate:
- AVAILABLE: print_finish
- UNAVAILABLE: print_finish
- print_finish:
do:
base.print:
- text: >
${'Created address: ' + address + ' for: ' + first_name + ' ' + last_name + '\n' +
'Missing items: ' + all_missing + ' Cost of ordered items: ' + total_cost}
navigate:
- SUCCESS: SUCCESS
- on_failure:
- print_fail:
do:
base.print:
- text: "${'Failed to create address for: ' + first_name + ' ' + last_name}"
order.sl
namespace: tutorials.hiring
operation:
name: order
inputs:
- item
- price
python_action:
script: |
print 'Ordering: ' + item
import random
rand = random.randint(0, 2)
available = rand != 0
not_ordered = item + ';' if rand == 0 else ''
spent = 0 if rand == 0 else price
if rand == 0: print 'Unavailable'
outputs:
- not_ordered
- spent: ${str(spent)}
results:
- UNAVAILABLE: ${rand == 0}
- AVAILABLE