Skip to content

Commit

Permalink
Merge branch 'dev' into feature/OTP-1458-replace-no-instruction-on-wa…
Browse files Browse the repository at this point in the history
…lk-leg
  • Loading branch information
br648 committed Nov 14, 2024
2 parents aab57b5 + 120d920 commit 8813403
Show file tree
Hide file tree
Showing 24 changed files with 826 additions and 149 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ The special E2E client settings should be defined in `env.yml`:
| TRIP_TRACKING_TRAM_ON_TRACK_RADIUS | integer | Optional | 100 | The threshold in meters below which travelling by tram is considered on track. |
| TRIP_INSTRUCTION_IMMEDIATE_RADIUS | integer | Optional | 2 | The radius in meters under which an immediate instruction is given. |
| TRIP_INSTRUCTION_UPCOMING_RADIUS | integer | Optional | 10 | The radius in meters under which an upcoming instruction is given. |
| TRIP_SURVEY_ID | string | Optional | abcdef123y | The ID of a survey (on the platform of your choice) for trip-related feedback. |
| TRIP_SURVEY_SUBDOMAIN | string | Optional | abcabc12a | The subdomain of a website where the trip-related surveys are administered. |
| TWILIO_ACCOUNT_SID | string | Optional | your-account-sid | Twilio settings available at: https://twilio.com/user/account |
| TRUSTED_COMPANION_CONFIRMATION_PAGE_URL | string | Optional | https://otp-server.example.com/trusted/confirmation | URL to the trusted companion confirmation page. This page should support handling an error URL parameter. |
| TWILIO_AUTH_TOKEN | string | Optional | your-auth-token | Twilio settings available at: https://twilio.com/user/account |
Expand Down
4 changes: 4 additions & 0 deletions configurations/default/env.yml.tmp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ TRIP_INSTRUCTION_IMMEDIATE_RADIUS: 2
# The radius in meters under which an upcoming instruction is given.
TRIP_INSTRUCTION_UPCOMING_RADIUS: 10

# Survey ID and domain that is offered after users complete certain trips.
TRIP_SURVEY_ID: abcdef123y
TRIP_SURVEY_SUBDOMAIN: abcabc12a

US_RIDE_GWINNETT_BUS_OPERATOR_NOTIFIER_API_URL: https://bus.notifier.example.com
US_RIDE_GWINNETT_BUS_OPERATOR_NOTIFIER_API_KEY: your-key
US_RIDE_GWINNETT_BUS_OPERATOR_NOTIFIER_QUALIFYING_ROUTES: agency_id:route_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.opentripplanner.middleware.otp.OtpVersion;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.tripmonitor.jobs.MonitorAllTripsJob;
import org.opentripplanner.middleware.triptracker.TripSurveySenderJob;
import org.opentripplanner.middleware.utils.ConfigUtils;
import org.opentripplanner.middleware.utils.HttpUtils;
import org.opentripplanner.middleware.utils.Scheduler;
Expand Down Expand Up @@ -85,6 +86,16 @@ public static void main(String[] args) throws IOException, InterruptedException
1,
TimeUnit.MINUTES
);

