A full-stack platform for tracking live Peruvian election results from ONPE (Oficina Nacional de Procesos Electorales) during the 2026 general elections. The system continuously ingests official vote count data, stores time-series snapshots, runs probabilistic outcome simulations, and serves an interactive web dashboard with an AI-powered chat assistant.
┌─────────────────────────────────────────────────────────────┐
│ ONPE API │
│ (Peru's official electoral authority) │
└──────────────────────┬──────────────────────────────────────┘
│ Selenium + httpx (requires browser session)
▼
┌─────────────────────────────────────────────────────────────┐
│ Backend (Python 3.12 / FastAPI) │
│ │
│ fetcher.py ──► processor.py ──► storage.py (JSON files) │
│ │ │
│ ┌───────┴───────┐ │
│ ▼ ▼ │
│ predictor.py AI/rag.py │
│ (Monte Carlo) (vectorstore rebuild) │
│ │
│ scheduler.py (60 s polling, change detection) │
│ main.py (FastAPI routes, SSE streaming) │
└──────────────────────┬──────────────────────────────────────┘
│ REST / SSE
▼
┌─────────────────────────────────────────────────────────────┐
│ Frontend (React 19 / TypeScript / Vite) │
│ │
│ Dashboard │ Electoral Map │ Predictions │ Timeline │
│ Candidates │ History │ AI Chat │ Actas │
└─────────────────────────────────────────────────────────────┘
.
├── Backend/ # Python FastAPI data pipeline and API server
│ ├── main.py
│ ├── fetcher.py
│ ├── processor.py
│ ├── scheduler.py
│ ├── storage.py
│ ├── predictor.py
│ ├── config.py
│ ├── actas_controller.py
│ ├── AI/
│ │ ├── rag.py
│ │ ├── data_loader.py
│ │ ├── embeddings.py
│ │ └── vectorstore.py
│ ├── ImageRecognition/ # Bulk acta PDF downloader
│ ├── requirements.txt
│ └── README.md
├── Frontend/ # React + TypeScript dashboard
│ ├── src/
│ │ ├── pages/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── types/
│ ├── public/
│ │ └── peru-departamentos.geojson
│ ├── package.json
│ └── README.md
└── Dockerfile # Multi-stage build: Node → Alpine Python + Nginx
The ONPE API requires an active browser session. fetcher.py manages a headless Chromium instance via Selenium to establish sessions, then uses httpx for HTTP requests. Retry logic with exponential backoff handles transient API failures.
processor.py normalizes raw API responses: it unifies field names across election types, computes participation rates and vote share percentages, ranks candidates, and assembles complete snapshot objects with a consistent schema regardless of election type.
storage.py writes to a structured data/ directory. The scheduler only persists a new snapshot when the counted-ballots percentage increases by more than 0.1%, avoiding storage of redundant updates. Atomic writes (temp file → rename) prevent consumers from reading partial data.
predictor.py runs 100,000 Monte Carlo simulations using a Dirichlet-Multinomial model vectorized with NumPy. Rather than drawing independent Gaussian noise per candidate (which can produce vote shares that don't sum correctly), the simulator draws each run's full vote-share vector from a Dirichlet distribution whose concentration parameter κ is derived from cross-regional variance and historical volatility. This enforces compositional consistency across candidates while still capturing momentum and geographic uncertainty. The output is a probability distribution over finishing positions, confidence intervals (p5–p95), and overtake probabilities between adjacent-ranked candidates.
The AI/ module implements a retrieval-augmented generation pipeline:
data_loader.pyconverts the latest snapshots into labeled text chunks.embeddings.pyencodes them locally usingsentence-transformers(all-MiniLM-L6-v2) — no external service required.vectorstore.pystores embeddings in memory and ranks by cosine similarity.rag.pyretrieves the top-k relevant chunks plus a set of always-pinned sources, and injects them into the LLM context window so responses stay grounded in current data.main.pystreams the final response token-by-token via Groq (llama-3.3-70b-versatile) using Server-Sent Events.
| Key | Election |
|---|---|
presidential |
National presidential race |
senate_national |
Senate — National Electoral District |
senate_regional |
Senate — Regional Electoral Districts |
deputies |
Congressional Deputies |
parlamento_andino |
Andean Parliament |
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript, Vite, Tailwind CSS 4, Recharts, D3-geo |
| Backend | Python 3.12, FastAPI, Uvicorn, Selenium, httpx |
| AI / NLP | Groq API (LLM streaming), sentence-transformers (local embeddings), custom RAG pipeline |
| Deployment | Docker, Nginx, multi-stage build |
- Node.js 20+
- Python 3.12+
- Google Chrome or Chromium installed
- A free Groq API key for the AI chat
cd Backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # add GROQ_API_KEY to enable AI chat
uvicorn main:app --reload --port 8000cd Frontend
npm install
cp .env.example .env.local
npm run dev # http://localhost:5173The Vite dev server proxies /api/* requests to http://localhost:8000.
docker build -t onpe-platform .
docker run -p 5000:5000 onpe-platform