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:
| Type | Geometry stored | Containment test |
|---|---|---|
POLYGON | GeoJSON polygon (coordinates) | Ray-casting algorithm |
RADIUS | centerLatitude / centerLongitude + radiusKm | Haversine distance ≤ radius |
POINT | centerLatitude / centerLongitude | Haversine distance ≤ 100 m |
CORRIDOR | Encoded 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:
- Filters all active zones to those that contain the point (using the appropriate containment test per zone type).
- 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:
| Strategy | Selection rule |
|---|---|
PRIORITY | Zone with the highest priority field value |
MOST_EXPENSIVE | Zone with the highest priceMultiplier |
CLOSEST | Zone whose center is closest to the point (Haversine) |
COMBINED | Highest 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_EXPENSIVEresolved to "Paris Centre". The engine therefore tests both the resolved zone and every candidate zone againstZoneRouteorigin/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 winsPrice source priority: ZoneRouteAssignment.overridePrice (partner-specific) → ZoneRoute.fixedPrice.
VAT source priority: ZoneRouteAssignment.overrideVatRate → ZoneRoute.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 durationHours ≥ package.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:
| Value | Meaning |
|---|---|
NO_CONTRACT | Partner has no active contract |
NO_ROUTE_MATCH | Contract 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:
VehicleCategory.baseRatePerKm/baseRatePerHour(if set on the category).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:
| Strategy | Effective multiplier |
|---|---|
MAX (default) | max(pickupMultiplier, dropoffMultiplier) |
PICKUP_ONLY | pickupZone.priceMultiplier |
DROPOFF_ONLY | dropoffZone.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 score | Default multiplier |
|---|---|
| 1 | 0.85 (discount) |
| 2 | 0.92 |
| 3 | 1.00 (neutral) |
| 4 | 1.15 |
| 5 | 1.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
| Level | Component | Mode | Skipped when |
|---|---|---|---|
| 1 | Grid match (ZoneRoute / ExcursionPackage / DispoPackage) | FIXED_GRID | Contact is not a partner |
| 2 | Dynamic base price (distance + duration) | DYNAMIC | Grid matched |
| 3 | Zone multiplier | DYNAMIC | Grid matched |
| 4 | Vehicle category multiplier | DYNAMIC | Grid matched or category rates used as base |
| 5 | Client difficulty multiplier | DYNAMIC | Grid matched, or contact is AGENCY/PARTNER |
| — | Advanced rates | DYNAMIC | Grid matched |
| — | Seasonal multipliers | DYNAMIC | Grid matched |
Transparency and Diagnostics
Every pricing result carries a ZoneTransparencyInfo object on PricingResult.zoneTransparency, exposing:
pickup/dropoffdetection 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
- Modes — FIXED_GRID vs DYNAMIC and bidirectional pricing
- Shadow Calculation — Segments A / B / C and availability overlap
- Cost Model — Fuel, tolls, wear, driver, compliance staffing
- Configuration → Behaviour —
OrganizationPricingSettingsfield map
Pricing Modes
The two pricing methods — Fixed Grid and Dynamic — and how the engine selects and stores the final mode on a quote.
Shadow Calculation
Segments A / B / C (approach, service, return), estimated end time, availability overlap detection, round-trip modes, and how positioning costs feed the margin model.