Upload a citizen feedback export (CSV/XLSX/XLS) and get back a short summary, the big overall themes, and a per-question breakdown of what people said — split into "heard often" and "also worth noting", each tagged positive or negative.
Peopulse is a single Next.js (App Router + TypeScript) app: the UI and the analysis API (column detection, grouping, LLM call) both live in this app and deploy together on Vercel.
cp .env.example .env # add OPENROUTER_API_KEY
npm install
npm run devOpen http://localhost:3000. One process — no separate backend to run.
POST /api/upload-csv(multipart, fieldfile) parses the CSV/XLSX/XLS.lib/data-pipeline.tsscores columns to find the feedback column(s), then groups non-empty responses by question.lib/llm.tssends the grouped feedback to an LLM over OpenRouter, using the system prompt inprompts/llm_prompt.md, with retries for transient failures and malformed JSON.lib/normalize.tsshapes the model output into theAnalysisResultthe UI renders (seelib/types.tsfor the full contract).
app/ Next.js App Router pages + API routes
components/ UI components (upload, output, toast, accordion)
lib/ Data pipeline, LLM client, normalization, shared types
public/assets/ Brand images
prompts/ LLM system prompt
legacy/ Archived pre-Next.js stack (reference only, not deployed)
- Set the
OPENROUTER_API_KEYenvironment variable on the Vercel project. - The
/api/upload-csvroute setsmaxDuration = 300(the Hobby plan max) to give the LLM call and its retries enough time; raise it if you're on Pro/Enterprise. - The browser always calls the same-origin
/api/upload-csv— no separate API URL or CORS config needed.
The pre-Next.js version (static HTML/JS frontend + FastAPI backend) is preserved under
legacy/ for reference and rollback. It is not deployed and
the Next app does not import anything from it.