mall数据库文件
This commit is contained in:
132
services/analytics/deliveryAnalysisService.uts
Normal file
132
services/analytics/deliveryAnalysisService.uts
Normal file
@@ -0,0 +1,132 @@
|
||||
import supa, { ensureSupabaseReady } from '@/components/supadb/aksupainstance.uts'
|
||||
import { computeDateRange } from './dateRange.uts'
|
||||
|
||||
export type DeliveryAnalysisData = {
|
||||
trendList: Array<UTSJSONObject>
|
||||
topList: Array<UTSJSONObject>
|
||||
startIso: string
|
||||
endIso: string
|
||||
}
|
||||
|
||||
export async function fetchDeliveryAnalysis(period: string): Promise<DeliveryAnalysisData> {
|
||||
const { startIso, endIso } = computeDateRange(period)
|
||||
|
||||
await ensureSupabaseReady()
|
||||
|
||||
// 优先走 RPC(需要在 Supabase 执行 DELIVERY_ANALYSIS_RPCS.sql 创建函数)
|
||||
let trendList: Array<UTSJSONObject> = []
|
||||
let topList: Array<UTSJSONObject> = []
|
||||
|
||||
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<UTSJSONObject>) : []
|
||||
|
||||
const dayAgg = new Map<string, UTSJSONObject>()
|
||||
const driverAgg = new Map<string, number>()
|
||||
const driverFeeAgg = new Map<string, number>()
|
||||
const driverTimeAgg = new Map<string, number>()
|
||||
|
||||
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<UTSJSONObject>) : []
|
||||
|
||||
// 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<UTSJSONObject>) : []
|
||||
}
|
||||
}
|
||||
|
||||
return { trendList, topList, startIso, endIso }
|
||||
}
|
||||
Reference in New Issue
Block a user