import supa, { ensureSupabaseReady } from '@/components/supadb/aksupainstance.uts' import { computeDateRange } from './dateRange.uts' export type DeliveryAnalysisData = { trendList: Array topList: Array startIso: string endIso: string } export async function fetchDeliveryAnalysis(period: string): Promise { const { startIso, endIso } = computeDateRange(period) await ensureSupabaseReady() // 优先走 RPC(需要在 Supabase 执行 DELIVERY_ANALYSIS_RPCS.sql 创建函数) let trendList: Array = [] let topList: Array = [] const trendRes: any = await supa.rpc('rpc_delivery_efficiency_daily', { p_start: startIso, p_end: endIso } as UTSJSONObject) if (trendRes.status === 404) { // RPC 不存在:降级到直查表聚合(测试阶段兜底) const taskRes: any = await supa .from('ml_delivery_tasks') .select('id,driver_id,assigned_at,delivered_at,delivery_fee', {}) .eq('status', 5) .gte('assigned_at', startIso) .order('assigned_at', { ascending: true } as any) .execute() if (taskRes?.error != null) throw taskRes.error const rowsAny = (taskRes.data != null ? taskRes.data : []) as any const tasks = Array.isArray(rowsAny) ? (rowsAny as Array) : [] const dayAgg = new Map() const driverAgg = new Map() const driverFeeAgg = new Map() const driverTimeAgg = new Map() for (let i = 0; i < tasks.length; i++) { const t = tasks[i] const assignedAt = t.getString('assigned_at') ?? '' const deliveredAt = t.getString('delivered_at') ?? '' const driverId = t.getString('driver_id') ?? '' if (assignedAt.trim() === '' || deliveredAt.trim() === '') continue const day = assignedAt.length >= 10 ? assignedAt.substring(0, 10) : assignedAt const a = new Date(assignedAt) const d = new Date(deliveredAt) const diffMin = Math.max(0, (d.getTime() - a.getTime()) / 60000) const fee = t.getNumber('delivery_fee') ?? 0 const old = dayAgg.get(day) if (old == null) { const obj = new UTSJSONObject() obj.set('day', day) obj.set('completed_orders', 1) obj.set('sum_minutes', diffMin) obj.set('total_fee', fee) dayAgg.set(day, obj) } else { old.set('completed_orders', (old.getNumber('completed_orders') ?? 0) + 1) old.set('sum_minutes', (old.getNumber('sum_minutes') ?? 0) + diffMin) old.set('total_fee', (old.getNumber('total_fee') ?? 0) + fee) } if (driverId.trim() !== '') { driverAgg.set(driverId, (driverAgg.get(driverId) ?? 0) + 1) driverFeeAgg.set(driverId, (driverFeeAgg.get(driverId) ?? 0) + fee) driverTimeAgg.set(driverId, (driverTimeAgg.get(driverId) ?? 0) + diffMin) } } // dayAgg -> trendList const days = Array.from(dayAgg.keys()).sort() for (let i = 0; i < days.length; i++) { const day = days[i] const obj = dayAgg.get(day) if (obj != null) { const completed = obj.getNumber('completed_orders') ?? 0 const sumMin = obj.getNumber('sum_minutes') ?? 0 const totalFee = obj.getNumber('total_fee') ?? 0 const out = new UTSJSONObject() out.set('day', day) out.set('avg_delivery_time', completed > 0 ? sumMin / completed : 0) out.set('total_fee', totalFee) out.set('completed_orders', completed) trendList.push(out) } } // driverAgg -> topList (Top10) const drivers = Array.from(driverAgg.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10) for (let i = 0; i < drivers.length; i++) { const [driverId, orders] = drivers[i] const out = new UTSJSONObject() out.set('driver_id', driverId) out.set('orders', orders) out.set('total_fee', driverFeeAgg.get(driverId) ?? 0) out.set('total_minutes', driverTimeAgg.get(driverId) ?? 0) topList.push(out) } } else if (trendRes.error != null) { throw trendRes.error } else { const anyData = trendRes.data as any trendList = Array.isArray(anyData) ? (anyData as Array) : [] // Top drivers const topRes = await supa.rpc('rpc_delivery_efficiency_top_drivers', { p_start: startIso, p_end: endIso, p_limit: 10 }) if (topRes.status === 404) { console.warn('rpc_delivery_efficiency_top_drivers not found, top drivers will be empty') } else if (topRes.error != null) { throw topRes.error } else { const topAny = topRes.data as any topList = Array.isArray(topAny) ? (topAny as Array) : [] } } return { trendList, topList, startIso, endIso } }