Skip to content

About

Plataforma civica anti-corrupcion para el Peru: 11 agentes de IA (Google ADK + Gemini 2.5 / Vertex AI) auditan contratos del Estado por ~1 sol. OCR con Document AI, RAG legal con Vertex AI Search, observabilidad con Arize.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

vigΓ­a_ PerΓΊ: plataforma cΓ­vica anticorrupciΓ³n

MIT Live Google Cloud Arize

Next.js TypeScript Tailwind Cloudflare Workers PostgreSQL Python Gemini Terraform

Live site Β |Β  How it works Β |Β  Architecture Β |Β  Data Β |Β  Run it locally


Corruption in Peru is already public. VigΓ­a makes it readable. Every tender, contract and debarred company is open data. The bottleneck was never the data, it was the cost of reading it. VigΓ­a funds that reading with small donations: a contract is read for S/ 3, and the result is public, always.

Escala del problema: contratos publicados, leΓ­dos y con riesgo alto

Why it exists

Investigating one suspicious state contract takes a journalist days: 80-page scanned PDFs spread over a dozen .gob.pe portals that do not talk to each other. Peru has published about 20 thousand contracts in 2026 alone. Oversight is a slow, heroic act that does not scale. VigΓ­a puts AI agents to read the bidding file, cross it with official records and point at the contracts that deserve a second look, each signal linked to the law it cites and the document it comes from.

Important

We never accuse anyone. A signal is a reason to review a contract, never a charge: not against a company, a person or a party. No verifiable link to an official source means it is not published.


What you can do

πŸ—ΊοΈ Citizens

One map, three readings: who governs your region, how much is contracted there, and how much VigΓ­a has read.

πŸ”Ž Investigators

Signals with the law and the evidence, searchable by contract, entity, RUC or region. A read-only MCP server brings it to any AI assistant.

🀝 Donors

Fund the reading by zone, by entity or by party and electoral term. Nobody picks which contracts are read.

πŸ“Έ Neighbours

Report a stalled work with a photo and a place. Two independent reports in 30 days confirm it.

Portada de VigΓ­a PerΓΊ El mapa con tres lecturas y la lista de partidos Inicio de la aplicaciΓ³n con buscador, rankings y lecturas en vivo Ficha de un partido con sus contratos por mes

How it works

flowchart LR
    A["πŸ›οΈ OECE publishes<br/>a contract"] --> B["πŸ“₯ Daily ingestion<br/>contract and documents"]
    B --> C[("πŸ—„οΈ Postgres<br/>one source of truth")]
    D["🀝 Someone donates<br/>S/ 3 per contract"] --> E["🎲 Contracts assigned<br/>nobody chooses which"]
    C --> E
    E --> F["πŸ€– AI agent reads<br/>bases, minutes, contract"]
    F --> G{"βš–οΈ 4 judges<br/>check the result"}
    G -->|solid| H["βœ… Published signal<br/>law and source attached"]
    G -->|doubtful| I["πŸ‘€ Human review"]
    H --> J["πŸ—ΊοΈ Map, rankings,<br/>donor receipt"]
Loading

Each reading ends with four self-evaluation judges (flag support, evidence citation, non-accusatory tone, pipeline completeness). A doubtful result goes to a person instead of being published, and a published result stays public even if it points at the donor who paid for it.

Contracts by political party

Every contract is linked to the party that governed the contracting entity in the month the contract was called, using the regional and municipal election results of 2014, 2018 and 2022 (JNE). A donor can fund the reading of the contracts of one party in one electoral term, and the contracts are picked at random among those entities.

flowchart LR
    J["πŸ“œ JNE results<br/>2014, 2018, 2022"] --> G["gobierno_periodo<br/>who governs each place,<br/>per term"]
    E["🏒 Entities<br/>by name: Regional Government,<br/>Provincial or District Municipality"] --> EG["entidad_gobierno"]
    G --> CP["contrato_partido"]
    EG --> CP
    C["πŸ“„ Contract<br/>call date"] --> CP
    CP --> R["πŸ“Š Ranking and funding<br/>per party and term"]
Loading

Note

The link is made with the election winner. Vacancies, recalls and replacements inside a term are not recorded, and the interface says so. Only entities that are a regional government or a municipality have a party. Ministries and agencies do not. See 41_aporte_por_partido.sql.

Contratos por partido gobernante, leΓ­dos y esperando financiamiento


Architecture

