Skip to content
Nicolás Forero

Case study, at work

Pharmacogenomics reports that match medicines to a patient’s genes

A patient gives a DNA sample, and MyEngene returns a report: which medicines should work normally, which need a different dose, and which carry raised risk. I work across its apps, its API and its database, and I brought coding agents into how we build it.

Role
Senior software engineer
Since
September 2023
Built with
Next.js, React, TypeScript, Python, Flask, PostgreSQL, AWS, Claude Code

The problem

The genetics are not the hard part. Several public scientific bodies publish guidance on the same gene and drug, and their guidance overlaps and sometimes disagrees. The product has to turn all of that into one sentence a person who isn’t a specialist can act on.

Patients read their report in a web app and as a PDF. Staff manage the orders in an internal app.

My role

I joined as a frontend engineer. Today I’m the engineer who has been on the team longest, and I look after all three codebases: the patient app, the API with its clinical engine, and an internal app an outside team built that I took over.

I built three things from scratch: the browser end-to-end test suite, the agent and skill tooling described below, and a scripted workflow to refresh the database.

The system, at block level

Patient appNext.js and React. Reports and their PDFs.

Internal appNext.js and React. Orders, for staff.

API and clinical enginePython and Flask, with caching and server-side PDFs.

PostgreSQLBuilt by scripts that ingest public drug, regulatory and pharmacogenomic sources.

It runs on AWS, described in Terraform, including logging of every access to genomic data. Simplified to blocks, with no code or configuration.

The hardest decisions

One scoring rule, one implementation

Problem
The rule that says how risky a drug is for a patient had grown two independent implementations behind different endpoints, and they didn’t always agree.
Decision
Make the newer one canonical and move the legacy path onto it step by step, each step behind tests.
Tradeoff
A long stretch where both exist and the tests have to cover both.
Outcome
Every path now under test gives one answer. Converging the rest of the catalogue is ongoing.

End-to-end tests first

Problem
There were tests for the scoring maths, but not for routes, authentication, database access or the PDF. That is exactly where the two implementations disagreed.
Decision
Write browser tests against the real running stack first, because a disagreement between layers is the failure that matters most here.
Tradeoff
They’re slower, need seeded data, and take care to keep from flaking.
Outcome
They found real defects early, including a frontend request that quietly dropped a response it didn’t expect.

Keep who said what

Problem
A normalisation step merged guidance from different scientific bodies, so the interface could attribute a statement to the wrong authority.
Decision
Carry each source’s identity through the whole pipeline, and deduplicate on content, not on label.
Tradeoff
More rows and slower pages.
Outcome
Every statement now names the body that made it. The page speed is still being tuned.

How we build with AI

The repository carries six agent definitions, for the backend, the data pipeline, the frontend, QA, operations and clinical safety, and skills for refreshing the database, driving a browser, querying the codebase and keeping memory across sessions. A task goes like this:

  1. Step 1

    I state the problem

    To one orchestrating session set up to act as an engineering manager, not as an implementer.

  2. Step 2

    It plans across the agents

    It reads the code, asks the agents the task touches, several at once when it cuts across layers, and comes back with one plan.

  3. Step 3

    The right agent proposes the change

    And the QA agent extends the tests for it.

  4. Step 4

    I run it and read every line

    I run the app and the browser suite myself and review the whole diff before it’s committed.

Some things stay with people by design: clinical judgment, deployments, database restores, and anything that touches production.

Guardrails, and where they came from

Agents can state wrong things with confidence. One insisted the patient app used a state library it doesn’t use, and that claim had made it into an agent definition before we caught it. Another time a slowdown was blamed on a recent change, until careful timing showed the change wasn’t the cause.

So the standing rule is simple: no agent signs off on clinical correctness. Any change to clinical logic or wording goes to the safety reviewer agent and is flagged for a pharmacist to approve. That gate has already held back a change that was otherwise ready to ship.

What I measured, and what I didn’t

The test suites grew while I wrote this, and I counted them in git, but I’m leaving the counts out until they cover a longer stretch.

I didn’t measure how many drugs the two scoring paths disagreed on before the fix, so I don’t claim a number. What I can say: the cases we could reproduce were found, fixed and are now under test. I don’t claim a speed improvement either; the slow endpoint I looked into is still being tuned.

What I’d do differently

Set up continuous integration first, because most of the rest follows from it. Never let a second implementation of a scoring rule exist, even for a while. Pin every dependency, even in one-off data scripts. Keep both frontends on one stack. And write the test that proves a bug exists before fixing it.