Skip to content

Commit

Permalink
Merge pull request #8 from Esri/f/context
Browse files Browse the repository at this point in the history
Added support for persisting context & usage tracking
  • Loading branch information
ajturner committed Mar 2, 2018
2 parents f71e29e + 255387a commit 07560b1
Show file tree
Hide file tree
Showing 17 changed files with 441 additions and 112 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,25 @@ To use the geoenrichment services for population, you will need to add an ArcGIS
1. Click on the gray checkmark to the far right side



### Update Amazon Lambda functions

`npm run create`

_or_ `claudia create --region us-east-1 --api-module bot`

`npm run update`

_or_ `claudia update`

### Get Logs

Requires [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html).

`npm run logs`

_or_ `aws logs filter-log-events --log-group-name /aws/lambda/claudia-test`

### Licensing

Copyright Esri
Expand Down
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
module.exports = require('./lib/sonar.js');
const botBuilder = require('claudia-bot-builder')
const sonar = require('./lib/sonar.js');

module.exports = botBuilder(sonar, { platforms: ['alexa', 'slackSlashCommand', 'facebook'] });
114 changes: 114 additions & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Persist user context during sessions
// handles "question & answer" abilities
// Also logs usage telemetry for UX/VX evaluation & improvements

var AWS = require('aws-sdk');
const tableUser = {
TableName : "sonar-bot-db",
KeySchema: [
{ AttributeName: "UserID", KeyType: "HASH"}
],
AttributeDefinitions: [
{ AttributeName: "UserID", AttributeType: "S" },
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10
}
};
const tableLog = {
TableName : "sonar-bot-log",
KeySchema: [
{ AttributeName: "Key", KeyType: "HASH"}
],
AttributeDefinitions: [
{ AttributeName: "Key", AttributeType: "S" }
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10
}
};;





AWS.config.update({
region: "us-east-1"
// , endpoint: "http://localhost:8000"
});

const dynamodb = new AWS.DynamoDB.DocumentClient();

// Useful for local development
function setupDB() {
const db = new AWS.DynamoDB;


db.createTable(tableUser, function(err, data) {
if (err) {
console.error("Unable to create " + tableUser.TableName + " . Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Created table " + tableUser.TableName + ". Table description JSON:", JSON.stringify(data, null, 2));
}
});

db.createTable(tableLog, function(err, data) {
if (err) {
console.error("Unable to create table " + tableLog.TableName + ". Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Created table " + tableLog.TableName + ". Table description JSON:", JSON.stringify(data, null, 2));
}
});
}

function restoreCtx(sender)//Function will be used later to restore database information for the user that accesses the bot.
{
// console.log("Trying to restore context for sender", sender);

var params = {
TableName: tableUser.TableName,
Key: {
'UserID': sender
}
};

return dynamodb.get(params).promise();
}

function persistCtx(sender, state) // This is used later to repopulate the database with user information
{
// console.log("Persisting context for sender", sender);

var params = {
TableName: tableUser.TableName,
Item:{
'UserID': sender,
'State': state // this is used for persistence. The bot interacts differently with users depending on whether their state is start or fire/trash/map
}
};

return dynamodb.put(params).promise();
}

function trackUsage(key, timestamp, sender, platform, state, request, response) //Very similar to the persist function, but used to log each user interaction
{
var params = {
TableName: tableLog.TableName,
Item:{
'Key': key,
'Timestamp': timestamp,
'UserID': sender,
'State': state,
'Request': request,
'Response': response,
'Platform': platform
}
};

return dynamodb.put(params).promise();
}


module.exports = {setupDB, persistCtx, restoreCtx, trackUsage}
21 changes: 21 additions & 0 deletions lib/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const _dayIndex = {
"mon": "Monday",
"tue": "Tuesday",
"wed": "Wednesday",
"thurs": "Thursday",
"thu": "Thursday",
"fri": "Friday",
"sat": "Saturday",
"sun": "Sunday"
}
function days(input) {

var output = new String(input.toLowerCase())
var lookup = Object.keys(_dayIndex);
for(var i=0; i<lookup.length; i++) {
output = output.replace(lookup[i], _dayIndex[lookup[i]])
}
return output;
}

