Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions lib/policy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, it, expect } from "vitest";
import { decide } from "./policy";
import type { InboundPayload, Triage } from "./types";

const baseTriage: Triage = {
category: "refund",
urgency: "normal",
sentiment: "neutral",
draftReply: "Thanks for reaching out.",
summary: "Customer requesting a refund.",
};

const basePayload: InboundPayload = {
sender: "customer@example.com",
body: "I'd like a refund please.",
};

describe("decide() refund policy", () => {
it("auto-resolves a valid refund within the auto-limit", () => {
const result = decide(
{ ...baseTriage, refundAmount: 25 },
basePayload,
);

expect(result.status).toBe("resolved");
expect(result.actionTaken).not.toBeNull();
});

it("escalates a refund above the auto-limit", () => {
const result = decide(
{ ...baseTriage, refundAmount: 500 },
basePayload,
);

expect(result.status).toBe("escalated");
expect(result.actionTaken).toBeNull();
});

it("escalates a zero refund amount instead of auto-resolving it", () => {
const result = decide({ ...baseTriage, refundAmount: 0 }, basePayload);

expect(result.status).toBe("escalated");
expect(result.actionTaken).toBeNull();
expect(result.reason).toMatch(/zero or negative/);
});

it("escalates a negative refund amount instead of auto-resolving it", () => {
const result = decide({ ...baseTriage, refundAmount: -10 }, basePayload);

expect(result.status).toBe("escalated");
expect(result.actionTaken).toBeNull();
expect(result.reason).toMatch(/zero or negative/);
});

it("escalates a non-finite refund amount instead of auto-resolving it", () => {
const result = decide(
{ ...baseTriage, refundAmount: NaN },
basePayload,
);

expect(result.status).toBe("escalated");
expect(result.actionTaken).toBeNull();
expect(result.reason).toMatch(/not a valid number/);
});

it("escalates when no refund amount was stated", () => {
const result = decide({ ...baseTriage, refundAmount: undefined }, basePayload);

expect(result.status).toBe("escalated");
expect(result.actionTaken).toBeNull();
expect(result.reason).toBe("refund amount not stated");
});
});
35 changes: 26 additions & 9 deletions lib/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ export function decide(triage: Triage, payload: InboundPayload): Decision {

case "refund": {
const amount = triage.refundAmount ?? null;
if (amount != null && amount <= refundAutoLimit) {
const isAutoResolvable =
amount != null &&
Number.isFinite(amount) &&
amount > 0 &&
amount <= refundAutoLimit;

if (isAutoResolvable) {
return {
status: "resolved",
proposedAction: `Issue refund of $${amount}`,
Expand All @@ -59,17 +65,28 @@ export function decide(triage: Triage, payload: InboundPayload): Decision {
draftReply: `Hi, we've issued your refund of $${amount}. ${triage.draftReply}`,
};
}

let reason: string;
let proposedAction: string;
if (amount == null) {
reason = "refund amount not stated";
proposedAction = "Confirm refund amount, then approve";
} else if (!Number.isFinite(amount)) {
reason = `refund amount $${amount} is not a valid number`;
proposedAction = "Confirm refund amount, then approve";
} else if (amount <= 0) {
reason = `refund amount $${amount} is zero or negative`;
proposedAction = "Confirm refund amount, then approve";
} else {
reason = `refund $${amount} exceeds auto-limit $${refundAutoLimit}`;
proposedAction = `Approve refund of $${amount}`;
}

return {
status: "escalated",
proposedAction:
amount != null
? `Approve refund of $${amount}`
: "Confirm refund amount, then approve",
proposedAction,
actionTaken: null,
reason:
amount != null
? `refund $${amount} exceeds auto-limit $${refundAutoLimit}`
: "refund amount not stated",
reason,
draftReply: triage.draftReply,
};
}
Expand Down
Loading