Sixième Étoile — Documentation

Hierarchical Algorithm

Zone resolution, conflict strategies, multiplier aggregation, and the four pricing levels that the engine walks before returning a final price.

The pricing engine resolves every quote through a strict hierarchy: zones are matched first, then a grid is attempted, and finally dynamic multipliers are applied in a fixed order. This page details each decision layer.

Zone Resolution

Zone types

A PricingZone can be one of four geometric types:

TypeGeometry storedContainment test
POLYGONGeoJSON polygon (coordinates)Ray-casting algorithm
RADIUScenterLatitude / centerLongitude + radiusKmHaversine distance ≤ radius
POINTcenterLatitude / centerLongitudeHaversine distance ≤ 100 m
CORRIDOREncoded polyline buffered into a polygon (geometry)Ray-casting on buffer polygon

For CLOSEST strategy distance computation, the engine calls getZoneCenter(). For POLYGON zones without explicit center fields, the centroid is computed from the polygon ring vertices.

Candidate zone collection

For pickup and dropoff points, the engine calls findZonesForPoint() which:

  1. Filters all active zones to those that contain the point (using the appropriate containment test per zone type).
  2. Sorts candidates by specificity: POINT > CORRIDOR (smaller buffer first) > RADIUS (smaller radius first) > POLYGON.

When no zone is configured — or the point matches none — both the selected zone and the candidate list are null/[].

Zone conflict resolution

When a point falls inside multiple overlapping zones, the engine applies resolveZoneConflict() with the ZoneConflictStrategy configured in OrganizationPricingSettings.zoneConflictStrategy:

StrategySelection rule
PRIORITYZone with the highest priority field value
MOST_EXPENSIVEZone with the highest priceMultiplier
CLOSESTZone whose center is closest to the point (Haversine)
COMBINEDHighest priority first; among ties, highest priceMultiplier

When zoneConflictStrategy is null, the engine falls back to pure specificity ordering (POINT > CORRIDOR > RADIUS > POLYGON).

Grid matching uses ALL candidates, not just the resolved zone. A partner route may name a broader zone (e.g., "Région Parisienne 100 km") even though MOST_EXPENSIVE resolved to "Paris Centre". The engine therefore tests both the resolved zone and every candidate zone against ZoneRoute origin/destination sets.

Zone surcharges

Each zone can carry fixed surcharges that are added to the service cost regardless of pricing mode:

  • fixedParkingSurcharge — added to the cost breakdown (e.g., airport parking fees).
  • fixedAccessFee — added to the cost breakdown (e.g., city-centre access tolls).

These are collected in ZoneTransparencyInfo.surcharges and visible in the trip transparency panel.


Grid Matching (Level 1 — FIXED_GRID)

Grid matching is attempted only when contact.isPartner === true and the contact holds an active PartnerContract.

ZoneRoute matching (TRANSFER)

For each active ZoneRoute assignment in the contract:
  1. vehicleCategoryId matches the request
  2. pickupZone ∈ route.originZones  (or any pickup candidate zone)
  3. dropoffZone ∈ route.destinationZones  (or any dropoff candidate zone)
  4. route.direction is compatible:
       BIDIRECTIONAL → always pass
       A_TO_B       → pass if pickup in originZones, dropoff in destinationZones
       B_TO_A       → pass if dropoff in originZones, pickup in destinationZones
→ First match wins

Price source priority: ZoneRouteAssignment.overridePrice (partner-specific) → ZoneRoute.fixedPrice.

VAT source priority: ZoneRouteAssignment.overrideVatRateZoneRoute.vatRate.

ExcursionPackage matching (EXCURSION)

Matches on originZoneId + destinationZoneId + vehicleCategoryId. The engine also checks candidate zone IDs for both origin and destination.

DispoPackage matching (DISPO)

Matches on vehicleCategoryId and durationHourspackage.durationHours. When multiple packages match, the longest included duration wins.

Grid match result

