The problem
Every month the finance team collected electricity, water and internet bills for more than 735 units. They came from over a dozen providers, each with its own portal, PDF format and quirks. Staff logged in to each portal, downloaded bills, read off the amounts, typed them into a large Excel workbook and then built owner statements by hand. It took days, mistakes slipped through, and nobody could tell which bills were still missing.
What I built
A provider plugin system. Each utility provider is a plugin with its own scraper and extractor, so adding a provider never touches the others. Bills arrive three ways:
- browser automation of provider portals
- an email inbox the system reads and classifies, including attachments inside nested zips
- manual or SFTP uploads
Reliable scraping at scale.
- Playwright runs through rotating residential proxies, with one sticky session per provider so portals don’t flag the logins.
- A circuit breaker pauses a failing provider instead of hammering it.
- Every task has a timeout, and an hourly cleanup job recovers runs that got stuck.
Extraction and reconciliation. The worker reads amounts from the bill PDFs and checks them against the amounts shown on the provider websites. Each bill is matched to the right unit by building and account. Anything that doesn’t reconcile is flagged for review instead of being booked silently.
Excel write-back. The finance team kept their existing monthly workbook. The system fills it in directly, so their process didn’t have to change.
Owner reports. Monthly owner earnings reports are generated and checked against a real historical report, then go through an approval step before they are sent.
An operations dashboard (Next.js). About 30 screens:
- scrape runs and their status
- bills and reconciliation
- the email inbox
- an Excel viewer
- owner reports
- proxy health
- user management
Engineering decisions worth noting
- Separate gateway and worker. The dashboard API stays fast and safe to expose. The heavy scraping and extraction runs on Celery workers that can be scaled or restarted without downtime.
- Write into the workbook the team already uses. Adoption was immediate because the finance team kept working the way they already did.
- Reconcile instead of trusting extraction. Extraction is never 100% accurate. Checking two sources against each other catches the errors that matter before they reach an owner.
What this proves for your project
If your team spends days a month copying numbers out of portals, PDFs or emails into spreadsheets, that work can be automated reliably, with an audit trail and checks a human approves. The same approach works for invoices, statements, purchase orders and any other recurring documents.
