Skip to content

Commit

Permalink
[incubator-kie-issues#1541] Add “evaluationHitIds” to the response of…
Browse files Browse the repository at this point in the history
… jitexecutor-dmn’s “/dmnresult” route (apache#2120)

* exp_dmn_result_mapped_to_input

* [exp_dmn_result_mapped_to_input] WIP

* [incubator-kie-issues#1541] WIP

* [incubator-kie-issues#1541] Implemented retrieval and population of "evaluationHitIds" field

* [incubator-kie-issues#1541] Fixed tests. Minor refactoring

* [incubator-kie-issues#1541] Fix formatting

* [incubator-kie-issues#1541] Fix as per suggestion

* [incubator-kie-issues#1541] Fix as per suggestion

* kie-issues#1541: Extend test coverage

- test DMN model where a decision table is nested under conditional expression
- test DMN model where collection data type is used as input and output

* Collections -> MultipleHitRules

* [incubator-kie-issues#1541] Remove duplicated test

* [incubator-kie-issues#1541] Fix imports

---------

Co-authored-by: Gabriele-Cardosi <[email protected]>
Co-authored-by: Jozef Marko <[email protected]>
  • Loading branch information
3 people authored and rgdoliveira committed Oct 24, 2024
1 parent 3bce465 commit eca7df5
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.kie.api.io.Resource;
import org.kie.dmn.api.core.DMNContext;
Expand All @@ -36,6 +38,7 @@
import org.kie.internal.io.ResourceFactory;
import org.kie.kogito.jitexecutor.common.requests.MultipleResourcesPayload;
import org.kie.kogito.jitexecutor.common.requests.ResourceWithURI;
import org.kie.kogito.jitexecutor.dmn.responses.JITDMNResult;
import org.kie.kogito.jitexecutor.dmn.utils.ResolveByKey;

public class DMNEvaluator {
Expand All @@ -47,6 +50,7 @@ public static DMNEvaluator fromXML(String modelXML) {
Resource modelResource = ResourceFactory.newReaderResource(new StringReader(modelXML), "UTF-8");
DMNRuntime dmnRuntime = DMNRuntimeBuilder.fromDefaults().buildConfiguration()
.fromResources(Collections.singletonList(modelResource)).getOrElseThrow(RuntimeException::new);
dmnRuntime.addListener(new JITDMNListener());
DMNModel dmnModel = dmnRuntime.getModels().get(0);
return new DMNEvaluator(dmnModel, dmnRuntime);
}
Expand All @@ -73,9 +77,16 @@ public Collection<DMNModel> getAllDMNModels() {
return dmnRuntime.getModels();
}

public DMNResult evaluate(Map<String, Object> context) {
DMNContext dmnContext = new DynamicDMNContextBuilder(dmnRuntime.newContext(), dmnModel).populateContextWith(context);
return dmnRuntime.evaluateAll(dmnModel, dmnContext);
public JITDMNResult evaluate(Map<String, Object> context) {
DMNContext dmnContext =
new DynamicDMNContextBuilder(dmnRuntime.newContext(), dmnModel).populateContextWith(context);
DMNResult dmnResult = dmnRuntime.evaluateAll(dmnModel, dmnContext);
Optional<List<String>> evaluationHitIds = dmnRuntime.getListeners().stream()
.filter(JITDMNListener.class::isInstance)
.findFirst()
.map(JITDMNListener.class::cast)
.map(JITDMNListener::getEvaluationHitIds);
return new JITDMNResult(getNamespace(), getName(), dmnResult, evaluationHitIds.orElse(Collections.emptyList()));
}

public static DMNEvaluator fromMultiple(MultipleResourcesPayload payload) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.kie.kogito.jitexecutor.dmn;

import java.util.ArrayList;
import java.util.List;

import org.kie.dmn.api.core.event.AfterConditionalEvaluationEvent;
import org.kie.dmn.api.core.event.AfterEvaluateAllEvent;
import org.kie.dmn.api.core.event.AfterEvaluateBKMEvent;
import org.kie.dmn.api.core.event.AfterEvaluateContextEntryEvent;
import org.kie.dmn.api.core.event.AfterEvaluateDecisionEvent;
import org.kie.dmn.api.core.event.AfterEvaluateDecisionServiceEvent;
import org.kie.dmn.api.core.event.AfterEvaluateDecisionTableEvent;
import org.kie.dmn.api.core.event.AfterInvokeBKMEvent;
import org.kie.dmn.api.core.event.DMNEvent;
import org.kie.dmn.api.core.event.DMNRuntimeEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JITDMNListener implements DMNRuntimeEventListener {

private final List<String> evaluationHitIds = new ArrayList<>();

private static final Logger LOGGER = LoggerFactory.getLogger(JITDMNListener.class);

@Override
public void afterEvaluateDecisionTable(AfterEvaluateDecisionTableEvent event) {
logEvent(event);
evaluationHitIds.addAll(event.getSelectedIds());
}

@Override
public void afterEvaluateDecision(AfterEvaluateDecisionEvent event) {
logEvent(event);
}

@Override
public void afterEvaluateBKM(AfterEvaluateBKMEvent event) {
logEvent(event);
}

@Override
public void afterEvaluateContextEntry(AfterEvaluateContextEntryEvent event) {
logEvent(event);
}

@Override
public void afterEvaluateDecisionService(AfterEvaluateDecisionServiceEvent event) {
logEvent(event);
}

@Override
public void afterInvokeBKM(AfterInvokeBKMEvent event) {
logEvent(event);
}

@Override
public void afterEvaluateAll(AfterEvaluateAllEvent event) {
logEvent(event);
}

@Override
public void afterConditionalEvaluation(AfterConditionalEvaluationEvent event) {
logEvent(event);
evaluationHitIds.add(event.getExecutedId());
}

public List<String> getEvaluationHitIds() {
return evaluationHitIds;
}

private void logEvent(DMNEvent toLog) {
LOGGER.info("{} event {}", toLog.getClass().getSimpleName(), toLog);
}

private void logEvent(AfterConditionalEvaluationEvent toLog) {
LOGGER.info("{} event {}", toLog.getClass().getSimpleName(), toLog);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ public JITDMNServiceImpl(int explainabilityLimeSampleSize, int explainabilityLim
@Override
public JITDMNResult evaluateModel(String modelXML, Map<String, Object> context) {
DMNEvaluator dmnEvaluator = DMNEvaluator.fromXML(modelXML);
DMNResult dmnResult = dmnEvaluator.evaluate(context);
return new JITDMNResult(dmnEvaluator.getNamespace(), dmnEvaluator.getName(), dmnResult);
return dmnEvaluator.evaluate(context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -49,16 +50,23 @@ public class JITDMNResult implements Serializable,

private Map<String, JITDMNDecisionResult> decisionResults = new HashMap<>();

private List<String> evaluationHitIds;

public JITDMNResult() {
// Intentionally blank.
}

public JITDMNResult(String namespace, String modelName, org.kie.dmn.api.core.DMNResult dmnResult) {
this(namespace, modelName, dmnResult, Collections.emptyList());
}

public JITDMNResult(String namespace, String modelName, org.kie.dmn.api.core.DMNResult dmnResult, List<String> evaluationHitIds) {
this.namespace = namespace;
this.modelName = modelName;
this.setDmnContext(dmnResult.getContext().getAll());
this.setMessages(dmnResult.getMessages());
this.setDecisionResults(dmnResult.getDecisionResults());
this.evaluationHitIds = evaluationHitIds;
}

public String getNamespace() {
Expand Down Expand Up @@ -102,6 +110,14 @@ public void setDecisionResults(List<? extends DMNDecisionResult> decisionResults
}
}

public List<String> getEvaluationHitIds() {
return evaluationHitIds;
}

public void setEvaluationHitIds(List<String> evaluationHitIds) {
this.evaluationHitIds = evaluationHitIds;
}

@JsonIgnore
@Override
public DMNContext getContext() {
Expand Down Expand Up @@ -151,6 +167,7 @@ public String toString() {
.append(", dmnContext=").append(dmnContext)
.append(", messages=").append(messages)
.append(", decisionResults=").append(decisionResults)
.append(", evaluationHitIds=").append(evaluationHitIds)
.append("]").toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
package org.kie.kogito.jitexecutor.dmn;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Assertions;
Expand All @@ -43,7 +46,7 @@ public static void setup() throws IOException {
}

@Test
public void testModelEvaluation() {
void testModelEvaluation() {
Map<String, Object> context = new HashMap<>();
context.put("FICO Score", 800);
context.put("DTI Ratio", .1);
Expand All @@ -57,7 +60,141 @@ public void testModelEvaluation() {
}

@Test
public void testExplainability() throws IOException {
void testDecisionTableModelEvaluation() throws IOException {
String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_x/LoanEligibility.dmn");
Map<String, Object> client = new HashMap<>();
client.put("age", 43);
client.put("salary", 1950);
client.put("existing payments", 100);

Map<String, Object> loan = new HashMap<>();
loan.put("duration", 15);
loan.put("installment", 180);
Map<String, Object> context = new HashMap<>();

context.put("Client", client);
context.put("Loan", loan);
context.put("SupremeDirector", "No");
context.put("Bribe", 10);
JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context);

Assertions.assertEquals("LoanEligibility", dmnResult.getModelName());
Assertions.assertEquals("https://github.com/kiegroup/kogito-examples/dmn-quarkus-listener-example", dmnResult.getNamespace());
Assertions.assertTrue(dmnResult.getMessages().isEmpty());
Assertions.assertEquals("Yes", dmnResult.getDecisionResultByName("Eligibility").getResult());
}

@Test
void testEvaluationHitIds() throws IOException {
final String thenElementId = "_6481FF12-61B5-451C-B775-4143D9B6CD6B";
final String elseElementId = "_2CD02CB2-6B56-45C4-B461-405E89D45633";
final String ruleId0 = "_1578BD9E-2BF9-4BFC-8956-1A736959C937";
final String ruleId1 = "_31CD7AA3-A806-4E7E-B512-821F82043620";
final String ruleId3 = "_2545E1A8-93D3-4C8A-A0ED-8AD8B10A58F9";
final String ruleId4 = "_510A50DA-D5A4-4F06-B0BE-7F8F2AA83740";
String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_5/RiskScore_Simple.dmn");
Map<String, Object> context = new HashMap<>();
context.put("Credit Score", "Poor");
context.put("DTI", 33);
JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context);

Assertions.assertEquals("DMN_A77074C1-21FE-4F7E-9753-F84661569AFC", dmnResult.getModelName());
Assertions.assertTrue(dmnResult.getMessages().isEmpty());
Assertions.assertEquals(BigDecimal.valueOf(50), dmnResult.getDecisionResultByName("Risk Score").getResult());
List<String> evaluationHitIds = dmnResult.getEvaluationHitIds();
Assertions.assertNotNull(evaluationHitIds);
Assertions.assertEquals(3, evaluationHitIds.size());
Assertions.assertTrue(evaluationHitIds.contains(elseElementId));
Assertions.assertTrue(evaluationHitIds.contains(ruleId0));
Assertions.assertTrue(evaluationHitIds.contains(ruleId3));

context = new HashMap<>();
context.put("Credit Score", "Excellent");
context.put("DTI", 10);
dmnResult = jitdmnService.evaluateModel(decisionTableModel, context);

Assertions.assertTrue(dmnResult.getMessages().isEmpty());
Assertions.assertEquals(BigDecimal.valueOf(20), dmnResult.getDecisionResultByName("Risk Score").getResult());
evaluationHitIds = dmnResult.getEvaluationHitIds();
Assertions.assertNotNull(evaluationHitIds);
Assertions.assertEquals(3, evaluationHitIds.size());
Assertions.assertTrue(evaluationHitIds.contains(thenElementId));
Assertions.assertTrue(evaluationHitIds.contains(ruleId1));
Assertions.assertTrue(evaluationHitIds.contains(ruleId4));
}

@Test
void testConditionalWithNestedDecisionTableFromRiskScoreEvaluation() throws IOException {
final String thenElementId = "_6481FF12-61B5-451C-B775-4143D9B6CD6B";
final String thenRuleId0 = "_D1753442-03F0-414B-94F8-6A86182DF6EB";
final String thenRuleId4 = "_E787BA51-E31D-449B-A432-50BE7466A15E";
final String elseElementId = "_2CD02CB2-6B56-45C4-B461-405E89D45633";
final String elseRuleId2 = "_945A5471-9F91-4751-9D96-74978F6FB12B";
final String elseRuleId5 = "_654BBFBC-9B84-4BD8-9D0B-13E8DD1B9F5D";
String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_5/RiskScore_Conditional.dmn");

Map<String, Object> context = new HashMap<>();
context.put("Credit Score", "Poor");
context.put("DTI", 33);
context.put("World Region", "Asia");
JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context);

Assertions.assertTrue(dmnResult.getMessages().isEmpty());
Assertions.assertEquals(BigDecimal.valueOf(50), dmnResult.getDecisionResultByName("Risk Score").getResult());
List<String> evaluationHitIds = dmnResult.getEvaluationHitIds();
Assertions.assertNotNull(evaluationHitIds);
Assertions.assertEquals(3, evaluationHitIds.size());
Assertions.assertTrue(evaluationHitIds.contains(thenElementId));
Assertions.assertTrue(evaluationHitIds.contains(thenRuleId0));
Assertions.assertTrue(evaluationHitIds.contains(thenRuleId4));

context = new HashMap<>();
context.put("Credit Score", "Excellent");
context.put("DTI", 10);
context.put("World Region", "Europe");
dmnResult = jitdmnService.evaluateModel(decisionTableModel, context);

Assertions.assertTrue(dmnResult.getMessages().isEmpty());
Assertions.assertEquals(BigDecimal.valueOf(30), dmnResult.getDecisionResultByName("Risk Score").getResult());
evaluationHitIds = dmnResult.getEvaluationHitIds();
Assertions.assertNotNull(evaluationHitIds);
Assertions.assertEquals(3, evaluationHitIds.size());
Assertions.assertTrue(evaluationHitIds.contains(elseElementId));
Assertions.assertTrue(evaluationHitIds.contains(elseRuleId2));
Assertions.assertTrue(evaluationHitIds.contains(elseRuleId5));
}

@Test
void testMultipleHitRulesEvaluation() throws IOException {
final String rule0 = "_E5C380DA-AF7B-4401-9804-C58296EC09DD";
final String rule1 = "_DFD65E8B-5648-4BFD-840F-8C76B8DDBD1A";
final String rule2 = "_E80EE7F7-1C0C-4050-B560-F33611F16B05";
String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_5/MultipleHitRules.dmn");

final List<BigDecimal> numbers = new ArrayList<>();
numbers.add(BigDecimal.valueOf(10));
numbers.add(BigDecimal.valueOf(2));
numbers.add(BigDecimal.valueOf(1));
final Map<String, Object> context = new HashMap<>();
context.put("Numbers", numbers);
final JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context);

final List<BigDecimal> expectedStatistcs = new ArrayList<>();
expectedStatistcs.add(BigDecimal.valueOf(6));
expectedStatistcs.add(BigDecimal.valueOf(3));
expectedStatistcs.add(BigDecimal.valueOf(1));
Assertions.assertTrue(dmnResult.getMessages().isEmpty());
Assertions.assertEquals(expectedStatistcs, dmnResult.getDecisionResultByName("Statistics").getResult());
final List<String> evaluationHitIds = dmnResult.getEvaluationHitIds();
Assertions.assertNotNull(evaluationHitIds);
Assertions.assertEquals(6, evaluationHitIds.size());
Assertions.assertEquals(3, evaluationHitIds.stream().filter(rule0::equals).count());
Assertions.assertEquals(2, evaluationHitIds.stream().filter(rule1::equals).count());
Assertions.assertEquals(1, evaluationHitIds.stream().filter(rule2::equals).count());
}

@Test
void testExplainability() throws IOException {
String allTypesModel = getModelFromIoUtils("valid_models/DMNv1_x/allTypes.dmn");

Map<String, Object> context = new HashMap<>();
Expand Down
Loading

0 comments on commit eca7df5

Please sign in to comment.