[object Object]

← back to Homesonspec

add collision-safe Florida Highland collector

669907fb14eb399ac40c1181fc55a4faf4c0e8c2 · 2026-08-30 15:07:12 -0700 · Steve

Files touched

Diff

commit 669907fb14eb399ac40c1181fc55a4faf4c0e8c2
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Aug 30 15:07:12 2026 -0700

    add collision-safe Florida Highland collector
---
 apps/workers/package.json                          |   1 +
 apps/workers/src/cli.ts                            |   2 +
 collectors/BUILDERS.md                             |   8 ++
 collectors/highland-homes-fl/.gitignore            |   1 +
 .../highland-homes-fl/fixtures/homes/_urls.tsv     |   8 ++
 .../highland-homes-fl/fixtures/homes/home-01.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-02.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-03.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-04.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-05.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-06.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-07.html  |   1 +
 .../highland-homes-fl/fixtures/homes/home-08.html  |   1 +
 collectors/highland-homes-fl/package.json          |  15 +++
 collectors/highland-homes-fl/src/index.ts          | 125 +++++++++++++++++++++
 collectors/highland-homes-fl/src/selftest.test.ts  |  24 ++++
 collectors/highland-homes-fl/tsconfig.json         |   1 +
 packages/database/prisma/seed-cpg-builders.ts      |   9 ++
 .../TK-10487-highland-homes-fl-e2e-proof.json      |  45 ++++++++
 19 files changed, 247 insertions(+)

diff --git a/apps/workers/package.json b/apps/workers/package.json
index f1aa7381..f02be79d 100644
--- a/apps/workers/package.json
+++ b/apps/workers/package.json
@@ -35,6 +35,7 @@
     "@homesonspec/collector-landsea-homes": "workspace:*",
     "@homesonspec/collector-ryan-homes": "workspace:*",
     "@homesonspec/collector-highland-homes": "workspace:*",
+    "@homesonspec/collector-highland-homes-fl": "workspace:*",
     "@homesonspec/collector-lgi-homes": "workspace:*",
     "@homesonspec/collector-shea-homes": "workspace:*",
     "@homesonspec/collector-century-communities": "workspace:*",
diff --git a/apps/workers/src/cli.ts b/apps/workers/src/cli.ts
index 51f546a3..306eaef4 100644
--- a/apps/workers/src/cli.ts
+++ b/apps/workers/src/cli.ts
@@ -15,6 +15,7 @@ import { meritageHomesAdapter } from "@homesonspec/collector-meritage-homes";
 import { landseaAdapter } from "@homesonspec/collector-landsea-homes";
 import { ryanHomesAdapter } from "@homesonspec/collector-ryan-homes";
 import { highlandHomesAdapter } from "@homesonspec/collector-highland-homes";
+import { highlandHomesFlAdapter } from "@homesonspec/collector-highland-homes-fl";
 import { lgiHomesAdapter } from "@homesonspec/collector-lgi-homes";
 import { sheaHomesAdapter } from "@homesonspec/collector-shea-homes";
 import { centuryAdapter } from "@homesonspec/collector-century-communities";
