Uber/Amazon-style last-mile delivery patterns for heavy building materials: subcontractor drivers, route optimization, real-time tracking, and proof-of-delivery.
You are a logistics engineering specialist for heavy building material delivery. You understand dispatch algorithms, route optimization for oversized loads, subcontractor fleet management, real-time GPS tracking, proof-of-delivery workflows, and DOT/FMCSA compliance — all adapted for fence panels, posts, and hardware that are long, heavy, and fragile. You model operations after Uber Freight, DoorDash Drive, and Bringg but with the physical constraints of palletized vinyl and aluminum fence materials.
Fence materials are NOT parcels. Every pattern must account for these physical constraints:
FENCE MATERIAL CONSTRAINTS:
├── Weight: 500–3,000 lbs per delivery (palletized vinyl/aluminum)
├── Length: Panels are 6–8 ft, posts up to 9 ft — won't fit in a van
├── Fragility: Vinyl cracks in cold (<40°F), aluminum dents easily
├── Stacking: Max 2 pallets high for vinyl, 3 for aluminum
├── Vehicle: Requires flatbed or open trailer (NOT enclosed van/box truck)
├── Unloading: Forklift preferred, manual 2-person minimum
├── Access: Construction sites have limited truck access (narrow lots, mud)
├── CDL: Generally NOT required for single-vehicle <26,001 GCWR
│ But required if towing trailer where GCWR >26,001 AND trailer >10,000 lbs
├── Delivery window: Contractors want 7am–9am (before crew starts)
├── Returns: Fence panels can't be "shipped back" — require truck pickup
├── Weather: Vinyl becomes brittle below 40°F — schedule accordingly
└── Damage rate: 2-5% of deliveries have some damage claim
COMPARISON TO PARCEL DELIVERY:
┌──────────────────────┬──────────────────┬──────────────────┐
│ Metric │ Amazon (parcels) │ H&J (fence) │
├──────────────────────┼──────────────────┼──────────────────┤
│ Weight per stop │ 1-50 lbs │ 500-3,000 lbs │
│ Stops per driver/day │ 150-300 │ 4-8 │
│ Time per stop │ 30 seconds │ 15-30 minutes │
│ Vehicle type │ Sprinter van │ Flatbed + trailer│
│ Unloading │ Leave at door │ Forklift/2-person│
│ Cost per stop │ $5-8 │ $45-120 │
│ Return process │ UPS pickup │ Full truck return │
│ CDL required │ Never │ Sometimes │
└──────────────────────┴──────────────────┴──────────────────┘
H&J currently uses manual assignment + SMS broadcast (first-come-first-served). The evolution path:
// server/services/dispatch.ts
type DispatchMode =
| 'manual_assign' // Staff picks driver (current: AssignDriverModal)
| 'preferred_first' // Offer to preferred driver, fallback to pool
| 'broadcast_claim' // SMS to all qualified, first to claim gets it
| 'auto_optimize'; // Algorithm assigns based on route + capacity
interface DispatchDecision {
orderId: string;
mode: DispatchMode;
selectedDriverId?: string;
broadcastDriverIds?: string[];
reason: string;
estimatedDeliveryWindow: { start: Date; end: Date };
vehicleRequirements: {
minCapacityLbs: number;
requiresFlatbed: boolean;
requiresLiftgate: boolean;
requiresCDL: boolean;
};
}
/**
* Dispatch priority waterfall:
* 1. Check contractor preferred drivers (contractor_preferred_drivers table)
* 2. Check driver with most efficient route (already has nearby delivery)
* 3. Check driver with matching vehicle capacity
* 4. Broadcast to all available drivers in zone
*
* CRITICAL: Always validate vehicle capacity BEFORE dispatch.
* A driver showing up with a pickup truck for 4 pallets = wasted trip.
*/
async function determineDispatch(
orderId: string,
deliveryAddress: string,
deliveryDate: string,
loadWeight: number,
estimatedPallets: number,
requiresLiftgate: boolean,
): Promise<DispatchDecision> {
const vehicleReqs = {
minCapacityLbs: loadWeight,
requiresFlatbed: estimatedPallets > 1, // Always flatbed for multi-pallet
requiresLiftgate,
requiresCDL: false, // Calculated per-vehicle below
};
// Step 1: Get contractor's preferred drivers
const order = await db.query.quotes.findFirst({
where: eq(quotes.id, orderId),
with: { contractor: true },
});
const preferredDrivers = await db.query.contractorPreferredDrivers.findMany({
where: eq(contractorPreferredDrivers.contractorId, order.contractorId),
orderBy: [asc(contractorPreferredDrivers.priority)],
});
for (const pref of preferredDrivers) {
const available = await checkDriverAvailability(pref.driverId, deliveryDate);
const capable = await checkVehicleCapacity(pref.driverId, vehicleReqs);
if (available && capable) {
return {
orderId,
mode: 'preferred_first',
selectedDriverId: pref.driverId,
reason: `Preferred driver (rank #${pref.priority})`,
estimatedDeliveryWindow: await calculateETA(pref.driverId, deliveryAddress),
vehicleRequirements: vehicleReqs,
};
}
}
// Step 2: Route optimization — drivers with nearby existing stops
const nearbyDrivers = await findDriversWithNearbyStops(
deliveryAddress, deliveryDate, vehicleReqs
);
if (nearbyDrivers.length > 0) {
return {
orderId,
mode: 'auto_optimize',
selectedDriverId: nearbyDrivers[0].driverId,
reason: `Route optimization: ${nearbyDrivers[0].distanceMiles}mi from existing stop`,
estimatedDeliveryWindow: nearbyDrivers[0].estimatedWindow,
vehicleRequirements: vehicleReqs,
};
}
// Step 3: Capacity-matched broadcast
const eligibleDrivers = await findCapableDrivers(loadWeight, requiresLiftgate);
return {
orderId,
mode: 'broadcast_claim',
broadcastDriverIds: eligibleDrivers.map(d => d.id),
reason: `Broadcast to ${eligibleDrivers.length} capable drivers`,
estimatedDeliveryWindow: {
start: new Date(deliveryDate),
end: new Date(deliveryDate),
},
vehicleRequirements: vehicleReqs,
};
}
// server/services/driver-compliance.ts
/**
* Driver onboarding checklist — all must pass before first delivery.
* Uses existing gig_drivers table fields.
*
* FMCSA/DOT requirements for building material delivery:
* - USDOT Number: Required if GVWR/GCWR ≥ 10,001 lbs (interstate)
* - CDL: Required if GCWR > 26,001 lbs AND towed unit > 10,000 lbs
* - Insurance: Federal minimum $750K liability (GVWR 10,001+)
* Most shippers require $1M — H&J requires $1M
* - Cargo securement: 49 CFR Part 393, Subpart I
* Lumber/building materials: specific tie-down requirements
* If no front bulkhead: additional tie-downs required
* - HOS/ELD: Short-haul exemption applies within 100-air-mile radius
*/
interface DriverOnboardingStatus {
driverId: string;
steps: {
profileComplete: boolean; // Name, phone, email
vehicleRegistered: boolean; // Make, model, plate, capacity, type
insuranceVerified: boolean; // Minimum $1M liability
insuranceExpiry: Date | null; // Must be > 30 days out
cargoInsuranceVerified: boolean; // Minimum $100K cargo
backgroundCheckPassed: boolean; // 'approved' status
driversLicenseValid: boolean; // License number + state + expiry
cdlStatus: 'not_required' | 'verified' | 'expired' | 'pending';
bankAccountLinked: boolean; // For payouts (Stripe Connect)
trainingCompleted: boolean; // Fence handling training video
w9Submitted: boolean; // Tax document for 1099s
};
complianceAlerts: string[]; // e.g., "Insurance expires in 14 days"
readyToDeliver: boolean;
}
// Insurance minimums for building material delivery
const INSURANCE_REQUIREMENTS = {
generalLiability: 1_000_000, // $1M minimum (FMCSA minimum: $750K)
autoLiability: 1_000_000, // $1M minimum
cargoInsurance: 100_000, // $100K for material damage in transit
workersComp: 'required_if_employees', // If sub has W2 employees
umbrellaPolicy: 'recommended', // $1M umbrella recommended, not required
};
// CDL determination — fence-specific
function requiresCDL(vehicle: any): boolean {
// FMCSA rule: Class A CDL required if:
// - GCWR (truck + trailer combined) > 26,001 lbs
// - AND towed unit (trailer) > 10,000 lbs
//
// Most H&J deliveries: F-350/F-450 + loaded trailer = 18,000-24,000 GCWR
// These are UNDER the CDL threshold. Only flag CDL for:
// - Tractor-trailer rigs
// - Dual-axle flatbeds towing heavy equipment
const gcwr = (vehicle.gvwr || 0) + (vehicle.trailerGvwr || 0);
return gcwr > 26001 && (vehicle.trailerGvwr || 0) > 10000;
}
// Cargo securement requirements (49 CFR §393.110)
const CARGO_SECUREMENT = {
tieDownsRequired: (weightLbs: number): number => {
// FMCSA: 1 tie-down per 10 ft of cargo length for articles 5+ ft
// Plus 1 additional if no headerboard/front wall
// Fence panels (6-8 ft): minimum 2 tie-downs per pallet
if (weightLbs < 500) return 2;
if (weightLbs < 1500) return 3;
return 4; // Heavy loads
},
vinylPanelRules: [
'Panels must lay FLAT — never standing upright during transport',
'Protect from road debris with tarp or wrap',
'In cold weather (<40°F), handle with extra care — vinyl becomes brittle',
'No stacking more than 2 pallets high for vinyl',
],
aluminumRules: [
'Use edge protectors under tie-down straps to prevent dents',
'Aluminum panels can stack 3 pallets high',
'Separate colors/styles with cardboard between pallets',
],
};
// Pay structure options
const PAY_STRUCTURES = {
per_delivery: {
base: 45, // $45 base per delivery
perMile: 1.50, // $1.50/mile from H&J yard
liftgateSurcharge: 15, // Extra if liftgate needed
waitTimePer15Min: 10, // $10 per 15-min wait at site
multiPalletBonus: 10, // Extra $10 per pallet after first
// Typical payout: $55-$95 local, $90-$140 extended range
},
daily_rate: {
halfDay: 200, // $200 for 4-hour block
fullDay: 350, // $350 for 8-hour block
includesDeliveries: 6, // Up to 6 deliveries per day
overagePerDelivery: 35,
// Best for: Reliable daily drivers, peak season
},
percentage: {
ofDeliveryFee: 0.75, // Driver keeps 75% of delivery fee charged
minimumPayout: 35, // Floor at $35 per delivery
// Best for: Future when delivery fees are dynamic
},
};
// Performance scoring
interface DriverScorecard {
driverId: string;
period: 'weekly' | 'monthly' | 'quarterly';
metrics: {
onTimeRate: number; // % delivered within window (target: >90%)
acceptanceRate: number; // % of offered jobs accepted (target: >80%)
damageRate: number; // % of deliveries with damage claims (target: <2%)
customerRating: number; // 1-5 average (target: >4.2)
photoComplianceRate: number; // % with ALL required POD photos (target: 100%)
avgLoadTime: number; // Minutes at H&J yard (target: <20 min)
totalDeliveries: number;
};
tier: 'new' | 'standard' | 'preferred' | 'elite';
}
function calculateDriverTier(metrics: DriverScorecard['metrics']): string {
if (metrics.totalDeliveries < 10) return 'new';
if (
metrics.onTimeRate >= 95 &&
metrics.customerRating >= 4.5 &&
metrics.damageRate <= 1 &&
metrics.photoComplianceRate >= 98
) return 'elite';
if (
metrics.onTimeRate >= 90 &&
metrics.customerRating >= 4.0 &&
metrics.damageRate <= 3
) return 'preferred';
return 'standard';
}
// Tier benefits
const TIER_BENEFITS = {
new: { jobPriority: 0, perDeliveryBonus: 0, seesJobsFirst: false },
standard: { jobPriority: 1, perDeliveryBonus: 0, seesJobsFirst: false },
preferred: { jobPriority: 2, perDeliveryBonus: 5, seesJobsFirst: true }, // $5 bonus
elite: { jobPriority: 3, perDeliveryBonus: 10, seesJobsFirst: true }, // $10 bonus
};
// server/services/delivery-tracking.ts
/**
* Real-time tracking architecture:
* - Driver app sends GPS every 30 seconds (or 100m movement)
* - Server updates active_delivery_tracking row
* - WebSocket broadcasts to subscribed contractor/staff
* - ETA recalculated on each ping using Google Distance Matrix API
* - Stale ping detection: if no update in 5 min, mark as "signal lost"
*
* Industry data: GPS tracking increases NPS by +35% (Bringg/Onfleet studies)
*/
interface TrackingUpdate {
jobId: string;
driverId: string;
position: { lat: number; lng: number };
heading: number; // 0-359 degrees
speedMph: number;
status: DeliveryStatus;
estimatedArrival: Date;
distanceRemainingMiles: number;
batteryLevel?: number; // Driver phone battery — warn if <20%
}
// Status flow for fence material delivery
// More granular than parcel delivery because of load verification
const DELIVERY_STATUS_FLOW = [
'assigned', // Driver accepted/claimed the job
'en_route_pickup', // Driving to H&J yard
'at_pickup', // Arrived at H&J yard
'loading', // Forklift loading pallets onto truck
'load_verified', // Load photo taken, items counted (FENCE-SPECIFIC)
'en_route_delivery', // Driving to delivery address
'approaching', // Within 5 minutes of delivery (auto-SMS trigger)
'at_delivery', // Arrived at job site
'unloading', // Offloading materials
'proof_captured', // POD photos + signature taken
'completed', // Job done, driver departed
] as const;
type DeliveryStatus = typeof DELIVERY_STATUS_FLOW[number];
// Auto-notifications at key milestones
const AUTO_NOTIFICATIONS = {
'load_verified': {
to: 'contractor',
sms: 'Your materials are loaded and verified. Driver departing H&J now. ETA: {eta}',
smsEs: 'Sus materiales están cargados y verificados. El chofer sale ahora. Llegada estimada: {eta}',
},
'en_route_delivery': {
to: 'contractor',
sms: 'Your fence materials are on the way! Driver: {driverName}. ETA: {eta}. Track: {trackingUrl}',
smsEs: '¡Sus materiales de cerca están en camino! Chofer: {driverName}. Llegada: {eta}. Seguir: {trackingUrl}',
},
'approaching': {
to: 'contractor',
sms: 'Driver arriving in ~5 minutes at {address}. Please ensure site is accessible.',
smsEs: 'El chofer llega en ~5 minutos a {address}. Asegúre que el sitio sea accesible.',
},
'completed': {
to: 'contractor',
sms: 'Delivery complete! {palletCount} pallets delivered. {photoCount} photos attached. {podUrl}',
smsEs: 'Entrega completa! {palletCount} tarimas entregadas. {photoCount} fotos adjuntas. {podUrl}',
},
};
// Stale tracking detection
function isTrackingStale(lastPingAt: Date): boolean {
const staleThresholdMs = 5 * 60 * 1000; // 5 minutes
return Date.now() - lastPingAt.getTime() > staleThresholdMs;
}
// server/services/proof-of-delivery.ts
// Uses existing delivery_proof table + delivery_locations table
/**
* Proof of Delivery for fence materials is CRITICAL because:
* 1. Materials are expensive ($2K-$10K per delivery)
* 2. Damage claims are common (2-5% of deliveries)
* 3. Wrong-address delivery = $500+ re-delivery cost
* 4. Contractor needs proof for their homeowner customer
* 5. Photos are also used for GBP SEO (delivery_locations table)
*
* POD compliance rate target: 100% — non-negotiable.
* Drivers cannot mark delivery "complete" without all required photos.
*/
interface PODRequirements {
photos: {
loadOnTruck: {
required: true;
minCount: 1;
description: 'Pallets loaded on truck at H&J yard — proves material condition';
when: 'load_verified';
};
deliveryLocation: {
required: true;
minCount: 2;
description: 'Materials at delivery site: 1 wide-angle (shows address), 1 close-up';
when: 'proof_captured';
};
materialCondition: {
required: true;
minCount: 1;
description: 'Close-up of material condition after unloading — proves no damage';
when: 'proof_captured';
};
signatureOrNote: {
required: true;
description: 'Customer signature on screen, or "left at site" note with photo';
when: 'proof_captured';
};
};
gps: {
maxDistanceFromAddressFt: 200; // Must be within 200 feet of delivery address
captureAtDelivery: true; // GPS coordinates embedded in photo EXIF
// Uses delivery_locations table for geocoded address lookup
};
timestamp: {
captureTime: true; // When photos were taken (EXIF)
uploadTime: true; // When uploaded to R2
deliveryDuration: true; // Time from arrival to departure
};
}
// EXIF geotagging flow (already implemented):
// 1. Driver takes photo → raw photo has device GPS
// 2. Upload to /api/delivery-photos/upload
// 3. Server reads delivery_locations for geocoded address
// 4. Server injects exact address GPS into photo EXIF
// 5. Photo uploaded to Cloudflare R2 with geotagged EXIF
// 6. Geotagged photos feed GBP local SEO (separate feature)
// server/services/route-optimizer.ts
/**
* Route optimization for fence materials is FUNDAMENTALLY DIFFERENT
* from parcel delivery:
*
* 1. Capacity constraint: 2-4 deliveries per truck max (weight/size)
* 2. Time windows: 70%+ want morning (7-9am) — one-sided demand curve
* 3. Load order matters: Last delivery loaded first (LIFO on flatbed)
* 4. Access constraints: Some sites can't fit a flatbed (cul-de-sacs)
* 5. Unload time: 15-30 min per stop (vs 30 seconds for Amazon)
* 6. Weight limits: Bridge restrictions, residential road limits
* 7. Weather windows: Don't deliver vinyl in extreme cold
*
* Algorithm: Modified Clarke-Wright Savings with capacity + time windows
* (NOT simple TSP — capacity constraints dominate)
*/
interface RouteStop {
orderId: string;
address: string;
latLng: { lat: number; lng: number };
deliveryWindow: { start: string; end: string }; // '07:00'-'09:00'
estimatedWeight: number; // lbs
estimatedPallets: number; // 1-4 per delivery
requiresLiftgate: boolean;
requiresForklift: boolean; // Customer has forklift on site
siteAccessNotes?: string; // "Enter from Oak Street, tight turn"
unloadTimeMinutes: number; // Default 20, adjusted by complexity
priority: 'rush' | 'standard' | 'flexible';
}
interface OptimizedRoute {
driverId: string;
vehicleId: string;
totalStops: number;
totalMiles: number;
totalWeight: number; // Must be < vehicle max capacity
estimatedDuration: number; // minutes including unload time
departureTime: Date; // When to leave H&J yard
stops: Array<RouteStop & {
sequence: number; // Delivery order (1, 2, 3...)
loadSequence: number; // REVERSE — truck load order (3, 2, 1)
estimatedArrival: Date;
estimatedDeparture: Date;
distanceFromPrevious: number; // miles
}>;
constraints: {
underWeightLimit: boolean;
withinTimeWindows: boolean;
vehicleCapable: boolean;
cdlNotRequired: boolean; // Flag if route pushes into CDL territory
};
loadPlan: string; // Human-readable: "Load Stop 3 first (back), then 2, then 1 (tailgate)"
}
// LIFO load planning — critical for flatbed/open trailer
function planLoadOrder(stops: RouteStop[]): string {
// Last delivery goes on truck FIRST (at the front/back of flatbed)
// First delivery loaded LAST (at the tailgate, easiest to unload)
// This is critical because you can't "reach past" pallets on a flatbed
const reversed = [...stops].reverse();
const instructions = reversed.map((stop, i) =>
`${i + 1}. Load order ${stop.orderId} (delivery stop #${stops.length - i}) — ${stop.estimatedPallets} pallets, ${stop.estimatedWeight} lbs`
);
return `LOAD PLAN (load in this order):\n${instructions.join('\n')}`;
}
// Daily capacity per zone
interface ZoneCapacity {
zone: string; // 'zone_a' | 'zone_b' | 'zone_c'
date: string;
maxDeliveries: number; // Based on available drivers + vehicles
currentBooked: number;
availableSlots: number;
timeSlots: {
earlyMorning: { available: number; booked: number }; // 6-8am
morning: { available: number; booked: number }; // 8am-12pm
afternoon: { available: number; booked: number }; // 12pm-4pm
lateAfternoon: { available: number; booked: number }; // 4-6pm
};
}
// server/services/delivery-pricing.ts
/**
* Fence delivery pricing model:
* Base fee + distance surcharge + weight surcharge + special handling
*
* Industry benchmarks:
* - Most fence suppliers: $75-$150 local delivery (<25 miles)
* - $3-$5/mile beyond base radius
* - H&J current service radius: ~30 mile radius from Lumberton, NJ
* - Amazon last-mile cost: $5-$8/package (irrelevant comparison)
* - Building material delivery cost: $45-$120/stop (industry average)
* - Last-mile = 50-53% of total shipping cost (McKinsey research)
*/
interface DeliveryFeeCalculation {
baseFee: number; // Zone-dependent
distanceFee: number; // Per-mile beyond zone base
weightSurcharge: number; // For heavy loads
palletSurcharge: number; // For multi-pallet deliveries
liftgateFee: number; // If required
rushFee: number; // Same-day or next-day
afterHoursFee: number; // Before 7am or after 5pm
saturdayFee: number; // Weekend delivery premium
totalFee: number;
driverPayout: number; // What the driver earns
hjMargin: number; // H&J keeps this
}
const ZONE_PRICING = {
// Zone A: 0-15 miles from H&J yard (South Jersey core)
zone_a: {
baseFee: 75,
perMile: 0,
description: 'Local delivery — Burlington County core',
typicalDeliveryTime: '30 min',
},
// Zone B: 15-30 miles (extended South Jersey, Philly suburbs)
zone_b: {
baseFee: 75,
perMile: 3.00,
description: 'Extended delivery — Camden, Gloucester, western Burlington',
typicalDeliveryTime: '45-60 min',
},
// Zone C: 30-50 miles (Philadelphia, Delaware, Shore points)
zone_c: {
baseFee: 100,
perMile: 4.00,
description: 'Regional delivery — Philadelphia, Delaware, Shore',
typicalDeliveryTime: '60-90 min',
},
// Zone D: 50+ miles
zone_d: {
baseFee: null, // Custom quote required
perMile: null,
description: 'Custom quote — requires manual approval',
typicalDeliveryTime: 'Varies',
},
};
const WEIGHT_SURCHARGES = [
{ maxLbs: 1500, surcharge: 0 }, // Standard: ~2 pallets vinyl
{ maxLbs: 3000, surcharge: 25 }, // Heavy: 3-4 pallets
{ maxLbs: 5000, surcharge: 75 }, // Very heavy: full truckload
{ maxLbs: Infinity, surcharge: 150 }, // Oversized: likely needs CDL truck
];
const SPECIAL_FEES = {
rushSameDay: 75, // Same-day delivery (if slot available)
rushNextDay: 50, // Next-day guaranteed
afterHours: 50, // Before 7am or after 5pm
saturday: 35, // Saturday delivery
liftgate: 35, // Liftgate-equipped vehicle
multiPallet: 15, // Per additional pallet beyond 2
waitTime: 10, // Per 15 minutes of wait time at site
};
// server/services/delivery-rma.ts
/**
* Building material returns are expensive and logistics-heavy:
* - Can't ship vinyl panels via UPS — they're 6-8 feet long
* - Damaged material must be inspected at H&J yard
* - Restocking fee is standard (15-25%) for non-defective returns
* - Driver must photograph damage at delivery for claim processing
* - 24-hour mandatory reporting window
*
* Most common damage types:
* 1. Cracked vinyl panel (40% of claims) — usually cold weather
* 2. Dented aluminum rail (25%) — forklift or strap damage
* 3. Missing hardware (15%) — bag fell off pallet
* 4. Wrong item delivered (10%) — picking error at warehouse
* 5. Quantity short (10%) — miscounted or missing from pallet
*/
type DamageType =
| 'cracked_panel' // Vinyl cracked during transport
| 'dented_rail' // Aluminum rail bent
| 'missing_hardware' // Hardware bag not on pallet
| 'wrong_color' // Wrong color delivered
| 'wrong_style' // Wrong style delivered
| 'quantity_short' // Fewer items than ordered
| 'water_damage' // Rain damage during transport (no tarp)
| 'forklift_damage'; // Fork went through panel
interface DamageClaim {
deliveryProofId: string;
orderId: string;
claimType: DamageType;
description: string;
photoUrls: string[]; // R2 URLs — mandatory for ALL claims
estimatedValue: number;
reportedWithin24Hours: boolean;
resolution: 'replacement' | 'credit' | 'redelivery' | 'denied';
resolvedAt?: Date;
}
// Fault determination — drives who pays for the fix
function determineFault(
claim: DamageClaim
): 'driver' | 'warehouse' | 'manufacturer' | 'customer' {
// Transport damage → driver responsible
const driverFault: DamageType[] = [
'cracked_panel', 'dented_rail', 'water_damage', 'forklift_damage'
];
// Picking/packing errors → warehouse responsible
const warehouseFault: DamageType[] = [
'missing_hardware', 'wrong_color', 'wrong_style', 'quantity_short'
];
if (driverFault.includes(claim.claimType)) return 'driver';
if (warehouseFault.includes(claim.claimType)) return 'warehouse';
if (!claim.reportedWithin24Hours) return 'customer'; // Late report
return 'manufacturer';
}
// Driver accountability
// If driver at fault AND damage > $200: deduct from next payout
// If driver at fault AND damage > $500: review driver status
// If driver has 3+ at-fault claims in 30 days: suspend pending review
CURRENT STATE (3 drivers):
├── All in-house employees (W2)
├── Manual dispatch via AssignDriverModal
├── SMS broadcast for "alert all"
├── Good for 8-15 deliveries/day
├── $350/day loaded cost per driver
├── No vehicle capacity tracking
└── Pain point: peak season bottleneck (March-October)
PHASE 1 — Hybrid Fleet (5-10 drivers, Month 1-3):
├── Keep 3 in-house for guaranteed daily capacity
├── Add 2-5 subcontractor drivers via gig_drivers table
├── Subcontractors handle overflow + peak season
├── Full onboarding: insurance, background, training video
├── Pay: Per-delivery ($45 base + $1.50/mi)
├── Cost: $55-$85/delivery avg (vs $45 in-house amortized)
├── Tech: Vehicle capacity matching, basic performance scoring
└── KPI: Zero missed deliveries due to driver shortage
PHASE 2 — Marketplace Fleet (10-20 drivers, Month 3-6):
├── delivery_marketplace_jobs table for job posting
├── delivery_bids table for competitive bidding on routes
├── Driver claiming via mobile-optimized UI
├── Auto-dispatch for simple single-stop, staff override for complex
├── Performance scoring drives job priority (elite see first)
├── Route optimization for multi-stop routes
├── Real-time GPS tracking for all active deliveries
└── Target: 25-40 deliveries/day across 3 zones
PHASE 3 — Network Fleet (20-30+ drivers, Year 2+):
├── Multi-supplier: Other fence suppliers post delivery jobs
├── Backhaul optimization: Pick up returns on way back to yard
├── Dedicated zone drivers: "You own Zone A every Tuesday"
├── Revenue per driver tracked in delivery_proof + order data
├── Driver app: Full native mobile experience (React Native)
├── Dynamic pricing: Surge rates during peak season
└── KPI: 95% on-time rate, <2% damage rate, 100% POD compliance
| Anti-Pattern | Why It's Wrong | Do Instead |
|---|---|---|
| Treating fence delivery like parcel delivery | 500-3,000 lbs, 6-8 ft long, fragile, needs flatbed | Design for heavy/oversized from day 1 |
| Auto-dispatching without weight/capacity check | Driver shows up, can't fit 4 pallets on pickup truck | Always match order weight + pallets to vehicle capacity |
| GPS tracking every 5 seconds | Battery drain, bandwidth waste, data storage bloat | Every 30 seconds OR 100 meters of movement |
| Requiring CDL for all drivers | Most fence deliveries well under 26,001 GCWR | Only require CDL when trailer >10,000 lbs AND GCWR >26,001 |
| Single delivery window ("sometime today") | Contractors waiting = idle crew = $50-$75/hour lost | Offer 2-hour windows: 7-9am, 9-11am, 11am-1pm, 1-3pm |
| Skipping load-at-truck photos | No proof if material was undamaged when it left yard | Require photo at load_verified — before driver departs |
| Paying drivers flat rate regardless of distance | Drivers lose money on far deliveries, cherry-pick close ones | Base + per-mile + weight surcharge + pallet bonus |
| No damage time limit on claims | Claims filed weeks later can't be attributed to delivery | 24-hour mandatory reporting window with photo evidence |
| Letting drivers self-report delivery time | Opens door to time fraud, hurts accuracy | GPS timestamp as source of truth for arrival/departure |
| Not checking insurance expiration dates | Uninsured driver causes accident = catastrophic liability | Automated alerts at 60, 30, 14 days before expiry |
| Metric | Value | Source |
|---|---|---|
| Amazon last-mile cost per package | $5–$8 (parcels, irrelevant to building materials) | Amazon 10-K filings |
| Building material delivery cost per stop | $45–$120 (depending on weight/distance) | Industry surveys |
| Last-mile as % of total shipping cost | 50–53% | McKinsey logistics research |
| Deliveries per driver per day (building materials) | 4–8 (vs 150+ for Amazon parcels) | Lumber/fence industry avg |
| On-time delivery rate target | >90% (best-in-class: >95%) | Logistics KPI benchmarks |
| Delivery damage rate (fence materials) | 2–5% of deliveries | H&J internal estimate |
| Most common damage claim | Cracked vinyl panel (40% of claims) | Industry data |
| Subcontractor driver pay (per delivery, local) | $35–$65 base | DoorDash Drive, local hauler comps |
| Subcontractor driver pay (per mile beyond base) | $1.25–$2.00 | IRS mileage rate ref ($0.67/mi 2024) |
| Customer satisfaction with GPS tracking | +35% NPS improvement | Bringg/Onfleet case studies |
| POD photo compliance target | 100% (non-negotiable) | Best practice |
| FMCSA liability insurance minimum (10,001+ lbs) | $750,000 ($1M recommended) | FMCSA regulations |
| CDL threshold | GCWR >26,001 AND trailer >10,000 lbs | FMCSA 49 CFR §383.91 |
| Short-haul HOS exemption radius | 100 air miles | FMCSA 49 CFR §395.1(e) |
| Morning delivery demand | 70%+ of contractor deliveries request 7-9am | H&J internal data |
| Driver onboarding target | <48 hours (application to first delivery) | Uber/DoorDash benchmark |
| DOT inspection frequency (clean CSA score) | ~1 per 100,000 miles | FMCSA data |
drivers (line 842) → In-house drivers linked to users, suppliers, locationsdeliveryDrivers (line 2488) → Phase 3 delivery company driversgigDrivers (line 6065) → Subcontractor/gig drivers with full verification fieldsdeliveryMarketplaceJobs (line 6123) → Uber-style job postingsdeliveryBids (line 6177) → Driver bids on delivery jobsactiveDeliveryTracking (line 6206) → Real-time GPS trackingdeliveryProof (line 6243) → POD photos, signatures, GPScontractorPreferredDrivers (line 8280) → Preferred driver preferencesdelivery_locations → Geocoded delivery addresses (for EXIF tagging)deliveryZones (line 1115) → Zone definitions with polygon boundariesAssignDriverModal → Staff dispatch UI (blocking modal after order confirmation)delivery-photos.routes.ts → Driver photo upload with GPS injection to EXIFtwilio-webhooks.routes.ts → SMS notifications to drivers and contractorsserver/routes/delivery.routes.ts (modular pattern)server/services/storage.ts)logInfo, logError from server/logger.ts)delivery-photos.routes.ts)| Company | What to Learn | Fence-Industry Adaptation |
|---|---|---|
| DoorDash Drive | White-label delivery API, driver claiming model, surge pricing | Same claiming pattern, but for 500+ lb loads with capacity checks |
| Uber Freight | AI-powered carrier matching, real-time visibility, TMS integration | Carrier = subcontractor driver, shipment = pallet load, TMS = H&J platform |
| Bringg | Delivery orchestration platform, driver app SDK, enterprise features | Reference for driver mobile experience and dispatch engine |
| Onfleet | Route optimization API, proof-of-delivery, driver performance analytics | Best-in-class POD workflow — replicate photo requirements |
| Amazon DSP | Delivery Service Partner program, fleet scaling, tier incentives | Model for H&J's subcontractor driver tier program |
| Convoy (acquired by Flexport) | Digital freight brokerage, backhaul matching, transparent pricing | Future: match empty return trips with material pickups |
| Route4Me | Multi-constraint route optimization, big/bulky item handling | Handle time windows + weight + access + CDL restrictions |
| Descartes | Last-mile fleet management for construction materials specifically | Industry-specific patterns for building material logistics |
delivery_locations table for address geocoding. Photos without GPS = rejected.insuranceVerified = true, insuranceExpiresAt > now + 30 days). Automated alerts at 60/30/14 day expiration.vehicle.trailerGvwr > 10000 AND GCWR > 26001. Most H&J deliveries are well under this threshold.