TOMP-WG / TOMP-API

Transport Operator to Mobility-as-a-Service Provider-API development for Mobility as a Service
Apache License 2.0
100 stars 41 forks source link

[CHANGE REQUEST] Add new Leg State : PAUSE_FORCED #554

Open zerwuff opened 4 months ago

zerwuff commented 4 months ago

API Version

TOMP 1.5

Summary

Add new Leg State FORCED_PAUSE to show the MP that the TO had set the bike in a forced Pause when trying to finish a booking that the TO will prevent for business reasons (non-parking area).

The API reflects that the users ride cannot be finished: the legs stays in FORCED_PAUSE state, and the user need to move the bike outside of a no-parking area.

Expected Behavior

Extend LegEvent Types [ PREPARE, ASSIGN_ASSET, SET_IN_USE, PAUSE, OPEN_TRUNK, START_FINISHING, FINISH, TIME_EXTEND, TIME_POSTPONE, CANCEL ]

to

[ PREPARE, ASSIGN_ASSET, SET_IN_USE, PAUSE, FORCED_PAUSE, OPEN_TRUNK, START_FINISHING, FINISH, TIME_EXTEND, TIME_POSTPONE, CANCEL ]

Current Behavior

Use PAUSE Leg Event Type and write the information that the pause was forced in one of the additional fields:

The Combination auf PAUSE + one of the above fields would be a workaround, but more complicated for the MP to implement.

Possible Solution

Extend the LegEvent Types to: [ PREPARE, ASSIGN_ASSET, SET_IN_USE, PAUSE, FORCED_PAUSE, OPEN_TRUNK, START_FINISHING, FINISH, TIME_EXTEND, TIME_POSTPONE, CANCEL ]

Steps to Reproduce

Not yet, it is still in design

Context (Environment)

Creating Legs from TO side.

We came over to this problem when when a user is trying to finish a ride by locking the bike:

If the bike got locked from the TO side, we check if the user can finish the booking there. If our backend decides that the ride could not be finished there (no-parking spaces, outside business area), we will notify the user about that.

If this situation comes along, the TO creates a leg of the user in a FORCED_PAUSE, this should be reflected through the API.

Detailed Description

see context.

Possible Implementation

See possible Solution

matt-wirtz commented 4 months ago

Hi! The use case sounds interesting but I think I didn't get it completely.

Basically the user has a running booking and wants to end it. For ending the leg the bike has to be in the business area and additionally not in a no-parking space.

If the leg would be ended by the user via his app, the MP would issue a START_FINISHING event. But this would not be successful because the conditions for ending the leg are not met. The user will be informed about that.

In your case the leg is not ended via app. Instead the user just locks the bike. The TO recognizes this and checks if the leg can be finished. Since the conditions are not met the leg can't be finished here.

I assume the user will be informed about the wrong location for ending the leg and will continue on with his leg and try to end the leg at a location that fulfills the conditions.

Is my understanding correct?

zerwuff commented 4 months ago

Yes, your understanding is correct. User locks the bike on the bike itself in a bad area. We need to inform the user about that. Besides that the TO is not finishing the booking, we want to inform the user about the fact, the LEG is currently in pause and he needs to take action.

In the regular flow, the user can issue regular pause requests to lock / unlock the bike. We want to separate those 2 pause types and a new leg state should reflect that best, instead of using one of the memo* fields.

From a business side we want to achieve:

  1. Notify user to take action on his devices -> TO creates new LEG event and pushes it to MP. MP recognizes FORCED_PAUSE type and shows the user "You are within a forced pause, please move outside this area, or you will be charged a 10 eur fine". The lock on the bike blinks additionally to show this to the user.
  2. Ultimately leep non-parking areas clear of our assets
matt-wirtz commented 3 months ago

What happens in your case when the user locks the bike and the bike is parked properly in a parking zone. How do you distinguish whether the user wants to pause the bike or end the rental? Or to frame it differently: when the user wants to return the bike does he need to do more then just lock the bike?

zerwuff commented 3 months ago

Hi Mat, The pause case is looking like that: The user needs to inform the MP that he is going in a pause before closing the lock. This is done by clicking a Pause button in the App. With hitting that button, the MP will send a new leg type "PAUSE" to the TO. If this event is received within 5 minutes before closing the lock on the bike, the leg is considered in PAUSE, the booking will be not finished.

The end rental case is looking like that: The user just closes the lock on the bike, no events sent. Booking is finished.

matt-wirtz commented 3 months ago

Thx for the additional info. That makes sense.

You already mentioned that one option would be to send a PAUSE event to the MP instead of a FINISH event when the bike is locked by the user (and no pause was requested before) and not parked in a parking zone. Then the leg state would be PAUSED. The comment in the LegEvent object might be used to provide some textual info. Or in addition a notification might be send to the MP of type USER_OUT_OF_LIMITS and a text that further explains why the leg is paused and not finished.

