Files
doormilxpress_astryx/src/lib/assistant/skills/definitions/LateDispatchSkill.js

134 lines
5.1 KiB
JavaScript

// ==============================|| Skill: Late Dispatch Agent ||============================== //
//
// Flags orders that have been accepted/created but not yet dispatched to a rider
// within the acceptable dispatch window during business hours.
// Late dispatch at creation cascades into late delivery at the door.
import { parseDoormileTimestamp } from '@/lib/doormileTimestamp';
const PENDING_DISPATCH_STATUSES = new Set(['pending']);
const minutesBetween = (from, to) => {
const a = parseDoormileTimestamp(from);
const b = parseDoormileTimestamp(to);
if (!a.isValid() || !b.isValid()) return null;
return b.diff(a, 'minute');
};
const ref = (row) => row?.orderid || row?.bookingno || (row?.bookingid ? `#${row.bookingid}` : '—');
export const LateDispatchSkill = {
id: 'skill_late_dispatch',
name: 'Late Dispatch Agent',
description: 'Flags orders accepted but sitting in queue without dispatch for too long. Triggers auto-assignment to prevent cascading delays.',
category: 'sla_management',
icon: 'ShieldAlert',
defaultEnabled: true,
thresholds: {
lateDispatchMinutes: {
label: 'Dispatch Deadline',
description: 'Minutes after order creation before flagging as late dispatch.',
value: 30,
min: 10,
max: 90,
step: 5,
unit: 'min'
},
criticalDispatchMinutes: {
label: 'Critical Dispatch Deadline',
description: 'Minutes after order creation before escalating to critical alert.',
value: 60,
min: 30,
max: 180,
step: 10,
unit: 'min'
}
},
instructions: `
- Orders waiting for dispatch beyond the threshold accumulate delay that can't be recovered at the doorstep.
- Auto-assign to the nearest available rider immediately.
- Prioritize orders with the earliest SLA deadlines when multiple are pending.
`,
tools: [
{
name: 'trigger_auto_dispatch',
description: 'Proposes auto-assigning late-pending orders to available riders in zone.',
parameters: {
type: 'object',
properties: {
bookingIds: { type: 'array', items: { type: 'number' }, description: 'Booking IDs requiring immediate dispatch' },
reason: { type: 'string', description: 'Dispatch urgency reason' }
},
required: ['bookingIds']
},
handler: async (args) => ({
success: true,
proposal: {
action: 'auto_dispatch',
bookingIds: args.bookingIds,
reason: args.reason || 'Late dispatch threshold exceeded',
blastRadius: `Auto-assigns ${args.bookingIds?.length || 'late'} order(s) to nearest available zone riders. Rider notifications will be sent.`
}
})
}
],
evaluate: (rows = [], now, currentThresholds = {}) => {
const lateThreshold = currentThresholds.lateDispatchMinutes ?? 30;
const criticalThreshold = currentThresholds.criticalDispatchMinutes ?? 60;
const findings = [];
const allPending = rows
.filter((r) => PENDING_DISPATCH_STATUSES.has(String(r?.orderstatus || '').toLowerCase()))
.map((r) => ({ row: r, waitMin: minutesBetween(r.orderdate, now) }))
.filter((m) => m.waitMin !== null && m.waitMin >= lateThreshold)
.sort((a, b) => b.waitMin - a.waitMin);
const critical = allPending.filter((m) => m.waitMin >= criticalThreshold);
const warning = allPending.filter((m) => m.waitMin >= lateThreshold && m.waitMin < criticalThreshold);
if (critical.length) {
findings.push({
id: 'late_dispatch_critical',
skillId: 'skill_late_dispatch',
severity: 'critical',
title: `${critical.length} order${critical.length === 1 ? '' : 's'} critically late for dispatch (>${criticalThreshold}m)`,
why: `Order has been sitting in queue for over ${criticalThreshold} minutes with no rider assigned. Delivery SLA is at severe risk.`,
count: critical.length,
rows: critical.map((m) => m.row),
detail: critical.slice(0, 8).map((m) => `${ref(m.row)} — waiting ${m.waitMin} min for dispatch`),
proposal: {
label: 'Auto-dispatch to available riders',
tool: 'trigger_auto_dispatch',
scope: critical.map((m) => m.row),
blastRadius: 'Immediately assigns nearest available riders. Overrides manual queue priority.'
}
});
}
if (warning.length) {
findings.push({
id: 'late_dispatch_warning',
skillId: 'skill_late_dispatch',
severity: 'warning',
title: `${warning.length} order${warning.length === 1 ? '' : 's'} approaching dispatch deadline (>${lateThreshold}m)`,
why: `Order has been waiting ${lateThreshold}+ minutes for rider assignment. Act now to prevent SLA breach.`,
count: warning.length,
rows: warning.map((m) => m.row),
detail: warning.slice(0, 8).map((m) => `${ref(m.row)} — waiting ${m.waitMin} min`),
proposal: {
label: 'Dispatch now',
tool: 'trigger_auto_dispatch',
scope: warning.map((m) => m.row),
blastRadius: 'Assigns available riders to pending orders based on zone proximity.'
}
});
}
return findings;
}
};