Adding New Arrivals in the Metrics of SUSI.AI

The SUSI Skill CMS homepage contains a lot of metics on the homepage. For example, the highest rated skills, latest skills, most used skills etc. Another important metric is the newly arrived skills in a given period of time, say in last week. This keeps the users updated with the system and allows them to know what work is going on the assistant. This also inspires the skill creators to create more skills.

Update skill listing API

To get the list of recently added skill, first of all, we need a mechanism to sort them in descending order of their creation time. Update the skill listing API ie, ListSkillService.java to sort skills by their creation time. The creation time is stored in “YYYY-MM-DD T M S” format, for ex “2018-08-12T03:11:32Z”. So it can be sorted easily using string comparison function.

Collections.sort(jsonValues, new Comparator<JSONObject>() {
    private static final String KEY_NAME = "creationTime";
    @Override
    public int compare(JSONObject a, JSONObject b) {
        String valA = new String();
        String valB = new String();
        int result = 0;
         try {
            valA = a.get(KEY_NAME).toString();
            valB = b.get(KEY_NAME).toString();
            result = valB.compareToIgnoreCase(valA);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return result;
    }
});

After sorting the skills in descending order of their creation date. We need to filter them based on the time period. For example, if the skills created in last days are required then we need a generalized filter for that. This can be achieved by creating a variable for the starting date of the required time period. Say, if the skill created in last 7 days are required, then the number of milliseconds equivalent to 7 days is subtracted from the current timestamp. All the skills created after this timestamp are added to the result while others are skipped.

if (dateFilter) {
	long durationInMillisec = TimeUnit.DAYS.toMillis(duration);
	long timestamp = System.currentTimeMillis() - durationInMillisec;
	String startDate = new Timestamp(timestamp).toString().substring(0, 10); //substring is used for getting timestamp upto date only
	String skillCreationDate = jsonValues.get(i).get("creationTime").toString().substring(0,10);
	if (skillCreationDate.compareToIgnoreCase(startDate) < 0)
	{
	 continue;
	}
}

This filtering works in the API only when the filter type is set to date and duration in days is passed in the endpoint.

Implement new arrivals on CMS

Create the MenuItems in the sidebar that shows the filter name and add onClick handler on them. The skill listing API with the duration filter is passed to the handler. 3 MenuItems are added:

  • Last 7 Days
  • Last 30 Days
  • Last 90 Days

<MenuItem  value="&applyFilter=true&filter_name=descending&filter_type=date&duration=7"  key="Last 7 Days"
  primaryText="Last 7 Days"
  onClick={event =>
    this.handleArrivalTimeChange(
      event, '&applyFilter=true&filter_name=descending&filter_type=date&duration=7',
    )
  }
/>

Create a handler that listens to the onClick event of the above MenuItems. This handler accepts the API endpoint and calls the loadCards function with it.

handleArrivalTimeChange = (event, value) => {
 this.setState({ filter: value }, function() {
   // console.log(this.state);
   this.loadCards();
 });
};

Resources

Continue ReadingAdding New Arrivals in the Metrics of SUSI.AI

Device wise Usage Statistics of a Skill in SUSI.AI

The device wise usage distribution in SUSI.AI helps in understanding what kind of skills are used more on which type of devices, so that the skill creator can harness the core features of that device to enhance the skills or make the user experience smoother. For example, music playing skill may be used mostly on Smart Speakers whereas Android devices may have higher usage of alarm setting skill.

Sending the device type (ex, web client)

  1. Send the device type parameter as “Web Client” along with the query while fetching reply from SUSI server in chat.json API. The parameter is device_type.

// Add the type of device in the query
{
url += '&device_type=Web Client';
}

Storage of Device Wise Skill Usage Data on SUSI Server

  1. Create a deviceWiseSkillUsage.json file to store the device wise skill usage stats and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the device type and the usage count on that type of device (like Android, iOS, Web Client, Smart Speaker and others).
  2. Modify the src/ai/susi/server/api/susi/SusiService.java file to fetch device_type from the query parameters and pass them SusiCognition constructor.

public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization user, final JsonObjectWithDefault permissions) throws APIException {
	...
	String deviceType = post.get("device_type", "Others");
	...
	SusiCognition cognition = new SusiCognition(q, timezoneOffset, latitude, longitude, countryCode, countryName, language, deviceType, count, user.getIdentity(), minds.toArray(new SusiMind[minds.size()]));
	...
}
  1. Modify the src/ai/susi/mind/SusiCognition.java file to accept the deviceType in the constructor parameters. Check which skill is being currently used for the response and update the skill’s usage stats for the current device in deviceWiseSkillUsage.json. Call the function updateDeviceWiseUsageData() to update the skill usage data.

