POC Decision Tree traverser with rules
This project requires Neo4j 3.3.x or higher
This project uses maven, to build a jar-file with the procedure in this project, simply package the project with maven:
mvn clean package
This will produce a jar-file, target/decision_trees_with_rules-1.0-SNAPSHOT.jar
,
that can be copied to the plugin
directory of your Neo4j instance.
cp target/decision_trees_with_rules-1.0-SNAPSHOT.jar neo4j-enterprise-3.3.1/plugins/.
Download and Copy two additional files to your Neo4j plugins directory:
http://central.maven.org/maven2/org/codehaus/janino/commons-compiler/3.0.8/commons-compiler-3.0.8.jar
http://central.maven.org/maven2/org/codehaus/janino/janino/3.0.8/janino-3.0.8.jar
Edit your Neo4j/conf/neo4j.conf file by adding this line:
dbms.security.procedures.unrestricted=com.blockfint.*
Restart your Neo4j Server.
Create the Schema by running this stored procedure:
CALL com.blockfint.schema.generate
Create some test data:
CREATE (tree:Tree { id: 'bar entrance' })
CREATE (over21_rule:Rule { name: 'Over 21?', parameter_names: 'age', parameter_types:'int', expression:'age >= 21' })
CREATE (gender_rule:Rule { name: 'Over 18 and female', parameter_names: 'age,gender', parameter_types:'int,String', expression:'(age >= 18) && gender.equals(\"female\")' })
CREATE (answer_yes:Answer { id: 'yes'})
CREATE (answer_no:Answer { id: 'no'})
CREATE (tree)-[:HAS]->(over21_rule)
CREATE (over21_rule)-[:IS_TRUE]->(answer_yes)
CREATE (over21_rule)-[:IS_FALSE]->(gender_rule)
CREATE (gender_rule)-[:IS_TRUE]->(answer_yes)
CREATE (gender_rule)-[:IS_FALSE]->(answer_no)
CREATE (tree:Tree { id: 'lending decision' })
CREATE (income_rule:Rule { name: 'Income range of applicant', expressions: 'income < 30000, income >= 30000 && income <= 70000, income > 70000', parameter_names: 'income', parameter_types: 'int' })
CREATE (criminal_rule:Rule { name: 'Criminal record?', expressions: 'has_criminal_record, !has_criminal_record', parameter_names: 'has_criminal_record', parameter_types: 'boolean' })
CREATE (criminal_2_rule:Rule { name: 'Criminal record?', expressions: '!has_criminal_record, has_criminal_record', parameter_names: 'has_criminal_record', parameter_types: 'boolean' })
CREATE (job_rule:Rule { name: 'Years in present job?', expressions: 'years < 1, years >=1 && years <=5, years > 5', parameter_names: 'years', parameter_types: 'int' })
CREATE (credit_rule:Rule { name: 'Make credit card payments?', expressions: 'paid, !paid', parameter_names: 'paid', parameters_types: 'boolean' })
CREATE (approve_answer:Answer { id: 'approve' })
CREATE (reject_answer:Answer { id: 'reject' })
CREATE (tree)-[:HAS]->(income_rule)
CREATE (income_rule)-[:CASE_0]->(criminal_rule)
CREATE (criminal_rule)-[:CASE_0]->(approve_answer)
CREATE (criminal_rule)-[:CASE_1]->(reject_answer)
CREATE (income_rule)-[:CASE_1]->(job_rule)
CREATE (job_rule)-[:CASE_0]->(reject_answer)
CREATE (job_rule)-[:CASE_1]->(credit_rule)
CREATE (credit_rule)-[:CASE_0]->(approve_answer)
CREATE (credit_rule)-[:CASE_1]->(reject_answer)
CREATE (job_rule)-[:CASE_2]->(approve_answer)
CREATE (income_rule)-[:CASE_2]->(criminal_2_rule)
CREATE (criminal_2_rule)-[:CASE_0]->(approve_answer)
CREATE (criminal_2_rule)-[:CASE_1]->(reject_answer)
Try it:
CALL com.blockfint.traverse.decision_tree('bar entrance', {gender:'male', age:'20'}) yield path return path;
CALL com.blockfint.traverse.decision_tree('bar entrance', {gender:'female', age:'19'}) yield path return path;
CALL com.blockfint.traverse.decision_tree('bar entrance', {gender:'male', age:'23'}) yield path return path;
Evaluating Scripts instead of expressions.
Create some test data:
CREATE (tree:Tree { id: 'funeral' })
CREATE (good_man_rule:Rule { name: 'Was Lil Jon a good man?', parameter_names: 'answer_1', parameter_types:'String', script:'switch (answer_1) { case \"yeah\": return \"OPTION_1\"; case \"what\": return \"OPTION_2\"; case \"okay\": return \"OPTION_3\"; default: return \"UNKNOWN\"; }' })
CREATE (good_man_two_rule:Rule { name: 'I said, was he a good man?', parameter_names: 'answer_2', parameter_types:'String', script:'switch (answer_2) { case \"yeah\": return \"OPTION_1\"; case \"what\": return \"OPTION_2\"; case \"okay\": return \"OPTION_3\"; default: return \"UNKNOWN\"; }' })
CREATE (rest_in_peace_rule:Rule { name: 'May he rest in peace', parameter_names: 'answer_3', parameter_types:'String', script:'switch (answer_3) { case \"yeah\": return \"OPTION_1\"; case \"what\": return \"OPTION_2\"; case \"okay\": return \"OPTION_3\"; default: return \"UNKNOWN\"; } ' })
CREATE (answer_correct:Answer { id: 'correct'})
CREATE (answer_incorrect:Answer { id: 'incorrect'})
CREATE (answer_unknown:Answer { id: 'unknown'})
CREATE (tree)-[:HAS]->(good_man_rule)
CREATE (good_man_rule)-[:OPTION_1]->(answer_incorrect)
CREATE (good_man_rule)-[:OPTION_2]->(good_man_two_rule)
CREATE (good_man_rule)-[:OPTION_3]->(answer_incorrect)
CREATE (good_man_rule)-[:UNKNOWN]->(answer_unknown)
CREATE (good_man_two_rule)-[:OPTION_1]->(rest_in_peace_rule)
CREATE (good_man_two_rule)-[:OPTION_2]->(answer_incorrect)
CREATE (good_man_two_rule)-[:OPTION_3]->(answer_incorrect)
CREATE (good_man_two_rule)-[:UNKNOWN]->(answer_unknown)
CREATE (rest_in_peace_rule)-[:OPTION_1]->(answer_incorrect)
CREATE (rest_in_peace_rule)-[:OPTION_2]->(answer_incorrect)
CREATE (rest_in_peace_rule)-[:OPTION_3]->(answer_correct)
CREATE (rest_in_peace_rule)-[:UNKNOWN]->(answer_unknown);
Try it:
CALL com.blockfint.traverse.decision_tree_two('funeral', {answer_1:'yeah', answer_2:'yeah', answer_3:'yeah'}) yield path return path
CALL com.blockfint.traverse.decision_tree_two('funeral', {answer_1:'what', answer_2:'', answer_3:''}) yield path return path
CALL com.blockfint.traverse.decision_tree_two('funeral', {answer_1:'what', answer_2:'yeah', answer_3:'okay'}) yield path return path