[object Object]

← back to Commercialrealestate

CRCP: DRE-licensed agents/brokers DB from CA public govt data

e914e06828917200fe0c0ee14cacc3d3aa13a389 · 2026-07-10 17:08:31 -0700 · Steve Abrams

- scripts/fetch-dre-licenses.js: name->License ID->detail against the CA DRE
  public lookup (www2.dre.ca.gov), $0 local curl, resumable, honest labeling
- /api/agents/licensed route (DB->snapshot fallback, same idiom as /api/residential)
- public/licensed-agents.html viewer: search + sort + density, per-agent DRE
  verification (license #, type Salesperson/Broker, status, expiration, resp. broker)
- mls.html nav link; bounded 25-agent proof run (19 verified, 0 errors, $0)
- full 637 run + launchd + prod deploy drafted to pending-approval (Steve-gated)

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

Files touched

Diff

commit e914e06828917200fe0c0ee14cacc3d3aa13a389
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 10 17:08:31 2026 -0700

    CRCP: DRE-licensed agents/brokers DB from CA public govt data
    
    - scripts/fetch-dre-licenses.js: name->License ID->detail against the CA DRE
      public lookup (www2.dre.ca.gov), $0 local curl, resumable, honest labeling
    - /api/agents/licensed route (DB->snapshot fallback, same idiom as /api/residential)
    - public/licensed-agents.html viewer: search + sort + density, per-agent DRE
      verification (license #, type Salesperson/Broker, status, expiration, resp. broker)
    - mls.html nav link; bounded 25-agent proof run (19 verified, 0 errors, $0)
    - full 637 run + launchd + prod deploy drafted to pending-approval (Steve-gated)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .gitignore                    |   1 +
 data/licensed-agents.json     | 415 ++++++++++++++++++++++++++++++++++++++++++
 public/licensed-agents.html   | 147 +++++++++++++++
 public/mls.html               |   2 +-
 scripts/fetch-dre-licenses.js | 221 ++++++++++++++++++++++
 scripts/serve.js              |  29 +++
 6 files changed, 814 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 1d368ff..9925182 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ data/morning-report.html
 data/assessor.sqlite
 data/assessor.sqlite-shm
 data/assessor.sqlite-wal
+data/dre-cache.json
diff --git a/data/licensed-agents.json b/data/licensed-agents.json
new file mode 100644
index 0000000..17aaf5c
--- /dev/null
+++ b/data/licensed-agents.json
@@ -0,0 +1,415 @@
+{
+ "meta": {
+  "source": "California Department of Real Estate — public license lookup (www2.dre.ca.gov/PublicASP/pplinfo.asp)",
+  "note": "Public government data. Every dre_verified record was confirmed against the live DRE lookup by name -> License ID -> detail. Unmatched/ambiguous agents are labeled honestly, never fabricated.",
+  "cost": "$0 (local plain-HTTP, no captcha, no paid API)",
+  "fetched_at": "2026-07-11T00:07:47.403Z",
+  "total_agents": 556,
+  "counts": {
+   "verified": 19,
+   "unmatched": 6,
+   "ambiguous": 0,
+   "errors": 0,
+   "from_cache": 6,
+   "in_snapshot": 25
+  }
+ },
+ "agents": [
+  {
+   "agent": "Allen Avanesyan",
+   "dre_query": "Avanesyan, Allen",
+   "brokerage": "Beverly & Company Luxury Properties",
+   "license": "02203716",
+   "license_type": "Salesperson",
+   "license_city": "BURBANK",
+   "status": "LICENSED",
+   "expiration": "12/13/26",
+   "issued": "12/14/22",
+   "responsible_broker": "Beverly and Company, Inc. 15301 VENTURA BLVD BLDG B STE 315 SHERMAN OAKS, CA 91403",
+   "responsible_broker_id": "02078273",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=02203716"
+  },
+  {
+   "agent": "Walter Castillo",
+   "dre_query": "Castillo, Walter",
+   "brokerage": "Coldwell Banker Realty",
+   "license": "01141632",
+   "license_type": "Broker/Officer",
+   "license_city": "LOS ANGELES",
+   "status": "LICENSED",
+   "expiration": "08/29/26",
+   "issued": "07/15/92 (Unofficial -- taken from secondary records) Broker License Issued : 08/30/10",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01141632"
+  },
+  {
+   "agent": "Tony Prichard",
+   "dre_query": "Prichard, Tony",
+   "brokerage": "BERKSHIRE HATHAWAY HOMESEVICES Crest Real estate",
+   "dre_verified": false,
+   "status_label": "unmatched",
+   "match_confidence": "no-match",
+   "candidates": 0
+  },
+  {
+   "agent": "Vahan Lux",
+   "dre_query": "Lux, Vahan",
+   "brokerage": "BERKSHIRE HATHAWAY HOMESERVICES Crest Real Estate",
+   "license": "00977686",
+   "license_type": "Salesperson",
+   "license_city": "BURBANK",
+   "status": "LICENSED",
+   "expiration": "05/21/28",
+   "issued": "12/28/87 (Unofficial -- taken from secondary records)",
+   "responsible_broker": "E A M Enterprises Inc 4005 FOOTHILL BLVD LA CRESCENTA, CA 91214",
+   "responsible_broker_id": "01190835",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=00977686"
+  },
+  {
+   "agent": "Narine Khachatryan",
+   "dre_query": "Khachatryan, Narine",
+   "brokerage": "EMPOWER Real Estate",
+   "license": "01843510",
+   "license_type": "Broker/Officer",
+   "license_city": "BURBANK",
+   "status": "LICENSED",
+   "expiration": "04/08/28",
+   "issued": "04/07/08",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01843510"
+  },
+  {
+   "agent": "Bradley Korb",
+   "dre_query": "Korb, Bradley",
+   "brokerage": "Brad Korb Real Estate Group",
+   "license": "00698730",
+   "license_type": "Broker/Officer",
+   "license_city": "BURBANK",
+   "status": "LICENSED",
+   "expiration": "11/29/28",
+   "issued": "07/02/83 (Unofficial -- taken from secondary records) Broker License Issued : 11/30/92 (Unofficial -- taken from secondary records)",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=00698730"
+  },
+  {
+   "agent": "Gun Swanson",
+   "dre_query": "Swanson, Gun",
+   "brokerage": "Engel & Volkers Burbank",
+   "license": "00638098",
+   "license_type": "Salesperson",
+   "license_city": "BURBANK",
+   "status": "LICENSED",
+   "expiration": "03/08/30",
+   "issued": "03/09/78 (Unofficial -- taken from secondary records)",
+   "responsible_broker": "SFRE LA Ventura, Inc. 345 E COLORADO BLVD PASADENA, CA 91101",
+   "responsible_broker_id": "02221897",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=00638098"
+  },
+  {
+   "agent": "Ida Altounian",
+   "dre_query": "Altounian, Ida",
+   "brokerage": "Coldwell Banker Realty",
+   "license": "02038924",
+   "license_type": "Salesperson",
+   "license_city": "GLENDALE",
+   "status": "LICENSED",
+   "expiration": "02/21/30",
+   "issued": "02/22/18",
+   "responsible_broker": "Coldwell Banker Residential Brokerage Company 300 COMMERCE STE 250 IRVINE, CA 92602",
+   "responsible_broker_id": "00616212",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=02038924"
+  },
+  {
+   "agent": "Armik Avedisian",
+   "dre_query": "Avedisian, Armik",
+   "brokerage": "Keller Williams R. E. Services",
+   "license": "01279085",
+   "license_type": "Broker/Officer",
+   "license_city": "GLENDALE",
+   "status": "LICENSED",
+   "expiration": "04/26/29",
+   "issued": "04/26/00 Broker License Issued : 04/27/17",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01279085"
+  },
+  {
+   "agent": "Ronnie Gharibian",
+   "dre_query": "Gharibian, Ronnie",
+   "brokerage": "Gharibian Real Estate",
+   "license": "01239506",
+   "license_type": "Broker/Officer",
+   "license_city": "GLENDALE",
+   "status": "LICENSED MLO License Endorsement : NMLS ID: 76000 (Click here to check the status of the MLO License Endorsement )",
+   "expiration": "02/07/27",
+   "issued": "06/05/98 (Unofficial -- taken from secondary records) Broker License Issued : 02/08/19",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01239506"
+  },
+  {
+   "agent": "Kyle Rodriguez",
+   "dre_query": "Rodriguez, Kyle",
+   "brokerage": "BLU DOT REALTY GROUP",
+   "license": "01904809",
+   "license_type": "Broker/Officer",
+   "license_city": "SANTA MONICA",
+   "status": "LICENSED",
+   "expiration": "08/15/29",
+   "issued": "11/01/11 Broker License Issued : 08/16/17",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01904809"
+  },
+  {
+   "agent": "JONATHAN BRAY",
+   "dre_query": "BRAY, JONATHAN",
+   "brokerage": "PULTE HOMES OF CALIFORNIA, INC",
+   "dre_verified": false,
+   "status_label": "unmatched",
+   "match_confidence": "ambiguous(2)",
+   "candidates": 2
+  },
+  {
+   "agent": "Mandy Liu",
+   "dre_query": "Liu, Mandy",
+   "brokerage": "BluEleven Realty Group",
+   "dre_verified": false,
+   "status_label": "unmatched",
+   "match_confidence": "no-match",
+   "candidates": 0
+  },
+  {
+   "agent": "Kristine Ng",
+   "dre_query": "Ng, Kristine",
+   "brokerage": "COMPASS",
+   "license": "01782549",
+   "license_type": "Broker/Officer",
+   "license_city": "SAN FRANCISCO",
+   "status": "LICENSED",
+   "expiration": "01/30/29",
+   "issued": "11/27/06",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01782549"
+  },
+  {
+   "agent": "Kenny Kim",
+   "dre_query": "Kim, Kenny",
+   "brokerage": "Coldwell Banker Realty",
+   "dre_verified": false,
+   "status_label": "unmatched",
+   "match_confidence": "ambiguous(6)",
+   "candidates": 6
+  },
+  {
+   "agent": "Danny Umana",
+   "dre_query": "Umana, Danny",
+   "brokerage": "Keller Williams World Media Center",
+   "license": "01927654",
+   "license_type": "Broker/Officer",
+   "license_city": "BURBANK",
+   "status": "LICENSED",
+   "expiration": "02/07/29",
+   "issued": "02/08/13",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01927654"
+  },
+  {
+   "agent": "Samuel Heller",
+   "dre_query": "Heller, Samuel",
+   "brokerage": "Realty World Legends of Santa Clarita Valley Inc.",
+   "license": "00891062",
+   "license_type": "Salesperson",
+   "license_city": "SANTA CLARITA",
+   "status": "LICENSED",
+   "expiration": "06/27/29",
+   "issued": "06/28/85 (Unofficial -- taken from secondary records)",
+   "responsible_broker": "Realty World Legends Of Santa Clarita Valley Inc 25115 AVENUE STANFORD STE B121 VALENCIA, CA 91355",
+   "responsible_broker_id": "01251341",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=00891062"
+  },
+  {
+   "agent": "Brenda Ross",
+   "dre_query": "Ross, Brenda",
+   "brokerage": "Equity Union",
+   "dre_verified": false,
+   "status_label": "unmatched",
+   "match_confidence": "ambiguous(2)",
+   "candidates": 2
+  },
+  {
+   "agent": "Vahe Patatanyan",
+   "dre_query": "Patatanyan, Vahe",
+   "brokerage": "CA Real Estate Group",
+   "license": "01747381",
+   "license_type": "Broker/Officer",
+   "license_city": "BURBANK",
+   "status": "LICENSED MLO License Endorsement : NMLS ID: 351242 (Click here to check the status of the MLO License Endorsement )",
+   "expiration": "07/13/30",
+   "issued": "06/02/06 Broker License Issued : 07/14/10",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01747381"
+  },
+  {
+   "agent": "Jerry Park",
+   "dre_query": "Park, Jerry",
+   "brokerage": "AVM Real Estate Services",
+   "dre_verified": false,
+   "status_label": "unmatched",
+   "match_confidence": "ambiguous(2)",
+   "candidates": 2
+  },
+  {
+   "agent": "Elliot Rousso",
+   "dre_query": "Rousso, Elliot",
+   "brokerage": "Pinnacle Estate Properties, Inc.",
+   "license": "01854651",
+   "license_type": "Salesperson",
+   "license_city": "CALABASAS",
+   "status": "",
+   "expiration": "",
+   "issued": "",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "unknown",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "unknown",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01854651"
+  },
+  {
+   "agent": "Michale Meyers",
+   "dre_query": "Meyers, Michale",
+   "brokerage": "The Agency",
+   "license": "01450380",
+   "license_type": "Salesperson",
+   "license_city": "CALABASAS",
+   "status": "LICENSED",
+   "expiration": "09/22/28",
+   "issued": "08/30/04",
+   "responsible_broker": "UMRO Realty Corp. 331 FOOTHILL RD STE 100 BEVERLY HILLS, CA 90210",
+   "responsible_broker_id": "01904054",
+   "discipline": "none",
+   "match_confidence": "exact",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01450380"
+  },
+  {
+   "agent": "Anthony Alonzo",
+   "dre_query": "Alonzo, Anthony",
+   "brokerage": "Casablanca Real Estate & Inv",
+   "license": "01200471",
+   "license_type": "Broker/Officer",
+   "license_city": "NORTHRIDGE",
+   "status": "LICENSED MLO License Endorsement : NMLS ID: 350312 (Click here to check the status of the MLO License Endorsement ) Company MLO License Endorsement : 379383 (Click here to check the status of the MLO License Endorsement.)",
+   "expiration": "10/19/27",
+   "issued": "08/15/95 (Unofficial -- taken from secondary records) Broker License Issued : 10/20/99 (Unofficial -- taken from secondary records)",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01200471"
+  },
+  {
+   "agent": "Hewitt Rolle",
+   "dre_query": "Rolle, Hewitt",
+   "brokerage": "Hewitt Rolle",
+   "license": "01453736",
+   "license_type": "Broker/Officer",
+   "license_city": "PORTER RANCH",
+   "status": "LICENSED",
+   "expiration": "04/02/30",
+   "issued": "09/16/04 Broker License Issued : 07/11/07",
+   "responsible_broker": "",
+   "responsible_broker_id": "",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=01453736"
+  },
+  {
+   "agent": "Neal Kang",
+   "dre_query": "Kang, Neal",
+   "brokerage": "Best Realty & Investment, Inc.",
+   "license": "00994541",
+   "license_type": "Salesperson",
+   "license_city": "NORTHRIDGE",
+   "status": "LICENSED",
+   "expiration": "09/16/28",
+   "issued": "06/21/88 (Unofficial -- taken from secondary records)",
+   "responsible_broker": "Best Realty & Investment Inc 17057 CHATSWORTH ST GRANADA HILLS, CA 91344",
+   "responsible_broker_id": "01816803",
+   "discipline": "none",
+   "match_confidence": "surname-only",
+   "dre_verified": true,
+   "status_label": "active",
+   "dre_url": "https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=00994541"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/public/licensed-agents.html b/public/licensed-agents.html
new file mode 100644
index 0000000..9d64e07
--- /dev/null
+++ b/public/licensed-agents.html
@@ -0,0 +1,147 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Licensed Agents & Brokers — CRCP</title>
+<style>
+  :root{ --bg:#0e1116; --card:#161b22; --line:#2a313c; --ink:#e6edf3; --mut:#8b949e; --acc:#3fb950; --warn:#d29922; --bad:#f85149; --blue:#58a6ff; }
+  *{box-sizing:border-box;} body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
+  header{display:flex;align-items:center;gap:12px;padding:11px 18px;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:40;background:rgba(14,17,22,.98);backdrop-filter:blur(8px);flex-wrap:wrap;}
+  header h1{font-size:16px;margin:0;} header h1 b{color:var(--acc);}
+  header .nav{display:flex;gap:12px;margin-left:6px;} header .nav a{color:var(--blue);text-decoration:none;font-size:13px;} header .nav a:hover{color:var(--warn);}
+  .search{background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:9px;padding:8px 12px;font-size:14px;min-width:240px;}
+  select,#density{background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:7px;padding:6px 8px;font-size:12px;}
+  label.densl{display:inline-flex;align-items:center;gap:6px;color:var(--mut);font-size:12px;}
+  .count{color:var(--mut);font-size:12px;}
+  .bar{display:flex;gap:8px;flex-wrap:wrap;padding:10px 18px;border-bottom:1px solid var(--line);align-items:center;}
+  .pill{display:inline-flex;gap:6px;align-items:center;background:var(--card);border:1px solid var(--line);border-radius:16px;padding:5px 11px;font-size:12px;color:var(--mut);cursor:pointer;}
+  .pill b{color:var(--ink);font-variant-numeric:tabular-nums;} .pill.on{border-color:var(--blue);color:var(--ink);}
+  .note{color:var(--mut);font-size:11.5px;padding:8px 18px;border-bottom:1px solid var(--line);}
+  main{padding:14px 18px;}
+  .grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(var(--cw,320px),1fr));}
+  .card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:13px 14px;}
+  .card .top{display:flex;justify-content:space-between;gap:8px;align-items:flex-start;}
+  .card .nm{font-weight:700;font-size:14px;}
+  .card .bk{color:var(--mut);font-size:12px;margin-top:1px;}
+  .badge{font-size:10.5px;font-weight:700;padding:3px 8px;border-radius:12px;white-space:nowrap;text-transform:uppercase;letter-spacing:.3px;}
+  .b-active{background:rgba(63,185,80,.14);color:var(--acc);border:1px solid rgba(63,185,80,.5);}
+  .b-un{background:rgba(139,148,158,.12);color:var(--mut);border:1px solid var(--line);}
+  .b-warn{background:rgba(210,153,34,.14);color:var(--warn);border:1px solid rgba(210,153,34,.5);}
+  .b-bad{background:rgba(248,81,73,.14);color:var(--bad);border:1px solid rgba(248,81,73,.5);}
+  .kv{margin-top:10px;display:grid;grid-template-columns:auto 1fr;gap:3px 10px;font-size:12px;}
+  .kv .k{color:var(--mut);} .kv .v{font-variant-numeric:tabular-nums;}
+  .kv .v a{color:var(--blue);text-decoration:none;} .kv .v a:hover{text-decoration:underline;}
+  .conf{font-size:10px;color:var(--mut);margin-top:7px;}
+  .verified{color:var(--acc);font-weight:700;}
+</style>
+</head>
+<body>
+<header>
+  <h1>🎫 Licensed <b>Agents &amp; Brokers</b></h1>
+  <div class="nav"><a href="/mls.html">← MLS</a><a href="/">Explorer</a><a href="/broker-grid.html">Broker Grid</a><a href="/crcp.html">🛰️ CRCP</a></div>
+  <input class="search" id="q" type="text" placeholder="search name, brokerage, license #, responsible broker…">
+  <select id="sortsel" title="Sort">
+    <option value="name">Sort: Name A→Z</option>
+    <option value="status">Sort: Status</option>
+    <option value="type">Sort: License type</option>
+    <option value="exp">Sort: Expiration (soonest)</option>
+    <option value="brokerage">Sort: Brokerage</option>
+    <option value="conf">Sort: Match confidence</option>
+  </select>
+  <label class="densl" title="Card density">⤢<input type="range" id="density" min="240" max="460" step="20" value="320"></label>
+  <span class="count" id="count">…</span>
+</header>
+<div class="bar" id="pills"></div>
+<div class="note" id="note"></div>
+<main><div class="grid" id="grid"></div></main>
+<script>
+const $=s=>document.querySelector(s);
+let DATA=[], META={}, filt='all', q='', sortKey='name';
+const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
+
+function statusClass(a){
+  if(!a.dre_verified) return a.status_label==='error'?'b-bad':'b-un';
+  const s=(a.status_label||'').toLowerCase();
+  if(s==='active') return 'b-active';
+  if(/expire|surrender|cancel|revok|suspend/.test(s)) return 'b-bad';
+  return 'b-warn';
+}
+function statusText(a){
+  if(!a.dre_verified) return a.status_label==='error'?'DRE error':(a.match_confidence||'unmatched');
+  return a.status||a.status_label||'?';
+}
+// expiration mm/dd/yy -> sortable
+function expNum(a){ const m=/(\d{2})\/(\d{2})\/(\d{2})/.exec(a.expiration||''); return m?(2000+ +m[3])*10000+ +m[1]*100+ +m[2]:99999999; }
+
+function render(){
+  let rows=DATA.filter(a=>{
+    if(filt==='verified'&&!a.dre_verified) return false;
+    if(filt==='broker'&&!/broker/i.test(a.license_type||'')) return false;
+    if(filt==='salesperson'&&!/salesperson/i.test(a.license_type||'')) return false;
+    if(filt==='unmatched'&&a.dre_verified) return false;
+    if(q){ const hay=(a.agent+' '+(a.brokerage||'')+' '+(a.license||'')+' '+(a.responsible_broker||'')+' '+(a.license_type||'')).toLowerCase(); if(!hay.includes(q)) return false; }
+    return true;
+  });
+  const cmp={
+    name:(a,b)=>a.agent.localeCompare(b.agent),
+    status:(a,b)=>(b.dre_verified-a.dre_verified)||(a.status_label||'').localeCompare(b.status_label||''),
+    type:(a,b)=>(a.license_type||'zz').localeCompare(b.license_type||'zz'),
+    exp:(a,b)=>expNum(a)-expNum(b),
+    brokerage:(a,b)=>(a.brokerage||'zz').localeCompare(b.brokerage||'zz'),
+    conf:(a,b)=>(a.match_confidence||'zz').localeCompare(b.match_confidence||'zz'),
+  }[sortKey];
+  rows.sort(cmp);
+  $('#grid').innerHTML=rows.map(cardHtml).join('')||'<div class="note">No agents match.</div>';
+  $('#count').textContent=rows.length+' / '+DATA.length+' agents';
+}
+function cardHtml(a){
+  const dre=a.dre_url?`<a href="${esc(a.dre_url)}" target="_blank" rel="noopener noreferrer">${esc(a.license)} ↗</a>`:'—';
+  const rb=a.responsible_broker?esc(a.responsible_broker.split(/\s\d/)[0]).slice(0,60):'—';
+  return `<div class="card">
+    <div class="top">
+      <div><div class="nm">${esc(a.agent)}${a.dre_verified?' <span class="verified" title="Verified against CA DRE public license record">✔</span>':''}</div>
+        <div class="bk">${esc(a.brokerage||'—')}</div></div>
+      <span class="badge ${statusClass(a)}">${esc(statusText(a))}</span>
+    </div>
+    <div class="kv">
+      <span class="k">License</span><span class="v">${dre}</span>
+      <span class="k">Type</span><span class="v">${esc(a.license_type||'—')}</span>
+      <span class="k">Expires</span><span class="v">${esc(a.expiration||'—')}</span>
+      <span class="k">Resp. broker</span><span class="v">${rb}</span>
+      <span class="k">Discipline</span><span class="v">${esc(a.discipline||'—')}</span>
+    </div>
+    ${a.match_confidence?`<div class="conf">match: ${esc(a.match_confidence)}</div>`:''}
+  </div>`;
+}
+function pills(){
+  const n=k=>DATA.filter(k).length;
+  const defs=[
+    ['all','All',()=>true],
+    ['verified','✔ DRE-verified',a=>a.dre_verified],
+    ['broker','Brokers',a=>/broker/i.test(a.license_type||'')],
+    ['salesperson','Salespersons',a=>/salesperson/i.test(a.license_type||'')],
+    ['unmatched','Unmatched',a=>!a.dre_verified],
+  ];
+  $('#pills').innerHTML=defs.map(([k,l,f])=>`<span class="pill ${filt===k?'on':''}" data-f="${k}">${l} <b>${n(f)}</b></span>`).join('');
+  document.querySelectorAll('.pill').forEach(p=>p.onclick=()=>{filt=p.dataset.f;pills();render();});
+}
+async function load(){
+  let d; try{ d=await (await fetch('/api/agents/licensed')).json(); }catch(e){ $('#note').textContent='Failed to load /api/agents/licensed'; return; }
+  DATA=d.agents||[]; META=d.meta||{};
+  const c=META.counts||{};
+  $('#note').innerHTML=`Source: <b>${esc(d.source||'?')}</b> · ${esc(d.label||'')} `+
+    (c.verified!=null?`— <span class="verified">${c.verified} verified</span>, ${c.unmatched||0} unmatched, ${c.errors||0} errors of ${META.total_agents||DATA.length}. ${esc(META.cost||'')}`:'');
+  pills(); render();
+}
+// controls
+$('#q').oninput=e=>{q=e.target.value.toLowerCase().trim();render();};
+$('#sortsel').onchange=e=>{sortKey=e.target.value;localStorage.setItem('laSort',sortKey);render();};
+$('#density').oninput=e=>{document.documentElement.style.setProperty('--cw',e.target.value+'px');localStorage.setItem('laDens',e.target.value);};
+// restore persisted
+(()=>{const s=localStorage.getItem('laSort'); if(s){sortKey=s;$('#sortsel').value=s;}
+  const dv=localStorage.getItem('laDens')||'320'; $('#density').value=dv; document.documentElement.style.setProperty('--cw',dv+'px');})();
+load();
+</script>
+</body>
+</html>
diff --git a/public/mls.html b/public/mls.html
index 2210983..8b62ed3 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -91,7 +91,7 @@
 <body>
 <header>
   <h1><b>MLS</b> · LA County — Residential + CRE</h1>
-  <div class="nav"><a href="/">← Explorer</a><a href="/map.html">🗺️ Map</a><a href="/crcp.html">🛰️ CRCP</a></div>
+  <div class="nav"><a href="/">← Explorer</a><a href="/map.html">🗺️ Map</a><a href="/licensed-agents.html" title="Real-estate agents &amp; brokers verified against the CA DRE public license roll">🎫 Licensed Agents</a><a href="/crcp.html">🛰️ CRCP</a></div>
   <span class="spacer"></span>
   <input class="search" id="q" type="text" placeholder="search anything — address, city, firm, type, year…">
   <div class="toggle" id="view"><button data-v="table" class="on">▦ Table</button><button data-v="grid">▤ Grid</button></div>
diff --git a/scripts/fetch-dre-licenses.js b/scripts/fetch-dre-licenses.js
new file mode 100644
index 0000000..18118c4
--- /dev/null
+++ b/scripts/fetch-dre-licenses.js
@@ -0,0 +1,221 @@
+#!/usr/bin/env node
+/*
+ * fetch-dre-licenses.js — obtain a REAL California DRE license for every CRCP
+ * real-estate agent/broker from PUBLIC GOVERNMENT DATA ($0, no captcha, no paid API).
+ *
+ * Source: CA Dept. of Real Estate public license lookup
+ *   https://www2.dre.ca.gov/PublicASP/pplinfo.asp   (classic ASP form)
+ *
+ * Two-step pipeline (both plain HTTP, $0 local):
+ *   1) NAME SEARCH  POST pplinfo.asp?start=1  LICENSEE_NAME="Last, First"
+ *        -> result rows: <a href="pplinfo.asp?License_id=NNNNNNNN">NNN</a>
+ *           <td> Name </td><td>Type</td><td>City</td>
+ *   2) DETAIL       GET  pplinfo.asp?License_id=NNNNNNNN
+ *        -> License Type / Name / License ID / Expiration Date / License Status /
+ *           Salesperson|Broker License Issued / Responsible Broker (name+id) / Comment
+ *
+ * Input : data/redfin-agents.json  (agent NAMES + brokerage; no license #s persisted)
+ * Output: data/licensed-agents.json (snapshot the /api/agents/licensed route serves)
+ * Cache : data/dre-cache.json  (per-name, resumable — safe to Ctrl-C and re-run)
+ *
+ * Honest labeling: unmatched / ambiguous / multi-match are recorded as such,
+ * never fabricated. Only records confirmed against DRE carry dre_verified=true.
+ *
+ * Usage:  node scripts/fetch-dre-licenses.js [--limit N] [--delay MS] [--force]
+ *         --limit N   cap agents processed this run (bounded proof runs)
+ *         --delay MS  polite delay between requests (default 1200ms + jitter)
+ *         --force     ignore cache, re-fetch
+ */
+'use strict';
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.join(__dirname, '..');
+const IN = path.join(ROOT, 'data', 'redfin-agents.json');
+const OUT = path.join(ROOT, 'data', 'licensed-agents.json');
+const CACHE = path.join(ROOT, 'data', 'dre-cache.json');
+const BASE = 'https://www2.dre.ca.gov/PublicASP/pplinfo.asp';
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36';
+
+const arg = (k, d) => { const i = process.argv.indexOf(k); return i > -1 ? (process.argv[i + 1] ?? true) : d; };
+const LIMIT = +arg('--limit', 0) || Infinity;
+const DELAY = +arg('--delay', 1200);
+const FORCE = process.argv.includes('--force');
+
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const jitter = () => DELAY + Math.floor(Math.random() * 500);
+const clean = s => (s || '').replace(/&nbsp;/g, ' ').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
+
+// "First M Last Jr" (Redfin order) -> "Last, First M" (DRE order). Strips brokerage suffixes.
+function toDreName(raw) {
+  let n = (raw || '').replace(/\s+/g, ' ').trim();
+  if (!n || n.includes(',')) return n;                 // already Last, First
+  n = n.replace(/\b(dre|lic|license)\s*#?\s*\d+/ig, '').trim();
+  const parts = n.split(' ').filter(Boolean);
+  if (parts.length < 2) return n;
+  const suffix = /^(jr|sr|ii|iii|iv)\.?$/i;
+  let last = parts.pop();
+  if (suffix.test(last) && parts.length >= 2) last = parts.pop(); // keep real surname before Jr/Sr
+  return `${last}, ${parts.join(' ')}`;
+}
+
+async function post(name) {
+  const body = new URLSearchParams({ LICENSEE_NAME: name, LICENSE_ID: '', CITY_STATE: '', B2: 'Find', h_nextstep: '' });
+  const r = await fetch(`${BASE}?start=1`, {
+    method: 'POST', headers: { 'User-Agent': UA, 'Content-Type': 'application/x-www-form-urlencoded' }, body,
+  });
+  return r.ok ? r.text() : '';
+}
+async function getDetail(id) {
+  const r = await fetch(`${BASE}?License_id=${encodeURIComponent(id)}`, { headers: { 'User-Agent': UA } });
+  return r.ok ? r.text() : '';
+}
+
+// Parse the name-search results table into candidate rows.
+function parseSearch(html) {
+  const out = [];
+  const re = /<a[^>]*href="pplinfo\.asp\?License_id=(\d+)"[^>]*>[^<]*<\/a>\s*<\/td>\s*<td>([^<]*)<\/td>\s*<td>([^<]*)<\/td>\s*<td>([^<]*)<\/td>/gi;
+  let m;
+  while ((m = re.exec(html))) {
+    out.push({ license: m[1], name: clean(m[2]), type: clean(m[3]), city: clean(m[4]) });
+  }
+  return out;
+}
+
+// Parse the license-detail page into the fields Steve wants.
+// The page renders each field as <strong>Label:</strong> ... </td><td><FONT>VALUE</FONT>,
+// so we flatten all tags to spaces and read each label-to-next-label span.
+function parseDetail(html) {
+  const flat = clean(html.replace(/&nbsp;/g, ' '));
+  // value between `label` and the first of the `stops` labels (or end).
+  const span = (label, stops) => {
+    const re = new RegExp(label + '\\s*:?\\s*(.*?)\\s*(?:' + stops.map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + '|$)', 'i');
+    const m = flat.match(re);
+    return m ? m[1].trim() : '';
+  };
+  const type = span('License Type', ['Name:', 'License ID', 'Expiration']);
+  const name = span('\\bName', ['Mailing Address', 'License ID', 'License Type', 'Expiration']);
+  const expiration = span('Expiration Date', ['License Status', 'Salesperson License', 'Broker License']);
+  const status = span('License Status', ['Salesperson License Issued', 'Broker License Issued', 'Former Name', 'Responsible Broker']);
+  const issued = span('Salesperson License Issued', ['Former Name', 'Responsible Broker', 'Comment'])
+              || span('Broker License Issued', ['Former Name', 'Responsible Broker', 'Comment']);
+  const idm = flat.match(/License ID\s*:?\s*(\d{6,8})/i);
+  const licenseId = idm ? idm[1] : '';
+  // Responsible broker: first "Responsible Broker:" block -> its License ID + firm name.
+  let respBrokerId = '', respBrokerName = '';
+  const rb = flat.match(/Responsible Broker\s*:?\s*License ID\s*:?\s*(\d{6,8})\s*(.*?)\s*(?:Former Responsible Broker|From \d|Comment|$)/i);
+  if (rb) { respBrokerId = rb[1]; respBrokerName = rb[2].trim(); }
+  const discipline = /NO DISCIPLINARY ACTION/i.test(flat) ? 'none'
+    : (/(disciplin|revoked|suspend|barred)/i.test(flat) ? 'see-dre-record' : 'unknown');
+  return { detail_type: type, dre_name: name, detail_license_id: licenseId, status, expiration, issued,
+           responsible_broker_id: respBrokerId, responsible_broker: respBrokerName, discipline };
+}
+
+// Choose the best candidate for an agent name (+ optional city hint).
+function pick(cands, wantName, cityHint) {
+  if (!cands.length) return { match: null, ambiguity: 'no-match' };
+  const norm = s => s.toLowerCase().replace(/[^a-z, ]/g, '').replace(/\s+/g, ' ').trim();
+  const want = norm(wantName);
+  const exact = cands.filter(c => norm(c.name) === want);
+  if (exact.length === 1) return { match: exact[0], ambiguity: 'exact' };
+  if (exact.length > 1) {
+    if (cityHint) {
+      const byCity = exact.filter(c => c.city && cityHint.toUpperCase().includes(c.city.toUpperCase()));
+      if (byCity.length === 1) return { match: byCity[0], ambiguity: 'exact+city' };
+    }
+    return { match: exact[0], ambiguity: `multi-exact(${exact.length})` };
+  }
+  // last-name-only fallthrough: if exactly one candidate shares the surname, take it (low confidence)
+  const surname = want.split(',')[0];
+  const bySur = cands.filter(c => norm(c.name).split(',')[0] === surname);
+  if (bySur.length === 1) return { match: bySur[0], ambiguity: 'surname-only' };
+  return { match: null, ambiguity: `ambiguous(${cands.length})` };
+}
+
+function loadCache() { try { return JSON.parse(fs.readFileSync(CACHE, 'utf8')); } catch { return {}; } }
+
+async function main() {
+  const src = JSON.parse(fs.readFileSync(IN, 'utf8'));
+  // Unique agent names (+ a brokerage/city hint) from redfin-agents results.
+  const seen = new Map();
+  for (const r of (src.results || [])) {
+    if (!r.agent) continue;
+    const key = r.agent.trim();
+    if (!seen.has(key)) seen.set(key, { agent: key, brokerage: r.brokerage || '', cityHint: '' });
+  }
+  const agents = [...seen.values()];
+  const cache = FORCE ? {} : loadCache();
+  let processed = 0, verified = 0, unmatched = 0, ambiguous = 0, fromCache = 0, errors = 0;
+  const results = [];
+
+  for (const a of agents) {
+    if (processed >= LIMIT) break;
+    const dreName = toDreName(a.agent);
+    if (cache[a.agent] && !FORCE) {
+      results.push(cache[a.agent]); fromCache++;
+      const c = cache[a.agent];
+      if (c.dre_verified) verified++; else if (c.status_label === 'unmatched') unmatched++; else ambiguous++;
+      continue;
+    }
+    processed++;
+    let rec = { agent: a.agent, dre_query: dreName, brokerage: a.brokerage };
+    try {
+      const html = await post(dreName);
+      const cands = parseSearch(html);
+      const { match, ambiguity } = pick(cands, dreName, a.cityHint);
+      if (!match) {
+        rec = { ...rec, dre_verified: false, status_label: 'unmatched', match_confidence: ambiguity, candidates: cands.length };
+        unmatched++;
+      } else {
+        await sleep(jitter());
+        const detail = parseDetail(await getDetail(match.license));
+        rec = {
+          ...rec,
+          license: match.license,
+          license_type: match.type || detail.detail_type,
+          license_city: match.city,
+          status: detail.status,
+          expiration: detail.expiration,
+          issued: detail.issued,
+          responsible_broker: detail.responsible_broker,
+          responsible_broker_id: detail.responsible_broker_id,
+          discipline: detail.discipline,
+          match_confidence: ambiguity,
+          dre_verified: true,
+          status_label: /LICENSED/i.test(detail.status) ? 'active' : (detail.status ? detail.status.toLowerCase() : 'unknown'),
+          dre_url: `${BASE}?License_id=${match.license}`,
+        };
+        verified++;
+      }
+    } catch (e) {
+      rec = { ...rec, dre_verified: false, status_label: 'error', error: String(e.message).split('\n')[0] };
+      errors++;
+    }
+    cache[a.agent] = rec;
+    results.push(rec);
+    fs.writeFileSync(CACHE, JSON.stringify(cache, null, 1)); // persist every step -> resumable
+    process.stdout.write(`\r  processed ${processed}  verified ${verified}  unmatched ${unmatched}  err ${errors}   `);
+    await sleep(jitter());
+  }
+  process.stdout.write('\n');
+
+  const snapshot = {
+    meta: {
+      source: 'California Department of Real Estate — public license lookup (www2.dre.ca.gov/PublicASP/pplinfo.asp)',
+      note: 'Public government data. Every dre_verified record was confirmed against the live DRE lookup by name -> License ID -> detail. Unmatched/ambiguous agents are labeled honestly, never fabricated.',
+      cost: '$0 (local plain-HTTP, no captcha, no paid API)',
+      fetched_at: new Date().toISOString(),
+      total_agents: agents.length,
+      counts: { verified, unmatched, ambiguous, errors, from_cache: fromCache, in_snapshot: results.length },
+    },
+    agents: results,
+  };
+  fs.writeFileSync(OUT, JSON.stringify(snapshot, null, 1));
+  console.log(`\n✔ wrote ${OUT}`);
+  console.log(`  agents total ${agents.length} | verified ${verified} | unmatched ${unmatched} | errors ${errors} | cached ${fromCache}`);
+  console.log(`  cost: $0 (local)`);
+}
+module.exports = { toDreName, parseSearch, parseDetail, pick };
+if (require.main === module) {
+  main().catch(e => { console.error('FATAL', e); process.exit(1); });
+}
diff --git a/scripts/serve.js b/scripts/serve.js
index 4c8914e..edfe026 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -225,6 +225,35 @@ app.get('/api/leads/frank', async (req, res) => {
   } catch (e) { res.status(502).json({ error: String(e.message).split('\n')[0], leads: [] }); }
 });
 
+// Licensed agents/brokers — every RE agent CRCP harvested, backed by a REAL California DRE
+// license pulled from PUBLIC government data (www2.dre.ca.gov). DB-first -> snapshot fallback,
+// same idiom as /api/residential so it works locally (Postgres) AND on prod (snapshot JSON,
+// produced by scripts/fetch-dre-licenses.js). Public-safe fields only — no mailing addresses.
+const LICENSED_LABEL = 'Real-estate agents/brokers verified against the California DRE public license lookup (govt data, $0). Unmatched/ambiguous agents labeled honestly, never fabricated.';
+app.get('/api/agents/licensed', async (req, res) => {
+  const PUBLIC = ['agent', 'brokerage', 'license', 'license_type', 'license_city', 'status',
+    'status_label', 'expiration', 'issued', 'responsible_broker', 'responsible_broker_id',
+    'discipline', 'match_confidence', 'dre_verified', 'dre_url'];
+  const pubOnly = a => Object.fromEntries(PUBLIC.filter(k => k in a).map(k => [k, a[k]]));
+  // 1) live DB first (broker table already carries a license column)
+  if (brokerdb) {
+    try {
+      const r = await brokerdb.pool.query(
+        `SELECT name AS agent, firm_name AS brokerage, license,
+                license_type, license_status AS status, license_expiration AS expiration
+           FROM broker WHERE license IS NOT NULL AND license <> '' ORDER BY name LIMIT 5000`);
+      if (r.rows.length) return res.json({ agents: r.rows, label: LICENSED_LABEL, source: 'db' });
+    } catch (_) { /* columns may not exist yet -> fall through to snapshot */ }
+  }
+  // 2) snapshot fallback (prod / no-DB)
+  try {
+    const { meta = {}, agents = [] } = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'licensed-agents.json'), 'utf8'));
+    return res.json({ agents: agents.map(pubOnly), meta, label: LICENSED_LABEL, source: 'snapshot' });
+  } catch (e) {
+    return res.json({ agents: [], note: 'no licensed-agent data yet — run scripts/fetch-dre-licenses.js', error: String(e.message).split('\n')[0] });
+  }
+});
+
 // Property history for ONE address — last ~100 yrs, ADDRESS-ONLY (no owner/resident names, by design).
 // Combines: the public assessor parcel roll (year built, beds/baths/sqft, use, valuation — current roll),
 // sold transactions from closed_sale (Redfin sold history), and the WhoLivedThere/pastdoor archive

← a3bff78 afternoon CRE update 2026-07-10  ·  back to Commercialrealestate  ·  CRCP DRE: contrarian fixes — verified only on exact match, d 6abac2e →