// ==============================|| Skill: Hub Congestion Agent ||============================== // // // Detects parcels that have been sitting at a hub for too long without // being picked up by a rider. These represent blocked capacity at the hub // and direct SLA risk for the customer. This is critical for hub operations import { parseDoormileTimestamp } from '@/lib/doormileTimestamp'; const HUB_STATUSES = new Set(['pending', 'accepted']); // at hub, not yet picked 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 HubCongestionSkill = { id: 'skill_hub_congestion', name: 'Hub Congestion Agent', description: 'Detects parcels sitting at a hub without rider pickup beyond the dwell threshold. Dispatches nearest idle rider.', category: 'sla_management', icon: 'ShieldAlert', defaultEnabled: true, thresholds: { hubDwellMinutes: { label: 'Hub Dwell Threshold', description: 'Minutes a parcel can sit at hub without a rider pickup before raising a congestion alert.', value: 45, min: 15, max: 120, step: 5, unit: 'min' } }, instructions: ` - Parcels sitting at a hub beyond the dwell threshold block capacity for the next batch. - Identify the nearest idle rider in the same zone and dispatch immediately. - Prefer riders with the fewest active orders to avoid overloading. `, tools: [ { name: 'dispatch_hub_idle_parcels', description: 'Proposes dispatching a nearby idle rider to collect stalled hub parcels.', parameters: { type: 'object', properties: { hubId: { type: 'string', description: 'Hub identifier where parcels are stalled' }, bookingIds: { type: 'array', items: { type: 'number' }, description: 'Booking IDs to dispatch' }, milerId: { type: 'number', description: 'Target idle rider user ID' } }, required: ['bookingIds'] }, handler: async (args) => ({ success: true, proposal: { action: 'dispatch_hub_parcels', hubId: args.hubId || 'unknown', bookingIds: args.bookingIds, milerId: args.milerId, blastRadius: `Dispatches rider to collect ${args.bookingIds?.length || 'stalled'} parcel(s) from hub. Updates parcel status to In-Transit.` } }) } ], evaluate: (rows = [], now, currentThresholds = {}) => { const dwellLimit = currentThresholds.hubDwellMinutes ?? 45; const findings = []; const stalled = rows .filter((r) => HUB_STATUSES.has(String(r?.orderstatus || '').toLowerCase())) .map((r) => ({ row: r, dwellMin: minutesBetween(r.orderdate, now) })) .filter((m) => m.dwellMin !== null && m.dwellMin >= dwellLimit) .sort((a, b) => b.dwellMin - a.dwellMin); if (stalled.length) { findings.push({ id: 'hub_parcel_stalled', skillId: 'skill_hub_congestion', severity: 'critical', title: `${stalled.length} parcel${stalled.length === 1 ? '' : 's'} stalled at hub for >${dwellLimit}m`, why: 'Parcel has been at the hub beyond the acceptable dwell time with no rider assigned for pickup.', count: stalled.length, rows: stalled.map((m) => m.row), detail: stalled.slice(0, 8).map((m) => `${ref(m.row)} — at hub for ${m.dwellMin} min`), proposal: { label: 'Dispatch idle rider to hub', tool: 'dispatch_hub_idle_parcels', scope: stalled.map((m) => m.row), blastRadius: 'Assigns nearest idle zone rider to collect and dispatch all stalled parcels.' } }); } return findings; } };