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.
The shadow calculation produces a full operational loop for every trip — including deadhead legs the client never pays for. It is the foundation for internal cost analysis, driver availability detection, and dispatch optimisation.
The Three Segments
| Segment | Name | Route | Billed to client? |
|---|---|---|---|
| A | Approach | Base → Pickup | No (internal cost only) |
| B | Service | Pickup → Dropoff | Yes |
| C | Return | Dropoff → Base | No (internal cost only) |
calculateShadowSegments() assembles these three SegmentAnalysis objects inside TripAnalysis.segments. Each segment carries its own CostBreakdown (fuel, tolls, wear, driver time).
When no vehicle has been assigned yet (quote creation stage), segments A and C may be absent or estimated — the approach distance depends on which base the eventually-assigned vehicle departs from.
Routing source
TripAnalysis.routingSource records where segment distances came from:
| Value | Meaning |
|---|---|
GOOGLE_API | Google Routes API called for real distances |
HAVERSINE_ESTIMATE | Straight-line distance × correction factor |
VEHICLE_SELECTION | Distances computed during vehicle dispatch selection |
Time Analysis
calculateTimeAnalysis() takes the raw Google Routes duration and applies three sequential adjustments, all recorded in TimeAnalysis.
1. Vehicle speed adjustment (HEAVY only)
Coaches and minibuses average ~70 km/h vs a car's ~100 km/h. The engine adds +40 % of the base duration for vehicles with regulatoryCategory = "HEAVY".
adjustedMinutes = baseGoogleDurationMinutes × 1.402. Traffic adjustment (pickup time)
The engine evaluates the pickup hour against a set of traffic rules. Only the first matching rule applies:
| Rule name | Hours (local) | Adjustment |
|---|---|---|
RUSH_HOUR_MORNING | 07:00 – 09:00 | +15 % |
RUSH_HOUR_EVENING | 17:00 – 19:00 | +15 % |
NIGHT | 22:00 – 06:00 | −10 % |
The percentage is applied to the base Google duration (before vehicle adjustment), then the resulting minutes are added to the running total.
3. RSE mandatory breaks (HEAVY only)
French RSE regulation (Art. 561-2) limits continuous driving. The engine computes the number of required breaks:
breakCount = floor(drivingMinutes / 270) // 4.5 h max continuous
totalBreakMinutes = breakCount × 45 // 45 min per breakConstants:
| Constant | Value |
|---|---|
| Max continuous driving | 270 min (4.5 h) |
| Break duration | 45 min |
| Regulation reference | RSE Art. 561-2 |
mandatoryBreaks is null for LIGHT vehicles, and for HEAVY vehicles where the trip is short enough that no break is triggered.
Estimated End Time
calculateEstimatedEndAt(pickupAt, tripAnalysis) computes the mission end timestamp:
estimatedEndAt = pickupAt + totalDurationMinutesSpecial case — MULTI_DAY compliance plan:
estimatedEndAt = pickupAt + (daysRequired × 24 × 60)This timestamp is used by the dispatch engine to detect driver availability overlap: a driver cannot be assigned to a new mission whose window overlaps [scheduledAt, estimatedEndAt] of an existing confirmed mission.
Positioning Costs
calculatePositioningCosts() breaks out the segments A and C as billable items for margin analysis.
Approach fee (Segment A)
When a vehicle is selected, the approach cost equals segments.approach.cost.total — the full internal cost of driving from the base to the pickup point.
When no vehicle has been assigned yet, approachFee.cost = 0 and the reason records that the cost will be computed at dispatch.
Empty return (Segment C)
When a vehicle is selected, the empty return cost is:
adjustedCost = segments.return.cost.total × (emptyReturnCostPercent / 100)emptyReturnCostPercent is configured in OrganizationPricingSettings (default 100 %). Setting it to 50 % means only half the return operating cost is counted against margin.
When no vehicle is selected, emptyReturn.cost = 0 — the engine cannot estimate the return distance without knowing the vehicle's base.
Neither the approach fee nor the empty return is added to the client-facing price. They are internal cost items used solely for profitability analysis and
ProfitabilityIndicatorcalculation.
Round-Trip Segments
When a quote is marked as a round trip, calculateRoundTripSegments() replaces the simple ×2 shortcut with accurate per-segment pricing.
The six segments for a round trip:
| Segment | Route | Present in |
|---|---|---|
| A | Base → Pickup | Both modes |
| B | Pickup → Dropoff (outbound) | Both modes |
| C | Dropoff → Base (empty return) | RETURN_BETWEEN_LEGS only |
| D | Base → Pickup (repositioning) | RETURN_BETWEEN_LEGS only |
| E | Dropoff → Pickup (return service) | Both modes |
| F | Pickup → Base (final empty return) | Both modes |
Round-trip mode selection
| Mode | Condition | Cost |
|---|---|---|
WAIT_ON_SITE | No waiting time, or waitingTimeMinutes < threshold | A + B + E + F ≈ 2× single leg |
RETURN_BETWEEN_LEGS | waitingTimeMinutes ≥ threshold (default 120 min) | A + B + C + D + E + F |
waitOnSiteThresholdMinutes defaults to 120 minutes and can be overridden per call.
TripAnalysis Structure
interface TripAnalysis {
segments: {
approach: SegmentAnalysis | null; // Segment A
service: SegmentAnalysis; // Segment B
return: SegmentAnalysis | null; // Segment C
};
totalDistanceKm: number;
totalDurationMinutes: number;
totalInternalCost: number;
costBreakdown: CostBreakdown; // Combined A + B + C
routingSource: "GOOGLE_API" | "HAVERSINE_ESTIMATE" | "VEHICLE_SELECTION";
vehicleSelection: VehicleSelectionInfo | undefined;
compliancePlan: CompliancePlan | undefined;
calculatedAt: string; // ISO 8601
}Each SegmentAnalysis contains distanceKm, durationMinutes, cost (a CostBreakdown), and isEstimated.
Relationship to the Cost Model
The shadow calculation feeds directly into the cost model. See Cost Model for:
- How
CostBreakdownis built (fuel, tolls, wear, driver). - Fuel resolution chain (vehicle → category → organization → default).
- Toll resolution (Google Routes → TollGuru →
tollCostPerKmestimate). - RSE compliance staffing costs and how they inflate
totalInternalCost.
See also
- Modes — FIXED_GRID vs DYNAMIC and bidirectional pricing
- Hierarchical Algorithm — Zone resolution and grid matching
- Cost Model — Fuel, tolls, wear, driver, compliance staffing
- Configuration → Behaviour —
OrganizationPricingSettingsfield map
Hierarchical Algorithm
Zone resolution, conflict strategies, multiplier aggregation, and the four pricing levels that the engine walks before returning a final price.
Cost Model
Fuel resolution chain (CollectAPI → FuelPriceCache → default), toll resolution (Google Routes → TollGuru → estimate), the CostBreakdown structure, and RSE compliance staffing costs.