Inspiration

Real focus groups and polls cost thousands of dollars and take weeks — so most teams ship products, run ad campaigns, and propose policies with almost no audience feedback at all. We were struck by a recent project that built ~10,000 synthetic San Francisco residents and forecast an election to roughly 81% accuracy. If a synthetic population can approximate a real one, then anyone should be able to pre-test an idea against a realistic, segmentable crowd — instantly and for free. That's SimCrowd.

What it does

SimCrowd builds a synthetic population grounded in real US-Census (ACS) demographics, then lets you ask it anything:

  • "Would 25-34 urban renters prefer Ad A or Ad B?"
  • "Will this neighborhood support the new zoning measure?"
  • "Which of these three taglines resonates with high-income suburban parents?"

You get an overall answer plus a breakdown by demographic segment, with a confidence signal, in seconds — a focus group that costs ~$0 and runs on demand.

How we built it

  • Frontend: Next.js (App Router), dashboard scaffolded with v0, deployed on Vercel.
  • Primary backend: Amazon Aurora PostgreSQL + pgvector.
  • AI: Amazon Bedrock — Claude for persona role-play, Titan v2 for embeddings.
  • ORM / migrations: Drizzle.

The pipeline:

  1. Stratified-sample personas from real census marginals (not random) so the population matches reality.
  2. Embed each persona's psychographic profile with Titan and store it in an HNSW-indexed vector(1024) column alongside its relational attributes.
  3. Cluster the embeddings with k-means to find representative archetypes.
  4. Role-play one representative per cluster with Claude, then weight-extrapolate to the whole population.
  5. Aggregate with SQL GROUP BY to segment results by income, geo, education, and occupation.

Why Aurora PostgreSQL + pgvector (the deliberate architecture)

SimCrowd needs both worlds at once:

  • Relational filtering and aggregation to segment the crowd (WHERE geo = 'NYC' AND age BETWEEN 25 AND 34 GROUP BY income_band), and
  • Vector similarity to cluster personas by psychographics.

A pure vector database can't do the demographic GROUP BYs; a pure relational database can't cluster psychographics. pgvector-on-Aurora is the exact fit — relational rows with an HNSW-indexed embedding. That same clustering is also what makes SimCrowd cheap: we role-play one representative per cluster instead of every persona, and the app reports the realized reduction — typically 10–50× fewer LLM calls — on every run.

\( \text{cost} \approx k \text{ LLM calls, not } N \quad (k \ll N) \)

Challenges we ran into

The core tension was cost vs. fidelity. Naively simulating N people means N LLM calls, which doesn't scale. Clustering representatives solved the cost problem but forced us to weight the extrapolation carefully so the aggregate stays honest. We also kept populations stratified from census marginals rather than randomly generated, so the results remain defensible rather than arbitrary.

What we learned

  • Embeddings aren't just for search — as a clustering substrate they turn an expensive per-item LLM workload into a cheap per-archetype one.
  • The most persuasive demo feature isn't the answer, it's the segment breakdown — that's what makes a synthetic crowd feel real.
  • Hybrid relational + vector workloads have a natural home, and Aurora + pgvector let us keep both in a single query path.

What's next

  • Validation mode: back-test against a known poll or election and surface the accuracy number for credibility.
  • Scenario A/B compare: two ad creatives or policy framings side by side.
  • Higher fidelity: real ACS ingestion + joint-distribution (PUMS / iterative proportional fitting) sampling.

Responsible use: every persona is a simulation/estimate, never a real individual. Results display confidence and their census basis.

Built With

Share this project:

Updates