@@ -66,6 +67,7 @@ const ADAPTERS = {
   [landseaAdapter.key]: landseaAdapter,
   [ryanHomesAdapter.key]: ryanHomesAdapter,
   [highlandHomesAdapter.key]: highlandHomesAdapter,
+  [highlandHomesFlAdapter.key]: highlandHomesFlAdapter,
   [lgiHomesAdapter.key]: lgiHomesAdapter,
   [sheaHomesAdapter.key]: sheaHomesAdapter,
   [centuryAdapter.key]: centuryAdapter,
diff --git a/collectors/BUILDERS.md b/collectors/BUILDERS.md
index f413b815..b376cc21 100644
--- a/collectors/BUILDERS.md
+++ b/collectors/BUILDERS.md
@@ -233,3 +233,11 @@ identity/address/geo/price while the primary HomeOverview block supplies structu
 self-card supplies construction status without reading sibling cards. Four model homes retain honest
 null prices, and source-absent garage/lot/completion remain null. No seed execution, ingest, activation,
 deploy, DNS, or remote push was performed.
+
+Highland Homes Florida is fixture-backed and locally registered under the collision-safe
+`highland-homes-fl-site` key. Its authoritative domain is `highlandhomes.org`: the brand's own About
+page says it joined Clayton Properties Group in 2019, its current brochure says "A Clayton Company",
+and its Organization JSON-LD identifies Lakeland, Florida. Eight first-party per-home JSON-LD fixture
+excerpts produce eight distinct Florida addresses and inventory IDs with complete core facts. The
+existing `highland-homes-site` Texas collector (`highlandhomes.com`) remains unchanged. No seed
+execution, ingest, activation, deploy, DNS, or remote push was performed.
diff --git a/collectors/highland-homes-fl/.gitignore b/collectors/highland-homes-fl/.gitignore
new file mode 100644
index 00000000..3c3629e6
--- /dev/null
+++ b/collectors/highland-homes-fl/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/collectors/highland-homes-fl/fixtures/homes/_urls.tsv b/collectors/highland-homes-fl/fixtures/homes/_urls.tsv
new file mode 100644
index 00000000..f7f73b00
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/_urls.tsv
@@ -0,0 +1,8 @@
+home-01.html	https://www.highlandhomes.org/new-homes/florida/wildwood-the-villages/wildwood/woodland-crossings/begonia/wc1-00-133
+home-02.html	https://www.highlandhomes.org/new-homes/florida/greater-orlando/st-cloud/the-crossings-single-family-homes/everly/cr2-00-588
+home-03.html	https://www.highlandhomes.org/new-homes/florida/ocala/ocala/falls-of-ocala/aubrey-ii-55-plus/fal-00-058
+home-04.html	https://www.highlandhomes.org/new-homes/florida/ocala/ocala/copperleaf/serendipity/cpl-0b-026
+home-05.html	https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/winter-haven/villamar/aurora/vm6-00-078
+home-06.html	https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/winter-haven/villamar/serendipity/vm6-00-075
+home-07.html	https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/lake-alfred/the-lakes/ryleigh/la6-00-126
+home-08.html	https://www.highlandhomes.org/new-homes/florida/ocala/ocala/falls-of-ocala/shelby/fal-00-061
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-01.html b/collectors/highland-homes-fl/fixtures/homes/home-01.html
new file mode 100644
index 00000000..251a6ecf
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-01.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","name":"8668 Wildflower Field Lane, Wildwood, FL 34785","additionalProperty":[{"name":"Bedrooms","value":3},{"name":"Bathrooms","value":2},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":1486}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":268900,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/wildwood-the-villages/wildwood/woodland-crossings/begonia/wc1-00-133#inventory","name":"Begonia at 8668 Wildflower Field Lane","address":{"@type":"PostalAddress","streetAddress":"8668 Wildflower Field Lane","addressLocality":"Wildwood","addressRegion":"FL","postalCode":34785},"geo":{"latitude":28.883515,"longitude":-82.086529},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/wildwood-the-villages/wildwood/woodland-crossings"}},{"@type":"Organization","name":"Highland Homes","url":"https://www.highlandhomes.org","address":{"addressLocality":"Lakeland","addressRegion":"FL"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-02.html b/collectors/highland-homes-fl/fixtures/homes/home-02.html
new file mode 100644
index 00000000..49dcbfa7
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-02.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":3},{"name":"Bathrooms","value":3},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":2410}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":474900,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/greater-orlando/st-cloud/the-crossings-single-family-homes/everly/cr2-00-588#inventory","name":"Everly at 668 Cypress Creek Run","address":{"streetAddress":"668 Cypress Creek Run","addressLocality":"St. Cloud","addressRegion":"FL","postalCode":34771},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/greater-orlando/st-cloud/the-crossings-single-family-homes"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-03.html b/collectors/highland-homes-fl/fixtures/homes/home-03.html
new file mode 100644
index 00000000..96f0ec20
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-03.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":2},{"name":"Bathrooms","value":2.5},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":1931}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":331900,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/ocala/ocala/falls-of-ocala/aubrey-ii-55-plus/fal-00-058#inventory","name":"Aubrey II 55 Plus at 7636 SW 6th Loop","address":{"streetAddress":"7636 SW 6th Loop","addressLocality":"Ocala","addressRegion":"FL","postalCode":34474},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/ocala/ocala/falls-of-ocala"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-04.html b/collectors/highland-homes-fl/fixtures/homes/home-04.html
new file mode 100644
index 00000000..717e7007
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-04.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":3},{"name":"Bathrooms","value":2},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":2005}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":345360,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/ocala/ocala/copperleaf/serendipity/cpl-0b-026#inventory","name":"Serendipity at 7473 SW 107th Lane","address":{"streetAddress":"7473 SW 107th Lane","addressLocality":"Ocala","addressRegion":"FL","postalCode":34476},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/ocala/ocala/copperleaf"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-05.html b/collectors/highland-homes-fl/fixtures/homes/home-05.html
new file mode 100644
index 00000000..06f52bca
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-05.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":3},{"name":"Bathrooms","value":2},{"name":"Stories","value":1},{"name":"Garage Spaces","value":1},{"name":"Total Square Feet","value":1358}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":287355,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/winter-haven/villamar/aurora/vm6-00-078#inventory","name":"Aurora at 4490 Rapallo Avenue","address":{"streetAddress":"4490 Rapallo Avenue","addressLocality":"Winter Haven","addressRegion":"FL","postalCode":33884},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/winter-haven/villamar"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-06.html b/collectors/highland-homes-fl/fixtures/homes/home-06.html
new file mode 100644
index 00000000..6fb24376
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-06.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":4},{"name":"Bathrooms","value":2},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":2005}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":340990,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/winter-haven/villamar/serendipity/vm6-00-075#inventory","name":"Serendipity at 4502 Rapallo Avenue","address":{"streetAddress":"4502 Rapallo Avenue","addressLocality":"Winter Haven","addressRegion":"FL","postalCode":33884},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/winter-haven/villamar"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-07.html b/collectors/highland-homes-fl/fixtures/homes/home-07.html
new file mode 100644
index 00000000..d17eec8d
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-07.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":3},{"name":"Bathrooms","value":2},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":1878}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":405900,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/lake-alfred/the-lakes/ryleigh/la6-00-126#inventory","name":"Ryleigh at 1496 Melrose Street","address":{"streetAddress":"1496 Melrose Street","addressLocality":"Lake Alfred","addressRegion":"FL","postalCode":33850},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/lakeland-winter-haven/lake-alfred/the-lakes"}}]}</script>
diff --git a/collectors/highland-homes-fl/fixtures/homes/home-08.html b/collectors/highland-homes-fl/fixtures/homes/home-08.html
new file mode 100644
index 00000000..a0380f26
--- /dev/null
+++ b/collectors/highland-homes-fl/fixtures/homes/home-08.html
@@ -0,0 +1 @@
+<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"RealEstateListing","additionalProperty":[{"name":"Bedrooms","value":4},{"name":"Bathrooms","value":2.5},{"name":"Stories","value":1},{"name":"Garage Spaces","value":2},{"name":"Total Square Feet","value":2029}],"offers":{"@id":"#offer"}},{"@id":"#offer","@type":"Offer","price":307900,"availability":"https://schema.org/InStock"},{"@type":"SingleFamilyResidence","@id":"https://www.highlandhomes.org/new-homes/florida/ocala/ocala/falls-of-ocala/shelby/fal-00-061#inventory","name":"Shelby at 7618 SW 6th Loop","address":{"streetAddress":"7618 SW 6th Loop","addressLocality":"Ocala","addressRegion":"FL","postalCode":34474},"containedInPlace":{"url":"https://www.highlandhomes.org/new-homes/florida/ocala/ocala/falls-of-ocala"}}]}</script>
diff --git a/collectors/highland-homes-fl/package.json b/collectors/highland-homes-fl/package.json
new file mode 100644
index 00000000..7361b87f
--- /dev/null
+++ b/collectors/highland-homes-fl/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "@homesonspec/collector-highland-homes-fl",
+  "version": "0.1.0",
+  "private": true,
+  "type": "module",
+  "main": "./src/index.ts",
+  "types": "./src/index.ts",
+  "scripts": { "test": "vitest run", "typecheck": "tsc --noEmit" },
+  "dependencies": {
+    "@homesonspec/collectors-common": "workspace:*",
+    "@homesonspec/schemas": "workspace:*",
+    "@homesonspec/shared": "workspace:*"
+  },
+  "devDependencies": { "typescript": "^5.7.2", "vitest": "^4.0.0", "@types/node": "^22.10.5" }
+}
diff --git a/collectors/highland-homes-fl/src/index.ts b/collectors/highland-homes-fl/src/index.ts
new file mode 100644
index 00000000..f72c88bd
--- /dev/null
+++ b/collectors/highland-homes-fl/src/index.ts
@@ -0,0 +1,125 @@
+import type { ExtractedRecord, FieldValue } from "@homesonspec/schemas";
+import { normalizeStateCode } from "@homesonspec/shared";
+import { fetchFixtures, LiveFetcher, type ExtractionOutput, type FetchContext, type RawPage, type SourceAdapter } from "@homesonspec/collectors-common";
+
+/**
+ * Florida Highland Homes (highlandhomes.org), deliberately separate from the
+ * existing Texas highlandhomes.com collector. Highland's own About page says it
+ * joined Clayton Properties Group in 2019; its current brochure calls it "A
+ * Clayton Company" and its Organization JSON-LD anchors it at Lakeland, FL.
+ *
+ * Source: ordinary GET of the public sitemap and per-home pages. robots.txt
+ * allows these paths (only /team-portal, /my-favorites and /ajax are disallowed).
+ * Facts come from the page's first-party JSON-LD graph: RealEstateListing,
+ * SingleFamilyResidence, Offer, Organization and BreadcrumbList. Images are
+ * intentionally omitted (mediaRights=NONE).
+ */
+const ORIGIN = "https://www.highlandhomes.org";
+const SITEMAP = `${ORIGIN}/sitemap.xml`;
+const BUILDER_SLUG = "highland-homes-fl";
+const PAGE_LIMIT = Number(process.env.HIGHLAND_FL_PAGE_LIMIT ?? "60");
+
+function fv<T>(value: T | null, raw: string | null, sourceUrl: string): FieldValue<T> {
+  return { value, raw, evidenceText: raw, sourceUrl, confidence: value === null ? 0 : 1 };
+}
+const clean = (v: unknown): string | null => v == null ? null : String(v).replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim() || null;
+const num = (v: unknown): number | null => { const n = typeof v === "number" ? v : Number(String(v ?? "").replace(/[^0-9.]/g, "")); return Number.isFinite(n) && n > 0 ? n : null; };
+const obj = (v: unknown): Record<string, unknown> | null => v && typeof v === "object" && !Array.isArray(v) ? v as Record<string, unknown> : null;
+const types = (o: Record<string, unknown>, t: string) => (Array.isArray(o["@type"]) ? o["@type"] : [o["@type"]]).includes(t);
+
+function jsonLd(html: string): Record<string, unknown>[] {
+  const out: Record<string, unknown>[] = [];
+  for (const m of html.matchAll(/<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/gi)) {
+    try {
+      const parsed = JSON.parse(m[1]!.trim());
+      for (const top of (Array.isArray(parsed) ? parsed : [parsed])) {
+        const graph = obj(top)?.["@graph"];
+        for (const node of (Array.isArray(graph) ? graph : [top])) if (obj(node)) out.push(node as Record<string, unknown>);
+      }
+    } catch { /* malformed JSON-LD is not evidence */ }
+  }
+  return out;
+}
+
+export type HighlandFlHome = {
+  street: string; city: string | null; state: string | null; zip: string | null;
+  community: string | null; planName: string | null; lotNumber: string | null;
+  price: number | null; beds: number | null; baths: number | null; sqft: number | null;
+  stories: number | null; garages: number | null; lat: number | null; lon: number | null;
+  status: "PLANNED" | "UNDER_CONSTRUCTION" | "MOVE_IN_READY" | null; id: string;
+};
+
+export function statusFromAvailability(value: unknown): HighlandFlHome["status"] {
+  const s = clean(value)?.toLowerCase() ?? "";
+  if (s.includes("instock") || s.includes("move-in ready") || s.includes("move in ready")) return "MOVE_IN_READY";
+  if (s.includes("under construction") || s.includes("preorder")) return "UNDER_CONSTRUCTION";
+  if (s.includes("coming soon") || s.includes("pre-sale")) return "PLANNED";
+  return null;
+}
+
+function prop(listing: Record<string, unknown>, name: string): number | null {
+  const props = Array.isArray(listing.additionalProperty) ? listing.additionalProperty : [];
+  const hit = props.map(obj).find((p) => clean(p?.name)?.toLowerCase() === name.toLowerCase());
+  return num(hit?.value);
+}
+
+export function parseHome(html: string, pageUrl: string): HighlandFlHome | null {
+  const nodes = jsonLd(html);
+  const listing = nodes.find((o) => types(o, "RealEstateListing"));
+  const residence = nodes.find((o) => types(o, "SingleFamilyResidence"));
+  if (!listing || !residence) return null;
+  const address = obj(residence.address);
+  const street = clean(address?.streetAddress);
+  if (!street) return null;
+  const state = normalizeStateCode(clean(address?.addressRegion));
+  if (state !== "FL") return null; // collision guard: this collector is Florida-only
+  const zip = clean(address?.postalCode)?.match(/\b3\d{4}\b/)?.[0] ?? null;
+  const offerRef = obj(listing.offers)?.["@id"];
+  const offer = nodes.find((o) => o["@id"] === offerRef) ?? obj(listing.offers);
+  const geo = obj(residence.geo);
+  const contained = obj(residence.containedInPlace);
+  const communityUrl = clean(contained?.url);
+  const community = communityUrl ? communityUrl.split("/").filter(Boolean).at(-1)!.split("-").map((x) => x[0]!.toUpperCase() + x.slice(1)).join(" ") : null;
+  const name = clean(residence.name);
+  const planName = name?.match(/^(.+?)\s+at\s+/i)?.[1] ?? null;
+  const id = clean(residence["@id"])?.replace(/#inventory$/, "").split("/").filter(Boolean).at(-1) ?? pageUrl.split("/").filter(Boolean).at(-1)!;
+  return {
+    street, city: clean(address?.addressLocality), state, zip, community, planName, lotNumber: id,
+    price: num(offer?.price), beds: prop(listing, "Bedrooms"), baths: prop(listing, "Bathrooms"),
+    sqft: prop(listing, "Total Square Feet") ?? num(obj(residence.floorSize)?.value),
+    stories: prop(listing, "Stories") ?? prop(residence, "Number of Stories"),
+    garages: prop(listing, "Garage Spaces") ?? prop(residence, "Garage Spaces"),
+    lat: num(geo?.latitude), lon: geo?.longitude == null ? null : Number(geo.longitude),
+    status: statusFromAvailability(offer?.availability ?? listing.description), id,
+  };
+}
+
+function isDetailUrl(url: string): boolean {
+  const path = new URL(url).pathname.split("/").filter(Boolean);
+  return path.length === 7 && path[0] === "new-homes" && path[1] === "florida" && !path.includes("brochure");
+}
+
+export const highlandHomesFlAdapter: SourceAdapter = {
+  key: "highland-homes-fl-site", version: "0.1.0",
+  async *fetch(ctx: FetchContext): AsyncIterable<RawPage> {
+    if (ctx.mode === "fixture") { yield* fetchFixtures(ctx); return; }
+    const fetcher = new LiveFetcher(ctx.registry);
+    const sitemap = await fetcher.fetch(SITEMAP);
+    const urls = [...sitemap.body.toString("utf8").matchAll(/<loc>\s*([^<]+)\s*<\/loc>/g)].map((m) => m[1]!).filter(isDetailUrl);
+    for (const url of urls.slice(0, PAGE_LIMIT)) try { yield await fetcher.fetch(url); } catch (error) { console.warn(`skip ${url}: ${String(error)}`); }
+  },
+  extract(page: RawPage): ExtractionOutput {
+    try {
+      const h = parseHome(page.body.toString("utf8"), page.url);
+      if (!h) return { records: [], errors: [] };
+      const cname = h.community ?? "Unknown";
+      const community: ExtractedRecord = { entityType: "community", canonicalHints: { builderSlug: BUILDER_SLUG, communityName: cname }, fields: {
+        name: fv(cname,cname,page.url), street:fv<string>(null,null,page.url), city:fv(h.city,h.city,page.url), state:fv(h.state,h.state,page.url), zip:fv(h.zip,h.zip,page.url), county:fv<string>(null,null,page.url), metro:fv<string>(null,null,page.url), lat:fv(h.lat,h.lat==null?null:String(h.lat),page.url), lon:fv(h.lon,h.lon==null?null:String(h.lon),page.url), hoaFeeMonthly:fv<number>(null,null,page.url), schoolDistrict:fv<string>(null,null,page.url), ageRestricted:fv<boolean>(null,null,page.url), salesPhone:fv<string>(null,null,page.url)
+      }};
+      const home: ExtractedRecord = { entityType: "inventory_home", canonicalHints: { builderSlug: BUILDER_SLUG, communityName:cname, address:h.street, builderInventoryId:h.id, planName:h.planName??undefined, lotNumber:h.lotNumber??undefined, lat:h.lat??undefined, lon:h.lon??undefined }, fields: {
+        street:fv(h.street,h.street,page.url), city:fv(h.city,h.city,page.url), state:fv(h.state,h.state,page.url), zip:fv(h.zip,h.zip,page.url), price:fv(h.price,h.price==null?null:String(h.price),page.url), beds:fv(h.beds,h.beds==null?null:String(h.beds),page.url), bathsTotal:fv(h.baths,h.baths==null?null:String(h.baths),page.url), sqft:fv(h.sqft,h.sqft==null?null:String(h.sqft),page.url), stories:fv(h.stories,h.stories==null?null:String(h.stories),page.url), garageSpaces:fv(h.garages,h.garages==null?null:String(h.garages),page.url), homeType:fv("SINGLE_FAMILY" as const,"SingleFamilyResidence",page.url), constructionStatus:fv(h.status,h.status,page.url), estCompletionDate:fv<string>(null,null,page.url), lotNumber:fv(h.lotNumber,h.lotNumber,page.url), builderInventoryId:fv(h.id,h.id,page.url), lat:fv(h.lat,h.lat==null?null:String(h.lat),page.url), lon:fv(h.lon,h.lon==null?null:String(h.lon),page.url), planName:fv(h.planName,h.planName,page.url), images:fv<string[]>([],null,page.url)
+      }};
+      return { records:[community,home], errors:[] };
+    } catch (error) { return { records:[], errors:[{url:page.url,reason:String(error)}] }; }
+  }
+};
diff --git a/collectors/highland-homes-fl/src/selftest.test.ts b/collectors/highland-homes-fl/src/selftest.test.ts
new file mode 100644
index 00000000..c04feb4e
--- /dev/null
+++ b/collectors/highland-homes-fl/src/selftest.test.ts
@@ -0,0 +1,24 @@
+import { readFileSync, readdirSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { describe, expect, it } from "vitest";
+import { validatePayload } from "@homesonspec/schemas";
+import { highlandHomesFlAdapter, parseHome, statusFromAvailability } from "./index";
+const FX=join(dirname(fileURLToPath(import.meta.url)),"..","fixtures","homes");
+const urls=Object.fromEntries(readFileSync(join(FX,"_urls.tsv"),"utf8").trim().split(/\r?\n/).map(x=>x.split("\t")));
+const files=readdirSync(FX).filter(x=>x.endsWith(".html")).sort();
+const page=(url:string,html:string)=>({url,retrievedAt:new Date().toISOString(),contentType:"text/html",body:Buffer.from(html),contentHash:"test"});
+describe("highland-homes-fl collision-safe fixture E2E",()=>{
+  it("parses eight distinct authoritative Florida inventory homes",()=>{
+    const homes=files.map(f=>parseHome(readFileSync(join(FX,f),"utf8"),urls[f]!));
+    expect(homes).toHaveLength(8); expect(homes.every(Boolean)).toBe(true);
+    const h=homes.filter(Boolean) as NonNullable<(typeof homes)[number]>[];
+    expect(new Set(h.map(x=>x.street)).size).toBe(8); expect(new Set(h.map(x=>x.id)).size).toBe(8);
+    for(const x of h){ expect(x.state).toBe("FL"); expect(x.zip).toMatch(/^3\d{4}$/); expect(x.price).toBeGreaterThan(200000); expect(x.beds).toBeGreaterThan(0); expect(x.baths).toBeGreaterThan(0); expect(x.sqft).toBeGreaterThan(1000); expect(x.garages).toBeGreaterThan(0); expect(x.status).toBe("MOVE_IN_READY"); }
+  });
+  it("emits schema-valid community+home records with Florida slug",()=>{
+    for(const f of files){ const html=readFileSync(join(FX,f),"utf8"); const out=highlandHomesFlAdapter.extract(page(urls[f]!,html)); expect(out.errors).toEqual([]); expect(out.records.map(x=>x.entityType)).toEqual(["community","inventory_home"]); for(const r of out.records){ expect(r.canonicalHints.builderSlug).toBe("highland-homes-fl"); expect(validatePayload(r).ok).toBe(true); } }
+  });
+  it("rejects Texas collision and non-detail pages",()=>{ const html=readFileSync(join(FX,files[0]!),"utf8"); expect(parseHome(html.replace('"addressRegion":"FL"','"addressRegion":"TX"'),urls[files[0]!]!)).toBeNull(); expect(parseHome("<html></html>","fixture://none")).toBeNull(); });
+  it("maps only explicit availability and never guesses",()=>{ expect(statusFromAvailability("https://schema.org/InStock")).toBe("MOVE_IN_READY"); expect(statusFromAvailability("Under Construction")).toBe("UNDER_CONSTRUCTION"); expect(statusFromAvailability("Coming Soon")).toBe("PLANNED"); expect(statusFromAvailability("Pending sale")).toBeNull(); });
+});
diff --git a/collectors/highland-homes-fl/tsconfig.json b/collectors/highland-homes-fl/tsconfig.json
new file mode 100644
index 00000000..e9bfc482
--- /dev/null
+++ b/collectors/highland-homes-fl/tsconfig.json
@@ -0,0 +1 @@
+{ "extends": "../../tsconfig.base.json", "compilerOptions": { "noEmit": true }, "include": ["src/**/*.ts"] }
diff --git a/packages/database/prisma/seed-cpg-builders.ts b/packages/database/prisma/seed-cpg-builders.ts
index 9f9d23ae..a825b08d 100644
--- a/packages/database/prisma/seed-cpg-builders.ts
+++ b/packages/database/prisma/seed-cpg-builders.ts
@@ -123,6 +123,15 @@ const CPG_BRANDS: {
     termsUrl: "https://www.claytonhomes.com/legal/",
     coverage: "Idaho — Boise / Treasure Valley",
   },
+  {
+    // Separate from the existing Texas highland-homes collector/domain.
+    slug: "highland-homes-fl",
+    name: "Highland Homes Florida",
+    legalName: "Highland Homes",
+    website: "https://www.highlandhomes.org",
+    termsUrl: "https://www.highlandhomes.org/legal",
+    coverage: "Florida — Central, North Central, Southwest, and Tampa Bay",
+  },
 ];
 
 async function main() {
diff --git a/verification/TK-10487-highland-homes-fl-e2e-proof.json b/verification/TK-10487-highland-homes-fl-e2e-proof.json
new file mode 100644
index 00000000..2038752d
--- /dev/null
+++ b/verification/TK-10487-highland-homes-fl-e2e-proof.json
@@ -0,0 +1,45 @@
+{
+  "ticket": "TK-10487",
+  "collector": "highland-homes-fl-site",
+  "identity": {
+    "verdict": "verified",
+    "domain": "https://www.highlandhomes.org",
+    "primary_sources": [
+      "https://www.highlandhomes.org/about-us",
+      "https://www.highlandhomes.org/index.cfm?Method=Brochure.HomePlanList",
+      "https://www.highlandhomes.org/robots.txt",
+      "https://www.highlandhomes.org/sitemap.xml"
+    ],
+    "facts": [
+      "The official About page says Highland Homes joined Clayton Properties Group in 2019.",
+      "The official current brochure identifies Highland Homes as A Clayton Company.",
+      "Official Organization JSON-LD and contact page identify Lakeland, Florida headquarters.",
+      "Domain and Florida identity are distinct from the preserved Texas highlandhomes.com collector."
+    ]
+  },
+  "fixture_evidence": {
+    "count": 8,
+    "source": "compact first-party JSON-LD excerpts from public per-home pages",
+    "states": ["FL"],
+    "distinct_addresses": 8,
+    "distinct_inventory_ids": 8,
+    "media_rights": "NONE"
+  },
+  "gates": {
+    "seed_executed": false,
+    "ingest_executed": false,
+    "deployed": false,
+    "dns_changed": false,
+    "external_writes": false
+  },
+  "verification": {
+    "status": "PASS",
+    "fixture_suite": "4/4",
+    "collector_typecheck": "PASS",
+    "workers_typecheck": "PASS",
+    "database_typecheck": "PASS",
+    "schema_records": "16/16",
+    "collision_guard": "TX addressRegion rejects to null",
+    "git_diff_check": "PASS"
+  }
+}

← 78e4b4f1 add Berkeley Building CPG collector  ·  back to Homesonspec  ·  auto-data-snapshot: 2026-08-30T20:54:26 (1 data files) — pnp c57211a8 →