processi

about processes and engines

Archive for the ‘decision’ Category

volute

Vos luttes partent en fumée
Vers des flûtes enchantées Et de cruelles espérances
Me lancent Des dagues et des lances En toute innocence

J’cloue des clous sur des nuages Un marteau au fond du garage
J’cloue des clous sur des nuages Sans échafaudage

volutes Alain Bashung (funny auto-translation)

 

This started out in a notebook. I wanted to do something about ‘state’ and ‘lifecyle’, something like a state machine for multiple objects or a rule system for families of resources.

It ended up as something that feels like the subset of an aspect oriented framework. Subset because in its most vanilla usage it only cares about calls to set methods, and there is no “before” (at first glance).

This Ruby gem is named volute. Here is an example of its usage :

require 'volute'

class Package
  include Volute

  attr_accessor :location
  attr_accessor :delivered
end

volute Package do
  # filters non Package instance out

  volute :delivered => true do
    # applies when delivered switches to true
    object.emit_invoice
  end

  volute :location do
    # filters out changes that are not for the :location attribute

    volute 'SFO' => :any, 'FRA' => :any do
      # SFO and FRA are our international hubs, for any package transiting from there,
      # initial 'international' tracking
      TrackingSystem.track_international(object)
    end
  end
end

The “include Volute” reworks the attr_accessor, and triggers the evaluation of the volutes on each set.

It’s not always necessary to include Volute. Here is a example of [business] rules derived from Ruleby (volute is very dumb compared to Ruleby, the example doesn’t do justice to that project).

Here is the diagnosis example with volute :

require 'volute'

class Patient

  attr_reader :name
  attr_accessor :fever
  attr_accessor :rash
  attr_accessor :spots
  attr_accessor :sore_throat
  attr_accessor :innoculated

  attr_accessor :diagnosis

  def initialize(name)
    @name = name
    @symptoms = {}
    @innoculated = false
  end

  def diagnose!
    Volute.apply(self) # trigger evalution of volutes
    return @diagnosis
  end
end

volute Patient do

  volute :fever => :high, :spots => :true, :innoculated => true do
    object.diagnosis = 'measles'
    over # prevent further evals
  end
  volute :spots => true do
    object.diagnosis = 'allergy (spots but no measles)'
  end
  volute :rash => true do
    object.diagnosis = 'allergy (rash)'
  end
  volute :sore_throat => true, :fever => :mild do
    object.diagnosis = 'flu'
  end
  volute :sore_throat => true, :fever => :high do
    object.diagnosis = 'flu'
  end
end

pat = Patient.new('alice')
pat.rash = true

puts "#{pat.name} : diagnosed with #{pat.diagnose!}"

pat = Patient.new('bob')
pat.sore_throat = true
pat.fever = :high

puts "#{pat.name} : diagnosed with #{pat.diagnose!}"

 

The readme for volute is quite extensive. There are a few examples included.

You’ve already seen the diagnosis one, it aimed at using volute for some mini rule engine (nothing fancy at all).

There is an example about a simple (kph = km/h) equation where each time an attribute is changed, other attributes get adjusted.

The light example simply keeps tracks of the last time an attribute got modified.

There is a book in a bookshop state machine example with a variation, to explore state volutes and transition volutes.

Note that state machines built with volute are incomplete, they know nothing about further transitions (could I even call them state machines ? Rather a ‘state transition system’).

Traffic could be fun : it tracks the state of two “lines” and adjust the colour of their lights according to the number of cars waiting. It’s more in line with the “multiple objects” goal I had initially (still here somehow, but examples with 1 class are smaller).

Not sure if it’s worth moving logic out of objects, but it is a fun experiment.

 

http://github.com/jmettraux/volute

 

Written by John Mettraux

October 12, 2010 at 7:28 am

ruote and decision tables

ruote 2.1.7 is out and it felt like it was time to update the CsvParticipant that could be found in ruote 0.9.

Ruote has been relying on rufus-decision for its decision table needs and ruote 0.9 was integrating a CsvParticipant directly. For ruote 2.1, the DecisionParticipant is left in the rufus-decision gem.

