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

ScheduledSession <-> Event reclassification #264

Open
nickevansuk opened this issue Mar 8, 2021 · 5 comments
Open

ScheduledSession <-> Event reclassification #264

nickevansuk opened this issue Mar 8, 2021 · 5 comments

Comments

@nickevansuk
Copy link
Contributor

nickevansuk commented Mar 8, 2021

Background

The types of opportunities in OpenActive have a specific semantic meaning:

  • SessionSeries and ScheduledSession: This is an event (usually 1 or 2 hours in duration), that happens with some regularity. It is expected that if a user attends one of these events, that they would find another event in the series happening soon afterwards. [ref]

  • Event: These events are one-off occurrences, e.g. a fun run organised by a local group may run as a standalone event on a particular date or an organisation that runs many individual events e.g. Human Race, Race for Life, International Women's Day. [ref]

  • CourseInstance: Courses generally involve an upfront commitment to a number of sessions, with a consistent attendee group and/or organisers for the duration of the course. [ref]

Within booking systems, the difference between an Event and a ScheduledSession is usually as subtle as whether the Event is part of a recurrence rule - often the user can tick a box to say "repeat every Tuesday" as they can in Google Calendar, and an Event then becomes a ScheduledSession.

The current guidance for this scenario is for the booking system to remove the Event from their Events feed, and add it into their SessionSeries/ScheduledSessions feed.

According to Bookwhen this is not an uncommon occurrence, e.g. an organiser scheduling the first Event, and then deciding to add a recurrence to it afterwards for it to become part of a SessionSeries. Potentially bookings have already been made against the Event when this happens.

Challenges

Within the booking system an Event and a SessionSeries/ScheduledSession are usually represented by the same underlying structure. Forcing the same underlying structure to output two feeds can create extra work for the booking system, and also is in opposition to one of the principles of RPDE (which exists for the same reason):

