Add Feature to Post Feedback for a Skill in SUSI.AI Server

In this blog post, we are going to discuss on how the feature to post user feedback was implemented on the SUSI. AI server side. The API endpoint by which a user can post feedback for a Skill is https://api.susi.ai/cms/feedbackSkill.json.

It accepts five url parameters –

  • model – It tells the model to which the skill belongs. The default value is set to General.
  • group – It tells the group(category) to which the skill belongs. The default value is set to Knowledge.
  • language – It tells the language to which the skill belongs. The default value is set to en.
  • skill – It is the name of the skill to be given a feedback. It is a compulsory param.
  • feedback – It is the parameter that contains the string type feedback of the skill, that the user wants to update.

The minimalUserRole is set to USER for this API, as only logged-in users can use this API.

Going through the API development

  • The parameters are first extracted via the call object that is passed to the main function. The  parameters are then stored in variables. If any parameter is absent, then it is set to the default value.
  • There is a check if the skill exists. It is done by checking, if there exists a file corresponding to that skill. If no, then an exception is thrown.
  • This code snippet discusses the above two points –

@Override
public ServiceResponse serviceImpl(Query call, HttpServletResponse response, Authorization authorization, final JsonObjectWithDefault permissions) throws APIException {

        String model_name = call.get("model", "general");
        File model = new File(DAO.model_watch_dir, model_name);
        String group_name = call.get("group", "Knowledge");
        File group = new File(model, group_name);
        String language_name = call.get("language", "en");
        File language = new File(group, language_name);
        String skill_name = call.get("skill", null);
        File skill = SusiSkill.getSkillFileInLanguage(language, skill_name, false);
        String skill_feedback = call.get("feedback", null);

        JSONObject result = new JSONObject();
        if (!skill.exists()) {
            throw new APIException(422, "Skill does not exist.");
        }
.
.
.

 

  • Then the  feedbackSkill.json that was initialized in the DAO object, is then opened. It is a JSONTray, which is then parsed.
  • It is checked if the skill is previously rated or not. If yes, has the user rated it before or not. In the case, when the user has given a feedback for it already, we just need to make changes in the feedbackSkill.json file as the feedback_count key present in the skillRatings.json doesn’t change. In case, the user hasn’t given the feedback earlier, the changes also needs to be reflected on the skillRatings.json file. For this use,  the utility function addToSkillRatingJSON is called. The feedback_count of that skill is increased by one, as it is a unique feedback and changes need to be reflected on the file.
  • The response object is then sent with three key values mainly, apart from the session object. They are
    • accepted –  true – It tells that the skill feedback has been updated.
    • message – “Skill feedback updated”
    • feedback –  <feedback> – It is the string that the client has sent as feedback.

Here are code snippets and data objects that will help in understanding the API well –

  • addToSkillRating function –
    public void addToSkillRatingJSON(Query call) {
        String model_name = call.get("model", "general");
        String group_name = call.get("group", "Knowledge");
        String language_name = call.get("language", "en");
        String skill_name = call.get("skill", null);

        JsonTray skillRating = DAO.skillRating;
        JSONObject modelName = new JSONObject();
        JSONObject groupName = new JSONObject();
        JSONObject languageName = new JSONObject();
        JSONObject skillName = new JSONObject();
        if (skillRating.has(model_name)) {
            modelName = skillRating.getJSONObject(model_name);
            if (modelName.has(group_name)) {
                groupName = modelName.getJSONObject(group_name);
                if (groupName.has(language_name)) {
                    languageName = groupName.getJSONObject(language_name);
                    if (languageName.has(skill_name)) {

                        skillName = languageName.getJSONObject(skill_name);
                    }
                }
            }
        }

        if (skillName.has("feedback_count")) {

            int skillFeedback = skillName.getInt("feedback_count");
            skillName.put("feedback_count", skillFeedback + 1 );
        } else {

            skillName.put("feedback_count", 1);
        }
                        
        if (!skillName.has("stars")) {

            JSONObject skillStars = new JSONObject();
            skillStars.put("one_star", "0");
            skillStars.put("two_star", "0");
            skillStars.put("three_star", "0");
            skillStars.put("four_star", "0");
            skillStars.put("five_star", "0");
            skillStars.put("avg_star", "0");
            skillStars.put("total_star", "0");
            skillName.put("stars", skillStars);
        }

        languageName.put(skill_name, skillName);
        groupName.put(language_name, languageName);
        modelName.put(group_name, groupName);
        skillRating.put(model_name, modelName, true);
        return;
    }
}

 

  • Sample  of feedbackSkill.json file –