It’s not the first time I write something about decision tables. But I noticed that I hadn’t really written anything about how to mix ruote processes and decision tables.

Let’s consider the decision table in the adjacent figure.

Given two input values “type of visit” and “participant physician ?” the level of “reimbursement” is decided.

The CSV version of this decision table is available online.

Clearly this decision has to take place after the customer filled his reimbursement claim and it got reviewed by a clerk in the organization.

Up until now, applying the reimbursement rules was part of the tasks of the clerk (with probably some downstream checks), and our goal is to remove some of that burden.

Our process definition would look like :

Ruote.process_definition :name => 'reimbursement', :revision => '1.0' do
  cursor do
    customer :task => 'fill form'
    clerk :task => 'control form'
    rewind :if => '${f:missing_info}'
    determine_reimbursement_level
    finance :task => 'reimburse customer'
    customer :task => 'reimbursement notification'
  end
end

There are four participants involved in that process definition. Customer, Clerk and Finance our implemented with classical inbox/worklist participants : workitems for them get placed in their workqueue. (With ruote 2.1 I tend to use StorageParticipant for that or I emit the workitem via a ruote-amqp participant towards the target worklist or task manager).

“rewind” isn’t a participant, it’s a ruote expression working with the cursor expression.

That leaves us with the participant “determine_reimbursement_level” to implement.

The straightforward Ruby implementation of the participant would leverage a BlockParticipant and look like :

engine.register_participant 'determine_reimbursement_level' do |workitem|
  workitem.fields['reimbursement'] =
    case workitem.fields['type of visit']
      when 'doctor office'
        workitem.fields['participating physician ?'] == 'yes' ? '90%' : '50%'
      when 'hospital visit'
        workitem.fields['participating physician ?'] == 'yes' ? '0%' : '80%'
      when 'lab visit'
        workitem.fields['participating physician ?'] == 'yes' ? '0%' : '70%'
      else
        '0%'
    end
end

Given the values in the fields ‘type of visit’ and ‘participating physician ?’ this participant sets the value of the field ‘reimbursement’.

All is well, but you may have noticed that the decision table is rather simplistic. And how does it cope with change ? What will it look like when the rules get hairy ? Will the Ruby developer have to intervene for each change ?

(it might not be a bad thing to rely on the Ruby developer, they tend to test carefully their creations)

Those rules are produced by ‘business users’ and one of their favourite tools is the spreadsheet. Rufus-decision can read CSV output of decision tables. The decision participant would look like :

require 'rufus/decision/participant'
  # gem install rufus-decision

engine.register_participant(
  'determine_reimbursement_level',
  Rufus::Decision::Participant,
  :table => {
in:type of visit,in:participating physician ?,out:reimbursement
doctor office,yes,90%
doctor office,no,50%
hospital visit,yes,0%
hospital visit,no,80%
lab visit,yes,0%
lab visit,no,70%
  })

Well, OK, that’s nice, but… We still have to restart the application at each change to the decision table or at least re-register the participant with the updated table. Tedious.

If our business people publish the decision table as CSV somewhere over the intranet (yes, I know, it’s an old-fashioned word), the decision participant can be simplified to :

require 'rufus/decision/participant'
  # gem install rufus-decision

engine.register_participant(
  'determine_reimbursement_level',
  Rufus::Decision::Participant,
  :table => 'http://somewhere.example.com/decision_tables/reimbursement.csv')

Each time a decision will have to be taken, the table will be read. This lets business users modify the rules at will.

The whole example is packaged as reimbursement.rb. It runs the process from the command line and grabs the decision table online. It shouldn’t be too difficult to take inspiration from it and integrate decision tables in web applications (like ruote-kit or barley).

There are many open possibilities when combining a workflow engine and a decision mechanism. One immediate advantage is that they can evolve with different rhythms. They can also be used in isolation : the clerk in our process could talk the claim over the phone with the customer and run blank decisions to help the customer with incomplete forms.

