cents_per_litre';
period_date: string;
frequency: 'weekly' | 'monthly';
source: 'eia' | 'statistics_canada';
}
interface FuelServiceConfig {
apiKey: string;
baseUrl: string;
cacheTtlMs: number;
fxRateProvider: (from: string, to: string) => Promise<number>;
}
### Step 2: Implement the Pricing Service with Caching and Conversion
```typescript
class JurisdictionalFuelService {
private cache = new Map<string, { data: FuelPriceRecord[]; expires: number }>();
constructor(private config: FuelServiceConfig) {}
async fetchLatestPrices(params: {
country?: 'US' | 'CA';
fuel_type?: FuelPriceRecord['fuel_type'];
limit?: number;
}): Promise<FuelPriceRecord[]> {
const cacheKey = JSON.stringify(params);
const cached = this.cache.get(cacheKey);
if (cached && Date.now() < cached.expires) return cached.data;
const query = new URLSearchParams();
if (params.country) query.set('country', params.country);
if (params.fuel_type) query.set('fuel_type', params.fuel_type);
if (params.limit) query.set('limit', String(params.limit));
query.set('latest', 'true');
const response = await fetch(
`${this.config.baseUrl}/api/v1/fuel-prices?${query.toString()}`,
{ headers: { 'X-API-Key': this.config.apiKey } }
);
if (!response.ok) throw new Error(`Fuel API failed: ${response.status}`);
const payload = await response.json();
const records: FuelPriceRecord[] = payload.data;
this.cache.set(cacheKey, {
data: records,
expires: Date.now() + this.config.cacheTtlMs,
});
return records;
}
normalizeToUsdPerGallon(record: FuelPriceRecord): number {
if (record.country === 'US' && record.unit === 'gallon') {
return record.price;
}
if (record.country === 'CA' && record.unit === 'cents_per_litre') {
const cadToUsd = 0.74; // Placeholder; replace with live FX call
const centsToDollars = 0.01;
const litresToGallons = 3.78541;
return (record.price * centsToDollars * litresToGallons) / cadToUsd;
}
throw new Error('Unsupported unit/currency combination');
}
}
Step 3: Correlate with Corridor Routing
The true engineering value emerges when fuel pricing intersects with physical route constraints. A typical workflow chains three operations:
- Corridor Resolution: Query
/truck/corridor to retrieve bridge clearances, weight limits, and restricted segments for a planned origin-destination pair.
- Jurisdiction Mapping: Extract the list of states/provinces the corridor traverses.
- Price Injection: Fetch the latest diesel rates for those jurisdictions and calculate segment-level fuel costs based on vehicle MPG and distance.
async function estimateRouteFuelCost(
corridor: string[],
mpg: number,
fuelService: JurisdictionalFuelService
): Promise<number> {
const usStates = corridor.filter((s) => s.length === 2 && s !== 'CA');
const prices = await fuelService.fetchLatestPrices({
country: 'US',
fuel_type: 'diesel',
});
const priceMap = new Map<string, number>();
prices.forEach((p) => priceMap.set(p.jurisdiction, p.price));
let totalCost = 0;
for (const state of usStates) {
const segmentMiles = getSegmentDistance(state); // External routing metric
const gallonsNeeded = segmentMiles / mpg;
const pricePerGallon = priceMap.get(state) ?? 4.50; // Fallback
totalCost += gallonsNeeded * pricePerGallon;
}
return totalCost;
}
Architecture Decisions and Rationale
- Cache-First Strategy: Government sources update on fixed schedules (EIA on Mondays, Statistics Canada monthly). Aggressive caching prevents unnecessary API calls and reduces latency. TTL should align with the slowest update cycle.
- Unit Normalization at the Edge: Converting CAD cents/litre to USD/gallon at ingestion time prevents calculation drift downstream. Never pass raw mixed units to routing algorithms.
- Fallback Pricing: Retail data occasionally experiences reporting gaps. Implementing a rolling 30-day average or regional baseline prevents route estimation failures during data outages.
- Separation of Concerns: Fuel pricing should never live inside the core routing engine. Treat it as a pluggable cost module that can be swapped for fleet card rates, contract pricing, or regional surcharges.
Pitfall Guide
1. Ignoring Unit and Currency Divergence
Explanation: US prices arrive in USD per gallon, while Canadian data uses CAD cents per litre. Direct arithmetic on mixed units produces mathematically invalid cost estimates.
Fix: Implement a strict normalization layer that converts all inputs to a single base unit before routing calculations. Validate units at the type level to prevent runtime mismatches.
2. Assuming Synchronous Update Cycles
Explanation: EIA publishes weekly on Mondays, while Statistics Canada updates monthly. Treating both as daily-refresh data causes stale Canadian prices or unnecessary US API calls.
Fix: Configure separate cache TTLs per jurisdiction. Invalidate US caches on Monday mornings and CA caches on the first business day of each month.
3. Over-Fetching Historical Ranges
Explanation: Requesting multi-year historical data for every route calculation triggers rate limits and inflates payload size. Historical trends belong in analytics pipelines, not real-time routing.
Fix: Reserve historical endpoints for batch reporting jobs. Use latest=true for operational routing, and archive historical snapshots to object storage for trend analysis.
4. Hardcoding Fuel Grade Mappings
Explanation: Different jurisdictions report varying grade classifications. Assuming regular always maps to 87 octane or diesel to on-road ultra-low sulfur causes pricing mismatches.
Fix: Map fuel types to operational vehicle requirements rather than octane ratings. Maintain a configuration table that aligns API fuel_type values with fleet equipment specs.
5. Neglecting Jurisdiction Boundary Logic
Explanation: Routes rarely stay within a single state. Calculating fuel cost using only the origin or destination state ignores price volatility along the corridor.
Fix: Extract jurisdiction segments from the corridor response and weight fuel costs by distance traveled in each state. Use routing geometry to calculate precise state-entry and state-exit mile markers.
6. Skipping Cache Invalidation Strategies
Explanation: Stale fuel prices compound over time. A 5% price increase that isn't reflected in the cache can cause systematic underpricing of freight quotes.
Fix: Implement time-based expiration combined with manual invalidation triggers. Monitor API response headers for Last-Modified or ETag values to detect upstream changes.
7. Treating Retail Prices as Fleet Pump Rates
Explanation: Government retail data includes state taxes and convenience margins. Fleet operators typically pay discounted contract rates or use fuel cards with network pricing.
Fix: Apply a fleet discount multiplier or integrate a separate fuel card pricing API. Use retail data for baseline modeling, but override with actual procurement costs for final invoicing.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Domestic US-only routing | Fetch weekly EIA data, cache 6 days | Aligns with Monday update cycle, minimizes API calls | Low (predictable caching) |
| Cross-border US/CA corridors | Normalize CAD cents/L to USD/gal, cache CA monthly | Prevents unit drift, respects slower Canadian cadence | Medium (FX conversion overhead) |
| Historical trend analytics | Use from/to date ranges, export to data warehouse | Keeps routing engine lightweight, enables BI modeling | Low (batch processing) |
| Real-time freight quoting | Inject latest prices into corridor cost module | Ensures rate accuracy reflects current market conditions | High (requires low-latency caching) |
| Fleet contract pricing | Overlay retail data with fuel card discounts | Retail data provides baseline; contracts drive actual spend | Medium (requires discount mapping) |
Configuration Template
// fuel-service.config.ts
export const fuelServiceConfig = {
apiKey: process.env.ROAD511_API_KEY || '',
baseUrl: 'https://api.road511.com',
cache: {
usTtlMs: 6 * 24 * 60 * 60 * 1000, // 6 days (EIA updates Mondays)
caTtlMs: 30 * 24 * 60 * 60 * 1000, // 30 days (StatsCan monthly)
fallbackPricePerGallon: 4.45,
},
conversion: {
baseCurrency: 'USD',
baseUnit: 'gallon',
fxRateSource: 'https://api.exchangerate.host/latest',
cadToUsdBaseline: 0.74,
litresToGallons: 3.78541,
},
routing: {
defaultMpg: 6.5,
segmentWeighting: true,
jurisdictionExtraction: 'corridor_geometry',
},
};
Quick Start Guide
- Provision API Access: Register for a developer key and verify endpoint availability for
/api/v1/fuel-prices.
- Initialize the Service: Import the configuration template and instantiate
JurisdictionalFuelService with your API key and cache TTLs.
- Fetch Latest Prices: Call
fetchLatestPrices({ country: 'US', fuel_type: 'diesel' }) and validate the response structure against the type contract.
- Normalize and Cache: Run the conversion utility on Canadian records, store results in the in-memory or Redis cache, and verify TTL alignment with update schedules.
- Inject into Routing: Map corridor state codes to cached price records, calculate segment fuel costs using vehicle MPG, and append the total to your route estimation payload.
Integrating jurisdictional fuel pricing transforms route planning from a geometric exercise into a financially aware operation. By treating fuel data as a first-class routing parameter, engineering teams can eliminate estimation drift, align freight quotes with market reality, and build logistics platforms that price corridors with institutional precision.