openmobilityfoundation / mobility-data-specification

A data standard to enable right-of-way regulation and two-way communication between mobility companies and local governments.
https://www.openmobilityfoundation.org/about-mds/
Other
687 stars 230 forks source link

Status change events for entering and leaving visible area #214

Closed rf- closed 5 years ago

rf- commented 5 years ago

Let’s call the area where a given Provider API consumer is allowed to see all status changes the “visible area” for purposes of this issue. This is likely to be the boundaries of the jurisdiction of the agency making the request, but it currently varies between providers.

Right now, it’s unclear what events should appear in the status changes API when a user reserves a vehicle and takes it out of the visible area or vice versa.

It seems like there are currently two possibilities:

In the first case, clients that want to put together a complete picture would need to observe that the trip ended outside the visible area and use that as a cue to not expect future events related to that vehicle (as opposed to the naive alternative of treating the vehicle as permanently available at whatever the end point of the trip was). This would work, but it depends on the provider and the client having a precise shared definition of the visible area, which doesn’t currently exist in MDS. Another downside of this approach is that, in the case where the user starts outside the visible area and then enters it, the provider will have to retroactively add a reserved event for the beginning of the trip once they see that the user has crossed into the visible area, making the contents of the feed inconsistent over time.

In the second case, the last event the client sees for a vehicle leaving the visible area is reserved. This prevents the client from using the status changes API to calculate whether the provider is compliant with vehicle limits, since they don’t know if or when the vehicle actually left the visible area, or when a vehicle entered the visible area before being dropped off. This also makes it difficult to validate the integrity of the event stream, since basically any event can follow a reserved event.

One solution to this problem would be to add explicit status change events when a vehicle leaves or enters the visible area, defined as the area within which the current API client is allowed to see events. The least invasive implementation would be to add a left_visible_area event type reason for the removed event type and an entered_visible_area reason for reserved. If that feels like an inappropriate use of removed, it could make sense to add a new event type instead.

marie-x commented 5 years ago

See the Agency spec. Relevant events are trip_leave (same as your left_visible_area) and trip_enter (entered_visible_area). Any trip_leave event changes the vehicle state to elsewhere. A vehicle in elsewhere can return to available via the usual events e.g. rebalance_drop_off, service_start, etc.

rf- commented 5 years ago

Interesting, so maybe the path forward is to port these events to the Provider side?

marie-x commented 5 years ago

I think so :)

hyperknot commented 5 years ago

This is a very important issue: the specs should contain exactly how trip_enter and trip_leave events are described in the Provider - Status Changes feed.

I agree there should be two new events: trip_leave or trip_enter inserted at the region boundary.

The only question is what happens with the out-of-region part of the trip: the user_drop_off and service_end events.

They can be either:

I recommend the Discard version as that would contain all information required to reconstruct the events of the vehicle, and would contain no duplication, hence no possibilities for conflicting data in the feed.

marie-x commented 5 years ago

Agree that anything between trip_leave and trip_enter should be discarded.

BTW Agency uses trip_start and trip_end rather than user_pick_up and user_drop_off btw. This should probably be reconciled. It wasn't flagged in the recent set of Agency PRs.

With trip_leave/trip_enter you have a new larger set of possibilities, which include but are not limited to: trip_start > trip_end trip_enter > trip_end trip_start > trip_leave trip_start > trip_leave > trip_enter > trip_end trip_start > trip_leave > trip_enter > trip_leave trip_start > trip_leave > trip_enter > trip_leave > trip_enter > trip_end

