How a Mid-Market Healthcare Company Modernized Its Internal Operations Platform – and Adopted Agentic AI Without Losing Its Mind

data engineering

A mid-market healthcare point-of-care media company had an internal application – critical-path to the business, that handles the booking and management of advertising campaigns and flights across the company’s point-of-care network – that had been quietly neglected for years. The engineers who built it had just left, the documentation was incomplete, and the application was running on out-of-support versions of every major component. Pinnacle helped modernize the application, updated the SDLC, and put an AI-assisted engineering workflow at the center of how the new team ships code – all in 18 weeks.

A mid-market healthcare point-of-care media company came to us with a problem every engineering leader dreads, even though it rarely makes it into a case study.

Not the problem of “we have a legacy codebase.” Every company has that.

The problem of: the codebase had been neglected for years, the engineers who had built it had moved on, the documentation was incomplete and outdated, and a new team had inherited a system they didn’t fully understand – running on out-of-support versions of every major component in the stack.

The application itself was not customer-facing. It was the company’s main media inventory and order management system. Without this application customer advertising campaigns and flights could not be booked.

It was, in other words, a critical-path internal application that the new engineering team could not afford to take offline –  but also could not afford to leave alone.

In 18 weeks, we did three things at once: modernized the application, modernized the SDLC, and put an AI-assisted engineering workflow at the center of how the team ships code.

This is what we did, how we did it.

1. Situation

The client is a mid-market healthcare point-of-care media company. The company operates a digital network that reaches patients and healthcare providers at the point of care – primarily in physician waiting rooms and exam rooms – delivering patient education and health information through digital screens and content platforms. The business is funded through pharmaceutical advertising and sponsorship, enabling healthcare practices to receive educational resources at no cost.

The internal application we were brought into is the company’s media inventory and order management system. This application handles the booking and management of advertising campaigns and flights across the company’s point-of-care network, supporting sales operations and campaign delivery.

The application architecture, as we found it:

Frontend: A ReactJS single-page application, pinned to a version that had not received updates in years.
Backend: A Django monolith, originally written for Python 3.6, running on Django 2 – both versions out of  support.
Datastores: Postgres as the system of record; Snowflake for analytics and reporting.
Team: The application team was one person (the dev lead) when I joined. A new FTE was added a couple months later, making us a three-person dev team including myself.

The business was healthy and growing. The internal tooling that ran media inventory management and campaign booking was not.

2. Problem

Three interlocking problems, all landing on the new team at once:

Problem 1: A knowledge vacuum where the institutional memory used to be.
The senior engineers and SMEs who had built the application had moved on. Their replacements were engineers new to this application – the dev lead was an experienced developer but had limited exposure to AI-assisted development and had only minimal prior familiarity with this application’s codebase before assuming responsibility for it. The internal documentation was sparse.

Problem 2: Every layer of the stack was behind.
The Python version was relatively ancient, preventing updates of other components to the system. The Django version was out of support. The React version was 5 major version behind current. The Postgres version was three major versions behind the company standard.

Problem 3: AI tools were available, but there was no team-wide practice and no policy framework.
This was the most interesting constraint, and the one that made the rest of the engagement possible. The company had made Cursor (and later Claude Code) available to its developers, with an explicit posture from leadership that amounted to: “play with it if you want, but if you don’t, that’s ok too.” There was no overarching policy, no required training, no enforcement, no security review. Just access, and permission.

Nobody on the team I’d been assigned to had started using AI tools at all. They were aware of the tools, but they hadn’t integrated any of it into their daily workflow. The institutional memory problem (Problem 1) and the upgrade problem (Problem 2) meant they were already heads-down on hard problems with no margin to try something new on their own.

The cost of all three together: A new team that should have been able to execute a quarter’s roadmap was instead constrained to delivering high-priority bugfixes while struggling to advance a critical compliance-driven feature. The technical debt was significant, and there was insufficient capacity to address necessary upgrades, code cleanup, or documentation – foundational work essential to the system’s long-term health.

3. Approach

I joined the engagement as the Pinnacle consultant embedded on the team – focused on documentation, framework modernization, and AI tooling coaching. I was not the team lead. The client’s team lead was juggling day-to-day bugfixes and a drop-dead new feature that had to ship during the engagement. I had no formal authority to impose anything – every move in this case study was adopted because the team agreed it was right, not because I could make them.

