Track and profile a person’s behavior in real life using Sitecore


We all know that profile and pattern cards are the state of the art when it comes to track visitors behavior on a website, if you don’t know – shame on you (you need to read this at once, Content profiling) πŸ˜‰

So why not apply this in real life? Why just track behaviors on a website, we could track a person’s behavior in real life πŸ™‚

Following fictional scenario:

Let’s say we have a “Chair sensor” placed on a chair, which will track a persons behavior. Here is what it will track:
How many times a person sits (Scale 1 to 10)
How much a person moves on the chair (Scale 1 to 10)
How long a person sits (Scale 1 to 10)

The person will also have a “Chair Tracker” mobile app, which will collect data (every day) from the sensor and send it to a website.

The app will then try to personalize content(in the app) depending on how the person is using the chair, the same goes for the website.

Is this doable? But of course, it’s Sitecore we are talking about πŸ™‚

Lets divide the work into following steps:
1. Create Profile and Pattern cards from the sensor data (Already covered in previous post)
2. Store the sensor data as custom facets in Sitecore xDB (Already covered in previous post)
3. Set profile keys from the custom facets in Sitecore xDB.
4. The piece de resistance – pattern matching
5. Putting it all together (will be covered in part 3)

Step 1 and 2 has already been done, please go to post Ants in the pants, Jojo or Chair potato? Track, Profile and Personalize IOT data in Sitecore and be thrilled πŸ™‚

Ok guys let’s proceed.

3. Set profile keys from the custom facets in Sitecore xDB

We need to get the sensor data (which is now stored in mongodb as custom facets on the contact) and somehow put it on the ProfileChairBehaviors.

We have to prepare the data from the custom facet and put in a KeyValuePair list (this will be the Profile Keys and values). To get the facet we will use SensorDataFacetRepository, which was explained in previous post – Ants in the pants, Jojo or Chair potato? Track, Profile and Personalize IOT data in Sitecore

private List<KeyValuePair<string, float>> PrepareProfileKeysFromSensorData(Contact contact)
{

  Assert.ArgumentNotNull(contact,"Contact is empty");

  ISensorDataFacet sensorDataFacet = _sensorDataFacetRepository.Get(contact);

  List<KeyValuePair<string, List<int>>> allValues = new List<KeyValuePair<string, List<int>>>();

  foreach (ChairDataRequestArg chairData in sensorDataFacet.SensorData.Select(sensorDataElement =>
	sensorDataElement.ChairData))
  {
	if (chairData == null)
	  continue;

	AddToAllValuesList(Constants.Profiles.ChairBehaviors.ProfileKeyNames.ScaleHowManyTimesPersonSits, chairData.ScaleHowManyTimesPersonSits, ref allValues);
	AddToAllValuesList(Constants.Profiles.ChairBehaviors.ProfileKeyNames.ScaleHowMuchPersonMovesOnChair, chairData.ScaleHowMuchPersonMovesOnChair, ref allValues);
	AddToAllValuesList(Constants.Profiles.ChairBehaviors.ProfileKeyNames.ScaleHowLongPersonSits, chairData.ScaleHowLongPersonSits, ref allValues);
  }

  List<KeyValuePair<string, float>> profileKeys = new List<KeyValuePair<string, float>>();
 
  foreach (var allValue in allValues)
  {
	double average = allValue.Value.Average();
	float profileKeyValue = Convert.ToSingle(average);
	profileKeys.Add(new KeyValuePair<string, float>(allValue.Key, profileKeyValue));
  }

  return profileKeys;
}

private void AddToAllValuesList(string key, int value, ref List<KeyValuePair<string, List<int>>> allValues)
{
  Assert.ArgumentNotNull(allValues, "allValues is empty");

  KeyValuePair<string, List<int>> foundKeyValue = allValues.FirstOrDefault(m => m.Key == key);

  if (!foundKeyValue.Equals(default(KeyValuePair<string, List<int>>)))
	foundKeyValue.Value.Add(value);

  if (foundKeyValue.Equals(default(KeyValuePair<string, List<int>>)))
  {
	foundKeyValue = new KeyValuePair<string, List<int>>(key, new List<int>() { value });
	allValues.Add(foundKeyValue);
  }
 
}

We also want to show an average of the accumulated sensor data, that’s why we set an average value on each profile value.

Ok we have prepared the profile keys and values, lets put it on the ProfileChairBehaviors. The parameter chairBehaviorsItemProfile is the item containing the ChairBehaviors profile (/sitecore/system/Marketing Control Panel/Profiles/ChairBehaviors).

public void SyncSensorDataWithProfile(Item chairBehaviorsItemProfile)
{
  Contact contact = Tracker.Current.Contact;
  
  //Prepare for the profile keys and values
  List<KeyValuePair<string, float>> profileKeys = PrepareProfileKeysFromSensorData(contact);

  //Remove and create profile on the Interaction
  if (Tracker.Current.Interaction.Profiles.ContainsProfile(Constants.Profiles.ChairBehaviors.Name))
	Tracker.Current.Interaction.Profiles.Remove(Constants.Profiles.ChairBehaviors.Name);

  List<ProfileData> listOfProfileData = new List<Sitecore.Analytics.Model.ProfileData>
  {
	new Sitecore.Analytics.Model.ProfileData(Constants.Profiles.ChairBehaviors.Name)
  };

  Tracker.Current.Interaction.Profiles.Initialize(listOfProfileData );

  //Set the profile keys and values
  Profile profile = Tracker.Current.Interaction.Profiles[Constants.Profiles.ChairBehaviors.Name];

  _visitorProfileRepository.SetProfileKeys(profile,profileKeys);

  //Remove and create profile on the Contact
  Tracker.Current.Contact.BehaviorProfiles.RemoveAll();

  _behaviorProfileRepository.Create(contact, chairBehaviorsItemProfile.ID, profile);
}

