-
Notifications
You must be signed in to change notification settings - Fork 1
Trip Management
The TripManagement service plans, dispatches and tracks trips: ordered stops, deliveries, route geometry with a deviation corridor, toll estimation, proof of delivery, and shareable public tracking links.
It owns the trip schema inside the shared TrackHub database, with PostGIS geometry at SRID 4326. In development it listens on https://localhost:5006 / http://localhost:5007; behind nginx it sits at /Trip/.
Related pages: Database · Router · Reporting
The trip-management feature key gates the whole tenant surface: the portal /tripManager dispatch board, every Trips and TripTracking command and query, both background jobs, the position feed and public-link resolution.
Resources.TollCatalog is deliberately not gated. The toll catalog is Administrator-maintained platform reference data with no AccountId, so gating it on a tenant feature would misclassify it.
The service registers its own
IFeatureFlagServiceinInfrastructure/TripDB/DependencyInjection.cs.Common's default is fail-open (AlwaysEnabledFeatureFlagService, registered withTryAddScoped), so a service that does not override it silently ignores every[RequireFeature]attribute.
stateDiagram-v2
[*] --> Created
Created --> InProgress: startTrip
Created --> Cancelled: cancelTrip
InProgress --> Paused: pauseTrip
Paused --> InProgress: resumeTrip
InProgress --> Completed: completeTrip
InProgress --> Aborted: abortTrip
Paused --> Aborted: abortTrip
Completed --> [*]
Cancelled --> [*]
Aborted --> [*]
A single transition matrix is the source of truth; every lifecycle mutation validates against it rather than checking statuses ad hoc.
Stops progress independently: Pending → Arrived → Departed, or Skipped. Deliveries close out as Pending, Delivered, PartiallyDelivered or Rejected.
TripDetectionService handles ProcessTripPositionsCommand (Resources.TripTracking / Custom) over the account's InProgress trips only.
| Signal | Rule |
|---|---|
| Arrival | ST_Contains(stop.ArrivalGeom, point) |
| Departure | 30 s debounce after leaving, tracked in TripStop.OutsideSinceAt
|
| Corridor deviation | 3 consecutive outside fixes, tracked in Trip.ConsecutiveOutsideFixes; the episode identity is Trip.DeviationOpenedAt
|
Detection state is persisted, never held in memory. The Router pushes one position per transporter per call, so any debounce or run length kept in memory could never elapse. A test that feeds a whole scenario in a single call proves nothing about deployed behaviour — write tests that mirror the one-position-per-call reality.
Idempotency comes from TripEvent.IdempotencyKey (unique) plus (TripStopId, ClientEventId) on proofs of delivery.
A stop may reference a geofence. The geofence polygon is read once, at StartTrip, to snapshot the stop's ArrivalGeom. Editing that geofence mid-trip therefore cannot move a running trip's arrival area.
This read goes straight to geofencing.geofences as a read-only EF projection — there is no TripManagement → Geofencing service call.
Route geometry comes from OpenRouteService over plain REST (Infrastructure/RoutingApi, AppSettings:Routing, throttled with bounded retry). planTripRoute stores the route LineString, a buffered corridor Polygon, planned distance and duration, and the toll estimate.
ORS failure degrades to
RoutePlan.Status = Failed— it never blocks a trip command.
| Situation | Result |
|---|---|
| A matched station has no tariff for the trip's vehicle class | Contributes 0 and sets TollStatus = PartialNoTariff
|
| The catalog is empty |
TollStatus = NoStations and a null estimate — never a fabricated number |
Tariffs are temporal: a price change closes the current row and inserts a new one, so a historical trip's estimate stays explainable.
The catalog is platform reference data — toll_stations, toll_tariffs, toll_vehicle_classes carry no AccountId. transporter_toll_classes is the per-account mapping from a transporter (or transporter type) to a vehicle class.
Performance note: the
ST_DWithin(..., TRUE)geography predicate used for station matching cannot use the geometry GiST index.
GET ~/public/trips/{publicLinkGrantId:guid} — an EndpointGroupBase endpoint marked AllowAnonymous, rate-limited per client IP, behind UseForwardedHeaders.
It bypasses the mediator (the pipeline's behaviours assume a principal) and resolves the link through Manager's shared ResolvePublicLinkGrantCommand.
Three deliberate decisions:
-
It is not output-cached. A cache hit would never reach Manager's resolver, so it would neither count the access nor write the
PublicLinkAccessedaudit event — and it would keep serving a revoked link. -
Disclosure is driven by the
trip_sharesfield flags, not by client-side filtering. Every flag defaults to false; a disclosure flag fails closed. -
A disabled feature returns 404, not
FEATURE_DISABLED— non-disclosure. The page must not reveal that a trip exists.
Both run as hosted services inside the web host — there is no separate worker process.
| Service | Job key | Interval | Purpose |
|---|---|---|---|
TripEtaRefreshService |
trip-eta-refresh |
5 min | Recomputes the ORS ETA to the next pending stop; raises TripDelayed once per stop |
TripScheduleReminderService |
trip-schedule-reminder |
15 min | Raises TripStartDue; it never changes trip status |
Both are on-work-only recorders: an old BackgroundJobRun timestamp is the healthy steady state and renders neutrally on the status page. Do not "fix" that as a stuck job.
| Target | Protocol | Identity | Purpose |
|---|---|---|---|
| Manager | GraphQL | trip_client |
Alert emission, job runs, driver/transporter/group validation, document metadata, public-link grants |
| Telemetry | GraphQL | trip_client |
positionHistoryRange for route replay |
| OpenRouteService | REST | API key | Route geometry, corridors, ETAs |
trip_client holds Alerts/Write, BackgroundJobs/Write, Drivers/Read, Transporters/Read, Groups/Read, Documents/Read, PublicLinks/Write|Delete|Read, PositionHistory/Read and AccountFeatures/Read.
The Router and SyncWorker identities hold TripTracking/Custom and nothing else in this module, so they can call processTripPositions only.
| Group | Operations |
|---|---|
| Trip CRUD |
createTrip, updateTrip, deleteTrip
|
| Assignment | assignTrip |
| Routing | planTripRoute |
| Lifecycle |
startTrip, pauseTrip, resumeTrip, completeTrip, cancelTrip, abortTrip
|
| Stop planning |
addTripStop, updateTripStop, removeTripStop, reorderTripStops
|
| Stop progression |
recordStopArrival, recordStopDeparture, skipStop
|
| Deliveries |
createDelivery, updateDelivery, updateDeliveryOutcome, deleteDelivery
|
| Proof of delivery | recordProofOfDelivery |
| Sharing |
shareTrip, revokeTripShare
|
| Detection feed | processTripPositions |
| External integration |
importTrips, updateTripStatus
|
| Toll catalog |
createTollVehicleClass, updateTollVehicleClass, deactivateTollVehicleClass, createTollStation, updateTollStation, deactivateTollStation, createTollTariff, updateTollTariff, deleteTollTariff, importTollCatalog, setTransporterTollClass
|
| Query | Purpose |
|---|---|
trips |
Paged trip list for the caller's account, with filtering |
tripDetail |
One trip with its stops, deliveries, assignment and route plan |
activeTrips |
Trips currently in progress, for the live map |
tripTimeline |
Paged event log for a trip |
tripRouteReplay |
Planned route plus recorded positions |
tripReportData, tripStopReportData, tripTollReportData, tripPodReportData
|
Paged report datasets consumed by Reporting |
tollStations, tollStationDetail, tollVehicleClasses, transporterTollClasses
|
Toll catalog reads |
estimateTolls |
Toll cost over a route for a given vehicle class |
| Endpoint | Purpose |
|---|---|
GET ~/public/trips/{publicLinkGrantId} |
Anonymous public tracking (see above) |
GET /health |
Health probe, including a database context check |
Reporting drains four paged feeds (500 per page, 100 000-row defensive cap) through Infrastructure/GraphQLApi/TripReportReader.cs, backing six catalog reports: trip-summary, trip-detail, trip-on-time-performance, trip-stop-dwell, trip-toll-cost and trip-pod-export. Only on-time-performance and toll-cost support PDF.
| View | Purpose |
|---|---|
trip.vw_visible_transporter |
The single source of portal group visibility — joins app.transporters → app.transporter_group → app.user_group, so a User principal only sees trips for transporters in their groups |
trip.vw_users |
Resolves the acting user's account locally from app.users, avoiding a cross-service call per request |
TripManagement additionally reads — but never owns or migrates — geofencing.geofences and several app tables (accounts, account features, transporters, drivers, audit events).
| Key | Purpose |
|---|---|
ConnectionStrings:DefaultConnection |
PostgreSQL connection for the trip schema |
AuthorityServer:ClientId (trip_client) |
Service client for service-to-service calls |
AppSettings:GraphQLIdentityService |
Security API endpoint |
AppSettings:GraphQLManagerService |
Manager API endpoint |
AppSettings:GraphQLTelemetryService |
Telemetry API endpoint |
AppSettings:Routing |
Routing provider settings: provider, base URL, API key, profile, rate limit, timeout, max waypoints |
AllowedCorsOrigins |
Origins allowed to call the API from the browser |
The portal reaches this service through REACT_APP_TRIPMANAGEMENT_ENDPOINT; other backend services reach it through AppSettings:GraphQLTripManagementService.
Platform
- Architecture
- Technology
- Inter-Service Communication
- Database
- Security and Identity
- User Permissions Overview
Services
- Manager
- Telemetry
- Router
- Adding a Provider
- Geofencing
- Trip Management
- Reporting
- Common Library
- Frontend
Engineering
User docs
- User Guide (ships in the app)