Skip to content

Commit

Permalink
Merge pull request #72 from beckn/add_search_along_a_route
Browse files Browse the repository at this point in the history
Add search along a route
  • Loading branch information
mayurvir authored Apr 8, 2024
2 parents 6bd07ee + 52a097f commit 10463d2
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 46 deletions.
12 changes: 2 additions & 10 deletions config/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,15 @@
"version": "1.1.0",
"policies": {
"domains": {
"uei:charging": {
"tags": [
{ "vehicle-type": { "enum": ["2-wheeler", "4-wheeler"]}},
{ "connector-type": { "enum": ["CCS", "CHAdeMo"]}}
],
"uei:charging": {
"rules": [
"item.descriptor should not be used in search intent for this domain",
"search should have fulfillment for this domain. fulfillment should only contain location for this domain.",
"fulfillment should contain only 1 stop for this domain",
"If a route polygon has been shared, fulfillment.stops[i].location should have the polygon field."
]
},
"hospitality": {
"tags": [
{ "pet-friendly": { "enum": ["yes", "no"]}},
{ "ev-charging": { "enum": ["yes", "no"]}},
{ "accomodation-type“": { "enum": ["campsite", "hotel", "independent-house"]}}
],
"rules": [
"item.descriptor should not be used in search intent for this domain",
"search must have two stops for this domain.",
Expand Down
28 changes: 24 additions & 4 deletions controllers/Bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async function process_text(req, res) {

// Update lat, long
if(req.body.Latitude && req.body.Longitude){
message+=` lat:${req.body.Latitude} long:${req.body.Longitude}`
message+=`Current location is: lat:${req.body.Latitude} long:${req.body.Longitude}`
}

logger.info(`Received message from ${sender}: ${message}. Response format: ${format}`)
Expand Down Expand Up @@ -188,7 +188,8 @@ async function process_text(req, res) {
message: ''
}
if(details_response && details_response.index){
session.selected_route = session.routes[details_response.index];
const index= Math.max(1-details_response.index,0);
session.selected_route = session.routes[index];
const url = `https://www.google.com/maps/dir/${session.selected_route.source_gps.lat},${session.selected_route.source_gps.lng}/${session.selected_route.destination_gps.lat},${session.selected_route.destination_gps.lng}/`;
route_response.message = `Your route has been actived. Here is the link to navigate : ${url}. What do you want to do next?`;

Expand Down Expand Up @@ -293,11 +294,30 @@ async function process_action(action, text, session, sender=null, format='applic
let search_context = session.text;
if(session.profile){
search_context=[
{ role: 'system', content: `User pforile: ${JSON.stringify(session.profile)}`},
{ role: 'system', content: `User profile: ${JSON.stringify(session.profile)}`},
...search_context
]
}
const message = await ai.get_beckn_message_from_text(text, search_context, beckn_context.domain);

// check if user trying to search along the route
const desired_struture = {
search_on_route_yn: 'boolean depending upon whether user is trying to search along a route.',
current_location: 'some location'
}
const details_response = await ai.get_details_by_description(`Find if user is trying to search along a route : ${text}`, session.text, JSON.stringify(desired_struture));
if(details_response.search_on_route_yn){
if(details_response.current_location){
const gps = await mapService.lookupGps(details_response.current_location);
if(gps)
text+= `\nMy current location is : ${JSON.stringify(gps)}`
}
else{
// ask for location.
}

}

const message = await ai.get_beckn_message_from_text(text, search_context, beckn_context.domain, session.selected_route?.overview_polyline?.points);
request = {
status: true,
data:{
Expand Down
16 changes: 14 additions & 2 deletions services/AI.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class AI {
return action_response;
}

async get_beckn_message_from_text(instruction, context=[], domain='') {
async get_beckn_message_from_text(instruction, context=[], domain='', polygon=null) {
logger.info(`Getting beckn message from instruction : ${instruction}, for domain : ${domain}`)
let domain_context = [], policy_context = [];
if(domain && domain!='') {
Expand All @@ -294,7 +294,7 @@ class AI {
]
}
}

const messages = [
...policy_context,
...domain_context,
Expand Down Expand Up @@ -324,6 +324,18 @@ class AI {
});
const responseMessage = JSON.parse(response.choices[0].message?.tool_calls[0]?.function?.arguments) || null;
logger.info(`Got beckn message from instruction : ${JSON.stringify(responseMessage)}`);
if(this.action?.action=='search' && responseMessage?.intent?.fulfillment?.stops[0]?.location){
if(polygon){
responseMessage.intent.fulfillment.stops[0].location.polygon = polygon;
const route_image = `https://maps.googleapis.com/maps/api/staticmap?size=300x300&path=color:%231d3b65|weight:5|enc:${polygon}&key=${process.env.GOOGLE_MAPS_API_KEY}`;
logger.info(`Map url for request : ${route_image}`)
// temp
delete responseMessage.intent.fulfillment.stops[0].location.gps;
}
else delete responseMessage.intent.fulfillment.stops[0].location.polygon;

}

return responseMessage
}
catch(e){
Expand Down
16 changes: 13 additions & 3 deletions services/MapService.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,24 @@ class MapsService {
}
else{
// generate routes
const routes = await this.getRoutes(source_gps, destination_gps);
const routes = await this.getRoutes(`${source_gps.lat},${source_gps.lng}`, `${destination_gps.lat},${destination_gps.lng}`);
response.data.routes = routes.map(route=>{
return {
...route,
overview_polyline: route.overview_polyline,
summary: route.summary,
source_gps: source_gps,
destination_gps: destination_gps
}
})

let polygon_path = '';
routes.forEach((route, index) => {
polygon_path+=`&path=color:${this.get_random_color()}|weight:${5-index}|enc:${route.overview_polyline.points}`;
})

const route_image = `https://maps.googleapis.com/maps/api/staticmap?size=300x300${polygon_path}&key=${process.env.GOOGLE_MAPS_API_KEY}`;
logger.info(`Map url :${route_image}`)

response.data.routes_formatted = {
"description": `these are the various routes that you can take. Which one would you like to select:`,
"routes": response.data.routes.map((route, index) => `Route ${index+1}: ${route.summary}`)
Expand All @@ -110,7 +120,7 @@ class MapsService {
}
}

logger.info(`Generated routes response : ${JSON.stringify(response, null, 2)}`);
// logger.info(`Generated routes response : ${JSON.stringify(response, null, 2)}`);
return response;
}
}
Expand Down
11 changes: 11 additions & 0 deletions tests/apis/agent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,5 +264,16 @@ describe.only('test cases for generating routes and selecting a route', ()=>{

})

it('should search along a route.', async () => {
const ask = "I'm looking for some ev chargers along my route. Im' currently near Casper.";
const response = await request(app).post('/webhook').send({
"From": process.env.TEST_RECEPIENT_NUMBER,
"Body": ask
})
logger.info(JSON.stringify(response.text, null, 2));
expect(response.status).to.be.eq(200)

})


})
67 changes: 41 additions & 26 deletions tests/unit/controllers/bot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import AI from '../../../services/AI.js'
import MapsService from '../../../services/MapService.js'
import logger from '../../../utils/logger.js'
import ActionService from '../../../services/Actions.js'
import DBService from '../../../services/DBService.js'
const expect = chai.expect
const mapService = new MapsService()
const ai = new AI();
const actionsService = new ActionService()

describe.only('Test cases for AI', () => {
describe('Test cases for AI', () => {
it('Should return message with location polygon', async () => {

const source_gps = await mapService.lookupGps('Denver');
const destination_gps = await mapService.lookupGps('Yellowstone national park');

// generate routes
const routes = await mapService.getRoutes(source_gps, destination_gps);

const context=[
{ role : 'user', content: `Selected route polygon is : ${routes[0].overview_polyline.points}`}
]
Expand All @@ -29,12 +30,12 @@ describe.only('Test cases for AI', () => {
expect(response.intent).to.have.property('fulfillment');
expect(response.intent.fulfillment.stops[0].location).to.have.property('polygon');
})


})


describe.only('Test cases for Google maps', () => {
describe('Test cases for Google maps', () => {
it.skip('Should Render a static map image based on source and destination', async ()=>{

const source ='37.422391,-122.084845';
Expand All @@ -46,19 +47,19 @@ describe.only('Test cases for Google maps', () => {
logger.info(`route_image: ${route_image}`);
expect(routes).to.be.an('array');
})

it.skip('Should calculate route between an origin and a destination', async ()=>{

const source_gps = await mapService.lookupGps('Denver');
const destination_gps = await mapService.lookupGps('Yelllowstone national park');

const directions = `https://www.google.com/maps/dir/${source_gps.lat},${source_gps.lng}/${destination_gps.lat},${destination_gps.lng}/`;
logger.info(`route_image: ${directions}`);
expect(source_gps).to.be.an('object');
expect(source_gps).to.have.property('lat');
expect(source_gps).to.have.property('lng');
})

it('It should take a trip plannign input and generate static route image and directions link.', async () => {
const ask = "Can you plean a trip from Denver to Yellowstone national park?";

Expand All @@ -67,35 +68,35 @@ describe.only('Test cases for Google maps', () => {
'source': 'SOURCE_LOCATION',
'destination': 'DESTINATION_LOCATION'
}

const details = await ai.get_details_by_description(ask, [], JSON.stringify(format));
expect(details).to.have.property('source');
expect(details).to.have.property('destination');

logger.info(JSON.stringify(details, null, 2));

// Get gps for source and destination

const source_gps = await mapService.lookupGps(details.source);
const destination_gps = await mapService.lookupGps(details.destination);

// generate routes
const routes = await mapService.getRoutes(source_gps, destination_gps);

// Selected route
const selected_route = 0;

// generate image and directions
const directions = `https://www.google.com/maps/dir/${source_gps.lat},${source_gps.lng}/${destination_gps.lat},${destination_gps.lng}/`;
const route_image = `https://maps.googleapis.com/maps/api/staticmap?size=300x300&path=enc:${routes[selected_route].overview_polyline.points}&key=${process.env.GOOGLE_MAPS_API_KEY}`;


await actionsService.send_message(process.env.TEST_RECEPIENT_NUMBER, `Here are the directions: ${directions}`); // should also pass the route image, its correctly throwing an error.
logger.info(`directions: ${directions}`);
logger.info(`route_image: ${route_image}`);
expect(routes).to.be.an('array').that.is.not.empty;
})

it('It should be able to take a route as an input and turn it into a text response', async () => {
const source ='37.422391,-122.084845';
const destination = '37.411991,-122.079414';
Expand All @@ -108,30 +109,30 @@ describe.only('Test cases for Google maps', () => {
logger.info(`Summary of routes: ${summary}`);
expect(summary).to.be.a('string');
})

it('Should generate a static image with multiple routes for a given source and destination', async ()=>{
const source = await mapService.lookupGps('Denver');
const destination = await mapService.lookupGps('Yelllowstone national park');

let routes = await mapService.getRoutes(source, destination);
let polygon_path = '';
routes.forEach((route, index) => {
polygon_path+=`&path=color:${mapService.get_random_color()}|weight:${5-index}|enc:${route.overview_polyline.points}`;
})

const route_image = `https://maps.googleapis.com/maps/api/staticmap?size=300x300${polygon_path}&key=${process.env.GOOGLE_MAPS_API_KEY}`;
logger.info(`route_image: ${route_image}`);
expect(routes).to.be.an('array');
})

it('Should generate a static image with selected route and multiple', async ()=>{
const source = await mapService.lookupGps('Denver');
const destination = await mapService.lookupGps('Yelllowstone national park');
const items=[
{name: 'Weld county - charging station', gps: '41.142558,-104.839765'},
{name: 'Natrony county - EV', gps: '42.958550,-106.651372'},
]

// gerenate routes
let routes = await mapService.getRoutes(source, destination);
let selected_route = routes[0];
Expand All @@ -142,13 +143,27 @@ describe.only('Test cases for Google maps', () => {
items.forEach((item) => {
markers_path+=`&markers=color:blue|label:${encodeURIComponent(item.name)}|${item.gps}`;
})

markers_path=`&markers=color:red|label:LABEL1|41.142558,-104.839765
&markers=color:blue|label:LABEL2|42.958550,-106.651372`

const route_image = `https://maps.googleapis.com/maps/api/staticmap?size=300x300${polygon_path}${markers_path}&key=${process.env.GOOGLE_MAPS_API_KEY}`;
logger.info(`route_image: ${route_image}`);
expect(routes).to.be.an('array');
})
})

it('Should test if the polygon saved and polygon received are same', async ()=>{
const db = new DBService()
const source ='37.422391,-122.084845';
const destination = '37.411991,-122.079414';

let routes = await mapService.getRoutes(source, destination);
const polygon = routes[0].overview_polyline.points;
await db.update_session('123', {
polygon: polygon
})

await db.get_session('123')
})
})

2 changes: 1 addition & 1 deletion tests/unit/services/ai.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const hotel_session = JSON.parse(readFileSync('./tests/data/sessions/hotel.json'


describe('Test cases for services/ai/get_beckn_action_from_text()', () => {
it('Should return null action when asked a general query', async () => {
it.skip('Should return null action when asked a general query', async () => {
const response = await ai.get_beckn_action_from_text(trip_planning.TRIP_QUERY);
expect(response).to.have.property('action')
expect(response.action).to.be.null
Expand Down

0 comments on commit 10463d2

Please sign in to comment.