A proactive simulation environment for marketers.
Test campaign copy against a simulated population of audience personas before publishing — and find out not just whether people will be offended, but what they think you said.
Offense is not a property of text. It is a relationship between text and an audience. So the primitive isn't a classifier — it's a population of simulated readers, each with distinct values, sensitivities, and readings of context.
Most backlash isn't caused by people being offended by what you said. It's caused by people being offended by what they think you said. Those are different failures with different fixes:
- Misunderstood → the copy is ambiguous → clarify it
- Controversial → the copy is understood and rejected → decide whether you accept that cost
A tool that reports only "risk: 62" can't distinguish them, and therefore can't tell you what to do. crowdLens models interpretation first and reaction second.
Eight questions, twenty-four analysis views:
| Question | |
|---|---|
| 🧠 | What did they understand? |
| ❤️ | How did they feel? |
| Why might they react badly? | |
| 🎯 | Who is affected? |
| 🔍 | What exactly caused it? |
| 📊 | How does it compare to similar campaigns? |
| ✏️ | How can we fix it? |
| 🔄 | Did the fix actually work? |
DESIGN-SYSTEM.md — visual language: palette, typography, components, and how risk bands and Tier-2 findings are rendered.
BacklashTest-Complete-Plan.md — the complete specification: product framing, persona graph architecture, full mathematical treatment, all 24 dashboard panels, validation strategy, build phases, and an honest accounting of the risks.
Backend stages 1-4 implemented (see backend/) — persona engine, scoring, rewrite engine, backlash graph, and live news ingest with a failure corpus. Frontend dashboard implemented (see frontend/) following the design system above.
Two processes: the FastAPI backend on port 8000 and the Vite dev server on port 5173, which proxies /v1/* to the backend.
Backend (Python 3.11 or newer):
cd backend
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
export CLAUDE_CODE_OAUTH_TOKEN='<token printed by: claude setup-token>'
.venv/bin/uvicorn app.main:app --reload --port 8000The credential is read from the process environment in this order: CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY. If none is set the server refuses to start and prints setup instructions. Optional settings use the CROWDLENS_ prefix (for example CROWDLENS_PERSONA_MODEL, CROWDLENS_TARGET_K, CROWDLENS_APIFY_TOKEN, CROWDLENS_SYNC_INTERVAL_HOURS, CROWDLENS_SYNC_ON_STARTUP); see backend/.env.example and backend/app/config.py.
Interactive API docs are served at http://localhost:8000/docs. A simulation request:
curl -X POST localhost:8000/v1/simulate -H 'content-type: application/json' -d '{
"copy": "Our new protein bar — finally, a beef bar that doesn'\''t taste like a cow.",
"brand_intent": "Position our protein bar as great-tasting and high-protein.",
"context_scenario": "dietary_controversy_india"
}'Frontend (Node.js and npm):
cd frontend
npm install
npm run devOpen http://localhost:5173. Without a running backend, the Demo button in the top bar loads a hardcoded report, so the dashboard can be viewed with no backend and no Claude credential.
Tests (backend unit tests):
cd backend
.venv/bin/python -m pytest tests/unit -qcrowdlens/
├── backend/
│ ├── app/
│ │ ├── main.py FastAPI entrypoint; mounts both routers under /v1
│ │ ├── config.py settings (models, selection, scoring weights, ingest)
│ │ ├── api/ routes.py (simulate, runs, panels, personas, scenarios), ingest_routes.py (ingest, corpus)
│ │ ├── personas/ schema, registry, and YAML persona definitions
│ │ ├── router/ N→K persona selection
│ │ ├── orchestrator/ run execution
│ │ ├── composition/ Tier-2 composites
│ │ ├── scoring/ risk index and uncertainty interval
│ │ ├── analysis/ panel projections, backlash graph, redesign
│ │ ├── rewrite/ rewrite engine
│ │ ├── context/ context provider and JSON scenario fixtures
│ │ ├── ingest/ news sources, sync pipeline and scheduler
│ │ ├── corpus/ incident (failure) corpus store
│ │ └── llm/ Anthropic client, credential resolution, JSON salvage
│ ├── tests/unit/ pytest suite
│ ├── pyproject.toml
│ └── .env.example
├── frontend/
│ ├── src/ App.tsx, panels/, components/, lib/ (API client, demo data), styles/
│ ├── public/ map, face and background assets
│ ├── package.json scripts: dev, build, preview
│ └── vite.config.ts dev server on 5173, proxies /v1 to localhost:8000
├── DESIGN-SYSTEM.md
└── BacklashTest-Complete-Plan.md
The scoring weights in the plan are priors to be calibrated against a golden set, not validated constants.