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

[GTFS-Fares v2] Add rule_priority to fare_leg_rules.txt #418

Merged

Conversation

tzujenchanmbd
Copy link
Collaborator

Context

The rule_priority field is originally derived from Ito World's "override" proposal, it is used to simply represent cases when some matching rules need to take precedence over other matching rules.

During the discussion of the rule_priority proposal, the working group identified issues regarding backward compatibility and how to align with the existing empty semantics(“anything except…”) of network_id, from_area_id, and to_area_id. In the meeting on August 22nd, the working group reached a consensus on the need for a rule_priority field and the mechanism for changing empty semantics. It was agreed that this mechanism should not cause breaking changes for feed producers. After this discussion, we have also drafted this spec modeling proposal.

In the meeting on September 26th, there was agreement on moving forward with Option 1 in the spec modeling proposal for changing empty semantics (rule_priority field existence as a trigger)

Changes in this PR

  • Add rule_priority field in fare_leg_rules.txt
  • Modify fare_leg_rules file description to indicate the difference between rule_priority and existing empty semantics
  • Modify fare_leg_rules.network_id, fare_leg_rules.from_area_id, fare_leg_rules.to_area_id field description to indicate the difference between rule_priority and existing empty semantics

This PR uses the existence of the rule_priority field as a trigger to change existing empty semantics.

What is not included in this PR

  • scope or rule_priority_scope field
  • rule_priority in fare_transfer_rules.txt

The working group agrees that we can consider introducing rule_priority for fare_transfer_rules after gaining a better understanding of the mechanism behind fare_transfer_rules.

Validator rule

We noticed that producers might directly include field headers (without any values), but failed to realize this could pose in altering the existing empty semantics. Therefore, the description of rule_priority included the sentence "If the rule_priority field exists, it should not have all records entirely empty." When the rule_priority field exists, but all records across the files are empty, the validator will trigger a warning.

For previous discussions, please see issue#383 and working group meeting minutes.

Please go through the changes and feel free to share your thoughts/questions here.

@tzujenchanmbd tzujenchanmbd added the Extension: GTFS-Fares Issues and Pull Requests that focus on GTFS-Fares Extension label Dec 19, 2023
@tzujenchanmbd tzujenchanmbd linked an issue Dec 19, 2023 that may be closed by this pull request
@eliasmbd eliasmbd added Status: Discussion Issues and Pull Requests that are currently being discussed and reviewed by the community. Change: Addition New function proposed to the specification. GTFS Schedule Issues and Pull Requests that focus on GTFS Schedule labels Jan 9, 2024
@westontrillium
Copy link
Contributor

In the following case:

fare_leg_rules.txt

leg_group_id network_id fare_product_id rule_priority
a route_1 one-way_general 3
a route_1 20dollar_card 2
a route_1 monthly_pass 1

All fare leg rules are equally valid for the same trip, but different fare products are available. Does adding rule_priority then just determine display order, or would a consumer only be expected to display the highest priority option (one-way_general, in this case)?

@tzujenchanmbd
Copy link
Collaborator Author

tzujenchanmbd commented Feb 20, 2024

@westontrillium To my understanding, rule_priority serves to implement "override" functionality rather than to "sort" search results. In other words, consumers will only be expected to display the highest priority option. In the example above, since "all fare leg rules are equally valid for the same trip," these three fare products should be modeled with the same rule_priority.

rule_priority can be used in scenarios like the following: From 11:00 to 15:00, there is cheaper fares available, and the all-day fare can still be used during this time frame. However, from the riders' perspective, when I ride at 11:30, the trip planner should not present the more expensive all-day fare. Therefore, fares from 11:00 to 15:00 have a higher priority and "override" the all-day fare.

leg_group_id fare_product from_timeframe_group_id to_timeframe_group_id rule_priority
1 all_day_single  
1 offpeak_special_single 11_to_15   

Regarding how to present all candidate fares on the frontend (e.g., sorting, menus, defaults, etc.), I am inclined to leave this up to consumers to decide.

Fwiw, regarding the expectations between producers and consumers about "what fares should be included in the candidate fares for presentation (sorting, menu, etc)," I've drafted the "expected outcome" section in this doc to share my thoughts.


(Also curious about what you thoughts on this @npaun @halbertram @bdferris-v2 @skinkie)

@bdferris-v2
Copy link
Collaborator

+1 to @tzujenchanmbd 's interpretation.

@npaun
Copy link
Contributor

npaun commented Feb 28, 2024

I agree with @tzujenchanmbd's explanation as well. I'll add an example of when rule priority can really simplify things:

network_id from_area_id to_area_id fare_product_id rule_priority
airport-lines airport airport-pass 2
airport-lines airport airport-pass 2
regular-ticket 1

In Denver, taking the A line or an airport express bus to the airport requires an additional charge. All other travel is at regular rate. Rule priority saves us the work of exhaustively defining cases like "a local route to the airport is regular rate", "an airport route to a destination outside the airport is regular rate", and so on.

@halbertram
Copy link

Also +1 to @tzujenchanmbd 's interpretation. It might be worth adding stronger wording that all but the highest rule(s) will be discarded from the match set and therefore not considered for display, transfers etc.

@halbertram
Copy link

Additionally, do we need to make clear that this effectively makes fare_product_id a predicate where it might not be otherwise considered as one? Ie in a version of @npaun 's airport example where the special airport fares only applied to contactless, we presumably don't want to regard either of the priority 2 rules as matched unless those products have a line that matches a contactless fare_media_id.

Or are there other thoughts of how it should work in those cases?

@bdferris-v2
Copy link
Collaborator

@halbertram I would not have considered fare_product_id a predicate in this case. Namely, if the airport bus only needs special pricing when paying with one media vs another, then I think you'd have to specify both those medias for the fare product associated with the higher rule_priority rule.

Put another way, I don't believe rule_priority allows a rule override for just one media type, per my interpretation. I recognize that this might make certain cases more verbose when you only need to override the price for a particular media type, but I think the implications of making fare_product_id a predicate would come with its own complications.

| `from_timeframe_group_id` | Foreign ID referencing `timeframes.timeframe_group_id` | Optional | Defines the timeframe for the fare validation event at the start of the fare leg.<br><br>The “start time” of the fare leg is the time at which the event is scheduled to occur. For example, the time could be the scheduled departure time of a bus at the start of a fare leg where the rider boards and validates their fare. For the rule matching semantics below, the start time is computed in local time, as determined by [Local Time Semantics](#localtimesemantics) of [timeframes.txt](#timeframestxt). The stop or station of the fare leg’s departure event should be used for timezone resolution, where appropriate.<br><br>For a fare leg rule that specifies a `from_timeframe_group_id`, that rule will match a particular leg if there exists at least one record in `timeframes.txt` where all of the following conditions are true<br>- The value of `timeframe_group_id` is equal to the `from_timeframe_group_id` value.<br>- The set of days identified by the record’s `service_id` contains the “current day” of the fare leg’s start time.<br>- The “time-of-day” of the fare leg's start time is greater than or equal to the record’s `timeframes.start_time` value and less than the `timeframes.end_time` value.<br><br>An empty `fare_leg_rules.from_timeframe_group_id` indicates that the start time of the leg does not affect the matching of this rule. |
| `to_timeframe_group_id` | Foreign ID referencing `timeframes.timeframe_group_id` | Optional | Defines the timeframe for the fare validation event at the end of the fare leg.<br><br>The “end time” of the fare leg is the time at which the event is scheduled to occur. For example, the time could be the scheduled arrival time of a bus at the end of a fare leg where the rider gets off and validates their fare. For the rule matching semantics below, the end time is computed in local time, as determined by [Local Time Semantics](#localtimesemantics) of [timeframes.txt](#timeframestxt). The stop or station of the fare leg’s arrival event should be used for timezone resolution, where appropriate.<br><br>For a fare leg rule that specifies a `to_timeframe_group_id`, that rule will match a particular leg if there exists at least one record in `timeframes.txt` where all of the following conditions are true<br>- The value of `timeframe_group_id` is equal to the `to_timeframe_group_id` value.<br>- The set of days identified by the record’s `service_id` contains the “current day” of the fare leg’s end time.<br>- The “time-of-day” of the fare leg's end time is greater than or equal to the record’s `timeframes.start_time` value and less than the `timeframes.end_time` value.<br><br>An empty `fare_leg_rules.to_timeframe_group_id` indicates that the end time of the leg does not affect the matching of this rule. |
| `fare_product_id` | Foreign ID referencing `fare_products.fare_product_id` | **Required** | The fare product required to travel the leg. |
| `rule_priority` | Non-negative integer | Optional | Defines the order of priority in which matching rules are applied to legs, allowing certain rules to take precedence over others. When multiple entries in `fare_leg_rules.txt` match, the rule or set of rules with the highest value for `rule_priority` will be selected.<br><br>An empty value for `rule_priority` is treated as zero. If the `rule_priority` field exists, it should not have all records entirely empty. |
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the rule_priority field exists, it should not have all records entirely empty.

If a feed provider just wanted to add an empty rule_priority column to turn off empty-value semantics for the feed, would that be acceptable?

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 this should be allowed.

Copy link
Collaborator Author

@tzujenchanmbd tzujenchanmbd May 6, 2024

Choose a reason for hiding this comment

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

Removed the sentence "If the rule_priority field exists, it should not have all records entirely empty."
0d3a8a5

@tzujenchanmbd
Copy link
Collaborator Author

Hello,

We have at least one producer: Ito. They have shared their dataset in this file, which is part of a private feed.
We have at least one consumer: Apple Maps. Please see the screenshot below for the same dataset.

As per the GTFS amendment process, the requirements to open a vote are met.
We are opening a vote for adding rule_priority.

Voting ends on 2024-05-20 at 23:59:59 UTC.

@tzujenchanmbd tzujenchanmbd added Status: Voting Pull Requests where the advocate has called for a vote as described in the changes.md and removed Status: Discussion Issues and Pull Requests that are currently being discussed and reviewed by the community. labels May 6, 2024
@bdferris-v2
Copy link
Collaborator

+1 from Google

I can confirm that we are using this extension as well.

@saraemarcus
Copy link

+1 from Apple

@npaun
Copy link
Contributor

npaun commented May 6, 2024

+1 from Transit

@halbertram
Copy link

+1 from Ito

@drewda
Copy link

drewda commented May 6, 2024

+1 from Interline

@tzujenchanmbd
Copy link
Collaborator Author

The vote passed on 2024-05-20 at 23:59:59 UTC.

5 votes in favour and no votes against.

The votes came from:
Google (@bdferris-v2)
Apple (@saraemarcus)
Transit (@npaun)
Ito (@halbertram)
Interline (@drewda)

Thanks to everyone who contributed and voted!

@tzujenchanmbd tzujenchanmbd removed the Status: Voting Pull Requests where the advocate has called for a vote as described in the changes.md label May 23, 2024
@tzujenchanmbd tzujenchanmbd merged commit d339c75 into google:master May 23, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Change: Addition New function proposed to the specification. Extension: GTFS-Fares Issues and Pull Requests that focus on GTFS-Fares Extension GTFS Schedule Issues and Pull Requests that focus on GTFS Schedule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add rule_priority field to fare_leg_rules.txt
8 participants