This is an automated email from the ASF dual-hosted git repository. jkevan pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/master by this push: new aa4dde9d6 UNOMI-666: fix personalization that use score sorting and interests (new interests data structure) (#503) aa4dde9d6 is described below commit aa4dde9d6115e2dd708cabd69461d3ae3dc09e3e Author: kevan Jahanshahi <ke...@jahia.com> AuthorDate: Mon Sep 19 09:10:52 2022 +0200 UNOMI-666: fix personalization that use score sorting and interests (new interests data structure) (#503) --- .../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;