Ants in the pants, Jojo or Chair potato? Track, Profile and Personalize IOT data in Sitecore


Ho ho ho, merry Christmas good people 🙂

I would like to share with you guys on how powerful Sitecore’s wonderful profiling can be by using Profile and Pattern cards.

Profile and pattern cards allows you to track and profile a visitors behavior on a website, here are some great posts you should read:

Sitecore’s own GREAT documentation
Content profiling
Pattern cards
Configure pattern matching
Planning your pattern card strategy
Create a profile key or persona
Create and assign values to a profile card
Assign a profile card to an item in the Experience Editor

Blog posts from the the best ever Sitecore community
Tips for using Sitecore profile cards effectively
Profile Cards and Other P’s of Sitecore Personalization
Sitecore Behavioral Profile Pattern Controls
How to Set Persona/Pattern Card Programmatically in Sitecore? (Great post, helped me a lot)
How to trigger xDB Pattern Cards using jQuery AJAX with Sitecore MVC (Great post, helped me a lot)
Tag Sitecore content with Profile Card Programmatically (Great post, helped me a lot)
Sitecore Boost User Pattern Programmatically (Great post, helped me a lot)
Support Explicit User Types with Sitecore Personas (Great post, helped me a lot)

OK so we know that profile and pattern cards are great for profiling a visitors behavior on a website.
Let’s take it a step further, why not profile a persons 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.
2. Store the sensor data as custom facets in Sitecore xDB
3. Set profile keys from the custom facets in Sitecore xDB. (Covered in part 2)
4. The piece de resistance – pattern matching (Covered in part 2)
5. Putting it all together (will be covered in part 3)

1. Create Profile and Pattern cards from the sensor data

Lets take a look at the “sensor data”:

[
{
	"scaleHowManyTimesPersonSits":2,
	"scaleHowMuchPersonMovesOnChair": 2,
	"scaleHowLongPersonSits":8,
	"registeredTimestamp": 1513172009
},
{
	"scaleHowManyTimesPersonSits":9,
	"scaleHowMuchPersonMovesOnChair": 8,
	"scaleHowLongPersonSits":2,
	"registeredTimestamp": 1513247609
},
]

Ok now we do the Profile, let’s call it ChairBehaviors

Content profiles are categories that you define to track a contact’s behavior as they navigate through your website. Content profiling can help you gain a better understanding of the behavior, actions, and interests of your contacts
explained by Sitecore

* Instead of navigate through your website we will navigate in real life 🙂

We will also need to create some Profile Keys and Values.

Profile keys describe different aspects of your profiles. You assign numerical profile values to your profile keys, then use profile values to track how contacts interact with the website
explained by Sitecore

*Instead of interact with the website we will interact in real life 🙂

They will mirror the attributes from the sensor data:

  • SitDowns mirror scaleHowManyTimesPersonSits, a scale between 0 to 10. 0 means no sitting down at all and 10 means we have a jojo in our midst. Here we set Min Value to 0 and Max Value to 10
  • MovesOnChair mirror scaleHowMuchPersonMovesOnChair, a scale between 0 to 10. 0 means no movement on chair and 10 means we have a restless soul on that chair. We will do as above, set Min Value to 0 and Max Value to 10.
  • LongSitsOnChair mirror scaleHowLongPersonSits, a scale between 0 to 10. 0 means “nothing” and 10 means did the guy fall a sleep on the chair. Again set the Min Value to 0 and Max Value to 10.

Time to create some Profile Cards.

Profile cards contain saved profile keys and profile values. You can use profile cards to assign standardized profile values to items across your website.
explained by Sitecore

*Instead across our website, we will have across in real life 🙂

Each profile card will symbol a persons behavior on the chair.
Profile card Ants in the pants, this person can never sit still – A high value on MovesOnChair.

Profile card The Jojo, this person is running around all the time – A high value on SitDowns.

Profile card Chair Potato, this person loves to sit – A high value on LongSitsOnChair.

Next will be the Pattern Cards.

When a visitor navigates through a website, they accumulate the profile values of all the pages and resources that they request. Sitecore calculates the average score that the visitor has accumulated for each profile and maps the visitor to the pattern card that is the closest match.
explained by Sitecore

In other words they will try to match a person’s profile score to the closest Pattern Card score.

The Pattern cards will be very similar to the Profile cards.
Pattern card Ants in the pants, will have Max value set on MovesOnChair.

Pattern card The Jojo, will have Max value set on SitDowns.

Pattern card Chair Potato, will have Max value set on LongSitsOnChair.

