import { DataSource } from 'typeorm'; /** Great-circle distance in km between two WGS84 points (haversine). */ export function haversineKm(lat1: number, lng1: number, lat2: number, lng2: number): number { const toRad = (d: number) => (d * Math.PI) / 180; const dLat = toRad(lat2 - lat1); const dLng = toRad(lng2 - lng1); const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2; return 2 * 6371 * Math.asin(Math.sqrt(a)); } /** * Estimated road-leg distance for a booking's first/last mile: straight-line km * from the yard (freight.yard_locations) to the customer's pickup/delivery GPS * point on the booking. FIRST = origin yard → pickup point, LAST = destination * yard → delivery point. Null when either end has no coordinates. * * ponytail: haversine straight-line, not road routing — plug a routing API in * here if real road km is ever required. */ export async function estimateMileKm( dataSource: DataSource, bookingId: string, mile: 'FIRST' | 'LAST', ): Promise { const [row] = await dataSource.query( mile === 'LAST' ? `SELECT b.last_mile_delivery_lat AS lat, b.last_mile_delivery_lng AS lng, l.latitude AS yard_lat, l.longitude AS yard_lng FROM freight.bookings b LEFT JOIN freight.yard_locations l ON l.yard_id = b.destination_yard_id AND l.deleted_at IS NULL WHERE b.id = $1 AND b.deleted_at IS NULL` : `SELECT b.first_mile_pickup_lat AS lat, b.first_mile_pickup_lng AS lng, l.latitude AS yard_lat, l.longitude AS yard_lng FROM freight.bookings b LEFT JOIN freight.yard_locations l ON l.yard_id = b.origin_yard_id AND l.deleted_at IS NULL WHERE b.id = $1 AND b.deleted_at IS NULL`, [bookingId], ); if (!row || row.lat == null || row.lng == null || row.yard_lat == null || row.yard_lng == null) { return null; } const km = haversineKm(Number(row.yard_lat), Number(row.yard_lng), Number(row.lat), Number(row.lng)); return Math.round(km * 100) / 100; }