From 0d4d963860b8f98284fb2210732c1a75aeb31d68 Mon Sep 17 00:00:00 2001 From: Octavian Patrascoiu Date: Thu, 15 Feb 2024 15:39:38 +0000 Subject: [PATCH] [#634] Add signavio transformer to correct paths when a child decision is used as output --- ...OutCorrectPathsInDecisionsTransformer.java | 130 +++++++ ...orrectPathsInDecisionsTransformerTest.java | 97 +++++ ...t-credit-decision-with-incorrect-paths.dmn | 334 ++++++++++++++++++ 3 files changed, 561 insertions(+) create mode 100644 dmn-signavio/src/main/java/com/gs/dmn/signavio/transformation/InOutCorrectPathsInDecisionsTransformer.java create mode 100644 dmn-signavio/src/test/java/com/gs/dmn/signavio/transformation/InOutCorrectPathsInDecisionsTransformerTest.java create mode 100644 dmn-test-cases/signavio/dmn/complex/example-in-out-credit-decision-with-incorrect-paths.dmn diff --git a/dmn-signavio/src/main/java/com/gs/dmn/signavio/transformation/InOutCorrectPathsInDecisionsTransformer.java b/dmn-signavio/src/main/java/com/gs/dmn/signavio/transformation/InOutCorrectPathsInDecisionsTransformer.java new file mode 100644 index 000000000..e1a51b0d8 --- /dev/null +++ b/dmn-signavio/src/main/java/com/gs/dmn/signavio/transformation/InOutCorrectPathsInDecisionsTransformer.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 Goldman Sachs. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.gs.dmn.signavio.transformation; + +import com.gs.dmn.DMNModelRepository; +import com.gs.dmn.ast.*; +import com.gs.dmn.log.BuildLogger; +import com.gs.dmn.log.Slf4jBuildLogger; +import com.gs.dmn.runtime.DMNRuntimeException; +import com.gs.dmn.runtime.Pair; +import com.gs.dmn.signavio.testlab.TestLab; +import com.gs.dmn.signavio.transformation.config.Correction; +import com.gs.dmn.signavio.transformation.config.DecisionTableCorrection; +import com.gs.dmn.transformation.SimpleDMNTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +public class InOutCorrectPathsInDecisionsTransformer extends SimpleDMNTransformer { + protected static final Logger LOGGER = LoggerFactory.getLogger(InOutCorrectPathsInDecisionsTransformer.class); + + protected final BuildLogger logger; + protected boolean transformRepository = true; + + public InOutCorrectPathsInDecisionsTransformer() { + this(new Slf4jBuildLogger(LOGGER)); + } + + protected InOutCorrectPathsInDecisionsTransformer(BuildLogger logger) { + this.logger = logger; + } + + @Override + public DMNModelRepository transform(DMNModelRepository repository) { + if (isEmpty(repository)) { + logger.warn("Repository is empty; transformer will not run"); + return repository; + } + + correctDecisions(repository); + + this.transformRepository = false; + return repository; + } + + @Override + public Pair> transform(DMNModelRepository repository, List testCasesList) { + if (isEmpty(repository, testCasesList)) { + logger.warn("DMN repository or test list is empty; transformer will not run"); + return new Pair<>(repository, testCasesList); + } + + // Transform model + if (this.transformRepository) { + transform(repository); + } + + return new Pair<>(repository, testCasesList); + } + + private void correctDecisions(DMNModelRepository repository) { + for (TDefinitions definitions : repository.getAllDefinitions()) { + for (TDecision decision : repository.findDecisions(definitions)) { + TExpression expression = repository.expression(decision); + if (expression instanceof TDecisionTable) { + List childNames = directChildDecisionNames(decision, repository); + correctDecision(decision, childNames, (TDecisionTable) expression); + } + } + } + } + + private List directChildDecisionNames(TDecision decision, DMNModelRepository repository) { + List result = new ArrayList<>(); + for (TInformationRequirement informationRequirement : decision.getInformationRequirement()) { + TDMNElementReference requiredDecision = informationRequirement.getRequiredDecision(); + if (requiredDecision != null) { + String href = requiredDecision.getHref(); + TDecision child = repository.findDecisionByRef(decision, href); + result.add(child.getName()); + } + } + + return result; + } + + private void correctDecision(TDecision decision, List childNames, TDecisionTable dte) { + if (childNames.isEmpty()) { + return; + } + + for (String childName : childNames) { + String oldValue = String.format("%s.%s", childName, childName); + String newValue = childName; + // Correct rules + correctRules(decision, oldValue, newValue, dte); + } + } + + private void correctRules(TDecision decision, String oldValue, String newValue, TDecisionTable dte) { + List ruleList = dte.getRule(); + for (int i=0; i> res = transformer.transform(repository, null); + assertEquals(repository, res.getLeft()); + } + + @Test + public void testTransformationWhenEmptyConfig() {; + DMNModelRepository repository = new DMNModelRepository(); + transformer.transform(repository); + Pair> res = transformer.transform(repository, null); + assertEquals(repository, res.getLeft()); + } + + private DMNModelRepository executeTransformation(URI dmnFileURI) throws Exception { + File dmnFile = new File(dmnFileURI); + DMNModelRepository repository = new SignavioDMNModelRepository(this.dmnSerializer.readModel(dmnFile)); + + return transformer.transform(repository); + } +} diff --git a/dmn-test-cases/signavio/dmn/complex/example-in-out-credit-decision-with-incorrect-paths.dmn b/dmn-test-cases/signavio/dmn/complex/example-in-out-credit-decision-with-incorrect-paths.dmn new file mode 100644 index 000000000..09a044745 --- /dev/null +++ b/dmn-test-cases/signavio/dmn/complex/example-in-out-credit-decision-with-incorrect-paths.dmn @@ -0,0 +1,334 @@ + + + + + + feel:string + + + feel:number + + + feel:number + + + sig:creditIssueType + + + + feel:number + + + feel:number + + + feel:number + + + feel:number + + + feel:string + + "Card rejection","Late payment","Default on obligations","Bankruptcy" + + + + feel:number + + [0.0..100.0] + + + + + feel:string + + + feel:number + + + feel:number + + + + feel:number + + + feel:string + + "Accept","Recommend further assessment","Reject" + + + + feel:number + + + feel:number + + + + + + + + + + + + + + + + + + + + + + applicant.priorIssues.priorIssues + + + + + string(-) + + not(notContainsAny(?, ["Card rejection","Late payment"])) + + + -10 + + + + string(-) + + not(notContainsAny(?, ["Default on obligations"])) + + + -30 + + + + string(-) + + not(notContainsAny(?, ["Bankruptcy"])) + + + -100 + + + + string(-) + + notContainsAny(?, ["Card rejection","Late payment","Default on obligations","Bankruptcy"]) + + + 50 + + + + string(-) + + - + + + (count(applicant.priorIssues)*(-5)) + + + + + + + + + + + + + compareAgainstLendingThreshold + + + + + string(-) + + < -0.1 + + + "Reject" + + + + string(-) + + [-0.1..0.1] + + + "Recommend further assessment" + + + + string(-) + + > 0.1 + + + "Accept" + + + + + + + + + + + + + + + + + zip(["Decision", "Assessment", "Issue"], [[makeCreditDecision], [compareAgainstLendingThreshold], [assessIssueRisk]]) + + + + + + + + + + + + (priorIssue_iterator*(max([0, (100-currentRiskAppetite)])*0.01)) + + + + + + + + + + + applicant.age + + + + + string(-) + + < 18 + + + -10 + + + + string(-) + + [18..25] + + + 40 + + + + string(-) + + > 25 + + + 60 + + + + + + + + + + + + + + + + + + + lendingThreshold + + + + + string("Raw issue score is ") + string(assessIssueRisk) + string(", Age-weighted score is ") + string(assessApplicantAge) + string(", Acceptance threshold is ") + string(lendingThreshold) + + not(null) + + + ((assessIssueRisk+assessApplicantAge)-lendingThreshold) + + + + string("Error: threshold undefined") + + null + + + lendingThreshold.lendingThreshold + + + + + + + + processPriorIssues + id-78d6a4b25e15dc5d22fe0cce65554804 + SUM + id-0f2f9823e96f0599d2739fda4c5b3c79 + + + + + + + + + + + + + + + + + + + + lendingThreshold + + + + + + not(null) + + + ((assessIssueRisk+assessApplicantAge)-lendingThreshold) + + + + + null + + + assessIssueRisk.assessIssueRisk + + + + +