← back to Nationalrealestate

src/ingest/listings/types.ts

40 lines

/**
 * M-B3 firm↔listing pilot types.
 *
 * HARD LEGAL RAIL: a ListingFacts row carries FACTS ONLY. No photo URLs, no agent
 * remarks, no marketing description — those are copyrighted and must never be stored.
 * Every row keeps `url` (the broker's own listing page) so we link out, never present
 * a listing as ours.
 */

export interface ListingFacts {
  sourceId: string; // stable per-listing id from the source (URL token / MLS#)
  url: string; // canonical listing URL on the broker's own site
  address: string | null;
  city: string | null;
  state: string | null; // 2-letter, used for firm + region crosswalk
  price: number | null;
  beds: number | null;
  baths: number | null;
  sqft: number | null;
  lat: number | null;
  lng: number | null;
  status: string | null; // 'active' | 'sold' | 'pending' | null
}

export interface ListingAdapter {
  /** stored in listing.source and ingest_runs.source */
  source: string;
  /** host used to resolve firm_id via firm_site.url domain match */
  host: string;
  /** robots.txt path the adapter reads listings under — used for the honor check */
  listingsPathHint: string;
  /**
   * Yield up to `cap` listing PDP URLs from the site's public sitemap/index.
   * Each URL must be robots-allowed (the engine re-checks before fetch).
   */
  discover(cap: number): Promise<string[]>;
  /** Fetch one listing URL and extract FACTS ONLY (JSON-LD). null = unparseable, skip. */
  parse(url: string, html: string): ListingFacts | null;
}