← vaughnheart.com

Case Study · Operations & Workflow

When the Vendor Breaks, You Build Around It

How I automated a weekly compliance workflow and stopped waiting for someone else to fix their servers.

Role: Operations & Project Manager, Fit At Work Tools: Python, GitHub Actions, Google Apps Script, Excel Time saved: 149 min/week, recurring
C — Context

The situation I inherited

I work for Fit At Work, a corporate wellness provider that manages on-site gyms and employee wellness programs for large companies. We deploy coaches to run classes and gym sessions, and bring in specialists for workshops — all designed to improve employee wellbeing, reduce absenteeism, and give companies something genuinely useful to put in their benefits package.

One of my weekly responsibilities: submit a list of employees who have signed a liability waiver to the MNC's security team. No signed waiver, no gym access. Simple enough! Except the list changes every week as new employees sign, and the security team has no visibility into our gym management system. That's my job to bridge.

The gym management app we use tracks check-ins, waivers, and participation through a frontend dashboard. Every week, I'd go in, manually copy the list, paste it into a spreadsheet, cross-reference against last week's version to find new signers, highlight them, add their email addresses, and send it off.

Functional. Also: 30 minutes of my life, every single week, doing something a machine should be doing. And that was before things got worse.


A — Action

Four iterations, one working system

Spoiler alert: I did not solve all my issues in one go, but four times(!) Each attempt taught me something the previous one didn't. The gym management app's dashboard started throwing server errors [1] shortly after Iteration #1, under the load of 1,000+ active users. The vendor was aware and were working on it. In the meantime, I had a weekly deadline and a security team waiting on me.

Iteration 1 — Smarter spreadsheet, same dependency

First move: make the manual process less painful. I built a script [2] that automated the cross-referencing. It compared the new list against last week's, highlighted new signers in yellow, sorted them to the top, and pulled in email addresses automatically. What used to take 30 minutes now took under fifteen.

Good improvement, I halved the time! But: wrong problem. I'd made the processing faster, but I was still dependent on the vendor's dashboard loading. When the servers went down again, I was back to zero.

Iteration 2 — Crawling user profiles (technically clever, practically terrible)

This was when the servers went down for about 2 months.

Each employee has a unique profile link in the system. On each profile, there's a visual marker showing whether they've signed the waiver. If I couldn't load the main dashboard, maybe I could check each profile individually, and automatically.

I wrote a script to do this: visit each profile, check for the marker, log the result [3]. It worked. It was also a disaster. With 600+ unsigned employees, the script took over 100 minutes to run, the computer had to stay idle the whole time, and after about 300 profiles, the system started returning wrong answers. I tried running multiple checks simultaneously [4] to speed things up. That made the errors worse, and accuracy dropped almost to 0%. I cannot work with 0% accuracy, even if I sped things up by 10x.

Iteration 3 — Abandoned

