From b145e757c01f57b7c30071c68075f4895a30cd91 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Thu, 30 Nov 2023 11:59:39 -0800 Subject: [PATCH 01/10] OTP-888 Refactor mobilityProfile into its own object --- .../controllers/api/OtpUserController.java | 61 +++++++++++-------- .../middleware/models/MobilityProfile.java | 31 ++++++++++ .../middleware/models/OtpUser.java | 14 +---- .../latest-spark-swagger-output.yaml | 29 ++++++--- 4 files changed, 87 insertions(+), 48 deletions(-) create mode 100644 src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java index 230190c05..90c0f8447 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java @@ -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; @@ -55,13 +56,19 @@ OtpUser preCreateHook(OtpUser user, Request req) { Auth0Connection.ensureApiUserHasApiKey(req); user.applicationId = requestingUser.apiUser.id; } - user.mobilityMode = calculateMobilityMode(user); + if (user.mobilityProfile == null) { + user.mobilityProfile = new MobilityProfile(); + } + user.mobilityProfile.mobilityMode = calculateMobilityMode(user); return super.preCreateHook(user, req); } @Override OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) { - user.mobilityMode = calculateMobilityMode(user); + if (user.mobilityProfile == null) { + user.mobilityProfile = new MobilityProfile(); + } + user.mobilityProfile.mobilityMode = calculateMobilityMode(user); return super.preUpdateHook(user, preExistingUser, req); } @@ -184,8 +191,8 @@ public static boolean isPhoneNumberValidE164(String phoneNumber) { /** * 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 + * based on a number of {@code OtpUser} moblity profile fields. + * @param user whose mobility profile is consulted to calculate mobility mode * @return mobility mode as a single string */ private static String calculateMobilityMode(OtpUser user) { @@ -195,42 +202,44 @@ private static String calculateMobilityMode(OtpUser user) { 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")) { + if (user.mobilityProfile.mobilityDevices == null) { + user.mobilityProfile.mobilityDevices = Collections.EMPTY_LIST; + } + if (user.mobilityProfile.mobilityDevices.isEmpty() || user.mobilityProfile.mobilityDevices.contains("none")) { + user.mobilityProfile.mobilityDevices.clear(); + } else { + if (user.mobilityProfile.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")) { + if (user.mobilityProfile.mobilityDevices.contains("manual walker") + || user.mobilityProfile.mobilityDevices.contains("wheeled walker") + || user.mobilityProfile.mobilityDevices.contains("cane") + || user.mobilityProfile.mobilityDevices.contains("crutches") + || user.mobilityProfile.mobilityDevices.contains("stroller") + || user.mobilityProfile.mobilityDevices.contains("service animal")) { mModeTemp = "Device"; } - if (user.mobilityDevices.contains("mobility scooter")) { + if (user.mobilityProfile.mobilityDevices.contains("mobility scooter")) { mModeTemp = "MScooter"; } - if (user.mobilityDevices.contains("electric wheelchair")) { + if (user.mobilityProfile.mobilityDevices.contains("electric wheelchair")) { mModeTemp = "WChairE"; } - if (user.mobilityDevices.contains("manual wheelchair")) { + if (user.mobilityProfile.mobilityDevices.contains("manual wheelchair")) { mModeTemp = "WChairM"; } - if ("None".equals(mModeTemp) && user.isMobilityLimited) { + if ("None".equals(mModeTemp) && user.mobilityProfile.isMobilityLimited) { mModeTemp = "Some"; } } - if (visionTemp.isEmpty() && "low-vision".equals(user.visionLimitation)) { - visionTemp = "LowVision"; - } else if (visionTemp.isEmpty() && "legally blind".equals(user.visionLimitation)) { - visionTemp = "Blind"; + if (visionTemp.isEmpty()) { + if (MobilityProfile.VisionLimitation.LOW_VISION == user.mobilityProfile.visionLimitation) { + visionTemp = "LowVision"; + } else if (MobilityProfile.VisionLimitation.LEGALLY_BLIND == user.mobilityProfile.visionLimitation) { + visionTemp = "Blind"; + } } // Create combinations for mobility mode and vision @@ -239,7 +248,7 @@ private static String calculateMobilityMode(OtpUser user) { return visionTemp; } else if (MOBILITY_DEVICES.contains(mModeTemp)) { return mModeTemp + "-" + visionTemp; - } + } } else if (MOBILITY_DEVICES.contains(mModeTemp)) { return mModeTemp; } diff --git a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java new file mode 100644 index 000000000..0f2215d2d --- /dev/null +++ b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java @@ -0,0 +1,31 @@ +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; + +/** + * Mobility profile data + */ +public class MobilityProfile implements Serializable { + 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 mobilityDevices; + + /** Compound keyword that controller calculates from mobility and vision values. */ + @JsonIgnore + public String mobilityMode; + + /** User may indicate levels of vision limitation. */ + public VisionLimitation visionLimitation; +} diff --git a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java index 68d95626b..937fe0fa5 100644 --- a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java +++ b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java @@ -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 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; + /** Mobilty profile. */ + public MobilityProfile mobilityProfile; /** * Notification preferences for this user diff --git a/src/main/resources/latest-spark-swagger-output.yaml b/src/main/resources/latest-spark-swagger-output.yaml index c92ec144a..29d94657d 100644 --- a/src/main/resources/latest-spark-swagger-output.yaml +++ b/src/main/resources/latest-spark-swagger-output.yaml @@ -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: @@ -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: From 1e61ed305440cfc0604c6987cc74033e17792fc3 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 4 Dec 2023 23:45:41 -0800 Subject: [PATCH 02/10] OTP-888 To satisfy review feedback. --- .../controllers/api/OtpUserController.java | 80 +--------------- .../middleware/models/MobilityProfile.java | 93 ++++++++++++++++++- 2 files changed, 91 insertions(+), 82 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java index 90c0f8447..25df5a110 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java @@ -17,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; @@ -40,8 +38,6 @@ public class OtpUserController extends AbstractUserController { 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 MOBILITY_DEVICES = Set.of("Device", "MScooter", "WChairE", "WChairM", "Some"); public OtpUserController(String apiPrefix) { super(apiPrefix, Persistence.otpUsers, OTP_USER_PATH); @@ -56,19 +52,13 @@ OtpUser preCreateHook(OtpUser user, Request req) { Auth0Connection.ensureApiUserHasApiKey(req); user.applicationId = requestingUser.apiUser.id; } - if (user.mobilityProfile == null) { - user.mobilityProfile = new MobilityProfile(); - } - user.mobilityProfile.mobilityMode = calculateMobilityMode(user); + MobilityProfile.updateMobilityMode(user.mobilityProfile); return super.preCreateHook(user, req); } @Override OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) { - if (user.mobilityProfile == null) { - user.mobilityProfile = new MobilityProfile(); - } - user.mobilityProfile.mobilityMode = calculateMobilityMode(user); + MobilityProfile.updateMobilityMode(user.mobilityProfile); return super.preUpdateHook(user, preExistingUser, req); } @@ -188,70 +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 of {@code OtpUser} moblity profile fields. - * @param user whose mobility profile is 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.mobilityProfile.mobilityDevices == null) { - user.mobilityProfile.mobilityDevices = Collections.EMPTY_LIST; - } - if (user.mobilityProfile.mobilityDevices.isEmpty() || user.mobilityProfile.mobilityDevices.contains("none")) { - user.mobilityProfile.mobilityDevices.clear(); - } else { - if (user.mobilityProfile.mobilityDevices.contains("white cane")) { - visionTemp = "Blind"; - } - if (user.mobilityProfile.mobilityDevices.contains("manual walker") - || user.mobilityProfile.mobilityDevices.contains("wheeled walker") - || user.mobilityProfile.mobilityDevices.contains("cane") - || user.mobilityProfile.mobilityDevices.contains("crutches") - || user.mobilityProfile.mobilityDevices.contains("stroller") - || user.mobilityProfile.mobilityDevices.contains("service animal")) { - mModeTemp = "Device"; - } - if (user.mobilityProfile.mobilityDevices.contains("mobility scooter")) { - mModeTemp = "MScooter"; - } - if (user.mobilityProfile.mobilityDevices.contains("electric wheelchair")) { - mModeTemp = "WChairE"; - } - if (user.mobilityProfile.mobilityDevices.contains("manual wheelchair")) { - mModeTemp = "WChairM"; - } - - if ("None".equals(mModeTemp) && user.mobilityProfile.isMobilityLimited) { - mModeTemp = "Some"; - } - } - - if (visionTemp.isEmpty()) { - if (MobilityProfile.VisionLimitation.LOW_VISION == user.mobilityProfile.visionLimitation) { - visionTemp = "LowVision"; - } else if (MobilityProfile.VisionLimitation.LEGALLY_BLIND == user.mobilityProfile.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"; - } } diff --git a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java index 0f2215d2d..42d2195c9 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java +++ b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java @@ -5,13 +5,32 @@ import java.io.Serializable; import java.util.Collection; +import java.util.Collections; +import java.util.Set; /** - * Mobility profile data + * Mobility profile data, to keeps track of values from UI or mobile app and + * uses them to maintain a "mobility mode," which are keywords specified in + * the Georgia Tech Mobility Profile Configuration / Logical Flow document. + *

+ * Provided as part of {@link OtpUser}, example JSON format: + * + * ... + * "mobilityProfile": { + * "isMobilityLimited": true, + * "mobilityDevices": ["service animal", "electric wheelchair"], + * "visionLimitation": "low-vision", + * "mobilityMode": "WChairE-LowVision", + * } + * ... + * */ public class MobilityProfile implements Serializable { + // Selected mobility mode keywords from Georgia Tech document. + private static final Set MOBILITY_DEVICES = Set.of("Device", "MScooter", "WChairE", "WChairM", "Some"); + public enum VisionLimitation { - @JsonProperty("legally blind") LEGALLY_BLIND, + @JsonProperty("legally-blind") LEGALLY_BLIND, @JsonProperty("low-vision") LOW_VISION, @JsonProperty("none") NONE } @@ -20,12 +39,78 @@ public enum VisionLimitation { public boolean isMobilityLimited; /** User may indicate zero or more mobility devices. */ - public Collection mobilityDevices; + public Collection 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; + 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) { + // 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.isEmpty() || 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"; + } } From 5b16401b953ec9e78ca0754b8dc7f536cf084212 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 4 Dec 2023 23:47:26 -0800 Subject: [PATCH 03/10] OTP-888 - Fix typo. --- .../java/org/opentripplanner/middleware/models/OtpUser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java index 937fe0fa5..6beddb246 100644 --- a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java +++ b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java @@ -36,7 +36,7 @@ public enum Notification { /** Whether the phone number has been verified. */ public boolean isPhoneNumberVerified; - /** Mobilty profile. */ + /** Mobility profile. */ public MobilityProfile mobilityProfile; /** From 9a05f8ef11cf43c90d14b079dc51ac44915d1b58 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Mon, 4 Dec 2023 23:59:43 -0800 Subject: [PATCH 04/10] OTP-888 - Remove redundant check of empty list. --- .../org/opentripplanner/middleware/models/MobilityProfile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java index 42d2195c9..d361433b9 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java +++ b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java @@ -66,7 +66,7 @@ public static void updateMobilityMode(MobilityProfile mobilityProfile) { // 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.isEmpty() || mobilityProfile.mobilityDevices.contains("none")) { + if (mobilityProfile.mobilityDevices.contains("none")) { mobilityProfile.mobilityDevices.clear(); } else { if (mobilityProfile.mobilityDevices.contains("white cane")) { From e37259017e1a475a8aea088f43791fb5a80de7af Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Tue, 5 Dec 2023 09:43:34 -0800 Subject: [PATCH 05/10] OPT-888 Review feedback, updating modes with instance method. --- .../controllers/api/OtpUserController.java | 4 +- .../middleware/models/MobilityProfile.java | 44 ++++++------- .../models/MobilityProfileTest.java | 62 +++++++++++++++++++ 3 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java index 25df5a110..69f93b89d 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java @@ -52,13 +52,13 @@ OtpUser preCreateHook(OtpUser user, Request req) { Auth0Connection.ensureApiUserHasApiKey(req); user.applicationId = requestingUser.apiUser.id; } - MobilityProfile.updateMobilityMode(user.mobilityProfile); + user.mobilityProfile.updateMobilityMode(); return super.preCreateHook(user, req); } @Override OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) { - MobilityProfile.updateMobilityMode(user.mobilityProfile); + user.mobilityProfile.updateMobilityMode(); return super.preUpdateHook(user, preExistingUser, req); } diff --git a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java index d361433b9..ba5e1d1e7 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java +++ b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java @@ -48,7 +48,6 @@ public enum VisionLimitation { /** 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 @@ -57,7 +56,7 @@ public enum VisionLimitation { * 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) { + public void updateMobilityMode() { // 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. @@ -66,51 +65,52 @@ public static void updateMobilityMode(MobilityProfile mobilityProfile) { // 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(); + if (mobilityDevices.contains("none")) { + mobilityDevices = Collections.EMPTY_LIST; } else { - if (mobilityProfile.mobilityDevices.contains("white cane")) { + if (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")) { + if (mobilityDevices.contains("manual walker") + || mobilityDevices.contains("wheeled walker") + || mobilityDevices.contains("cane") + || mobilityDevices.contains("crutches") + || mobilityDevices.contains("stroller") + || mobilityDevices.contains("service animal")) { mModeTemp = "Device"; } - if (mobilityProfile.mobilityDevices.contains("mobility scooter")) { + if (mobilityDevices.contains("mobility scooter")) { mModeTemp = "MScooter"; } - if (mobilityProfile.mobilityDevices.contains("electric wheelchair")) { + if (mobilityDevices.contains("electric wheelchair")) { mModeTemp = "WChairE"; } - if (mobilityProfile.mobilityDevices.contains("manual wheelchair")) { + if (mobilityDevices.contains("manual wheelchair")) { mModeTemp = "WChairM"; } - if ("None".equals(mModeTemp) && mobilityProfile.isMobilityLimited) { + if ("None".equals(mModeTemp) && isMobilityLimited) { mModeTemp = "Some"; } } - if (MobilityProfile.VisionLimitation.LOW_VISION == mobilityProfile.visionLimitation) { + if (MobilityProfile.VisionLimitation.LOW_VISION == visionLimitation) { visionTemp = "LowVision"; - } else if (MobilityProfile.VisionLimitation.LEGALLY_BLIND == mobilityProfile.visionLimitation) { + } else if (MobilityProfile.VisionLimitation.LEGALLY_BLIND == visionLimitation) { visionTemp = "Blind"; } // Create combinations for mobility mode and vision if (Set.of("LowVision", "Blind").contains(visionTemp)) { if ("None".equals(mModeTemp)) { - mobilityProfile.mobilityMode = visionTemp; + mobilityMode = visionTemp; } else if (MOBILITY_DEVICES.contains(mModeTemp)) { - mobilityProfile.mobilityMode = mModeTemp + "-" + visionTemp; + mobilityMode = mModeTemp + "-" + visionTemp; } } else if (MOBILITY_DEVICES.contains(mModeTemp)) { - mobilityProfile.mobilityMode = mModeTemp; - } - mobilityProfile.mobilityMode = "None"; + mobilityMode = mModeTemp; + } else { + mobilityMode = "None"; + } } } diff --git a/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java b/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java new file mode 100644 index 000000000..13df4e7c7 --- /dev/null +++ b/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java @@ -0,0 +1,62 @@ +package org.opentripplanner.middleware.models; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Set; + +/** + * This class contains tests of selected scenarios in {@link MobilityProfile}. + */ +public class MobilityProfileTest { + // The mobility modes tested are tightly coupled with algorithms in the + // Georgia Tech Mobility Profile Configuration / Logical Flow document, as + // implemented in the MobilityPorfile#updateMobilityMode() method. Changes + // to that document msut be reflected in that method and in these tests. + + @Test + public void testModesKnown() { + var prof = new MobilityProfile(); + prof.mobilityDevices = Set.of("service animal", "crutches"); + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "Device"); + prof.mobilityDevices = Set.of("service animal", "electric wheelchair"); + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "WChairE"); + prof.mobilityDevices = Set.of("service animal", "electric wheelchair", "white cane"); + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "WChairE-Blind"); + prof.mobilityDevices = Set.of("manual wheelchair", "electric wheelchair", "white cane"); + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "WChairM-Blind"); + } + + @Test + public void testModesNone() { + var prof = new MobilityProfile(); + prof.mobilityDevices = Collections.EMPTY_LIST; + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "None"); + prof.mobilityDevices = Set.of("cardboard transmogrifier"); // Unknown/invalid device + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "None"); + prof.mobilityDevices = Set.of("cane", "none", "service animal"); // Devices with "none" poison pill + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "None"); + } + + @Test + public void testModesVision() { + var prof = new MobilityProfile(); + prof.mobilityDevices = Set.of("service animal", "electric wheelchair", "white cane"); + prof.visionLimitation = MobilityProfile.VisionLimitation.LOW_VISION; // Overrides "white cane" effect + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "WChairE-LowVision"); + prof.mobilityDevices = Set.of("manual wheelchair", "stroller"); + prof.visionLimitation = MobilityProfile.VisionLimitation.LEGALLY_BLIND; + prof.updateMobilityMode(); + Assertions.assertEquals(prof.mobilityMode, "WChairM-Blind"); + } +} From f3576ba0e48479408a5b1b629f86fca3669b62bd Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Tue, 5 Dec 2023 12:38:55 -0800 Subject: [PATCH 06/10] Docstring typo. --- .../opentripplanner/middleware/models/MobilityProfileTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java b/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java index 13df4e7c7..e9035de74 100644 --- a/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java +++ b/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java @@ -14,7 +14,7 @@ public class MobilityProfileTest { // The mobility modes tested are tightly coupled with algorithms in the // Georgia Tech Mobility Profile Configuration / Logical Flow document, as // implemented in the MobilityPorfile#updateMobilityMode() method. Changes - // to that document msut be reflected in that method and in these tests. + // to that document must be reflected in that method and in these tests. @Test public void testModesKnown() { From a4a6cf46aa2f1986a01438a74e440592383c830e Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Tue, 5 Dec 2023 12:38:57 -0800 Subject: [PATCH 07/10] OPT-888 Restore null check for nonstatic method. --- .../middleware/controllers/api/OtpUserController.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java index 69f93b89d..b91df942f 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java @@ -18,6 +18,7 @@ import spark.Response; import java.util.Date; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -52,13 +53,17 @@ OtpUser preCreateHook(OtpUser user, Request req) { Auth0Connection.ensureApiUserHasApiKey(req); user.applicationId = requestingUser.apiUser.id; } - user.mobilityProfile.updateMobilityMode(); + if (Objects.nonNull(user.mobilityProfile)) { + user.mobilityProfile.updateMobilityMode(); + } return super.preCreateHook(user, req); } @Override OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) { - user.mobilityProfile.updateMobilityMode(); + if (Objects.nonNull(user.mobilityProfile)) { + user.mobilityProfile.updateMobilityMode(); + } return super.preUpdateHook(user, preExistingUser, req); } From 1b1edad14388150bf1b250c57614f514fb58769d Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Tue, 5 Dec 2023 12:47:11 -0800 Subject: [PATCH 08/10] OTP-888 No @JsonIgnore. --- .../org/opentripplanner/middleware/models/MobilityProfile.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java index ba5e1d1e7..e0e07a3d5 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java +++ b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java @@ -42,7 +42,6 @@ public enum VisionLimitation { public Collection 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. */ From 6ac00ba46d624e578d309545bed3fb0456b6ca59 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Tue, 5 Dec 2023 14:34:10 -0800 Subject: [PATCH 09/10] OTP-888 - Parameterized tests. --- .../models/MobilityProfileTest.java | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java b/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java index e9035de74..5fe9bfbaf 100644 --- a/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java +++ b/src/test/java/org/opentripplanner/middleware/models/MobilityProfileTest.java @@ -1,11 +1,13 @@ package org.opentripplanner.middleware.models; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Collections; import java.util.Set; +import java.util.stream.Stream; /** * This class contains tests of selected scenarios in {@link MobilityProfile}. @@ -16,47 +18,43 @@ public class MobilityProfileTest { // implemented in the MobilityPorfile#updateMobilityMode() method. Changes // to that document must be reflected in that method and in these tests. - @Test - public void testModesKnown() { - var prof = new MobilityProfile(); - prof.mobilityDevices = Set.of("service animal", "crutches"); - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "Device"); - prof.mobilityDevices = Set.of("service animal", "electric wheelchair"); - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "WChairE"); - prof.mobilityDevices = Set.of("service animal", "electric wheelchair", "white cane"); - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "WChairE-Blind"); - prof.mobilityDevices = Set.of("manual wheelchair", "electric wheelchair", "white cane"); - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "WChairM-Blind"); + private static Stream provideModes() { + return Stream.of( + Arguments.of(Set.of("service animal", "crutches"), "Device"), + Arguments.of(Set.of("service animal", "electric wheelchair"), "WChairE"), + Arguments.of(Set.of("service animal", "electric wheelchair", "white cane"), "WChairE-Blind"), + Arguments.of(Set.of("manual wheelchair", "electric wheelchair", "white cane"), "WChairM-Blind"), + Arguments.of(Collections.EMPTY_SET, "None"), + Arguments.of(Set.of("cardboard transmogrifier"), "None"), // Unknown/invalid device + Arguments.of(Set.of("cane", "none", "service animal"), "None") // Devices include "none" poison pill + ); } - @Test - public void testModesNone() { + @ParameterizedTest + @MethodSource("provideModes") + public void testModes(Set devices, String mode) { var prof = new MobilityProfile(); - prof.mobilityDevices = Collections.EMPTY_LIST; + prof.mobilityDevices = devices; prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "None"); - prof.mobilityDevices = Set.of("cardboard transmogrifier"); // Unknown/invalid device - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "None"); - prof.mobilityDevices = Set.of("cane", "none", "service animal"); // Devices with "none" poison pill - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "None"); + Assertions.assertEquals(mode, prof.mobilityMode); } - @Test - public void testModesVision() { + private static Stream provideModesVision() { + return Stream.of( + Arguments.of(MobilityProfile.VisionLimitation.LOW_VISION, // Overrides "white cane" default + Set.of("service animal", "electric wheelchair", "white cane"), "WChairE-LowVision"), + Arguments.of(MobilityProfile.VisionLimitation.LEGALLY_BLIND, + Set.of("manual wheelchair", "stroller"), "WChairM-Blind") + ); + } + + @ParameterizedTest + @MethodSource("provideModesVision") + public void testModesVision(MobilityProfile.VisionLimitation limitation, Set devices, String mode) { var prof = new MobilityProfile(); - prof.mobilityDevices = Set.of("service animal", "electric wheelchair", "white cane"); - prof.visionLimitation = MobilityProfile.VisionLimitation.LOW_VISION; // Overrides "white cane" effect - prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "WChairE-LowVision"); - prof.mobilityDevices = Set.of("manual wheelchair", "stroller"); - prof.visionLimitation = MobilityProfile.VisionLimitation.LEGALLY_BLIND; + prof.mobilityDevices = devices; + prof.visionLimitation = limitation; prof.updateMobilityMode(); - Assertions.assertEquals(prof.mobilityMode, "WChairM-Blind"); + Assertions.assertEquals(mode, prof.mobilityMode); } } From a0610978118ca1642da24c490600b9df828b369e Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Wed, 6 Dec 2023 08:36:26 -0800 Subject: [PATCH 10/10] Typo. --- .../org/opentripplanner/middleware/models/MobilityProfile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java index e0e07a3d5..c9e02644c 100644 --- a/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java +++ b/src/main/java/org/opentripplanner/middleware/models/MobilityProfile.java @@ -9,7 +9,7 @@ import java.util.Set; /** - * Mobility profile data, to keeps track of values from UI or mobile app and + * Mobility profile data, to keep track of values from UI or mobile app and * uses them to maintain a "mobility mode," which are keywords specified in * the Georgia Tech Mobility Profile Configuration / Logical Flow document. *