{
  "general": {
    "Knowledge": {
      "en": {
        "aboutsusi": [
          {
            "feedback": "The skill is awesome!",
            "email": "[email protected]",
            "timestamp": "2018-06-06 02:20:20.245"
          },
          {
            "feedback": "Great!",
            "email": "[email protected]",
            "timestamp": "2018-06-06 02:20:20.245"
          }
        ],
        "quotes": [
          {
            "feedback": "Wow!",
            "email": "[email protected]",
            "timestamp": "2018-06-06 02:19:53.86"
          }
        ]
      }
    }
  }
}
  • Sample  of skillRatings.json file –

{
  "general": {
    "Knowledge": {
      "en": {
        "aboutsusi": {
          "negative": "0",
          "positive": "0",
          "stars": {
            "one_star": 0,
            "four_star": 2,
            "five_star": 0,
            "total_star": 5,
            "three_star": 3,
            "avg_star": 3.4,
            "two_star": 0
          },
          "feedback_count": 2
        },
        "quotes": {
          "negative": "0",
          "positive": "0",
          "stars": {
            "one_star": 0,
            "four_star": 0,
            "five_star": 0,
            "total_star": 0,
            "three_star": 0,
            "avg_star": 0,
            "two_star": 0
          },
          "feedback_count": 1
        }
      }
    }
  }
}

 

I hope the development of creating the aforesaid API is clear and proved to be helpful for your understanding.

Continue Reading

Demystifying a travis.yml file

In this blog post, we are going to demystify a travis.yml file. For reference, we will be using the travis configuration used in the  SUSI skill CMS project. But before delving into it, I would like to just give a brief introduction about Continuous Integration (CI) and Travis CI.

What is CI and Travis ?

Continuous Integration represents the practice of integrating a piece of work as early as possible instead of later so that one can receive immediate and frequent feedback on things that are wrong.

“ Continuous Integration is the practice of merging all developer working copies to a shared mainline several times a day.

-Wikipedia

Travis CI is a hosted continuous integration platform that is free for all open source projects hosted on Github. With just a file called .travis.yml containing some information about our project, we can trigger automated builds with every change to our code base in the master branch, other branches or even a pull request.

sudo: required
dist: trusty
language: node_js

node_js:
  - 6

script:
  - npm test

after_success:
 - bash ./pr_deploy.sh
 - bash ./deploy.sh

cache:
  directories:
    - node_modules

branches:
  only:
    - master

Travis configuration file of susi_skill_cms

Part-wise explanation of the file

  • When specifying sudo: required, Travis CI runs each build in an isolated Google Compute Engine virtual machine that offer a vanilla build environment for every build. This has the advantage that no state persists between builds, offering a clean slate and making sure that the tests run in an environment built from scratch. Builds have access to a variety of services for data storage and messaging, and can install anything that’s required for them to run.
  • The keyword dist represents the virtualization environment that is being used. trusty here refers to the distribution of the Linux environment used.
  • The keyword language represents the programming language that is be used for the project. The language used for the project is nodeJs.
  • Then follows the version details of node_js that is to be used. The node_js version used is 6.
  • This is the step where Travis runs the test script. Unless otherwise specified, it runs the default for the set language. In the case of node, it does node_js. The script stands for the build script that would be executed during the build process. The default build script for nodeJs is npm test. The result of execution of npm test can be found from the package.json file. It executes the npm run lint && react-scripts test –env=jsdom command, which is responsible for checking the linting issues and runs various unit and snapshot tests. We can add multiple lines of command to be executed.
  • The after_success block runs after the entire script is done. It’s the last step in the normal build process has been executed successfully. It has 2 commands to be executed –
    • bash ./pr_deploy.sh : Responsible for making a surge deployment of each PR
    • bash ./deploy.sh : Responsible for making a deployment to master branch
  • Travis CI can cache content that does not often change, to speed up the build process. The cache setting caches the node_modules directory, without the need to install the dependencies repeatedly.
  • We can specify certain branches to run, either by specifying a white list (using only keyword) or a black list (using except keyword). Here, the configuration mentions to run the build only for PRs to master branch.

