← back to Homesonspec

apps/web/src/lib/parcel.ts

96 lines

// Parcel / public-records drill helpers.
//
// SOURCING-COMPLIANCE: we source parcel facts (lot, county) that the ingest
// pipeline already legitimately captured, and we link OUT to the county's own
// public-records / assessor parcel-search surface. We NEVER fabricate an APN —
// the data model has no APN column, so we surface what we honestly have (lot
// number + county) and hand the user a public-records search scoped to the
// county so they can look the parcel up on the authoritative source themselves.
//
// A small curated map of county → assessor parcel-search URL covers the highest
// -volume counties in our data with a known, stable public search page. For any
// county not in the map we fall back to a generic public-records search that is
// still scoped to the county + state, so there is never a dead-end data point.

interface CountyAssessor {
  /** Display name of the authority the link points to. */
  authority: string;
  /** Landing/search URL on the county's own public parcel-search surface. */
  url: (city: string, state: string) => string;
}

// Normalize "Solano County" / "solano" → "solano" for keying.
export function normCounty(county: string | null | undefined): string {
  if (!county) return "";
  return county
    .toLowerCase()
    .replace(/\s+county$/i, "")
    .trim();
}

// state-scoped county key so identical county names in different states don't collide.
function key(county: string | null | undefined, state: string): string {
  return `${state.toUpperCase()}:${normCounty(county)}`;
}

// Curated public parcel-search surfaces. URLs point at the county's OWN public
// search landing page (public-records backbone) — the user runs the lookup.
const ASSESSOR_MAP: Record<string, CountyAssessor> = {
  // California
  "CA:solano": { authority: "Solano County Assessor", url: () => "https://services.solanocounty.com/apps/PropertyInformation/" },
  "CA:san diego": { authority: "San Diego County Assessor", url: () => "https://arcc.sdcounty.ca.gov/Pages/PropertySearch.aspx" },
  "CA:kern": { authority: "Kern County Assessor", url: () => "https://recorderonline.co.kern.ca.us/" },
  "CA:riverside": { authority: "Riverside County Assessor", url: () => "https://ca-riverside-acr.publicaccessnow.com/" },
  "CA:sacramento": { authority: "Sacramento County Assessor", url: () => "https://assessorparcelviewer.saccounty.gov/" },
  // Texas
  "TX:harris": { authority: "Harris County Appraisal District", url: () => "https://hcad.org/property-search/" },
  "TX:fort bend": { authority: "Fort Bend Central Appraisal District", url: () => "https://www.fbcad.org/property-search/" },
  "TX:comal": { authority: "Comal Appraisal District", url: () => "https://esearch.comalad.org/" },
  "TX:kendall": { authority: "Kendall Appraisal District", url: () => "https://esearch.kendallad.org/" },
  "TX:travis": { authority: "Travis Central Appraisal District", url: () => "https://traviscad.org/property-search/" },
  // North Carolina
  "NC:wake": { authority: "Wake County Real Estate", url: () => "https://services.wake.gov/realestate/" },
  "NC:forsyth": { authority: "Forsyth County Tax Assessor", url: () => "https://www.forsyth.cc/tax/property_records.aspx" },
  "NC:cherokee": { authority: "Cherokee County Tax Office", url: () => "https://cherokeecounty-nc.gov/213/Tax-Assessor" },
  // Delaware
  "DE:sussex": { authority: "Sussex County Assessment", url: () => "https://sussexcountyde.gov/property-information" },
  // Virginia
  "VA:albemarle": { authority: "Albemarle County Real Estate", url: () => "https://www.albemarle.org/government/finance-budget/real-estate-tax-assessment" },
  // Georgia
  "GA:forsyth": { authority: "Forsyth County GA Assessor", url: () => "https://qpublic.schneidercorp.com/Application.aspx?App=ForsythCountyGA" },
};

export interface ParcelLink {
  /** Human label for the authority the link points at. */
  authority: string;
  /** URL to the county's public parcel/records search. */
  url: string;
  /** True when we matched a curated county assessor; false = generic fallback. */
  curated: boolean;
}

// Resolve a public-records drill target for a home's county+state. Always returns
// something — a curated assessor when known, else a generic county-scoped
// public-records web search — so a county/parcel data point is never a dead end.
export function parcelLink(
  county: string | null | undefined,
  state: string | null | undefined,
  city: string | null | undefined,
): ParcelLink | null {
  if (!state) return null;
  const st = state.toUpperCase();
  const hit = county ? ASSESSOR_MAP[key(county, st)] : undefined;
  if (hit) {
    return { authority: hit.authority, url: hit.url(city ?? "", st), curated: true };
  }
  // Generic, honest fallback: a web search scoped to the county assessor. No
  // fabricated parcel id — just routes the user to the authoritative source.
  const label = county ? `${normCounty(county).replace(/\b\w/g, (c) => c.toUpperCase())} County` : st;
  const q = encodeURIComponent(`${label} ${st} county assessor parcel property records search`);
  return {
    authority: county ? `${label} public records` : `${st} public records`,
    url: `https://www.google.com/search?q=${q}`,
    curated: false,
  };
}