← back to Govarbitrage
src/agents/skeptic-rules.test.ts
83 lines
import { describe, expect, it } from "vitest";
import { findCloneGroups, isClosingStale, isLowConfidence, type ValuationTuple } from "./skeptic-rules";
const t = (
listingId: string,
newRetail: number | null,
expectedNetProfit: number | null,
roi: number | null,
): ValuationTuple => ({ listingId, title: listingId, newRetail, expectedNetProfit, roi });
describe("findCloneGroups", () => {
it("flags >=3 listings sharing the exact (newRetail, net, roi) tuple", () => {
const items = [
t("a", 900, 189, 2.14),
t("b", 900, 189, 2.14),
t("c", 900, 189, 2.14),
t("d", 450, 80, 0.6),
];
const groups = findCloneGroups(items);
expect(groups).toHaveLength(1);
expect(groups[0].members.map((m) => m.listingId)).toEqual(["a", "b", "c"]);
expect(groups[0].key).toBe("$900 retail / $189 net / 214% ROI");
});
it("does not flag pairs (below minGroup)", () => {
const items = [t("a", 900, 189, 2.14), t("b", 900, 189, 2.14), t("c", 450, 80, 0.6)];
expect(findCloneGroups(items)).toHaveLength(0);
});
it("treats a small dollar difference as distinct", () => {
const items = [t("a", 900, 189, 2.14), t("b", 901, 189, 2.14), t("c", 900, 189, 2.14)];
expect(findCloneGroups(items)).toHaveLength(0);
});
it("ignores listings with missing values", () => {
const items = [t("a", 900, 189, 2.14), t("b", 900, 189, 2.14), t("c", null, 189, 2.14)];
expect(findCloneGroups(items)).toHaveLength(0);
});
it("returns multiple groups, largest first", () => {
const items = [
t("a", 900, 189, 2.14),
t("b", 900, 189, 2.14),
t("c", 900, 189, 2.14),
t("d", 600, 120, 1.5),
t("e", 600, 120, 1.5),
t("f", 600, 120, 1.5),
t("g", 600, 120, 1.5),
];
const groups = findCloneGroups(items);
expect(groups).toHaveLength(2);
expect(groups[0].members).toHaveLength(4);
expect(groups[1].members).toHaveLength(3);
});
});
describe("isLowConfidence", () => {
it("handles the 0..100 scale (Research.confidenceScore)", () => {
expect(isLowConfidence(35)).toBe(true);
expect(isLowConfidence(62)).toBe(false);
});
it("handles the 0..1 scale (identificationConfidence)", () => {
expect(isLowConfidence(0.3)).toBe(true);
expect(isLowConfidence(0.55)).toBe(false);
});
it("treats missing confidence as low", () => {
expect(isLowConfidence(null)).toBe(true);
expect(isLowConfidence(undefined)).toBe(true);
});
});
describe("isClosingStale", () => {
const now = new Date("2026-08-06T12:00:00Z");
it("flags ACTIVE listings whose closingAt is in the past", () => {
expect(isClosingStale(new Date("2026-08-05T12:00:00Z"), "ACTIVE", now)).toBe(true);
});
it("does not flag open or ended listings", () => {
expect(isClosingStale(new Date("2026-08-07T12:00:00Z"), "ACTIVE", now)).toBe(false);
expect(isClosingStale(new Date("2026-08-05T12:00:00Z"), "ENDED", now)).toBe(false);
expect(isClosingStale(null, "ACTIVE", now)).toBe(false);
});
});