Resources

Continue Reading

Persisting Cookies over SUSI.AI Subdomains

In this blog post, we are going to see, how the cookies are persisted for all the subdomains of SUSI.AI. By this implementation, the session for the user is maintained over all the SUSI.AI websites.

The cookies are persisted over these SUSI.AI websites –

All the web clients are developed using ReactJs framework and for the manipulation of cookies in React, a npm package – universal-cookie is used. Firstly, we will see how the cookies are set/created during login, followed by removal of cookies during logout.

Creating cookies

  • Firstly, we need to import the universal-cookie package into the component and create an instance of it.
import Cookies from 'universal-cookie';
const cookies = new Cookies();

Now, we can set a new cookie using this instance of cookies.

  • The following snippet sets the cookies after the login is done.

// AJAX call for login

let email = this.state.email.trim();
$.ajax({
  url: loginEndpoint,
  dataType: 'jsonp',
  jsonp: 'callback',
  crossDomain: true,
  success: function(response) {
  if (response.accepted) {
    cookies.set('serverUrl', BASE_URL, {
      path: '/',
      domain: '.susi.ai',
    });
    let accessToken = response.access_token;
    let state = this.state;
    let time = response.valid_seconds;
    this.handleOnSubmit(email, accessToken, time);
  }.bind(this),
  error: function(errorThrown) {
    .
    .
    .
  }.bind(this)    
});

handleOnSubmit = (email, loggedIn, showAdmin, time) => {
  let state = this.state;
  if (state.success) {
    cookies.set('loggedIn', loggedIn, {
      path: '/',
      maxAge: time,
      domain: '.susi.ai',
    });
    cookies.set('emailId', this.state.email, {
      path: '/',
      maxAge: time,
      domain: '.susi.ai',
    });
    this.props.history.push('/', { showLogin: false });
    window.location.reload();
  } else {
    this.setState({
      error: true,
      accessToken: '',
      success: false,
    });
  }
};

 

  • The cookies.set is a function provided by the package, that takes in three (3) parameters –
    • Cookie name
    • Cookie vallue
    • Options – an object containing the cookies properties
  • In the above example, say setting the loggedIn cookie, that contains the access token. We set the cookie name as loggedIn, the cookie value equal to the access token value received from the server response.
  • Apart from that, we have set 3 properties of the cookies, by passing an optional options parameter to the set function.
    • path – It indicates a URL path that must exist in the requested URL in order to send the Cookie header.
    • domainIt specifies allowed hosts to receive the cookie. If unspecified, it defaults to the host of the current document location, excluding subdomains.
    • maxAgeIt specifies a time duration after which the cookie gets expired.

Deleting cookies

  • It is mainly used, when a user wants to logout. It is used in the Logout component of the client’s codebase.
  • An approach to delete/remove a cookie is to set the expiry date of the cookie as Thu, 01 Jan 1970 00:00:01 GMT, which results in the removal of the cookie after a page refresh.
  • Following is the code snippet of how the cookies are removed to log-out a user of the website.

.
.
.
let deleteCookie = function(name, options = {}) {
  let cookieString = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
  if (options.domain) {
    cookieString = `${cookieString}domain=${options.domain};`;
  }
  if (options.path) {
    cookieString = `${cookieString}path=${options.path};`;
  }
  document.cookie = cookieString;
};
.
.
.
.
deleteCookie('loggedIn', { domain: '.susi.ai', path: '/' });
deleteCookie('serverUrl', { domain: '.susi.ai', path: '/' });
deleteCookie('emailId', { domain: '.susi.ai', path: '/' });
deleteCookie('showAdmin', { domain: '.susi.ai', path: '/' });
deleteCookie('username', { domain: '.susi.ai', path: '/' });

 

  • The deleteCookie function takes in 2 params –
    • Cookie name
    • options – an object containing the cookies properties
  • The options parameter needs to be passed while deleting the cookie, as it defines the scope for which the cookie has to be deleted.
  • The function creates a string and appends to it the expiry date, path, domain to the cookie name, if provided.
  • Finally, it sets the cookie by assigning the string to the document object.

Resources

Continue Reading

Adding a Horizontally scrollable component to display Skills based on metrics

In this blog post, I will discuss about the implementation of a horizontally scrollable component to display skill based on metrics. The purpose of the implementation is to show top skills based on metrics related to usage, ratings, etc in SUSI.AI Skills CMS.

Implementational details

  • We call this component SkillCardScrollList which takes in a list of cards to be displayed along with some other properties and returns an UI, as shown in the above GIF.
  • The parameters that the component takes are:
    • scrollId: It is a required field of the type String. It is the id name of the horizontally scrollable div.
    • skills: It contains an array of cards that are to displayed inside the container.
    • languageValue: It represents the language of the skills that are shown.
    • skillUrl: It contains the URL that the app would be taken to, on clicking individual Skill Card.
    • modelValue: It contains the model that the skill belongs to.
  • Here is a sample of how it is used in the BrowseSkill component, for showing the Top Rated Skills in a SkillCardsScrollList
<SkillCardScrollList
    scrollId="topRated"
    skills={this.state.topRatedSkills}
    modalValue={this.state.modalValue}
    languageValue={this.state.languageValue}
    skillUrl={this.state.skillUrl}
/>

 

  • The reason behind passing an unique scrollId as a prop to the component is that, there was a need to trigger the scroll event of the scrollable div n the click of left and right Floating Action Buttons (FABs) as shown in the UI. And, on multiple imports of this component, there would have been inconsistent scroll behaviour seen, had it not been unique.
  • Following in the code block of the component, which will be explained in details, that deals with the main implementation –
.
.
.
.
  scrollLeft = () => {
    let parentEle = document.getElementById(this.props.scrollId);
    let scrollValue = $(parentEle).scrollLeft() - 200;
    $(parentEle)
      .stop()
      .animate({ scrollLeft: scrollValue }, 100);
  };

  scrollRight = () => {
    // Similar function of scrollLeft
  };

  loadSkillCards = () => {
    let cards = [];
    Object.keys(this.state.skills).forEach(el => {
      .
      /* Each skill object is passed and then pushed to the cards
        array*/
      .
      );
    });
    // Set the cards array in the state 
    this.setState({
      cards,
    });
  };

  render() {
    return (
      <div
        style={{
          marginTop: '20px',
          marginBottom: '40px',
          textAlign: 'justify',
          fontSize: '0.1px',
          width: '100%',
        }}
      >
        <div>
          <div
            id={this.props.scrollId}
            className="scrolling-wrapper"
            style={styles.gridList}
          >
            <FloatingActionButton
              mini={true}
              backgroundColor={'#4285f4'}
              style={styles.leftFab}
              onClick={this.scrollLeft}
            >
              <NavigationChevronLeft />
            </FloatingActionButton>
            {this.state.cards}
            <FloatingActionButton
              mini={true}
              backgroundColor={'#4285f4'}
              style={styles.rightFab}
              onClick={this.scrollRight}
            >
              <NavigationChevronRight />
            </FloatingActionButton>
          </div>
        </div>
      </div>
    );
  }
}

 

  • The div with class scrolling-wrapper is actually scrolled on the click of the left and right FAB. For choosing the correct div to be scrolled, there was a necessary condition of an unique id as explained earlier, which has been set to the div.
  • For making the component horizontally scrollable, specific CSS rules are added to the div. They are –
