Case study / 2026
JVVNL Electricity Bill Automation
A scheduled Python system that logs into a state electricity portal, downloads every account's monthly bill, parses three different PDF layouts into one master ledger, works out what has actually been paid, and files each PDF where the accountant expects to find it.
Primary result
34 accounts collected, parsed and filed without supervision
- Python
- Selenium
- pdfplumber
- pandas
- openpyxl
- OpenCV
- Tesseract / ddddocr
- 34Accounts automated
- Accounts automated
- 3Bill layouts parsed
- Bill layouts parsed
- 40+Fields per bill
- Fields per bill
- 34/34Verified vs source PDFs
- Verified vs source PDFs
Challenge
The problem to solve
Thirty-four electricity accounts were being handled by hand every month: log in, solve a CAPTCHA, pick each account, download the bill, read the numbers off it, type them into a spreadsheet, then save the PDF into the right client folder on a network drive. It is slow, it is dull, and every step is a place to make a quiet mistake that nobody catches until a payment is missed.
Approach
Technical direction
Selenium drives the portal login through a multi-engine CAPTCHA solver with escalating backoff and lockout detection. Downloaded bills go through a layout classifier into one of three dedicated parsers, because the same utility issues bills in English, in transliterated Hindi, and in Devanagari with CID-encoded fonts. Extracted fields merge into a single formatted workbook keyed on account and bill month, and a separate distribution step files each PDF into its per-client folder. The whole cycle runs from one idempotent entry point on Windows Task Scheduler.
Outcomes
Key outcomes
- 01Built a layout classifier and three parsers so the same pipeline reads English, transliterated-Hindi and Devanagari bills, extracting 40+ fields from each.
- 02Replaced a text-order regex with a coordinate-based parser for solar bills, after the two-column layout made pdfplumber return a serial number — and once a day-of-month from a payment date — instead of the meter reading.
- 03Derived payment status from the utility's own ledger: the next bill's "Previous Outstanding" settles the previous one, so only the newest bill needs a portal check and every earlier verdict corrects itself.
- 04Made the run idempotent end to end — already-saved bills are not re-downloaded and already-filed PDFs are not re-copied — so it is safe to schedule hourly.
- 05Preserved the accountant's hand-typed columns across every automated rebuild of the sheet, keyed on account and bill month.
- 06Verified all 34 accounts field-by-field against their source PDFs: 34 matched, 0 mismatched.
Process
Process & architecture
The manual loop being replaced
Every month, thirty-four accounts had to be pulled one at a time from the state electricity portal, read off the PDF, typed into a spreadsheet, and saved into the right client folder. The cost is not just the hours. It is that nothing about the process tells you when a number was mistyped or a bill was never fetched.
Getting through the front door
The portal is behind a login with an image CAPTCHA. The solver tries ddddocr first, falls back to a Tesseract voting pass over several preprocessed variants of the image, then a paid service, then a human. Failures back off exponentially rather than retrying immediately, and an explicit lockout detector aborts the run and saves a resume point instead of hammering the login into a longer block.
One utility, three bill layouts
The same provider issues bills in three forms: plain English, transliterated Hindi that extracts as ASCII noise, and Devanagari whose CID-encoded fonts come through as glyph codes. A classifier detects which one it is and dispatches to a parser written for that layout, so a single pipeline handles all of them instead of failing on two thirds of the accounts.
When the text order lies
Solar bills carry a generator block that only appears on the English copy, in a two-column layout. Reading it with regex over extracted text worked until it did not: on one bill the pattern returned the right column's serial number, and on another it returned 13 — the day from a payment date printed on the same line — instead of a 2166 kWh reading. The fix pairs each label with the numeric value in its own row by word coordinates, and refuses the result unless net generation equals present minus previous.
Payment status without asking
Rather than reconciling receipts for every bill, the system reads the utility's own arithmetic: the next month's bill states the previous outstanding, and a zero there means the earlier bill was settled. Only the newest bill, which has no successor yet, needs a receipt lookup. Every verdict becomes certain on its own once the following bill arrives, and the model distinguishes paid, unpaid, credit and nil rather than collapsing to a yes/no.
Filing where a human would
Destinations are configuration, not code. Each account maps to a client folder; accounts without one share a fallback. The financial-year segment of every path is substituted at run time, so April needs no code change, and the month subfolder is matched against what already exists — if bills are filed under "June 2026", the new one joins them rather than starting a rival "June".
Safe to run again
The scheduled entry point never rebuilds the workbook from scratch. New bills are added, existing rows are merged on account and bill month, PDFs already on disk are not re-downloaded, and files already filed are skipped by size comparison. That is what makes an hourly schedule reasonable: most runs correctly do nothing.
Leaving room for the human
The accountant annotates each row by hand — payment mode, who paid, solar or not. Those columns are not part of the scraper's schema, so the canonical rebuild would drop them. They are snapshotted before the rewrite and reattached after, keyed on account and bill month, and unmatched new rows are left blank on purpose rather than guessed at.