Step 1. Contact Forsta Support

Contact Forsta support and ask them to whitelist https://api.roundtable.ai for API calls.

Step 2. Initialize Variables

Add this block to the start of the survey

<exec>
survey_id = gv.survey.root.alt
api_key = ''
p.client_dict = {'responses': {}, 'questions': {}, 'question_histories': {}, 'fingerprint_id': '', 'survey_id': '', 'participant_id': '', 'num_non_empty': 0, 'question_labels': []}
</exec>

Step 3. Embed Tracking Code for Every Question

In the XML for your survey, add the following lines of code to every “Text” or “Essay” questions you want to track (text and textarea elements in XML). This code embeds our Javascript tracker for the question, automatically extracts the question and response, and sets up hidden rows to store the Alias API data for the question when it is later called. The actual response is stored in the shown “response” row.

<row label="response"/>
<row label="d1" cond="False">alias-flagged</row>
<row label="d2" cond="False">alias-checks</row>
<row label="d3" cond="False">alias-group</row>
<row label="d4" cond="False">alias-effort</row>
<style name="question.after"><![CDATA[<script src='https://roundtable.ai/js/forsta-tracker-v2.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
  const q = ${jsexport()};
  const label = q['label']
  const oes = document.querySelectorAll('input[type="text"], textarea');
  oes.forEach(el => {
    el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
	  el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
	});
	initializeClientDict(label, q);
]]></style>

Step 4. Call API and Store Data

Add the following code to the end of your survey and set the api_key to your Alias API Key (generated on your account account). This code will call the API on the page the code is embedded in. It generally takes a few seconds and must finish executing before the next page is loaded. The code must be placed after any open-ends you are tracking and on a separate page from them (i.e., after a <suspend/> in XML).

<exec>
survey_id = gv.survey.root.alt
api_key = '' # Set your API key here
p.headers = {'api_key': api_key, 'Content-Type': 'application/json'}
p.data = {'responses': p.client_dict['responses'], 'questions': p.client_dict['questions'],'question_histories': p.client_dict['question_histories'], 'fingerprint_id':  p.client_dict['fingerprint_id'], 'survey_id': survey_id, 'participant_id': uuid }
</exec>

<logic label="alias_api" cond="p.client_dict['num_non_empty'] != 0" api:data="p.data" api:headers="p.headers" api:method="POST" api:url="https://api.roundtable.ai/alias/v020" uses="api.1"/>

<exec>
# Define functions
def checks_array_to_string(array):
    return ', '.join(map(str, array)) if array else "None"

def format_checks(response, keys):
    return {key: checks_array_to_string(response.r.get('checks', {}).get(key, [])) for key in keys}

def format_response_groups(response, keys,type):
    return {key: response.r.get(type, {}).get(key, 0) for key in keys}
    
def format_effort_ratings(response, keys,type):
    return {key: response.r.get(type, {}).get(key, 'NA') for key in keys}
    
def get_fp_value(fp_dict, field):
    if not isinstance(fp_dict, dict):
        return "None"
    if field not in fp_dict:
        return "None"
    value = fp_dict.get(field)
    return "None" if value is None else value
    
def get_problem(api_response):
    problem = api_response.r.get('problem', "None")
    return problem if isinstance(problem, basestring) and problem else "None"
    

# Add all API data to hidden question
labels = p.client_dict['question_labels']
fp_checks = alias_api.r.get('fingerprint_checks', {})
alias_data.d1.val = alias_api.r.get('flagged', False)
alias_data.d2.val = alias_api.r.get('num_checks_failed', 0)
alias_data.d3.val = alias_api.r.get('model', 'API not called')
alias_data.d4.val = alias_api.r.get('error', False)
alias_data.d5.val = get_problem(alias_api)
alias_data.d6.val = get_fp_value(fp_checks,'participant_id')
alias_data.d7.val = get_fp_value(fp_checks,'duplicate')
alias_data.d8.val = get_fp_value(fp_checks,'error')
alias_data.d9.val = format_checks(alias_api, labels)
alias_data.d10.val = format_response_groups(alias_api,labels,'response_groups')
alias_data.d11.val = format_effort_ratings(alias_api,labels,'effort_ratings')
alias_data.d12.val = alias_api.r.get('typing_url', 'None')

