You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by jk...@apache.org on 2022/09/16 16:13:20 UTC

[unomi] 01/01: UNOMI-666: fix personalization that use score sorting and interests (new interests data structure)

This is an automated email from the ASF dual-hosted git repository.

jkevan pushed a commit to branch fixPersoScoreInterestsSort
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 418b498e30980a165818a658dd8c483c684940b8
Author: Kevan <ke...@jahia.com>
AuthorDate: Fri Sep 16 18:12:37 2022 +0200

    UNOMI-666: fix personalization that use score sorting and interests (new interests data structure)
---
 .../org/apache/unomi/itests/ContextServletIT.java  |  69 ++++++++
 .../resources/personalization-score-interests.json | 178 +++++++++++++++++++++
 .../unomi/rest/endpoints/ContextJsonEndpoint.java  |   2 -
 .../sorts/ScorePersonalizationStrategy.java        |  15 +-
 4 files changed, 257 insertions(+), 7 deletions(-)

diff --git a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
index 6559c4660..cfa9275cd 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
@@ -550,6 +550,75 @@ public class ContextServletIT extends BaseIT {
         assertEquals("Invalid response code", 200, response.getStatusCode());
     }
 
+    @Test
+    public void testScorePersonalizationStrategy_Interests() throws Exception {
+        // Test request before adding interests to current profile.
+        HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-score-interests.json", null), ContentType.APPLICATION_JSON));
+        TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request);
+        ContextResponse contextResponse = response.getContextResponse();
+        List<String> variants = contextResponse.getPersonalizations().get("perso-by-interest");
+        assertEquals("Invalid response code", 200, response.getStatusCode());
+        assertEquals("Perso should be empty, profile is empty", 0, variants.size());
+
+        // set profile for matching
+        Profile profile = profileService.load(TEST_PROFILE_ID);
+        profile.setProperty("age", 30);
+        profileService.save(profile);
+        keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID),
+                savedProfile -> (savedProfile != null && savedProfile.getProperty("age").equals(30)), DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+
+        // check results of the perso now
+        request = new HttpPost(getFullUrl(CONTEXT_URL));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-score-interests.json", null), ContentType.APPLICATION_JSON));
+        response = TestUtils.executeContextJSONRequest(request);
+        contextResponse = response.getContextResponse();
+        variants = contextResponse.getPersonalizations().get("perso-by-interest");
+        assertEquals("Invalid response code", 200, response.getStatusCode());
+        assertEquals("Perso should contains the good number of variants", 1, variants.size());
+        assertEquals("Variant is not the expected one", "matching-fishing-interests-custom-score-100-variant-expected-score-120", variants.get(0));
+
+        // modify profile to add interests
+        profile = profileService.load(TEST_PROFILE_ID);
+        List<Map<String, Object>> interests = new ArrayList<>();
+        Map<String, Object> interest1 = new HashMap<>();
+        interest1.put("key", "cars");
+        interest1.put("value", 50);
+        interests.add(interest1);
+        Map<String, Object> interest2 = new HashMap<>();
+        interest2.put("key", "football");
+        interest2.put("value", 40);
+        interests.add(interest2);
+        Map<String, Object> interest3 = new HashMap<>();
+        interest3.put("key", "tennis");
+        interest3.put("value", 30);
+        interests.add(interest3);
+        Map<String, Object> interest4 = new HashMap<>();
+        interest4.put("key", "fishing");
+        interest4.put("value", 20);
+        interests.add(interest4);
+        profile.setProperty("interests", interests);
+        profileService.save(profile);
+        keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID),
+                savedProfile -> (savedProfile != null && savedProfile.getProperty("interests") != null), DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+
+        // re test now that profiles has interests
+        request = new HttpPost(getFullUrl(CONTEXT_URL));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-score-interests.json", null), ContentType.APPLICATION_JSON));
+        response = TestUtils.executeContextJSONRequest(request);
+        contextResponse = response.getContextResponse();
+        variants = contextResponse.getPersonalizations().get("perso-by-interest");
+        assertEquals("Invalid response code", 200, response.getStatusCode());
+        assertEquals("Perso should contains the good number of variants", 7, variants.size());
+        assertEquals("Variant is not the expected one", "matching-fishing-interests-custom-score-100-variant-expected-score-120", variants.get(0));
+        assertEquals("Variant is not the expected one", "matching-football-cars-interests-variant-expected-score-91", variants.get(1));
+        assertEquals("Variant is not the expected one", "not-matching-football-cars-interests-variant-expected-score-90", variants.get(2));
+        assertEquals("Variant is not the expected one", "not-matching-tennis-fishing-interests-variant-expected-score-50", variants.get(3));
+        assertEquals("Variant is not the expected one", "matching-football-interests-variant-expected-score-51", variants.get(4));
+        assertEquals("Variant is not the expected one", "matching-tennis-interests-variant-expected-score-31", variants.get(5));
+        assertEquals("Variant is not the expected one", "not-matching-tennis-interests-custom-score-100-variant-expected-score-30", variants.get(6));
+    }
+
     @Test
     public void testPersonalizationWithControlGroup() throws Exception {
 
diff --git a/itests/src/test/resources/personalization-score-interests.json b/itests/src/test/resources/personalization-score-interests.json
new file mode 100644
index 000000000..0f19d62a5
--- /dev/null
+++ b/itests/src/test/resources/personalization-score-interests.json
@@ -0,0 +1,178 @@
+{
+  "source": {
+    "itemId": "CMSServer",
+    "itemType": "custom",
+    "scope": "acme",
+    "properties": {}
+  },
+  "requireSegments": true,
+  "requiredProfileProperties": [
+    "*"
+  ],
+  "requiredSessionProperties": [
+    "*"
+  ],
+  "personalizations": [
+    {
+      "id": "perso-by-interest",
+      "strategy": "score-sorted",
+      "strategyOptions": {
+        "threshold": 25
+      },
+      "contents": [
+        {
+          "id": "matching-football-cars-interests-variant-expected-score-91",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "25",
+                  "comparisonOperator": "greaterThan"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "interests": "football cars"
+          }
+        },
+        {
+          "id": "not-matching-football-cars-interests-variant-expected-score-90",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "35",
+                  "comparisonOperator": "greaterThan"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "interests": "football cars"
+          }
+        },
+        {
+          "id": "matching-football-interests-variant-expected-score-51",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "25",
+                  "comparisonOperator": "greaterThan"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "interests": "football"
+          }
+        },
+        {
+          "id": "less-threshold-fishing-interests-variant-expected-score-21",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "25",
+                  "comparisonOperator": "greaterThan"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "interests": "fishing"
+          }
+        },
+        {
+          "id": "matching-tennis-interests-variant-expected-score-31",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "25",
+                  "comparisonOperator": "greaterThan"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "interests": "tennis"
+          }
+        },
+        {
+          "id": "not-matching-tennis-fishing-interests-variant-expected-score-50",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "35",
+                  "comparisonOperator": "greaterThan"
+                }
+              }
+            }
+          ],
+          "properties": {
+            "interests": "tennis fishing"
+          }
+        },
+        {
+          "id": "not-matching-tennis-interests-custom-score-100-variant-expected-score-30",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "35",
+                  "comparisonOperator": "greaterThan"
+                }
+              },
+              "properties": {
+                "score": 100
+              }
+            }
+          ],
+          "properties": {
+            "interests": "tennis"
+          }
+        },
+        {
+          "id": "matching-fishing-interests-custom-score-100-variant-expected-score-120",
+          "filters": [
+            {
+              "condition": {
+                "type": "profilePropertyCondition",
+                "parameterValues": {
+                  "propertyName": "properties.age",
+                  "propertyValueInteger": "25",
+                  "comparisonOperator": "greaterThan"
+                }
+              },
+              "properties": {
+                "score": 100
+              }
+            }
+          ],
+          "properties": {
+            "interests": "fishing"
+          }
+        }
+      ]
+    }
+  ],
+  "sessionId": "dummy-session",
+  "profileId": "test-profile-id"
+}
\ No newline at end of file
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
index 9d2a3c661..6207845f8 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
@@ -157,12 +157,10 @@ public class ContextJsonEndpoint {
         // init ids
         String profileId = null;
         String scope = null;
-        List<Event> events = null;
         if (contextRequest != null) {
             scope = contextRequest.getSource() != null ? contextRequest.getSource().getScope() : scope;
             sessionId = contextRequest.getSessionId() != null ? contextRequest.getSessionId() : sessionId;
             profileId = contextRequest.getProfileId();
-            events = contextRequest.getEvents();
         }
 
         // build public context, profile + session creation/anonymous etc ...
diff --git a/services/src/main/java/org/apache/unomi/services/sorts/ScorePersonalizationStrategy.java b/services/src/main/java/org/apache/unomi/services/sorts/ScorePersonalizationStrategy.java
index 7e636d32f..82bcc165b 100644
--- a/services/src/main/java/org/apache/unomi/services/sorts/ScorePersonalizationStrategy.java
+++ b/services/src/main/java/org/apache/unomi/services/sorts/ScorePersonalizationStrategy.java
@@ -49,10 +49,15 @@ public class ScorePersonalizationStrategy implements PersonalizationStrategy {
 
             String interestList = (String) (personalizedContent.getProperties() != null ? personalizedContent.getProperties().get("interests") : null);
             if (interestList != null) {
-                Map<String,Integer> interestValues = (Map<String, Integer>) profile.getProperties().get("interests");
-                for (String interest : interestList.split(" ")) {
-                    if (interestValues != null && interestValues.get(interest) != null) {
-                        score += interestValues.get(interest);
+                List<Map<String, Object>> profileInterests = (List<Map<String, Object>>) profile.getProperties().get("interests");
+                if (profileInterests != null) {
+                    for (String interest : interestList.split(" ")) {
+                        for (Map<String, Object> profileInterest : profileInterests) {
+                            if (profileInterest.get("key").equals(interest)){
+                                score += ((Number) profileInterest.get("value")).intValue();
+                                break;
+                            }
+                        }
                     }
                 }
             }
@@ -74,7 +79,7 @@ public class ScorePersonalizationStrategy implements PersonalizationStrategy {
                     Condition condition = filter.getCondition();
                     if (condition != null && condition.getConditionTypeId() != null) {
                         if (profileService.matchCondition(condition, profile, session)) {
-                            if (filter.getProperties().get("score") != null) {
+                            if (filter.getProperties() != null && filter.getProperties().get("score") != null) {
                                 score += (int) filter.getProperties().get("score");
                             } else {
                                 score += 1;