OK 🙂 We are now finished with our ChairBehaviors profile.

2. Store the sensor data as custom facets in Sitecore xDB

In this case we need the historical data for profiling the persons behavior(on a chair), that means we need to store the sensor data somewhere.
Why not do it in Sitecore xDB(Mongo db in this scenario)?
Storing data in Sitecore xDB is great, that means we will put data on the user(contact) and that is great for future tracking 🙂

We could just store the data directly to the Contact in MongoDB, by using MongoDB .Net Driver. But that means we will bypass Sitecore who will not have a clue what is stored on the Contact and that is not good 😦
No let’s do it the proper way, we will store the sensor data as custom facets.

First we need to create the facet, let’s call it ChairSensor. The ChairSensor facet will contain a collection of “SensorData” elements and a “LastUpdated” field:

namespace Sandbox.Foundation.ChairSensor.Models.Facets
{

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Web;
  using Sitecore.Analytics.Model.Framework;


  [Serializable]
  public class SensorDataFacet : Facet, ISensorDataFacet
  {
    public const string FacetName = "ChairSensor";

    private const string ElementName = "ChairData";

    private const string FieldLastUpdated = "LastUpdated";


    public SensorDataFacet()
    {
      EnsureCollection<ISensorDataElement>(ElementName);
      EnsureAttribute<DateTime>(FieldLastUpdated);
    }

    public IElementCollection<IChairDataElement> SensorData => GetCollection<ISensorDataElement>(ElementName);

    
    public DateTime LastUpdated
    {
      get { return GetAttribute<DateTime>(FieldLastUpdated); }
      set { SetAttribute(FieldLastUpdated, value); }

    }

  }
}

namespace Sandbox.Foundation.ChairSensor.Models.Facets
{

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Web;
  using Sitecore.Analytics.Model.Framework;

  public interface ISensorDataFacet : IFacet
  {
    IElementCollection<ISensorDataElement> SensorData { get; }

    DateTime LastUpdated { get; set; }
  }
}

Next will be to create the SensorData element, which will hold the sensor data from the chair sensor.

namespace Sandbox.Foundation.ChairSensor.Models
{

  using Sitecore.Analytics.Model.Framework;
  using System.Collections.Generic;
  using System;

  [Serializable]
  public class SensorDataElement: Element, ISensorDataElement
  {

    public struct Fieldnames
    {
      public const string ChairData = "chairData";
    }

    public ChairDataElement()
    {
      EnsureAttribute<ChairDataRequestArg>(Fieldnames.ChairData);
    }

    public ChairDataRequestArg ChairData
    {
      get { return GetAttribute<ChairDataRequestArg>(Fieldnames.ChairData); }
      set { SetAttribute(Fieldnames.ChairData, value); }
    }

  }
}


namespace Sandbox.Foundation.ChairSensor.Models
{

  using System.Collections.Generic;
  using Sitecore.Analytics.Model.Framework;
  using System;

  public interface ISensorDataElement: IElement
  {

    ChairDataRequestArg ChairData { get; set; }
	
  }
}

ChairDataRequestArg will be the POCO class for the sensor data attributes:

namespace Sandbox.Foundation.ChairSensor.Models
{
  using System;

  
  public class ChairDataRequestArg
  {
    public int ScaleHowManyTimesPersonSits { get; set; }
    public int ScaleHowMuchPersonMovesOnChair { get; set; }
    public int ScaleHowLongPersonSits { get; set; }
    public long RegisteredTimestamp { get; set; }
  }
}

I almost forgot, we need the config patch:

<?xml version="1.0"?>

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <model>
      <elements>
        <element interface="Sandbox.Foundation.ChairSensor.Models.Facets.ISensorDataFacet, Sandbox.Foundation.ChairSensor" implementation="Sandbox.Foundation.ChairSensor.Models.Facets.SensorDataFacet, Sandbox.Foundation.ChairSensor"/>
        <element interface="Sandbox.Foundation.ChairSensor.Models.ISensorDataElement, Sandbox.Foundation.ChairSensor" implementation="Sandbox.Foundation.ChairSensor.Models.SensorDataElement, Sandbox.Foundation.ChairSensor"/>
      </elements>

      <entities>
        <contact>
          <facets>
            <facet name="ChairSensor" contract="Sandbox.Foundation.ChairSensor.Models.Facets.ISensorDataFacet, Sandbox.Foundation.ChairSensor" />
          </facets>
        </contact>
      </entities>
    </model>
  </sitecore>
</configuration>