But you suggested to have a new leg state FORCED_PAUSED to differentiate from the existing PAUSED state. Probably there needs to be an additional leg event like FORCED_PAUSE too then. So the TO can inform the MP about the actual status. Would this new leg state be sufficient for a proper information of the user? Or would the TO send a notification anyhow? If a notification will be sent I don't see right now a real advantage of an additional leg event & state. But maybe I overlook something.

In addition the status of the bike looks the same: it's locked but still in rental. Only the user who rented it can open the bike. It's not shown as available to anyone else. But maybe there is one difference: what happens if the user doesn't react to the info that the leg can't be finished at this location? Will the leg ultimately be FINISHED by the TO? So is there a direct transition from PAUSED to FINISHED without reaching IN_USE again? And is this different compared to the PAUSED status? Are there any circumstances when a rental of a regularly paused bike is finished by the TO?

zerwuff commented 3 months ago

Hi, we send changes from any ’booking' or 'leg' events from TO to the MP, so actively pushing any information to the MP.

In our solution with the new leg type, the MP would just receive an POST /legs/events(type)=FORCED_PAUSE leg.memo="please move the bike from non-parking space" event from the TO. If the MP would fetch the leg by id, the leg state should be also FORCED_PAUSE to make it coherent states. You are right the API change needs to be about a new leg event type FORCED_PAUSE and a new leg state FORCE_PAUSE.

This would be sufficient for us. The MP's app could explicitly distinguish between the 2 pause types using this leg type, rather than use PAUSE, and parse the leg.memo, which could easily break- looking i18n and changing texts here. We would not send any additional notifications there, to keep it simple. The state would solve this for use, allowing a simpler solution rather than use multiple events here, which implies further logic of collecting events and interpreting them.

After a defined time if the user does nothing (in our case 24 hours), is exactly like you described: the leg will be ultimately FINISHED by the to, the MP gets also notified here. There is no SET_IN_USE leg event here.

By contrast, for a regular pause within the usual business zone, the MP sends SET_IN_USE leg to the TO to open the lock and continue the ride.

There are no other circumstances or business cases where a regular pause is FINISHED by the TO.

matt-wirtz commented 3 months ago

Hi! I now fully understand the use case and your reasoning for the new leg state FORCE_PAUSE. Should be applicable especially for shared bikes where it's quite common that by closing the lock manually the bike is returned (leg finished) - if parked properly etc. We will discuss this in the next WT1 meeting to see if there is something overlooked so far.

edwinvandenbelt commented 3 months ago

Hmmm. Personally, I don't think it is wise to add another event type (reflecting the status). Other MPs don't know (yet) how to handle it and it doesn't simplify the API. Isn't there another way?

What's the sequence? a) bike is IN_USE b) user locks the bike c?) TO detects that the bike is locked at an incorrect location (triggered by the bike?) => d) TO sends notification to the MP, MP shows it to the user e) MP fetches areas with limitations (GBFS or /operator/areas) f) user unlocks the bike and places it correctly

OR

a) bike is IN_USE b) user lock the bike c?) MP (user) sends /legs/{id}/events - FINISH => d) TO responds with a 400, indicating that the bike is placed incorrectly, MP shows this to the user e) MP fetches areas with limitations (GBFS or /operator/areas) f) user unlocks the bike and places it correctly

If the MP doesn't show it to the end user OR the end user doesn't replace the bike, a fine can be added to the /payment/journal-entry endpoint.

zerwuff commented 3 months ago

Hi @edwinvandenbelt the first option is the sequence. You are right, the incorrect location check is done by the bike and TO backend communication, this is handled according to bike position and known locations/areas in the TO backend. We want achieve multiple things: 1) actively communicate this situation directly afterwards from TO to MP by sending a special pause event (POST legs/events(type)=FORCED_PAUSE). This will pop up a dialog in the MPs frontend app. ("Hey, you tried to return this bike here, but this is not possible at this place. Please move the bike, the booking will run for 24 more hours.") 2) The booking shall remain CONFIRMED and shall not FINISH, because we want to a) motivate the user to move the bike, by leaving the booking running and an not accepting the return on that place . This is a relevant information that the end user needs to know.

Alternatively, for communicating this we could use the standard PAUSE event in combination with the field comment to mark this as a forced pause to the MP. This would be a non-standardized workaround, just for our case. This is doable, but i think this is a problem that bikesharing in general has and therefore looking for a new leg event type. Yes, it would bring more complexity to the API, since extending the leg type enum. But,we are not actively implementing all leg event types, for us its fine to skip the irrelevant ones, likely also the other TOs/MPs. I think the API is clearer then and tells the MP that particular type of the pause.

Edit (21.08.2024) Another solution to mark this clearer as a forced PAUSE, using the current to PAUSE leg type is by sending the field "userCommunication" and with the type "Instruction"

to MPs POST legs/events(type)=PAUSE. Therefore, this API model needs to be extended with the userCommunication field (this field exists already in the TO's side on GET legs/{id}). This would make the call more explicit and allow the MP implicitly to determine that he needs to show some instructions to the user in the PAUSE. So, together this would make:

TO -> MP POST: legs/events {type=PAUSE,userCommunication=Instruction,comment='Please move the bike out of this area'}