List<String> skills = dispute.get(0).getSkills();
for (String skill : skills) {
    updateDeviceWiseUsageData(skill, deviceType);
}

The updateDeviceWiseUsageData() function accepts the skill path and type of device. It parses the skill path to get the skill metadata like its model name, group name, language etc. The function then checks if the device already exists in the JSON file or not. If it exists then it increments the usage count by 1 else it creates an entry for the device in the JSON file and initializes it with the usage count 1.

for (int i = 0; i < deviceWiseUsageData.length(); i++) {
  deviceUsage = deviceWiseUsageData.getJSONObject(i);
  if (deviceUsage.get("device_type").equals(deviceType)) {
    deviceUsage.put("count", deviceUsage.getInt("count") + 1);
    deviceWiseUsageData.put(i,deviceUsage);
  }
}

API to access the Device Wise Skill Usage Data

  1. Create GetDeviceWiseSkillUsageService.java file to return the usage stats stored in deviceWiseSkillUsage.json

public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization rights, final JsonObjectWithDefault permissions) {        
  ...  // Fetch the query parameters
  JSONArray deviceWiseSkillUsage = languageName.getJSONArray(skill_name);
  result.put("skill_name", skill_name);
  result.put("skill_usage", deviceWiseSkillUsage);
  result.put("accepted", true);
  result.put("message", "Device wise skill usage fetched"); 
  return new ServiceResponse(result);    
}
  1. Add the API file to src/ai/susi/server/api/susi/SusiServer.java

services = new Class[]{
	...

	//Skill usage data
	GetDeviceWiseSkillUsageService.class
	
	...
}

 

Endpoint : /cms/getDeviceWiseSkillUsage.json

Parameters

  • model
  • group
  • language
  • Skill

Sample query: /cms/getDeviceWiseSkillUsage.json?model=general&group=Knowledge&language=en&skill=aboutsusi

Sample response:

{  
   "skill_usage":[  
    {
      "device_type": "Web Client",
      "count": 1
    },
    {
        "device_type": "Android",
        "count": 4
    },
    {
        "device_type": "iOS",
        "count": 2
    },
    {
        "device_type": "Smart Speaker",
        "count": 1
    },
    {
        "device_type": "Others",
        "count": 2
    }
   ],
   "session":{  
      "identity":{  
         "type":"host",
         "name":"162.158.154.147_81c88a10",
         "anonymous":true
      }
   },
   "skill_name":"ceo",
   "accepted":true,
   "message":"Device wise skill usage fetched"
}

Resources

Continue ReadingDevice wise Usage Statistics of a Skill in SUSI.AI

Countrywise Usage Analytics of a Skill in SUSI.AI

The statistics regarding which country the skills are being used is quite important. They help in updating the skill to support the native language of those countries. SUSI.AI must be able to understand as well as reply in its user’s language. So mainly the server side and some client side (web client) implementation of country wise skill usage statistics is explained in this blog.

Fetching the user’s location on the web client

  1. Add a function in chat.susi.ai/src/actions/API.actions.js to fetch the users location. The function makes a call to freegeoip.net API which returns the client’s location based on its IP address. So country name and code are required for country wise usage analytics.

