openmobilityfoundation / curb-data-specification

A data specification to help cities manage their curb zone programs and surrounding areas, and measure the utilization and impact.
https://www.openmobilityfoundation.org/about-cds/
Other
47 stars 18 forks source link

Support for Flat-Rate Policies #131

Open lummish opened 7 months ago

lummish commented 7 months ago

Is your feature request related to a problem? Please describe.

Certain cities have requested the implementation of a flat-rate fee structure for parking, for example:

For sessions lasting between 0 and 15 minutes, no charge will be assessed. For sessions lasting between 15 and 30 minutes, a charge of $1.50 will be assessed. For sessions lasting between 30 and 60 minutes, a charge of $3.00 will be assessed.

A few examples of this policy at work:

A vehicle parks for a total of 7 minutes and is charged $0.00 for the session. A vehicle parks for a total of 16 minutes and is charged $1.50 for the session. A vehicle parks for a total of 28 minutes and is charged $1.50 for the session. A vehicle parks for a total of 40 minutes and is charged $3.00 for the session.

At present, the specification for policy definition does not allow for expression of this kind of policy; the current implementation depends on policies and their corresponding rates being assessed in an additive fashion, rather than as a flat fee for different session durations.

Describe the solution you'd like

We propose the addition of a single new property to the Rule type called rate_application_type.

The new property would assume one of two values: additive or flat, where the additive value would correspond to the current definition and behavior of a Rule, and the flat value would correspond to the proposed new behavior.

This property would be optional, and when not specified would designate the current behavior (i.e. additive).

When the rate_application_type for a given rule is defined as additive, the current behavior will persist. That is to say, the elements in the rate array for the given rule will apply additively.

When the rate_application_type for a given rule is defined as flat, only one element of the rate array of the rule will apply to a session.

As an illustrative example, let's use the policy described above. We could define a rule to represent this policy like so:

{
  "activity": "parking",
  "max_stay": 60,
  "max_stay_unit": "minute",
  "rate_application_type": "flat",
  "rate": [
    {
      "rate": 0,
      "rate_unit": "minute",
      "increment_duration": "minute",
      "start_duration": 0,
      "end_duration": 15
    },
    {
      "rate": 150,
      "rate_unit": "minute",
      "increment_duration": "minute",
      "start_duration": 15,
      "end_duration": 30
    },
    {
      "rate": 300,
      "rate_unit": "minute",
      "increment_duration": "minute",
      "start_duration": 30,
      "end_duration": 60
    }
  ]
}

Is this a breaking change

A breaking change would require consumers or implementors of an API to modify their code for it to continue to function (ex: renaming of a required field or the change in data type of an existing field). A non-breaking change would allow existing code to continue to function (ex: addition of an optional field or the creation of a new optional endpoint).

Impacted Spec

For which spec is this feature being requested?

Describe alternatives you've considered

We explored implementing the desired feature by modifying the Policy and Rate types, however it seems that the most sensible place to make the change is on the Rule type.

cc: @michael-automotus

jiffyclub commented 7 months ago

I think this may technically be possible in the current rate structure by setting the increment_duration value (which is an integer, not a string) to the same duration as the payment period, e.g. like this:

{
  "activity": "parking",
  "max_stay": 60,
  "max_stay_unit": "minute",
  "rate": [
    {
      "rate": 0,
      "rate_unit": "minute",
      "increment_duration": 15,
      "start_duration": 0,
      "end_duration": 15
    },
    {
      "rate": 150,
      "rate_unit": "minute",
      "increment_duration": 15,
      "start_duration": 15,
      "end_duration": 30
    },
    {
      "rate": 150,
      "rate_unit": "minute",
      "increment_duration": 30,
      "start_duration": 30,
      "end_duration": 60
    }
  ]
}

I think that rate structure would make it so the vehicle was charged $1.50 for being parked during the 15-30 durations and then another $1.50 for being parked during the 30-60 durations for a total of $3.00.

lummish commented 4 months ago

@jiffyclub I'm not sure this is entirely correct, at least not based upon how I'm reading the specification.

Let's look at the second rate in your definition in particular. My interpretation of this would be that the price applied here would be $1.50 per minute, where the driver would be billed a flat rate of $22.50 for parking in the zone for a duration of between 15 and 30 minutes.

I think we would need to divide the rate by 15 to achieve our initially proposed policy, so the policy would look like this:

{
  "activity": "parking",
  "max_stay": 60,
  "max_stay_unit": "minute",
  "rate": [
    {
      "rate": 0,
      "rate_unit": "minute",
      "increment_duration": 15,
      "start_duration": 0,
      "end_duration": 15
    },
    {
      "rate": 10,
      "rate_unit": "minute",
      "increment_duration": 15,
      "start_duration": 15,
      "end_duration": 30
    },
    {
      "rate": 10,
      "rate_unit": "minute",
      "increment_duration": 30,
      "start_duration": 30,
      "end_duration": 60
    }
  ]
}

