Sixième Étoile — Documentation

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 typeTrip typeWhat it stores
ZoneRouteTRANSFERA fixed price per zone-pair and vehicle category
ExcursionPackageEXCURSIONA fixed package price per origin / destination zone
DispoPackageDISPOA 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:

  1. Vehicle category matches the request.
  2. 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).
  3. The route direction (BIDIRECTIONAL, A_TO_B, or B_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:

  1. Zone multiplierpickupZone.priceMultiplier and/or dropoffZone.priceMultiplier, combined via the configured zoneMultiplierAggregationStrategy.
  2. Vehicle category multiplierVehicleCategory.priceMultiplier (skipped when category-specific rates are already used as base rates).
  3. Client difficulty multiplier — A score-driven surcharge/discount (1–5 scale) configured in OrganizationPricingSettings.difficultyMultipliers. Not applied for AGENCY or PARTNER contacts.
  4. Advanced rates — Time-of-day or day-of-week surcharges (e.g. NIGHT, WEEKEND). Applied as percentage adjustments or fixed amounts.
  5. 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:

ValueCause
nullGrid was matched — no fallback
PRIVATE_CLIENTContact is not a partner
NO_CONTRACTPartner has no active contract
NO_ROUTE_MATCHContract 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 operator

The 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:

ModeEffect
NONENo rounding (default)
CEIL_1Ceil to next euro
CEIL_5 / CEIL_10Ceil to next multiple of 5 or 10
FLOOR_5 / FLOOR_10Floor to next multiple of 5 or 10
ROUND_5 / NEAREST_5Round to nearest 5
ROUND_10 / NEAREST_10Round 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.unitPrice and totalPrice are always HT (excl. tax).
  • QuoteLine.vatRate is a decimal percentage: 10.00 = 10 %, not 0.10.
  • TripType in the database is an enum: TRANSFER | EXCURSION | DISPO | OFF_GRID.
  • The internal pricing types use lowercase aliases: "transfer" | "excursion" | "dispo".

See also

Was this page helpful?

On this page