Builder Name Resolution

Four resolvers that turn messy permit and MLS names into one trustworthy builder and community. This is the join key the Market Report depends on. Repo: builder_resolving_cc.

Why we have it

Every source mislabels who built a home, in a different way. A building permit lists five or more companies (contractor, owner, applicant, subs, architect), and the legacy logic just coalesced them, so it routinely tagged an electrician, architect, or lot owner as the builder (about 15,000 mislabeled). An MLS "builder" field is usually the listing brokerage (Coldwell Banker, RE/MAX), so about a third of listings showed a real-estate office as a homebuilder. The MLS community field is free text with mixed casing, county-code suffixes, and phase markers. Until builder is separated from brokerage and names are made canonical, every Hayden-versus-competitor number in the Market Report is wrong. This is the number one blocker.

How it works

flowchart TB classDef src fill:#e7eef2,stroke:#2e5a6e,color:#1a2530; classDef proc fill:#f6ecd9,stroke:#b5731f,color:#1a2530; classDef shared fill:#f6efe0,stroke:#8a6d2f,color:#1a2530,font-weight:600; classDef store fill:#efe7f2,stroke:#7a5a8e,color:#1a2530; classDef out fill:#e7f0ea,stroke:#1f7a4d,color:#1a2530; classDef val fill:#eef5f0,stroke:#1f7a4d,color:#11402a,font-weight:600; shared["builder_utils.py: shared canonical dictionary
(entity normalize + builder / sub / architect / person classifiers)"]:::shared subgraph PERMIT["Permit side"] direction TB cm["Bronze CM_PERMIT_DATA
(companylinks JSON, 5+ companies per permit)"]:::src alb["Albany Accela scraper
PDF to licensed contractor (ground truth)"]:::src t1["Tool 1 . resolve_permit_builders.py
tiered waterfall: master, fuzzy,
weighted role scorer, supplemental, classify
(self-improves the master each run)"]:::proc plk[("PERMIT_BUILDER_LOOKUP")]:::store cm --> t1 alb -->|ground-truth override| t1 t1 --> plk end subgraph MLS["MLS side"] direction TB mls1["Silver MLS . SOURCE_BUILDER"]:::src mls2["Silver MLS . SUBDIVISION"]:::src t2["Tool 2 . resolve_mls_builders.py
waterfall + brokerage detection
(brokerage to NULL, flagged)"]:::proc t3["Tool 3 . resolve_mls_community.py
normalize, match KOVA + competitor communities"]:::proc mlk[("MLS_BUILDER_LOOKUP")]:::store clk[("MLS_COMMUNITY_LOOKUP")]:::store mls1 --> t2 --> mlk mls2 --> t3 --> clk end shared -.imported by.-> t1 shared -.imported by.-> t2 plk -.master grows.-> shared dbt["dbt: 3 LEFT JOINs back onto Silver
Permits.sql . MLS.sql (Prefect computes, dbt joins)"]:::out gold["Gold: clean builder + community + geo"]:::out val["Accurate builder share, price-vs-size competitive map,
and supply. Feeds Power BI, the Market Report,
and the chatbot."]:::val plk --> dbt mlk --> dbt clk --> dbt dbt --> gold --> val
90.3%permits labeled
(~586K permits, Tool 1)
47 to 96%MLS builder cleaned
(Tool 2; 34% flagged brokerage)
0 to 98.7%MLS communities cleaned
(Tool 3; 17,533 subdivisions)
94%Albany PDF contractor hit-rate
(Tool 4)

What each resolver does

How it connects to the rest of the stack

Feeds the Market Report (Power BI and the chatbot) through the three dbt joins. It is the join key for builder market share, the competitive space map, and supply.
Shares its dictionary with Parcela. Parcela vendors the same builder master and name-canonicalizer to flag Hayden-owned and competitor-owned land the same way.

How it can expand

Self-contained file. To edit, change the content or flowchart block, keep the CDN script tag, then re-run the inline-mermaid helper. Source: builder_resolving_cc/docs/architecture.html.