We only want fresh data on the profile, that is why we remove and then re create the profile on the Interaction. I’m not sure it’s needed on the Contact, but for safety’s sake let’s do it.
This was indeed a tough nut to crack, thank you Fire Breaks Ice for your post – Support Explicit User Types with Sitecore Personas

Here is the method for saving the profile keys on the profile, _visitorProfileRepository.SetProfileKeys(profile,profileKeys):

public void SetProfileKeys(Profile profile, List<KeyValuePair<string, float>> profileKeys)
{

  Assert.ArgumentNotNull(profile, nameof(profile));

  try
  {
	profile.Score(profileKeys);
	profile.UpdatePattern();
  }
  catch (Exception ex)
  {
	Sitecore.Diagnostics.Log.Error($"Could not set profile keys for profile {profile.ProfileName}", ex, this);
	throw;
  }

}

Here is the method for creating the profile on the Contact, _behaviorProfileRepository.Create(contact, chairBehaviorsItemProfile.ID, profile):

public IBehaviorProfileContext Create(Contact contact, ID profileItemId, Profile profile)
{
  try
  {
	BehaviorProfileConverterBase profileConverterBase = BehaviorProfileConverterBase.Create();

	MatchedBehaviorProfile matchedBehaviorProfile = profileConverterBase.Convert(profile);

	contact.BehaviorProfiles.Add(matchedBehaviorProfile.Id, matchedBehaviorProfile);

	return contact.BehaviorProfiles[profileItemId];
  }
  catch (Exception ex)
  {
	Sitecore.Diagnostics.Log.Error($"Could not create profile {profileItemId}", ex, this);
	throw;
  }
  
}

Great πŸ™‚ Let’s have a look at it in the Experienceprofile and see how the profile is presented on a visitor:

4. The piece de resistance – pattern matching

Next is the pattern matching. Did you notice the percentage values (to the right) in the screendump above? That is a pattern matching.

This is the heart of our solution, that is why we are using profile cards. Without pattern matching it will all fall down.

What we want, is to make a pattern matching “manually”(And then call it from an rest api). How can we do this? Well, the Sitecore Habitat team comes to our rescue πŸ™‚
In order for you to follow a visitors journey, the habitat gang has added a “Track view”. It shows visitors data, goals, events, profiles etc. So lets grab some code from the “Feature Demo” project and put it in use.

Here is the piece de resistance – pattern matching. The method GetPatternsWithGravityShare will do the pattern matching by returning a collection of pattern matches. The parameter chairBehaviorsItem is the item containing the ChairBehaviors profile (/sitecore/system/Marketing Control Panel/Profiles/ChairBehaviors).

public IEnumerable<PatternMatchModel> GetPatternsWithGravityShare(Item chairBehaviorsItem)
{
  Assert.ArgumentNotNull(chairBehaviorsItem, nameof(chairBehaviorsItem));

  Sitecore.Analytics.Data.Items.ProfileItem chairBehaviorsProfileItem = new Sitecore.Analytics.Data.Items.ProfileItem(chairBehaviorsItem);

  Pattern userPattern = chairBehaviorsProfileItem.PatternSpace.CreatePattern(Tracker.Current.Interaction.Profiles[chairBehaviorsProfileItem.Name]);

  Dictionary<PatternCardItem, double> patterns = Sitecore.Cintel.Reporting.Contact.ProfilePatternMatch.Processors.PopulateProfilePatternMatchesWithXdbData.GetPatternsWithGravityShare(chairBehaviorsProfileItem, userPattern);
  return patterns.Select(patternKeyValuePair => PopulatePatternMatchModel(chairBehaviorsProfileItem, patternKeyValuePair))
    .OrderByDescending(pm => pm.MatchPercentage);

}

Notice the GetPatternsWithGravityShare, that is the guy who does the actual pattern matching. We then sort on highest percentage value and return the “matched” result in PatternMatchModel’s.

Here is the PopulatePatternMatchModel method:

PopulatePatternMatchModel(ProfileItem chairBehaviorsProfileItem, KeyValuePair<PatternCardItem, double> patternKeyValuePair)
  return new PatternMatchModel()
      {
        Profile = chairBehaviorsProfileItem.NameField,
        PatternName = patternKeyValuePair.Key.NameField,
        MatchPercentage = patternKeyValuePair.Value < 0.00001 ? 0 : patternKeyValuePair.Value * 100
      };
}

To avoid really low values like “8.0000000 e-26”, I added the “< 0.00001" check.

Here is the PatternMatchModel

public class PatternMatchModel
{
  public string Profile { get; set; }
  public string PatternName { get; set; }
  public double MatchPercentage { get; set; }
}

This is how a json result will look like when it’s called from the rest api:

[
    {
        "Profile": "ChairBehaviors",
        "PatternName": "Ants In The Pants",
        "MatchPercentage": 100
    },
    {
        "Profile": "ChairBehaviors",
        "PatternName": "The Jojo",
        "MatchPercentage":0
    },
    {
        "Profile": "ChairBehaviors",
        "PatternName": "Chair Potato",
        "MatchPercentage":0
    }
]

Wondeful, people πŸ™‚

Ok once again the post seems to grow a lot, I think we will stop here. Stay tuned for a third post, which will cover the final step – Putting it all together.

Have a wonderful Christmas my friends.

That’s all for now folks πŸ™‚


3 thoughts on “Track and profile a person’s behavior in real life using Sitecore

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.