Inspiration
Communities have things to say but no good way to say them. Traditional surveys give everyone the same questions regardless of their situation. Focus groups are expensive and reach few people. Neither produces a living picture of what a community is actually experiencing.
I wanted to build something where the AI doesn't just collect data but listens. Where each person's answers shape what the next person gets asked. Where the output isn't a spreadsheet of percentages but a narrative that reads like the community speaking for itself.
The cost of living felt like the right topic. It's universal, it's personal, and everyone has something to say about it.
What it does
Community Pulse is an anonymous feedback platform with a closed AI feedback loop at its core:
- Submit - Three quick questions about cost-of-living pressures (biggest pressure, direction of change, what you've sacrificed)
- Adapt - Gemini analyses your answers alongside the full dataset and generates 1-2 personalised follow-up questions in real time
- Moderate - All free-text is screened by Gemini for PII, hate speech and spam before entering the AI pipeline
- Synthesise - Gemini reads every response and produces a "Community Voice" narrative written in first-person plural ("We are concerned about...")
- Evolve - Themes are automatically extracted every 5 submissions, feeding back into smarter questions and richer narratives
Themes discovered from free-text feed back into question generation and insight prompts. The system gets smarter as more people participate.
How I built it
Stack: Next.js 14 (App Router), TypeScript, Drizzle ORM + SQLite, Gemini API (@google/genai), Tailwind CSS, Framer Motion, Recharts.
Architecture: The app is config-driven. A single TypeScript file (survey.config.ts) controls all questions, branding, AI prompts and page copy. Swap the config and you have a completely different survey for a different community. No database migration needed when changing questions because responses are stored as a JSON blob keyed by whatever field names the config defines.
Four Gemini integration points:
- Adaptive questions - Gemini 3 Flash with low thinking budget for speed. Returns structured JSON validated by Zod schemas. The prompt includes the user's answers, a dataset summary and any AI-discovered themes.
- Content moderation - Gemini 3 Flash screens all free-text in a single batched call. Store-and-gate pattern: submissions are never lost to API errors, but unverified free-text stays excluded from AI queries until moderation passes. Automatic retry with exponential backoff.
- Theme extraction - Gemini 3 Pro in JSON mode. Every 5 submissions it reads all free-text and discovers emergent themes in the community's own language, finding patterns that hardcoded keyword categories could never find.
- Community Voice narrative - Gemini 3 Pro with streaming. The full dataset (stats, sacrifices, adaptive answers, pressure counts, AI themes) is synthesised into a 250-350 word narrative, streamed progressively to the client.
Deployment: Self-hosted on a VPS, with SQLite as a single database file. No managed database services, no serverless quirks. Clone, install, run.
The feedback loop
This is what makes Community Pulse more than four API calls:
Submissions > Theme Extraction > Richer Prompts > Better Questions > Richer Submissions
|
Better Narratives
Three concrete handoff points close the loop:
- Themes to Adaptive Questions: AI-discovered themes (e.g. "social isolation, ~12 mentions") replace flat keyword labels in the question generation prompt, giving Gemini enough context to ask targeted follow-ups about real community patterns.
- Themes to Insight Narrative: The narrative prompt receives a structured themes section with names, frequencies, descriptions and representative quotes, so the Community Voice is grounded in patterns the AI has already identified.
- Adaptive Answers to Future Themes: Follow-up answers become new data points, influencing what themes emerge in future extractions.
Early on, questions are broad. As patterns emerge, the AI probes for nuance, contradictions and underreported experiences.
Stateless re-analysis: a design choice enabled by large context
Theme extraction uses what I'm calling stateless re-analysis. Every extraction reads all raw responses fresh rather than building on previous results. There's no step where themes from round N get summarised and fed into round N+1. The model always works from primary sources.
Many AI pipelines use incremental approaches, feeding previous output as input to the next run to save compute. The cost is information loss: each summary is a lossy compression of the original data and early themes tend to dominate. With stateless re-analysis:
- No information degradation - the model does pattern recognition on the full corpus every time
- Self-correcting taxonomy - if the community's language shifts, theme names and groupings can completely reorganise. A theme called "heating" in extraction #3 can naturally merge into "energy costs" by extraction #6 without any migration logic
- Gemini's large context window makes this viable - rather than needing windowing or summarisation tricks, hundreds of raw responses are fed directly
It borrows from the same philosophy as event sourcing in databases: keep the raw events, derive state on read rather than maintaining it incrementally.
Challenges I ran into
Making the adaptive questions feel natural. Early iterations generated questions that felt clinical or repetitive. The prompt needed careful engineering, including the dataset summary and AI-discovered themes, to produce questions that feel like a thoughtful follow-up conversation rather than a bot.
SQLite and deployment. Choosing SQLite meant the app couldn't run on serverless platforms like Vercel (ephemeral filesystem). Self-hosting on a VPS solved this and actually reinforced the project's "self-hostable" narrative. Any community org can deploy it on their own infrastructure.
Content moderation edge cases. A cost-of-living survey naturally attracts frustrated, emotional language. The moderation prompt needed to be lenient enough to accept genuine expressions of hardship while still catching actual abuse, PII about others and spam.
What I learned
- Config-driven architecture pays off fast. Building the survey as a data structure rather than hardcoded components meant I could iterate on questions, prompts and copy without touching any UI code.
- Feedback loops are the real product. The individual Gemini calls are straightforward. The value comes from connecting them: themes feeding into prompts, prompts producing better data, better data producing better themes.
- Streaming matters for perception. A 250-word narrative takes several seconds to generate. Streaming it progressively makes the wait feel like watching the AI think rather than staring at a spinner.
What's next
- Multi-topic deployment - The config-driven architecture is ready for it. A housing association, a foodbank, a local council: each gets their own config, same codebase.
- Export reports - AI-generated summaries formatted for council meetings and funding bids.
- Longitudinal tracking - Insight snapshots are already stored with timestamps. Showing how the Community Voice evolves over weeks and months is the next visualisation challenge.
- Google Search grounding - Cross-referencing community data with real-world context ("Housing is your top concern at 31% and UK mortgage rates rose to X% this quarter").
Built With
- drizzle
- framer-motion
- gemini
- google-ai
- next-js
- recharts
- sqlite
- tailwind-css
- typescript
Log in or sign up for Devpost to join the conversation.