Pricing Modes
The two pricing methods — Fixed Grid and Dynamic — and how the engine selects and stores the final mode on a quote.
The pricing engine resolves a trip fare using two distinct methods. For partner contacts who hold a contractual rate grid, both prices can be computed simultaneously so the operator can compare them.
Method 1 — Fixed Grid (FIXED_GRID)
A partner contact with an active PartnerContract carries three grid types:
| Grid type | Trip type | What it stores |
|---|---|---|
ZoneRoute | TRANSFER | A fixed price per zone-pair and vehicle category |
ExcursionPackage | EXCURSION | A fixed package price per origin / destination zone |
DispoPackage | DISPO | A fixed base price per included duration block |
Zone-route matching
For a transfer the engine iterates all active ZoneRoute assignments in the contract and tests each one against the pickup and dropoff zones. A route passes when:
- Vehicle category matches the request.
- Both origin and destination zone sets contain the resolved pickup / dropoff zone (or at least one candidate zone — see the Hierarchical Algorithm section on candidate-zone matching).
- The route direction (
BIDIRECTIONAL,A_TO_B, orB_TO_A) is compatible with the trip direction.
The first matching route wins. The price used is overridePrice (partner-specific) if set, otherwise fixedPrice from the ZoneRoute.
Price storage: HT vs TTC
Each grid entry carries a priceMode field ("HT" or "TTC", default "TTC") and a vatRate (decimal percentage, e.g. 10.00 = 10 %). The engine applies the correct conversion:
// If grid price is TTC → back-calculate HT
amountHt = roundedTtc / (1 + vatRate / 100)
// If grid price is HT → forward-calculate TTC
amountTtc = amountHt * (1 + vatRate / 100)The partner-specific overrideVatRate takes precedence over the route's vatRate.
What multipliers are skipped in FIXED_GRID mode
When a grid match succeeds, the following steps are bypassed because the contractual price is all-inclusive:
- Zone price multiplier
- Vehicle category multiplier
- Client difficulty multiplier ("Patience Tax")
- Advanced rates (night/weekend surcharges)
- Seasonal multipliers
Positioning costs (approach + empty return) are still computed for margin analysis but are not added to the client price.
Method 2 — Dynamic (DYNAMIC)
When no grid match is found — or the contact is a private client without a partner contract — the engine falls back to rule-based dynamic pricing.
Base price calculation
The engine computes two candidate prices and selects the higher one:
distanceBasedPrice = distanceKm × baseRatePerKm / (1 − targetMarginPercent / 100)
durationBasedPrice = durationMinutes / 60 × baseRatePerHour / (1 − targetMarginPercent / 100)
basePrice = max(distanceBasedPrice, durationBasedPrice)baseRatePerKm and baseRatePerHour resolve from the vehicle category first, then fall back to the organization-level settings.
Multipliers applied on top of the base price
For dynamic pricing, the following layers are applied in order:
- Zone multiplier —
pickupZone.priceMultiplierand/ordropoffZone.priceMultiplier, combined via the configuredzoneMultiplierAggregationStrategy. - Vehicle category multiplier —
VehicleCategory.priceMultiplier(skipped when category-specific rates are already used as base rates). - Client difficulty multiplier — A score-driven surcharge/discount (1–5 scale) configured in
OrganizationPricingSettings.difficultyMultipliers. Not applied for AGENCY or PARTNER contacts. - Advanced rates — Time-of-day or day-of-week surcharges (e.g. NIGHT, WEEKEND). Applied as percentage adjustments or fixed amounts.
- Seasonal multipliers — Date-range multipliers applied as a factor on the current price.
Fallback reasons
The PricingResult.fallbackReason field records why the grid was not used:
| Value | Cause |
|---|---|
null | Grid was matched — no fallback |
PRIVATE_CLIENT | Contact is not a partner |
NO_CONTRACT | Partner has no active contract |
NO_ROUTE_MATCH | Contract exists but no grid entry matched the trip |
Bidirectional pricing
For partner contacts, the engine can compute both the fixed grid price and the equivalent dynamic price so the operator can choose. Quote.pricingMode stores the final selection:
export type PricingMode =
| "FIXED_GRID" // Partner contractual price
| "DYNAMIC" // Rule-based dynamic price
| "PARTNER_GRID" // Legacy alias (same as FIXED_GRID)
| "CLIENT_DIRECT" // Direct client pricing
| "MANUAL"; // Price set manually by the operatorThe BidirectionalPricingInfo object on PricingResult exposes both prices and their difference:
interface BidirectionalPricingInfo {
partnerGridPrice: number | null;
clientDirectPrice: number | null;
priceDifference: number | null;
priceDifferencePercent: number | null;
}Price rounding
A configurable roundingRule (stored in OrganizationPricingSettings) adjusts the client-facing TTC price after all multipliers are applied. Supported modes:
| Mode | Effect |
|---|---|
NONE | No rounding (default) |
CEIL_1 | Ceil to next euro |
CEIL_5 / CEIL_10 | Ceil to next multiple of 5 or 10 |
FLOOR_5 / FLOOR_10 | Floor to next multiple of 5 or 10 |
ROUND_5 / NEAREST_5 | Round to nearest 5 |
ROUND_10 / NEAREST_10 | Round to nearest 10 |
When a non-NONE rounding rule is active, HT is back-calculated from the rounded TTC using decimal.js to avoid floating-point drift.
Key semantics to remember
QuoteLine.unitPriceandtotalPriceare always HT (excl. tax).QuoteLine.vatRateis a decimal percentage:10.00= 10 %, not0.10.TripTypein the database is an enum:TRANSFER | EXCURSION | DISPO | OFF_GRID.- The internal pricing types use lowercase aliases:
"transfer" | "excursion" | "dispo".
See also
- Hierarchical Algorithm — Zone resolution and the four pricing levels
- Shadow Calculation — Segments A / B / C and availability overlap
- Cost Model — Fuel, tolls, wear, driver, compliance staffing
- Configuration → Behaviour —
OrganizationPricingSettingsfield map - Operators — Pricing Config — Admin-facing configuration reference (FR410)