update on the agents and registry side

This commit is contained in:
2026-09-30 12:03:13 +05:30
parent 92e8da2842
commit 53199127ba
73 changed files with 7268 additions and 1134 deletions

View File

@@ -0,0 +1,67 @@
import { CashExposureSkill } from '@/lib/assistant/skills/definitions/CashExposureSkill';
describe('CashExposureSkill', () => {
const NOW = '2026-08-31 14:00:00';
it('flags riders carrying total COD cash above max threshold', () => {
const rows = [
// Rider 1: 12000 total (Critical)
{ bookingid: 1, userid: 101, ridername: 'Rider A', orderstatus: 'picked', paymenttype: 'cod', cod_amount: '5000' },
{ bookingid: 2, userid: 101, ridername: 'Rider A', orderstatus: 'accepted', paymenttype: 'cod', cod_amount: '7000' },
// Rider 2: 8000 total (Warning)
{ bookingid: 3, userid: 102, ridername: 'Rider B', orderstatus: 'picked', paymenttype: 'cod', cod_amount: '8000' },
// Rider 3: 4000 total (Safe)
{ bookingid: 4, userid: 103, ridername: 'Rider C', orderstatus: 'picked', paymenttype: 'cod', cod_amount: '4000' },
// Non-COD order for Rider 1 (Ignored)
{ bookingid: 5, userid: 101, ridername: 'Rider A', orderstatus: 'picked', paymenttype: 'prepaid', cod_amount: '0' }
];
const findings = CashExposureSkill.evaluate(rows, NOW, { maxCashPerRider: 10000, warningCashPercent: 75 });
const critical = findings.find((f) => f.id === 'cash_exposure_critical');
const warning = findings.find((f) => f.id === 'cash_exposure_warning');
expect(critical).toBeDefined();
expect(critical.count).toBe(1); // Only Rider 1
expect(critical.rows.length).toBe(2); // Rider 1 has 2 COD orders
expect(critical.detail[0]).toContain('12,000');
expect(warning).toBeDefined();
expect(warning.count).toBe(1); // Only Rider 2
expect(warning.detail[0]).toContain('8,000');
});
it('returns no findings when all riders are under warning threshold', () => {
const rows = [
{ bookingid: 1, userid: 101, orderstatus: 'picked', paymenttype: 'cod', cod_amount: '5000' }
];
const findings = CashExposureSkill.evaluate(rows, NOW, { maxCashPerRider: 10000, warningCashPercent: 75 });
expect(findings.length).toBe(0);
});
it('executes enforce_cash_handoff tool handler', async () => {
const tool = CashExposureSkill.tools.find((t) => t.name === 'enforce_cash_handoff');
expect(tool).toBeDefined();
const result = await tool.handler({ milerId: 101, totalCash: 12000, hubId: 'HUB-1' });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('cash_handoff');
expect(result.proposal.milerId).toBe(101);
expect(result.proposal.totalCash).toBe(12000);
expect(result.proposal.hubId).toBe('HUB-1');
});
it('handles orders with non-numeric or missing amounts gracefully', () => {
const rows = [
{ bookingid: 1, userid: 101, orderstatus: 'picked', paymenttype: 'cod' }, // missing amount
{ bookingid: 2, userid: 101, orderstatus: 'picked', paymenttype: 'cod', cod_amount: 'invalid' }, // invalid amount
{ bookingid: 3, userid: 101, orderstatus: 'picked', paymenttype: 'cod', cod_amount: '12000' } // valid amount
];
const findings = CashExposureSkill.evaluate(rows, NOW, { maxCashPerRider: 10000, warningCashPercent: 75 });
const critical = findings.find((f) => f.id === 'cash_exposure_critical');
expect(critical).toBeDefined();
expect(critical.detail[0]).toContain('12,000');
});
});

View File

