← back to Homesonspec

collectors/elite-homes/src/selftest.test.ts

333 lines

import { readFileSync, readdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { validatePayload } from "@homesonspec/schemas";
import { eliteHomesAdapter, parseHome, statusFromLead } from "./index";

/**
 * Bounded, read-only self-test for the Elite Homes adapter. Runs `parseHome` +
 * `eliteHomesAdapter.extract()` over the fixtures captured (rate-limited, honest
 * UA, --compressed) from elitebuilthomes.com on 2026-08-30, proves facts-only
 * coverage, that addresses AND ids are genuinely per-home (not aggregate), that
 * honest-null fields stay null (never fabricated, asserted directly on the real
 * fixtures), that status is derived only from real page evidence, and that every
 * emitted record validates against the published schema.
 *
 * REAL INVENTORY: elitebuilthomes.com's sitemap lists exactly TEN per-home detail
 * pages (all Louisville-metro KY). The floor is therefore the REAL count (10), NOT
 * a padded number. N=10 makes the distinct-address / distinct-id assertions a
 * genuine aggregate-vs-detail guard (unlike the sister Silverthorne's N=1).
 *
 * SELECTOR DRIFT documented in the field-specific tests below:
 *   - garageSpaces is an HONEST NULL — Elite's HomeOverview list omits garage
 *     (same as Arbor; unlike Silverthorne which publishes "N-Car"). Asserted null.
 *   - constructionStatus comes from the authoritative Carousel_h2Lead BANNER
 *     ("Move-In Ready" / "Quick Move-In" -> MOVE_IN_READY), present on EVERY Elite
 *     detail page — so status is present on every captured home (asserted). The
 *     HomeOverview_lead prose is only a fallback. UNDER_CONSTRUCTION / PLANNED are
 *     exercised directly via statusFromLead (they fire on live homes with such
 *     phrasing); stripping BOTH sources drops status to an honest null.
 */

const HERE = dirname(fileURLToPath(import.meta.url));
const FX = join(HERE, "..", "fixtures", "homes");
// The real, honestly-reported available-inventory floor. Bump when the sitemap
// grows and more fixtures are captured — never pad it above the real count.
const REAL_HOME_FLOOR = 10;

function urlMap(): Record<string, string> {
  const tsv = readFileSync(join(FX, "_urls.tsv"), "utf8");
  const out: Record<string, string> = {};
  for (const line of tsv.split(/\r?\n/)) {
    const [file, url] = line.split("\t");
    if (file && url) out[file.trim()] = url.trim();
  }
  return out;
}

describe("elite-homes adapter — coverage + per-home + honest-nulls + schema", () => {
  const files = readdirSync(FX).filter((f) => f.endsWith(".html")).sort();
  const urls = urlMap();

  it("parses per-home facts with high coverage and distinct addresses + ids", () => {
    let n = 0;
    const cov = {
      price: 0, beds: 0, baths: 0, sqft: 0, stories: 0, address: 0, garage: 0,
      community: 0, plan: 0, lot: 0, id: 0, phone: 0, status: 0, geo: 0, zip: 0,
    };
    const addresses = new Set<string>();
    const ids = new Set<string>();

    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const h = parseHome(html, url);
      if (!h) continue;
      n++;
      if (h.street) {
        cov.address++;
        addresses.add(`${h.street}, ${h.city}, ${h.state}`);
      }
      if (h.builderInventoryId) {
        cov.id++;
        ids.add(h.builderInventoryId);
      }
      if (h.price != null) cov.price++;
      if (h.beds != null) cov.beds++;
      if (h.bathsTotal != null) cov.baths++;
      if (h.sqft != null) cov.sqft++;
      if (h.stories != null) cov.stories++;
      if (h.garages != null) cov.garage++;
      if (h.community) cov.community++;
      if (h.planName) cov.plan++;
      if (h.lotNumber) cov.lot++;
      if (h.phone) cov.phone++;
      if (h.status) cov.status++;
      if (h.lat != null && h.lon != null) cov.geo++;
      if (h.zip) cov.zip++;
    }

    // eslint-disable-next-line no-console
    console.log(
      `\n[elite-homes self-test] records=${n} distinctAddresses=${addresses.size} distinctIds=${ids.size}\n` +
        `  price=${pct(cov.price, n)}  beds=${pct(cov.beds, n)}  baths=${pct(cov.baths, n)}  ` +
        `sqft=${pct(cov.sqft, n)}  stories=${pct(cov.stories, n)}  garage=${pct(cov.garage, n)}  ` +
        `address=${pct(cov.address, n)}  zip=${pct(cov.zip, n)}  geo=${pct(cov.geo, n)}  ` +
        `community=${pct(cov.community, n)}  plan=${pct(cov.plan, n)}  lot=${pct(cov.lot, n)}  ` +
        `id=${pct(cov.id, n)}  phone=${pct(cov.phone, n)}  status=${pct(cov.status, n)}`,
    );

    // The REAL available-inventory floor (honest — the sitemap lists 10 homes).
    expect(n).toBeGreaterThanOrEqual(REAL_HOME_FLOOR);
    // Distinct street address AND builder id per record — a genuine
    // aggregate-vs-detail guard at N=10 (every home carries its OWN JSON-LD
    // address + productId; a regex that read an aggregate would collapse these).
    expect(addresses.size).toBe(n);
    expect(ids.size).toBe(n);
    // Facts-only coverage — every core structural field present on every captured
    // home (all sourced from the authoritative per-home JSON-LD + primary
    // HomeOverview).
    expect(cov.address).toBe(n);
    expect(cov.beds).toBe(n);
    expect(cov.baths).toBe(n);
    expect(cov.sqft).toBe(n);
    expect(cov.stories).toBe(n);
    expect(cov.geo).toBe(n);
    expect(cov.zip).toBe(n);
    expect(cov.community).toBe(n);
    expect(cov.plan).toBe(n);
    expect(cov.id).toBe(n);
    expect(cov.price).toBe(n);
    expect(cov.lot).toBe(n);
    expect(cov.phone).toBe(n);
    // GARAGE is an HONEST NULL on Elite (the HomeOverview list omits it) — must be
    // 0% coverage, never fabricated (the direct proof it is not guessed).
    expect(cov.garage).toBe(0);
    // STATUS: every Elite detail page carries a Carousel_h2Lead banner
    // ("Move-In Ready" / "Quick Move-In" -> MOVE_IN_READY), so status is present
    // on EVERY captured home. (TK-10487 Cody catch: a toothless <= n bound hid
    // that reading the marketing prose alone null'd 8/10 genuine MOVE_IN_READY.)
    expect(cov.status).toBe(n);
  });

  it("extracts the EXACT known facts of a captured home (value snapshot — catches misparses coverage can't)", () => {
    // Pin every field of home-01 (15967 Cumberland Lake Circle, Twin Lakes) to the
    // value verified against the live JSON-LD + HomeOverview, so any extraction
    // drift fails loudly even though coverage-% could still read 100% on garbage.
    const first = files[0]!;
    const h = parseHome(readFileSync(join(FX, first), "utf8"), urls[first] ?? "")!;
    expect(h.street).toBe("15967 Cumberland Lake Circle");
    expect(h.city).toBe("Louisville");
    expect(h.state).toBe("KY");
    expect(h.zip).toBe("40245");
    expect(h.community).toBe("Twin Lakes, Regal Series");
    expect(h.planName).toBe("The Hamilton");
    expect(h.lotNumber).toBe("1");
    expect(h.price).toBe(689900);
    expect(h.beds).toBe(5);
    expect(h.bathsTotal).toBe(4.5);
    expect(h.sqft).toBe(3780);
    expect(h.stories).toBe(2);
    expect(h.garages).toBeNull(); // honest null — Elite publishes no garage field
    expect(h.status).toBe("MOVE_IN_READY"); // home-01 Carousel banner = "Move-In Ready" (pins the Cody fix)
    expect(h.builderInventoryId).toBe("65b41e535c5d2c57a900586f");
    expect(h.lat).toBeCloseTo(38.2648, 2);
    expect(h.lon).toBeCloseTo(-85.4681, 2);
  });

  it("honest-null fields (garageSpaces, estCompletionDate) stay null on the real fixtures — never fabricated", () => {
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const { records } = eliteHomesAdapter.extract(mkPage(url, html));
      const home = records.find((r) => r.entityType === "inventory_home");
      expect(home).toBeDefined();
      const f = home!.fields as Record<string, { value: unknown }>;
      // garageSpaces is a genuine honest-null — Elite's HomeOverview list omits
      // garage — so it MUST be null on every real fixture (not a fabricated 0).
      expect(f["garageSpaces"]!.value).toBeNull();
      // estCompletionDate is a genuine honest-null — the page publishes no per-home
      // completion date.
      expect(f["estCompletionDate"]!.value).toBeNull();
      // images is facts-only empty (mediaRights=NONE), never populated.
      expect(f["images"]!.value).toEqual([]);
    }
  });

  it("canonicalHints.address is the REAL street, never a 'Lot X' fallback or URL", () => {
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const page = mkPage(url, html);
      const { records } = eliteHomesAdapter.extract(page);
      const home = records.find((r) => r.entityType === "inventory_home");
      expect(home).toBeDefined();
      const addr = home!.canonicalHints.address ?? "";
      expect(addr).not.toMatch(/^lot\b/i);
      expect(addr).not.toMatch(/^https?:\/\//);
      expect(addr).toMatch(/\d/); // real street addresses carry a house number
      // And it must equal the parsed street exactly (no ternary drift).
      const h = parseHome(html, url);
      expect(addr).toBe(h!.street);
    }
  });

  it("confirms Elite is the Louisville-KENTUCKY CPG builder — every home is KY (metro footprint KY/IN/OH)", () => {
    const states = new Set<string>();
    let sawKY = false;
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const h = parseHome(html, url);
      if (h?.state) {
        states.add(h.state);
        if (h.state === "KY") sawKY = true;
      }
    }
    // Only Elite's real operating-metro states may appear — a sanity check that
    // we're on the CPG Louisville Elite Homes.
    for (const s of states) expect(["KY", "IN", "OH"]).toContain(s);
    // The captured (Louisville) inventory must include at least one KY home.
    expect(sawKY).toBe(true);
  });

  it("status is a valid enum-or-null derived from page evidence — honest-null when lead prose carries no phrase", () => {
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const h = parseHome(html, url)!;
      expect(["PLANNED", "UNDER_CONSTRUCTION", "MOVE_IN_READY", null]).toContain(h.status);
      // Whatever the status resolves to, it must survive extraction as-is into the
      // constructionStatus field (never coerced to a non-null stub).
      const { records } = eliteHomesAdapter.extract(mkPage(url, html));
      const home = records.find((r) => r.entityType === "inventory_home");
      expect((home!.fields as Record<string, { value: unknown }>)["constructionStatus"]!.value).toBe(h.status);
    }
    // Stripping BOTH status sources — the Carousel_h2Lead banner (authoritative)
    // AND the HomeOverview_lead prose (fallback) — from home-01 must drop status
    // to null while the home still parses (a DIRECT semantic-null assertion):
    // proves status is derived ONLY from real page evidence and never guessed.
    const first = files[0]!;
    const html = readFileSync(join(FX, first), "utf8");
    const url = urls[first]!;
    const stripped = html
      .replace(/class="Carousel_h2Lead/g, 'class="_gone_')
      .replace(/class="HomeOverview_lead/g, 'class="_gone_');
    const strippedHome = parseHome(stripped, url);
    expect(strippedHome?.status).toBeNull();
    expect(strippedHome?.street, "null-status home still needs a real street").toBeTruthy();
  });

  it("statusFromLead maps every branch — no dead MOVE_IN_READY/UNDER_CONSTRUCTION/PLANNED code", () => {
    // These branches fire on live Elite homes whose HomeOverview_lead carries the
    // phrasing — the captured inventory's generic luxury copy does not, so exercise
    // every branch directly to guarantee a mapping regression can't ship.
    expect(statusFromLead("this outstanding quick move-in opportunity")).toBe("MOVE_IN_READY");
    expect(statusFromLead("Move-In Ready")).toBe("MOVE_IN_READY");
    expect(statusFromLead("Available Now")).toBe("MOVE_IN_READY");
    expect(statusFromLead("This home is Under Construction")).toBe("UNDER_CONSTRUCTION");
    expect(statusFromLead("currently being built")).toBe("UNDER_CONSTRUCTION");
    expect(statusFromLead("Coming Soon")).toBe("PLANNED");
    expect(statusFromLead("To Be Built")).toBe("PLANNED");
    expect(statusFromLead("Presale")).toBe("PLANNED");
    // Elite's actual captured copy → honest null (no recognizable phrase).
    expect(statusFromLead("Luxury New Construction in Prospect – Elevated Design with Exceptional Finishes")).toBeNull();
    expect(statusFromLead("LAST REMAINING HOME IN THE TWIN LAKES COMMUNITY!")).toBeNull();
    // Unrecognized / absent -> honest null, never a guess.
    expect(statusFromLead("a beautiful two-story home in a peaceful community")).toBeNull();
    expect(statusFromLead("")).toBeNull();
    expect(statusFromLead(null)).toBeNull();
  });

  it("a home with no offers[].price would carry a SEMANTIC null price (never a stub)", () => {
    // Every captured Elite home IS priced; assert the price is a real integer, AND
    // prove that a home stripped of its offers[].price resolves to a real semantic
    // null (not a fabricated 0 / stub) — the honest-null discipline.
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const h = parseHome(html, url)!;
      expect(Number.isInteger(h.price!)).toBe(true);
      expect(h.price!).toBeGreaterThan(50_000);
      // Strip the offers price integer → posInt(null) → honest null.
      const noPrice = html.replace(/"price"\s*:\s*\d+/g, '"price":null');
      const hNull = parseHome(noPrice, url)!;
      expect(hNull.price).toBeNull();
      // And the extracted field is a direct semantic null, not a $0 stub.
      const { records } = eliteHomesAdapter.extract(mkPage(url, noPrice));
      const home = records.find((r) => r.entityType === "inventory_home");
      expect((home!.fields as Record<string, { value: unknown }>)["price"]!.value).toBeNull();
    }
  });

  it("geo comes from real sources (US KY/IN/OH bounding box)", () => {
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const h = parseHome(html, url)!;
      // Geo is present on every home (JSON-LD GeoCoordinates) and must sit in a
      // Louisville-metro bounding box — proves real coords, not placeholders.
      expect(h.lat!).toBeGreaterThan(37);
      expect(h.lat!).toBeLessThan(40);
      expect(h.lon!).toBeLessThan(-84);
      expect(h.lon!).toBeGreaterThan(-87);
    }
  });

  it("emits schema-valid community + inventory_home records", () => {
    let homes = 0;
    for (const file of files) {
      const html = readFileSync(join(FX, file), "utf8");
      const url = urls[file] ?? `fixture://${file}`;
      const page = mkPage(url, html);
      const { records, errors } = eliteHomesAdapter.extract(page);
      expect(errors).toEqual([]);
      for (const rec of records) {
        if (rec.entityType === "inventory_home") homes++;
        const result = validatePayload(rec);
        if (!result.ok) {
          // eslint-disable-next-line no-console
          console.error(`validation failed for ${rec.entityType} @ ${url}`, result);
        }
        expect(result.ok).toBe(true);
      }
    }
    expect(homes).toBeGreaterThanOrEqual(REAL_HOME_FLOOR);
  });
});

function mkPage(url: string, html: string) {
  return {
    url,
    retrievedAt: new Date().toISOString(),
    contentType: "text/html",
    body: Buffer.from(html),
    contentHash: "test",
  };
}

function pct(a: number, b: number): string {
  return b ? `${Math.round((100 * a) / b)}%` : "n/a";
}