Skip to content

Transit Time Profiles Architecture

This document describes the **configurable transit time architecture** for iDrv5.

This document describes the configurable transit time architecture for iDrv5. The design follows the platform’s core philosophy of making all features configuration-driven and adaptable to different client requirements.

The original transit time implementation had these limitations:

IssueImpact
Tightly coupled to rate cardsSame transit times duplicated across rate cards
Service levels stored as rows3 rows for each zone pair (Express, Standard, Economy)
No customization optionsAll rate cards use the same transit times
No opt-out capabilityEvery rate card had to have transit times

A Transit Time Profile is a named, reusable collection of transit times that can be:

  • Shared across multiple rate cards
  • Customized per customer/contract
  • Disabled entirely for specific rate cards
┌─────────────────────────────────────────────────────────────────────────┐
│ TRANSIT TIME PROFILES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Standard AU Network │ │ Express Metro Only │ │
│ │ (DEFAULT) │ │ │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TRANSIT TIME ENTRIES │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ SYD → MEL : 24 hours base │ │ │
│ │ │ MEL → BNE : 48 hours base │ │ │
│ │ │ SYD → BNE : 36 hours base │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ SERVICE LEVEL MULTIPLIERS │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Express : 0.50x (50% = faster) │ │ │
│ │ │ Standard : 1.00x (100% = base time) │ │ │
│ │ │ Economy : 1.50x (150% = slower) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

Each rate card can configure how it handles transit times:

ModeDescriptionUse Case
inheritUse the default global profileMost rate cards (default)
profileUse a specific transit time profileCustomer-specific transit commitments
customUse rate card-specific overridesOne-off customer requirements
noneNo transit times (quote only)Price quotes without ETA
┌─────────────────────────────────────────────────────────────────────────┐
│ RATE CARDS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Standard Card │ │ Premium Client │ │ Quote Only Card │ │
│ │ mode: inherit │ │ mode: profile │ │ mode: none │ │
│ │ │ │ profile_id: 2 │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Default Profile Express Metro No Transit Times │
│ Profile │
│ │
│ ┌─────────────────┐ │
│ │ Custom Client │ │
│ │ mode: custom │──────► Rate Card Transit Overrides │
│ └─────────────────┘ (SYD→MEL: 18 hours for this client) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
CREATE TABLE transit_time_profiles (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
is_default BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER REFERENCES users(id)
);
CREATE TABLE transit_time_entries (
id SERIAL PRIMARY KEY,
profile_id INTEGER NOT NULL REFERENCES transit_time_profiles(id),
origin_zone_id INTEGER REFERENCES zones(id),
destination_zone_id INTEGER REFERENCES zones(id),
base_transit_hours INTEGER NOT NULL, -- Before service level adjustment
distance_km DECIMAL(10, 2),
notes TEXT,
UNIQUE (profile_id, origin_zone_id, destination_zone_id)
);
CREATE TABLE service_level_transit_multipliers (
id SERIAL PRIMARY KEY,
profile_id INTEGER NOT NULL REFERENCES transit_time_profiles(id),
service_level_id INTEGER NOT NULL REFERENCES service_levels(id),
transit_multiplier DECIMAL(4, 2) NOT NULL DEFAULT 1.00,
adjustment_hours INTEGER DEFAULT 0, -- Fixed adjustment after multiplier
display_priority INTEGER DEFAULT 0,
UNIQUE (profile_id, service_level_id)
);
CREATE TABLE rate_card_transit_overrides (
id SERIAL PRIMARY KEY,
rate_card_id INTEGER NOT NULL REFERENCES rate_cards(id),
origin_zone_id INTEGER REFERENCES zones(id),
destination_zone_id INTEGER REFERENCES zones(id),
service_level_id INTEGER REFERENCES service_levels(id),
custom_transit_hours INTEGER NOT NULL,
price_adjustment_percent DECIMAL(5, 2),
notes TEXT,
UNIQUE (rate_card_id, origin_zone_id, destination_zone_id, service_level_id)
);
ALTER TABLE rate_cards
ADD COLUMN transit_time_mode VARCHAR(20) DEFAULT 'inherit'
CHECK (transit_time_mode IN ('inherit', 'profile', 'custom', 'none'));
ALTER TABLE rate_cards
ADD COLUMN transit_time_profile_id INTEGER REFERENCES transit_time_profiles(id);
MethodEndpointDescription
GET/api/transit-profiles/List all profiles
POST/api/transit-profiles/Create new profile
GET/api/transit-profiles/{id}Get profile with entries
PUT/api/transit-profiles/{id}Update profile
DELETE/api/transit-profiles/{id}Delete profile
POST/api/transit-profiles/{id}/copyCopy profile
GET/api/transit-profiles/defaultGet default profile
MethodEndpointDescription
GET/api/transit-profiles/{id}/entriesList entries for profile
POST/api/transit-profiles/{id}/entriesAdd entry
PUT/api/transit-profiles/{id}/entries/{entry_id}Update entry
DELETE/api/transit-profiles/{id}/entries/{entry_id}Delete entry
POST/api/transit-profiles/{id}/entries/bulkBulk import entries
MethodEndpointDescription
GET/api/transit-profiles/{id}/multipliersList multipliers
POST/api/transit-profiles/{id}/multipliersAdd/update multiplier
DELETE/api/transit-profiles/{id}/multipliers/{service_level_id}Delete multiplier
MethodEndpointDescription
GET/api/transit-profiles/rate-card/{id}/transit-timesGet all transit times for rate card
GET/api/transit-profiles/rate-card/{id}/transit-timeGet single transit time
GET/api/transit-profiles/rate-card/{id}/overridesList custom overrides
POST/api/transit-profiles/rate-card/{id}/overridesAdd override
DELETE/api/transit-profiles/rate-card/{id}/overrides/{override_id}Delete override
MethodEndpointDescription
POST/api/transit-profiles/calculateCalculate transit time
// POST /api/transit-profiles/
{
"name": "Premium Metro Network",
"description": "Faster transit times for premium customers in metro areas",
"is_default": false
}
// POST /api/transit-profiles/1/entries
{
"origin_zone_id": 1, // Sydney
"destination_zone_id": 2, // Melbourne
"base_transit_hours": 20, // Base time (before service level adjustment)
"distance_km": 878
}
// POST /api/transit-profiles/1/multipliers
{
"service_level_id": 1, // Express
"transit_multiplier": 0.5, // 50% of base time
"adjustment_hours": 0,
"display_priority": 1
}