The single most consequential decision I made on the engagement was convincing the team that the documentation should live in the repo as markdown files with embedded Mermaid diagrams. I had to sell this before anything else could work. The company standard, handed down by enterprise architecture, was that all application documentation lived in Confluence. The team’s initial position was that this meant docs had to be authored in Confluence – which would have kept them outside the codebase. I argued for a different approach: author the docs in the repo as markdown, then sync them to Confluence via a GitHub Actions workflow. This was the key to getting buy-in. There were four specific benefits I articulated to make the case:

  1. Docs travel with code, so they stay up to date. When a PR changes a piece of the architecture, the reviewer can ask -by convention, in the PR review UI – “were any of the .md files in this repo touched?” If not, the PR is incomplete. This made documentation a reviewable artifact, the same way tests are. (More on this below – it was the single biggest behavior change on the team.)
  2. AI tools can read the docs out of the box. Once the docs are in the repo, Cursor and Claude Code see them without any MCP-server setup, without any enterprise authentication flow, without any permission boundary between “the code” and “the docs.” They’re just files in the same tree. A new engineer – or an AI agent – can ask “how does this work?” and the answer is in the same place the code is.
  3. AI tools can update the docs as part of their normal workflow. Once a rule is in place (“when you change a code path, also update the relevant markdown file”), Cursor and Claude Code can be prompted to do that automatically. The docs become a living artifact, updated by the same agents that update the code.
  4. Enterprise compliance is automatable, not a blocker. The company had an enterprise architecture mandate that all application documentation live in Confluence. The team’s initial read of that mandate was that docs had to be *authored* in Confluence – which would have killed the in-repo decision entirely. I argued for a different reading: the mandate said where docs live, not where they had to be authored. We built a GitHub Actions CI/CD job that ran on every PR merge, took the markdown files from the repo, and synced them into Confluence via the Atlassian REST API. The repo remained the source of truth. Confluence remained the mandated system of record. The two stayed in sync without anyone copy-pasting anything. (See Section 4.3 for the implementation details and gotchas.)

This decision ended up being the foundation for everything else in the case study.

4. What We Did

4.1 Documentation archaeology + in-repo architecture map (the first two weeks).

With the docs-in-repo decision in place, I established three rules for how documentation would be managed:

  1. Architecture documentation lives in the ./docs/ directory in the code repositories, as .md files.
  2. Diagrams are embedded Mermaid, rendered by GitHub on PR view and by Cursor/Claude Code on read.
  3. Every PR that touches architecture is checked by the reviewer for “were there any .md files updated in this PR?” – and if not, the PR can be blocked on documentation grounds. This was enforced by convention: reviewers were instructed to look for doc updates when reviewing PRs.

I then used Cursor to produce comprehensive developer documentation for the application, including overall architecture, data flows, upstream and downstream system dependencies, code organization, developer setup, deployment and CI/CD, and code quality and testing requirements and patterns. Where pre-existing docs and the code disagreed, I treated the code as truth and corrected the docs. All generated documentation went through peer review with the developers and the dev lead for accuracy and completeness.

4.2 Cursor-generated documentation as the AI coaching channel.
Once the team had agreed that docs lived in the repo, I had a wedge I could use to do two things at once: generate the documentation the codebase actually needed, and give the rest of the team a hands-on education in how to use Cursor productively. While I was generating (via Cursor) generating the vast majority of the initial documentation, the other devs on the team were (per convention) generating docs related to code changes they were making.  The trick was using PR review of AI-generated docs as the coaching vehicle.

Here’s how it worked in practice. I used Cursor myself to draft the architecture docs for each major component of the application, giving Cursor guidance via tried-and-true prompt patterns. Then I opened each generated doc as a pull request against the repo. The team got to review them the way they’d review any other PR: line comments, suggested edits, blocking reviews where the doc was wrong. This is where the AI coaching happened, and it happened for free.  Every docs PR was a chance to:

  1. Show the team what good Cursor output looks like.
  2. Show them what bad Cursor output looks like and how to fix it.
  3. Show them how to write Cursor rules to prevent the bad output in the first place. This was the highest-leverage thing I taught.
  4. Teach them how to use Cursor to help review the PR itself – specifically, how to use Cursor to compare the generated docs against the code to verify accuracy.
  5. Walk through the docs with the other developers in person, verifying accuracy and answering “why did Cursor phrase this that way?” – questions that became the entry point for explaining prompt patterns, when to trust the AI, and when to override it.

I allocated time in those PR reviews to work hands-on with the other developers – this was by design, as it gave me the opportunity to coach and teach them Cursor techniques in context. Using Cursor to generate the documentation was dramatically faster than writing it by hand – it would have taken orders of magnitude longer without AI assistance. This was not a “give it to AI and walk away” process; there was significant human review and editing involved at every step. The docs PRs were the channel. The docs were the artifact.

