← back to Nationalrealestate
src/ingest/listings/coldwellbanker.ts
54 lines
/**
* Coldwell Banker adapter. Discovery: robots → new-day listings sitemap-index → leaf
* sitemaps → listing PDP URLs (`/{st}/{city}/{street}/lid-{ID}`). Facts from JSON-LD
* (`Product`+`SingleFamilyResidence`+`Offer`). source_id = the `lid-{ID}` URL token.
*/
import type { ListingAdapter, ListingFacts } from './types.ts';
import { extractJsonLd, typeMatches, num, sitemapLocs } from './shared.ts';
const SITEMAP_INDEX =
'https://www.coldwellbanker.com/xml-sitemap/states/sitemapindex-listings-new-day.xml';
export const coldwellbanker: ListingAdapter = {
source: 'coldwellbanker',
host: 'www.coldwellbanker.com',
listingsPathHint: '/tx/',
async discover(cap) {
const locs = await sitemapLocs(SITEMAP_INDEX, 1);
const listings = locs.filter((u) => /\/lid-[A-Za-z0-9]+/.test(u));
return listings.slice(0, cap);
},
parse(url, html): ListingFacts | null {
const nodes = extractJsonLd(html);
const res = nodes.find((n) => typeMatches(n, 'SingleFamilyResidence', 'Residence', 'House'));
const prod = nodes.find((n) => typeMatches(n, 'Product'));
const addr = res?.address || prod?.offers?.address || {};
const geo = res?.geo || {};
const offer = prod?.offers || res?.offers || {};
const idMatch = url.match(/\/lid-([A-Za-z0-9]+)/);
if (!idMatch) return null;
const streetParts = [addr.streetAddress, addr.addressLocality, addr.addressRegion, addr.postalCode]
.filter(Boolean).join(', ');
const facts: ListingFacts = {
sourceId: idMatch[1],
url,
address: streetParts || res?.name || prod?.name || null,
city: addr.addressLocality ?? null,
state: addr.addressRegion ?? null,
price: num(offer.price),
beds: num(res?.numberOfBedrooms),
baths: num(res?.numberOfBathroomsTotal),
sqft: num(res?.floorSize),
lat: geo.latitude != null ? Number(geo.latitude) : null,
lng: geo.longitude != null ? Number(geo.longitude) : null,
status: offer.availability?.includes('InStock') ? 'active' : null,
};
// fail-loud contract: a listing with neither an address nor a price is not usable
if (!facts.address && facts.price == null) return null;
return facts;
},
};