← back to Govarbitrage

src/engines/types.ts

134 lines

// Shared engine types. Engines operate on plain numbers (US dollars) so they
// are pure, deterministic, and unit-testable independent of Prisma/Decimal.

export type ConditionKey =
  | "NEW"
  | "LIKE_NEW"
  | "USED_GOOD"
  | "USED_FAIR"
  | "FOR_PARTS"
  | "UNKNOWN";

export type SourceKey =
  | "GOVDEALS"
  | "PUBLIC_SURPLUS"
  | "GSA_AUCTIONS"
  | "COUNTY"
  | "STATE_SURPLUS"
  | "UNIVERSITY_SURPLUS"
  | "MUNICIBID"
  | "BID4ASSETS"
  | "CSV"
  | "EXTENSION"
  | "OTHER";

export type RiskKey = "LOW" | "MEDIUM" | "HIGH";
export type DropShipKey = "EASY" | "MODERATE" | "DIFFICULT" | "INFEASIBLE";

export type ScoreProfileKey =
  | "OVERALL_OPPORTUNITY"
  | "BEST_ARBITRAGE"
  | "QUICK_FLIP"
  | "COLLECTOR"
  | "LOCAL_PICKUP"
  | "EASY_FREIGHT"
  | "PARTS_ONLY"
  | "HIGH_CONFIDENCE"
  | "HIGH_PROFIT";

/** Inputs the valuation engine needs (anchor + item facts). */
export interface ValuationInput {
  /** Best estimate of new retail price; the anchor for all derived values. */
  newRetail: number;
  condition: ConditionKey;
  quantity: number;
  /** 0..100 demand for this category/brand (from research/AI or a default). */
  demandScore: number;
  /** How well the item was identified (0..1); drives confidence. */
  identificationConfidence: number;
  /** Count of real comparable sales found; drives confidence. */
  comparableCount?: number;
}

export interface Valuation {
  newRetail: number;
  newReplacement: number;
  avgRetail: number;
  usedSoldPrice: number;
  usedAskingPrice: number;
  usedLow: number;
  usedHigh: number;
  wholesaleValue: number;
  liquidationValue: number;
  sellTodayValue: number;
  value7Day: number;
  value30Day: number;
  value90Day: number;
  expectedSalePrice: number;
  probabilityOfSale: number; // 0..1
  daysUntilSold: number;
  confidenceScore: number; // 0..100
}

export interface CostInput {
  source: SourceKey;
  winningBid: number;
  quantity: number;
  weightLbs: number;
  condition: ConditionKey;
  expectedSalePrice: number;
  daysUntilSold: number;
  /** true when the item must be picked up in person (no shipping from seller). */
  localPickup?: boolean;
  /** destination sales-tax rate applied to the purchase (resale certs may zero this). */
  purchaseTaxRate?: number;
  /** repair estimate override (else derived from condition). */
  repairsOverride?: number;
}

export interface CostBreakdown {
  winningBid: number;
  buyerPremium: number;
  salesTax: number;
  shipping: number;
  freight: number;
  insurance: number;
  packing: number;
  pickupLabor: number;
  testing: number;
  repairs: number;
  certification: number;
  marketplaceFees: number;
  paymentFees: number;
  storage: number;
  photography: number;
  listingLabor: number;
  expectedReturns: number;
  totalInvestment: number;
  expectedNetProfit: number;
  roi: number;
  annualizedReturn: number;
  recommendedMaxBid: number;
  assumptions: Record<string, string | number>;
}

export interface ScoreComponents {
  arbitrage: number;
  demand: number;
  velocity: number;
  logistics: number;
  condition: number;
  competition: number;
  buyer: number;
}

export interface ProfileScore {
  profile: ScoreProfileKey;
  value: number;
  components: ScoreComponents;
  risk: RiskKey;
  dropShip: DropShipKey;
  explanation: string;
  factors: { label: string; weight: number; contribution: number }[];
}