By the time the docs were merged, the whole team had had the opportunity to read, ingest, review, and update all the documentation, and we had a high degree of confidence in its accuracy and completeness.

4.3 Confluence sync job (the bridge to enterprise compliance).
With the docs-in-repo decision made, I implemented a GitHub Actions workflow to satisfy the enterprise architecture mandate. The workflow ran on every merge to main, walked /docs/ directory in the repo, and synced each markdown file into the team’s Confluence space via the Atlassian REST API. Added or modified files became new or updated Confluence pages; deleted files archived their corresponding pages (rather than hard-deleting them – enterprise audit requirements). If the job failed, GitHub notified interested parties so we knew if/when things weren’t working.

The end state: the repo was the source of truth. Confluence was the mandated system of record. They stayed in sync without anyone copy-pasting anything.  The mandate was satisfied. The team’s workflow was not compromised.

4.4 Python upgrade first, with Cursor planning and enhancing the test suite.
Once the docs were in place and the team had been through enough docs PRs to be comfortable with Cursor, I moved on to the framework upgrade. I started with Python – and I started with Python deliberately because it felt like the easiest win. Newer Python versions are mostly backwards compatible with older versions, unlike the massive backwards compatibility problems with React and other frameworks.  Additionally, updating libraries like Django required newer versions of Python anyway.

Cursor’s role in the Python upgrade was specifically not “write the upgrade code.” Cursor’s role was in planning and test-suite enhancement.

On the planning side, I leveraged Cursor to do an analysis of what needed to be updated. Left to my own research, I would have overlooked issues like docker-compose.yml changes or GitHub Actions workflow updates. I then used Cursor to generate a plan of action for the upgrade, asking it to write the plan as a markdown file in the format of a checklist that could be updated as we progressed. The plan was socialized with the team and when everyone agreed it was complete, I then used Cursor to implement the plan itself.

On the test-suite side, I included a step in the implementation checklist to add or update unit tests that would illuminate issues with the new Python version. About 70% of what Cursor proposed was useful as-is; the other 30% was either wrong (testing the wrong thing) or unnecessary (testing behavior that hadn’t actually changed). The team got to see, in real time, what “AI-assisted test enhancement” actually looks like in practice and why the human review step is non-negotiable.

The Python upgrade was delivered as a single PR containing roughly a half dozen commits. My prompt to Cursor was explicit: only change code that breaks as a result of the update. If code used outdated but still accepted code style or practices, Cursor was instructed to leave it alone. This kept the upgrade focused and minimized unnecessary churn.

By the end of the Python upgrade, the team’s relationship with Cursor had shifted again. They were no longer “using Cursor to read code.” They were “using Cursor to plan a migration, with rules that prevent it from going off the rails, and human review steps that catch the rest.” That was the second unlock. It set up Section 4.5 – the moment the team took the pattern and ran it themselves on the remaining upgrades.

4.5 Django upgrade – and the repeatable upgrade pattern that transferred to the team.
Once the Python upgrade shipped and the team had watched it work, the engagement crossed an inflection point. Up to that point, I had been driving: I had sold the foundation, I had used Cursor to generate the docs, I had walked the team through the docs PRs as the AI coaching channel, I had personally used Cursor to plan and execute the Python upgrade. The team had been involved – reviewing docs, contributing Cursor rules, learning the workflow – but I had been the one operating.

After the Python upgrade, that changed. The team didn’t wait for me to drive the next upgrade. They had a workflow they trusted, and they started running it on their own. Of the four major framework upgrades the codebase needed, I personally drove two – Python and Django – and the team drove the other two – React and Postgres – themselves, using the same pattern.

The pattern that emerged was a six-step repeatable loop. I didn’t design it as a methodology; it just emerged from how the team was using Cursor, and once it had emerged, we documented it in /docs/upgrade-pattern.md.

  1. Have Cursor review the documentation to verify dependencies of [tech X].  What does the codebase actually say this depends on? What’s pinned, what’s loose, what hasn’t been touched in three years?
  2. Have Cursor update or add unit tests specifically targeted at verifying an upgrade doesn’t break anything.  Coverage on the affected paths before any upgrade code is written.
  3. Have Cursor create a plan document (a markdown file in the repo) describing how the upgrade will be done – the rollback path, the things to watch out for. Plan goes in-repo, plan goes through PR review.
  4. Circulate the plan with the other developers on the team. Review, revise, approve.  No one – me, a senior engineer, anyone – starts writing upgrade code until the plan has been agreed.
  5. Have Cursor implement the plan. The mechanical parts are AI-generated; humans review every line. The team uses the .cursor/rules/ patterns they’d learned during the docs work to keep Cursor from going off the rails.
  6. Validate with Cursor and the existing CI/CD tooling, then PR the upgrade as a single PR with multiple commits.  Each commit is independently reversible. The whole upgrade gets human review. Each PR is squash merged and easily revertible if things go south. The overall upgrade emerges as a sequence of individual, reversible squashed commits rather than one big-bang merge.

