← back to Unclaimed Property Platform
README.md
74 lines
# National Unclaimed-Property Platform (prototype)
A defensible, **government-partnered (B2G)** replication of the MissingMoney model: ingest
**authorized** state unclaimed-property feeds, normalize them into a common NAUPA-aligned
schema, offer **free** national consumer search with **masked** results, and route claims
back to the state of record. Revenue comes from **governments** (participation /
implementation / white-label / workflow), never from taking a cut of a claimant's recovery.
> **This repo is a tested prototype on 100% SYNTHETIC data.** It never connects to a real
> state feed, never scrapes, never contracts. It runs entirely locally at **$0** (Python
> stdlib — no installs needed for the demo or tests).
## See it in 60 seconds
```bash
make demo # narrated end-to-end tour: ingest (3 formats) → search → suppress → claim
make test # run all 11 test suites
make serve # start the masked-search HTTP endpoint on http://127.0.0.1:8799
```
`make demo` runs the whole vertical slice on synthetic data and narrates each step. No
dependencies, no network, no keys.
## What actually works (every subsystem is tested + runs on real persistence)
| Subsystem | What it does | Proof |
|---|---|---|
| **Ingestion** | One idempotent pipeline, three formats: CSV, NAUPA II (fixed-width), NAUPA III (XML, namespace-tolerant, XXE-guarded). Immutable raw archive + provenance. | `tests/test_ingest_and_match`, `test_cycle3_naupa_and_blocking`, `test_cycle6_naupa3` |
| **Reconciliation** | Compares parsed vs the state's control total → catches **silent truncation**. | `test_cycle11_reconciliation` |
| **Normalize / mask** | Constant-width masking (no length-leak confirmation oracle); coarse amount bands. | `test_cycle2_hardening` |
| **Entity resolution** | Fellegi-Sunter-style probabilistic matcher; phonetic key + blocking for candidate generation; coverage-fair across scripts; **distinct from search ranking**. | `test_cycle3…`, `test_cycle4_fairness` |
| **Public search** | Masked, fail-closed projection allowlist, **real** sliding-window rate limiter, apostrophe/hyphen recall fix. Runnable HTTP endpoint. | `test_cycle5_search` |
| **Suppression** | State takedown removes a record from search immediately; data + history retained; audited. | `test_cycle8_suppression` |
| **Claims** | Explicit state machine (claimant can't self-approve), real persistence, **transactional outbox** with at-least-once delivery + bounded retry. | `test_cycle9_claims_persistence`, `test_cycle10_outbox_worker` |
## The real critical path is NOT code
The software risk is largely retired. A **real deployment is blocked on authorized state
data**, which requires a signed **data-use agreement per state** (or a master NAUPA
relationship) — a human/legal negotiation no code cycle advances. The fill-in template +
go-to-market sequence live in **[`docs/04-data-use-agreement-template.md`](docs/04-data-use-agreement-template.md)**.
Sequence: retain counsel → LOIs from ≥3 states → execute DUAs → *then* connect real feeds.
## Guardrails (enforced, not just documented)
1. **No unauthorized scraping / no real data** — ingestion asserts a synthetic-only source
allowlist; `.gitignore` + a **pre-commit tripwire** block real-feed formats and
SSN-shaped strings from ever being committed.
2. **State is adjudicator + payer** — the platform is a workflow processor; `APPROVED`/
`PAID_BY_STATE` require a `state:` actor. No fund custody anywhere.
3. **Masked results only** — anonymous search returns a fail-closed allowlisted projection.
## Repo map
```
docs/ thesis+guardrails, 50-state acquisition matrix, architecture, roadmap+budget,
DUA template, jurisdictions.json, LOOP-LEDGER (build history)
db/ canonical schema (non-destructive versioning, provenance, outbox, audit)
services/ ingestion (csv/naupa2/naupa3) · common (normalize/mask/rate_limit/sqlite) ·
matching · search (service + runnable HTTP) · claims (state machine + outbox)
scripts/ demo_tour.py · pre-commit-tripwire.sh
tests/ 11 suites, all green, stdlib-only
```
## Production notes
The prototype substitutes SQLite + filesystem + an in-DB search for the production stack
(PostgreSQL system-of-record + object storage + OpenSearch). Service interfaces are
unchanged — only the concrete adapters differ. `requirements.txt` lists the production
reference deps (FastAPI/OpenSearch); `make install` if you want to run those paths.
Built over an 11-cycle DTD-decided, contrarian-gated autonomous loop — see
[`docs/LOOP-LEDGER.md`](docs/LOOP-LEDGER.md).