Case study 01
Energy intelligence at utility scale
An enterprise energy platform that turns meter data from thousands of sites into answers analysts can ask for in plain English.

Outcome
10,000+ meters · readings every minute to every hour
- Scale
- 10,000+ meters
- Readings
- Every minute to hourly
- Stack
- AI · Postgres · BullMQ
Context
A multi-tenant enterprise energy platform monitoring energy use across client facilities. I worked across the backend, the data pipeline, and the AI layer. Two constraints shaped everything: organizations must never see each other's data, and the schema was complex enough that hand-written SQL had become the bottleneck between analysts and their own grid.
The problem
More than 10,000 meters, each reporting on its own cadence - some every minute, some every five, some once an hour - streamed in from SkySpark and direct utility feeds, with every tenant configuring its own sources, units, and rules. The platform had to normalize all of it into unified reporting, keep every tenant's data hard-isolated, and answer analysts' questions without a data team writing queries in the loop.
The outcome
For the business: analysts question their own grid in plain language - no SQL, no data-team queue, and no tenant ever seeing another's data. For the system: a production multi-tenant time-series platform with database-per-tenant isolation, an AI layer that writes and runs its own queries safely, and 30+ queue types of asynchronous infrastructure behind it.
The approach
Per-tenant databases over row-level tenancy. Isolation had to be structural, not a WHERE clause: each organization runs on its own database, and a central control database holds the per-tenant configuration - which sources, units, and rules apply - that the middleware reads on every request. A query written for one tenant physically cannot reach another's data.
An import pipeline that assumes the data is messy: alongside the live feeds, tenants upload CSV and Excel files, and the parser auto-detects 40+ date-format variations. BullMQ workers (5 concurrent) batch-insert 1,000 records at a time, and a 270+ unit mapping decides whether each measurement is summed or averaged during aggregation.
Conversational analytics that writes its own queries. An analyst asks in plain English; the assistant - OpenAI's Assistants API with function calling - plans a query against that tenant's database, runs it through a retrieval tool, and turns the result into a written insight instead of a raw table. 300+ lines of system instructions encode the unit-conversion rules and result caps. Letting it query directly is only safe because the query never leaves the tenant's own database: the isolation is structural, so it never rides on the model's judgment.
30+ BullMQ queue types orchestrate the rest: data sync, report generation, carbon calculations, and notification delivery, each in its own worker pool so the API stays responsive under load.
Built with