See how the whole system works
16 diagrams covering the architecture, the role hierarchy, the data model and every operational workflow. The trip lifecycle and the permission matrix are generated from the code that enforces them, so they cannot fall out of date.
Flowcharts
Planning, publication rules, exceptions, attendance, billing and notifications — each decision drawn out.
Data models
Three entity diagrams: the core spine, fleet and compliance, and everything commercial.
Live from the code
The lifecycle state machine and the role permission grid are read straight from the enforcing source.
Start here
Two diagrams that explain the shape of the whole product.
The core journey: demand becomes a trip
This is the loop the product exists to run. Everything else supports it. Note that a route is a plan you can edit freely, and publishing is the one-way door that turns it into an operational record.
flowchart TD
START(["Transport Manager opens the Route Planner"]) --> PICK["Choose service date,<br/>site and shift"]
PICK --> LOAD["Load demand"]
LOAD --> HAS{"Any open<br/>demand?"}
HAS -->|"No"| GEN["Generate demand<br/>from the roster"]
GEN --> LOAD
HAS -->|"Yes"| POOL["Generate routes<br/>pool by pickup area,<br/>split to fit capacity"]
POOL --> EDIT["Reorder stops,<br/>move passengers"]
EDIT --> ALLOC["Allocate vendor,<br/>vehicle and driver"]
ALLOC --> VALID{"All rules<br/>pass?"}
VALID -->|"No"| FIX["Fix the cause,<br/>or record an<br/>authorised override"]
FIX --> VALID
VALID -->|"Yes"| PUB["Publish route"]
PUB --> TRIP["Trip created<br/>Manifest built"]
TRIP --> NOTIFY["Passengers and driver<br/>notified"]
NOTIFY --> DONE(["Ready to run"])
classDef gate fill:#fdf3e3,stroke:#f2d9a8,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class VALID,HAS gate
class TRIP,NOTIFY,DONE good
flowchart TD
START(["Transport Manager opens the Route Planner"]) --> PICK["Choose service date,<br/>site and shift"]
PICK --> LOAD["Load demand"]
LOAD --> HAS{"Any open<br/>demand?"}
HAS -->|"No"| GEN["Generate demand<br/>from the roster"]
GEN --> LOAD
HAS -->|"Yes"| POOL["Generate routes<br/>pool by pickup area,<br/>split to fit capacity"]
POOL --> EDIT["Reorder stops,<br/>move passengers"]
EDIT --> ALLOC["Allocate vendor,<br/>vehicle and driver"]
ALLOC --> VALID{"All rules<br/>pass?"}
VALID -->|"No"| FIX["Fix the cause,<br/>or record an<br/>authorised override"]
FIX --> VALID
VALID -->|"Yes"| PUB["Publish route"]
PUB --> TRIP["Trip created<br/>Manifest built"]
TRIP --> NOTIFY["Passengers and driver<br/>notified"]
NOTIFY --> DONE(["Ready to run"])
classDef gate fill:#fdf3e3,stroke:#f2d9a8,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class VALID,HAS gate
class TRIP,NOTIFY,DONE good
System architecture
Five kinds of client reach the same domain modules through one access layer. Nothing bypasses authentication, capability checks or tenant scoping — including the REST API. The transport policy sits to one side because it configures the others rather than being called by them.
flowchart TB
subgraph Clients["Who uses it"]
OPS["Operations console<br/>desktop"]
EMP["Employee<br/>mobile web"]
DRV["Driver<br/>mobile web"]
VEN["Vendor portal"]
API["Mobile apps &<br/>integrations"]
end
subgraph Access["Access layer"]
AUTH["Authentication<br/>apps/accounts"]
RBAC["Capability RBAC<br/>core/permissions.py"]
TEN["Tenant scoping<br/>core/middleware.py"]
end
subgraph Domain["Domain modules"]
PLAN["planning<br/>shifts, demand, routes"]
FLEET["fleet<br/>vendors, vehicles, drivers"]
TRIP["trips<br/>lifecycle, manifest, GPS"]
SAFE["safety<br/>incidents, SOS"]
BILL["billing<br/>rate cards, invoices"]
EMPL["employees<br/>profiles, eligibility"]
end
subgraph Cross["Cross-cutting"]
POLICY["Transport policy<br/>the rules engine"]
AUDIT["Audit log"]
NOTIF["Notifications"]
REP["Reports"]
end
DB[("PostgreSQL")]
OPS --> AUTH
EMP --> AUTH
DRV --> AUTH
VEN --> AUTH
API --> AUTH
AUTH --> RBAC --> TEN
TEN --> Domain
Domain --> Cross
Domain --> DB
Cross --> DB
POLICY -.->|"configures"| PLAN
POLICY -.->|"configures"| TRIP
POLICY -.->|"configures"| SAFE
flowchart TB
subgraph Clients["Who uses it"]
OPS["Operations console<br/>desktop"]
EMP["Employee<br/>mobile web"]
DRV["Driver<br/>mobile web"]
VEN["Vendor portal"]
API["Mobile apps &<br/>integrations"]
end
subgraph Access["Access layer"]
AUTH["Authentication<br/>apps/accounts"]
RBAC["Capability RBAC<br/>core/permissions.py"]
TEN["Tenant scoping<br/>core/middleware.py"]
end
subgraph Domain["Domain modules"]
PLAN["planning<br/>shifts, demand, routes"]
FLEET["fleet<br/>vendors, vehicles, drivers"]
TRIP["trips<br/>lifecycle, manifest, GPS"]
SAFE["safety<br/>incidents, SOS"]
BILL["billing<br/>rate cards, invoices"]
EMPL["employees<br/>profiles, eligibility"]
end
subgraph Cross["Cross-cutting"]
POLICY["Transport policy<br/>the rules engine"]
AUDIT["Audit log"]
NOTIF["Notifications"]
REP["Reports"]
end
DB[("PostgreSQL")]
OPS --> AUTH
EMP --> AUTH
DRV --> AUTH
VEN --> AUTH
API --> AUTH
AUTH --> RBAC --> TEN
TEN --> Domain
Domain --> Cross
Domain --> DB
Cross --> DB
POLICY -.->|"configures"| PLAN
POLICY -.->|"configures"| TRIP
POLICY -.->|"configures"| SAFE
People & permissions
Who does what, and what the server actually allows them to do.
Role hierarchy
Solid lines are administrative authority; dashed lines are working relationships. Vendors, drivers and employees sit outside the internal console and see deliberately narrower views of the same data.
flowchart TB
SA["Platform Super Admin<br/><i>provisions tenants</i>"]
TA["Tenant Admin<br/><i>configures the rules</i>"]
TM["Transport Manager<br/><i>plans and publishes</i>"]
DP["Dispatcher<br/><i>runs the shift</i>"]
CR["Control Room<br/><i>answers safety alerts</i>"]
FI["Finance<br/><i>rate cards and invoices</i>"]
VE["Vendor<br/><i>supplies cabs</i>"]
DR["Driver<br/><i>drives the trip</i>"]
EM["Employee<br/><i>travels</i>"]
SA --> TA
TA --> TM
TA --> FI
TA --> CR
TM --> DP
TM -.->|"contracts"| VE
VE --> DR
DP -.->|"dispatches"| DR
DR -.->|"carries"| EM
DP -.->|"serves"| EM
classDef platform fill:#111a4c,stroke:#111a4c,color:#fff
classDef internal fill:#eaf1fd,stroke:#c3d8f8,color:#101828
classDef external fill:#f1f3f7,stroke:#dfe3ea,color:#101828
class SA platform
class TA,TM,DP,CR,FI internal
class VE,DR,EM external
flowchart TB
SA["Platform Super Admin<br/><i>provisions tenants</i>"]
TA["Tenant Admin<br/><i>configures the rules</i>"]
TM["Transport Manager<br/><i>plans and publishes</i>"]
DP["Dispatcher<br/><i>runs the shift</i>"]
CR["Control Room<br/><i>answers safety alerts</i>"]
FI["Finance<br/><i>rate cards and invoices</i>"]
VE["Vendor<br/><i>supplies cabs</i>"]
DR["Driver<br/><i>drives the trip</i>"]
EM["Employee<br/><i>travels</i>"]
SA --> TA
TA --> TM
TA --> FI
TA --> CR
TM --> DP
TM -.->|"contracts"| VE
VE --> DR
DP -.->|"dispatches"| DR
DR -.->|"carries"| EM
DP -.->|"serves"| EM
classDef platform fill:#111a4c,stroke:#111a4c,color:#fff
classDef internal fill:#eaf1fd,stroke:#c3d8f8,color:#101828
classDef external fill:#f1f3f7,stroke:#dfe3ea,color:#101828
class SA platform
class TA,TM,DP,CR,FI internal
class VE,DR,EM external
Navigation map
Every screen in the product, grouped the way the sidebar groups them, with the roles that can reach each one. Read from the same schema that builds the real sidebar, so it always matches what people actually see.
Permission matrix
Read straight out of the role table the server enforces on every view and every API endpoint. A tick means that role holds the capability; hiding a button in the interface is never what grants access.
| Role | Operations | Planning | People & fleet | Safety | Commercial | Insight & admin | Self-service | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
View dashboard
|
Dispatch
|
View trips
|
View location
|
Plan routes
|
Publish routes
|
Override policy
|
Manage requests
|
Manage shifts
|
View employees
|
Manage employees
|
View fleet
|
Manage fleet
|
Manage vendors
|
View incidents
|
Manage incidents
|
Manage billing
|
Approve invoices
|
Submit invoice
|
View reports
|
Manage settings
|
Manage users
|
View audit log
|
Employee portal
|
Driver app
|
Vendor portal
|
|
| Tenant Admin | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Not allowed | Allowed | Allowed | Allowed | Allowed | Not allowed | Not allowed | Not allowed |
| HR / Transport Manager | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed |
| Transport Desk / Dispatcher | Allowed | Allowed | Allowed | Allowed | Allowed | Allowed | Not allowed | Allowed | Not allowed | Allowed | Not allowed | Allowed | Not allowed | Not allowed | Allowed | Allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed |
| Security / Control Room | Allowed | Not allowed | Allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed | Allowed | Not allowed | Not allowed | Allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed |
| Management / Finance | Allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed |
| Vendor Manager | Not allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed |
| Driver | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed |
| Employee | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Not allowed | Allowed | Not allowed | Not allowed |
Data model
Three views of the schema, split so each one stays readable.
Core: tenant, people, demand and trips
Follow the spine: an employee raises a transport request for a shift, the request becomes a route stop, the route is published as a trip, and the trip carries passengers. Everything hangs off a tenant.
erDiagram
TENANT ||--o{ SITE : "operates"
TENANT ||--|| TRANSPORT_POLICY : "configured by"
TENANT ||--o{ BUSINESS_UNIT : "reports against"
SITE ||--o{ SHIFT : "runs"
SITE ||--o{ EMPLOYEE : "employs"
BUSINESS_UNIT ||--o{ EMPLOYEE : "groups"
EMPLOYEE ||--o{ EMERGENCY_CONTACT : "has"
PICKUP_POINT ||--o{ EMPLOYEE : "collects from"
EMPLOYEE ||--o{ TRANSPORT_REQUEST : "raises"
SHIFT ||--o{ TRANSPORT_REQUEST : "for"
SHIFT ||--o{ ROUTE : "planned for"
TRANSPORT_REQUEST ||--o| ROUTE_STOP : "rostered as"
ROUTE ||--o{ ROUTE_STOP : "sequences"
PICKUP_POINT ||--o{ ROUTE_STOP : "at"
ROUTE ||--o| TRIP : "published as"
TRIP ||--o{ TRIP_PASSENGER : "manifests"
TRIP ||--o{ TRIP_EVENT : "records"
TRIP ||--o{ LOCATION_PING : "reports"
EMPLOYEE ||--o{ TRIP_PASSENGER : "travels as"
erDiagram
TENANT ||--o{ SITE : "operates"
TENANT ||--|| TRANSPORT_POLICY : "configured by"
TENANT ||--o{ BUSINESS_UNIT : "reports against"
SITE ||--o{ SHIFT : "runs"
SITE ||--o{ EMPLOYEE : "employs"
BUSINESS_UNIT ||--o{ EMPLOYEE : "groups"
EMPLOYEE ||--o{ EMERGENCY_CONTACT : "has"
PICKUP_POINT ||--o{ EMPLOYEE : "collects from"
EMPLOYEE ||--o{ TRANSPORT_REQUEST : "raises"
SHIFT ||--o{ TRANSPORT_REQUEST : "for"
SHIFT ||--o{ ROUTE : "planned for"
TRANSPORT_REQUEST ||--o| ROUTE_STOP : "rostered as"
ROUTE ||--o{ ROUTE_STOP : "sequences"
PICKUP_POINT ||--o{ ROUTE_STOP : "at"
ROUTE ||--o| TRIP : "published as"
TRIP ||--o{ TRIP_PASSENGER : "manifests"
TRIP ||--o{ TRIP_EVENT : "records"
TRIP ||--o{ LOCATION_PING : "reports"
EMPLOYEE ||--o{ TRIP_PASSENGER : "travels as"
Fleet, vendors and compliance
A compliance document belongs to a driver or a vehicle, never both — a database constraint enforces that. Mandatory documents with a past expiry are what block allocation.
erDiagram
TENANT ||--o{ VENDOR : "contracts"
VENDOR ||--o{ VEHICLE : "supplies"
VENDOR ||--o{ DRIVER : "employs"
VEHICLE_TYPE ||--o{ VEHICLE : "classifies"
DRIVER ||--o| VEHICLE : "usually drives"
DRIVER ||--o{ ASSIGNMENT_HISTORY : "history"
VEHICLE ||--o{ ASSIGNMENT_HISTORY : "history"
DOCUMENT_TYPE ||--o{ COMPLIANCE_DOCUMENT : "defines"
DRIVER ||--o{ COMPLIANCE_DOCUMENT : "holds"
VEHICLE ||--o{ COMPLIANCE_DOCUMENT : "holds"
VENDOR ||--o{ TRIP : "operates"
VEHICLE ||--o{ TRIP : "used on"
DRIVER ||--o{ TRIP : "drives"
USER ||--o| DRIVER : "signs in as"
USER }o--o{ VENDOR : "portal access"
erDiagram
TENANT ||--o{ VENDOR : "contracts"
VENDOR ||--o{ VEHICLE : "supplies"
VENDOR ||--o{ DRIVER : "employs"
VEHICLE_TYPE ||--o{ VEHICLE : "classifies"
DRIVER ||--o| VEHICLE : "usually drives"
DRIVER ||--o{ ASSIGNMENT_HISTORY : "history"
VEHICLE ||--o{ ASSIGNMENT_HISTORY : "history"
DOCUMENT_TYPE ||--o{ COMPLIANCE_DOCUMENT : "defines"
DRIVER ||--o{ COMPLIANCE_DOCUMENT : "holds"
VEHICLE ||--o{ COMPLIANCE_DOCUMENT : "holds"
VENDOR ||--o{ TRIP : "operates"
VEHICLE ||--o{ TRIP : "used on"
DRIVER ||--o{ TRIP : "drives"
USER ||--o| DRIVER : "signs in as"
USER }o--o{ VENDOR : "portal access"
Rate cards, invoices and payments
An invoice line points back at the trip it prices and the rate card it was priced with, so any figure can be traced to the journey that produced it.
erDiagram
VENDOR ||--o{ RATE_CARD : "priced by"
VEHICLE_TYPE ||--o{ RATE_CARD : "narrows"
SITE ||--o{ RATE_CARD : "narrows"
VENDOR ||--o{ INVOICE : "billed on"
INVOICE ||--o{ INVOICE_LINE : "itemises"
TRIP ||--o| INVOICE_LINE : "priced as"
RATE_CARD ||--o{ INVOICE_LINE : "applied to"
INVOICE ||--o{ PAYMENT : "settled by"
USER ||--o{ INVOICE : "approves"
USER ||--o{ INVOICE_LINE : "adjusts"
erDiagram
VENDOR ||--o{ RATE_CARD : "priced by"
VEHICLE_TYPE ||--o{ RATE_CARD : "narrows"
SITE ||--o{ RATE_CARD : "narrows"
VENDOR ||--o{ INVOICE : "billed on"
INVOICE ||--o{ INVOICE_LINE : "itemises"
TRIP ||--o| INVOICE_LINE : "priced as"
RATE_CARD ||--o{ INVOICE_LINE : "applied to"
INVOICE ||--o{ PAYMENT : "settled by"
USER ||--o{ INVOICE : "approves"
USER ||--o{ INVOICE_LINE : "adjusts"
Trip execution
How a trip moves, who moves it, and what the system refuses to do.
Trip lifecycle
Generated directly from the transition table the product enforces, so this diagram cannot drift from the code. Any jump not drawn here is rejected — a planned trip cannot become completed, for example.
stateDiagram-v2
direction LR
[*] --> Planned
Planned --> Assigned
Planned --> Cancelled
Planned --> Exception
Assigned --> Cancelled
Assigned --> Confirmed
Assigned --> Exception
Assigned --> Planned
Confirmed --> Cancelled
Confirmed --> EnRoute
Confirmed --> Exception
EnRoute --> AtPickup
EnRoute --> Cancelled
EnRoute --> Exception
EnRoute --> InProgress
AtPickup --> Cancelled
AtPickup --> Exception
AtPickup --> InProgress
InProgress --> Completed
InProgress --> Exception
Exception --> Cancelled
Exception --> Completed
Exception --> InProgress
Completed --> [*]
Cancelled --> [*]
Planned : Planned
Assigned : Assigned
Confirmed : Confirmed
EnRoute : Driver en route
AtPickup : At pickup
InProgress : In progress
Completed : Completed
Cancelled : Cancelled
Exception : Exception
note right of Completed
Locked for billing.
No further edits.
end note
stateDiagram-v2
direction LR
[*] --> Planned
Planned --> Assigned
Planned --> Cancelled
Planned --> Exception
Assigned --> Cancelled
Assigned --> Confirmed
Assigned --> Exception
Assigned --> Planned
Confirmed --> Cancelled
Confirmed --> EnRoute
Confirmed --> Exception
EnRoute --> AtPickup
EnRoute --> Cancelled
EnRoute --> Exception
EnRoute --> InProgress
AtPickup --> Cancelled
AtPickup --> Exception
AtPickup --> InProgress
InProgress --> Completed
InProgress --> Exception
Exception --> Cancelled
Exception --> Completed
Exception --> InProgress
Completed --> [*]
Cancelled --> [*]
Planned : Planned
Assigned : Assigned
Confirmed : Confirmed
EnRoute : Driver en route
AtPickup : At pickup
InProgress : In progress
Completed : Completed
Cancelled : Cancelled
Exception : Exception
note right of Completed
Locked for billing.
No further edits.
end note
Passenger attendance
Drivers record boarding and drops as they go. A no-show raises an incident and feeds vendor and route metrics. Dispatchers can correct any of it, and the correction is attributed and reasoned.
flowchart LR
S["Scheduled"] --> N["Notified"]
N --> B["Boarded"]
N --> X["No-show"]
S --> B
S --> X
B --> D["Dropped"]
X -.->|"Dispatcher correction<br/>with a reason"| S
B -.->|"Dispatcher correction"| S
D -.->|"Trip completion<br/>closes any open boarding"| D
X --> INC["Incident raised<br/>Feeds vendor and<br/>route metrics"]
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class X,INC bad
class D good
flowchart LR
S["Scheduled"] --> N["Notified"]
N --> B["Boarded"]
N --> X["No-show"]
S --> B
S --> X
B --> D["Dropped"]
X -.->|"Dispatcher correction<br/>with a reason"| S
B -.->|"Dispatcher correction"| S
D -.->|"Trip completion<br/>closes any open boarding"| D
X --> INC["Incident raised<br/>Feeds vendor and<br/>route metrics"]
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class X,INC bad
class D good
Location handling
The rule that matters is at the bottom: a position older than the configured threshold is labelled last known, never live. Out-of-order pings cannot rewind the marker.
flowchart TD
PING(["Location ping arrives<br/>driver app or GPS provider"]) --> VALID{"Coordinates<br/>valid?"}
VALID -->|"No"| REJECT["Rejected"]
VALID -->|"Yes"| STORE["Stored as a LocationPing"]
STORE --> NEWER{"Newer than the trip's<br/>last known position?"}
NEWER -->|"No"| KEEP["Marker unchanged<br/>out-of-order pings<br/>never rewind it"]
NEWER -->|"Yes"| UPDATE["Update the trip's<br/>last known position<br/>and timestamp"]
UPDATE --> AGE{"How old is it?<br/><i>stale threshold from policy</i>"}
KEEP --> AGE
AGE -->|"Within threshold"| LIVE["Shown as Live"]
AGE -->|"Older"| STALEV["Shown as Last known<br/>with the timestamp<br/><i>BR-05</i>"]
AGE -->|"Never received"| NONE["Shown as No GPS"]
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
class LIVE good
class STALEV,NONE warn
flowchart TD
PING(["Location ping arrives<br/>driver app or GPS provider"]) --> VALID{"Coordinates<br/>valid?"}
VALID -->|"No"| REJECT["Rejected"]
VALID -->|"Yes"| STORE["Stored as a LocationPing"]
STORE --> NEWER{"Newer than the trip's<br/>last known position?"}
NEWER -->|"No"| KEEP["Marker unchanged<br/>out-of-order pings<br/>never rewind it"]
NEWER -->|"Yes"| UPDATE["Update the trip's<br/>last known position<br/>and timestamp"]
UPDATE --> AGE{"How old is it?<br/><i>stale threshold from policy</i>"}
KEEP --> AGE
AGE -->|"Within threshold"| LIVE["Shown as Live"]
AGE -->|"Older"| STALEV["Shown as Last known<br/>with the timestamp<br/><i>BR-05</i>"]
AGE -->|"Never received"| NONE["Shown as No GPS"]
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
class LIVE good
class STALEV,NONE warn
Rules & exceptions
The decisions the product makes on its own, and where a human overrules it.
Route publication checks
Every gate a route passes before it can be published. Five blocks can be overridden by an authorised user with a written reason; a duplicate passenger cannot be overridden at all, because it would double-book a person.
flowchart TD
PUB(["Publish requested"]) --> EMPTY{"Has<br/>passengers?"}
EMPTY -->|"No"| BLOCK1["Blocked<br/>route is empty"]
EMPTY -->|"Yes"| ALLOC{"Vendor, vehicle<br/>and driver set?"}
ALLOC -->|"No"| BLOCK2["Blocked<br/>allocation incomplete"]
ALLOC -->|"Yes"| CAP{"Seats within<br/>vehicle capacity?<br/><i>BR-01</i>"}
CAP -->|"No"| BLOCK3["Blocked<br/>over capacity"]
CAP -->|"Yes"| DOC{"Driver and vehicle<br/>documents valid?<br/><i>BR-03</i>"}
DOC -->|"No"| BLOCK4["Blocked<br/>expired documents"]
DOC -->|"Yes"| DUP{"Passenger on<br/>another route<br/>this shift?"}
DUP -->|"Yes"| HARD["Blocked — cannot<br/>be overridden"]
DUP -->|"No"| SAFE{"Protected passenger<br/>as last drop at night?<br/><i>BR-02</i>"}
SAFE -->|"Yes"| BLOCK5["Blocked<br/>last-drop policy"]
SAFE -->|"No"| OK["Published"]
BLOCK1 --> OVR{"Authorised<br/>override with<br/>a reason?<br/><i>BR-09</i>"}
BLOCK2 --> OVR
BLOCK3 --> OVR
BLOCK4 --> OVR
BLOCK5 --> OVR
OVR -->|"Yes, and the user holds<br/>override permission"| AUDIT["Published<br/>override and reason<br/>written to the audit log"]
OVR -->|"No"| STOP["Not published"]
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
class BLOCK1,BLOCK2,BLOCK3,BLOCK4,BLOCK5,HARD,STOP bad
class OK good
class AUDIT warn
flowchart TD
PUB(["Publish requested"]) --> EMPTY{"Has<br/>passengers?"}
EMPTY -->|"No"| BLOCK1["Blocked<br/>route is empty"]
EMPTY -->|"Yes"| ALLOC{"Vendor, vehicle<br/>and driver set?"}
ALLOC -->|"No"| BLOCK2["Blocked<br/>allocation incomplete"]
ALLOC -->|"Yes"| CAP{"Seats within<br/>vehicle capacity?<br/><i>BR-01</i>"}
CAP -->|"No"| BLOCK3["Blocked<br/>over capacity"]
CAP -->|"Yes"| DOC{"Driver and vehicle<br/>documents valid?<br/><i>BR-03</i>"}
DOC -->|"No"| BLOCK4["Blocked<br/>expired documents"]
DOC -->|"Yes"| DUP{"Passenger on<br/>another route<br/>this shift?"}
DUP -->|"Yes"| HARD["Blocked — cannot<br/>be overridden"]
DUP -->|"No"| SAFE{"Protected passenger<br/>as last drop at night?<br/><i>BR-02</i>"}
SAFE -->|"Yes"| BLOCK5["Blocked<br/>last-drop policy"]
SAFE -->|"No"| OK["Published"]
BLOCK1 --> OVR{"Authorised<br/>override with<br/>a reason?<br/><i>BR-09</i>"}
BLOCK2 --> OVR
BLOCK3 --> OVR
BLOCK4 --> OVR
BLOCK5 --> OVR
OVR -->|"Yes, and the user holds<br/>override permission"| AUDIT["Published<br/>override and reason<br/>written to the audit log"]
OVR -->|"No"| STOP["Not published"]
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
class BLOCK1,BLOCK2,BLOCK3,BLOCK4,BLOCK5,HARD,STOP bad
class OK good
class AUDIT warn
Transport request and cut-offs
Whether a request is approved automatically depends on one configured number. The same idea governs cancellation.
flowchart TD
RAISE(["Employee raises a<br/>transport request"]) --> CHECK{"Is it before the<br/>request cut-off?<br/><i>BR-06</i>"}
CHECK -->|"Yes"| APPROVED["Approved automatically"]
CHECK -->|"No"| PENDING["Pending approval<br/>Transport desk decides"]
PENDING --> DECIDE{"Desk decision"}
DECIDE -->|"Approve"| APPROVED
DECIDE -->|"Reject"| REJECTED["Rejected<br/>Employee notified"]
APPROVED --> POOL["Enters the demand pool<br/>for that shift and date"]
POOL --> ROSTERED["Rostered onto a route"]
ROSTERED --> ASSIGNED["Assigned<br/>route published, trip created"]
APPROVED -.->|"Employee cancels"| CANCELCHK{"Before the<br/>cancellation cut-off?"}
CANCELCHK -->|"Yes"| CANCELLED["Cancelled<br/>Seat released"]
CANCELCHK -->|"No"| LATE["Cancelled late<br/>Policy applies<br/>Transport team notified"]
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
class PENDING,LATE warn
class APPROVED,ASSIGNED good
class REJECTED bad
flowchart TD
RAISE(["Employee raises a<br/>transport request"]) --> CHECK{"Is it before the<br/>request cut-off?<br/><i>BR-06</i>"}
CHECK -->|"Yes"| APPROVED["Approved automatically"]
CHECK -->|"No"| PENDING["Pending approval<br/>Transport desk decides"]
PENDING --> DECIDE{"Desk decision"}
DECIDE -->|"Approve"| APPROVED
DECIDE -->|"Reject"| REJECTED["Rejected<br/>Employee notified"]
APPROVED --> POOL["Enters the demand pool<br/>for that shift and date"]
POOL --> ROSTERED["Rostered onto a route"]
ROSTERED --> ASSIGNED["Assigned<br/>route published, trip created"]
APPROVED -.->|"Employee cancels"| CANCELCHK{"Before the<br/>cancellation cut-off?"}
CANCELCHK -->|"Yes"| CANCELLED["Cancelled<br/>Seat released"]
CANCELCHK -->|"No"| LATE["Cancelled late<br/>Policy applies<br/>Transport team notified"]
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
class PENDING,LATE warn
class APPROVED,ASSIGNED good
class REJECTED bad
Exception detection and dispatcher response
The system watches for delay, stale telemetry and no-shows, raises the exception itself, and puts it in front of the dispatcher. Closing it always needs a resolution.
flowchart TD
RUN(["Trips running"]) --> SCAN["System scans active trips<br/><i>trips/services.detect_exceptions</i>"]
SCAN --> LATE{"Past scheduled start<br/>by more than the<br/>late threshold?"}
LATE -->|"Yes"| INC1["Raise 'Driver late'<br/>incident"]
SCAN --> STALE{"Last GPS ping older<br/>than the staleness<br/>threshold?"}
STALE -->|"Yes"| INC2["Raise 'GPS unavailable'<br/>incident<br/>Label position as last known"]
SCAN --> NOSHOW{"Passenger not boarded<br/>after the wait time?"}
NOSHOW -->|"Yes"| INC3["Driver marks no-show<br/>Incident raised"]
INC1 --> QUEUE["Exception appears on the<br/>dispatch board and pins to<br/>the top of Live Operations"]
INC2 --> QUEUE
INC3 --> QUEUE
QUEUE --> ACK["Dispatcher acknowledges"]
ACK --> DECIDE{"What is needed?"}
DECIDE -->|"Different cab or driver"| REASSIGN["Reassign with a reason<br/>Passengers notified<br/>Audit written"]
DECIDE -->|"Cannot run"| CANCEL["Cancel trip<br/>Non-billable"]
DECIDE -->|"Senior decision"| ESC["Escalate"]
DECIDE -->|"Handled"| RESOLVE["Resolve with a resolution"]
REASSIGN --> RESOLVE
ESC --> RESOLVE
CANCEL --> RESOLVE
RESOLVE --> CLOSED(["Closed with an audit trail"])
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class INC1,INC2,INC3,QUEUE warn
class RESOLVE,CLOSED good
flowchart TD
RUN(["Trips running"]) --> SCAN["System scans active trips<br/><i>trips/services.detect_exceptions</i>"]
SCAN --> LATE{"Past scheduled start<br/>by more than the<br/>late threshold?"}
LATE -->|"Yes"| INC1["Raise 'Driver late'<br/>incident"]
SCAN --> STALE{"Last GPS ping older<br/>than the staleness<br/>threshold?"}
STALE -->|"Yes"| INC2["Raise 'GPS unavailable'<br/>incident<br/>Label position as last known"]
SCAN --> NOSHOW{"Passenger not boarded<br/>after the wait time?"}
NOSHOW -->|"Yes"| INC3["Driver marks no-show<br/>Incident raised"]
INC1 --> QUEUE["Exception appears on the<br/>dispatch board and pins to<br/>the top of Live Operations"]
INC2 --> QUEUE
INC3 --> QUEUE
QUEUE --> ACK["Dispatcher acknowledges"]
ACK --> DECIDE{"What is needed?"}
DECIDE -->|"Different cab or driver"| REASSIGN["Reassign with a reason<br/>Passengers notified<br/>Audit written"]
DECIDE -->|"Cannot run"| CANCEL["Cancel trip<br/>Non-billable"]
DECIDE -->|"Senior decision"| ESC["Escalate"]
DECIDE -->|"Handled"| RESOLVE["Resolve with a resolution"]
REASSIGN --> RESOLVE
ESC --> RESOLVE
CANCEL --> RESOLVE
RESOLVE --> CLOSED(["Closed with an audit trail"])
classDef warn fill:#fdf3e3,stroke:#f2d9a8,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class INC1,INC2,INC3,QUEUE warn
class RESOLVE,CLOSED good
Safety
The path an emergency takes, end to end.
SOS escalation
Numbered so you can follow the order. Two details matter: the person is told what will happen before they confirm, and they see the acknowledgement when it arrives — so they know someone is responding.
sequenceDiagram
autonumber
actor E as Employee or Driver
participant A as Mobile experience
participant S as Safety service
participant N as Notification service
actor C as Control Room
E->>A: Press Emergency
A-->>E: Explain what will happen
E->>A: Confirm
A->>S: raise_sos(trip, location)
S->>S: Create critical incident
S->>S: Write trip event and audit record
S->>N: notify_sos(incident)
N->>C: Critical alert, bypasses quiet hours
C->>S: Acknowledge
S-->>E: Status: acknowledged
C->>C: Call traveller, driver, emergency contacts
C->>S: Add investigation notes
C->>S: Resolve with a resolution
S-->>E: Status: resolved
Note over S: A resolution is required.<br/>Nothing closes silently.
sequenceDiagram
autonumber
actor E as Employee or Driver
participant A as Mobile experience
participant S as Safety service
participant N as Notification service
actor C as Control Room
E->>A: Press Emergency
A-->>E: Explain what will happen
E->>A: Confirm
A->>S: raise_sos(trip, location)
S->>S: Create critical incident
S->>S: Write trip event and audit record
S->>N: notify_sos(incident)
N->>C: Critical alert, bypasses quiet hours
C->>S: Acknowledge
S-->>E: Status: acknowledged
C->>C: Call traveller, driver, emergency contacts
C->>S: Add investigation notes
C->>S: Resolve with a resolution
S-->>E: Status: resolved
Note over S: A resolution is required.<br/>Nothing closes silently.
Commercial
From a completed trip to money leaving the building.
Billing and reconciliation cycle
The gate before approval is the point of the whole flow: while any line is still disputed, the invoice cannot be approved.
flowchart TD
PERIOD(["Billing period ends"]) --> GEN["Finance generates the invoice<br/>for one vendor and period"]
GEN --> ELIG["Select completed, billable trips<br/>not already invoiced<br/><i>BR-04</i>"]
ELIG --> PRICE["Price each trip from the<br/>most specific active rate card"]
PRICE --> DRAFT["Draft invoice"]
DRAFT --> CLAIM["Vendor submits their claim<br/>per line"]
CLAIM --> COMPARE{"Claim matches the<br/>system figure?"}
COMPARE -->|"Yes"| CLEAN["Line agreed"]
COMPARE -->|"No"| DISPUTE["Line disputed<br/>Goes to the reconciliation queue"]
DISPUTE --> RESOLVE{"Which figure<br/>stands?"}
RESOLVE -->|"System"| CLEAN
RESOLVE -->|"Vendor"| ADJ["Adjustment recorded<br/>with a reason and an actor"]
ADJ --> CLEAN
CLEAN --> ALLCLEAR{"Any line still<br/>disputed?"}
ALLCLEAR -->|"Yes"| DISPUTE
ALLCLEAR -->|"No"| APPROVE["Approve"]
APPROVE --> EXPORT["Export to finance or ERP"]
EXPORT --> PAY["Record payment"]
PAY --> PAID(["Paid"])
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class DISPUTE bad
class APPROVE,EXPORT,PAID good
flowchart TD
PERIOD(["Billing period ends"]) --> GEN["Finance generates the invoice<br/>for one vendor and period"]
GEN --> ELIG["Select completed, billable trips<br/>not already invoiced<br/><i>BR-04</i>"]
ELIG --> PRICE["Price each trip from the<br/>most specific active rate card"]
PRICE --> DRAFT["Draft invoice"]
DRAFT --> CLAIM["Vendor submits their claim<br/>per line"]
CLAIM --> COMPARE{"Claim matches the<br/>system figure?"}
COMPARE -->|"Yes"| CLEAN["Line agreed"]
COMPARE -->|"No"| DISPUTE["Line disputed<br/>Goes to the reconciliation queue"]
DISPUTE --> RESOLVE{"Which figure<br/>stands?"}
RESOLVE -->|"System"| CLEAN
RESOLVE -->|"Vendor"| ADJ["Adjustment recorded<br/>with a reason and an actor"]
ADJ --> CLEAN
CLEAN --> ALLCLEAR{"Any line still<br/>disputed?"}
ALLCLEAR -->|"Yes"| DISPUTE
ALLCLEAR -->|"No"| APPROVE["Approve"]
APPROVE --> EXPORT["Export to finance or ERP"]
EXPORT --> PAY["Record payment"]
PAY --> PAID(["Paid"])
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class DISPUTE bad
class APPROVE,EXPORT,PAID good
Notification dispatch
Critical safety messages bypass quiet hours; everything else respects them. An unconfigured channel is recorded as failed rather than quietly pretending to have sent.
flowchart TD
EVENT(["Domain event<br/>cab assigned, en route,<br/>arrived, delayed, SOS"]) --> TPL{"Tenant template<br/>for this event<br/>and channel?"}
TPL -->|"Yes"| RENDER["Render the tenant's copy"]
TPL -->|"No"| DEFAULT["Render the built-in copy"]
RENDER --> CRIT{"Marked critical?"}
DEFAULT --> CRIT
CRIT -->|"Yes"| SEND["Deliver now<br/>quiet hours bypassed"]
CRIT -->|"No"| QUIET{"Inside quiet<br/>hours?"}
QUIET -->|"Yes"| SUPPRESS["Suppressed<br/>recorded in the delivery log"]
QUIET -->|"No"| SEND
SEND --> ADAPTER{"Channel adapter<br/>configured?"}
ADAPTER -->|"In-app"| STORED["Stored and shown<br/>in the recipient's inbox"]
ADAPTER -->|"SMS, email, push,<br/>WhatsApp"| PROVIDER["Provider adapter"]
ADAPTER -->|"Not configured"| FAILED["Recorded as failed<br/>never reported as sent"]
PROVIDER --> SENTOK["Sent"]
STORED --> SENTOK
SENTOK --> LOG[("Delivery log")]
FAILED --> LOG
SUPPRESS --> LOG
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class FAILED bad
class SENTOK,STORED good
flowchart TD
EVENT(["Domain event<br/>cab assigned, en route,<br/>arrived, delayed, SOS"]) --> TPL{"Tenant template<br/>for this event<br/>and channel?"}
TPL -->|"Yes"| RENDER["Render the tenant's copy"]
TPL -->|"No"| DEFAULT["Render the built-in copy"]
RENDER --> CRIT{"Marked critical?"}
DEFAULT --> CRIT
CRIT -->|"Yes"| SEND["Deliver now<br/>quiet hours bypassed"]
CRIT -->|"No"| QUIET{"Inside quiet<br/>hours?"}
QUIET -->|"Yes"| SUPPRESS["Suppressed<br/>recorded in the delivery log"]
QUIET -->|"No"| SEND
SEND --> ADAPTER{"Channel adapter<br/>configured?"}
ADAPTER -->|"In-app"| STORED["Stored and shown<br/>in the recipient's inbox"]
ADAPTER -->|"SMS, email, push,<br/>WhatsApp"| PROVIDER["Provider adapter"]
ADAPTER -->|"Not configured"| FAILED["Recorded as failed<br/>never reported as sent"]
PROVIDER --> SENTOK["Sent"]
STORED --> SENTOK
SENTOK --> LOG[("Delivery log")]
FAILED --> LOG
SUPPRESS --> LOG
classDef bad fill:#fdeceb,stroke:#f6c9c5,color:#101828
classDef good fill:#e8f6ee,stroke:#b6e2c9,color:#101828
class FAILED bad
class SENTOK,STORED good
Code structure
How a request travels through one Django app.
Request path inside a module
Every app follows this shape. Business rules live in services.py, read queries in selectors.py, and views only orchestrate — so a rule is written once and cannot be bypassed by reaching a different screen.
flowchart LR
URL["urls.py<br/>routing"] --> VIEW["views.py<br/>orchestration only"]
VIEW --> FORM["forms.py<br/>validation"]
VIEW --> SVC["services.py<br/>business rules"]
VIEW --> SEL["selectors.py<br/>read queries"]
FORM --> SVC
SVC --> MODEL["models.py<br/>data & constraints"]
SEL --> MODEL
SVC --> AUD["core/services.py<br/>record_audit"]
SVC --> NOT["notifications<br/>services.py"]
MODEL --> DB[("PostgreSQL")]
VIEW --> TPL["templates<br/>presentation"]
flowchart LR
URL["urls.py<br/>routing"] --> VIEW["views.py<br/>orchestration only"]
VIEW --> FORM["forms.py<br/>validation"]
VIEW --> SVC["services.py<br/>business rules"]
VIEW --> SEL["selectors.py<br/>read queries"]
FORM --> SVC
SVC --> MODEL["models.py<br/>data & constraints"]
SEL --> MODEL
SVC --> AUD["core/services.py<br/>record_audit"]
SVC --> NOT["notifications<br/>services.py"]
MODEL --> DB[("PostgreSQL")]
VIEW --> TPL["templates<br/>presentation"]
Now see it running
Diagrams explain the shape. Signing in as a role and following its guided tour shows you the screens.