Skip to content

Commit

Permalink
Merge pull request #83 from vikram31291/vikram/multipleStops
Browse files Browse the repository at this point in the history
Fix #53 - Disambiguate Stop Codes
  • Loading branch information
barbeau authored Sep 3, 2016
2 parents 5bf1d3d + 5e7988a commit 26818ea
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 12 deletions.
6 changes: 6 additions & 0 deletions interaction model/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.YesIntent"
},
{
"intent": "AMAZON.NoIntent"
},
{
"intent": "SetCityIntent",
"slots": [
Expand Down
124 changes: 112 additions & 12 deletions src/main/java/org/onebusaway/alexa/AnonSpeechlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import javax.annotation.Resource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -93,6 +95,8 @@ public SpeechletResponse onLaunch(LaunchRequest launchRequest, Session session)
public SpeechletResponse onIntent(IntentRequest request, Session session)
throws SpeechletException {
Intent intent = request.getIntent();
AskState askState = getAskState(session);
session.setAttribute(ASK_STATE, AskState.NONE.toString());
if (HELP.equals(intent.getName()) ||
GET_ARRIVALS.equals(intent.getName()) ||
GET_STOP_NUMBER.equals(intent.getName()) ||
Expand Down Expand Up @@ -160,6 +164,10 @@ public SpeechletResponse onIntent(IntentRequest request, Session session)
return setStopNumber(
stopNumberStr,
session);
} else if (YES.equals(intent.getName())) {
return handleYesIntent(session, askState);
} else if (NO.equals(intent.getName())) {
return handleNoIntent(session, askState);
} else if (STOP.equals(intent.getName())) {
return goodbye();
} else {
Expand All @@ -172,6 +180,74 @@ public void onSessionEnded(SessionEndedRequest sessionEndedRequest, Session sess

}

private SpeechletResponse handleYesIntent(Session session, AskState askState) throws SpeechletException {
if (askState == AskState.VERIFYSTOP) {
return handleVerifyStopResponse(session, true /*stopFound*/);
}

log.error("Received yes intent without a question.");
return askForCity(Optional.empty());
}

private SpeechletResponse handleNoIntent(Session session, AskState askState) throws SpeechletException {
if (askState == AskState.VERIFYSTOP) {
return handleVerifyStopResponse(session, false /*stopFound*/);
}

log.error("Received no intent without a question.");
return askForCity(Optional.empty());
}

private AskState getAskState(Session session) {
AskState askState = AskState.NONE;
String savedAskState = (String)session.getAttribute(ASK_STATE);
if (savedAskState != null) {
askState = AskState.valueOf(savedAskState);
}
return askState;
}

private SpeechletResponse handleVerifyStopResponse(Session session, boolean stopFound) throws SpeechletException {
ArrayList<ObaStop> stops = (ArrayList<ObaStop>) session.getAttribute(FOUND_STOPS);
if (stops != null) {
if (stopFound && stops.size() > 0) {
String cityName = (String)session.getAttribute(CITY_NAME);

Optional<Location> location = googleMaps.geocode(cityName);
if (!location.isPresent()) {
return askForCity(Optional.of(cityName));
}

Optional<ObaRegion> region = null;
try {
region = obaClient.getClosestRegion(location.get());
} catch (IOException e) {
log.error("Error getting closest region: " + e.getMessage());
return askForCity(Optional.of(cityName));
}

ObaUserClient obaUserClient;
try {
obaUserClient = obaClient.withObaBaseUrl(region.get().getObaBaseUrl());
} catch (URISyntaxException e) {
log.error("ObaBaseUrl " + region.get().getObaBaseUrl() + " for " + region.get().getName()
+ " is invalid: " + e.getMessage());
// Region didn't have a valid URL - ask again and hopefully we find a different one
return askForCity(Optional.of(cityName));
}

LinkedHashMap<String, String> stopData = (LinkedHashMap<String, String>) stops.get(0);
return createOrUpdateUser(session, cityName, stopData.get("id"), stopData.get("stopCode"), region.get(), obaUserClient);
} else if (!stopFound && stops.size() > 1) {
stops.remove(0);
session.setAttribute(FOUND_STOPS, stops);
return askToVerifyStop(session, null);
}
}

return reaskForStopNumber();
}

private OnboardState getOnboardState(Session session) {
if (session.getAttribute(CITY_NAME) != null) {
return OnboardState.OnlyCity;
Expand Down Expand Up @@ -234,30 +310,54 @@ private SpeechletResponse setStopNumber(String spokenStopNumber, Session session
if (searchResults.length == 0) {
return reaskForStopNumber();
} else if (searchResults.length > 1) {
PlainTextOutputSpeech out = new PlainTextOutputSpeech();
out.setText(String.format("I expected to find exactly one stop for the number " +
"you gave, but instead found %d. This is not yet supported.",
searchResults.length));
return SpeechletResponse.newTellResponse(out);
return askToVerifyStop(session, searchResults);
} else {
// Perfect!
return createOrUpdateUser(session, cityName, searchResults[0], region.get(), obaUserClient);
}
}

private SpeechletResponse askToVerifyStop(Session session, ObaStop[] stops) {
PlainTextOutputSpeech askForVerifyStop = new PlainTextOutputSpeech();
String stopName = "";

if (stops != null && stops.length > 0) {
session.setAttribute(FOUND_STOPS, stops);
stopName = stops[0].getName();
askForVerifyStop.setText(String.format("We found %d stops associated with the stop number. Did you mean the %s stop?", stops.length, stopName));
} else {
ArrayList<ObaStop> foundStops = (ArrayList<ObaStop>) session.getAttribute(FOUND_STOPS);
LinkedHashMap<String, String> stopData = (LinkedHashMap<String, String>) foundStops.get(0);
stopName = stopData.get("name");
askForVerifyStop.setText(String.format("Ok, what about the %s stop?", stopName));
}

PlainTextOutputSpeech verifyStopSpeech = new PlainTextOutputSpeech();
verifyStopSpeech.setText(String.format("Did you mean the %s stop?", stopName));
Reprompt verifyStopReprompt = new Reprompt();
verifyStopReprompt.setOutputSpeech(verifyStopSpeech);

session.setAttribute(ASK_STATE, AskState.VERIFYSTOP.toString());
return SpeechletResponse.newAskResponse(askForVerifyStop, verifyStopReprompt);
}

private SpeechletResponse createOrUpdateUser(Session session,
String cityName,
ObaStop stop,
ObaRegion region,
ObaRegion region,
ObaUserClient obaUserClient) throws SpeechletException {
return createOrUpdateUser(session, cityName, stop.getId(), stop.getStopCode(), region, obaUserClient);
}

private SpeechletResponse createOrUpdateUser(Session session, String cityName, String stopId, String stopCode, ObaRegion region, ObaUserClient obaUserClient) throws SpeechletException {
log.debug(String.format(
"Crupdating user with city %s and stop ID %s, code %s, regionId %d, regionName %s, obaBaseUrl %s.",
cityName, stop.getId(), stop.getStopCode(), region.getId(), region.getName(), region.getObaBaseUrl()));
"Crupdating user with city %s and stop ID %s, code %s, regionId %d, regionName %s, obaBaseUrl %s.",
cityName, stopId, stopCode, region.getId(), region.getName(), region.getObaBaseUrl()));

ObaArrivalInfoResponse response;
try {
response = obaUserClient.getArrivalsAndDeparturesForStop(
stop.getId(),
stopId,
ARRIVALS_SCAN_MINS
);
} catch (IOException e) {
Expand All @@ -270,13 +370,13 @@ private SpeechletResponse createOrUpdateUser(Session session,
String outText = String.format("Ok, your stop number is %s in the %s region. " +
"Great. I am ready to tell you about the next bus. You can always ask me for arrival times " +
"by saying 'Alexa, open One Bus Away'. Right now, %s",
stop.getStopCode(), region.getName(), arrivalInfoText);
stopCode, region.getName(), arrivalInfoText);

Optional<ObaUserDataItem> optUserData = obaDao.getUserData(session);
if (optUserData.isPresent()) {
ObaUserDataItem userData = optUserData.get();
userData.setCity(cityName);
userData.setStopId(stop.getId());
userData.setStopId(stopId);
userData.setRegionId(region.getId());
userData.setRegionName(region.getName());
userData.setObaBaseUrl(region.getObaBaseUrl());
Expand All @@ -287,7 +387,7 @@ private SpeechletResponse createOrUpdateUser(Session session,
ObaUserDataItem userData = new ObaUserDataItem(
session.getUser().getUserId(),
cityName,
stop.getId(),
stopId,
region.getId(),
region.getName(),
region.getObaBaseUrl(),
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/onebusaway/alexa/AuthedSpeechlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public SpeechletResponse onIntent(final IntentRequest request,
return getCity();
} else if (SET_STOP_NUMBER.equals(intent.getName())) {
return anonSpeechlet.onIntent(request, session);
} else if (YES.equals(intent.getName())) {
return anonSpeechlet.onIntent(request, session);
} else if (NO.equals(intent.getName())) {
return anonSpeechlet.onIntent(request, session);
} else if (GET_STOP_NUMBER.equals(intent.getName())) {
return getStopDetails();
} else if (GET_ARRIVALS.equals(intent.getName())) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/onebusaway/alexa/ObaIntent.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ public class ObaIntent {
public static final String HELP = "AMAZON.HelpIntent";
public static final String REPEAT = "AMAZON.RepeatIntent";
public static final String STOP = "AMAZON.StopIntent";
public static final String YES = "AMAZON.YesIntent";
public static final String NO = "AMAZON.NoIntent";
}
7 changes: 7 additions & 0 deletions src/main/java/org/onebusaway/alexa/SessionAttribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ public class SessionAttribute {
public static final String OBA_BASE_URL = "obaBaseUrl";
public static final String PREVIOUS_RESPONSE = "previousResponse";
public static final String LAST_ACCESS_TIME = "lastAccessTime";
public static final String FOUND_STOPS = "foundStops";
public static final String ASK_STATE = "askState";

public enum AskState {
NONE,
VERIFYSTOP
}
}
49 changes: 49 additions & 0 deletions src/test/java/org/onebusaway/alexa/AuthedSpeechletTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,55 @@ public void noUpcomingArrivals() throws SpeechletException, IOException {
+ ARRIVALS_SCAN_MINS + " minutes."));
}

@Test
public void setStopWithDuplicateIds() throws SpeechletException, IOException {
String newStopCode = "2340";

// Mock persisted user data for Test Region 2
testUserData.setUserId(TEST_USER_ID);
testUserData.setStopId("6497");
testUserData.setCity(TEST_REGION_2.getName());
testUserData.setRegionName(TEST_REGION_2.getName());
testUserData.setRegionId(TEST_REGION_2.getId());
testUserData.setObaBaseUrl(TEST_REGION_2.getObaBaseUrl());

// Mock stop info
ObaStop[] obaStopsArray = new ObaStop[2];
obaStopsArray[0] = obaStop;
obaStopsArray[1] = obaStop;

new Expectations() {{
googleMaps.geocode(TEST_REGION_2.getName());
Location l = new Location("test");
l.setLatitude(27.9681);
l.setLongitude(-82.4764);
result = Optional.of(l);

obaUserClient.getStopFromCode(l, newStopCode); result = obaStopsArray;
obaStop.getName(); result = "stop name";
obaClient.getClosestRegion(l); result = Optional.of(TEST_REGION_2);
}};

HashMap<String, Slot> slots = new HashMap<>();
slots.put(STOP_NUMBER, Slot.builder()
.withName(STOP_NUMBER)
.withValue(newStopCode).build());
SpeechletResponse sr = authedSpeechlet.onIntent(
IntentRequest.builder()
.withRequestId("test-request-id")
.withIntent(
Intent.builder()
.withName(SET_STOP_NUMBER)
.withSlots(slots)
.build()
)
.build(),
session
);
String spoken = ((PlainTextOutputSpeech)sr.getOutputSpeech()).getText();
assertThat(spoken, startsWith("We found 2 stops associated with the stop number."));
}

@Test
public void goodbye() throws SpeechletException, IOException {
TestUtil.assertGoodbye(authedSpeechlet, session);
Expand Down

0 comments on commit 26818ea

Please sign in to comment.