The reporting platform: 400 branded reports without hands
outcome 30 to 80 hours of weekly manual work replaced by a run that takes minutes
Problem
Around 400 client reports, 200 of them sent automatically, ran through the BI platform’s built-in subscription system, and it fought us at every step. Subscriptions could only send to licence holders, so nothing could go directly to clients; emails arrived branded as Microsoft, not as the company; every report was its own subscription to find and amend; delivery to clients relied on forwarding rules buried in the mail system; and when something failed, tracing which report and why was archaeology. Before any of this was automated, the underlying work had consumed 30 to 80 hours per week across a 10-person Client Success team.
What I built
A nightly dispatch engine in a PySpark notebook, with a programmatically filled SharePoint list as the single source of truth for every report, its schedule, and its recipients. Each night it filters to just the reports due that day (daily, weekly by weekday, or monthly by date), renders them with the company’s branding, attaches the PDF, embeds a chosen report page as an image, and sends. Delivery is mail-system agnostic; it survived the company’s Gmail to Outlook migration without touching a forwarding rule. Every send is written back to the list, so a re-run is safe and picks up only what failed.
The engine also refuses to send bad data. It extracts the text from each rendered PDF and scans for capacity and licensing error messages hiding inside visuals, retries, and skips the report rather than emailing a client something broken. Before sending anything it verifies that the upstream pipeline and semantic model refreshes actually succeeded, so a delivered report always carries current data.
Alongside delivery sits generation: a skill that builds complete branded Power BI reports (PBIR format) from a one-line prompt, using ranked findings from the semantic model and a curated pattern catalogue, plus a provisioning skill that stands up a new client’s semantic model from a baseline and a documentation agent that keeps the measure catalogue in sync.
Techniques that made it work
- Replacing a no-code layer (an earlier Power Automate iteration iterated all 200 reports to find the day’s six; the notebook filters first and most days finishes in minutes, with the ~100-report Monday peak done inside an hour)
- Fail-closed quality gates: a report that cannot prove it is healthy is not sent
- Idempotent runs: state written back per send, so recovery is “run it again”
Outcome
Hundreds of reports across 150 to 200 clients delivered branded, traceable, and auditable, with data integrity checks a manual process never had. The 30 to 80 hours of weekly manual work is gone, and new-client report production compressed from days of skilled work to minutes of review.