I drove the Django upgrade using this pattern myself, in the room with the team, the same way I had done Python. Then the team took it and ran. I sat in on reviews but I was not driving. By the time the last upgrade merged, the team had run a successful multi-component framework upgrade without me in the room for the last two of the four components.  That was the proof the engagement worked: not that I had done the work, but that the team could do it without me.

4.6 Coaching seasoned engineers on AI-assisted workflows.
This ended up being my primary deliverable on the team. When I joined, there was just one other developer on the team – the dev lead. During the engagement, the team hired another developer, also senior but with no prior experience with AI-assisted development. Throughout the process, I made myself available to help and coach them on using the tools effectively.

My coaching focused on three areas:

  1. Teaching them how to prompt to get good results. I showed them the patterns that work, the patterns that don’t, and how to iterate on prompts when the output isn’t what they need.
  2. Showing them how to create rules that work.  Through the docs PR reviews and upgrade work, I demonstrated how to write Cursor rules that keep the AI on track and prevent it from going off the rails.
  3. Helping them troubleshoot when things weren’t working well.  When they hit a wall with Cursor – whether it was generating bad code, missing context, or producing hallucinations – I worked with them to diagnose the issue and adjust their approach.
  4. Helped them to recognize opportunities to utilize AI tools to simplify their lives.  One concrete example illustrates this coaching approach. The new developer was tasked with documenting a business process that fed into our application. After scheduling and attending meetings with business stakeholders to understand the processes and their rationale, she mentioned in standup that she would be crafting markdown documentation in the ./docs/ directory based on her notes. I suggested she leverage the Teams meeting transcripts by saving them to a directory accessible to Cursor, then prompting Cursor to read the transcripts and generate a first draft of the documentation. This approach saved over a week of work and demonstrated Cursor’s utility beyond pure coding tasks.

By the end of the engagement, both developers were comfortable using Cursor as part of their daily workflow. They had developed their own rules in the .cursor/rules/ directory and were applying the patterns we’d established during the docs and upgrade work to their own tasks.

4.7 Atlassian MCP – extending Cursor into the team’s planning workflow.
By this point in the engagement, the team was already using Cursor effectively: planning changes in response to Jira stories, implementing those changes, updating code, adding and editing docs and unit tests, and occasionally using Cursor to help review code in PRs. This was where I introduced them to the Model Context Protocol (MCP) and specifically to the Atlassian MCP server.

I walked the team through the MCP integration in five steps:

  1. Pulling Jira tickets into Cursor. First, I showed them how to use MCP to pull the Jira tickets they were assigned so that Cursor was fully aware of the requirements before planning began. I also showed them practical prompting techniques to integrate the Jira ticket text with the existing docs and source code maximize generated plan accuracy.
  2. Updating Jira tickets on PR. After that, I showed them how to use MCP to have Cursor update the Jira ticket when they were submitting their PRs. This closed the loop between implementation and ticket status, keeping the work tracking in sync with the actual code changes.
  3. Generating tickets from meeting transcripts. Once the team was comfortable with those basics, we expanded the use case. I showed them they could take transcripts from meetings with business stakeholders or internal team planning and ask Cursor to generate Jira tickets from those transcripts. During sprint planning, we would have someone with Cursor at the ready to update and edit Jira tickets with new information or questions as they emerged from the planning discussion.
  4. Cursor rules for Jira conventions. By the end of the engagement, we were working on generating Cursor rules that would help Cursor adhere to the company’s Jira formatting, tagging, and mandatory field conventions. This further streamlined the Cursor-to-Jira connection, ensuring that AI-generated tickets met the organization’s standards without manual cleanup.
  5. Team-driven MCP innovation. Near the end of the engagement, I showed the dev-lead how to use FastMCP to write his own MCP server. He implemented an MCP server to query the company’s NewRelic logs, giving him the ability to simply talk to Cursor about something he’s looking for in the logs and have Cursor retrieve it for him. This demonstrated that the team was not just adopting the patterns I introduced – they were extending them on their own. The team continues to grow in their AI-assisted use even today.