flowchart TB
    P(["πŸ‘₯ People"]) --> W
    subgraph CF["☁️ Cloudflare Workers"]
        W["vigia-web<br/>Next.js 14, OpenNext"]
        API["vigia-api<br/>Hono, TypeScript"]
        DSP["vigia-dispatcher"]
        MCP["vigia-mcp"]
        REL["vigia-relay<br/>Zurich"]
        W --> API
    end
    subgraph GCP["🟦 Google Cloud"]
        PGB["PgBouncer<br/>e2-micro VM"]
        SQL[("Cloud SQL<br/>Postgres 16 and PostGIS")]
        AG["πŸ€– Agents on Cloud Run<br/>one per contract type, Google ADK"]
        VX["Vertex AI<br/>Gemini 3.6 Flash"]
        DOC["Document AI"]
        RAG["RAG Engine<br/>4 corpora of law"]
        JOB["⏱️ Cloud Run Jobs<br/>and Cloud Scheduler"]
        PGB --> SQL
        AG --> VX
        AG --> DOC
        AG --> RAG
    end
    API -->|"Hyperdrive and tunnel"| PGB
    DSP --> AG
    AG --> PGB
    MCP --> PGB
    JOB --> SQL
    REL --> OECE
    JOB --> OECE
    subgraph SRC["πŸ›οΈ Public sources"]
        OECE["OECE, OCDS API"]
        PNDA["PNDA"]
        JNE["JNE"]
        MEF["MEF"]
        ONPE["ONPE"]
    end
    JOB --> PNDA
    JOB --> JNE
    JOB --> MEF
    AG -.->|traces| ARZ["πŸ“Š Arize Phoenix"]
Loading
Layer What it does Where it runs
Web and API The site, the read and write API, the dispatcher and the MCP server Cloudflare Workers
Database One Postgres is the source of truth. Roles per component, read models and the contract assignment functions live inside it Cloud SQL, behind PgBouncer
Agents One service per contract type (goods, services, works, other). The order of the agents is code, not a model's decision Cloud Run
Ingestion Contracts every day at 06:00 Lima, documents at 02:00, plus weekly and monthly scrapers Cloud Run Jobs and Scheduler
Relay The OECE API only answers to nearby IPs, so a Worker in Zurich relays it Cloudflare Worker

Cadencia de cada fuente de datos

Contratos por regiΓ³n y cuΓ‘ntos se leyeron


Data

