@entur/add-customers-to-offer-configurations
Adds customers to offer configurations in the Entur Sales System.
Background
In the Entur Sales system, if an offer requires one or more entitlements to be purchased, the offer configurations that
refer to this offer need to contain a customer with the said entitlements. And for all other offers, you can still
supply a customer to an offer configuration if you want to attach that customer to the resulting order lines.
Installation
Using yarn:
yarn add @entur/add-customers-to-offer-configurations
Using npm:
npm install @entur/add-customers-to-offer-configurations
Usage
The module has a default export:
export default function addCustomersToOfferConfigurations(
customers: Customer[],
offerConfigurations: OfferConfiguration[],
offers: Offer[]
): OfferConfiguration[];
And it is imported the way you import any other default import:
import addCustomersToOfferConfigurations from "@entur/add-customers-to-offer-configurations";
The OfferConfiguration and Customer types are as found in
the Reserve Offer API.
The Offer type is as found in
the Offers API. You will
find examples of all of these types in the test/data/
folder. For your convenience, here is one example
for each type, as well as example output:
Travelers and Customers
Note that throughout the documentation, we will refer to a "traveler" a lot, which refers to the Traveler type from the
Offers API as well as the real-world concept of a named or an anonymous traveler. A Traveler and a Customer refer to the
same person if Traveler.id is the same as Customer.customerId. For example, the
example customer looked
as follows when it was used to request offers:
{
id: "3537054",
userType: "ADULT",
products: [{ id: "NSB:EntitlementProduct:levelA1", version: "NSB:Version:V1" }],
}
Use cases
The addCustomersToOfferConfigurations
function has been designed to work for all reasonable use cases we could think
of. It should therefore work with …
- … offerConfigurations spanning several stretches (i.e. service journeys).
- … offers that require entitlements and not.
- … groups containing both anonymous and named travelers.
- … offer configurations with supplement products.
See examples of usage in the corresponding test file.
Caveats
You can only add customers that were present in the offers request
The algorithm uses traveler IDs contained within the offers to attach customers to them (see
Travelers and customers). The offers' traveler IDs are subsets of the traveler IDs that were
supplied when requesting the offers. In other words, given a customer where customerId === "123"
, the algorithm will
only be able to attach this customer to an offer that was requested with a traveler with id === "123"
in its request
body. As long as you keep the group of travelers unchanged from requesting offers until reserving them, which is
standard procedure, you should be fine.
Use at your own risk, but tell us if things go wrong
We, the authors, have chosen to share this code because
- solving this problem took considerable work (and some eureka moments),
- sharing our solution will save time and money for Entur as a whole (as most clients accessing the Entur Sales System
will have to solve the same problem), and
- we are quite certain that our solution is correct.
We have made our best effort to ensure that our package solves the problem correctly. A potential user should be able to
make an informed decision about including the project by looking at this readme, the code comments and tests. But we are
only human. And we are also humans without any control of neither the Offers nor Reserve Offer API. So we may have made
mistakes, and API changes may invalidate our assumptions and design choices (even if we are paying attention to these
APIs).
That being said, we hope you decide to use this package. If you ever experience problems, please:
- Report them to support@entur.org.
- Help fixing the problem in any way you can (file a PR if you work inside Entur, or just mail us your suggestions)
How it works
Optimally assigning customers to offer configurations is an
assignment problem (specifically, a balanced assignment problem),
which is solvable as a minimum-cost flow problem.
Transforming the problem to a graph
To solve the problem, we first make the following assumption:
- Each stretch in the journey (each service journey) is independent of the others. That is to say, there is no
information about which customers were assigned to what offerConfiguration for some earlier or later stretch of the
journey that has any impact on how the current problem should be solved.
For each stretch (or service journey, as they're called in the Entur domain), we can then construct a problem graph in
the following way:
- Every traveler and offer configuration on this service journey is considered a node.
- There is also a start node and an end node (a flow problem convention)
- An edge goes from the start node to each traveler.
- An edge goes from a traveler to an offer configuration if the traveler is in the offer's traveler mapping.
- Edges from anonymous travelers to an offer have a higher cost than those from named travelers to the same offer (see
detailed explanation below).
- An edge goes from each offer configuration to the end node.
- Its cost is equal to the cost of the offer configuration.
- All edges have capacity 1, except those going from an offer configuration (whose capacity is equal to the offer
configuration's count).
Example
The following illustration shows a problem with three offer configurations and three travelers. Offer configurations
oc1
and oc2
can be used by all travelers, while oc3
is only available to t3
.
One optimal assignment of customers to offerConfigurations has t3
assigned to oc3
, t1
to oc1
and t2
assigned
to oc2
. The only other possible, optimal assignment is to switch t1
and t2
around.