One question raised is: how do you know if a trip_leave definitively ends a trip? I think the answer is you need at least one more event (any event that's not trip_enter I think) to be fully certain.

I'm sure other questions have yet to even be identified. :)

hyperknot commented 5 years ago

That's an interesting issue, what happens when riding on the region boundary. From a trip point of view, I believe those events shouldn't be needed at all. That is, if someone went out of the region (or it was just a GPS error) and then came back within the same trip, then it shouldn't matter.

On the other hand, status changes feeds are about live-updates, so it might be needed.

dyakovlev commented 5 years ago

a way to address the border issue could be to establish a buffer zone along the boundary (e.g. 15m) and surface events pessimistically:

vehicles riding in the buffer would then be associated more tightly with the region they originated from.

the major problem with this approach is that it is heavily dependent on both GPS accuracy and consistent implementation across mobility providers.

hyperknot commented 5 years ago

Why not just add these events after the trip has finished? That would be a very clean solution, avoiding all the recursive in-out-in-out algorithms, which would be quite hard to implement correctly, both for providers and agencies or processors. and would just be asking for bugs.

So I recommend:

At trip start:

At trip end:

I strongly believe that anything more complicated than this will not be consistently implemented across providers / agencies and will just create unreliable data.

dyakovlev commented 5 years ago

The major pain point with that approach is alluded to in the op:

Another downside of this approach is that, in the case where the user starts outside the visible area and then enters it, the provider will have to retroactively add a reserved event for the beginning of the trip once they see that the user has crossed into the visible area, making the contents of the feed inconsistent over time.

Some providers are already adding events retroactively to their feeds for trips that begun outside regions, and those events are already being missed by data ingestion setups that haven't been prepared with that consideration in mind, which is already confounding a variety of analysis scripts.

Ideally, whatever solution we arrive at gets to a final decision about event inclusion in a particular feed as close to the event time as possible.

marie-x commented 5 years ago

Agency distinguishes between reserved and trip states. In Agency, reserved means "reserved but not yet picked up" while trip means "in an active trip". This underscores the need to invest in reconciling the current Agency and the current Provider.

hyperknot commented 5 years ago

Some providers are already adding events retroactively to their feeds for trips that begun outside regions, and those events are already being missed by data ingestion setups that haven't been prepared with that consideration in mind, which is already confounding a variety of analysis scripts.

Yes, I agree. Many status change events are only reliably calculated once the trip has completed. For example, for technical reasons, associated_trips usually requires a trip_id from a database, which only exists once the trip has been finished and calculated.

dyakovlev commented 5 years ago

I think it would be reasonable for there to be a trip_id in a database somewhere as soon as a user reserves a vehicle, especially if providers are doing their own internal telemetry and data warehousing.

hyperknot commented 5 years ago

I think it would be reasonable for there to be a trip_id in a database somewhere as soon as a user reserves a vehicle, especially if providers are doing their own internal telemetry and data warehousing.

But you cannot require providers to architect their database systems based on one idea we have. I believe most of them will correct information in user_pick_up events when the trip completes. And also I believe it is much better this way, for the reliability of the data.

marie-x commented 5 years ago

But you cannot require providers to architect their database systems based on one idea we have.

LADOT is not requiring providers to "architect their database systems" a particular way. LADOT is requiring providers to comply with the MDS Agency standard. The MDS Agency standard, as written, says that when a provider submits trip-related events, those events will contain a UUID4 trip_id created by the provider. Determining rides is ambiguous when rides can span jurisdictions. E.g. you won't be able to distinguish trip_start > trip_leave > trip_enter > trip_end (one trip) from trip_start > trip_leave followed by trip_enter > trip_end (two trips) without it. Adding a UUID4 to trip data is not particularly onerous relative to the overall Agency implementation requirement.

hyperknot commented 5 years ago

Of course not, it's an absolutely important requirement. All I said was that technically most providers might only fill in the associated_trips after the trip has completed. BTW, anyone with insider knowledge, why is it associated_trips and a list and not associated_trip and a string? How could a user_pick_up or user_drop_off event possibly be in multiple trips?

dyakovlev commented 5 years ago

BTW, anyone with insider knowledge, why is it associated_trips and a list and not associated_trip and a string? How could a user_pick_up or user_drop_off event possibly be in multiple trips?

this has been discussed but has not made it into dev or any release yet - https://github.com/CityOfLosAngeles/mobility-data-specification/pull/202, https://github.com/CityOfLosAngeles/mobility-data-specification/issues/88

We should probably circle back to the original issue. It would be useful to hear from some mobility providers and agencies about their thoughts on events as they relate to region crossing. If they think this would be a functional way to approach it, porting the applicable events from agency API for an upcoming release seems like it would be a reasonable way forward.

asadowns commented 5 years ago

I think @hyperknot 's suggestion here makes the most sense to use with a couple of additional thoughts.

Why not just add these events after the trip has finished? That would be a very clean solution, avoiding all the recursive in-out-in-out algorithms, which would be quite hard to implement correctly, both for providers and agencies or processors. and would just be asking for bugs.

So I recommend:

At trip start:

  • user_pick_up, if within the region.

At trip end:

  • if the both endpoints of the trip were within the region, user_drop_off
  • if one endpoint was inside, one was outside, add exactly one trip_enter / trip_leave event, and do not add user_drop_off / user_pick_up at that end.

I strongly believe that anything more complicated than this will not be consistently implemented across providers / agencies and will just create unreliable data.

The one alternative I think we'd propose is instead of trip_enter and trip_leave stick with the existing service_start and user_pick_up in place of trip_enter and service_end and user_drop_off on trip_leave. While I understand the semantic value of knowing when trips entered or left the fact that the other status_changes currently exist in the specification means that providers can implement this solution today rather than wait for more event_types and event_type_reasons to be added.

Another benefit here is that reconciliation is easier since trip_enter and trip_leave both conceivably now need to affect two multiple other events for instance trip_leave needs to be used to end the current trip window and end the current service window. Using a simultaneous service_end and user_drop_off while less semantic keeps the 1:1 relationship between paired events.

That being said, I think trip_enter and trip_leave could be an excellent solution for 0.3.0 and forward.

As @dyakovlev mentioned this is already something we are trying to do in our feed. It is obviously more difficult to do in near-time. While MDS specifies the Provider API is for "historical" data there is no clear definition of what this means.

I would propose a two-pass approach data is available very quickly (a few minutes) to allow Provider data to be used for more real-time analysis but that agencies and aggregators understand to pull data 24 hours after the event_time requested to get all historical reconciliations.

hyperknot commented 5 years ago

I think an almost-instant feed + a 24 hour reprocessing sounds reasonable. This way both up-to-date data can be fetched, as well as reliable, processed historical data for calculations after 24 hours.

hyperknot commented 5 years ago

I also agree that it's great to have the temporary solution of user + service event at the boundary. Only two important remarks about that:

rf- commented 5 years ago

Thanks so much for the discussion, everyone! I see a few key issues to deal with here:

Porting the trip_enter and trip_leave events

As a first step, the least invasive approach would probably be adding the following events:

event_type event_type_reason Description
reserved trip_enter Customer enters a service area managed by agency during an active trip.
removed trip_leave Customer leaves a service area managed by agency during an active trip.

This would require redefining removed to encompass cases other than the device physically being removed from the street.

The next step would be to more broadly harmonize the Agency and Provider event schemas; I have thoughts about how to do this that I’ll save for another issue.

Enter/leave event semantics

I think @hyperknot’s proposal defining all trips in terms of two events at the endpoints makes sense, but the downside (in addition to timeliness stuff that would be nice to avoid getting into here if we can help it) is that API consumers can’t use the status changes API to get an accurate picture of how many vehicles were on trips within the area managed by an agency at a specific time.

To address a couple of specific points:

One question raised is: how do you know if a trip_leave definitively ends a trip? I think the answer is you need at least one more event (any event that’s not trip_enter I think) to be fully certain.

This is true but seems OK to me. If a client wants to track the lifetime of a particular trip, they can use the trips API to get that information retroactively. The purpose of the status changes API is to track the statuses of vehicles within the visible area, so once a vehicle has left that area anything that happens afterwards (until/unless it re-enters the area) seems like it doesn’t belong in the API.

I strongly believe that anything more complicated than this will not be consistently implemented across providers / agencies and will just create unreliable data.

We’ve internally kicked around both the physical buffer zone idea and a time-based debouncing idea and decided that both of these approaches are potentially beneficial but feel too complicated to bake into the spec. It seems OK to start with the assumption that providers will publish enter/leave events for every boundary crossing, while leaving room for providers to deduplicate noisy enter/leave events if they want to.

My suggestion:

What providers should do

I think @asadowns’s proposal of pairing service_start/service_end/user_drop_off/user_pick_up events makes sense as a workaround for now, as long as providers transition to real enter/leave events as soon as the relevant version of the spec is released.

hyperknot commented 5 years ago

Thanks for the detailed writeup @rf- . While it looks nice from a theoretical specs point of view, I honestly cannot imagine providers being able to offer a reliable source for the multiple trip_enter, trip_leave case. I can guarantee you that there'll be all possible combinations of user_pick_up, user_drop_off, trip_enter, trip_leave events, just like how today you can see all the possible combinations of user_pick_up, user_drop_off, service_start, service_end, including all the duplicates.

At this point, why not just simplify the whole problem by adding a key like visibility_event=leave, and simply issuing a trip_end + service_end event combination if the trip ended outside the region? That seems to be a simpler and much more reliable idea.

rf- commented 5 years ago

If I understand correctly, you're suggesting a sequence of (using the Provider nomenclature) an available/user_drop_off/visibility_event=leave event followed by a removed/service_end/visibility_event=leave event that should be interpreted by clients as a single event representing the vehicle leaving the visible area. I'm not sure I understand the benefit of adopting that convention over adding a single event that represents the same information.

hyperknot commented 5 years ago

No, what I recommend is what some providers are already doing: inserting a user_drop_off + service_end event when a vehicle leaves the visible area.

The only improvement I'd propose, is to add a new key/property visibility_event within these evens, so they'd look something like this:

{
  "provider_id": "...",
  "provider_name": "...",
  "device_id": "...",
  "vehicle_id": "...",
  "vehicle_type": "scooter",
  "propulsion_type": ["electric"],
  "event_type": "removed",
  "event_type_reason": "service_end",
  "event_time": 123.0,
  "event_location": {},
  "battery_pct": 0.12,
  "visibility_event": "leave"
}
marie-x commented 5 years ago

Here is the current thinking at Ellis (who are building the reference implementation of MDS-agency for LADOT):

trip_leave and trip_enter are the visibility events.

Sending user_drop_off + service_end to indicate leaving a visible area is misleading, though in the absence of trip_leave it's an understandable work-around. But this will not be considered compliant behavior for agency.

If a ride starts in one jurisdiction and ends in another, the sequence should be trip_start -> trip_leave That will leave the vehicle in the elsewhere state, which will not count against caps.

A provider trip can be deduced from those two events. We might want to have some sort of tag or bool on trip to indicate that it's boundary-crossing trip with incomplete path information.

marie-x commented 5 years ago

Separately, I'm not following the line of "all the recursive in-out-in-out algorithms" as relates to multiple trip_leave / trip_enter pairs. Can someone ELI5 that?

rf- commented 5 years ago

I think complexity with multiple trip_leave/trip_enter events only exists to the extent that the provider wants to avoid publishing a noisy event stream if the vehicle is frequently crossing the boundary. Just publishing one enter or leave event every time there's an observed point that's on the other side of the boundary from the previous observed point seems straightforward (and IMO should be the baseline expectation in the spec).

toddapetersen commented 5 years ago

@rf- Sounds like general agreement. Any unresolved issues here?

hunterowens commented 5 years ago

Hi everybody-

This is a great discussion. There is actually some intentional divergence between the provider and agency specs here, since agency is about giving future permission while provider is about providing reporting information about historical ops.

Referring directly to provider, there are two streams of data. status_changes events which only should be for locations inside visible area, and trips, which should be in the API given any of following conditions being met (at least for the City of LA and SM rules/regs to my knowledge).

1. Trip starts/ends within the visible area. 
2. Trip has an observed GPS point within the visible area. 
3. Trip crow fly path intersects visible area. 

Give this, it is fairly easy to construct a record of when devices left and entered the public right of way or jurisdiction by preforming a join. See SM's implementation for more details.

Let me know if I missed any nuance or anything after reading this thread over. Going to close for now.

hyperknot commented 5 years ago

@hunterowens SM's implementation will only work if the providers choose to terminate trips at city boundaries and add a service_end events afterwards.

That is a good solution, I actually recommend that. Still, we need to put it to specs. Right now these two points are not written anywhere for providers. Here is my go at it:

Specs:

  1. When vehicle leaves a visibility area, the status changes stream should terminate the trip at the boundary and insert a service_end event one second after the trip's end.

  2. When a vehicle enters the visibility area, the status changes stream should only start the trip from the boundary, and insert a service_start event one second before the trip's start.

marie-x commented 5 years ago

@hyperknot I'll ask @toddapetersen to open a PR to add this clarification -- thanks!

Just a clarification, the provider will stop sending events and telemetry once a jurisdiction is exited, but you don't necessarily want to terminate the trip, because the rider could return to the original jurisdiction. Those events (e.g. trip_enter and trip_end) would use the same trip_id. Is that your understanding?

hyperknot commented 5 years ago

@karcass this is not what @hunterowens proposes. There will be no new events, like trip_enter or trip_end in the Prover specs.

A trip will always have exactly one start and one end (the user_ events). If a trip crossed the boundary, it's start or end will be adjusted to be on the boundary and a service_start or service_end event will be inserted. While the vehicle is outside the boundary, there will be no events in the stream, it's last event will be service_end, until it returns to the visible area.

marie-x commented 5 years ago

Sounds great! I'm not trying to over-complicate provider. Agency has somewhat different goals and constraints, and should be constructed as such. IMHO YMMV etc.

rf- commented 5 years ago

I think folks are talking at cross purposes a bit here.

My understanding of @hunterowens' position (which may be inaccurate) is that providers shouldn't emit any special events at service boundaries, but that API consumers should be expected to combine data from both the status changes endpoint and the trips endpoint if they want to put together a full picture. This is very different from (and contradictory to) asking providers to emit service_start and service_end events at service boundaries.

If the decision ends up being not to add trip_enter/trip_leave events to the status changes API on the grounds that they're redundant with information from the trips API, that does not constitute an endorsement of providers using service_start and service_end events to indicate the same information.

marie-x commented 5 years ago

Yeah, in the context of agency I do not want service_start and service_end events at jurisdictional boundaries, just trip_leave and trip_enter. In the context of provider I am agnostic.

hyperknot commented 5 years ago

@rf- I agree. If there will be no service_end added and no new event types, then the only way to figure out if a trip crossed the boundary is to do geospatial lookup on the trips data and combine it with status changes intelligently. It's doable but I believe totally against the point of the specs, that is to make agency-prover communications simple and transparent.

thekaveman commented 5 years ago

@hyperknot I'm a little late to this discussion, but just want to chime in on your point:

@hunterowens SM's implementation will only work if the providers choose to terminate trips at city boundaries and add a service_end events afterwards.

This interpretation is incorrect. Santa Monica's implementation works on two distinct windows: inactive and active, each calculated using a part of MDS Provider:

inactive_windows are constructed via Status Change events, and represent time that a device is sitting in the PROW awaiting further activity. For example, the window between (available, service_start) and (reserved, user_pick_up); or the window between (available, rebalance_drop_off) and (removed, rebalance_pick_up).

active_windows are constructed via Trips, much like you suggest in your comment above. This ensures that only the time the device is active within the geo-boundary (Santa Monica in this case) is what is counted.

Additional Status Changes at the boundary are spurious and would not even play a role in the calculation. Further, it represents an inaccurate view of the activity/fleet - e.g. service_end is clearly defined as the device is no longer in service, not that it (even temporarily) moved out of a service area. Identifying (via trip.route) exactly where and when a device crosses a boundary is out of the box functionality with any geo-processing library/database.

hyperknot commented 5 years ago

@thekaveman without additional service_end like events, how do you count the following device:

before 13:00 ready to be picked up, inside PROW 13:00 trip start, inside PROW 14:00 trip ends, outside PROW 16:00 trip starts, outside PROW 17:00 trip ends, outside PROW 19:00 trip starts, outside PROW 20:00 trip ends, inside PRO after 20:00 ready to be picked up, inside PROW

thekaveman commented 5 years ago

@hyperknot I will assume that by "inside/outside PROW" what you mean is "inside/outside the boundary", e.g. inside/outside the city limits of Santa Monica. Under your scenario:

before 13:00 ready to be picked up, inside PROW

So we have an available Status Change sometime before 13:00 (let's say 12:00). There are four different kinds of available events, doesn't matter which one it was.

13:00 trip start, inside PROW

This implies a (reserved, user_pick_up) event, so our first inactive_window is defined from 12:00 to 13:00.

14:00 trip ends, outside PROW

This implies there was a final route point inside the boundary (just before it crossed out), let's say it was at 13:55. So the first active_window is defined from 13:00 to 13:55.

16:00 trip starts, outside PROW 17:00 trip ends, outside PROW

None of this data is seen, the device is not counted.

19:00 trip starts, outside PROW 20:00 trip ends, inside PRO[W]

Again, this implies there was an initial route point inside the boundary (just after it crossed in), let's say it was at 19:05. So another active_window is defined from 19:05 to 20:00.

after 20:00 ready to be picked up, inside PROW

Status Change of (available, user_drop_off) defines an open inactive_window, awaiting the next event (pick up, service end, etc.)

Windows:

12:00 - 13:00 (inactive) 13:00 - 13:55 (active) 19:05 - 20:00 (active) 20:00 - open (inactive)

hyperknot commented 5 years ago

@thekaveman I agree with your calculation and this is exactly the right logic we need to use.

Where I don't agree is your usage of let's say it was at 13:55 and let's say it was at 19:05. This whole discussion is about specifying those events, so they are no longer "let's say".

How can you possibly calculate those let's say moments.

A. The provider cuts the trip at the boundary and inserts a service_end like event at those moments. B. The provider adds a new event type like trip_enter or trip_leave which defines exactly those moments. C. We combine the trips stream and the status changes stream and do a geospatial processing on the trip's route data, which - if reliable and high resolution enough (!) - can give us both a position and a point in time when the vehicle crossed the border. Then we internally generate events for solution A. or B.

Point C. also requires to have a reference GeoJSON/WKT file about the exact boundary for each city, and it also means that we are dropping the user_ events from status changes stream, because we are substituting those events from the trips stream.

You are right C. could work, but this whole issue is about why C. is not a good solution, and why we recommend choosing A. or B.

thekaveman commented 5 years ago

Where I don't agree is your usage of let's say it was at 13:55 and let's say it was at 19:05. This whole discussion is about specifying those events, so they are no longer "let's say".

This was for our discussion here in the thread, using the scenario that you laid out.

In the real world, we don't have before 13:00 ready to be picked up, inside PROW as you state. We have an exact time, before 13:00, that the device became available and ready to be picked up. Similarly, the device doesn't magically teleport from inside the boundary to outside - there is a precise time (and place) that this occurs (whether through user action, or company/provider action).

In my answer to your scenario, I picked the times (e.g. 13:55, 19:05) for illustrative purposes - it really doesn't matter what we choose, the logic is exactly the same. Again, in the real world, this is (should be, if providers are sending accurate and valid MDS feeds) a known time.

thekaveman commented 5 years ago

Point C. also requires to have a reference GeoJSON/WKT file about the exact boundary for each city

Seems like a bare minimum requirement for a company operating physical devices in geographic space, driving user engagement via geo-locating technology, to know the geographic bounds within which they operate. I would be willing to bet that nearly every municipality looking to participate in MDS already has this defined and readily accessible as data (maybe not GeoJSON/WKT as there are a lot of ESRI shops out there, but conversion tools are widely and freely available).

and it also means that we are dropping the user_ events from status changes stream, because we are substituting those events from the trips stream.

Quite the opposite. The user_ events are critical to the definition of inactive_windows (again, based on Status Changes) - a device becomes inactive when an (available, user_drop_off) event occurs (among others). Similarly, a device ceases to be inactive when a (reserved, user_pick_up) event occurs (among others).

I am still not seeing how additional event types or "fake" events at the boundary clarify any of this. Status Changes and Trips were specified as they are for exactly this reason - so they can be used together to get a complete picture of historical device activity.