Skip to content

Trip Management

Sergio Hernandez edited this page Jul 24, 2026 · 1 revision

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


Feature gating

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 IFeatureFlagService in Infrastructure/TripDB/DependencyInjection.cs. Common's default is fail-open (AlwaysEnabledFeatureFlagService, registered with TryAddScoped), so a service that does not override it silently ignores every [RequireFeature] attribute.


Trip lifecycle

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 --> [*]
Loading

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.


Detection

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.

Arrival geometry is snapshotted

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 planning and tolls

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.

Toll estimates are explainable, never silently understated

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.


Public tracking

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 PublicLinkAccessed audit event — and it would keep serving a revoked link.
  • Disclosure is driven by the trip_shares field 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.

Background services

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.


Outbound dependencies

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.


GraphQL surface

Mutations

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

Queries

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

REST

Endpoint Purpose
GET ~/public/trips/{publicLinkGrantId} Anonymous public tracking (see above)
GET /health Health probe, including a database context check

Reporting integration

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.


Views

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).


Configuration

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.

Clone this wiki locally