gridList: {
  margin: '10px',
  textAlign: 'center',
  overflowX: 'scroll',
  overflowY: 'hidden',
  whiteSpace: 'nowrap',
},
leftFab: {
  position: 'absolute',
  left: 260,
  marginTop: 75,
},
rightFab: {
  position: 'absolute',
  right: 0,
  marginTop: 75,
  marginRight: 10,
},

 

  • The CSS rules for the FABs make them fixed in a position and only lets the card list scroll.
  • Lastly, there was a issue regarding the presence of horizontal scroll-bar been shown, which makes the UI look a bit unpleasant.

  • It was hidden with a pseudo selector CSS rule.
div.scrolling-wrapper::-webkit-scrollbar {
    display: none;
}

 

This was the implementation for the horizontally scrollable component for displaying Skill List based on a standard metrics. I hope, you found the blog helpful in making the understanding of the implementation better.

Resources

Continue Reading

Implementing the Feedback section on Skills CMS

In this blog post, we are going to implement the Skill feedback system on the Skills CMS. The features that are added by this implementation are displaying all the comments/feedbacks of the user, ability to add new feedback and also option to edit a previous feedback that was added.

The UI interacts with the back-end server via two APIs –

Detailed explanation of the implementation

  • The first task was to create a separate component for the feedback section – SkillFeedbackCard.js, along with the CSS file SkillFeedbackCard.css
  • Create ES6 function to get all the Feedbacks of a skill,namely getFeedback(), on the parent component, i.e, SkillListing.js