@@ -0,0 +1,42 @@
import { DoorstepStallSkill } from '@/lib/assistant/skills/definitions/DoorstepStallSkill';
describe('DoorstepStallSkill', () => {
const fixedNow = '2026-08-31 12:00:00';
it('detects riders stalled at door past threshold', () => {
const rows = [
{
bookingid: 1,
bookingno: 'BK-1',
orderstatus: 'arrived',
reachedat: '2026-08-31 11:35:00', // 25 min waiting
ridername: 'Karthik Raja',
userid: 44,
},
{
bookingid: 2,
bookingno: 'BK-2',
orderstatus: 'arrived',
reachedat: '2026-08-31 11:55:00', // 5 min waiting
ridername: 'Praveen',
userid: 45,
},
];
const findings = DoorstepStallSkill.evaluate(rows, fixedNow, { arrivedStalledMin: 20 });
expect(findings.length).toBe(1);
expect(findings[0].id).toBe('stalled_at_door');
expect(findings[0].count).toBe(1);
expect(findings[0].rows[0].bookingid).toBe(1);
});
it('executes ping_stalled_rider tool handler', async () => {
const tool = DoorstepStallSkill.tools.find((t) => t.name === 'ping_stalled_rider');
expect(tool).toBeDefined();
const result = await tool.handler({ milerId: 44 });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('notify_miler');
expect(result.proposal.milerId).toBe(44);
});
});

View File

@@ -0,0 +1,39 @@
import { FleetBalancerSkill } from '@/lib/assistant/skills/definitions/FleetBalancerSkill';
describe('FleetBalancerSkill', () => {
it('detects saturated riders when pending orders are waiting in queue', () => {
const rows = [
// 3 active jobs for rider 10
{ bookingid: 1, userid: 10, ridername: 'Rider A', orderstatus: 'picked' },
{ bookingid: 2, userid: 10, ridername: 'Rider A', orderstatus: 'active' },
{ bookingid: 3, userid: 10, ridername: 'Rider A', orderstatus: 'accepted' },
// 1 pending order in queue
{ bookingid: 4, userid: null, orderstatus: 'pending' },
];
const findings = FleetBalancerSkill.evaluate(rows, '2026-08-31 12:00:00', { riderActiveCap: 3 });
expect(findings.length).toBe(1);
expect(findings[0].id).toBe('rider_saturation');
expect(findings[0].count).toBe(1);
});
it('does not flag saturation if no unassigned orders are waiting in queue', () => {
const rows = [
{ bookingid: 1, userid: 10, ridername: 'Rider A', orderstatus: 'picked' },
{ bookingid: 2, userid: 10, ridername: 'Rider A', orderstatus: 'active' },
{ bookingid: 3, userid: 10, ridername: 'Rider A', orderstatus: 'accepted' },
];
const findings = FleetBalancerSkill.evaluate(rows, '2026-08-31 12:00:00', { riderActiveCap: 3 });
expect(findings.length).toBe(0);
});
it('executes balance_rider_load tool handler', async () => {
const tool = FleetBalancerSkill.tools.find((t) => t.name === 'balance_rider_load');
expect(tool).toBeDefined();
const result = await tool.handler({ fromMilerId: 10, toMilerId: 20 });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('rebalance_workload');
});
});

View File

@@ -0,0 +1,37 @@
import { HighValueCodSkill } from '@/lib/assistant/skills/definitions/HighValueCodSkill';
describe('HighValueCodSkill', () => {
it('detects high value COD consignments at or above threshold', () => {
const rows = [
{
bookingid: 1,
bookingno: 'BK-COD-1',
orderstatus: 'active',
paymenttype: 'COD',
orderamount: 4500,
},
{
bookingid: 2,
bookingno: 'BK-COD-2',
orderstatus: 'active',
paymenttype: 'COD',
orderamount: 1500,
},
];
const findings = HighValueCodSkill.evaluate(rows, '2026-08-31 12:00:00', { codRiskThresholdAmount: 3000 });
expect(findings.length).toBe(1);
expect(findings[0].id).toBe('high_value_cod_alert');
expect(findings[0].count).toBe(1);
expect(findings[0].rows[0].bookingid).toBe(1);
});
it('executes enforce_otp_verification tool handler', async () => {
const tool = HighValueCodSkill.tools.find((t) => t.name === 'enforce_otp_verification');
expect(tool).toBeDefined();
const result = await tool.handler({ bookingId: 1 });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('require_otp');
});
});

View File

