OSINT: Business Lead Identification
Turns public property records into a ranked list of business owners and the people who run them.

Reads the parcel exports that city and county assessors publish through ArcGIS REST and Socrata open-data APIs. The tool labels each owner name, merges spelling variants, and groups the owner's properties in those records into one portfolio, largest first. Across about 486,000 properties in 8 cities, that narrowed 414,000 owners to about 23,500 businesses and conglomerates to target as outreach leads.
For the top owners, it pulls state business filings to name the managers, members and officers behind each LLC. Every fact links to the record it came from.
How it works
- Each export is pulled page by page and cached. A pull that fails or comes back empty never replaces the last good copy; the run stops and prints the command to resume.
- Every owner name gets one of nine tiers, first match wins: utility, government, religious, HOA, trust, trade, business, ambiguous, individual. Trusts go to a review list instead of being dropped.
- "HOLDINGS, L.L.C." and "HOLDINGS LLC" count as one owner. Each owner's parcels roll up into property count, cities, and total assessed value.
- Registry matches add the entity ID, status, registered agent, and the people named in annual reports and officer filings.
- Results open in a local dashboard: records, then portfolios, then one owner's profile.
Built with
Python 3 (standard library only) · SQLite · ArcGIS REST and Socrata open-data APIs · HTML, CSS, JavaScript · pytest
Selected code
| File | What it shows |
|---|---|
| leads/classify.py | Ordered tier rules, word-boundary matching, and how couples are told apart from small businesses. |
| leads/portfolio.py | One portfolio per owner: merges spelling variants, ranks by size, sets trusts aside. |
| leads/local_server.py | Dashboard security: loopback-only bind, Host-header check, Origin and CSRF checks on writes, no CORS, strict CSP. |
| leads/feed_guard.py | Stops the run when a source fails or returns nothing, and keeps the last good data. |
| tests/ | 58 offline tests: tiering edge cases, portfolio totals, feed failures, and attacks on the local server. |
| tools/make_demo_corpus.py | Builds the demo data set with the same classifier and portfolio code. |
Design notes
- An ampersand alone usually means a married couple, not a company. In this data, couples outnumber unsuffixed businesses about 1,000 to 1, so a name needs a real business signal to count as a lead.
- The dashboard listens only on 127.0.0.1, with no option to change it, and refuses requests that could come from another site.
- Each property links to its parcel record and each person to the filing that names them, so a lead can be checked before anyone acts on it.