From 08d96959576cb3e902e837d368e5a23d05b37a82 Mon Sep 17 00:00:00 2001 From: Jym Dyer Date: Thu, 30 Nov 2023 00:46:00 -0800 Subject: [PATCH] OTP-888 Mobility profile additions. --- .../controllers/api/OtpUserController.java | 75 +++++++++++++++++++ .../middleware/models/OtpUser.java | 13 ++++ 2 files changed, 88 insertions(+) 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 3141ee9ef..230190c05 100644 --- a/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java +++ b/src/main/java/org/opentripplanner/middleware/controllers/api/OtpUserController.java @@ -16,7 +16,9 @@ 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; @@ -37,6 +39,8 @@ 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); @@ -51,9 +55,16 @@ OtpUser preCreateHook(OtpUser user, Request req) { Auth0Connection.ensureApiUserHasApiKey(req); user.applicationId = requestingUser.apiUser.id; } + user.mobilityMode = calculateMobilityMode(user); return super.preCreateHook(user, req); } + @Override + OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) { + user.mobilityMode = calculateMobilityMode(user); + return super.preUpdateHook(user, preExistingUser, req); + } + @Override protected void buildEndpoint(ApiEndpoint baseEndpoint) { LOG.info("Registering path {}/{}.", ROOT_ROUTE, VERIFY_PATH); @@ -170,4 +181,68 @@ 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"; + } } diff --git a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java index f73af6694..68d95626b 100644 --- a/src/main/java/org/opentripplanner/middleware/models/OtpUser.java +++ b/src/main/java/org/opentripplanner/middleware/models/OtpUser.java @@ -33,9 +33,22 @@ 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; + /** * Notification preferences for this user * (EMAIL and/or SMS and/or PUSH).