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

Post deviated trip notification #260

Merged
merged 44 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9fc6822
feat(TrackedJourney): Add method to compute total deviation.
binh-dam-ibigroup Oct 4, 2024
6507d5a
feat(TrackedJourney): Persist total deviation upon completing journey.
binh-dam-ibigroup Oct 4, 2024
44c456e
.feat(OtpUser): Add field for last survey notif sent.
binh-dam-ibigroup Oct 8, 2024
9e19b1e
refactor(ApiController): Extract const for mongo id field.
binh-dam-ibigroup Oct 8, 2024
462639b
refactor(MonitoredTrip): Extract const for trip id field.
binh-dam-ibigroup Oct 8, 2024
1f31842
feat(TripSurveySenderJob): Add basic logic for trip survey job.
binh-dam-ibigroup Oct 8, 2024
2e2473f
refactor(TripSurveySenderJob): Convert methods to static.
binh-dam-ibigroup Oct 8, 2024
2432090
test(TripSurveySenderJob): Reuse journeys.
binh-dam-ibigroup Oct 8, 2024
c00ac92
feat(TripSurveySenderJob): Send push notification using current svc f…
binh-dam-ibigroup Oct 9, 2024
ca2b8aa
feat(OtpMiddlewareMain): Schedule trip survey job.
binh-dam-ibigroup Oct 9, 2024
20fdb33
docs(swagger): Update snapshot.
binh-dam-ibigroup Oct 9, 2024
ab5a82f
fix(TripSurveySenderJob): Include missing last trip survey field in u…
binh-dam-ibigroup Oct 9, 2024
828afdc
refactor(TripSurveySenderJob): Remove day of week in survey push mess…
binh-dam-ibigroup Oct 9, 2024
3ed298b
docs(OtpMiddlewareMain): Fix trip survey comment typo.
binh-dam-ibigroup Oct 10, 2024
fa6b838
refactor(TrackedJourney): Introduce consecutive deviation metric.
binh-dam-ibigroup Oct 10, 2024
72ecf80
refactor(TripSurveySenderJob): Add a minimum 1-minute deviation thres…
binh-dam-ibigroup Oct 10, 2024
e46abca
Merge branch 'dev' into post-deviated-trip-notification
binh-dam-ibigroup Oct 14, 2024
25d19f9
Merge branch 'dev' into post-deviated-trip-notification
binh-dam-ibigroup Oct 21, 2024
be37335
refactor(TrackedJourney): Remove cumulative deviation field.
binh-dam-ibigroup Oct 21, 2024
653b62c
Merge branch 'dev' into post-deviated-trip-notification
binh-dam-ibigroup Oct 25, 2024
eb59c11
fix(TripSurveySenderJob): Send survey notifications within 30 mins of…
binh-dam-ibigroup Oct 25, 2024
cd2b05a
docs: Introduce new TRIP_SURVEY_ID config parameter.
binh-dam-ibigroup Oct 29, 2024
b27a4dd
refactor(Message): Extract i18n message for trip survey notification,…
binh-dam-ibigroup Oct 29, 2024
4cd55b0
refactor(TripSurveySenderJob): Streamline notification for trip surveys.
binh-dam-ibigroup Oct 29, 2024
740fdc7
refactor(NotificationUtils): Add surveyId and userId fields and rewor…
binh-dam-ibigroup Oct 29, 2024
6c55481
feat(NotificationUtils): Pass surveyId with trip survey notifications.
binh-dam-ibigroup Oct 29, 2024
90d1f4b
docs(swagger): Update snapshot.
binh-dam-ibigroup Oct 29, 2024
b89e1db
refactor(TripSurveySenderJob): Rename variable.
binh-dam-ibigroup Oct 29, 2024
8d92c4f
refactor(NotificationUtils): Check for TRIP_SURVEY_ID before attempti…
binh-dam-ibigroup Oct 29, 2024
1381b06
refactor(NotificationUtils): Check for devices before rest of trip su…
binh-dam-ibigroup Oct 29, 2024
edb9392
refactor(TripSurveySenderJob): Tweak comments.
binh-dam-ibigroup Oct 29, 2024
4755ba8
refactor(TrackedJourney): Remove unused method.
binh-dam-ibigroup Oct 30, 2024
0a8a81b
style(ManageTripTracking):Wrap line
binh-dam-ibigroup Oct 31, 2024
2065188
style(NotificationUtils): Use static import
binh-dam-ibigroup Oct 31, 2024
de1d9f4
docs: Introduce TRIP_SURVEY_SUBDOMAIN config param
binh-dam-ibigroup Nov 1, 2024
5bfe653
feat(NotificationUtils): Include survey subdomain to push notification
binh-dam-ibigroup Nov 1, 2024
e8256da
refactor(OtpUser): Use list of sent notifications for traceability.
binh-dam-ibigroup Nov 5, 2024
e096f84
refactor(TripSurveySenderJob): Support instances where users had prev…
binh-dam-ibigroup Nov 5, 2024
402383f
refactor(TripSurveySenderJob): Improve Mongo filters.
binh-dam-ibigroup Nov 5, 2024
986d5ce
refactor(TripSurveySenderJob): Include notification id.
binh-dam-ibigroup Nov 6, 2024
8dba681
Merge branch 'dev' into post-deviated-trip-notification
binh-dam-ibigroup Nov 6, 2024
9f2ab38
refactor(OtpUser): Fix imports
binh-dam-ibigroup Nov 6, 2024
bb99963
docs(Swagger): Update snapshots
binh-dam-ibigroup Nov 6, 2024
6128725
Merge branch 'dev' into post-deviated-trip-notification
binh-dam-ibigroup Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the trigger location is fine. Perhaps follow the approach of ConnectedDataManager.scheduleTripHistoryUploadJob(); and have the schduler in the class. It might also be benefical to have the ability to disable this via a config property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@binh-dam-ibigroup thoughts on these two points?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd have to rethink where to run periodic tasks.

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
Loading