Building a Hospital System With an AI Pair Programmer
2026-09-27
Key takeaway
When an AI writes most of the code, the engineer's job moves to three places: deciding what the system must guarantee, giving the AI rules and memory it can work inside, and checking its output with tests it can't bend. On the HMS hospital SaaS, that meant a written plan, a progress file read at the start of every session, and tests against a real Postgres database on freshly provisioned hospitals. The most useful lesson was an allergy test that passed because its fixture had been shaped to fit the code, not the medical rule.
I built the MVP of HMS, a multi-tenant hospital management SaaS, with Claude Code as a pair programmer. It wrote a large share of the code. People usually ask one of two things about that: whether it's cheating, or whether the code can be trusted. I think the second question is the useful one, and answering it ended up being most of my job.
Here's what that job looked like in practice.
I wrote the plan before any code
The project started from a design prototype and a data spec for one hospital. Turning that into a system many hospitals share meant making decisions no AI should make on its own: how hospitals are kept apart, where tenant data lives, what happens when PgBouncer hands one hospital's connection to another.
So the first thing I wrote was a plan. It covers the architecture, the decisions I'd rejected and why (Citus, an ORM), and milestones small enough that each one ends in something testable. The plan is what each session was measured against. It did change along the way, but those changes were my calls, not the model's.
Priorities changed too. After the front desk was done, I noticed lab orders could be created but nothing could ever pick them up. That was a dead end in the product, so I moved the clinical milestone ahead of the SaaS billing milestone. That kind of reordering is a product judgment, and it's the part I don't want to hand off.
Rules it had to follow
A short list of rules lives in the README, and every change had to respect them:
- Every database query goes through
withTenant(). Nothing touches a connection directly. - The API connects as a role that can't bypass row-level security.
- Migrations are never edited once applied. Fixes go forward in a new file.
- Bookkeeping that has to survive a failed request, like failed-login counters, gets its own transaction.
Each rule came from a real mistake or a real risk. They're short enough to read every session, and specific enough that breaking one is obvious in review.
Memory between sessions
An AI assistant starts every session with no memory of the last one. On a project this size, that's the biggest practical problem. I fixed it the boring way: a progress file whose first line says to read it before starting and update it before finishing. It records the current milestone, what's done, what's next, how to get the stack running, and a long "known gaps" list.
That list turned out to be one of the most valuable parts of the project. When something was left out on purpose, it went on the list with the reason. When something turned out worse than we thought, the entry was rewritten to say so. For example, a doctor's consulting hours were only advisory, and the system would happily book a walk-in at five past midnight. The list is where decisions are recorded honestly, instead of living in someone's head or getting lost between sessions.
The test that was lying
This is the story that changed how I review AI-written code.
HMS blocks a prescription if the patient is allergic to the drug. There was a test for it, and it passed. Then I drove the actual screens in a browser: a patient recorded as allergic to penicillin was prescribed amoxicillin, and the system let it through.
The matcher compared strings. "Amoxicillin" doesn't contain "penicillin", but amoxicillin is a penicillin. The API test only passed because its sample data set the allergy's generic name to "Amoxicillin". The data had been shaped to fit the code, not to test the medical rule.
Nobody did that on purpose. It's what happens when the code and its tests are written in the same breath. They agree with each other, and neither one is checked against the real world. The fix was to model allergies as drug classes, with a set of aliases front desks actually write ("PCN", "Sulfa"), and a warning rather than a hard block for known cross-reactions like penicillin to a cephalosporin. Block too much and people learn to click past the warning that matters.
Tests it can't bend
After that I got stricter about what counts as a test:
- A real database, not mocks. The API tests run against real Postgres, on a fresh hospital created by the real provisioning code for every test suite. A mock can't tell you whether tenant isolation holds.
- Tests that go after the system, not just exercise it. The cross-tenant test tries every route with another hospital's IDs and hashes every table before and after. That's how I found that Postgres skips row-level security on foreign-key checks, which meant rebuilding 235 foreign keys.
- Arithmetic a person can redo. An end-to-end test runs a known day at the hospital (a consultation, a pharmacy sale, a return, a waiver) and checks the revenue report against numbers an accountant could work out by hand. On its first run it found pharmacy returns being reported as charity write-offs. The 16 existing report tests had passed before and after that fix, because none of them had ever told a return and a waiver apart.
- Checking the checker. A later run of the cross-tenant test looked green, but a typo and empty request bodies meant a third of the routes were rejected before they reached the tenant check. A test that can't fail isn't a test.
What I'd tell another engineer
AI made me faster at writing code. It didn't make me faster at knowing what the code should do, and it made checking more important. The model is very good at producing something that looks finished and passes the tests that came with it. Your job is everything around that: the plan, the rules, the memory, and tests built from the real world rather than from the code.
If I had to pick one habit, it's this: when a test passes, ask what data it used and who chose it. The allergy test was green, and it was wrong.
This is the writeup behind HMS — Hospital Management SaaS.
FAQ
- What does an engineer do when an AI writes most of the code?
- Decide what the system must guarantee, set the rules the AI works inside, keep context going between sessions, and check the output with tests that use real data and a real database instead of mocks.
- What's the biggest risk with AI-written code?
- It looks finished. The code and its tests are often written together, so the tests can end up checking what the code does rather than what it should do. In HMS, an allergy test passed because its sample data was shaped to match a string comparison, while the real rule (an allergy is to a drug class) was broken.
- How do you keep an AI coding assistant consistent across sessions?
- Write things down where it will read them. HMS has a plan of record, a progress file that says where work stopped and what's next, and a short list of rules in the README, such as every database query going through one tenant-scoping helper.