module.exports = {days}
65 changes: 40 additions & 25 deletions lib/datasets.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
const _datasets = {
"trash": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Public_Service_WebMercator/MapServer/13",
"distance": 1,
"template": "Trash pickup is coming up on {DAY_}"
"dc": {
"trash": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Public_Service_WebMercator/MapServer/13",
"distance": 1,
"template": "Trash pickup is coming up on {DAY_}"
},
"crime": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/FEEDS/MPD/MapServer/8",
"distance": 200,
"template": "The most recent crime was {OFFENSE}"
},
"anc": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Administrative_Other_Boundaries_WebMercator/MapServer/1",
"distance": 1,
"template": "The ANC is {NAME}"
},
"bus stops": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/53",
"distance": 200,
"template": "The nearest stop is at {BSTP_MSG_TEXT}"
},
"notes": {
"url": "https://services.arcgis.com/bkrWlSKcjUDFDtgw/ArcGIS/rest/services/SonarComments/FeatureServer/0",
"distance": 200,
"template": "The nearest notes is {Comments} at {Location}"
}
},
"crime": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/FEEDS/MPD/MapServer/8",
"distance": 200,
"template": "The most recent crime was {OFFENSE}"
},
"anc": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Administrative_Other_Boundaries_WebMercator/MapServer/1",
"distance": 1,
"template": "The ANC is {NAME}"
},
"bus stops": {
"url": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/53",
"distance": 200,
"template": "The nearest stop is at {BSTP_MSG_TEXT}"
},
"notes": {
"url": "https://services.arcgis.com/bkrWlSKcjUDFDtgw/ArcGIS/rest/services/SonarComments/FeatureServer/0",
"distance": 200,
"template": "The nearest notes is {Comments} at {Location}"
"nyc": {
"trash": {
"url": "https://services.arcgis.com/uKN48PkxmWiqJM9q/arcgis/rest/services/DSNY_Frequencies_OFFICIAL/FeatureServer/1",
"distance": 1,
"template": "Trash pickup is {FREQ_REFUSE} and Recycling is {FREQ_RECYCLING}"
},
"zoning": {
"url": "https://services5.arcgis.com/GfwWNkhOj9bNBqoJ/arcgis/rest/services/nyzd/FeatureServer/0",
"distance": 1,
"template": ""
}
}
}
//
Expand All @@ -34,6 +48,7 @@ const _datasets = {
// }


module.exports = function datasets(dataset) {
return _datasets[dataset.toLowerCase()]
module.exports = function datasets(dataset, region) {
console.log("region: " + region)
return _datasets[region][dataset.toLowerCase()]
}
27 changes: 27 additions & 0 deletions lib/device.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Returns tokens and metadata about the Alexa devices

function askPermission() {
return response.card({
type: "AskForPermissionsConsent",
permissions: [ "read::alexa:device:all:address" ] // full address
});
}

function get(request) {

console.log("env 1: " + JSON.stringify(request))
// console.log("env 2: " + JSON.stringify(originalRequest))

var deviceId = request.originalRequest.context.System.device.deviceId
var userId = request.originalRequest.context.System.user.userId

console.log("user: " + userId + " | device: " + deviceId)
console.log("user: " + JSON.stringify(request.originalRequest.context.System.user))

var consentToken = request.originalRequest.context.System.user.permissions.consentToken
console.log("consentToken: " + consentToken)

return {deviceId, userId, consentToken}
}

module.exports = {get, askPermission};
7 changes: 6 additions & 1 deletion lib/directions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var request = require('request-promise')

const directionsUrl = "http://route.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/solve"
const directionsUrl = "https://route.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/solve"

function getRoute(directions) {
var steps = directions.directions[0].features;
Expand All @@ -19,6 +19,11 @@ module.exports = function directions(start, stop, env) {
"f": "json"
}
params.stops = [[start.x, start.y],[stop.x, stop.y]].join(";")
console.log("Bus Stop request " + JSON.stringify(params))
return request({method: 'post', qs: params, uri: directionsUrl, json: true })
.then(getRoute)
.catch(err => {
console.log("Error getting stops")
console.log(err)
})
}
1 change: 1 addition & 0 deletions lib/geolocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function locations(address) {

function geometry(address) {
return locations(address).then(function(locations) {
// console.log("geometry", JSON.stringify(locations[0]))
var coordinates = locations[0].feature.geometry
return coordinates;

Expand Down
2 changes: 1 addition & 1 deletion lib/get_crime.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = function getCrime(address) {
response += " Stay safe."
break;
default:
response += " It's a little scary out there."
response += " It's something to be worried about."
}
return response;
})
Expand Down
21 changes: 18 additions & 3 deletions lib/get_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,38 @@ const geoservice = require('./geoservice')
const directions = require('./directions')
const datasets = require('./datasets')
const error = require('./error')
const convert = require('./convert')

module.exports = function getData(dataset, address, env) {
return geolocation.geometry(address).then(function(location) {
var config = datasets(dataset);

// TODO: use the geocoder response
var region = "dc"
if(address.match(/New York/)) {
region = "nyc"
}
var config = datasets(dataset, region);

var url = config.url;
var geometry = location.x + "," + location.y;
return geoservice().query(url, geometry, config.distance).then(function(layer) {

var response = config.template.replace(/\{(\w*)\}/g, function(m,key) {
return layer.features[0].attributes[key];
var answer = layer.features[0].attributes[key];

return convert.days(answer)
});

if(dataset == "bus stops") {
console.log("Getting Bus stops: " + JSON.stringify(location))
return directions(location, layer.features[0].geometry, env).then(function(steps) {
console.log("Got Bus stops: " + JSON.stringify(steps))
response += "\nHow to get there: " + steps;
return response;
})
}).catch(err => {
console.log("Error Bus stops")
console.log(err)
})
} else {
return response;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/get_enrichment.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ module.exports = function getEnrichment(location, collections, attributes, env)
return request({ method: 'get', url:url + "?" + qs.stringify(params), json: true})
.then(function (body) {
var values = {}
console.log("Enrichment." + JSON.stringify(attributes))
for(var i=0; i<attributes.length;i++) {
values[attributes[i]] = body.results[0].value.FeatureSet[0].features[0].attributes[attributes[i]];
}
return values;
})
.catch(function (message) {
// return "There was an error in Get Enrichment."
console.log("There was an error in Get Enrichment.")
console.log(message)
return Promise.resolve(error());
});
}
5 changes: 4 additions & 1 deletion lib/get_population.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ const getEnrichment = require('./get_enrichment')
const error = require('./error')

module.exports = function getPopulation(location, env) {
return getEnrichment(location, ["KeyGlobalFacts"], ["TOTPOP", "TOTHH"], env).then(function(values) {
return getEnrichment(location, ["KeyGlobalFacts"], ["TOTPOP", "TOTHH"], env)
.then(function(values) {
return " There are " + values["TOTPOP"] + " neighbors living in " + values["TOTHH"] + " households witin 200 meters";

})
.catch(function (message) {
// return "There was an error in Get Population. (" + error + ")";
console.log("Error getting population")
console.log(message)
return Promise.resolve(error())
});
}
2 changes: 1 addition & 1 deletion lib/layer_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ const datasets = require('./datasets')

module.exports = function layerMap(dataset, address) {
return geolocation.geometry(address).then(function(location) {
return webmapUrl(location, {"url": datasets(dataset).url})
return webmapUrl(location, {"url": datasets(dataset, 'dc').url})
});
}
Loading

0 comments on commit 07560b1

Please sign in to comment.