← back to Govarbitrage

src/lib/digest-top.ts

33 lines

import { queryListings, type ListingRow } from "@/lib/listings";

// Shared "top ranked opportunities" selection — the single definition of how
// the Top-10 digest (scripts/send-digest.ts) and the skeptic agent pick the
// ranked head of the corpus, so the adversarial gate audits exactly what the
// digest is about to email.

export interface TopRankedOptions {
  limit: number;
  /**
   * When true (digest behavior) only still-open auctions (closingAt in the
   * future) are returned. The skeptic passes false so it can also catch
   * stale rows — closed auctions still ranked as if live.
   */
  openOnly?: boolean;
}

/** ACTIVE listings ranked by Overall Opportunity, best first. */
export async function topRankedOpportunities(opts: TopRankedOptions): Promise<ListingRow[]> {
  const { limit, openOnly = true } = opts;
  const { rows } = await queryListings({
    profile: "OVERALL_OPPORTUNITY",
    sort: "opportunityScore",
    dir: "desc",
    pageSize: Math.max(200, limit),
  });
  const now = Date.now();
  const pool = openOnly
    ? rows.filter((r) => r.closingAt && new Date(r.closingAt).getTime() > now)
    : rows;
  return pool.slice(0, limit);
}