Skip to content
PT
All work
FlagshipLive

OlhoNaLei

Nonprofit civic-tech platform that makes Brazil's federal legislation understandable.

Role
Founder & sole engineer
Period
2026 – Present
OlhoNaLei page for the tax-reform bill PLP 68/2024, with its status, its path through Congress and its plain-language summary.

By the numbers

bills ingested
167k+
AI analyses written
29k+
faster vector search
~220×
missing historical bills recovered
8,300+

Overview

OlhoNaLei puts in one place what happens in Brazil's Chamber of Deputies and Senate: bills in progress, how each vote ended and how each member of Congress voted, with plain-language AI summaries. Data comes from the two houses' official open-data portals.

I designed, built and operate it alone, from the ingestion pipeline to the pixels. It is live and in low-maintenance mode.

Independent and nonprofit: it is not a government service and has no link to any party or public body.

How the data flows

  1. Sources

    Chamber API

    Senate API

  2. Ingestion

    Python pipeline

    nightly · resumable · idempotent

  3. Staging

    Firestore

    write-only

  4. Store

    PostgreSQL + pgvector

    Supabase, São Paulo

  5. AI analysis

    Claude Message Batches

    monthly budget caps

  6. Delivery

    Next.js site + admin

    Search Edge Function

    Social + newsletter

    human review

What I built

  1. 01Designed, built and operate the whole platform alone: data pipeline, REST API, AI analysis, frontend, infrastructure and growth.
  2. 02Nightly, resumable, idempotent Python ingestion from the Chamber and Senate open-data APIs (Firestore staging → PostgreSQL), unifying two different JSON shapes into one relational model.
  3. 03Hybrid search (PostgreSQL full-text with the Portuguese dictionary + pgvector), rebuilt with binary quantization and reranking: ~1,850 ms → ~8 ms.
  4. 04Claude analysis pipeline: 29,000+ plain-language summaries, fiscal-impact assessments and legislative-rider (“jabuti”) detection, under monthly LLM budget caps with the Message Batches API.
  5. 05Public site and internal admin console in Next.js 15 / React 19 / TypeScript / Tailwind (Server Components, ISR, dynamic OG images).
  6. 06Automated distribution to Instagram, Facebook, Bluesky and a newsletter, with human-in-the-loop review.
  7. 07Diagnosed and eliminated a ~39,000-invocation serverless cost blowout.
  8. 08Security: row-level security, PostgREST and compliance with the LGPD, Brazil's data-protection law.

Before and after

Database search latency
Before
~1,850 ms
After
~8 ms (~220×)
Vector index size
Before
1.3 GB
After
69 MB
End-to-end search in production
Before
~1.49 s
After
~0.55 s

Case studies

Making vector search 220× faster on a small Postgres

Problem

Hybrid search (full-text + pgvector) over 167k+ bills took ~1,850 ms per query, and the 1.3 GB HNSW index didn't fit comfortably in the memory of a budget Postgres instance.

What I did

  • Profiled the query path before changing anything.
  • Rebuilt the HNSW index with binary quantization: vectors stored as bits for a fast first pass, then the top candidates reranked with full-precision vectors to keep result quality.
  • Benchmarked before and after.
  • Then measured the rest of the end-to-end time: the API ran in the US while the database and the site were in São Paulo, so every search crossed the equator three times (184 ms per hop, for 18 ms of actual Postgres work).
  • Moved search to a Supabase Edge Function in the database's region, with cached query embeddings, a one-variable rollback to the old path, and an explicit lexical fallback (busca_degradada: true) so silent degradation shows up.

Result

Index 1.3 GB → 69 MB. Database search ~1,850 ms → ~8 ms (~220×). End-to-end search in production ~1.49 s → ~0.55 s.

Lesson

Performance measured, not guessed.

Cost-governed AI at scale

Problem

Write useful, plain-language analysis for tens of thousands of bills without an open-ended LLM bill.

What I did

  • A Claude pipeline on the Message Batches API, which is cheaper and asynchronous.
  • Monthly budget quotas enforced in code. The backfill is budget-gated and resumable: it stops at the cap and continues next month without losing work.
  • Each analysis covers a plain-language summary, the fiscal impact and possible “jabutis”: Brazilian slang for an unrelated provision slipped into a bill, like a legislative rider (“a tortoise doesn't climb a tree by itself”). Flagged bills get a badge on the site.
  • Posts to Instagram, Facebook, Bluesky and the newsletter go through human review.

Result

29,000+ analyses with predictable spend. Separately, I found and eliminated a ~39,000-invocation serverless cost blowout.

Lesson

Cost is a feature.

Two government APIs, one data model

Problem

The Chamber and the Senate publish legislation through two very different flat JSON APIs.

What I did

  • A nightly Python pipeline that is resumable, checkpointed and idempotent: retries never duplicate or lose records.
  • Raw data is staged in Firestore (write-only), then normalized into one relational PostgreSQL model: bills, authors, votes and members of Congress.
  • The product never calls a government API at request time; everything is served from PostgreSQL.
  • Structured logs throughout.

Result

167,000+ bills in one model, and 8,300+ missing historical bills recovered by a budget-gated backfill.

Architecture decisions

  1. ADR 01

    No live government API calls at request time: all data is pre-ingested and served from PostgreSQL.

  2. ADR 02

    Firestore is a write-only staging layer; the product API reads only from PostgreSQL.

  3. ADR 03

    PostgreSQL full-text search with the Portuguese dictionary.

  4. ADR 04

    Semantic search runs in a Supabase Edge Function in São Paulo (sa-east-1), next to the database.

Stack

  • Python
  • FastAPI
  • Next.js 15
  • React 19
  • TypeScript
  • Tailwind CSS
  • PostgreSQL
  • pgvector
  • Supabase
  • Deno Edge Functions
  • Firestore
  • Voyage embeddings
  • Claude Message Batches
  • Railway
  • Vercel
  • GitHub Actions
  • Sentry

Want the details behind this?