On success: pricingMode = "FIXED_GRID", multipliers skipped (see Modes).

On failure: fallbackReason is set to one of:

ValueMeaning
NO_CONTRACTPartner has no active contract
NO_ROUTE_MATCHContract exists but no entry matched

When contact.isPartner === false: fallbackReason = "PRIVATE_CLIENT", dynamic pricing proceeds immediately.


Dynamic Pricing Layers (Level 2–5)

When grid matching fails or is not applicable, the engine builds the price through five ordered layers.

Level 2 — Base price

distanceBasedPrice = distanceKm × baseRatePerKm / (1 − targetMarginPercent / 100)
durationBasedPrice = durationMinutes / 60 × baseRatePerHour / (1 − targetMarginPercent / 100)
basePrice = max(distanceBasedPrice, durationBasedPrice)

Rate resolution order:

  1. VehicleCategory.baseRatePerKm / baseRatePerHour (if set on the category).
  2. OrganizationPricingSettings.baseRatePerKm / baseRatePerHour (organization default).

targetMarginPercent always comes from OrganizationPricingSettings.

Level 3 — Zone multiplier

Applied immediately after the base price. The engine calls applyZoneMultiplier() with the configured zoneMultiplierAggregationStrategy:

StrategyEffective multiplier
MAX (default)max(pickupMultiplier, dropoffMultiplier)
PICKUP_ONLYpickupZone.priceMultiplier
DROPOFF_ONLYdropoffZone.priceMultiplier
AVERAGE(pickupMultiplier + dropoffMultiplier) / 2 (rounded to 3 dp)

When a zone has no explicit multiplier, it defaults to 1.0 (neutral).

The source field in AppliedRule records which zone drove the effective multiplier ("pickup", "dropoff", or "both").

Level 4 — Vehicle category multiplier

VehicleCategory.priceMultiplier is applied next. It is skipped when category-specific rates (baseRatePerKm / baseRatePerHour) were already used as the Level 2 base — avoiding double-counting.

Level 5 — Client difficulty multiplier

A score-driven surcharge or discount applied for PRIVATE contacts. Score range 1–5, mapped to multipliers configured in OrganizationPricingSettings.difficultyMultipliers:

Default scoreDefault multiplier
10.85 (discount)
20.92
31.00 (neutral)
41.15
51.30 (surcharge)

Skipped for contacts with type AGENCY or PARTNER.

Advanced rates

Time-of-day or day-of-week rate rules applied as percentage adjustments or fixed amounts. Each rule carries a rateType (e.g., NIGHT, WEEKEND) and is evaluated against the pickup scheduledAt timestamp.

Seasonal multipliers

Date-range multipliers applied as a factor on the current cumulated price. Evaluated against pickupDate. Multiple active seasonal multipliers are applied sequentially.


Pricing Level Hierarchy Summary

LevelComponentModeSkipped when
1Grid match (ZoneRoute / ExcursionPackage / DispoPackage)FIXED_GRIDContact is not a partner
2Dynamic base price (distance + duration)DYNAMICGrid matched
3Zone multiplierDYNAMICGrid matched
4Vehicle category multiplierDYNAMICGrid matched or category rates used as base
5Client difficulty multiplierDYNAMICGrid matched, or contact is AGENCY/PARTNER
Advanced ratesDYNAMICGrid matched
Seasonal multipliersDYNAMICGrid matched

Transparency and Diagnostics

Every pricing result carries a ZoneTransparencyInfo object on PricingResult.zoneTransparency, exposing:

  • pickup / dropoff detection info: selected zone, all candidate zones, rejection reasons.
  • conflictResolution: strategy used, whether a conflict was resolved at each end.
  • multiplierApplication: raw multipliers, effective multiplier, aggregation strategy, price before/after.
  • surcharges: parking and access fee amounts per zone.

Each applied rule is recorded in PricingResult.appliedRules with priceBefore / priceAfter for full audit traceability.


See also

Was this page helpful?

On this page