I believe you are correct that our desired policy representation can be achieved with the policy specification as is, assuming of course that the intended flat rate divides evenly into the number of rate_units in the increment_duration (which may not always be the case.) Additionally, I would contend that this representation is rather confusing to read/define in JSON form, and would also be somewhat difficult to render in a GUI in such a way that a driver could easily comprehend the rate structure.

Perhaps there is a non-breaking modification we could introduce (either to the Rate object or to the Rule object) that would allow us to specify that a rate (or all rates attached to a rule) is defined based on the price per increment rather the price per rate unit, which would make the policy easier to comprehend and would additionally remove the constraint requiring that the total rate paid for a particular duration is evenly divisible by the increment_duration.

I'm a bit torn as to where this modification would belong. An argument could be made either for the Rate object or the Rule object:

lummish commented 4 months ago

One possibility would be adding a new property to the Rate object called rate_basis, which could assume one of two values: "unit" or "increment".

If the new property is not defined, the default value is assumed to be "unit" as that would preserve the current behavior.

If the new property is defined, the following conditions apply:

For example, let's examine the following rate:

    {
      "rate": 150,
      "rate_unit": "minute",
      "increment_duration": 15,
      "start_duration": 15,
      "end_duration": 30
    }

In this case, the rate is defined as 150 per minute, with a minimum payment of $22.50 (i.e. 15 minutes * $1.50 / minute).

If we were to instead express this rate like so:

    {
      "rate": 150,
      "rate_unit": "minute",
      "rate_basis": "increment",
      "increment_duration": 15,
      "start_duration": 15,
      "end_duration": 30
    }

Then the rate is defined as 150 per 15 minutes. So the minimum payment is simply $1.50.

kas2020-commits commented 4 months ago

I think I agree with @lummish here in that flat-rate policies are discontinuous step function-based rate policies whereas the current notion of rates assumes a linear model that scales with time. In this way the rate field is actually the gradient, so for a flat-rate model you would want to set rate to 0. However this is obviously not going to work because we don't have a way to set the second parameter of the linear model to a non-zero value.

If we were to support flat-rate policies I think the simplest solution would be to add an optional, default-initialized field called rate_intercept or similar where the default would be 0. This would make it backwards-compatible with the current schema and also allow flat-rates. In this solution the rate policy for OP would be:

{
  "activity": "parking",
  "max_stay": 60,
  "max_stay_unit": "minute",
  "rate": [
    {
      "rate": 0,
      "rate_unit": "minute",
      "start_duration": 0,
      "end_duration": 15
    },
    {
      "rate": 0,
      "rate_intercept": 150,
      "rate_unit": "minute",
      "start_duration": 15,
      "end_duration": 30
    },
    {
      "rate": 0,
      "rate_intercept": 150,
      "rate_unit": "minute",
      "start_duration": 30,
      "end_duration": 60
    }
  ]
}

So the vehicle will be charged nothing for minutes 0-15, $1.50 for any active minutes 15-30 and $1.50 for any active minutes between 30-60. Since the rates apply on individual chunks of the duration itself, they are additive and hence relative to each other, so rate_intercept would be the change in the rate intercept relative to the prior rate.

Note that from the definitions of start_duration and end_duration the 2nd rate stops applying at the 30th minute, and so you do not need to define rate_intercept relative to 2nd rate because the 3rd rate will be the only active rate from 30 to 60 minutes.

EDIT: Fixed incorrect explination, oops.

schnuerle commented 4 months ago

Would like to hear back from @jiffyclub to see if he still thinks this is possible in the current spec.

LaurentG-AMD commented 4 months ago

I think @jiffyclub's interpretation is correct.

So if increment_duration is set as the payment period, we charge the full period as soon as the even hits that period. So for an event duration of 28 minutes in the above exemple, we would charge the full first period since 28 > 15, for a subtotal of 0$, and the full second period since 15 < 28 < 30, for a subtotal of 1,50$, for a grand total of 1,50$.

The thing that is probably missing is some langages/exemples to document this.

jiffyclub commented 4 months ago

I think either of these are possible depending on how you interpret the spec, hinging on whether we interpret the rate to be per rate_unit or per increment_duration. Looking closer at the spec it does say "per rate_unit". There is also an increment_amount field that I am not at all sure what it is for, and the description is not helping, but could potentially be useful here, @schnuerle do you remember?

A flag like rate_basis would definitely make things entirely explicit. I would prefer it on the rate, not the rule, so that rates are self-contained.