Closing – what to take away

The shape of this engagement is what I believe every legacy-web-app modernization is going to look like by the end of 2026: framework and language upgrades, documentation rebuild, and an AI-assisted SDLC woven through all of it from day one, not bolted on at the end. But the most important thing about this engagement isn’t what I built. It’s that I was transferred to another team at the client to start pollinating the same pattern there – an engagement that continues today – and the original team kept running the practice without me in the room.

The key moves that made this work:

  1. Move the documentation into the repository, then use AI to generate it as a coaching channel. I used Cursor myself to draft the docs, then PR’d them through the team. Every docs PR was a small lesson in how to use Cursor on a real codebase. By the time the docs were merged, the team had been through enough of those lessons to use Cursor on the framework upgrade without me sitting next to them. The durable artifact was the rule library in .cursor/rules/ – every rule the team wrote during the docs PRs was a lesson in how to make the AI better that survived after I left.  Documentation embedded in the repo has many benefits to AI-assisted software development.
  2. If your enterprise mandates Confluence (or some other system), automate the bridge – don’t fight the mandate.  The company had an enterprise architecture mandate that all application documentation live in Confluence. Rather than fighting that mandate, we built a GitHub Actions job that ran on every PR merge, walked /docs/ in the repo, and synced the markdown files into Confluence via the Atlassian REST API. The repo remained the source of truth. Confluence remained the mandated system of record. The two stayed in sync without anyone copy-pasting anything.
  3. Sequence the engagement: foundation, then leverage, then upgrades.  The docs-in-the-repo decision unlocked the Cursor-generated-docs-as-coaching-channel pattern, which unlocked the team’s Cursor proficiency, which made the framework upgrade tractable. If I’d tried to start with the framework upgrade, the team wouldn’t have had the AI muscle to contribute reviews on the upgrade PRs, and the patterns wouldn’t have stuck. Order matters: foundation first.
  4. Use MCP to extend Cursor from the engineering half of the loop into the planning half — and don’t stop at Atlassian. The Atlassian MCP server is the unlock that turns Cursor from a coding tool into a delivery tool. The team adopted it in stages: pulling Jira tickets into Cursor for context, updating tickets on PR, generating tickets from meeting transcripts, and finally using Cursor rules to enforce Jira formatting and tagging conventions. This closed the loop between planning and implementation. But the Atlassian server is just one example of a much broader pattern. The same principle applies across the full SDLC toolchain: any tool your team already operates from a CLI or API is a candidate for the same treatment. The GitHub CLI (gh) lets Cursor create and manage pull requests, branches, and issues directly from the agent. The AWS CLI (aws) lets it query infrastructure state, tail logs, or check deployment status without leaving the coding environment. Any tool with a command-line surface area can be wrapped in an MCP server – and once it is, Cursor or Claude Code can use it the same way a developer would. The dev lead on this engagement demonstrated exactly this when he built his own MCP server on top of the NewRelic API. That instinct – if I can reach it from a terminal, the AI can reach it too – is the right mental model for where AI-assisted SDLC tooling is heading.

The most important outcome: by the end of the engagement, the team was shipping faster than defects could be filed or features could be requested. The backlog started shrinking for the first time.  That was the proof that the engagement worked – not that I had done the work, but that the team could continue doing it without me.

I was transferred to a different team within the client organization to continue pollinating these same AI-assisted development practices – an engagement that continues today. The first team kept running without me. Since then, the team has transitioned from Cursor to Claude Code – and the same rules, the same upgrade pattern, and the same documentation practices carried over to the new tool without any additional coaching from me. That’s the real measure of success: the patterns outlasted the consultant, and they outlasted the specific tool they were built on.

If you’re staring at a legacy internal tool – the kind that runs the business but never gets a roadmap quarter – and wondering whether the upgrade is “worth it now” or “should wait until next year,” the answer is: the work is the same either way. The difference is whether your team uses the AI-assisted workflows that have become standard practice in 2026, or whether they’re still doing the upgrade the way it would have been done before Cursor and Claude Code (as well as many others) existed.

Pinnacle Solutions Group is a technology consulting firm whose consultants average over 15 years of field experience, with practices spanning AI solutions, custom development, data engineering, DevOps, and technology assessments. If you’d like to talk through what this kind of engagement looks like for your codebase, reach out at https://pinnsg.com/get-started/.