[object Object]

← back to Homesonspec

david-weekley: capture city-gated community MapPoint coord (yolo iter-6)

de09b35174d70d47989f2324a817620f0fa373dc · 2026-07-29 02:42:15 -0700 · Steve Abrams

Adds communityGeo() that extracts the community coord from the page JSON,
strictly distinguishing it from the two decoy coords each DW page carries:
- REJECTS the warranty/regional-HQ coord (MapCenter, sibling of WarrantyEmail)
- REJECTS regional sales-office coords (MapPoint inside Offices[]) via a
  city-equality gate — one office serves a whole metro (all Phoenix homes ->
  one Tempe office), so its city never matches the home's city
- ACCEPTS only a community object (Grouping + Geolocation + Address + MapPoint)
  whose MapPoint.Address.City/State equals the home's city+state, plus a coarse
  US bbox. A wrong pin is worse than null.

The community coord exists only on per-community DETAIL pages; the per-metro
homes-ready-soon LIST pages this adapter fetches carry only warranty + office
coords, so on those it returns null (no wrong pins). Verified against a cached
list snapshot (null for all cities) and a live detail page (correct San Tan
Valley pin 33.217,-111.539; Tempe office + wrong-city both rejected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit de09b35174d70d47989f2324a817620f0fa373dc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 29 02:42:15 2026 -0700

    david-weekley: capture city-gated community MapPoint coord (yolo iter-6)
    
    Adds communityGeo() that extracts the community coord from the page JSON,
    strictly distinguishing it from the two decoy coords each DW page carries:
    - REJECTS the warranty/regional-HQ coord (MapCenter, sibling of WarrantyEmail)
    - REJECTS regional sales-office coords (MapPoint inside Offices[]) via a
      city-equality gate — one office serves a whole metro (all Phoenix homes ->
      one Tempe office), so its city never matches the home's city
    - ACCEPTS only a community object (Grouping + Geolocation + Address + MapPoint)
      whose MapPoint.Address.City/State equals the home's city+state, plus a coarse
      US bbox. A wrong pin is worse than null.
    
    The community coord exists only on per-community DETAIL pages; the per-metro
    homes-ready-soon LIST pages this adapter fetches carry only warranty + office
    coords, so on those it returns null (no wrong pins). Verified against a cached
    list snapshot (null for all cities) and a live detail page (correct San Tan
    Valley pin 33.217,-111.539; Tempe office + wrong-city both rejected).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 collectors/david-weekley/src/index.ts | 53 +++++++++++++++++++++++++++++++++--
 1 file changed, 50 insertions(+), 3 deletions(-)

diff --git a/collectors/david-weekley/src/index.ts b/collectors/david-weekley/src/index.ts
index 8aba17a8..40e5b692 100644
--- a/collectors/david-weekley/src/index.ts
+++ b/collectors/david-weekley/src/index.ts
@@ -65,6 +65,45 @@ function featureValue(block: string, featureClass: string): string | null {
   return m ? m[1]!.replace(/\s+/g, " ").trim() : null;
 }
 
+/**
+ * COMMUNITY coordinate from the page JSON — city-gated, warranty-safe. [yolo iter-6]
+ *
+ * DW pages embed several distinct coords and picking the wrong one wrong-pins a home
+ * tens of miles from its city (worse than null), so this only trusts the ONE coord we
+ * can prove is the home's own community:
+ *   - REJECTED: the warranty/regional-HQ coord — it lives in a `"MapCenter":{...}` sibling
+ *     of `WarrantyEmail`/`WarrantyPhone`/`AfterHoursPhone`. Never a `MapCenter`.
+ *   - REJECTED: the regional SALES-OFFICE coords — `"MapPoint"` objects nested inside an
+ *     `"Offices":[{OfficeType,Address,DrivingDirections,MapImage,PhoneNumber,MapPoint}]`
+ *     array. One office serves a whole metro (e.g. all Phoenix homes → one Tempe office),
+ *     so its coord is 10-40mi from most homes. Its Address.City is the office city, which
+ *     will NOT match the home's city — the city-gate below rejects it.
+ *   - ACCEPTED: the community coord — a community object carrying `"Grouping":"<name>"` +
+ *     `"Geolocation":N` + an `Address` whose City/StateAbbreviation is the COMMUNITY's own
+ *     city, immediately followed by `"MapPoint":{Latitude,Longitude}`. This only appears on
+ *     the per-community DETAIL page (the per-metro `homes-ready-soon` LIST pages carry only
+ *     the warranty + office coords, so this returns null there — correct, no wrong pin).
+ *
+ * Gate: the matched community MapPoint's OWN Address.City+State must equal the home's
+ * city+state, AND the coord must sit in a coarse US bbox. City-equality is the strong gate
+ * that distinguishes the community coord from every regional-office coord on the same page.
+ */
+function communityGeo(html: string, city: string | null, state: string | null): { lat: number; lon: number } | null {
+  if (!city || !state) return null;
+  const re = /"Grouping":"[^"]*","Geolocation":\d+,"Address":\{"Line1":"[^"]*","Line2":[^,]*,"City":"([^"]*)","StateAbbreviation":"([^"]*)"[^}]*\},"MapPoint":\{"Latitude":(-?\d+(?:\.\d+)?),"Longitude":(-?\d+(?:\.\d+)?)/g;
+  const wantCity = city.toLowerCase().trim();
+  const wantState = state.toUpperCase().trim();
+  for (const m of html.matchAll(re)) {
+    if (m[1]!.toLowerCase().trim() !== wantCity) continue; // office/HQ coord → City mismatch → reject
+    if (m[2]!.toUpperCase().trim() !== wantState) continue;
+    const lat = Number(m[3]), lon = Number(m[4]);
+    if (!Number.isFinite(lat) || !Number.isFinite(lon) || lat === 0 || lon === 0) continue;
+    if (lat < 15 || lat > 72 || lon < -180 || lon > -60) continue; // coarse US bbox (lon must be west)
+    return { lat, lon };
+  }
+  return null;
+}
+
 /** MOVE_IN_READY when "Ready Now"; else UNDER_CONSTRUCTION (dated) */
 function statusOf(label: string | null): "MOVE_IN_READY" | "UNDER_CONSTRUCTION" {
   if (label && /ready\s*now/i.test(label)) return "MOVE_IN_READY";
@@ -203,12 +242,17 @@ export const davidWeekleyAdapter: SourceAdapter = {
       if (homes.length === 0) return { records: [], errors: [] }; // market page with no listed QMIs
 
       const records: ExtractedRecord[] = [];
-      // one community record per distinct community on the page (facts-only; geo is a follow-up)
+      // Community coord (city-gated, warranty/office-safe) — present on per-community DETAIL
+      // pages, null on the per-metro LIST pages (they carry only warranty + office coords). A
+      // home inherits its own community's coord only. [yolo iter-6]
+      const cgeoFor = (city: string | null, state: string | null) => communityGeo(html, city, state);
+      // one community record per distinct community on the page (facts-only)
       const communitiesSeen = new Set<string>();
       for (const h of homes) {
         const cname = h.community ?? "Unknown";
         if (!communitiesSeen.has(cname)) {
           communitiesSeen.add(cname);
+          const cg = cgeoFor(h.city, h.state);
           records.push({
             entityType: "community",
             canonicalHints: { builderSlug: BUILDER_SLUG, communityName: cname },
@@ -220,8 +264,8 @@ export const davidWeekleyAdapter: SourceAdapter = {
               zip: fv(h.zip, h.zip, page.url),
               county: fv<string>(null, null, page.url),
               metro: fv<string>(null, null, page.url),
-              lat: fv<number>(null, null, page.url),
-              lon: fv<number>(null, null, page.url),
+              lat: fv(cg?.lat ?? null, cg ? String(cg.lat) : null, page.url, cg ? "community MapPoint (city-gated)" : null),
+              lon: fv(cg?.lon ?? null, cg ? String(cg.lon) : null, page.url, cg ? "community MapPoint (city-gated)" : null),
               hoaFeeMonthly: fv<number>(null, null, page.url),
               schoolDistrict: fv<string>(null, null, page.url),
               ageRestricted: fv<boolean>(null, null, page.url),
@@ -233,6 +277,7 @@ export const davidWeekleyAdapter: SourceAdapter = {
 
       for (const h of homes) {
         if (!h.street) continue;
+        const cg = cgeoFor(h.city, h.state); // home inherits its own community's city-gated coord (null on list pages)
         records.push({
           entityType: "inventory_home",
           canonicalHints: {
@@ -247,6 +292,8 @@ export const davidWeekleyAdapter: SourceAdapter = {
             city: fv(h.city, h.city, page.url),
             state: fv(h.state, h.state, page.url),
             zip: fv(h.zip, h.zip, page.url),
+            lat: fv(cg?.lat ?? null, cg ? String(cg.lat) : null, page.url, cg ? "community MapPoint (home inherits community coord, city-gated)" : null),
+            lon: fv(cg?.lon ?? null, cg ? String(cg.lon) : null, page.url, cg ? "community MapPoint (home inherits community coord, city-gated)" : null),
             price: fv(h.price, h.priceRaw, page.url, h.priceRaw ? `price ${h.priceRaw}` : null),
             beds: fv(h.beds, null, page.url),
             bathsTotal: fv(h.baths, null, page.url),

← 11a5e5fa pipeline: make evidence delete+create atomic (contrarian fix  ·  back to Homesonspec  ·  auto-save: 2026-07-29T07:06:42 (3 files) — collectors/dream- 0b79470c →