I almost committed to making iteration 2 faster. I spent hours on it: I woke up, turned on my computer, and started to "problem solve" away. Trying new things, exploring obscure sub-reddits. It was only after realizing the fundamental flaw in my approach that I pivoted. The solution had to change, not just get optimized. (The servers also came back online briefly... Long enough to lull me into thinking it was over when it wasn't. Cruel.)

Iteration 4 — My Eureka Moment

Here's the thing about any web application: the visual dashboard you see is just a presentation layer. Underneath it, the app is constantly making requests to its own database and receiving structured data back [5]. That data exists whether or not the dashboard loads.

Using my browser's built-in network inspector [6], I watched those behind-the-scenes requests happen in real time, understood what they were asking for, and replicated them directly in my own script. This allowed me to bypass the visual dashboard entirely. (Heck, freaking, yes!)

Instead of loading 600+ individual pages and waiting for a colored dot to appear, I was querying the data directly. Complete. Accurate. Done in 90 seconds.

I then set up an automated scheduler [7] to run this script every week at noon, generate the formatted Excel file, and have it waiting by the time I'm back from lunch. I download it, do a quick accuracy check, attach it to an email template [8], and send.

The vendor's server going down wasn't the problem — it was a symptom that I'd been solving at the wrong level. When I stopped trying to fix the symptom and asked "what is the dashboard actually doing?", the solution became straightforward.

Workflow breakdown

Three states, three very different realities. The colors show what's automated (green), manual (white), and error-prone (red).

Workflow A — Dashboard available (original)

Open vendor dashboard
Copy user list from UI
Paste into Google Sheets
Manual index match against last week
Manual highlight new rows
Manual input today's date
Manual add employee emails
Save as .xlsx
Trigger email template
Attach & send
Total time: ~30 minutes · Manual steps: 9 · Error-prone steps: 4

Workflow B — Dashboard down (forced workaround)

Run Python script (browser crawl)
Wait ~60 min, computer idle
Check for false positives & errors
Manually correct errors, re-run
Index match, highlight, date, emails via Apps Script
Save as .xlsx
Trigger email template
Attach & send
Total time: ~150 minutes · Manual steps: 8 · Error-prone steps: 4 · Computer locked for 60+ min

Workflow C — New system (dashboard-independent)

Scheduled job runs at noon automatically
Queries data directly (no dashboard needed)
Generates formatted .xlsx with highlights
Download, quick accuracy check
Email template auto-fills today's date
Attach & send
Total time: ~1 minute · Manual steps: 2 · Error-prone steps: 0 · Runs while I'm at lunch

R — Results

What actually changed

99%
reduction in time spent (worst case: 150 min → 1 min)
0
error-prone manual steps remaining in new workflow
67×
faster than the broken-dashboard workaround
Workflow Condition Time Manual steps Error risk
A — Original Dashboard available ~30 min 9 High (copy-paste, manual dates)
B — Workaround server down Dashboard broken ~150 min 8 + error correction Very high (false positives, re-runs)
C — New system current Dashboard-independent ~1 min 2 Negligible

Beyond the time numbers: the new system eliminated an entire class of errors that came from manual copy-pasting, including wrong date in the email, missed a new signer, inconsistent formatting. The email template handles the date automatically. The script handles the rest. And whether the vendor's dashboard loads or not is now completely irrelevant to my workflow. 🎉


L — Learning

Don't fall in love with your solution

The thing I almost did wrong was commit too hard to iteration 2. It was technically working, but only under the right conditions. Sunk cost fallacy would have me spend another week making it faster. But "technically working" and "actually solved" are not the same thing.

In hindsight, Iteration 2 was built on a fragile foundation of loading visual pages that a server under stress couldn't reliably serve. No amount of optimization was going to change that. The only move was to go one layer deeper and ask what the dashboard was actually doing, rather than trying to replicate what the dashboard was showing.

The other thing worth naming: I almost stopped iterating entirely when the servers came back online for three weeks. Problem solved, right? Nah, it was deferred. Operational dependencies on a vendor's frontend availability aren't a solved problem just because the vendor fixed their servers temporarily. Building independence from that dependency was worth doing regardless.

Every operational problem has a layer beneath the obvious one. Vendor problems are rarely vendor problems. They're invitations to understand a system better than the people selling it to you.

The broader principle for any operations or workflow role: don't wait for the vendor to solve your problem. Understand the system well enough to route around the failure. The 149 minutes saved per week was such a win (I was so ecstatic I told my friends and my boss about it); plus a bonus: the workflow now runs on my terms, not the vendor's.

Notes for non-technical readers

[1] A server error (specifically a "502") means the system couldn't respond to requests, like calling a shop and getting a busy signal, but for weeks on end.
[2] A script is a small program that automates repetitive tasks. In this case, it replaced the manual copy-paste-highlight-and-date routine.
[3] Think of this like hiring someone to knock on 600 doors and check if a welcome mat is out. Technically works, but guess how long it takes?
[4] Running multiple checks simultaneously, like sending ten people to knock on ten doors at once instead of one at a time. Sounds faster but in this case, caused more confusion than it solved.
[5] Think of the dashboard like a shop window display. Behind it, there's a stockroom with all the actual inventory. The display can be broken while the stockroom is completely fine and accessible.
[6] A built-in browser tool (available in Chrome, Firefox, Safari) that shows every behind-the-scenes request a webpage makes: what it's asking for, and what it gets back. Free, built-in, and powerful. Ctrl + Shift + C or F12 to access it. Try it out for yourself! :)
[7] An automated timer that runs the script on a fixed weekly schedule, like setting a coffee maker the night before, except it generates the list of users who've signed the waiver instead of making coffee.
[8] A reusable email draft with placeholder variables, so "as of [today's date]" fills in automatically with the date I trigger the template, and I never accidentally send a stale date again (or worse, having to manually correct the date every time).