# Add question-specific data to hidden rows of tracked questions
for label in labels:
	flag_array = alias_api.r.get('checks',{}).get(label,[])
	flag_str = checks_array_to_string(flag_array)
	allQuestions[label].d1.val = flag_str != "None"
	allQuestions[label].d2.val = flag_str
	allQuestions[label].d3.val = alias_api.r.get('response_groups',{}).get(label,0)
	allQuestions[label].d4.val = alias_api.r.get('effort_ratings',{}).get(label,'NA')
</exec>

<text 
  label="alias_data"
  optional="0"
  size="25"
  translateable="0"
  where="execute,survey,report">
  <title>roundtable data holder</title>
  <row label="d1">alias-flagged</row>
  <row label="d2">alias-num_checks_failed</row>
  <row label="d3">alias-model</row>
  <row label="d4">alias-error</row>
  <row label="d5">alias-problem</row>
  <row label="d6">alias-fp-device_id</row>
  <row label="d7">alias-fp-duplicate</row>
  <row label="d8">alias-fp-error</row>
  <row label="d9">alias-oe-checks</row>
  <row label="d10">alias-oe-response_groups</row>
  <row label="d11">alias-oe-effort_scores</row>
  <row label="d12">alias-oe-typing_url</row>
  <style name="question.after" wrap="ready"><![CDATA[
// For debugging and test mode
    sessionStorage.removeItem('client_dict');
    sessionStorage.removeItem('initialized');
    sessionStorage.removeItem('fingerprint_id');
]]></style>
</text>

The Alias response (both open-end and fingerprinting checks) will now be stored in your survey data in the alias_data question - note that the flags, response groups, and effort scores for each tracked open-end question will also be stored in row variables next to the response.

Example XML Script

Here is a example of a simple survey with all of the Alias code integrated. Make sure to set your API key for the script to work when testing:

<?xml version="1.0" encoding="UTF-8"?>
<survey 
  alt="roundtable-example-survey"
  autosaveKey="tsid"
  browserDupes="safe"
  builder:wizardCompleted="1"
  builderCompatible="1"
  compat="153"
  delphi="1"
  deviceNotAllowedMessage="Please enter the survey using one of the following devices: smartphone, tablet or desktop."
  displayOnError="all"
  extraVariables="source,record,decLang,list,userAgent,c,branding,is_test,survey_id"
  fir="on"
  html:showNumber="0"
  mobile="compat"
  mobileDevices="smartphone,tablet,desktop"
  name="Survey"
  remerged="20240924_19:33"
  secure="1"
  setup="term,decLang,quota,time"
  spssSimpleCheckbox="1"
  ss:disableBackButton="0"
  ss:enableNavigation="1"
  ss:hideProgressBar="0"
  ss:listDisplay="1"
  state="testing"
  version="15">

<suspend/>

<exec>
#intialize Roundtable.ai variables
survey_id = gv.survey.root.alt
api_key = ''
p.client_dict = {'responses': {}, 'questions': {}, 'question_histories': {}, 'fingerprint_id': '', 'survey_id': '', 'participant_id': '', 'num_non_empty': 0, 'question_labels': []}
</exec>

<text 
  label="q1"
  size="25">
  <title>What do you do first in the morning?</title>
  <comment>Be specific</comment>
  <row label="response"/>
  <row label="d1" cond="False">alias-flagged</row>
  <row label="d2" cond="False">alias-checks</row>
  <row label="d3" cond="False">alias-group</row>
  <row label="d4" cond="False">alias-effort</row>
  <style name="question.after"><![CDATA[
<script src='https://roundtable.ai/js/forsta-tracker-v2.js'></script>
]]></style>
  <style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
    const label = q['label']
    const oes = document.querySelectorAll('input[type="text"], textarea');
    oes.forEach(el => {
      el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
      el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
    });
    initializeClientDict(label, q);
]]></style>
</text>

<suspend/>

<textarea 
  label="q2"
  height="10"
  width="50">
  <title>Tell me about yourself</title>
  <comment>Be specific</comment>
  <row label="response"/>
  <row label="d1" cond="False">alias-flagged</row>
  <row label="d2" cond="False">alias-checks</row>
  <row label="d3" cond="False">alias-group</row>
  <row label="d4" cond="False">alias-effort</row>
  <style name="question.after"><![CDATA[
<script src='https://roundtable.ai/js/forsta-tracker-v2.js'></script>
]]></style>
  <style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
    const label = q['label']
    const oes = document.querySelectorAll('input[type="text"], textarea');
    oes.forEach(el => {
      el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
	    el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
	  });
	  initializeClientDict(label, q);
]]></style>
</textarea>