export function getLocation(){
  $.ajax({
    url: 'https://cors-anywhere.herokuapp.com/http://freegeoip.net/json/',
    success: function (response) {
      _Location = {
        lat: response.latitude,
        lng: response.longitude,
        countryCode: response.country_code,
        countryName: response.country_name
      };
    },
  });
}
  1. Send the location parameters along with the query while fetching reply from SUSI server in chat.json API. The parameters are country_name and country_code.

if(_Location){
	url += '&latitude='+_Location.lat+'&longitude='+_Location.lng+'&country_code='+_Location.countryCode+'&country_name='+_Location.countryName;
}

Storage of Country Wise Skill Usage Data on SUSI Server

  1. Create a countryWiseSkillUsage.json file to store the country wise skill usage stats and make a JSONTray object for that in src/ai/susi/DAO.java file. The JSON file contains the country name, country code and the usage count in that country.
  1. Modify the src/ai/susi/server/api/susi/SusiService.java file to fetch country_name and country_code from the query parameters and pass them SusiCognition constructor.

String countryName = post.get("country_name", "");
String countryCode = post.get("country_code", "");
...

SusiCognition cognition = new SusiCognition(q, timezoneOffset, latitude, longitude, countryCode, countryName, language, count, user.getIdentity(), minds.toArray(new SusiMind[minds.size()]));
  1. Modify the src/ai/susi/mind/SusiCognition.java file to accept the countryCode and countryName in the constructor parameters. Check which skill is being currently used for the response and update the skill usage stats for that country in countryWiseSkillUsage.json. Call the function updateCountryWiseUsageData() to update the skill usage data.

