Verification Platform for Renewable Energy Certificates
Client: Eric Arnold, Viro Trade
Role: Sole engineer — architecture, backend, frontend, data pipeline, deployment
Domain: Renewable energy certificate (REC) verification and audit
Status: Built and deployed; engagement concluded before full rollout
Executive Summary
Viro Trade audits renewable energy certificates, confirming that RECs a buyer paid for were genuinely issued, retired, and not double-counted. Every audit was manual assembly across registry exports, PDFs, and emails, and ran to days. I built the platform that ingests proof in whatever form a registry emits, extracts it with AI, and checks it against the registries down a second independent path, so a fault in one can't manufacture agreement with the other. Sole engineer, architecture through deployment.
1. The Project
Viro Trade performs independent verification audits on renewable energy
certificates — confirming that RECs a buyer paid for were genuinely issued,
genuinely retired, and not double-counted. The firm reports that its team now
audits roughly 30% of all voluntary REC transactions in the United States
each year.
Every audit was performed by hand, against spreadsheets accumulated across
multiple files and several years of trading. The auditor's real work wasn't
judgment — it was assembly: opening registry exports, chasing down the PDF or
email evidencing a particular retirement, cross-checking figures between
workbooks never designed to be joined. A single audit ran to days, and
scaled only by hiring more auditors.
The brief was to automate that assembly and turn weeks into seconds — not
just to absorb more volume, but to make the capability sellable to other
firms as automated auditing. That second goal set the engineering bar: a
system sold to peers and subject to scrutiny has to be auditable and
traceable by construction, not by careful use.
The hard part is that RECs are issued and retired through several independent
registries — WREGIS, M-RETS, NAR, ERCOT, Green-e — sharing no common format
and no common export. Proof of a transaction arrives as whatever the registry
or counterparty emitted: a CSV, a PDF certificate, an email, a screenshot, a
chat transcript. Viro ingests any of those, uses AI models to extract the
transaction data, and retains every original file linked to the records
derived from it. Separately, it pulls data directly from the registries. Those
two sources — what the paperwork claims and what the registry shows — are
what a verification compares.
Scope of delivery. Built and deployed solo, architecture through
deployment. The end-to-end path runs for a subset of the target registries,
and real registry documents were processed through the extraction pipeline.
The engagement wound down before the remaining registries were completed and
before the platform entered day-to-day audit work, so no complete audit was
executed through it and the weeks-to-seconds objective was never measured.
What follows describes what was designed and built, not an operational
result.
2. Technical Details
Stack
| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.5.5 (Web, Data JPA, Security, Validation) |
| Build | Gradle 8.14.3 (multi-module) |
| Database | MySQL (AWS RDS) |
| Migrations | Liquibase |
| Auth | Spring Security + JWT (jjwt) |
| API docs | springdoc-openapi / Swagger UI |
| AI integration | Spring AI (OpenAI, Anthropic, Google GenAI providers) |
| Document processing | Apache PDFBox, tabula-java |
| Frontend | React 19, TypeScript, Vite 7, Tailwind CSS 4 |
| Frontend data/state | TanStack React Query 5, React Hook Form + Zod |
| Frontend testing | Vitest, Testing Library, MSW |
| Automation | n8n (workflow orchestration) |
| OS target | Ubuntu 24.04 |
Architecture
A seven-module Gradle build, separated by responsibility rather than by feature, so that each layer can be developed and tested independently.

common— shared domain models, DTOs, and enums. No framework dependencies; pure shared code.database— JPA entities, Spring Data repositories, and the Liquibase changelog. Owns every schema migration.ai-provider— the LLM integration layer (detailed below).fileloader— ingestion. Identifies incoming document type (CSV, PDF, email, chat export), extracts raw content, and routes it for structured extraction.docstorage— retains the original source file for every processed record, so any extracted value can be traced back to the document it came from.datafetcher— scrapes and imports certificate and transaction data directly from the registries on a schedule.api-validation— a standalone test module that captures REST Assured JSON snapshots of the core transactional endpoints and verifies they haven't regressed, plus CSV-based data validation.
Multi-Provider AI Layer
The most substantial piece of engineering in the system is the ai-provider module: a vendor abstraction that lets the platform talk to four AI providers — OpenAI, Anthropic, Google Gemini, and OpenRouter — through a single interface.
This was a deliberate architectural decision rather than a convenience. Most AI-backed platforms hard-wire themselves to one vendor and inherit that vendor's pricing, availability, and model deprecation schedule as a business risk. Viro selects a client per request, with a configured fallback, so a price change or an outage at any one provider is a configuration change, not a rearchitecture. Different document types can also be routed to whichever model handles them best or most cheaply.
Built on that abstraction:
- Structured extraction from PDFs, images, and scanned documents.
- Vision analysis — dedicated vision-capable clients per provider, for screenshot and image-based document types.
- Multi-step orchestration — pipelines that combine text and vision analysis and chain multi-turn conversations, rather than single one-shot model calls.
- Tool-calling — models can invoke registered business functions (company and document lookups) mid-reasoning instead of only emitting text.
- Cost and usage observability — every call is metered: token usage, per-provider cost calculation, and audit logging, giving a real view of AI spend by provider and model.
- Database-managed prompts — extraction prompts are stored and versioned in the database, not hardcoded, so extraction behavior is tuned without a code deployment.
Two of those capabilities matter more here than they would in a typical AI product. Audit logging of every model call means the extraction step is itself auditable — for a verification firm, "a model read it off the PDF" is only acceptable if there's a record of which model, which prompt, and what it returned. Database-managed prompts mean extraction can be corrected and re-versioned as registry document formats change, without a deployment and without losing the history of how a given record was originally extracted.
This layer is the most complete part of the system: it is deployed and has processed real registry documents, driving the AI extraction paths for retirement certificate ingestion.
Ingestion Pipeline
The two sides are gathered by completely independent paths, which is what makes the comparison meaningful: the document side never informs the registry side. The frontend queries the REST API to review discrepancies, correct extraction errors, and produce reporting.

Frontend
A React 19 + TypeScript single-page application built with Vite, communicating with the backend exclusively over REST/JSON. TanStack React Query handles server state, React Hook Form with Zod schemas handles form validation, and Recharts drives the reporting views. Tested with Vitest, Testing Library, and MSW.
Engineering Practices
- Schema as code — all database changes go through Liquibase; no manual schema edits.
- Contract regression testing — the
api-validationmodule snapshots the core transactional endpoints so an unintended response change fails a test rather than reaching the frontend. - Source traceability — every extracted record keeps a link to the original document it came from. In an audit business this is not a convenience feature: a finding is only as defensible as the evidence behind it, and the platform is built so any figure can be traced back to the PDF, email, or screenshot it was read from.
- Independent data paths — document extraction and registry retrieval share no code path and no inputs, so a fault in one cannot quietly propagate into the other and manufacture agreement.
- Provider independence — no single AI vendor is a single point of failure.