Technical Architecture
The hard part of Privyus is the data work under the screens. Records about one person sit in many sources, and each one must join a single node with a link to the original record. The demo shows layer 4 (the product experience) with fixed and illustrative data. The real product adds three layers under it: collect, resolve, and store.
- Public records
- Federal sources first, then the 50 states
- Available today
- Standard databases, connectors, and LLM extraction
- One topic, federal sources
- A goal for a small senior team
Data Volume
~108
Hundreds of millions of rows
~107
Tens of millions of rows
~107
Tens of millions of pages
→109
Approaching billions
×n
Several times all of the above
Hard Problems at This Scale
Name Matching Without IDs
One person appears under a different name in each source. Most records carry no common identifier.
A Source on Every Edge
Each edge keeps its source document with the fetch time, the URL, and a content hash.
Sub-Second Answers
Queries stay fast while new filings arrive every day and the graph changes under them.
Four System Layers
Resolve and Connect
Records about the same person become one node, and each record becomes typed edges. This step is what a customer pays for because no single public database does it.
- Normalize
- Clean names, dates, amounts, and addresses into one format.
- Entity Resolution
- Start from public IDs (Bioguide, FEC, LDA). Use fuzzy matching and an LLM check for records without IDs. Send uncertain matches to a human review queue. Never guess silently.
- Relationship Extraction
- Each record becomes typed edges: member voted on bill, lobbyist contacted office, committee contributed to member, member traveled to place. Each edge keeps its source document and its date.
- Social Network Precedent
- Large social networks resolve identities the same way. They combine many weak signals into one confident identity.
Entity Resolution
Hartley, Ellen
1.00HARTLEY, ELLEN M.
1.00Hartley (R-OH)
1.00HARTLEY, ELLEN
0.93Sen. E. Hartley
0.97
Sen. Ellen Hartley
privyus_id per_01H…
crosswalk (key_type, key_value, privyus_id, confidence, decided_by) bioguide H001234 per_01H… 1.00 public_id fec_candidate S6OH00123 per_01H… 1.00 public_id senate_lis S401 per_01H… 1.00 public_id lda_contact "HARTLEY, ELLEN" per_01H… 0.93 model travel_filer "Sen. E. Hartley" per_01H… 0.97 reviewerStable IDs
A rebuild reuses an existing ID. It creates a new ID only for a new entity, so links, watchlists, and saved checkpoints never break.
Human Review Queue
Uncertain matches go to a human reviewer. Reviewer decisions (“same person”, “not the same person”) live in Postgres, and every rebuild applies them first.
Release Checks
The release compares the new IDs with the last release. A sudden jump in merges or new IDs stops the release.
Serving Data Model
- A meeting or a trip is a node (type = event). The attendees connect to it. This is the Meetings → Private meeting → attendees chain in the demo.
- Every edge has a source_doc_id. This is the source chip on every answer.
- Money from a company PAC or its employees links to the company with an edge that has a confidence score and a source. It is never a silent merge.
One-Hop Queries
| Demo action | Query | See it in the demo |
|---|---|---|
| Select a bill, see its supporters | edges WHERE dst_id = bill AND type IN (sponsored, cosponsored) JOIN entities | S. 456 supporters(opens the demo in a new tab) |
| Select a member, see the categories | entity_stats WHERE entity_id = member | Sen. Ellen Hartley(opens the demo in a new tab) |
| Open Meetings | edges WHERE src_id = member AND type = attended JOIN entities -- events | Meetings(opens the demo in a new tab) |
| Open a private meeting | edges WHERE dst_id = meeting AND type = attended | Private meeting(opens the demo in a new tab) |
| “Which defense contractors gave to this senator?” | edges WHERE dst_id = member AND type = contributed JOIN entities -- orgs | Defense contractors(opens the demo in a new tab) |
| Open a source | documents WHERE doc_id = edge.source_doc_id | Visitor log source(opens the demo in a new tab) |
How the Graph Is Drawn
Template
The agent maps the question to a tested query template and fills in the parameters. It does not write free-form SQL.
Graph Delta
It returns the nodes and edges to add, with the answer text, the citations, and the steps. The demo already uses this exact shape.
Layout
The app keeps the existing nodes in place and lays out the new nodes around the selected node with a force layout.
Map Arcs
The map uses the same edges. Each organization and member has a state, so a contribution becomes an arc from one state to another.
// trimmed: 3 of 5 meetings, 4 of 5 steps{ "id": "hartley-meetings", "steps": [ { "label": "Focus Meetings", "delta": { "add": { "entities": [], "edges": [] }, "focus": "cat-meetings" } }, { "label": "Query meeting records", "delta": { "add": { "entities": [], "edges": [] } } }, { "label": "Populate meetings", "delta": { "add": { "entities": ["meeting-private", "meeting-meridian", "meeting-committee"], "edges": ["cat-meetings-meeting-private", "cat-meetings-meeting-meridian", "cat-meetings-meeting-committee"] } } }, { "label": "Render", "delta": { "add": { "entities": [], "edges": [] }, "focus": "cat-meetings", "expand": "cat-meetings" } } ], "answer": "Five meetings appear in Hartley’s office records, …", "citations": ["disc-meetings", "disc-private"]}// entities{ "id": "meeting-private", "type": "detail", "label": "Private meeting", "sublabel": "Jun 17, 2026" }// edges{ "id": "cat-meetings-meeting-private", "source": "cat-meetings", "target": "meeting-private", "label": "meeting", "date": "2026-06-17", "sourceIds": ["disc-private"] }// documents{ "id": "disc-private", "kind": "DISCLOSURE", "title": "Visitor log · Jun 17, 2026", "geo": { "from": "DC", "to": "OH" } }One Shared Shape
Each step completes when its part of the graph appears. The edge carries the id of its source document, so the answer can show a source chip. The document carries the two states that the map draws as an arc.
A real backend returns this same shape, so it can replace the fixed data file without a rebuild of the screens.
Agent Access
PlannedWeb App
Analysts ask in plain words and explore the graph. Each answer shows its source chips.
REST API
Customer software calls the same query templates. Each response is JSON with the answer, the graph delta, and the source ids.
MCP Server
MCP (Model Context Protocol) is an open standard that lets AI assistants call outside tools. Any agent that supports it can search Privyus and cite each filing it uses. Each agent platform that connects brings Privyus records into the tools its users already use.
Agent Research Session
A fund’s research agent asks one question and makes five tool calls. Each result adds nodes to the same graph that the app draws. The records, dates, and amounts come from the demo data.
Which defense contractors met with Sen. Ellen Hartley’s office or gave to her campaign in 2026?
search_entities({ query: "Ellen Hartley" })per_hartleySen. Ellen HartleyR-OHexpand_connections({ id: "per_hartley", type: "attended", from: "2026-01-01" })evt_meridian_0224Meridian policy briefingFeb 24evt_ukraine_0429Ukraine embassy delegationApr 29evt_private_0617Private meetingJun 17expand_connections({ id: "evt_private_0617", type: "attended_by" })per_vossClara VossMeridian Public Affairsper_pierceNathaniel PierceAegis Systemsper_senDr. Priya SenAtlantic Security Instituteexpand_connections({ id: "per_hartley", type: "contributed", from: "2026-01-01" })org_aegisAegis PAC$18,750 · Apr 8org_redwoodRedwood PAC$7,625 · Aug 21get_sources({ edges: ["edg_private_pierce", "edg_aegis_hartley"] })[1] doc_disc_privateVisitor log · Jun 17, 2026urlhttps://…/visitor-log-0617.pdffetched_at2026-06-18T09:14Zhashsha256:9f2c…41ab[2] doc_fec_hartleyFEC Schedule A · Aug 2026urlhttps://…/schedule-a/S6OH00123fetched_at2026-08-22T06:02Zhashsha256:5d07…c3e2
Nathaniel Pierce of Aegis Systems attended a private meeting in Hartley’s office on Jun 17, 2026 1. FEC receipts list 2026 contributions to her campaign from Aegis PAC ($18,750) and Redwood PAC ($7,625) 2.
MCP Tools
| Tool | What it returns | In the session |
|---|---|---|
search_entities | People, organizations, bills, and filings that match a name or a topic | Call 1 |
get_profile | One entity with its category counts (votes, trips, meetings, contributions) | |
expand_connections | The one-hop neighbors of an entity, filtered by edge type and date | Calls 2 to 4 |
get_sources | The original filings behind an edge, with URL, fetch time, and hash | Call 5 |
find_path | The shortest documented chain between two entities | |
watch | Notifications when a new record about an entity arrives |
Stores and Releases
| Store | Holds | Answers | Example technology |
|---|---|---|---|
| Raw document store | Every original filing | “Show me the source” | Amazon S3 |
| Graph database | People, organizations, bills, events, and edges | “How is A connected to B?” | Neo4j or Amazon Neptune |
| Search and vector index | Full text and embeddings | “Find statements about Ukraine aid” | OpenSearch or pgvector |
| Analytics warehouse | Counts and trends over time | Dashboard counters, Radar, trends | ClickHouse, Snowflake, or BigQuery |
| App database | Users, watchlists, checkpoints | The personal workspace | Postgres |
Start with one Postgres database, and split out stores as the data grows. A columnar database or Postgres answers the one-hop queries above; a graph database becomes necessary only for multi-hop path questions.
Release Safety
Parallel Build
Each release builds new serving tables next to the live ones.
Release Checks
Row counts, ID churn, empty fields, and a fixed set of benchmark queries compared with the last release.
Atomic Swap
The new tables replace the live ones in one atomic swap.
30-Day Rollback
The old tables stay for 30 days, so a rollback takes one command.
Incremental Updates
Each source also runs an incremental update, daily or hourly, that keeps its position with a cursor. This feeds the live activity feed and the watchlist alerts.
Risks and Mitigations
| Risk | Why it matters | How to reduce it |
|---|---|---|
| Entity resolution errors | A wrong link between two people is a false claim. | Public IDs first, a confidence score on each match, human review for uncertain matches, and a source on every edge. |
| Inferred claims about real people | Defamation and reputational risk. | Show records, not conclusions. The product says “the visitor log lists X”, never “X influenced Y”. Legal review of the wording. |
| Scanned and messy documents | OCR and extraction errors. | Schema checks, confidence scores, and a link to the original page. |
| Source changes | A site changes its layout, and a scraper breaks. | Monitoring per connector, alerts, and API sources first. |
| Gaps in the public record | Private meetings are often not disclosed. | Be clear about coverage. Show what each source covers and does not cover. |
| LLM cost and speed | Each question calls the model several times. | Cache common queries, use templates, and use smaller models for extraction. |
| Data terms | Some sites limit automated access. | Check each source’s terms. Prefer official APIs and bulk files. |
Phased Plan
| Phase | Scope | Team | Time |
|---|---|---|---|
| Demo | The current interactive demo, with fixed data. | Done | Done |
| Data foundation | Federal sources: congress.gov, votes, LDA, FEC. Entity resolution for members of Congress and committees. Raw store and one Postgres database. | 2 to 3 engineers | About 3 months |
| First product | The AI query agent with citations, the graph and dashboard on real data, one tracked topic, watchlists and checkpoints, sign-on. Pilot with 3 to 5 design partners. | 3 to 4 engineers, 1 designer | About 3 more months |
| Coverage | FARA, travel, financial disclosures, statements. The human review queue. Alerts. | 4 to 6 people | Ongoing |
| Analysis layers | Sentiment over time, then prediction. Needs the history from phases 1 to 3. | Adds data science | After phase 3 |
Running cost in phases 1 and 2 is mostly cloud hosting and LLM usage. It grows with users and sources. A design partner pilot can run on a modest monthly budget.