// Schedule recurring job for post-trip surveys, once every half-hour to catch recently completed trips.
// TODO: Determine whether this should go in some other process.
TripSurveySenderJob tripSurveySenderJob = new TripSurveySenderJob();
Scheduler.scheduleJob(
tripSurveySenderJob,
0,
30,
TimeUnit.MINUTES
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public abstract class ApiController<T extends Model> implements Endpoint {
public static final int DEFAULT_OFFSET = 0;
public static final String OFFSET_PARAM = "offset";
public static final String USER_ID_PARAM = "userId";
public static final String ID_FIELD_NAME = "_id";

public static final ParameterDescriptor LIMIT = ParameterDescriptor.newBuilder()
.withName(LIMIT_PARAM)
Expand Down Expand Up @@ -219,7 +220,7 @@ private ResponseList<T> getMany(Request req, Response res) {
// will be limited to just the entity matching this Otp user.
Bson filter = (requestingUser.apiUser != null)
? Filters.eq("applicationId", requestingUser.apiUser.id)
: Filters.eq("_id", requestingUser.otpUser.id);
: Filters.eq(ID_FIELD_NAME, requestingUser.otpUser.id);
return persistence.getResponseList(filter, offset, limit);
} else if (requestingUser.isAPIUser()) {
// A user id must be provided if the request is being made by a third party user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import static io.github.manusant.ss.descriptor.MethodDescriptor.path;
import static com.mongodb.client.model.Filters.eq;
import static org.opentripplanner.middleware.models.MonitoredTrip.USER_ID_FIELD_NAME;
import static org.opentripplanner.middleware.utils.ConfigUtils.getConfigPropertyAsInt;
import static org.opentripplanner.middleware.utils.HttpUtils.JSON_ONLY;
import static org.opentripplanner.middleware.utils.JsonUtils.getPOJOFromRequestBody;
Expand Down Expand Up @@ -197,7 +198,7 @@ private static ItineraryExistence checkItinerary(Request request, Response respo
*/
private void verifyBelowMaxNumTrips(String userId, Request request) {
// filter monitored trip on user id to find out how many have already been saved
Bson filter = Filters.and(eq("userId", userId));
Bson filter = Filters.and(eq(USER_ID_FIELD_NAME, userId));
long count = this.persistence.getCountFiltered(filter);
if (count >= MAXIMUM_PERMITTED_MONITORED_TRIPS) {
logMessageAndHalt(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public enum Message {
TRIP_DELAY_MINUTES,
TRIP_NOT_FOUND_NOTIFICATION,
TRIP_NO_LONGER_POSSIBLE_NOTIFICATION,
TRIP_REMINDER_NOTIFICATION;
TRIP_REMINDER_NOTIFICATION,
TRIP_SURVEY_NOTIFICATION;

private static final Logger LOG = LoggerFactory.getLogger(Message.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class MonitoredTrip extends Model {

public static final String USER_ID_FIELD_NAME = "userId";

/**
* Mongo Id of the {@link OtpUser} who owns this monitored trip.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/opentripplanner/middleware/models/OtpUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@


import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -33,6 +35,7 @@ public enum Notification {
public static final String AUTH0_SCOPE = "otp-user";
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(OtpUser.class);
public static final String TRIP_SURVEY_NOTIFICATIONS_FIELD = "tripSurveyNotifications";

/** Whether the user would like accessible routes by default. */
public boolean accessibilityRoutingByDefault;
Expand Down Expand Up @@ -83,6 +86,9 @@ public enum Notification {
/** Whether to store the user's trip history (user must opt in). */
public boolean storeTripHistory;

/** The trail of survey notifications sent for journeys completed by the user. */
public List<TripSurveyNotification> tripSurveyNotifications = new ArrayList<>();

@JsonIgnore
/** If this user was created by an {@link ApiUser}, this parameter will match the {@link ApiUser}'s id */
public String applicationId;
Expand Down Expand Up @@ -193,4 +199,10 @@ public void setNotificationChannel(String channels) {
});
}
}

/** Obtains the last trip survey notification sent. */
public Optional<TripSurveyNotification> findLastTripSurveyNotificationSent() {
if (tripSurveyNotifications == null) return Optional.empty();
return tripSurveyNotifications.stream().max(Comparator.comparingLong(n -> n.timeSent.getTime()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.triptracker.TrackingLocation;
import org.opentripplanner.middleware.triptracker.TripStatus;

import java.util.ArrayList;
import java.util.Date;
Expand All @@ -26,6 +27,10 @@ public class TrackedJourney extends Model {

public Map<String, String> busNotificationMessages = new HashMap<>();

public int longestConsecutiveDeviatedPoints = -1;

public transient MonitoredTrip trip;

public static final String TRIP_ID_FIELD_NAME = "tripId";

public static final String LOCATIONS_FIELD_NAME = "locations";
Expand All @@ -35,6 +40,8 @@ public class TrackedJourney extends Model {

public static final String END_CONDITION_FIELD_NAME = "endCondition";

public static final String LONGEST_CONSECUTIVE_DEVIATED_POINTS_FIELD_NAME = "longestConsecutiveDeviatedPoints";

public static final String TERMINATED_BY_USER = "Tracking terminated by user.";

public static final String FORCIBLY_TERMINATED = "Tracking forcibly terminated.";
Expand Down Expand Up @@ -91,4 +98,30 @@ public void updateNotificationMessage(String routeId, String body) {
busNotificationMessages
);
}

/** The largest consecutive deviations for all tracking locations marked "deviated". */
public int computeLargestConsecutiveDeviations() {
if (locations == null) return -1;

int count = 0;
int maxCount = 0;
for (TrackingLocation location : locations) {
// A trip status must have been computed for a location to count.
// (The mobile app will send many other more for reference, but only those for which we compute a status
// (i.e. the last coordinate in every batch) will potentially count.
if (location.tripStatus != null) {
// Traveler must be moving (speed != 0) for a deviated location to be counted.
if (location.tripStatus == TripStatus.DEVIATED) {
if (location.speed != 0) {
count++;
if (maxCount < count) maxCount = count;
}
} else {
// If a location has a status computed and is not deviated, reset the streak.
count = 0;
}
}
}
return maxCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.opentripplanner.middleware.models;

import java.util.Date;
import java.util.UUID;

/** Contains information regarding survey notifications sent after a trip is completed. */
public class TripSurveyNotification {

public static final String TIME_SENT_FIELD = "timeSent";

/**
* Unique ID to link a survey entry to the corresponding notification
* (and to find which notifications were dismissed without opening the survey)
*/
public String id;

/** Date/time when the trip survey notification was sent. */
public Date timeSent;

/** The {@link TrackedJourney} (and, indirectly, the {@link MonitoredTrip}) that this notification refers to. */
public String journeyId;

public TripSurveyNotification() {
// Default constructor for deserialization
}

public TripSurveyNotification(String id, Date timeSent, String journeyId) {
this.id = id;
this.timeSent = timeSent;
this.journeyId = journeyId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.opentripplanner.middleware.controllers.api.ApiController.ID_FIELD_NAME;

/**
* This job will analyze applicable monitored trips and create further individual tasks to analyze each individual trip.
*/
Expand Down Expand Up @@ -55,7 +57,7 @@ public void run() {
// This saves bandwidth and memory, as only the ID field is used to set up this job.
// The full data for each trip will be fetched at the time the actual analysis takes place.
List<String> allTripIds = Persistence.monitoredTrips.getDistinctFieldValues(
"_id",
ID_FIELD_NAME,
makeTripFilter(),
String.class
).into(new ArrayList<>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ private static EndTrackingResponse completeJourney(TripTrackingData tripData, bo
trackedJourney.end(isForciblyEnded);
Persistence.trackedJourneys.updateField(trackedJourney.id, TrackedJourney.END_TIME_FIELD_NAME, trackedJourney.endTime);
Persistence.trackedJourneys.updateField(trackedJourney.id, TrackedJourney.END_CONDITION_FIELD_NAME, trackedJourney.endCondition);
trackedJourney.longestConsecutiveDeviatedPoints = trackedJourney.computeLargestConsecutiveDeviations();
Persistence.trackedJourneys.updateField(
trackedJourney.id,
TrackedJourney.LONGEST_CONSECUTIVE_DEVIATED_POINTS_FIELD_NAME,
trackedJourney.longestConsecutiveDeviatedPoints
);

return new EndTrackingResponse(
TripInstruction.NO_INSTRUCTION,
Expand Down
Loading

0 comments on commit 8813403

Please sign in to comment.