Skip to content

Commit

Permalink
Fjern krav om kun Ja i sekvens, foreach (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
jolarsen authored Feb 23, 2022
1 parent f855e29 commit 53ad273
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 133 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ To model decision trees so they can be visualised, documented, and traced (input

# Features
* Unary expressions:
- Sequence
- Sequence: Sequence of expressions, result = result from last evaluation.
- Foreach:
- Not
- Node: (node computation supplied by implementor) Allows for arbitrary evaluations to be included.
* Binary expressions:
- And
- Or
* Ternary expressions:
- Conditional If/Else: Allows for evaluating only specific subtrees based on a provided condition.
* - Computational If/Else: Simple if/then or if/then/else. Returns result from .
* N-ary expressions:
- Conditional If/Or/Else: Allows for evaluating only specific subtrees based on a provided condition.

# Rule documentation
The rule specification may be output to Asciidoc using the supplied Javadoc Doclet. This will output names of parameters and rules to a single asciidoc document, while the trees will be output to a json document as a set of nodes and edges. The output format in json is the same for both the specification and evaluation trees (except a few evaluation specific parameters), making both suitable for long term storage.
Expand Down
45 changes: 41 additions & 4 deletions src/main/java/no/nav/fpsak/nare/Ruleset.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

Expand All @@ -21,16 +22,24 @@ public Specification<V> regel(String id, String beskrivelse, Specification<V> sp
return specification.medBeskrivelse(beskrivelse).medID(id);
}

public ConditionalOrSpecification.Builder<V> hvisRegel() {
return ConditionalOrSpecification.<V>regel();
}

public ConditionalOrSpecification.Builder<V> hvisRegel(String id, String beskrivelse) {
return ConditionalOrSpecification.<V>regel(id, beskrivelse);
}

public SequenceSpecification.Builder<V> sekvensRegel() {
return SequenceSpecification.<V>regel();
}

public SequenceSpecification.Builder<V> sekvensRegel(String id, String beskrivelse) {
return SequenceSpecification.<V>regel(id, beskrivelse);
}

public ComputationalIfSpecification.Builder<V> betingetSekvensRegel(Specification<V> hvis, Specification<V> evaluer) {
return ComputationalIfSpecification.<V>regel().hvis(hvis, evaluer);
public ComputationalIfSpecification.Builder<V> sekvensHvisRegel(Specification<V> hvis, Specification<V> evaluer) {
return ComputationalIfSpecification.<V>regel();
}

/**
Expand Down Expand Up @@ -82,6 +91,16 @@ public Specification<V> beregningsRegel(String id, String beskrivelse, Specifica
return new SequenceSpecification<>(id, beskrivelse, specs);
}

/**
* Realiserer sekvens av N spesifikasjoner der bare den siste har betydning for videre flyt
*
* @param specs
* @return
*/
public Specification<V> beregningsRegel(Specification<V>... specs) {
return new SequenceSpecification<>(Arrays.asList(specs));
}

/**
* Realiserer sekvens av N spesifikasjoner der bare den siste har betydning for videre flyt
*
Expand All @@ -91,8 +110,7 @@ public Specification<V> beregningsRegel(String id, String beskrivelse, Specifica
* @param spec2
* @return
*/
public Specification<V> beregningsRegel(String id, String beskrivelse, List<Specification<V>> spec1list,
Specification<V> spec2) {
public Specification<V> beregningsRegel(String id, String beskrivelse, List<Specification<V>> spec1list, Specification<V> spec2) {
List<Specification<V>> specs = new ArrayList<>();
specs.addAll(spec1list);
specs.add(spec2);
Expand All @@ -119,6 +137,25 @@ public Specification<V> beregningsForeachRegel(String id, String beskrivelse, Sp
return new ForeachSpecification<>(id, beskrivelse, spec, args, argName);
}

/**
* Realiserer sekvens der en regel utføres N ganger - 1 gang for hvert element i en collection
*
* @param id
* @param beskrivelse
* @param spec regel som utføres 1 gang for hvert element i argumentlisten, med argName + arg som "scope"
* @param argName
* @param args argumentlisten, inneholder N argumenter
* @return
*/
public Specification<V> beregningsForeachRegel(Specification<V> spec, String argName, List<?> args) {
Objects.requireNonNull(spec, "spec1");
Objects.requireNonNull(args, "args1");
if (args.isEmpty()) {
throw new IllegalArgumentException("Argumentlisten kan ikke være tom");
}
return new ForeachSpecification<>(spec, args, argName);
}

/**
* Realiserer sekvens der en regel utføres N ganger - 1 gang for hvert element i en collection - fulgt av annen regel
*
Expand Down
20 changes: 16 additions & 4 deletions src/main/java/no/nav/fpsak/nare/doc/RuleDescriptionDigraph.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;

import no.nav.fpsak.nare.evaluation.Operator;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import no.nav.fpsak.nare.evaluation.Operator;

@JsonIgnoreProperties(ignoreUnknown = true)
public class RuleDescriptionDigraph {

Expand Down Expand Up @@ -37,7 +37,7 @@ private RuleNode process(RuleDescription ruledesc) {
RuleNode myNode = new RuleNode(ruledesc);
nodes.add(myNode);

if (ruledesc.getOperator() == Operator.COND_OR) {
if (ruledesc.getOperator() == Operator.COND_OR && erConditionalOrSpecification(ruledesc)) {
processCondOrNodes(ruledesc, myNode);
} else {
processRegularNodes(ruledesc, myNode);
Expand All @@ -53,9 +53,21 @@ protected void processRegularNodes(RuleDescription ruledesc, RuleNode myNode) {
}
}

// Forksjell på EvalDescriptions og SpecDescriptions her
// SpecDescription vil ha en magisk unær AND ..... og man lager en rolle på edge
// EvalDescription kun vil treffe en av grenene
private boolean erConditionalOrSpecification(RuleDescription description) {
return description.getChildren().stream()
.anyMatch(c -> Operator.AND.equals(c.getOperator()) && c.getChildren().size() == 1);
}

/*
* TODO: Vurder et binært expression-tree, som dette i realiteten er
*/
protected void processCondOrNodes(RuleDescription ruledesc, RuleNode condorNode) {
for (RuleDescription child : ruledesc.getChildren()) {
if (child.getOperator() == Operator.AND) {
// SpecDescription vil ha en magisk unær AND ..... og man lager en rolle på edge
if (child.getOperator() == Operator.AND && child.getChildren().size() == 1) {
RuleNode flowChild = process(child.firstChild());
String edgeRole = child.getRuleDescription();
edges.add(new RuleEdge(condorNode, flowChild, edgeRole));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public ForeachEvaluation(String id, String ruleDescription, Evaluation... childr

@Override
public String reason() {
return "Utført " + ruleDescriptionText();
return "Utført " + lastChild().ruleIdentification();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package no.nav.fpsak.nare.evaluation.summary;

import no.nav.fpsak.nare.doc.JsonOutput;
import no.nav.fpsak.nare.doc.RuleDescriptionDigraph;
import no.nav.fpsak.nare.evaluation.Evaluation;
import no.nav.fpsak.nare.specification.Specification;

public class EvaluationSerializer {

/**
* @deprecated Kun av historisk interesse. Bruk #asJson
*/
@Deprecated
public static String asLegacyJsonTree(Evaluation evaluation) {
return JsonOutput.asJson(evaluation);
}

public static String asJson(Evaluation evaluation) {
RuleDescriptionDigraph digraph = new RuleDescriptionDigraph(evaluation.toRuleDescription());
var desc = evaluation.toRuleDescription();
RuleDescriptionDigraph digraph = new RuleDescriptionDigraph(desc);
return digraph.toJson();
}

public static String asJson(Specification<?> specification) {
RuleDescriptionDigraph digraph = new RuleDescriptionDigraph(specification.ruleDescription());
return digraph.toJson();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,38 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import no.nav.fpsak.nare.evaluation.AggregatedEvaluation;
import no.nav.fpsak.nare.evaluation.Evaluation;
import no.nav.fpsak.nare.evaluation.Operator;
import no.nav.fpsak.nare.evaluation.Resultat;
import no.nav.fpsak.nare.evaluation.RuleReasonRef;

/** Hjelpeklasse for å lage sammendrag av en evaluering. */
public class EvaluationSummary {

private final class LeafNodeVisitorCondition implements EvaluationVisitorCondition {
private final Set<Resultat> accepted;
private boolean all;

private LeafNodeVisitorCondition(Set<Resultat> accepted) {
private LeafNodeVisitorCondition(Set<Resultat> accepted, boolean all) {
this.accepted = accepted;
this.all = all;
}

@Override
public boolean check(Operator op, Evaluation parent, Evaluation child) {
if (!(Operator.SINGLE.equals(op) && accepted.contains(child.result()))) {
return false;
}
if (all) {
return true;
}

// special case, kun prosessere siste SingleEvaluation barn av SequenceEvaluation siden alle andre returnerer ja.
if (Operator.SEQUENCE.equals(parent.getOperator()) || Operator.FOREACH.equals(parent.getOperator())) {
Expand All @@ -38,7 +47,7 @@ public boolean check(Operator op, Evaluation parent, Evaluation child) {
}
}

interface EvaluationVisitorCondition {
public interface EvaluationVisitorCondition {
boolean check(Operator operator, Evaluation parent, Evaluation child);
}

Expand Down Expand Up @@ -68,9 +77,29 @@ public Collection<Evaluation> leafEvaluations(Resultat... acceptedResults) {
Set<Resultat> accepted = acceptedResults.length > 0 ? EnumSet.copyOf(Arrays.asList(acceptedResults))
: EnumSet.allOf(Resultat.class);

NonCircularVisitorEvaluationCollector visitor = new NonCircularVisitorEvaluationCollector(new LeafNodeVisitorCondition(accepted));
NonCircularVisitorEvaluationCollector visitor = new NonCircularVisitorEvaluationCollector(new LeafNodeVisitorCondition(accepted, false));
rootEvaluation.visit(rootEvaluation, visitor);
return visitor.getCollected();
}

public Collection<Evaluation> allLeafEvaluations(Resultat... acceptedResults) {
Set<Resultat> accepted = acceptedResults.length > 0 ? EnumSet.copyOf(Arrays.asList(acceptedResults))
: EnumSet.allOf(Resultat.class);

NonCircularVisitorEvaluationCollector visitor = new NonCircularVisitorEvaluationCollector(new LeafNodeVisitorCondition(accepted, true));
rootEvaluation.visit(rootEvaluation, visitor);
return visitor.getCollected();
}

public List<RuleReasonRef> allOutcomes() {
Set<Resultat> accepted = EnumSet.allOf(Resultat.class);

NonCircularVisitorEvaluationCollector visitor = new NonCircularVisitorEvaluationCollector(new LeafNodeVisitorCondition(accepted, true));
rootEvaluation.visit(rootEvaluation, visitor);
return visitor.getCollected().stream()
.map(Evaluation::getOutcome)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package no.nav.fpsak.nare.specification;

import java.util.Map;
import java.util.Optional;

import no.nav.fpsak.nare.doc.JsonOutput;
import no.nav.fpsak.nare.doc.RuleDescription;
Expand Down Expand Up @@ -90,4 +91,12 @@ public RuleDescription ruleDescription() {
public String toString() {
return JsonOutput.asJson(this);
}

protected Optional<String> identifikatorIkkeTom () {
return Optional.ofNullable(id).filter(s -> !s.isEmpty());
}

protected Optional<String> beskrivelseIkkeTom () {
return Optional.ofNullable(beskrivelse).filter(s -> !s.isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ public AndSpecification(final Specification<T> spec1, final Specification<T> spe

@Override
public String beskrivelse() {
String beskrivelse = super.beskrivelse();
if (beskrivelse.isEmpty()) {
return "(" + spec1.beskrivelse() + " OG " + spec2.beskrivelse() + ")";
} else {
return beskrivelse;
}
return beskrivelseIkkeTom().orElse("(OG)");
}

@Override
Expand All @@ -31,12 +26,7 @@ public Evaluation evaluate(final T t) {

@Override
public String identifikator() {
String id = super.identifikator();
if (id.isEmpty()) {
return "(" + spec1.identifikator() + " OG " + spec2.identifikator() + ")";
} else {
return id;
}
return identifikatorIkkeTom().orElseGet(() -> "(" + spec1.identifikator() + " OG " + spec2.identifikator() + ")");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package no.nav.fpsak.nare.specification;

import java.util.Optional;

import no.nav.fpsak.nare.ServiceArgument;
import no.nav.fpsak.nare.doc.RuleDescription;
import no.nav.fpsak.nare.evaluation.Evaluation;
Expand Down Expand Up @@ -33,6 +35,12 @@ public Builder<T> hvis(Specification<T> ifSpec, Specification<T> thenSpec) {
return this;
}

public ComputationalIfSpecification<T> hvisEllersJa(Specification<T> ifSpec, Specification<T> thenSpec) {
this.testSpec = ifSpec;
this.ifTrueSpec = thenSpec;
return this.build();
}

public ComputationalIfSpecification<T> ellers(Specification<T> specification) {
this.ifFalseSpec = specification;
return this.build();
Expand Down Expand Up @@ -67,9 +75,14 @@ public ComputationalIfSpecification(final Specification<T> testSpec, final Speci
this.ifFalseSpec = ifFalseSpec;
}

private Optional<Specification<T>> eller() {
return Optional.ofNullable(ifFalseSpec);
}

@Override
public String beskrivelse() {
return "...";
return beskrivelseIkkeTom()
.orElseGet(() -> "(COMP HVIS/SÅ" + eller().map(s -> "/ELLERS").orElse("") + ")");
}

@Override
Expand Down Expand Up @@ -108,12 +121,16 @@ private Evaluation doEvaluateIfFalse(final T t, ServiceArgument serviceArgument)

@Override
public String identifikator() {
return "...";
return identifikatorIkkeTom().orElseGet(() -> "(HVIS " + testSpec.identifikator() + " SÅ " + ifTrueSpec.identifikator() + eller().map(s -> " ELLERS " + s.identifikator()).orElse("") + ")");
}

@Override
public RuleDescription ruleDescription() {
return new SpecificationRuleDescription(Operator.COMPUTATIONAL_IF, identifikator(), beskrivelse(), testSpec.ruleDescription(), ifTrueSpec.ruleDescription(), ifFalseSpec.ruleDescription());
if (ifFalseSpec != null) {
return new SpecificationRuleDescription(Operator.COMPUTATIONAL_IF, identifikator(), beskrivelse(), testSpec.ruleDescription(), ifTrueSpec.ruleDescription(), ifFalseSpec.ruleDescription());
} else {
return new SpecificationRuleDescription(Operator.COMPUTATIONAL_IF, identifikator(), beskrivelse(), testSpec.ruleDescription(), ifTrueSpec.ruleDescription());
}
}

@Override
Expand Down
Loading

0 comments on commit 53ad273

Please sign in to comment.