getFeedback = () => {
    let getFeedbackUrl = `${urls.API_URL}/cms/getSkillFeedback.json`;
    let modelValue = 'general';
    this.groupValue = this.props.location.pathname.split('/')[1];
    this.languageValue = this.props.location.pathname.split('/')[3];
    getFeedbackUrl = getFeedbackUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;

    let self = this;
    // Get skill feedback of the visited skill
    $.ajax({
        url: getFeedbackUrl,
        dataType: 'jsonp',
       crossDomain: true,
        jsonp: 'callback',
        success: function (data) {
            self.saveSkillFeedback(data.feedback);
        },
        error: function(e) {
            console.log(e);
        }
    });
};

saveSkillFeedback = (feedback = []) => {
    this.setState({
        skill_feedback: feedback
    })
}

 

  • This above code contains the function getFeedback(), that makes an API call to the server for getting all the feedbacks. On successfully getting the response, the feedback array of the response is then passed to a function, saveSkillFeedback(), which in turn updates the skill_feedback state, which was declared in the constructor. This re-renders the components and displays the feedback in the UI.
{
  "feedback": [
    {
      "feedback": "Awesome skill!",
      "email": "[email protected]",
      "timestamp": "2018-06-12 19:28:39.297"
    },
    {
      "feedback": "Awesome skill!",
      "email": "[email protected]",
      "timestamp": "2018-06-12 21:35:53.048"
    }
  ],
  "session": {"identity": {
    "type": "host",
    "name": "141.101.98.18_a7ab9c4d",
    "anonymous": true
  }},
  "skill_name": "aboutsusi",
  "accepted": true,
  "message": "Skill feedback fetched"
}

 

  • Then, we go ahead and create the function that is responsible for posting new feedback and editing them as well.
postFeedback = (newFeedback) => {

    let baseUrl = urls.API_URL + '/cms/feedbackSkill.json';
    let modelValue = 'general';
    this.groupValue = this.props.location.pathname.split('/')[1];
    this.languageValue = this.props.location.pathname.split('/')[3];
    let postFeedbackUrl = baseUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name + '&feedback=' + newFeedback + '&access_token='+cookies.get('loggedIn');

    let self = this;
    $.ajax({
        url: postFeedbackUrl,
        dataType: 'jsonp',
        jsonp: 'callback',
        crossDomain: true,
        success: function (data) {
            self.getFeedback()
        },
        error: function(e) {
            console.log(e);
        }
    });
};

 

  • This above code snippet contains the function postFeedback(newFeedback), that takes the user feedback and make an API call to update it on the server.
  • All the required functions are ready. Now we add the SkillFeedbackCard.js component on the SkillListing.js component and pass useful data in the props.
<SkillFeedbackCard
    skill_name={this.state.skill_name}
    skill_feedback={this.state.skill_feedback}
    postFeedback={this.postFeedback}
/>
  • The next step is creating the UI for the SkillFeedbackCard.js component. We have used standard Material-UI components for creating the UI, that includes List, ListItem, Divider, IconButton, etc.
  • Code snippet for the Feedback ListItem  –
<ListItem
    key={index}
    leftAvatar={<CircleImage name={data.email.toUpperCase()} size='40' />}
    primaryText={data.email}
    secondaryText={<p> {data.feedback} </p>}
/>

 

  • The next part of the UI implementation creating option to edit and post feedback.
  • Code snippet for the Post feedback section  –
className=“feedback-textbox”> id=“post-feedback” hintText=“Skill Feedback” defaultValue=“” errorText={this.state.errorText} multiLine={true} fullWidth={true} /> label=“Post” primary={true} backgroundColor={‘#4285f4’} style={{ margin: 10 }} onClick={this.postFeedback} />

 


 

  • For the edit section, I have used a Dialog box for it. Code snippet for the Edit feedback section  –
<Dialog
    title="Edit Feedback"
    actions={actions}
    modal={false}
    open={this.state.openDialog}
    onRequestClose={this.handleClose}
>
    <TextField
        id="edit-feedback"
        hintText="Skill Feedback"
        defaultValue={userFeedback}
        errorText={this.state.errorText}
        multiLine={true}
        fullWidth={true}
    />
</Dialog>

 

This was the implementation for the Skill Feedback System on the Skills CMS and I hope, you found the blog helpful in making the understanding of the implementation better.

Resources

 

Continue Reading

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 Reading

Making a SUSI Skill to get details about bank from IFSC

We are going to make a SUSI skill that fetches information about a bank when the IFSC (Indian Financial System Code) is known. Here is a detailed explanation of how we going about doing this.

Getting started with the skill creation

API endpoint that returns the bank details

Before going to the skill development, we need to find an API that would return the bank details from the IFSC, On browsing through various open source projects. I found an apt endpoint by Razorpay. Razorpay is a payment gateway for India which allows businesses to accept, process and disburse payments with ease. The Github link  to the repository is https://github.com/razorpay/ifsc.

API endpoint –  https://ifsc.razorpay.com/<:ifsc>
Request type –  GET
Response type –  JSON

Now, head over to the SUSI Etherpad, which is the current SUSI Skill Development Environment and create a new Pad. 

Here, we need to define the skill in the Etherpad. We will now write rules/intents for the skill. An intent represents an action that fulfills a user’s spoken request.

Intents consist of 2 parts –

  • User query – It contains different patterns of query that user can ask.
  • Answer – It contains the possible answer to the user query.

The main intent that our skill focuses on is, returning the bank name and address from the IFSC code. Here is how it looks –

Name of bank with IFSC code * | Bank's name with IFSC code *
!example:Name of bank with IFSC code SBIN0007245
!expect: The name of bank is State Bank of India
!console:The name of bank with IFSC code $1$ is $object$
{
"url":"https://ifsc.razorpay.com/$1$",
"path":"$.BANK"
}
eol

Part-wise explanation of the intent

  • The first line contains the query pattern that the user can use while querying. You can see that a wildcard character (*) is used in the pattern. It contains the IFSC of the bank that we wish to know, and will later on use to fetch the details via the API.
  • The second line contains an example query, followed by third line that contains the expected answer.
  • Last part of the rule contains the answer that is fetched from an external API –  https://ifsc.razorpay.com/<:ifsc>  ,via the console service  provided by SUSI Skills. Here, <:ifsc> refers to the IFSC that the user wants to know about. We get it from the user query itself, and can access it by the variable name $1$ as it matches with the 1st wildcard present in the query. If there would be 2 wildcards, we could have accessed them by $1$ and $2$ respectively.
  • The console service provides us with an option to enter the url of the API that we want to hit and path of the key we want to use.

The sample response of the endpoint looks like this :

{
  "BANK": "Karnataka Bank",
  "IFSC": "KARB0000001",
  "BRANCH": "RTGS-HO",
  "ADDRESS": "REGD. & HEAD OFFICE, P.B.NO.599, MAHAVEER CIRCLE, KANKANADY, MANGALORE - 575002",
  "CONTACT": "2228222",
  "CITY": "DAKSHINA KANNADA",
  "RTGS": true,
  "DISTRICT": "MANGALORE",
  "STATE": "KARNATAKA"
}

 

  • Since, we want to extract the name of the bank, the BANK key contains our desired value and we will use $.BANK in the path of the console service. And it can be accessed by $object$ in the answer. We frame the answer using $object$ and $1$ variables, and it like the one mentioned in the expected answer. eol marks the end of the console service.
  • Similarly, the intent that gives us the address of the bank looks like this –
Address of bank with IFSC code * | Bank's address with IFSC code *
!example:Address of bank with IFSC code SBIN0007245
!expect: The address of bank is TILAK ROAD HAKIMPARA, P.O.SILIGURI DARJEELING, WEST BENGAL ,PIN - 734401
!console:The address of bank with IFSC code $1$ is $object$
{
  "url":"https://ifsc.razorpay.com/$1$",
  "path":"$.BANK"
}
eol

Testing the skill

  • Open any SUSI Client and then write dream <your dream name> so that dreaming is enabled for SUSI. We will write down dream ifsc. Once dreaming is enabled, you can now test any skills which you’ve made in your Etherpad.
  • We can test the skills by asking queries and matching it with the expected answer. Once the testing is done, write stop dreaming to disable dreaming for SUSI.

  • After the testing was successful completely, we will go ahead and add it to the susi_skill_data.
  • The general skill format is –
::name <Skill_name>
::author <author_name>
::author_url <author_url>
::description <description> 
::dynamic_content <Yes/No>
::developer_privacy_policy <link>
::image <image_url>
::term_of_use <link>

#Intent
User query1|query2|query3....
Answer answer1|answer2|answer3...

We will add the basic skill details and author details to the etherpad file and make it in the format as mentioned above. The final text file looks like this –

::name IFSC to Bank Details
::author Akshat Garg
::author_url https://github.com/akshatnitd
::description It is a bank lookup skill that takes in IFSC code from the user and provides you all the necessary details for the Bank. It is valid for banks in India only
::dynamic_content Yes
::developer_privacy_policy 
::image images/download.jpeg
::terms_of_use 

Name of bank with IFSC code * | Bank's name with IFSC code *
!example:bank with IFSC code *
!expect: The name of bank is SBI
!console:The name of bank with IFSC code $1$ is $object$
{
"url":"https://ifsc.razorpay.com/$1$",
"path":"$.BANK"
}
eol

Address of bank with IFSC code * | Bank's address with IFSC code *
!example:Address of bank with IFSC code *
!expect: The address of bank is 
!console:The address of bank with IFSC code $1$ is $object$
{
"url":"https://ifsc.razorpay.com/$1$",
"path":"$.ADDRESS"
}
eol

Submitting the skill

The final part is adding the skill to the list of skills for SUSI. We can do it by 2 ways:

1st method (using the web interface)

  • Open https://susi.skills.com and login into SUSI account (or sign up, if not done).
  • Click on the create skill button.
  • Select the appropriate fields like Category, Language, Skill name, Logo.
  • Paste the text file that we had created.
  • Add comments regarding the skill and click on Save to save the skill.

2nd method (sending a PR)

  • Send a Pull Request to susi_skill_data repository providing the dream name. The PR should have the text file containing the skill.

So, this was a short blog on how we can develop a SUSI skill of our choice.

Resources

Continue Reading
  • 1
  • 2
Close Menu