<suspend/>

<exec>
survey_id = gv.survey.root.alt
api_key = '' # Set your API key here
p.headers = {'api_key': api_key, 'Content-Type': 'application/json'}
p.data = {'responses': p.client_dict['responses'], 'questions': p.client_dict['questions'],'question_histories': p.client_dict['question_histories'], 'fingerprint_id':  p.client_dict['fingerprint_id'], 'survey_id': survey_id, 'participant_id': uuid }
</exec>

<logic label="alias_api" cond="p.client_dict['num_non_empty'] != 0" api:data="p.data" api:headers="p.headers" api:method="POST" api:url="https://api.roundtable.ai/alias/v020" uses="api.1"/>

<exec>
# Define functions
def checks_array_to_string(array):
    return ', '.join(map(str, array)) if array else "None"

def format_checks(response, keys):
    return {key: checks_array_to_string(response.r.get('checks', {}).get(key, [])) for key in keys}

def format_response_groups(response, keys,type):
    return {key: response.r.get(type, {}).get(key, 0) for key in keys}
    
def format_effort_ratings(response, keys,type):
    return {key: response.r.get(type, {}).get(key, 'NA') for key in keys}
    
def get_fp_value(fp_dict, field):
    if not isinstance(fp_dict, dict):
        return "None"
    if field not in fp_dict:
        return "None"
    value = fp_dict.get(field)
    return "None" if value is None else value
    
def get_problem(api_response):
    problem = api_response.r.get('problem', "None")
    return problem if isinstance(problem, basestring) and problem else "None"
    

# Add all API data to hidden question
labels = p.client_dict['question_labels']
fp_checks = alias_api.r.get('fingerprint_checks', {})
alias_data.d1.val = alias_api.r.get('flagged', False)
alias_data.d2.val = alias_api.r.get('num_checks_failed', 0)
alias_data.d3.val = alias_api.r.get('model', 'API not called')
alias_data.d4.val = alias_api.r.get('error', False)
alias_data.d5.val = get_problem(alias_api)
alias_data.d6.val = get_fp_value(fp_checks,'participant_id')
alias_data.d7.val = get_fp_value(fp_checks,'duplicate')
alias_data.d8.val = get_fp_value(fp_checks,'error')
alias_data.d9.val = format_checks(alias_api, labels)
alias_data.d10.val = format_response_groups(alias_api,labels,'response_groups')
alias_data.d11.val = format_effort_ratings(alias_api,labels,'effort_ratings')
alias_data.d12.val = alias_api.r.get('typing_url', 'None')

# Add question-specific data to hidden rows of tracked questions
for label in labels:
	flag_array = alias_api.r.get('checks',{}).get(label,[])
	flag_str = checks_array_to_string(flag_array)
	allQuestions[label].d1.val = flag_str != "None"
	allQuestions[label].d2.val = flag_str
	allQuestions[label].d3.val = alias_api.r.get('response_groups',{}).get(label,0)
	allQuestions[label].d4.val = alias_api.r.get('effort_ratings',{}).get(label,'NA')
</exec>

<text 
  label="alias_data"
  optional="0"
  size="25"
  translateable="0"
  where="execute,survey,report">
  <title>roundtable data holder</title>
  <row label="d1">alias-flagged</row>
  <row label="d2">alias-num_checks_failed</row>
  <row label="d3">alias-model</row>
  <row label="d4">alias-error</row>
  <row label="d5">alias-problem</row>
  <row label="d6">alias-fp-device_id</row>
  <row label="d7">alias-fp-duplicate</row>
  <row label="d8">alias-fp-error</row>
  <row label="d9">alias-oe-checks</row>
  <row label="d10">alias-oe-response_groups</row>
  <row label="d11">alias-oe-effort_scores</row>
  <row label="d12">alias-oe-typing_url</row>
  <style name="question.after" wrap="ready"><![CDATA[
// For debugging and test mode
    sessionStorage.removeItem('client_dict');
    sessionStorage.removeItem('initialized');
    sessionStorage.removeItem('fingerprint_id');
]]></style>
</text>

<suspend/>
</survey>