Of course, you can use rufus-decision without ruote, it’s a plain ruby gem.

 

http://github.com/jmettraux/rufus-decision – the source code of rufus-decision
http://github.com/jmettraux/ruote – the source code of ruote
http://ruote.rubyforge.org – ruote’s documentation
http://groups.google.com/group/openwferu-users – the mailing list for ruote and rufus-decision

 

Written by John Mettraux

February 17, 2010 at 4:32 am

Posted in bpm, bpms, decision, ruby, rufus, rules, ruote

rufus-decision 1.1, ruby decision tables

rufus-decision is a small ruby gem for ‘running decision tables’. Decision tables are useful for mapping conditions to actions.

which reporter for which eventThis example decision table considers two conditions : ‘topic’ and ‘region’ (I tend to call them ‘inputs’). Certain combinations of condition yield one or more output value.

In the example, when the topic is about finance and the region is Europe, the team member Donald gets selected.

This is a vanilla decision table, as soon as a ‘match’ is found, the run is over, an output value has been found.

Note that the arrangement of rules, their order, does matter. Ernest who is in charge of finance for the rest of the world, comes after America and Europe, Charly and Donald respectively. If Ernest were placed before them, all the input sets with the topic set to ‘finance’ would go to him, ignoring his two colleagues.

Likewise, our ‘handle all the rest’ Zach has been placed last, collecting all the input sets that didn’t match.

It’s easy to reproduce the table with some code :

if topic == 'sports'
  if region == 'europe'
    team_member = 'Alice'
  else
    team_member = 'Bob'
  end
elsif topic == 'finance'
  # ...
elsif topic == 'politics'
  # ...
else
  team_member = 'Zach'
end

Turning business logic into code is a common task for software developers, but what about simply ‘running’ a decision table directly ?

Decision tables are easy to edit with spreadsheet software and that’s a happy coincidence since many domain experts are Excel jockeys (see lay programmer).

Excel isn’t the only spreadsheet software, see for example our decision table in Google Spreadsheet.

At this point, I have to say I’m sorry, I won’t suggest any mean to run decision tables directly inside of Excel, I will bring back the flow to the Ruby environment, with some code triggering our business logic.

require 'rubygems'
require 'rufus/decision' # sudo gem install rufus-decision

table = %{
  in:topic,in:region,out:team_member
  sports,europe,Alice
  sports,,Bob
  finance,america,Charly
  finance,europe,Donald
  finance,,Ernest
  politics,asia,Fujio
  politics,america,Gilbert
  politics,,Henry
  ,,Zach
}

table = Rufus::Decision::Table.new(table)

p table.run('topic' => 'politics', 'region' => 'america')
  # => {"region"=>"america", "topic"=>"politics", "team_member"=>"Gilbert"}

p table.run('topic' => 'sports', 'region' => 'antarctic')
  # => {"region"=>"antarctic", "topic"=>"sports", "team_member"=>"Bob"}

p table.run('topic' => 'culture', 'region' => 'america')
  # => {"region"=>"america", "topic"=>"culture", "team_member"=>"Zach"}

rufus-decision understands decision tables with rules expressed per column as well as per row :

table = %{
  in:topic,sports,sports,finance,finance,finance,politics,politics,politics,
  in:region,europe,,america,europe,,asia,america,,
  out:team_member,Alice,Bob,Charly,Donald,Ernest,Fujio,Gilbert,Henry,Zach
}

The parameter to Rufus::Decision::Table.new() can be a CSV string, an array of arrays, the path to a CSV file or the URI of a CSV file. With a Google spreadsheet [published] table, our example could be cut to :

require 'rubygems'
require 'rufus/decision' # sudo gem install rufus-decision

table = Rufus::Decision::Table.new(
  'http://spreadsheets.google.com/pub?key=rs4Z_1gjtyfgXupxXz_nAYw&output=csv')

# ...

So far so good, our domain expert may publish decision tables on the company information’s system and we can use them directly. But what to do when the number of conditions and rules get large ?