The raw datasets are not in this repository: they are large and some contain personal data (the RNP registry has partners' names and IDs). We link the official open-data sources instead. Details in docs/DATA.md.

Source Used for Cadence
OECE, Contrataciones Abiertas (OCDS) Contracts, entities, amounts, stage 🟒 daily
OECE documents Bidding files and contracts in PDF 🟒 daily
PNDA (datosabiertos.gob.pe) Sanctions, entity visits, penalties, interest declarations 🟒 weekly to monthly
JNE, Infogob Authorities, candidacies, who governs each place 🟑 monthly and per election
MEF Regional budget πŸ”΄ monthly, the source changed its API
ONPE, Claridad Campaign contributions 🟠 manual, it needs a browser and a Peruvian IP
SUNAT (via decolecta) Company age and status βšͺ on demand

Warning

Personal data is never shown in clear. IDs and surnames sit behind frosted glass until clicked. Companies and public officials are not censored.

The alert engine, rule by rule
Rule What it looks for
Sanctioned supplier A winner with a current OSCE sanction
Single bidder One valid offer at 100 % of the reference value
Direct award Direct hiring without valid grounds
Addenda Changes above 25 % of the original amount
New company A company created shortly before winning
Penalties Suppliers with a history of penalties
Revolving door An official whose declared past jobs include the supplier
Campaign money Contributions to a campaign followed by contracts from the same government

Each flag cites its legal basis and the official document it comes from. Model-assisted legal and market analysis sits on top of the rules.


MCP server

backend/mcp is a remote Model Context Protocol server with read-only tools. A journalist can ask their own AI assistant "show me the high-risk contracts in Áncash" and get them back with their official evidence attached, without scraping and without the interface. It only exposes public data and never writes to the database.

> show me the high-risk
  contracts in Áncash

each result carries:
  the law it cites
  the official document
  the state of the reading

Built with

AreaTechnology
🌐 WebNext.js 14, React, Tailwind, GSAP, d3-geo. Identity: charcoal and lime, a dot-matrix eye
βš™οΈ APIHono (TypeScript) on Cloudflare Workers and Cloud Run
πŸ€– AgentsPython, Google ADK, Gemini on Vertex AI (3.6 flash for analysis, 3.5 flash-lite for fast steps and judges), Flex pricing
πŸ“„ Documents and lawDocument AI for PDF text, RAG Engine with 4 corpora (current and historical law, criteria, control)
πŸ—„οΈ DataPostgreSQL 16 with PostGIS, PgBouncer, Hyperdrive
πŸ“₯ IngestionPython scrapers as Cloud Run Jobs, Cloud Scheduler
πŸ“Š ObservabilityOpenInference, OpenTelemetry, Arize Phoenix
πŸ—οΈ InfrastructureTerraform and one deploy script per component

A reading costs about US$ 0.17 after the Flex pricing and caching work, against a S/ 3 donation per contract.

Observability with Arize

OpenInference instruments the ADK runner and every Gemini call and exports to Arize Phoenix: a full span tree per contract with tokens, cost, latency, prompts and responses. If an agent drifts, it shows up in the trace.

Panel de Arize MΓ©tricas de Arize

Repository

vigia-peru/
β”œβ”€β”€ frontend/                 Next.js 14: portada, /app (map, contracts, signals, entities, parties, funding), /admin
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ agent/                ADK agents in Python, one per contract type, deterministic pipeline
β”‚   β”œβ”€β”€ api/                  Read and write API (Hono) for Cloudflare Workers and Cloud Run
β”‚   β”œβ”€β”€ dispatcher-worker/    Hands paid contracts to the agents
β”‚   β”œβ”€β”€ mcp/ and mcp-worker/  Read-only MCP server
β”‚   β”œβ”€β”€ relay-worker/         Zurich relay to the OECE API
β”‚   β”œβ”€β”€ r2-sync-worker/       GCS to R2 file copy
β”‚   β”œβ”€β”€ db/migrations/        Numbered SQL migrations (01 to 41) and apply_all.py
β”‚   β”œβ”€β”€ scrapers/             Ingestion pipelines: PNDA, OCDS, MEF, ONPE, JNE
β”‚   └── scripts/              Loaders and generators (who governs each place, README charts)
β”œβ”€β”€ infrastructure/
β”‚   β”œβ”€β”€ terraform/            The platform as code
β”‚   └── deploy/               One script per component, and the project-move runbook
└── docs/                     DATA.md, DATASET_MAP.md, MARCO_NORMATIVO.md, images

Run it locally

Each component ships a .env.example.

# Database: Postgres 16 with PostGIS
docker compose -f infrastructure/docker-compose.yml up -d
python backend/db/apply_all.py              # PGHOST=127.0.0.1 PGPASSWORD=vigia by default

# API
cd backend/api && npm install && cp .env.example .env && npm run dev
npm test                                     # unit tests

# Web
cd frontend && npm install && cp .env.example .env.local && npm run dev

# Scrapers
cd backend && pip install -r scrapers/requirements.txt && python -m pytest scrapers/tests

# Regenerate the charts in this README from the live API
python backend/scripts/generar_graficos_readme.py

Tip

Deploys go one component at a time, in this order: migration, then API, then web (infrastructure/deploy/*.sh). Credentials live in environment variables or Secret Manager, never in the repo.

Principles

  1. We never accuse anyone. A signal is a reason to review, never a charge.
  2. We never publish citizens' personal data. Only serving officials and companies that contract with the State.
  3. We do not replace the Comptroller, the prosecutors or journalism. We detect and prioritize.
  4. Citizen reports: two independent reports within 30 days to be confirmed. No photo, not published.
  5. Funding buys reading, not results: donors do not choose contracts, cannot edit findings, and are published even if flagged.
  6. Every flag links to official evidence. No verifiable link, not published.
  7. No ads, no data monetization. Open source and non-profit.

Status

A working platform with national coverage of published contracts (about 20 thousand in the base), the three-reading map, funding by zone, entity and party, public donor profiles and an admin panel. Reading with AI is funded by donations, so the share of contracts read grows as people contribute.

Known gaps: ONPE contributions are loaded by hand, payments are validated manually (there is no gateway yet), and the 2026 election results will be added when the JNE proclaims them.

Spanish documents: ARQUITECTURA.md, SOLUCION.md, OBSERVABILITY.md.


MIT License. Open source, non-profit, no ads, no data monetization.

Built for Peru πŸ‡΅πŸ‡ͺ

About

Plataforma civica anti-corrupcion para el Peru: 11 agentes de IA (Google ADK + Gemini 2.5 / Vertex AI) auditan contratos del Estado por ~1 sol. OCR con Document AI, RAG legal con Vertex AI Search, observabilidad con Arize.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages