-
Notifications
You must be signed in to change notification settings - Fork 13
Business Rules
There are two attributes associated with facilities that can be used to lower capacity: base capacity and modifiers.
Base capacity dictates the capacity for a facility for the current day and all days in the future. If one were to change this value, all reservation objects (https://github.com/bcgov/parks-reso-api/wiki/Models#reservations-object) will be be updated accordingly.
The following is an example of how facility stores its base capacity:
bookingTimes: {
"AM": {
"max": 20
},
"PM": {
"max": 3
}
}
"AM" and "PM" dictates the type of passes you can book. "max" is the base capacity set for the given pass type.
Upon an edit that increases the base capacity, all reservation objects in the future are retrieved. We then iterate through and increase available passes by the increased difference.
For example: If we had the following reservation object in the database:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 100,
"capacityModifier": 0,
"availablePasses": 50
}
}
}
If someone updated the facility to have AM base capacity as 120, the reservation object would be updated to the following:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 120,
"capacityModifier": 0,
"availablePasses": 70
}
}
}
We should be free to increase passes without any problems. Issues arise when decreasing capacity.
When decreasing capacities, we have a chance to set our base capacity under the number of passes already distributed. There are specific business rules to account for this.
Consider the following reservation object:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 100,
"capacityModifier": 0,
"availablePasses": 0
}
}
}
If one were to lower base capacity for AM, the reservation object would look as follows:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 80,
"capacityModifier": 0,
"availablePasses": 0 (-20)
}
}
}
A negative available pass count will not happen. If the availablePasses attribute is determined to go into the negatives, the value will remain 0. To account for the remainder, two things that must happen:
- The most recent 20 passes must be put into an "overbooked" state (set by the park operators)
- These passes are then must be retrieved and manually set to cancelled.
Also, there is a chance that the last few passes to set to "overbooked" is part of a group. In this case, the whole group will be cancelled. The implication of this is that by cancelling those passes, could open spots up for others to book.
When a pass is determined to be overbooked, the pass has a boolean attributed named isOverbooked. This is set to true. Passes that are overbooked retain the same state as when they were not overbooked. This allows us to pull these passes back out of overbooked if needed. This logic exists in the code.
Consider the following reservation object:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 2,
"capacityModifier": 0,
"availablePasses": 0
}
}
}
If we were to decrease the base capacity by one, the object will look as follows:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 1,
"capacityModifier": 0,
"availablePasses": 0 (-1)
}
}
}
This transaction has a remainder of 1. This means enough passes must be removed to satisfy the remainder. In this example, let's assume one pass was set into overbooked.
If a park operator were to set it's capacity to three for example, the overbooked passes have priority and will be set back into a normal state. The resulting object will be as follows:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 3,
"capacityModifier": 0,
"availablePasses": 1
}
}
}
A modifier is one-off capacity change for a single day. Modifiers share the same capacity logic as base capacity changes except they only effect a single day at a time. When setting a modifier, we use the capacityModifier attribute found on the reservation object. Assume the following reservation object:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 100,
"capacityModifier": 0,
"availablePasses": 100
}
}
}
If one were to add a +50 modifier to 2022-06-09 the object would be set as follows:
{
pk: reservations::{park name}::{facility name}
sk: 2022-06-09
capacities: {
"AM":{
"baseCapacity": 100,
"capacityModifier": 50,
"availablePasses": 150
}
}
}
As seen, 50 passes have been added to availablePasses. You are also able to apply negative modifiers.
The following image is an illustration of the decision tree that encapsulates capacity logic:
The following is the formula referenced in the figure found in the decision tree section:
a = availability c = capacity p = number of passes booked (not overbooked) f(p) = Overbooked passes handling function that depends on whether capacity is increasing or decreasing. w = number of passes reinstated from overbooked (reduction in availability). r = remainder (excess availability)
da = a1 - a0 = change in availability dc = c1 - c0 = change in capacity dp = p1 - p0 = change in number of passes booked
(0 denotes 'before change', 1 denotes 'after change')
We will know a0 and c0 from the reso object, and c1 from the new capacity. Remainder r and number of reinstated passes w are dependent on passes in the system and are calculated independently. The rest can be calculated using known values. We are ultimately looking for a1 (availability after).
p0 = c0 - a0 number of passes booked is equal to the capacity minus the availability.
if (p0 - c1 >= 0) { f(x) = p0 - c1 + r } (capacity is decreasing) Number of overbooked passes is the difference between the original number of booked passes and the new capacity level (plus any calculated remainder). We calculate r in this case. We must overbook all passes within a registration at once, or not at all. If overbooked state requires any portion of a registration to be overbooked, we overbook all passes and then calculate how many excess passes were overbooked to reach the capacity target. This excess is r.
else f(p) = -w (r = 0) (capacity is increasing) Otherwise, number of overbooked passes is negative (we are bringing passes out of overbooked state) and f(p) is set to -w. We calculate w in this case. We must query the database and get a count of how many passes are in the overbooked state.
dp = -f(p) Change in number of passes (if any) is equal to the number of passes overbooked/reinstated.
da = dc - dp change in availability is equal to the change in capacity minus the change in passes booked.
Therefore, da = dc + f(p) a1 - a0 = c1 - c0 + f(p)
a1 = a0 + c1 - c0 + f(p) Since f(p) is nonlinear, this whole formula is nonlinear, meaning it does not follow the same pattern for all cases.
if p0 - c1 >= 0, the formula is: a1 = a0 + c1 - c0 + (p0 - c1 + r) a1 = a0 - c0 + c0 - a0 + r a1 = r
if p0 - c1 < 0, the formula is: a1 = a0 + c1 - c0 + (-w) a1 = a0 + c1 - c0 - w
However, if all values are set before evaluating, a1 = a0 + c1 - c0 + f(p) will work for all cases.
In these cases, capacity c is the total capacity, which is equal to the base capacity if no modifiers are present. However, with modifiers, total capacity c includes any modifiers added to the base capacity. With modifiers, we don’t immediately know c given a reservation object unless we do a little math:
b = base capacity, m = modifier c = b + m
The formula with modifiers in terms of values provided by reservation objects then becomes: a1 = a0 + b1 - b0 + m1 - m0 + f(p) === a1 = a0 + c1 - c0 + f(p)