A programmer is expected to test his code, he’s even expected to deliver code with tests. A sane programmer can’t live without tests. Lay programmers should welcome testing as well.

rufus-decision comes with a “rufus_decide” command line tool for running decision tables in batch.

Given this input file (topics_in.csv), where the first row lists the keys and the next rows hold the values :

topic,region
sports,america
politics,europe
culture,
politics,africa

the decision table can be exercised with :

rufus_decide \
-t "http://spreadsheets.google.com/pub?key=rs4Z_1gjtyfgXupxXz_nAYw&output=csv" \
-i topics_in.csv

which will yield :

region,team_member,topic
america,Bob,sports
europe,Henry,politics
,Zach,culture
africa,Henry,politics

‘rufus_decide’ ran the decision table for each row in the input data and generated an output table with one row for each input row.

This ‘ideal’ output can saved in a ‘goal.csv’ file and used to ‘test’ the decision table :

rufus_decide \
-t "http://spreadsheets.google.com/pub?key=rs4Z_1gjtyfgXupxXz_nAYw&output=csv" \
-i topics_in.csv \
-g goal.csv

Running rufus_decide with a goal will emit an output similar to the one of classical unit test tools.

This was an introduction to rufus-decision, whose version 1.1.0 was just released.

web_decisionThe new release contains some kind of web-based environment for testing rules (see thumbnail on left), but well, I’m not quite convinced it’s useful.

source : http://github.com/jmettraux/rufus-decision/
rdoc : http://rufus.rubyforge.org/rufus-decision/

 

Written by John Mettraux

April 25, 2009 at 2:19 pm

Posted in decision, ruby, rufus, rules

Ruote presentation at Stockholm University

I was invited to talk about Ruote at the Information Labs of the Stockholm University (syslab).

My presentation covered the general concepts of Ruote (OpenWFEru) over a few slides. As always, it’s difficult to select the subjects for a rather unknown target audience.

(This time, I was happy to notice that only one person fell asleep. Well, after reflection, I think it was mostly due to the fact it was a lunch presentation, people were eating)

Many thanks to Petia and to the Professor Johannesson for inviting me.

Read the rest of this entry »

Written by John Mettraux

October 15, 2008 at 7:06 am

‘rufus-decision’ csv decision tables

This is the latest episode of my move to Rufus of some of the OpenWFEru subprojects.

The ‘rufus-decision‘ gem contains a Ruby implementation of [CSV] decision tables.

After

sudo gem install -y rufus-decision

given that table :

you can have :

Read the rest of this entry »

Written by John Mettraux

January 28, 2008 at 8:04 am

Posted in csv, decision, ruby, rufus

decision, accumulation

I had a few email exchanges with Fu and I ended up integrating Ruby ranges and ‘accumulate‘ into OpenWFEru’s Ruby CSV decision tables (see that post for an introduction to them).

Not much explanations, just code and links. A Ruby range (x..y) is used in the CSV table to express an age range and ‘accumulate’ is used in the second version of the CSV table to gather a list of results instead of one single result.

The two examples are runnable with the OpenWFEru trunk (will be part of the 0.9.16 gem, promised), so there is some manual work :

wget -O dec.rb http://pastie.caboo.se/pastes/106162/download
svn checkout http://openwferu.rubyforge.org/svn/trunk/openwfe-ruby/lib owfelib
ruby -Iowfelib dec.rb

Here’s what the code looks like, it’s about determining which sales person is appropriate for a given customer :



Noticed that only one answer came back per customer profile ?

If you add the “accumulate” key word on top of the CSV table, you’ll end up with

thorsten, ojiisan
adeslky, bronco, kerfelden
adeslky, swanson
adeslky, bronco, korolev
espadas, ojiisan, korolev

instead of

thorsten
adeslky
adeslky
adeslky
espadas

which were just plain ‘first matches’.

The rdoc of CsvTable is available as well as an [incomplete] page about decision implementations.

Written by John Mettraux

October 12, 2007 at 12:22 am

Posted in decision, ruby