Does your implementation match your internal state as closely as possible (i.e. you are not generating sessions that don't actually yet exist as records in your system from a recurrence rule, but are instead providing the recurrence rule data directly). [ref]

When an Event becomes a ScheduledSession and visa versa, the current guidance implies that the item be marked as deleted in its current feed, and a new item (with a new @id) be added to a new feed. When the Open Booking API is implemented, changing the @id of an item will break Change of Logistics Notifications (openactive/open-booking-api#138) (e.g. if an Event becomes a ScheduledSession then its location is updated).

Potential solutions

If we want to retain the semantics of Event and its subclasses, given that it is possible for systems to switch between the types after bookings have been made, one potential solution is to allow an opportunity to have its @type changed, while maintaining its @id.

The feed split could then be:

  • SessionSeries
  • ScheduledSession + Event (where this feed contains both types, with the same @id namespace used for both)

When an Event becomes a ScheduledSession in the second feed, it is then linked to the schedule it is now part of (SessionSeries). When a ScheduledSession becomes an Event in the second feed, it is effectively unlinked from the schedule, as it is a one-off session.

(N.B in the case of Bookwhen this doesn’t actually help as their Events feed and ScheduledSession feed are driven from two different underlying database tables)

Further investigation is required to determine whether having a JSON-LD object change @type while maintaining its @id is (a) allowable and (b) best practice

@nickevansuk
Copy link
Contributor Author

nickevansuk commented Mar 8, 2021

Further note on this:

  • The current C1, C2, P and B endpoints, and the Orders Feed of the booking spec all require opportunities to be referenced using a pair of @type and @id. Hence if an Event was reclassified, the Orders Feed would also need to be updated with the new @type.

The reason for reflecting the structure of C1, C2, P and B endpoints for @id references simplifies use of the model for both requests and responses, e.g:

  "orderedItem": [
    {
      "@type": "OrderItem",
      "acceptedOffer": {
        "@type": "Offer",
        "@id": "https://example.com/events/452#/offers/878"
      },
      "orderedItem": {
        "@type": "ScheduledSession",
        "@id": "https://example.com/events/452/subEvents/132"
      }
    }
  ]

This is in contrast with the referencing approach used in feeds, which uses the @id as a direct reference e.g. for superEvent below:

"data": {
  "@context": "https://openactive.io/",
  "@type": "ScheduledSession",
  "@id": "https://id.ourparks.org.uk/api/session-series/1234#/subEvent/C5EE1E55-2DE6-44F7-A865-42F268A82C63",
  "superEvent": "https://id.ourparks.org.uk/api/session-series/1234",
  ...
}

A move towards internal consistency would be to use the following in the Orders feed (and also potentially in C1, C2, B, P etc too):

  "orderedItem": [
    {
      "@type": "OrderItem",
      "acceptedOffer": "https://example.com/events/452#/offers/878",
      "orderedItem": "https://example.com/events/452/subEvents/132"
    }
  ]

A move towards external consistency would be to use the following in all feeds and APIs (as per this example):

  "orderedItem": [
    {
      "@type": "OrderItem",
      "acceptedOffer": { "@id": "https://example.com/events/452#/offers/878" },
      "orderedItem": { "@id": "https://example.com/events/452/subEvents/132" } 
    }
  ]

Both of the above approaches are valid JSON-LD (https://www.w3.org/TR/json-ld/#advanced-concepts), however the latter does not require changes to the @context to support it in different scenarios (though the changes to @context would be trivial to make, and are already set on several OpenActive terms) (N.B. this is not a comment on the validity of changing the @type)

Although @type in the Orders Feed can help data consumers to match the @id to the relevant feed, a data consumer will want to compare all received @id to their stored Orders rather than the other way around, which means this is unlikely to be used for this purpose.

@nickevansuk
Copy link
Contributor Author

nickevansuk commented Mar 8, 2021

Given that @type is optional in JSON-LD, can be an array, and in some cases can be inferred (https://www.w3.org/TR/json-ld/#specifying-the-type), it seems that reassigning the @type is not an issue here?

@nickevansuk
Copy link
Contributor Author

An issue with using the same @id namespace across multiple feeds is that RPDE is not designed to resolve conflicts between feeds - the modified timestamp is only local to the feed (and globalising the scope of modified creates sync issues for data users consuming multiple parallel feeds, as well as breaking the RPDE transport-level abstraction). Therefore each feed requires a unique @id namespace to ensure that the latest data is discernible to the data user.

Another option here is to simply use the Replacement within the Orders feed when a booked event changes type. This would mean that the @type and @id could then be easily updated when an Event is recategorised. This would trigger a notification to the user by default, however.

@thill-odi
Copy link
Contributor

Reviewing this thread, it doesn't appear to me that there are any good options for changing @type unobtrusively.

The chief difficulty is that at the very least we're forcing data consumers to consume all feeds, on the off-chance that one of the items they've harvested has changed @type and henceforth jumped feeds.

It would be possible to get around this to some degree by ensuring all changes are listed in the Orders feed, but problems of modelling arise: if the change from e.g. Event to CourseInstance includes the addition of Course-specific data, consumers developed to process Events will not be able to parse the relevant data. This points to a larger problem of differentiating between cases where the change in @type is trivial (and hence shouldn't trigger operations knock-ons such as automated emails, cancellations, etc.) and where it's significant (and therefore should).

On reflection, the guidance should simply be that changes in @type result in:

  • deletion of the former opportunity (explicitly flagged in the feed, as per the RPDE specification) with the old @type
  • creation of a new opportunity in its place, using the new '@type`

This implies that all @type changes will be significant: bookings will be cancelled in the event of a @type change and so forth. Given the difficulty of automatically determining which changes should be considered significant and which not, however, this is preferable to the alternative of treating all changes as __in__significant.

Interfaces that allow users to change @type easily, however, should probably add guidance or e.g. modal dialogue boxes warning users of the possible consequences of @type changes.

@nickevansuk
Copy link
Contributor Author

nickevansuk commented Mar 18, 2021

Further alternative solutions are considered below:

Potential Solution 1: Represent the “Event” as a SessionSeries with a Schedule with repeat count 1

Issues:

  • This would require redefining the “Event” type such that booking systems that only support “Event” (with no recurrence support), would need to include a fake “Schedule” which is not actually a schedule.
  • Problematic from a data modelling perspective (and hence will likely cause issues as the specification evolves), as the SessionSeries is not in fact a series of sessions, and the Schedule is not in fact a schedule.
  • Doesn’t solve CourseInstance recategorisation
  • Breaking change to Modelling Opportunity Data 2.0

Potential Solution 2: Represent the “Event” as a SessionSeries without a Schedule and with a single ScheduledSession, and rename “SessionSeries” to something that infers both.

Advantages:

  • No fake “Schedule” required

Issues:

  • Loses the semantic distinction between SessionSeries and Event based on type - which has implications for data processing, filtering, etc.
  • Complicates the structure of an “Event”, as it will need an array constrained to only one subEvent, which is not good modelling practice (and hence will likely cause issues as the specification evolves).
  • Validation made more difficult
  • In Bookwhen’s implementation, would require ScheduledSession to feature in both the SessionSeries feed as a subEvent, and in the ScheduledSession feed.
  • This complicates data consumption - keeping track of the subEvent being removed from embedding in one entity and appearing later standalone in a different feed. And so biases the complexity further towards data consumers
  • Issues also apply if the approach was extended to CourseInstance recategorisation
  • Breaking change to Modelling Opportunity Data 2.0

Potential Solution 3: “Delete and recreate the opportunity” with a new opportunity using a new @type and @id. Use a mechanism similar Open Booking API “Replacement” when an opportunity changes an @type and @id to update the data user with the new @id

Advantages:

  • This does solve CourseInstance recategorisation, in the recategorisation to CourseInstance, not from CourseInstance (as the CourseInstance is no longer bookable, those booked onto the Course cannot be reassigned)

Issues:

  • Race conditions relating to items jumping between feeds and then being immediately referenced in the Orders feed make solution inherently brittle
  • “Replacement” might be made to opportunities that are outside of the feeds consumed by the Broker - it would require a Broker to consume all feeds in order to support bookings from any one feed (and not to filter any of those feeds either)

Potential Solution 4: Combine types in the same feed - e.g. SessionSeries and Event, sharing the @id between types

Would still require separate RPDE items and deletion for the different types within the same feed unless @ids could be reused between types in the same feed

Advantages:

  • Conforms to modelling spec
  • Does not create extraneous deletion issues
  • Conforms to RPDE

Issues:

  • Makes it more difficult for consumers to filter on specific types - though in-stream filtering would make this easy to do
  • Complicates dataset site spec
  • @ids still jump across feeds, though at least only between dependent feeds (SessionSeries and ScheduledSessions) - which means still need to keep track of deletes in each feed - and deal with associated race conditions - so doesn’t solve the problem
  • Doesn’t solve CourseInstance recategorisation

Potential Solution 5: Keep the same @id and change the @type, meaning that items jump between the current structure of feeds

Advantages:

  • No need to do a “Replacement"
  • This does solve CourseInstance recategorisation

Issues:

  • Data consumers would need to track @ids across types, which has implications on their storage and indexing (e.g. they can’t treat different types separately)
  • A data consumer would need to consume all feeds and data, as there’s no guarantee of where an @id would be moved to
  • Race conditions of feed jumping still exist
  • Also doesn’t solve the problem of what to do if someone has booked on a CourseInstance (bookable) that is converted to a SessionSeries (not bookable)

Potential Solution 6: Keep the same @id and change the @type, and unify feeds around parents/child: so one feed of Event+ScheduledSession, another for CourseInstance+SessionSeries

Allow types to change within the same feed, just as updates. This lessens the importance of the “type” attribute for how it is when storing data.

Could prioritise the latest @id based on the “modified” timestamp for conflict resolution, which means it doesn’t matter which feed it comes from (though processing “delete” across feeds is difficult - if one feed deletes it and it appears in the other, what then? RPDE doesn’t currently support cross-feed deletes)

Issues:

  • Not all implementations will have the underlying database structure necessary to combine feeds in this way - which complicates data publishing - however arguably we can provide the flexibility for representation here
  • e.g. in the case of Bookwhen this doesn’t actually help as their Events feed and ScheduledSession feed are driven from two different underlying database tables
  • Complicates consumption of only one type of data (e.g. courses only), as feeds combine all data
  • Different types in the same feed also make data consumption more difficult
  • Complicates dataset site spec

Potential Solution 7: "Delete and recreate the opportunity” with a new opportunity using a new @type and @id. Force Seller-requested Cancellation when an opportunity changes classification, and warn the admin user in advance of this happening to discourage them doing so for an event that already has bookings associated

Advantages:

  • Greatly simplifies implementation
  • Does not require broker consumption of all feeds

Issues

  • Not possible to reclassify events after bookings have been made

Potential Solution 8: “Replacement” includes further Change Of Logistics notifications embedded in the Orders feed

Simple implementation:

  • If “Replacement” is not supported Change Of Logistics is achieved via the RPDE feed.
  • Might be too much of a tall order to ask for Orders and open data RPDE feeds to maintain conflict resolution for Change Number, as they are likely sourced from different data stores. This means that only one source or the other must be used (and hence if implementing (cross-type?) replacement, Change Of Logistics for replaced items must all be done via the Orders Feed, and so the opportunity data is always included in the feed in this case.)
  • This intuitively makes sense, as if the booking system has instigated the replacement, it should not expect the broker to hunt down the ID across all known feeds and track it further (especially when it might have filtered out the same ID)
  • If only the type changes in the Orders feed, the Customer should not be notified. Change of Logistics rules + Change of Offer rules apply
  • Do replacement Order item opportunities exclude remainingAttendeeCapacity or remainingUses? Almost certainly

Advantages:

  • Does not require broker consumption of all feeds unfiltered (though still requires broker to recognise all @types, which is perhaps less of a tall order? Perhaps this should be feature flagged?)
  • Also does not require broker to have stored data from all feeds (e.g. if they used in-stream filtering) - which is a real advantage for geographically local implementations

Issues:

  • Complicates implementation for booking systems (as updates need to be made to Orders feed in some / all? cases)

Summary

Potential Solution 7 and Potential Solution 8 have the least number of issues and complexity.

Hence, if both were supported, the tradeoff for the booking system is between:

  • Implement “replacement” and hence opportunities in Orders feed, which allows the item to be changed
    • Though for recategorisation from CourseInstance to SessionSeries, all course bookings must be cancelled
  • Disallow recategorisation for events with bookings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants