← back to Govarbitrage

apps/mobile/lib/types.ts

245 lines

// TypeScript types derived directly from the GovArbitrage API response shapes.
// Source: src/lib/listings.ts (ListingRow), src/prisma/schema.prisma,
//         src/app/api/listings/route.ts, src/lib/listing-detail.ts

// ── Enum mirrors ─────────────────────────────────────────────────────────────

export type AuctionSource =
  | "GOVDEALS"
  | "PUBLIC_SURPLUS"
  | "GSA_AUCTIONS"
  | "COUNTY"
  | "STATE_SURPLUS"
  | "UNIVERSITY_SURPLUS"
  | "MUNICIBID"
  | "BID4ASSETS"
  | "GOINDUSTRY"
  | "NETWORK_INTL"
  | "GRAYS_AU"
  | "GOVPLANET"
  | "CSV"
  | "EXTENSION"
  | "OTHER";

export type Condition = "NEW" | "LIKE_NEW" | "USED_GOOD" | "USED_FAIR" | "FOR_PARTS" | "UNKNOWN";
export type RiskLevel = "LOW" | "MEDIUM" | "HIGH";
export type DropShipFeasibility = "EASY" | "MODERATE" | "DIFFICULT" | "INFEASIBLE";
export type ResearchStatus = "PENDING" | "QUEUED" | "IN_PROGRESS" | "COMPLETE" | "FAILED";
export type ListingStatus = "ACTIVE" | "ENDED" | "REMOVED";
export type Tier = "FREE" | "STANDARD" | "PREMIUM";
export type ScoreProfile =
  | "OVERALL_OPPORTUNITY"
  | "BEST_ARBITRAGE"
  | "QUICK_FLIP"
  | "COLLECTOR"
  | "LOCAL_PICKUP"
  | "EASY_FREIGHT"
  | "PARTS_ONLY"
  | "HIGH_CONFIDENCE"
  | "HIGH_PROFIT";

// ── Flat listing row (from GET /api/listings) ────────────────────────────────

export interface ListingRow {
  id: string;
  source: AuctionSource;
  sourceAuctionId: string;
  sourceUrl: string | null;
  title: string;
  category: string | null;
  manufacturer: string | null;
  model: string | null;
  condition: Condition;
  quantity: number;
  currentBid: number;
  currentCost: number; // bid + premium + tax
  /** Null for FREE tier — money-math gate */
  recommendedMaxBid: number | null;
  retailLow: number | null;
  retailAverage: number | null;
  retailHigh: number | null;
  usedLow: number | null;
  usedAverage: number | null;
  usedHigh: number | null;
  wholesale: number | null;
  liquidation: number | null;
  sellNow: number | null;
  value7Day: number | null;
  value30Day: number | null;
  value90Day: number | null;
  expectedSale: number | null;
  shipping: number;
  freight: number;
  repairs: number;
  marketplaceFees: number;
  netProfit: number | null;
  roi: number | null; // decimal ratio e.g. 0.42 = 42%
  risk: RiskLevel;
  confidence: number | null; // 0..100
  opportunityScore: number | null;
  arbitrageScore: number | null;
  demandScore: number | null;
  velocityScore: number | null;
  logisticsScore: number | null;
  conditionScore: number | null;
  competitionScore: number | null;
  buyerScore: number | null;
  dropShip: DropShipFeasibility;
  closingAt: string | null; // ISO8601
  researchStatus: ResearchStatus;
  imageUrl: string | null;
  locationCity: string | null;
  locationState: string | null;
  // Now returned on list rows too (backend flattenListing), so the "Newest"
  // sort and the admin created-date chip work on the grid, not just detail.
  createdAt: string;
}

// ── Paginated list response ───────────────────────────────────────────────────

export interface ListingsResponse {
  rows: ListingRow[];
  total: number;
  page: number;
  pageSize: number;
  tier: Tier;
  gated: boolean;
}

// ── Detail response (GET /api/listings/:id) ───────────────────────────────────

export interface Research {
  id: string;
  newRetail: number | null;
  newReplacement: number | null;
  avgRetail: number | null;
  usedSoldPrice: number | null;
  usedAskingPrice: number | null;
  usedLow: number | null;
  usedHigh: number | null;
  wholesaleValue: number | null;
  liquidationValue: number | null;
  sellTodayValue: number | null;
  value7Day: number | null;
  value30Day: number | null;
  value90Day: number | null;
  expectedSalePrice: number | null;
  probabilityOfSale: number | null; // 0..1
  daysUntilSold: number | null;
  confidenceScore: number | null; // 0..100
  summary: string | null;
  createdAt: string;
}

export interface CostBreakdown {
  id: string;
  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; // decimal ratio
  annualizedReturn: number;
  recommendedMaxBid: number;
}

export interface Score {
  id: string;
  profile: ScoreProfile;
  value: number; // 0..100
  arbitrage: number;
  demand: number;
  velocity: number;
  logistics: number;
  condition: number;
  competition: number;
  buyer: number;
  risk: RiskLevel;
  dropShip: DropShipFeasibility;
  explanation: string;
}

export interface Comparable {
  id: string;
  kind: "SOLD" | "ACTIVE" | "RETAIL";
  title: string;
  price: number;
  url: string | null;
  source: string | null;
  soldAt: string | null;
}

export interface ListingDetail {
  id: string;
  source: AuctionSource;
  sourceAuctionId: string;
  sourceUrl: string | null;
  title: string;
  description: string | null;
  category: string | null;
  manufacturer: string | null;
  model: string | null;
  serialNumber: string | null;
  condition: Condition;
  quantity: number;
  accessories: string | null;
  missingParts: string | null;
  weightLbs: number | null;
  dimensions: string | null;
  locationCity: string | null;
  locationState: string | null;
  locationZip: string | null;
  currentBid: number;
  bidCount: number;
  closingAt: string | null;
  imageUrls: string[];
  researchStatus: ResearchStatus;
  listingStatus: ListingStatus;
  createdAt: string;
  updatedAt: string;
  research: Research | null;
  costBreakdown: CostBreakdown | null;
  scores: Score[];
  comparables: Comparable[];
  tier: Tier;
  gated: boolean;
}

// ── Query params ──────────────────────────────────────────────────────────────

export type SortField =
  | "opportunityScore"
  | "roi"
  | "netProfit"
  | "closingAt"
  | "currentBid"
  | "createdAt";

export interface ListingsQueryParams {
  search?: string;
  source?: AuctionSource;
  category?: string;
  condition?: Condition;
  risk?: RiskLevel;
  closingWithinHours?: number;
  sort?: SortField;
  dir?: "asc" | "desc";
  page?: number;
  pageSize?: number;
  profile?: ScoreProfile;
}