if (!countryCode.equals("") && !countryName.equals("")) {
    List<String> skills = dispute.get(0).getSkills();
    for (String skill : skills) {
        try {
            updateCountryWiseUsageData(skill, countryCode, countryName);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The updateCountryWiseUsageData() function accepts the skill path , country name and country code. It parses the skill path to get the skill metadata like its model name, group name, language etc. The function then checks if the country already exists in the JSON file or not. If it exists then it increments the usage count by 1 else it creates an entry for the skill in the JSON file and initializes it with the current country name and usage count 1.

for (int i = 0; i < countryWiseUsageData.length(); i++) {
  countryUsage = countryWiseUsageData.getJSONObject(i);
  if (countryUsage.get("country_code").equals(countryCode)) {
    countryUsage.put("count", countryUsage.getInt("count")+1);
    countryWiseUsageData.put(i,countryUsage);
  }
}

API to access the Country Wise Skill Usage Data

  1. Create GetCountryWiseSkillUsageService.java file to return the usage stats stored in countryWiseSkillUsage.json

public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization rights, final JsonObjectWithDefault permissions) {
        
  ...  // Fetch the query parameters
  JSONArray countryWiseSkillUsage = languageName.getJSONArray(skill_name);
  return new ServiceResponse(result);
}
  1. Add the API file to src/ai/susi/server/api/susi/SusiServer.java

services = new Class[]{
	...
	//Skill usage data
	GetCountryWiseSkillUsageService.class
	...
}

 

Endpoint : /cms/getCountryWiseSkillUsage.json

Parameters

  • model
  • group
  • language
  • Skill

Sample query: /cms/getCountryWiseSkillUsage.json?model=general&group=Knowledge&language=en&skill=aboutsusi

Sample response:

{  
   "skill_usage":[  
      {  
         "country_code":"MYS",
         "country_name":"Malaysia",
         "count":1
      },
      {  
         "country_code":"MYS",
         "country_name":"Malaysia",
         "count":1
      }
   ],
   "session":{  
      "identity":{  
         "type":"host",
         "name":"162.158.154.147_81c88a10",
         "anonymous":true
      }
   },
   "skill_name":"ceo",
   "accepted":true,
   "message":"Country wise skill usage fetched"
}

Resources

Continue ReadingCountrywise Usage Analytics of a Skill in SUSI.AI

Showing top metrics from skill groups

SUSI.AI shows top metrics on the home page. They include highest rated skills, most used skills, latest skills and skills with most feedbacks etc. Now the idea is to include top skills from a particular category also. For example “SUSI, what are your top games”? So how to fetch the required metrics in a generalized way.

Updating the skill metrics data API

Add an API parameter in SkillMetricsDataService.java to specify the names of groups to fetch the required metrics. It accepts a semicolon (;) separated list of group names. If no group is passed then by default it shows the top games.

String metrics_list = call.get("metrics", "Games, Trivia and Accessories");
String[] metrics_names = metrics_list.split(";");

Split the metrics parameter by semicolon and store in an array. This array contains all the groups of which top skills are to be displayed on the CMS home page. Loop over the array, group by group and filter out the skills that don’t belong to the current metrics group. Sort the filtered skills in decreasing order of the overall rating. But this sorting does not simply arrange the skills in decreasing order of their overall rating. Actually, it divides the skills into 2 halves. The first half contains the skills that have been rated by at least 10 users in decreasing order of the overall ratings. And the second half contains the rest of the skills in decreasing order of their overall rating.

for (String metric_name : metrics_names) {
    try {
        metric_name = metric_name.trim();
        List<JSONObject> groupJsonValues = new ArrayList<JSONObject>();
        for (int i = 0; i < jsonArray.length(); i++) {
            if (jsonArray.getJSONObject(i).get("group").toString().equals(metric_name)) {
                groupJsonValues.add(jsonArray.getJSONObject(i));
            }
        }
        // Get skills based on ratings of a particular group
        SusiSkill.sortByAvgStar(groupJsonValues, false);

        JSONArray topGroup = new JSONArray();
        topGroup = getSlicedArray(groupJsonValues, count);
        skillMetrics.put(metric_name, topGroup);
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

So if top games and news skills are to be shown on the CMS then the API endpoint looks like :

https://api.susi.ai/cms/getSkillMetricsData.json?metrics=Games, Trivia and Accessories; News

And the response is like :

{
	accepted: true,
	model: "general",
	group: "All",
	language: "en",
	metrics: {
		+newest: [...],
		+latest: [...],
		+rating: [...],
		+usage: [...],
		+feedback: [...],
		+staffPicks: [...],
		+Games, Trivia and Accessories: [...],
		+News: [...]
	},
	message: "Success: Fetched skill data based on metrics"
}

Resources

  • NA
Continue ReadingShowing top metrics from skill groups

Showing only those languages for which skills are available

SUSI.AI is available for almost all the internationally recognised languages of the world. An author is allowed to create a skill in any of these languages. But there are some languages for which skills have not been created yet. So only those languages should be shown in the SUSI Skill CMS for which the skills are available. The approach is that all the languages must be listed while creating the skills but only non-empty languages must be listed while filtering skills on the CMS category page.

Updating the get languages API

  1. Add an API parameter in GetAllLanguages.java to fetch the group name. It is used to fetch the list of languages for which skills are available in that particular group. If no group is passed it means that the all the languages are to be listed. For that, we can use any group and show all the languages in that group. Say “Knowledge”.

String group_name = call.get("group", null);
if (group_name == null) {
    File group = new File(model, "Knowledge");
}

 

  1. Now check if the file inside the group folder is a directory. If yes then add it to the list of languages to be returned.

String[] languages = group.list((current, name) -> new File(current, name).isDirectory());

 

  1. If the languages corresponding to a particular category are to be fetched then first checked if the group is “All” or any specific group. Since the “All” category is not stored as such so we need to iterate over all the groups present in the parent directory ie, the model directory.

String[] group_names = model.list((current, name) -> new File(current, name).isDirectory());

 

  1. Now iterate over all the groups present in the group_names array and list the files present in it. Apply a filter to the list that accepts a file only if it is a directory and not empty ie, contains at least 1 language. Add that file to the list of languages.

group.list(new FilenameFilter() {
	@Override
	public boolean accept(File file, String s) {
		Boolean accepted = new File(file, s).list().length > 1;
		if (accepted) {
			if (!languages.contains(s)) {
		    	languages.add(s);
			}
		}
		return accepted;
	}
});

 

  1. The processing of getting languages for a particular group is same, just the iteration over the model directory is not required.

Resources

Continue ReadingShowing only those languages for which skills are available

Setup interactive charts for data representation

At the end of this blog, you would be able to setup interactive charts using HighCharts and D3.js. As the charts/data-visualisation models will form the backbone of the upcoming SUSI.AI Analytics dashboard, as well as the data representational model for various useful data. For the purpose of integration with the SUSI Skills CMS project, we will be using the react-highcharts and react-tagcloud library.

There are various kinds of charts and plots that HighCharts offers. They are –

  • Line charts
  • Area charts
  • Column and Bar Charts
  • Pie Charts
  • Scatter and Bubble Charts
  • Combinations
  • Dynamic Charts
  • Gauges
  • Heat and Tree maps
  • 3D charts

Check out this link for the full list.

We would be aiming to build up the above charts for analysis of Term Frequency Trends and Trending Clouds.

For the Term Frequency Trends, we will need to setup a Basic Line Graph and for the later,  we need a World Cloud.

Setting up a Basic Line Graph

The aim is to setup a basic line graph and to accomplish that we use a react library called react-highcharts, which makes our work very easier.

Firstly, we create an object config that contains the labels and the required data, with the key values as mentioned in the API reference. The object looks like this –

const config = {
  xAxis: {
    categories: ['01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20']
  },
  series: [{
    data: [750, 745, 756, 740, 760, 752, 765]
  }]
};

Secondly, we create a React Component and pass the config object as a property to the ReactHighcharts component.

Finally, we render the component in a div of the index.html file, and the following output is achieved.

The code for the component that renders the Chart is as follows:

import React from 'react';
import ReactHighcharts from 'react-highcharts';
import ReactDOM from 'react-dom';

const config = {
  xAxis: {
    categories: ['01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20']
  },
  series: [{
    data: [750, 745, 756, 740, 760, 752, 765]
  }]
};

ReactDOM.render(<ReactHighcharts  config={config} />, document.getElementById('app'));

Setting up a WordCloud

Here, we wish to setup a WordCloud that would show the different words that got searched or the top trending words. We would be using the react-tagcloud library for this.

Firstly, we create an object data that contains the text along with the count/frequency of search. The object looks like this –

const data = [
  { value: "JavaScript", count: 38 },
  { value: "React", count: 30 },
  { value: "Nodejs", count: 28 },
  { value: "Express.js", count: 25 },
  { value: "HTML5", count: 33 },
  { value: "MongoDB", count: 18 },
  { value: "CSS3", count: 20 }
];

Secondly, we create a React Component and pass the data object as a property to the TagCloud component.

Finally, we render the component in a div of the index.html file, and the following output is achieved.

The code for the component that renders the Chart is as follows:

import React from 'react';
import React, { Component } from 'react';
import { TagCloud } from "react-tagcloud";
 
const data = [
  { value: "JavaScript", count: 38 },
  { value: "React", count: 30 },
  { value: "Nodejs", count: 28 },
  { value: "Express.js", count: 25 },
  { value: "HTML5", count: 33 },
  { value: "MongoDB", count: 18 },
  { value: "CSS3", count: 20 }
];
 
const SimpleCloud = () => (
  <TagCloud 
       minSize={12}
       maxSize={35}
       tags={data}
       onClick={tag => alert(`'${tag.value}' was selected!`)} />
);

ReactDOM.render(<SimpleCloud />, document.getElementById('app'));

 

These were some examples of setting up some of the data-visualization models, that would form the basic building block of the SUSI Analytics project. I hope this blogs would be a good starting point for those wanting to start with setting up charts, graphs, etc.

Resources

Continue ReadingSetup interactive charts for data representation