Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTP-888 Refactor mobilityProfile into its own object #200

Merged
merged 10 commits into from
Dec 8, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.auth.Auth0Connection;
import org.opentripplanner.middleware.auth.RequestingUser;
import org.opentripplanner.middleware.models.MobilityProfile;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.utils.JsonUtils;
Expand All @@ -16,9 +17,7 @@
import spark.Request;
import spark.Response;

import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -39,8 +38,6 @@ public class OtpUserController extends AbstractUserController<OtpUser> {
private static final String VERIFY_ROUTE_TEMPLATE = "/:%s/%s/:%s";
/** Regex to check E.164 phone number format per https://www.twilio.com/docs/glossary/what-e164 */
private static final Pattern PHONE_E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
/** Mobility devices used to calculate mobility mode. Keywords are taken from Georgia Tech document. */
private static final Set<String> MOBILITY_DEVICES = Set.of("Device", "MScooter", "WChairE", "WChairM", "Some");

public OtpUserController(String apiPrefix) {
super(apiPrefix, Persistence.otpUsers, OTP_USER_PATH);
Expand All @@ -55,13 +52,13 @@ OtpUser preCreateHook(OtpUser user, Request req) {
Auth0Connection.ensureApiUserHasApiKey(req);
user.applicationId = requestingUser.apiUser.id;
}
user.mobilityMode = calculateMobilityMode(user);
MobilityProfile.updateMobilityMode(user.mobilityProfile);
return super.preCreateHook(user, req);
}

@Override
OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) {
user.mobilityMode = calculateMobilityMode(user);
MobilityProfile.updateMobilityMode(user.mobilityProfile);
return super.preUpdateHook(user, preExistingUser, req);
}

Expand Down Expand Up @@ -181,68 +178,4 @@ public static boolean isPhoneNumberValidE164(String phoneNumber) {
Matcher m = PHONE_E164_PATTERN.matcher(phoneNumber);
return m.matches();
}