@@ -0,0 +1,50 @@
import { HubCongestionSkill } from '@/lib/assistant/skills/definitions/HubCongestionSkill';
describe('HubCongestionSkill', () => {
const NOW = '2026-08-31 14:00:00';
it('detects parcels stalled at hub beyond dwell threshold', () => {
const rows = [
{ bookingid: 1, bookingno: 'BK-1', orderstatus: 'pending', orderdate: '2026-08-31 13:00:00' }, // 60 min
{ bookingid: 2, bookingno: 'BK-2', orderstatus: 'accepted', orderdate: '2026-08-31 13:10:00' }, // 50 min
{ bookingid: 3, bookingno: 'BK-3', orderstatus: 'pending', orderdate: '2026-08-31 13:50:00' }, // 10 min — not stalled
{ bookingid: 4, bookingno: 'BK-4', orderstatus: 'picked', orderdate: '2026-08-31 12:00:00' }, // in transit — excluded
];
const findings = HubCongestionSkill.evaluate(rows, NOW, { hubDwellMinutes: 45 });
expect(findings.length).toBe(1);
expect(findings[0].id).toBe('hub_parcel_stalled');
expect(findings[0].count).toBe(2);
expect(findings[0].rows.map((r) => r.bookingid)).toContain(1);
expect(findings[0].rows.map((r) => r.bookingid)).toContain(2);
expect(findings[0].rows.map((r) => r.bookingid)).not.toContain(3);
});
it('returns no findings when all parcels are within dwell threshold', () => {
const rows = [
{ bookingid: 5, orderstatus: 'pending', orderdate: '2026-08-31 13:50:00' }, // 10 min
];
const findings = HubCongestionSkill.evaluate(rows, NOW, { hubDwellMinutes: 45 });
expect(findings.length).toBe(0);
});
it('executes dispatch_hub_idle_parcels tool handler', async () => {
const tool = HubCongestionSkill.tools.find((t) => t.name === 'dispatch_hub_idle_parcels');
expect(tool).toBeDefined();
const result = await tool.handler({ bookingIds: [1, 2], milerId: 99 });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('dispatch_hub_parcels');
expect(result.proposal.bookingIds).toEqual([1, 2]);
});
it('respects custom dwell threshold', () => {
const rows = [
{ bookingid: 10, orderstatus: 'pending', orderdate: '2026-08-31 13:45:00' }, // 15 min
];
const findingsStrict = HubCongestionSkill.evaluate(rows, NOW, { hubDwellMinutes: 10 });
expect(findingsStrict.length).toBe(1);
const findingsLenient = HubCongestionSkill.evaluate(rows, NOW, { hubDwellMinutes: 30 });
expect(findingsLenient.length).toBe(0);
});
});

View File

@@ -0,0 +1,53 @@
import { LateDispatchSkill } from '@/lib/assistant/skills/definitions/LateDispatchSkill';
describe('LateDispatchSkill', () => {
const NOW = '2026-08-31 14:00:00';
it('detects critically late dispatch orders', () => {
const rows = [
{ bookingid: 1, bookingno: 'BK-1', orderstatus: 'pending', orderdate: '2026-08-31 11:30:00' }, // 150 min
{ bookingid: 2, bookingno: 'BK-2', orderstatus: 'pending', orderdate: '2026-08-31 12:30:00' }, // 90 min
{ bookingid: 3, bookingno: 'BK-3', orderstatus: 'pending', orderdate: '2026-08-31 13:20:00' }, // 40 min — warning only
{ bookingid: 4, bookingno: 'BK-4', orderstatus: 'pending', orderdate: '2026-08-31 13:50:00' }, // 10 min — no issue
{ bookingid: 5, bookingno: 'BK-5', orderstatus: 'picked', orderdate: '2026-08-31 11:00:00' }, // dispatched — excluded
];
const findings = LateDispatchSkill.evaluate(rows, NOW, { lateDispatchMinutes: 30, criticalDispatchMinutes: 60 });
const critical = findings.find((f) => f.id === 'late_dispatch_critical');
const warning = findings.find((f) => f.id === 'late_dispatch_warning');
expect(critical).toBeDefined();
expect(critical.count).toBe(2); // BK-1 and BK-2
expect(warning).toBeDefined();
expect(warning.count).toBe(1); // BK-3
});
it('returns no findings when all orders are dispatched quickly', () => {
const rows = [
{ bookingid: 1, orderstatus: 'pending', orderdate: '2026-08-31 13:55:00' }, // 5 min
];
const findings = LateDispatchSkill.evaluate(rows, NOW, { lateDispatchMinutes: 30, criticalDispatchMinutes: 60 });
expect(findings.length).toBe(0);
});
it('executes trigger_auto_dispatch tool handler', async () => {
const tool = LateDispatchSkill.tools.find((t) => t.name === 'trigger_auto_dispatch');
expect(tool).toBeDefined();
const result = await tool.handler({ bookingIds: [1, 2], reason: 'Test late dispatch' });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('auto_dispatch');
expect(result.proposal.bookingIds).toEqual([1, 2]);
});
it('excludes non-pending status orders', () => {
const rows = [
{ bookingid: 1, orderstatus: 'accepted', orderdate: '2026-08-31 11:00:00' },
{ bookingid: 2, orderstatus: 'picked', orderdate: '2026-08-31 11:00:00' },
{ bookingid: 3, orderstatus: 'delivered', orderdate: '2026-08-31 11:00:00' },
];
const findings = LateDispatchSkill.evaluate(rows, NOW, { lateDispatchMinutes: 30, criticalDispatchMinutes: 60 });
expect(findings.length).toBe(0);
});
});