Let’s put it together in a repository , SensorDataFacetRepository, where we can read and write the facets data:

namespace Sandbox.Foundation.ChairSensor.Repositories
{

  using Sitecore.Foundation.DependencyInjection;
  using Sandbox.Foundation.ChairSensor.Models;
  using Sandbox.Foundation.ChairSensor.Models.Facets;
  using Sitecore.Analytics.Tracking;
  using Sitecore.Diagnostics;
  using System;
  using System.Collections.Generic;
  using System.Linq;


  [Service(typeof(ISensorDataFacetRepository))]
  public class SensorDataFacetRepository : ISensorDataFacetRepository
  {
    public ISensorDataFacet Get(Contact contact)
    {
      return contact.GetFacet<ISensorDataFacet>(SensorDataFacet.FacetName);
    }

    public Tuple<bool, string> UpdateSensorData(Contact contact, ChairDataRequestArg chairData)
    {
      ISensorDataFacet facet = Get(contact);

      IEnumerable<ISensorDataElement> elements = facet.SensorData.ToList();

      ISensorDataElement element = elements.FirstOrDefault(e => e.RegisteredTimestamp == chairData.RegisteredTimestamp);

      if (element == null)
        return new Tuple<bool, string>(false, $"Could not find sensordata on following params - Contact: {contact.Identifiers.Identifier}, Registered Timestamp: {chairData.RegisteredTimestamp}");

      try
      {
        MapSensorData(element, chairData);

        facet.LastUpdated = DateTime.Now;

        return new Tuple<bool, string>(true, "Success");
      }
      catch (Exception e)
      {
		string errorMessage = $"Error updating sensordata on following params - Contact: {contact.Identifiers.Identifier}, Registered Timestamp: {chairData.RegisteredTimestamp}";
        Log.Error(errorMessage, e, this);
        return new Tuple<bool, string>(false, errorMessage);
      }

    }

   
    public void AddSensorDataCollections(Contact contact, ChairDataRequestArg[] chairDataCollection)
    {
      foreach (ChairDataRequestArg chairData in chairDataCollection)
      {
        AddSensorDataCollection(contact, chairData);
      }

    }


    public void AddSensorDataCollection(Contact contact, ChairDataRequestArg chairData)
    {
      ISensorDataFacet facet = Get(contact);

      ISensorDataElement element = facet.SensorData.Create();
      MapSensorData(element, chairData);
     
      facet.LastUpdated = DateTime.Now;

    }

    public IEnumerable<IChairDataElement> GetSensorDataCollections(Contact contact, long? startTimeStamp, long? endTimeStamp)
    {
      ISensorDataFacet facet = Get(contact);

      IEnumerable<ISensorDataElement> elements = facet.SensorData.ToList();

      if (startTimeStamp.HasValue)
        elements = elements.Where(e => e.RegisteredTimestamp >= startTimeStamp.Value).ToList();

      if (endTimeStamp.HasValue && elements.Any())
        elements = elements.Where(e => e.RegisteredTimestamp <= endTimeStamp.Value).ToList();


      return elements;


    }

    private void MapSensorData(IChairDataElement element, ChairDataRequestArg chairData)
    {
      element.ChairData = chairData;
    }

   
  }
}

namespace Sandbox.Foundation.ChairSensor.Repositories
{
  using System.Collections.Generic;
  using Sandbox.Foundation.ChairSensor.Models;
  using Sandbox.Foundation.ChairSensor.Models.Facets;
  using Sitecore.Analytics.Tracking;
  using System;

  public interface ISensorDataFacetRepository
  {
    Tuple<bool, string> UpdateSensorData(Contact contact, ChairDataRequestArg chairData);
    void AddSensorDataCollection(Contact contact, ChairDataRequestArg chairData);
    void AddSensorDataCollections(Contact contact, ChairDataRequestArg[] chairDataCollection);
    IEnumerable<ISensorDataElement> GetSensorDataCollections(Contact contact, long? startTimeStamp, long? endTimeStamp);
    ISensorDataFacet Get(Contact contact);
  }
}

Here is how it will look like in the Sitecore xDB(in MongoDB):

Wonderful 🙂

Ops the blogpost grow a lot here 😦 I think we need to take a break and continue in a second blog post.

Stay tuned for a second part, in that one I will continue to cover the following things:
Set profile keys from the custom facets in Sitecore xDB.
The piece de resistance – pattern matching
Putting it all together

That’s all for now folks 🙂


One thought on “Ants in the pants, Jojo or Chair potato? Track, Profile and Personalize IOT data in Sitecore

Leave a comment

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