dev:congress{}; Connect
← All projects

mellowfm.com – Read, Search and Check News

News site for readers, Ghana-first: search coverage from many outlets in plain language, check a claim against stored reporting, and get topic briefs by email.

What it does

mellowfm.com gathers reporting from many news outlets, mostly Ghanaian, into one archive. Readers can catch up on the news, search it, and check a claim against what was actually reported. The site opens on the Ghana edition by default.

Who it's for:

  • Readers, mostly on phones, who want a quick, calm way to follow the news and check something they heard
  • Admins, who submit, approve and monitor news sources and crawls

What readers can do (no account needed):

  • Browse today's news by edition (Ghana, worldwide or another country) and by topic
  • Search the archive in plain language
  • Check a claim against the archive's own stored reporting
  • Open articles in a clean reader page, with the publisher credited and linked
  • Read topic briefs (Politics, Business & Economy, Sport, Tech, Health, Entertainment, General), also available as RSS
  • Subscribe to briefs by email, with double opt-in and one-click unsubscribe

What makes it different:

  • Verdicts come from the archive itself. A claim is judged by six separate checks: entailment, contradiction, timing, source authority, numbers and entity consistency. Every verdict links to the articles behind it, and "inconclusive" is a real outcome.
  • Briefs rank stories by independent coverage. Articles about the same event are merged into one story. Stories are scored on how many outlets reported them (45%), freshness (25%), source trust (15%) and reporting speed (15%).
  • Publishers are credited everywhere their work appears.

WhatsApp sign-up exists, but WhatsApp sending isn't connected yet. Its only provider is a console logger that sends nothing.

How I built it

Frontend

  • React 19, Vite 7, Tailwind CSS 4 and React Router 7
  • Two separate apps, a public site and an admin, that share one code folder
  • Apple-style design language, with a dedicated accessibility pass
  • It is a client-rendered app, so the API serves crawler-readable versions of each public page, with title, canonical tag, Open Graph tags and JSON-LD. nginx rewrites public routes to these. The API also serves sitemap.xml, robots.txt and llms.txt.
  • Article reader pages are noindex, follow, since they republish other outlets' text

Backend

  • Python 3.11 with FastAPI, async SQLAlchemy 2, asyncpg and Pydantic 2, about 27,000 lines across 175 modules
  • Search and Check a claim are public and rate-limited. Admin routes use an API key or an approved Google sign-in.

Ingestion

  • Crawling uses crawl4ai (headless Chromium), sitemap and RSS checks, and trafilatura for text extraction
  • Celery with Redis runs separate workers for crawling, RSS, RSS scheduling and briefs, plus one beat scheduler
  • A quality gate rejects error pages, listing pages and boilerplate. Raw pages are kept, so articles can be re-extracted safely.
  • Each publication date records where it came from: page metadata, RSS, URL or a backfill

Search and claim checking

  • PostgreSQL with pgvector, plus Postgres full-text search. Results are merged by reciprocal-rank fusion, then reranked.
  • Embeddings use all-MiniLM-L6-v2 and reranking uses ms-marco-MiniLM-L-6-v2. Both are baked into the image and run offline.
  • The Search page uses no LLM
  • Claim checking uses DSPy modules and a local Phi-4 Mini model (4-bit) served by Ollama. Production runs it on a CPU-only server. Hosted providers can be switched in by configuration.
  • Brief summaries are written in the background, so briefs are saved first and summaries fill in afterwards

Data and infrastructure

  • PostgreSQL behind PgBouncer in transaction-pooling mode, and Redis for cache, Celery broker and results
  • Alembic migrations, applied by hand only (see "What I learned")
  • Prometheus metrics with alert rules, structured logs, optional OpenTelemetry tracing, and Flower for Celery
  • Sentry error tracking, switched on only when a SENTRY_DSN is set
  • Search and claim events are logged for offline evaluation, without IP addresses, API keys or user identity. The log can be turned off.

Testing

  • About 600 backend test functions across 95 test files, covering ingestion, date handling, briefs, country ranking, auth, SEO and migrations
  • End-to-end tests against a populated database exist but are excluded from CI
  • Frontend testing is thin: one test file for the shared helpers