From 53ad273a450ff84b4c8ea49acb91bc339c1bd398 Mon Sep 17 00:00:00 2001 From: Jens-Otto Larsen <46576810+jolarsen@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:10:38 +0100 Subject: [PATCH] Fjern krav om kun Ja i sekvens, foreach (#95) --- README.md | 7 ++- src/main/java/no/nav/fpsak/nare/Ruleset.java | 45 ++++++++++++-- .../nare/doc/RuleDescriptionDigraph.java | 20 ++++-- .../evaluation/node/ForeachEvaluation.java | 2 +- .../summary/EvaluationSerializer.java | 18 +++--- .../evaluation/summary/EvaluationSummary.java | 35 ++++++++++- .../specification/AbstractSpecification.java | 9 +++ .../nare/specification/AndSpecification.java | 14 +---- .../ComputationalIfSpecification.java | 23 ++++++- .../ConditionalOrSpecification.java | 28 ++++++++- .../specification/ForeachSpecification.java | 27 +++----- .../nare/specification/NotSpecification.java | 15 +---- .../nare/specification/OrSpecification.java | 14 +---- .../specification/SequenceSpecification.java | 61 +++++++++++++------ ...rekvoteConditionalOrSpecificationTest.java | 7 +-- .../regler/CountingpecificationTest.java | 55 +++++++++-------- 16 files changed, 247 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index ad4f0a4..362d33c 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/main/java/no/nav/fpsak/nare/Ruleset.java b/src/main/java/no/nav/fpsak/nare/Ruleset.java index 1376105..3d4f954 100644 --- a/src/main/java/no/nav/fpsak/nare/Ruleset.java +++ b/src/main/java/no/nav/fpsak/nare/Ruleset.java @@ -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; @@ -21,16 +22,24 @@ public Specification regel(String id, String beskrivelse, Specification sp return specification.medBeskrivelse(beskrivelse).medID(id); } + public ConditionalOrSpecification.Builder hvisRegel() { + return ConditionalOrSpecification.regel(); + } + public ConditionalOrSpecification.Builder hvisRegel(String id, String beskrivelse) { return ConditionalOrSpecification.regel(id, beskrivelse); } + public SequenceSpecification.Builder sekvensRegel() { + return SequenceSpecification.regel(); + } + public SequenceSpecification.Builder sekvensRegel(String id, String beskrivelse) { return SequenceSpecification.regel(id, beskrivelse); } - public ComputationalIfSpecification.Builder betingetSekvensRegel(Specification hvis, Specification evaluer) { - return ComputationalIfSpecification.regel().hvis(hvis, evaluer); + public ComputationalIfSpecification.Builder sekvensHvisRegel(Specification hvis, Specification evaluer) { + return ComputationalIfSpecification.regel(); } /** @@ -82,6 +91,16 @@ public Specification 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 beregningsRegel(Specification... specs) { + return new SequenceSpecification<>(Arrays.asList(specs)); + } + /** * Realiserer sekvens av N spesifikasjoner der bare den siste har betydning for videre flyt * @@ -91,8 +110,7 @@ public Specification beregningsRegel(String id, String beskrivelse, Specifica * @param spec2 * @return */ - public Specification beregningsRegel(String id, String beskrivelse, List> spec1list, - Specification spec2) { + public Specification beregningsRegel(String id, String beskrivelse, List> spec1list, Specification spec2) { List> specs = new ArrayList<>(); specs.addAll(spec1list); specs.add(spec2); @@ -119,6 +137,25 @@ public Specification 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 beregningsForeachRegel(Specification 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 * diff --git a/src/main/java/no/nav/fpsak/nare/doc/RuleDescriptionDigraph.java b/src/main/java/no/nav/fpsak/nare/doc/RuleDescriptionDigraph.java index 90878f9..1d6b239 100644 --- a/src/main/java/no/nav/fpsak/nare/doc/RuleDescriptionDigraph.java +++ b/src/main/java/no/nav/fpsak/nare/doc/RuleDescriptionDigraph.java @@ -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 { @@ -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); @@ -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)); diff --git a/src/main/java/no/nav/fpsak/nare/evaluation/node/ForeachEvaluation.java b/src/main/java/no/nav/fpsak/nare/evaluation/node/ForeachEvaluation.java index 2f28c6d..7d4991d 100644 --- a/src/main/java/no/nav/fpsak/nare/evaluation/node/ForeachEvaluation.java +++ b/src/main/java/no/nav/fpsak/nare/evaluation/node/ForeachEvaluation.java @@ -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 diff --git a/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSerializer.java b/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSerializer.java index 9506e9d..8316067 100644 --- a/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSerializer.java +++ b/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSerializer.java @@ -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(); } diff --git a/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSummary.java b/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSummary.java index 5228adb..122fdeb 100644 --- a/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSummary.java +++ b/src/main/java/no/nav/fpsak/nare/evaluation/summary/EvaluationSummary.java @@ -3,22 +3,28 @@ 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 accepted; + private boolean all; - private LeafNodeVisitorCondition(Set accepted) { + private LeafNodeVisitorCondition(Set accepted, boolean all) { this.accepted = accepted; + this.all = all; } @Override @@ -26,6 +32,9 @@ 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())) { @@ -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); } @@ -68,9 +77,29 @@ public Collection leafEvaluations(Resultat... acceptedResults) { Set 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 allLeafEvaluations(Resultat... acceptedResults) { + Set 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 allOutcomes() { + Set 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()); + } + } diff --git a/src/main/java/no/nav/fpsak/nare/specification/AbstractSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/AbstractSpecification.java index ab25d27..6bc08f7 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/AbstractSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/AbstractSpecification.java @@ -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; @@ -90,4 +91,12 @@ public RuleDescription ruleDescription() { public String toString() { return JsonOutput.asJson(this); } + + protected Optional identifikatorIkkeTom () { + return Optional.ofNullable(id).filter(s -> !s.isEmpty()); + } + + protected Optional beskrivelseIkkeTom () { + return Optional.ofNullable(beskrivelse).filter(s -> !s.isEmpty()); + } } diff --git a/src/main/java/no/nav/fpsak/nare/specification/AndSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/AndSpecification.java index c61b2e2..c2f41e8 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/AndSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/AndSpecification.java @@ -16,12 +16,7 @@ public AndSpecification(final Specification spec1, final Specification 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 @@ -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 diff --git a/src/main/java/no/nav/fpsak/nare/specification/ComputationalIfSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/ComputationalIfSpecification.java index 80c1db5..0ea951b 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/ComputationalIfSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/ComputationalIfSpecification.java @@ -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; @@ -33,6 +35,12 @@ public Builder hvis(Specification ifSpec, Specification thenSpec) { return this; } + public ComputationalIfSpecification hvisEllersJa(Specification ifSpec, Specification thenSpec) { + this.testSpec = ifSpec; + this.ifTrueSpec = thenSpec; + return this.build(); + } + public ComputationalIfSpecification ellers(Specification specification) { this.ifFalseSpec = specification; return this.build(); @@ -67,9 +75,14 @@ public ComputationalIfSpecification(final Specification testSpec, final Speci this.ifFalseSpec = ifFalseSpec; } + private Optional> eller() { + return Optional.ofNullable(ifFalseSpec); + } + @Override public String beskrivelse() { - return "..."; + return beskrivelseIkkeTom() + .orElseGet(() -> "(COMP HVIS/SÅ" + eller().map(s -> "/ELLERS").orElse("") + ")"); } @Override @@ -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 diff --git a/src/main/java/no/nav/fpsak/nare/specification/ConditionalOrSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/ConditionalOrSpecification.java index 4a238f4..94b981b 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/ConditionalOrSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/ConditionalOrSpecification.java @@ -28,10 +28,12 @@ public class ConditionalOrSpecification extends AbstractSpecification { public static class Builder { private final List> conditionalEntries = new ArrayList<>(); private Specification elseCondition; - private String id; - private String beskrivelse; + private final String id; + private final String beskrivelse; public Builder() { + this.id = ""; + this.beskrivelse = ""; } public Builder(String id, String beskrivelse) { @@ -108,6 +110,10 @@ public ConditionalOrSpecification(List> conditionalEntries, Speci } } + /* + * TODO: Vurder et binært/ternært expression-tree, som dette i realiteten er. + * OBS: testSpec som eval til NEI kastes - tenk ikke-komplett regelsporing vs regelversjonering + */ @Override public Evaluation evaluate(final T t) { AtomicReference lastTestResult = new AtomicReference<>(); @@ -131,6 +137,24 @@ public Evaluation evaluate(final T t) { } } + @Override + public String identifikator() { + return identifikatorIkkeTom().orElseGet(() -> { + var condFirstEntry = conditionalEntries.isEmpty() ? null : conditionalEntries.get(0); + var condId = condFirstEntry != null ? ("HVIS " + condFirstEntry.testSpec.identifikator() + " SÅ ... ") : ""; + var elseId = elseCondition != null ? ("ELLERS " + elseCondition.identifikator()) : ""; + return "(COND " + condId + elseId + ")"; + }); + } + + @Override + public String beskrivelse() { + return beskrivelseIkkeTom().orElse("(COND HVIS/SÅ/.../ELLERS)"); + } + + /* + * TODO: Vurder et binært expression-tree, som dette i realiteten er. Dessuten flyr det ikke for komplekse test-clauses + */ @Override public RuleDescription ruleDescription() { var rootSpecId = identifikator(); diff --git a/src/main/java/no/nav/fpsak/nare/specification/ForeachSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/ForeachSpecification.java index 6918b56..0985431 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/ForeachSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/ForeachSpecification.java @@ -7,7 +7,6 @@ import no.nav.fpsak.nare.doc.RuleDescription; 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.node.ForeachEvaluation; /** @@ -19,26 +18,27 @@ public class ForeachSpecification extends AbstractSpecification { private List args; private String argName; - public ForeachSpecification(String id, String beskrivelse, Specification spec, List args, String argName) { + public ForeachSpecification(Specification spec, List args, String argName) { super(); - Objects.requireNonNull(id, "ID"); - Objects.requireNonNull(beskrivelse, "beskrivelse"); Objects.requireNonNull(spec, "spec"); Objects.requireNonNull(args, "args"); Objects.requireNonNull(argName, "argName"); this.spec = spec; this.args = args; this.argName = argName; + } + + public ForeachSpecification(String id, String beskrivelse, Specification spec, List args, String argName) { + this(spec, args, argName); + Objects.requireNonNull(id, "ID"); + Objects.requireNonNull(beskrivelse, "beskrivelse"); medID(id); medBeskrivelse(beskrivelse); } @Override public String beskrivelse() { - if (super.beskrivelse().isEmpty()) { - return "(FOREACH " + argName + ": " + spec.beskrivelse() + ")"; - } - return super.beskrivelse(); + return beskrivelseIkkeTom().orElseGet(() -> "(FOREACH " + argName + ")"); } @Override @@ -47,11 +47,7 @@ public Evaluation evaluate(final T t) { Evaluation[] evaluations = new Evaluation[args.size()]; for (var arg : args) { var serviceArgument = new ServiceArgument(argName, arg); - var eval = spec.evaluate(t, serviceArgument); - evaluations[ix] = eval; - if (!Resultat.JA.equals(evaluations[ix].result())) { - throw new IllegalArgumentException("Utviklerfeil: ForeachSpecification evaluering annet enn JA."); - } + evaluations[ix] = spec.evaluate(t, serviceArgument); ix++; } var resultingEval = new ForeachEvaluation(identifikator(), beskrivelse(), evaluations); @@ -68,10 +64,7 @@ public Evaluation evaluate(final T t, ServiceArgument serviceArgument) { @Override public String identifikator() { - if (super.identifikator().isEmpty()) { - return "(FOREACH " + argName + ": " + spec.beskrivelse() + ")"; - } - return super.identifikator(); + return identifikatorIkkeTom().orElseGet(() -> "(FOREACH " + argName + ": " + spec.identifikator() + ")"); } @Override diff --git a/src/main/java/no/nav/fpsak/nare/specification/NotSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/NotSpecification.java index 6c46928..78209ab 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/NotSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/NotSpecification.java @@ -22,13 +22,7 @@ public NotSpecification(final Specification spec1) { @Override public String beskrivelse() { - String beskrivelse = super.beskrivelse(); - if (beskrivelse.isEmpty()) { - return "(IKKE " + spec1.beskrivelse() + ")"; - } else { - return beskrivelse; - } - + return beskrivelseIkkeTom().orElse("(IKKE)"); } @Override @@ -38,12 +32,7 @@ public Evaluation evaluate(final T t) { @Override public String identifikator() { - String id = super.identifikator(); - if (id.isEmpty()) { - return "(IKKE " + spec1.identifikator() + ")"; - } else { - return id; - } + return identifikatorIkkeTom().orElseGet(() -> "(IKKE " + spec1.identifikator() + ")"); } @Override diff --git a/src/main/java/no/nav/fpsak/nare/specification/OrSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/OrSpecification.java index 0ce3846..a13ad82 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/OrSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/OrSpecification.java @@ -16,12 +16,7 @@ public OrSpecification(final Specification spec1, final Specification spec @Override public String beskrivelse() { - String beskrivelse = super.beskrivelse(); - if (beskrivelse.isEmpty()) { - return "(" + spec1.beskrivelse() + " ELLER " + spec2.beskrivelse() + ")"; - } else { - return beskrivelse; - } + return beskrivelseIkkeTom().orElse("(ELLER)"); } @Override @@ -31,12 +26,7 @@ public Evaluation evaluate(final T t) { @Override public String identifikator() { - String id = super.identifikator(); - if (id.isEmpty()) { - return "(" + spec1.identifikator() + " ELLER " + spec2.identifikator() + ")"; - } else { - return id; - } + return identifikatorIkkeTom().orElseGet(() -> "(" + spec1.identifikator() + " ELLER " + spec2.identifikator() + ")"); } @Override diff --git a/src/main/java/no/nav/fpsak/nare/specification/SequenceSpecification.java b/src/main/java/no/nav/fpsak/nare/specification/SequenceSpecification.java index cf6a4ab..c16f5f7 100644 --- a/src/main/java/no/nav/fpsak/nare/specification/SequenceSpecification.java +++ b/src/main/java/no/nav/fpsak/nare/specification/SequenceSpecification.java @@ -9,7 +9,6 @@ import no.nav.fpsak.nare.doc.RuleDescription; 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.node.SequenceEvaluation; /** @@ -18,6 +17,10 @@ */ public class SequenceSpecification extends AbstractSpecification { + public static SequenceSpecification.Builder regel() { + return new Builder<>(); + } + public static SequenceSpecification.Builder regel(String id, String beskrivelse) { return new Builder<>(id, beskrivelse); } @@ -40,9 +43,32 @@ public Builder neste(Specification specification) { return this; } + public Builder hvis(Specification test, Specification spec) { + this.sekvens.add(new ComputationalIfSpecification<>(test, spec)); + return this; + } + + public Builder hvisEllers(Specification test, Specification hvis, Specification eller) { + this.sekvens.add(new ComputationalIfSpecification<>(test, hvis, eller)); + return this; + } + + public Builder forAlle(String argName, List args, Specification spec) { + this.sekvens.add(new ForeachSpecification<>(spec, args, argName)); + return this; + } + public SequenceSpecification siste(Specification specification) { this.sekvens.add(specification); - return new SequenceSpecification<>(id, beskrivelse, sekvens); + return build(); + } + + public SequenceSpecification build() { + var spec = new SequenceSpecification(sekvens); + if (id != null) { + spec.medID(id).medBeskrivelse(beskrivelse); + } + return spec; } } @@ -50,20 +76,23 @@ public SequenceSpecification siste(Specification specification) { private final List> specs = new ArrayList<>(); - public SequenceSpecification(final String id, final String beskrivelse, final List> specs) { + public SequenceSpecification(final List> specs) { super(); - Objects.requireNonNull(id, "ID"); - Objects.requireNonNull(beskrivelse, "beskrivelse"); Objects.requireNonNull(specs, "specs"); if (specs.size() < 2) { throw new IllegalArgumentException("Utviklerfeil: SequenceSpecification må inneholde minst to Specifications."); } this.specs.addAll(specs); + } + + public SequenceSpecification(final String id, final String beskrivelse, final List> specs) { + this(specs); + Objects.requireNonNull(id, "ID"); + Objects.requireNonNull(beskrivelse, "beskrivelse"); medID(id); medBeskrivelse(beskrivelse); } - @SafeVarargs public SequenceSpecification(final String id, final String beskrivelse, final Specification... specs) { this(id, beskrivelse, Arrays.asList(specs)); @@ -77,12 +106,13 @@ private Specification first() { return specs.get(0); } + private Specification last() { + return specs.get(specs.size()-1); + } + @Override public String beskrivelse() { - if (super.beskrivelse().isEmpty()) { - return "(" + first().beskrivelse() + " FULGT AV...)"; - } - return super.beskrivelse(); + return beskrivelseIkkeTom().orElse("(SEKVENS ...)"); } @Override @@ -111,11 +141,7 @@ private SequenceEvaluation doEvaluate(final T t, ServiceArgument serviceArgument var specSize = specs.size(); Evaluation[] evaluations = new Evaluation[specSize]; for (int ix = 0; ix < specSize; ix++) { - var result = serviceArgument != null ? specs.get(ix).evaluate(t, serviceArgument) : specs.get(ix).evaluate(t); - evaluations[ix] = result; - if (ix < specSize - 1 && !Resultat.JA.equals(evaluations[ix].result())) { - throw new IllegalArgumentException("Utviklerfeil: SequenceSpecification evaluering annet enn JA før siste spec."); - } + evaluations[ix] = serviceArgument != null ? specs.get(ix).evaluate(t, serviceArgument) : specs.get(ix).evaluate(t); } return new SequenceEvaluation(identifikator(), beskrivelse(), evaluations); @@ -123,10 +149,7 @@ private SequenceEvaluation doEvaluate(final T t, ServiceArgument serviceArgument @Override public String identifikator() { - if (super.identifikator().isEmpty()) { - return "(" + first().identifikator() + " FULGT AV...)"; - } - return super.identifikator(); + return identifikatorIkkeTom().orElseGet(() -> "(SEKVENS " + first().identifikator() + " ... " + last().identifikator() + ")"); } @Override diff --git a/src/test/java/no/nav/fpsak/nare/specification/modrekvote/ModrekvoteConditionalOrSpecificationTest.java b/src/test/java/no/nav/fpsak/nare/specification/modrekvote/ModrekvoteConditionalOrSpecificationTest.java index 789a572..5da7c23 100644 --- a/src/test/java/no/nav/fpsak/nare/specification/modrekvote/ModrekvoteConditionalOrSpecificationTest.java +++ b/src/test/java/no/nav/fpsak/nare/specification/modrekvote/ModrekvoteConditionalOrSpecificationTest.java @@ -29,9 +29,8 @@ public class ModrekvoteConditionalOrSpecificationTest { RuleService modrekvote = new ModrekvoteConditional(); Evaluation evaluation = modrekvote.evaluer(soknad); - - @SuppressWarnings("deprecation") - String asJson = EvaluationSerializer.asLegacyJsonTree(evaluation); + + String asJson = EvaluationSerializer.asJson(evaluation); EvaluationSummary evaluationSummary = new EvaluationSummary(evaluation); Collection leafReasons = evaluationSummary.leafEvaluations(Resultat.NEI, Resultat.IKKE_VURDERT).stream() .map(Evaluation::getOutcome) @@ -49,7 +48,7 @@ public class ModrekvoteConditionalOrSpecificationTest { System.out.println(asJson); System.out.println("\n\n\n\n\n-------------\n\n\n"); - System.out.println(EvaluationSerializer.asJson(evaluation)); + System.out.println(EvaluationSerializer.asJson(modrekvote.getSpecification())); } } diff --git a/src/test/java/no/nav/fpsak/nare/specification/modrekvote/regler/CountingpecificationTest.java b/src/test/java/no/nav/fpsak/nare/specification/modrekvote/regler/CountingpecificationTest.java index 7392aad..763fe8c 100644 --- a/src/test/java/no/nav/fpsak/nare/specification/modrekvote/regler/CountingpecificationTest.java +++ b/src/test/java/no/nav/fpsak/nare/specification/modrekvote/regler/CountingpecificationTest.java @@ -3,55 +3,54 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; -import java.util.Objects; import java.util.Optional; import org.junit.Test; import no.nav.fpsak.nare.RuleService; +import no.nav.fpsak.nare.Ruleset; import no.nav.fpsak.nare.ServiceArgument; import no.nav.fpsak.nare.evaluation.Evaluation; import no.nav.fpsak.nare.evaluation.Resultat; import no.nav.fpsak.nare.evaluation.RuleOutcome; +import no.nav.fpsak.nare.evaluation.node.SingleEvaluation; +import no.nav.fpsak.nare.evaluation.summary.EvaluationSerializer; import no.nav.fpsak.nare.evaluation.summary.EvaluationSummary; -import no.nav.fpsak.nare.specification.ConditionalOrSpecification; -import no.nav.fpsak.nare.specification.ForeachSpecification; import no.nav.fpsak.nare.specification.LeafSpecification; -import no.nav.fpsak.nare.specification.SequenceSpecification; import no.nav.fpsak.nare.specification.Specification; public class CountingpecificationTest { @Test - public void skal_evaluere_regel_med_hvis_branch() { + public void skal_evaluere_regel_med_ellers_branch() { var singleSpecification = new SingleRule(); var evaluation = singleSpecification.evaluer(new MellomregnInt(1)); assertThat(evaluation.result()).isEqualTo(Resultat.JA); - + System.out.println(EvaluationSerializer.asJson(evaluation)); + System.out.println(EvaluationSerializer.asJson(singleSpecification.getSpecification())); var evaluationSummary = new EvaluationSummary(evaluation); var outcome = hentOutcome(evaluationSummary); assertThat(outcome).contains(12); } @Test - public void skal_evaluere_regel_med_ellers_branch() { + public void skal_evaluere_regel_med_hvis_branch() { var singleSpecification = new SingleRule(); var evaluation = singleSpecification.evaluer(new MellomregnInt(2)); assertThat(evaluation.result()).isEqualTo(Resultat.JA); - + //System.out.println(EvaluationSerializer.asJson(evaluation)); + //System.out.println(EvaluationSerializer.asJson(singleSpecification.getSpecification())); var evaluationSummary = new EvaluationSummary(evaluation); var outcome = hentOutcome(evaluationSummary); assertThat(outcome).contains(24); } private Optional hentOutcome(EvaluationSummary evaluationSummary) { - return evaluationSummary.leafEvaluations(Resultat.JA).stream() - .map(Evaluation::getOutcome) - .filter(Objects::nonNull) - .map(o -> o instanceof RuleOutcome p ? p.calculated() : null) - .map(o -> o instanceof Integer i ? i : null) + return evaluationSummary.allOutcomes().stream() + .map(o -> o instanceof RuleOutcome p ? p.calculated() : 0) + .map(o -> o instanceof Integer i ? i : 0) .findFirst(); } @@ -79,22 +78,22 @@ public Evaluation evaluer(MellomregnInt data) { @SuppressWarnings("unchecked") @Override public Specification getSpecification() { - return new SequenceSpecification<>("Seq", "Seq", - new AddOneLeaf(), - new ForeachSpecification<>("Sum", "Sum for Bokstav", new AddOneLeaf(), List.of("A", "B", "C"), "bokstav"), - ConditionalOrSpecification.regel("Hvis", "Så") - .hvis(new EvenLeaf(), new MultiplyByTwoLeaf()) - .ellers(new AddOneLeaf()), - new MultiplyByTwoLeaf(), - new SinkLeaf() - ); + var rs = new Ruleset(); + var regel = rs.sekvensRegel() + .neste(new AddOneLeaf()) + .forAlle("bokstav", List.of("A", "B", "C"), new AddOneLeaf()) + //.hvisEllers(new EvenLeaf(), new MultiplyByTwoLeaf(), new AddOneLeaf()) + .neste(rs.hvisRegel().hvis(new EvenLeaf(), new MultiplyByTwoLeaf()).ellers(new AddOneLeaf())) + .neste(new MultiplyByTwoLeaf()) + .siste(new SinkLeaf()); + return regel;//rs.regel("Seq", "Seq", regel); } } private static class AddOneLeaf extends LeafSpecification { private AddOneLeaf() { - super("AddOne"); + super("AddOne", "AddOne"); } @Override @@ -114,7 +113,7 @@ public Evaluation evaluate(MellomregnInt input, ServiceArgument args) { private static class MultiplyByTwoLeaf extends LeafSpecification { private MultiplyByTwoLeaf() { - super("TimesTwo"); + super("TimesTwo", "TimesTwo"); } @Override @@ -126,19 +125,21 @@ public Evaluation evaluate(MellomregnInt input) { private static class EvenLeaf extends LeafSpecification { private EvenLeaf() { - super("CarryOn"); + super("CarryOn", "IsEvenNumber"); } + @Override public Evaluation evaluate(MellomregnInt input) { - return input.carryon % 2 == 0 ? ja() : nei(); + var desc = beskrivelse() + ": " + input.carryon; + return new SingleEvaluation(input.carryon % 2 == 0 ? Resultat.JA : Resultat.NEI, identifikator(), desc, null); } } private static class SinkLeaf extends LeafSpecification { private SinkLeaf() { - super("Collect"); + super("Collect", "Collect"); } @Override