The frontend provides three input modes for entering multipliers, making it easier to configure transit times:

ModeUser EntersSystem Calculates
Multiplier0.5Stores directly as transit_multiplier
Hours12 (with base=24)12 / 24 = 0.5 multiplier
Days0.5 (with base=24)(0.5 × 24) / 24 = 0.5 multiplier

Example: Sydney → Melbourne with 24-hour base time

Base Hours Reference: 24 hours (Standard service)
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ Service │ Multiplier │ Hours Mode │ Days Mode │
│ Level │ Mode │ │ │
├─────────────┼─────────────┼─────────────┼─────────────┤
│ Express │ 0.50x │ 12 hrs │ 0.5 days │
│ Standard │ 1.00x │ 24 hrs │ 1.0 days │
│ Economy │ 1.50x │ 36 hrs │ 1.5 days │
└─────────────┴─────────────┴─────────────┴─────────────┘
All modes save the same multiplier value to the database.

Note: The API always receives and stores the transit_multiplier value. The Hours/Days input modes are a frontend convenience feature that auto-calculates the multiplier before saving.

// PUT /api/rate-cards/123
{
"transit_time_mode": "profile",
"transit_time_profile_id": 1
}
// GET /api/transit-profiles/rate-card/123/transit-time?origin_zone_id=1&destination_zone_id=2&service_level_id=1
// Response:
{
"success": true,
"data": {
"transit_hours": 10, // 20 base × 0.5 Express multiplier
"transit_days": 1,
"source": "profile"
}
}
calculated_hours = (base_transit_hours × transit_multiplier) + adjustment_hours
calculated_days = CEIL(calculated_hours / 24)

For SYD → MEL with Express service:

  • Base transit: 24 hours
  • Express multiplier: 0.5
  • Adjustment: 0 hours
calculated_hours = (24 × 0.5) + 0 = 12 hours
calculated_days = CEIL(12 / 24) = 1 day

When getting transit time for a rate card:

  1. If mode = 'none' → Return null (no transit times)
  2. If mode = 'custom' → Check rate_card_transit_overrides first
  3. If mode = 'profile' → Use linked transit_time_profile
  4. If mode = 'inherit' or fallback → Use default profile
Terminal window
psql $DATABASE_URL -f migrations/create_transit_time_profiles.sql

The migration automatically creates a “Standard Australian Network” default profile.

-- Insert existing zone_transit_times into the new structure
INSERT INTO transit_time_entries (profile_id, origin_zone_id, destination_zone_id, base_transit_hours)
SELECT
(SELECT id FROM transit_time_profiles WHERE is_default = TRUE),
origin_zone_id,
destination_zone_id,
transit_time * 24 -- Convert days to hours if needed
FROM zone_transit_times;

Existing rate cards will default to transit_time_mode = 'inherit', which uses the default profile.

BenefitDescription
Reduced RedundancyOne profile serves multiple rate cards
Service Level EfficiencyMultipliers instead of duplicate rows
Customer FlexibilityCustom profiles or overrides per contract
Optional FeatureRate cards can opt out of transit times
Easy MaintenanceUpdate one profile, affects all linked rate cards
ScalabilityAdd new service levels without updating every zone pair
Flexible InputEnter multipliers as ratios, hours, or days - system auto-converts
Rate Card Transit Time Configuration:
inherit:
description: "Use the system default transit time profile"
use_case: "Most standard rate cards"
setup: "No additional configuration needed"
profile:
description: "Use a specific transit time profile"
use_case: "Customer-specific transit commitments"
setup: "Set transit_time_profile_id to the profile ID"
custom:
description: "Use rate card-specific transit time overrides"
use_case: "One-off customer requirements"
setup: "Add entries to rate_card_transit_overrides table"
none:
description: "Disable transit times for this rate card"
use_case: "Quote-only pricing without delivery estimates"
setup: "No additional configuration needed"
FilePurpose
migrations/create_transit_time_profiles.sqlDatabase migration
models/transit_time_profiles.pySQLAlchemy models
api/transit_time_profiles_api.pyREST API endpoints
models/rate_cards.pyUpdated rate card model

  • Added Input Mode Selector feature for service level multipliers
    • Multiplier mode: Direct decimal input (0.1 - 5.0)
    • Hours mode: Enter actual hours, auto-calculates multiplier
    • Days mode: Enter actual days, auto-calculates multiplier
  • Added Base Hours Reference field for Hours/Days mode calculations
  • Enhanced effect preview to show calculated hours alongside speed badges