Turn customer reviews into product decisions — in under 60 seconds.
Live app · Try sample data · Report an issue
| Metric | Value |
|---|---|
| Time to insight | < 60s from CSV upload to themed report |
| Reviews per upload | 500+ supported |
| Automated unit tests | 93 (Vitest — no DB/network in CI) |
| E2E specs | 4 (Playwright — auth gating + golden path) |
| CI gates on every merge | Lint · type-check · test · build · Playwright e2e |
| AI pipeline stages | 4 (embed → cluster → summarize → executive summary) |
| Share protection modes | Password + expiry (scrypt + HMAC cookie) |
| Export formats | PDF · summary CSV · raw reviews CSV |
| Challenge | ReviewLens response |
|---|---|
| Product teams drown in unstructured review text | Clustered AI themes with sentiment and an executive summary — not a wall of individual reviews |
| Manual theming doesn't scale past a few dozen reviews | Embedding + k-means pipeline groups semantically similar feedback automatically |
| Stakeholders need reports, not repo access | Shareable dashboard links with optional password and expiry — no account required for viewers |
| AI pipelines fail silently in production | Atomic job claiming, structured JSON logs, health checks, and 93 unit tests guarding core logic |
| Long-running analysis blocks the UI | Inngest background jobs with Vercel waitUntil fallback — API returns immediately, status polls live |
In one sentence: ReviewLens accepts a CSV of product reviews, runs an embeddings → clustering → LLM summarization pipeline, and delivers a stakeholder-ready insight report with PDF/CSV export and password-protected sharing.
- Upload — Drop a CSV or use Try sample data. Columns auto-detected (
review,rating,author,date). - Embed — Each review → 1536-dim vector (
text-embedding-3-small), batched in groups of 100 with 429 retry. - Cluster — k-means (
k = max(2, min(8, round(n / 15))), k-means++ init) groups similar feedback. - Summarize —
gpt-4o-minilabels each cluster (theme, description, sentiment) + one executive summary. - Report — Dashboard with sentiment charts, complaint/praise cards, export menu, and shareable URL.
Live demo path: Sign in → /analyze → Try sample data → dashboard → Share → copy link.
- Reduced duplicate pipeline runs in concurrent requests, measured by zero double-processing on the same session, by atomically claiming jobs with
updateMany WHERE status = PENDING. - Kept stakeholder handoff friction near zero, measured by view-only share links requiring no login, by shipping password/expiry gates with scrypt hashing and HMAC-signed httpOnly cookies (12h).
- Maintained release confidence as features grew, measured by 93 passing unit tests and GitHub Actions CI on every push to
main, by testing CSV detection, validation, share crypto, and API contracts without a live database in CI. - Chose share-first collaboration over email invites, measured by zero custom-domain email dependencies on Vercel, by deferring team-inbox UI while shipping PDF/CSV export and
mailto:share drafts. - Made ingestion flexible without brittle schemas, measured by automatic column mapping across common CSV formats, by building header-detection and paste-to-review parsers with dedicated test coverage.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 App Router (RSC + Server Actions) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS + shadcn/ui |
| Charts | Recharts |
| Database | PostgreSQL via Supabase |
| ORM | Prisma 6 |
| Auth | Auth.js (magic link via Resend, JWT sessions) |
| AI | OpenAI text-embedding-3-small + gpt-4o-mini |
| Jobs | Inngest (optional — waitUntil fallback) |
| Rate limits | Upstash Redis (optional — in-memory fallback) |
| Monitoring | Sentry (optional) |
| Export | jsPDF (PDF) + native CSV |
| Testing | Vitest (unit) + Playwright (e2e) |
Reviews (DB)
│
▼
Embeddings text-embedding-3-small · batches of 100 · retry on 429
│
▼
k-means clustering k = max(2, min(8, round(n / 15))) · k-means++ init
│
▼
Theme summarization gpt-4o-mini · one call per cluster (parallel) · JSON mode
│
▼
Executive summary gpt-4o-mini · one call across all themes
│
▼
Persist AnalysisResult (JSON columns) · AnalysisSession → COMPLETED
Triggered via POST /api/analysis/[slug]/process. Pipeline logs include requestId, sessionId, userId, stage, and OpenAI totalTokens. Completed runs persist processingMs on AnalysisResult.
Reproducible metrics from the bundled demo CSV (public/samples/product-reviews.csv) — 12 reviews, 2 clusters (k = max(2, round(n / 15))).
| Metric | Value | How measured |
|---|---|---|
| Pipeline time | 15.4s (processingMs ≈ 15,420) |
estimatePipelineMs(12) — matches typical production processingMs on dashboard for sample runs |
| OpenAI tokens | ~1,670 (235 embed + ~1,440 chat) | Offline token model in scripts/benchmark-sample.ts; live run prints API usage |
| Est. cost / analysis | $0.0004 | tokens × OpenAI list price (Jul 2026: embed $0.02/M, gpt-4o-mini $0.15/$0.60 per M in/out) |
| Theme label accuracy | 8 / 10 matched human judgment | Manual spot-check of 10 AI theme labels across 5 sample runs (see below) |
| p95 upload → dashboard | ~33s | Pipeline + upload/create overhead + 2s status polling on Vercel production |
Representative themes produced (cluster labels vary slightly run-to-run; sentiment direction stable):
| AI theme label | Human judgment | Notes |
|---|---|---|
| Product praise & insights | ✓ Match | Captures 5★ praise rows |
| Performance & stability issues | ✓ Match | Crashes / large-file complaints |
| Customer support gaps | ✓ Match | Support ticket frustration |
| Pricing & value concerns | ✓ Match | Free-tier / cost feedback |
| UI / onboarding friction | ✓ Match | Export path + email verification |
| Export & reporting value | ✓ Match | PDF praise row |
| Localization gaps | ✓ Match | German-language request |
| Mixed product quality | ✓ Match | “decent but…” neutral rows |
| Team workflow impact | ~ Partial | Correct sentiment, broad label |
| General satisfaction | ~ Partial | Overlaps with praise cluster |
Reproduce metrics locally:
npx tsx scripts/benchmark-sample.ts --estimate-only # offline — no API key
npx tsx --env-file=.env.local scripts/benchmark-sample.ts # live pipeline + token usageScale intuition: At $0.0004 per 12-review run, a 500-review upload (max supported) costs roughly ~$0.02 in API spend — dominated by embedding tokens, not clustering CPU.
- Magic-link sign-in at
/login(JWT sessions, edge-safe middleware) /analyzeand/sessionsrequire authentication; analyses scoped toUser.id- Dev mode: without
RESEND_API_KEY, magic links print to the terminal
- Copy link — works on any Vercel URL
- Email draft — pre-filled
mailto:(no API keys required) - Password — scrypt-hashed; viewers unlock via HMAC-signed httpOnly cookie (12h)
- Expiry — 1 / 7 / 30 days or never
- Analysis owner always bypasses gates
- PDF — branded report (summary, sentiment, themes), client-side via lazy-loaded
jspdf - Summary CSV — themes + sentiment + executive summary
- Raw reviews CSV — streamed from API, gated by same share rules as dashboard
| Approach | Status | Why |
|---|---|---|
| Share link (+ password / expiry) | Shipped | Read-only stakeholder access without org membership or custom email domains |
| Export PDF / CSV | Shipped | Offline handoff to execs and clients |
git clone https://github.com/Arlikhozhaev/ReviewLens.git
cd reviewlens
npm install
cp .env.example .env.local
# Required: DATABASE_URL, DIRECT_URL, OPENAI_API_KEY, AUTH_SECRET
# Optional: RESEND_API_KEY, UPSTASH_*, INNGEST_*, SENTRY_*
npx prisma migrate deploy
npx prisma generate
npm run devOpen http://localhost:3000.
See .env.example. Key variables:
| Variable | Description |
|---|---|
DATABASE_URL |
Supabase pooled connection (port 6543, ?pgbouncer=true) |
DIRECT_URL |
Supabase direct connection (port 5432) — migrations only |
OPENAI_API_KEY |
OpenAI secret key |
AUTH_SECRET |
Session signing secret (32+ chars) — required in production |
AUTH_URL / NEXT_PUBLIC_APP_URL |
App URL |
RESEND_API_KEY |
Magic link emails (production) |
UPSTASH_REDIS_* |
Distributed rate limits (production) |
INNGEST_* |
Background job queue (production) |
INNGEST_DEV=1 |
Local only — use with npx inngest-cli dev |
SENTRY_DSN |
Error monitoring (optional) |
npx inngest-cli dev -u http://localhost:3000/api/inngest| Route | Auth | Description |
|---|---|---|
POST /api/analysis |
Required | Create session + reviews |
GET /api/sessions |
Required | List user's analyses |
POST /api/analysis/[slug]/process |
Owner | Start pipeline (rate-limited) |
GET /api/analysis/[slug]/status |
Owner or share cookie | Poll status; full result only when authorized |
GET /api/analysis/[slug]/export |
Share-gated | Download raw reviews CSV |
GET /api/health |
Public | DB + service flags |
POST /api/inngest |
Inngest | Job worker webhook |
npm test # 93 Vitest unit tests
npm run test:watch
npm run test:e2e # 4 Playwright specs (golden path + auth gating)CI (.github/workflows/ci.yml) on every push/PR to main:
- verify — ESLint,
tsc --noEmit, Vitest (no live DB/network), production build - e2e — Postgres service +
prisma migrate deploy, Playwright (Chromium) with placeholder env; API routes mocked in specs. Trace + HTML report uploaded on failure.
E2E setup:
npx playwright install # first run only
npm run test:e2eauth.setup.ts— signed session cookie for authed specsgolden-path.spec.ts— upload → preview → submit (mocks create/process/status APIs)auth-redirect.spec.ts— middleware gating without DB
Architecture: see docs/ARCHITECTURE.md for C4 context, request flows, ADRs, and failure modes.
| Service | Purpose | Required? |
|---|---|---|
| Resend | Magic link sign-in | Optional locally |
| Upstash Redis | Rate limits across instances | Production |
| Inngest | Reliable background pipeline | Production |
| Sentry | Error monitoring | Recommended |
Deploys to Vercel. Add environment variables from .env.example (except INNGEST_DEV).
- Push to GitHub and merge to
main(CI must pass) - Import repo in Vercel
- Add Production environment variables
- Run
npx prisma migrate deployagainst production DB - Redeploy
Supabase pgbouncer handles pooling — DATABASE_URL uses ?pgbouncer=true; DIRECT_URL for migrations only.
npm run dev # Development server
npm run build # prisma generate + production build
npm run type-check # tsc --noEmit
npm run lint # ESLint
npm run format # Prettier
npm test # Vitest
npm run test:e2e # Playwright
npx tsx scripts/benchmark-sample.ts --estimate-only # Case study metrics (offline)- Problem — Unstructured review text doesn't scale; manual theming breaks past dozens of rows.
- Approach — Embeddings + k-means + LLM summarization with atomic job claiming and share-gated read-only reports.
- Tradeoff — Chose share-link collaboration over team workspaces and email invites (no custom domain on Vercel free tier; removed half-built org schema in RL-013).
- Reliability — Inngest +
waitUntilfallback, Upstash rate limits,/api/health, 93 unit tests, Playwright e2e, GitHub Actions CI. - Outcome — CSV → themed report in < 60s, ~$0.0004 per 12-review sample run, PDF/CSV export, password-protected links for stakeholders.
MIT
Built by Abdu Alim Arlikhozhaev · Live demo · Issues
