systems / secondhome
SecondHome
Source-to-score qualification with a reason on every record
A score without a reason is just a guess with confidence.
- Period
- July 2026
- My role
- Product lead and sole author. 223 commits in a twelve-day sprint, working with a real-estate operator as the intended user.
- State
- Working MVP with a demo, an acceptance packet, and a thousand passing tests.
- 20+County connectorsscraped portals and open-data feeds
- 1,039Tests passingreal fixtures from captured portal payloads
- 23Schema migrations
- 2Independent signals before a contact counts
- Python
- Postgres
- FastAPI
- Windmill
- Next.js
- MCP
- Anthropic API
- Playwright
Diagram nodes, in flow order: County records (foreclosure · tax · eviction); Normalize (one schema); Enrichment (ownership · equity · skip trace); Agent skills (17 bounded tasks); Contact confidence (identity · authority · reachability); Scoring engine (versioned weights); Call rationale (from evidence); Caller brief (sources · confidence · why); Operator review (captured).
The problem
Distressed-property leads are cheap to list and expensive to qualify. A broker can't research eight hundred foreclosure filings a month. The economics only work if a machine does an unreasonable amount of work per lead and then tells a human, honestly, which ones deserve a phone call and why.
The architecture
Public records come in through county-specific connectors, twenty-plus of them, some of which reverse-engineer ancient government web portals and work around their bugs. Everything is normalized into one schema that keeps the original source on every field. Enrichment providers add ownership, equity, event timing, and contact data behind a shared interface, so a vendor can be swapped without touching the scoring.
Seventeen bounded agent skills do the fuzzy parts: extraction, maturity inference, decision-maker ranking, street-view condition, contradiction detection, and the caller brief itself. Each skill is a versioned task spec loaded by content hash, and the agent's system prompt has hard rules against foreclosure-rescue language.
Every distress signal carries a source class, official or vendor or agent, and every field carries an epistemic label: hard, inferred, estimated, or agent judgment. Contact channels are scored on identity, authority, reachability, and eligibility with named floors, and a channel needs at least two independent signals before it's promoted to primary.
The score is a deterministic, versioned formula over persisted evidence: opportunity value times distress severity times urgency, adjusted by confidence, contactability, and freshness decay. It does no I/O, so the number on the screen composes exactly from the components you can inspect. Then a model writes a plain-language rationale from that evidence, and the whole thing lands in a caller brief served to the operator's app and, through an MCP server, to their agent.
I set three boundaries early. Downstream access is read-only. Scoring is client-specific, not a universal number. Nothing transfers a score without its provenance.
What happened
The product existed. Twelve days, 223 commits, a Next.js review app, a demo script, and an acceptance packet. A thousand tests pass against captured portal payloads. The commercial partnership didn't close, and I'd rather show a good system that didn't sell than hide it.
What I kept
Every scoring system I've built since carries a written reason. It's the cheapest form of explainability there is, and it's the one users actually read.
SecondHome, in twenty-five seconds
SecondHome reads twenty county sources, keeps the source on every field, scores each lead with a formula you can inspect, and writes the reason to call before anyone dials.