← back to Govarbitrage

src/engines/scoring.test.ts

73 lines

import { describe, expect, it } from "vitest";
import { computeScores, computeComponents } from "./scoring";
import type { ScoreInput } from "./scoring";

const base: ScoreInput = {
  roi: 0.6,
  expectedNetProfit: 3000,
  demandScore: 70,
  daysUntilSold: 25,
  weightLbs: 40,
  condition: "USED_GOOD",
  bidCount: 2,
  localPickup: false,
  hasBuyerLeads: false,
  confidenceScore: 80,
  probabilityOfSale: 0.7,
  isFreight: false,
};

describe("computeScores", () => {
  it("returns all nine scoring profiles", () => {
    const scores = computeScores(base);
    expect(scores).toHaveLength(9);
    const profiles = scores.map((s) => s.profile).sort();
    expect(profiles).toContain("OVERALL_OPPORTUNITY");
    expect(profiles).toContain("BEST_ARBITRAGE");
    expect(profiles).toContain("PARTS_ONLY");
  });

  it("every score is 0..100 and carries a non-empty explanation", () => {
    for (const s of computeScores(base)) {
      expect(s.value).toBeGreaterThanOrEqual(0);
      expect(s.value).toBeLessThanOrEqual(100);
      expect(s.explanation.length).toBeGreaterThan(20);
      expect(s.factors.length).toBeGreaterThan(0);
    }
  });

  it("rewards a genuine salvage lot under PARTS_ONLY and penalizes non-parts", () => {
    const parts = computeScores({ ...base, condition: "FOR_PARTS" }).find(
      (s) => s.profile === "PARTS_ONLY",
    )!;
    const notParts = computeScores({ ...base, condition: "NEW" }).find(
      (s) => s.profile === "PARTS_ONLY",
    )!;
    expect(parts.value).toBeGreaterThan(notParts.value);
  });

  it("higher ROI raises the arbitrage component and BEST_ARBITRAGE score", () => {
    const low = computeScores({ ...base, roi: 0.05 }).find((s) => s.profile === "BEST_ARBITRAGE")!;
    const high = computeScores({ ...base, roi: 1.5 }).find((s) => s.profile === "BEST_ARBITRAGE")!;
    expect(high.value).toBeGreaterThan(low.value);
  });

  it("flags HIGH risk on low confidence + thin margin", () => {
    const risky = computeScores({
      ...base,
      confidenceScore: 30,
      roi: 0.05,
      condition: "UNKNOWN",
      daysUntilSold: 100,
    })[0];
    expect(risky.risk).toBe("HIGH");
  });

  it("marks light non-pickup items as EASY drop-ship, heavy freight as harder", () => {
    const easy = computeComponents(base);
    expect(easy.logistics).toBeGreaterThan(50);
    const heavy = computeScores({ ...base, weightLbs: 700, isFreight: true })[0];
    expect(["DIFFICULT", "INFEASIBLE", "MODERATE"]).toContain(heavy.dropShip);
  });
});