← 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";
}