View File

@@ -0,0 +1,39 @@
import { RiderBatterySafetySkill } from '@/lib/assistant/skills/definitions/RiderBatterySafetySkill';
describe('RiderBatterySafetySkill', () => {
it('detects active deliveries where rider device battery is critical', () => {
const rows = [
{
bookingid: 1,
bookingno: 'BK-1',
orderstatus: 'picked',
userid: 50,
ridername: 'Karthik',
battery_percentage: 12,
},
{
bookingid: 2,
bookingno: 'BK-2',
orderstatus: 'picked',
userid: 51,
ridername: 'Praveen',
battery_percentage: 85,
},
];
const findings = RiderBatterySafetySkill.evaluate(rows, '2026-08-31 12:00:00', { criticalBatteryPercent: 15 });
expect(findings.length).toBe(1);
expect(findings[0].id).toBe('rider_critical_battery');
expect(findings[0].count).toBe(1);
expect(findings[0].rows[0].bookingid).toBe(1);
});
it('executes alert_low_battery_rider tool handler', async () => {
const tool = RiderBatterySafetySkill.tools.find((t) => t.name === 'alert_low_battery_rider');
expect(tool).toBeDefined();
const result = await tool.handler({ milerId: 50 });
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('battery_alert');
});
});

View File

@@ -0,0 +1,78 @@
import { SlaGuardianSkill } from '@/lib/assistant/skills/definitions/SlaGuardianSkill';
describe('SlaGuardianSkill', () => {
const fixedNow = '2026-08-31 12:00:00';
it('detects breached SLA orders', () => {
const rows = [
{
bookingid: 1,
bookingno: 'BK-1',
orderstatus: 'pending',
expecteddeliverytime: '2026-08-31 11:30:00', // 30 min overdue
},
{
bookingid: 2,
bookingno: 'BK-2',
orderstatus: 'delivered', // terminal, should be ignored
expecteddeliverytime: '2026-08-31 11:30:00',
},
];
const findings = SlaGuardianSkill.evaluate(rows, fixedNow);
const breach = findings.find((f) => f.id === 'sla_breached');
expect(breach).toBeDefined();
expect(breach.count).toBe(1);
expect(breach.rows[0].bookingid).toBe(1);
});
it('detects at-risk SLA orders within custom threshold window', () => {
const rows = [
{
bookingid: 10,
bookingno: 'BK-10',
orderstatus: 'accepted',
expecteddeliverytime: '2026-08-31 12:20:00', // due in 20 min
},
];
// Default window is 45 min -> should match
const findings = SlaGuardianSkill.evaluate(rows, fixedNow, { slaRiskWindowMin: 45 });
expect(findings.some((f) => f.id === 'sla_at_risk')).toBe(true);
// If threshold overridden to 15 min -> should not match 20 min
const restricted = SlaGuardianSkill.evaluate(rows, fixedNow, { slaRiskWindowMin: 15 });
expect(restricted.some((f) => f.id === 'sla_at_risk')).toBe(false);
});
it('detects unassigned aging orders', () => {
const rows = [
{
bookingid: 20,
bookingno: 'BK-20',
orderstatus: 'pending',
orderdate: '2026-08-31 10:30:00', // 90 min old
},
];
const findings = SlaGuardianSkill.evaluate(rows, fixedNow, { unassignedAgingMin: 60 });
expect(findings.some((f) => f.id === 'unassigned_aging')).toBe(true);
});
it('executes reassign_sla_critical_order tool handler', async () => {
const tool = SlaGuardianSkill.tools.find((t) => t.name === 'reassign_sla_critical_order');
expect(tool).toBeDefined();
const result = await tool.handler({
bookingId: 101,
targetMilerId: 55,
reason: 'Urgent priority reassignment',
});
expect(result.success).toBe(true);
expect(result.proposal.action).toBe('reassign_miler');
expect(result.proposal.bookingId).toBe(101);
expect(result.proposal.milerId).toBe(55);
});
});