This document discusses using decision trees to build dynamic rule-based systems in Neo4j. It describes representing rules as nodes connected in a graph structure, with relationships determining the tree's logic flow. An example checks age and gender rules to determine entrance to a bar. Traversal APIs and custom expanders/evaluators are used to dynamically traverse the tree based on input facts. The approach allows complex rulesets to be modeled and evaluated through graph queries and traversal.
6. Rule for Getting In
• Anyone over 21 years old gets in.
• On some nights, women 18 years or older get in.
• On some nights, men 18 years or older get in.
• The last two change dynamically.
15. @Procedure(name = "com.maxdemarzi.traverse.decision_tree", mode = Mode.READ)
@Description("CALL com.maxdemarzi.traverse.decision_tree(tree, facts)")
public Stream<PathResult> traverseDecisionTree(
@Name("tree") String id,
@Name("facts") Map<String, String> facts){
// Which Decision Tree are we interested in?
Node tree = db.findNode(Labels.Tree, "id", id);
if ( tree != null) {
// Find the paths by traversing this graph and the facts given
return decisionPath(tree, facts);
}
return null;
}
Tree Facts
18. public class DecisionTreeEvaluator implements PathEvaluator {
@Override
public Evaluation evaluate(Path path, BranchState branchState) {
// If we get to an Answer stop traversing, we found a valid path.
if (path.endNode().hasLabel(Labels.Answer)) {
return Evaluation.INCLUDE_AND_PRUNE;
} else {
// If not, continue down this path
// to see if there is anything else to find.
return Evaluation.EXCLUDE_AND_CONTINUE;
}
}
Build a Path Evaluator
19.
20. public class DecisionTreeExpander implements PathExpander {
private Map<String, String> facts;
private ExpressionEvaluator ee = new ExpressionEvaluator();
public DecisionTreeExpander(Map<String, String> facts) {
this.facts = facts;
ee.setExpressionType(boolean.class);
}
Build a Path Expander
21. @Override
public Iterable<Relationship> expand(Path path, BranchState branchState) {
// If we get to an Answer stop traversing, we found a valid path.
if (path.endNode().hasLabel(Labels.Answer)) {
return Collections.emptyList();
}
// If we have Rules to evaluate, go do that.
if (path.endNode()
.hasRelationship(Direction.OUTGOING, RelationshipTypes.HAS)) {
return path.endNode()
.getRelationships(Direction.OUTGOING, RelationshipTypes.HAS);
}
The expand method
22. if (path.endNode().hasLabel(Labels.Rule)) {
try {
if (isTrue(path.endNode())) {
return path.endNode()
.getRelationships(Direction.OUTGOING,
RelationshipTypes.IS_TRUE);
} else {
return path.endNode()
.getRelationships(Direction.OUTGOING,
RelationshipTypes.IS_FALSE);
}
} catch (Exception e) {
// Could not continue this way!
return Collections.emptyList();
}
}
The expand method (cont.)