Inspiration
It started with a simple, uncomfortable question: who actually gets left out when we say "the news is online now"?
In Nepal, that's not a small group. It's blind and visually impaired people who can't use a screen no matter how well-designed it is. It's elderly people who lost the ability to read comfortably. It's people who never had the chance to build reading fluency in the first place. For all of them, "just read the news app" was never actually an option — and nobody had built anything that treated that as a real product problem instead of an afterthought accessibility checkbox.
There was a second, quieter problem underneath the first one: even people who can read don't actually cross-check outlets. Every Nepali news portal covers the same event slightly differently — one names a source, another doesn't; one gives a date, another skips it — and nobody has the time to read five newspapers to catch the gaps. We realized the same voice-first design that would help a blind user could also surface something almost nobody does today: an honest, factual comparison of what each outlet chose to report and what it left out.
That's Sanksep — a voice-first Nepali news app that aggregates, compares, and reads the news aloud, and now, talks back when you ask it something.
What it does
- Pulls from 12 Nepali news portals via RSS every hour, scrapes full article text, and classifies each article into one of 8 categories using a local LLM.
- Automatically clusters articles that describe the same real-world event, even across different outlets with completely different headlines.
- For every multi-source story, generates a one-liner, a short spoken summary, key facts, a factual gap report (concrete things one outlet reported that another didn't), and a neutral per-source coverage breakdown.
- Reads all of it aloud in Nepali, at a pace tuned for clarity, through a Voice-Only mode that needs no screen interaction at all — including a spoken loop where you can ask a follow-up question or say "back," entirely by voice.
- Now supports live Q&A: ask a question about any story, by voice or text, and get a grounded, spoken answer pulled from that story's actual coverage — not a guess.
- Tracks a lightweight credibility signal per outlet based on how often its reporting shows up corroborated across multiple sources.
How we built it
The pipeline. A Python service polls all 12 RSS feeds, scrapes full article bodies with newspaper3k, and classifies each one with Gemma 3:4b running locally through Ollama. Clustering doesn't use embeddings — it uses token overlap with Union-Find: we extract the distinctive words from each headline (filtering out grammar words, pronouns, and generic verbs with a two-layer stopword list — one static, one dynamically detected per news cycle via TF-IDF, so election-season words don't drown out the signal) and group articles that share enough of them. Source-diversity enforcement makes sure three articles from the same outlet never fake a "trending" story.
The analysis engine. For every cluster with 2+ sources, we generate three tiers of summary and the factual-gap comparison using the same local model, deliberately avoiding vague "tone" or "bias" scoring in favor of concrete, checkable facts — names, numbers, quotes — because "Source A mentioned माघ १०, Source B didn't" is something you can actually verify and something that's actually useful read aloud.
The backend. Originally scoped for Supabase, we migrated to Neon (serverless Postgres) with a Node/Express API. Six tables track sources, articles, clusters, and now Q&A pairs, with realtime-friendly polling so the Flutter app gets new trending stories without hammering the API.
The Flutter app. Two interaction modes — Normal (touch, with a mic button) and Voice-Only (fully spoken) — and two tabs, समाचार and तुलना. TTS runs in Nepali at a deliberately slowed rate for clarity; STT handles both navigation commands and live questions.
The Q&A layer (RAG). This was the newest piece: live questions hit a keyword-retrieval search over our own Postgres data — using full-text search (tsvector/ts_rank) with prefix matching to handle Nepali's postposition-heavy morphology — and the retrieved context, never the open web, is handed to a fast Gemini model with a system prompt that forces it to answer only from that context, or explicitly say it doesn't know.
Challenges we ran into
Embeddings failed on our own language. We assumed a modern multilingual embedding model would handle Nepali clustering out of the box. We tested paraphrase-multilingual-MiniLM-L12-v2 on real same-story article pairs and got a cosine similarity of 0.28 on two articles describing the literal same event — worse than useless. Devanagari is badly underrepresented in these models' training data. We threw the embedding approach out entirely and rebuilt clustering around plain token overlap, which correctly caught every case the embeddings missed.
Postgres full-text search doesn't stem Nepali. When we built the RAG retrieval layer, questions kept coming back empty even when relevant articles clearly existed. The cause: Postgres's 'simple' text-search config does exact-lexeme matching, and Nepali glues postpositions directly onto word stems (ट्रम्प + को → ट्रम्पको), so a bare keyword from a question never matched the inflected form actually stored. Switching to prefix matching (word:*) fixed it.
Retrieval precision vs. hallucination. Early on, a single generic overlapping word could pull an entirely unrelated story into a question's context — we caught a case where an off-topic question surfaced an irrelevant cluster purely because of a shared common verb. We fixed this by re-ranking retrieved candidates by how many distinct keywords they actually matched, rather than trusting raw text-search rank alone, mirroring the same "count real overlap, don't trust a black box" philosophy we'd already proven out in clustering.
Free-tier API limits, twice. We initially wired the Q&A layer to a newer Gemini model and immediately hit a zero-quota rate limit on our own key. We had to actually query the model list and test several candidates live to find one with working free-tier headroom.
The assistant answering questions it had no business answering. Once grounding worked, a new failure mode appeared: "hello," "what can you do," and "how's the weather" were all being run through the full news-retrieval pipeline, finding nothing, and returning a generic "not enough information" message — technically correct, but a broken-feeling experience for something as simple as a greeting. We added a small, deliberately narrow small-talk detector that intercepts these before retrieval ever runs, while leaving genuinely out-of-scope factual questions (like weather) correctly declined with an explanation of why, not a vague failure.
Accomplishments that we're proud of
- Proving, with a real measured number, that the "obvious" AI approach doesn't work for Nepali — and shipping a working alternative instead of a caveat.
- A Q&A system that we can actually show refusing to hallucinate, live, on a real off-topic question, rather than just asserting it's "grounded."
- A Voice-Only mode where a user never has to touch the screen, including for follow-up questions — not just for reading the news, but for interrogating it.
What we learned
The biggest lesson was that "AI-native" doesn't mean "trust the black box." Every time we defaulted to the modern, popular approach — embeddings for similarity, a single LLM call for grounding — it either failed outright or introduced a subtle failure mode we had to go looking for. The fixes that actually worked were the simple, inspectable ones: count overlapping words, re-rank by literal keyword matches, explicitly detect the cases the system shouldn't try to answer at all. For a low-resource language like Nepali, boring and verifiable beat clever and opaque, every single time.
We also learned that accessibility isn't a feature you add at the end — designing for voice-only, screen-free interaction from day one forced better decisions everywhere else in the product, including the parts sighted, literate users benefit from too.
What's next for Sanksep
- Cache common questions so repeat queries don't hit the LLM every time, both for cost and latency.
- Bring the same voice-driven Q&A loop to the news feed and individual articles, not just the story-comparison screen.
- Real testing with blind and elderly users — everything so far has been built against our own assumptions about what "accessible" means, and that needs to be checked against the people it's actually for.
- Publisher partnerships, so the aggregation model that currently leans on RSS and fair-use-style summarization has an explicit, licensed foundation if this goes beyond a hackathon.
Built With
- express.js
- flutter
- gemini
- neon
- node.js
- ollama
- python
Log in or sign up for Devpost to join the conversation.