/**
* Calculate and return the "mobility mode", a keyword or compound keyword specified by Georgia Tech,
* based on a number {@code OtpUser} fields related to mobility.
* @param user with fields that are consulted to calculate mobility mode
* @return mobility mode as a single string
*/
private static String calculateMobilityMode(OtpUser user) {
// Variable names and the strings we parse are from Georgia Tech document, to facilitate syncing changes.
// The testing for devices and vision in this order are from the same document; note that this means the
// devices tested for later will override the earlier "Temp"orary settings.
String mModeTemp = "None";
String visionTemp = "None";

if (user.mobilityDevices == null) {
user.mobilityDevices = Collections.EMPTY_LIST;
}
if (user.mobilityDevices.isEmpty() || user.mobilityDevices.contains("none")) {
user.mobilityDevices.clear();
} else {
if (user.mobilityDevices.contains("white cane")) {
visionTemp = "Blind";
}
if (user.mobilityDevices.contains("manual walker")
|| user.mobilityDevices.contains("wheeled walker")
|| user.mobilityDevices.contains("cane")
|| user.mobilityDevices.contains("crutches")
|| user.mobilityDevices.contains("stroller")
|| user.mobilityDevices.contains("service animal")) {
mModeTemp = "Device";
}
if (user.mobilityDevices.contains("mobility scooter")) {
mModeTemp = "MScooter";
}
if (user.mobilityDevices.contains("electric wheelchair")) {
mModeTemp = "WChairE";
}
if (user.mobilityDevices.contains("manual wheelchair")) {
mModeTemp = "WChairM";
}

if ("None".equals(mModeTemp) && user.isMobilityLimited) {
mModeTemp = "Some";
}
}

if (visionTemp.isEmpty() && "low-vision".equals(user.visionLimitation)) {
visionTemp = "LowVision";
} else if (visionTemp.isEmpty() && "legally blind".equals(user.visionLimitation)) {
visionTemp = "Blind";
}

// Create combinations for mobility mode and vision
if (Set.of("LowVision", "Blind").contains(visionTemp)) {
if ("None".equals(mModeTemp)) {
return visionTemp;
} else if (MOBILITY_DEVICES.contains(mModeTemp)) {
return mModeTemp + "-" + visionTemp;
}
} else if (MOBILITY_DEVICES.contains(mModeTemp)) {
return mModeTemp;
}
return "None";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.opentripplanner.middleware.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;

/**
* Mobility profile data, to keeps track of values from UI or mobile app and
JymDyerIBI marked this conversation as resolved.
Show resolved Hide resolved
* uses them to maintain a "mobility mode," which are keywords specified in
* the Georgia Tech Mobility Profile Configuration / Logical Flow document.
* <p>
* Provided as part of {@link OtpUser}, example JSON format:
* <code>
* ...
* "mobilityProfile": {
* "isMobilityLimited": true,
* "mobilityDevices": ["service animal", "electric wheelchair"],
* "visionLimitation": "low-vision",
* "mobilityMode": "WChairE-LowVision",
JymDyerIBI marked this conversation as resolved.
Show resolved Hide resolved
* }
* ...
* </code>
*/
public class MobilityProfile implements Serializable {
// Selected mobility mode keywords from Georgia Tech document.
private static final Set<String> MOBILITY_DEVICES = Set.of("Device", "MScooter", "WChairE", "WChairM", "Some");

public enum VisionLimitation {
@JsonProperty("legally-blind") LEGALLY_BLIND,
@JsonProperty("low-vision") LOW_VISION,
@JsonProperty("none") NONE
}

/** Whether the user indicates that their mobility is limited (slower). */
public boolean isMobilityLimited;

/** User may indicate zero or more mobility devices. */
public Collection<String> mobilityDevices = Collections.EMPTY_LIST;

/** Compound keyword that controller calculates from mobility and vision values. */
@JsonIgnore
public String mobilityMode;

/** User may indicate levels of vision limitation. */
public VisionLimitation visionLimitation = VisionLimitation.NONE;


/**
* Construct the mobility mode keyword or compound keyword from fields in
* a mobility profile, and update the mobility profile with it. Follows
* the Georgia Tech Mobility Profile Configuration / Logical Flow document,
* so that the mode is constructed based on specific strings in a specific
* order. The device strings are expected to change on occasion.
* @param mobilityProfile consulted to construct and update mobility mode
*/
public static void updateMobilityMode(MobilityProfile mobilityProfile) {
JymDyerIBI marked this conversation as resolved.
Show resolved Hide resolved
// Variable names and the strings we parse are from Georgia Tech document, to facilitate syncing
// changes. The testing for devices and vision in this order are from the same document; note
// that this means the devices tested for later will override the earlier "Temp"orary settings.
String mModeTemp = "None";
String visionTemp = "None";

// If "none" has been specified at all, we just wipe the mobility devices clear,
// else we look at the mobility devices and settle on the one that is the most involved.
if (mobilityProfile.mobilityDevices.contains("none")) {
mobilityProfile.mobilityDevices.clear();
} else {
if (mobilityProfile.mobilityDevices.contains("white cane")) {
visionTemp = "Blind";
}
if (mobilityProfile.mobilityDevices.contains("manual walker")
|| mobilityProfile.mobilityDevices.contains("wheeled walker")
|| mobilityProfile.mobilityDevices.contains("cane")
|| mobilityProfile.mobilityDevices.contains("crutches")
|| mobilityProfile.mobilityDevices.contains("stroller")
|| mobilityProfile.mobilityDevices.contains("service animal")) {
mModeTemp = "Device";
}
if (mobilityProfile.mobilityDevices.contains("mobility scooter")) {
mModeTemp = "MScooter";
}
if (mobilityProfile.mobilityDevices.contains("electric wheelchair")) {
mModeTemp = "WChairE";
}
if (mobilityProfile.mobilityDevices.contains("manual wheelchair")) {
mModeTemp = "WChairM";
}

if ("None".equals(mModeTemp) && mobilityProfile.isMobilityLimited) {
mModeTemp = "Some";
}
}

if (MobilityProfile.VisionLimitation.LOW_VISION == mobilityProfile.visionLimitation) {
visionTemp = "LowVision";
} else if (MobilityProfile.VisionLimitation.LEGALLY_BLIND == mobilityProfile.visionLimitation) {
visionTemp = "Blind";
}

// Create combinations for mobility mode and vision
if (Set.of("LowVision", "Blind").contains(visionTemp)) {
if ("None".equals(mModeTemp)) {
mobilityProfile.mobilityMode = visionTemp;
} else if (MOBILITY_DEVICES.contains(mModeTemp)) {
mobilityProfile.mobilityMode = mModeTemp + "-" + visionTemp;
}
} else if (MOBILITY_DEVICES.contains(mModeTemp)) {
mobilityProfile.mobilityMode = mModeTemp;
}
mobilityProfile.mobilityMode = "None";
JymDyerIBI marked this conversation as resolved.
Show resolved Hide resolved
}
}
14 changes: 2 additions & 12 deletions src/main/java/org/opentripplanner/middleware/models/OtpUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,11 @@ public enum Notification {
/** Whether the user has consented to terms of use. */
public boolean hasConsentedToTerms;

/** Whether the user has indicated that their mobility is limited (slower). */
public boolean isMobilityLimited;

/** Whether the phone number has been verified. */
public boolean isPhoneNumberVerified;

/** User may have indicated zero or more mobility devices. */
public Collection<String> mobilityDevices;

/** Compound keyword that controller calculates from mobility and vision values. */
@JsonIgnore
public String mobilityMode;

/** One of "low-vision" "legally blind" "none" */
public String visionLimitation;
/** Mobility profile. */
public MobilityProfile mobilityProfile;

/**
* Notification preferences for this user
Expand Down
29 changes: 19 additions & 10 deletions src/main/resources/latest-spark-swagger-output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2550,18 +2550,10 @@ definitions:
type: "boolean"
hasConsentedToTerms:
type: "boolean"
isMobilityLimited:
type: "boolean"
isPhoneNumberVerified:
type: "boolean"
mobilityDevices:
type: "array"
items:
type: "string"
mobilityMode:
type: "string"
visionLimitation:
type: "string"
mobilityProfile:
$ref: "#/definitions/MobilityProfile"
notificationChannel:
type: "array"
items:
Expand All @@ -2588,6 +2580,23 @@ definitions:
type: "boolean"
applicationId:
type: "string"
MobilityProfile:
type: "object"
properties:
isMobilityLimited:
type: "boolean"
mobilityDevices:
type: "array"
items:
type: "string"
mobilityMode:
type: "string"
visionLimitation:
type: "string"
enum:
- "LEGALLY_BLIND"
- "LOW_VISION"
- "NONE"
GetUsageResult:
type: "object"
properties:
Expand Down
Loading