Case Study - The Data Model That Makes an Unreviewed AI Report Unreachable
A clinical assessment pipeline where an LLM drafts a report, deterministic logic owns every medically consequential decision, and the data model — not a status flag — makes it structurally impossible for a patient to see a draft before a physician signs off.
- Client
- Metabolic Health Platform
- Year
- Service
- Regulated AI & Clinical Safety Architecture
- My role
- Fractional CTO
We built the pipeline that turns a member’s intake survey and lab results into a clinician-reviewed clinical assessment, then built the cross-repo architecture that makes an unreviewed draft unreachable by a patient — not policy-forbidden, structurally impossible.
The challenge
The client delivers personalized care plans built from a member's health survey and blood work, and every member needs an individualized clinical assessment. Written by hand, that doesn't scale past a small caseload — so the obvious move is to let an LLM read the survey and labs and draft the narrative.
That obvious move is also the dangerous one. Two decisions in this pipeline change a patient's care: which treatment pathway a member is on, and whether they have enough data — survey and labs — to be assessed at all. Getting either wrong isn't a copy problem; it's a wrong report reaching a patient. And once a draft exists, the business needed a guarantee that a physician reviews it before the member sees it — across three separately built systems (assessment generation, the clinician-facing EMR, and the patient app) that deliberately don't trust each other.
My role
I owned the product and technical direction for the assessment pipeline and its publish workflow: what the model was allowed to decide, how review fits the clinicians' working day, and how the three systems fit together. The work ran across engineering on all three codebases, alongside the client's physicians and clinical operations team — the people who review every report.
Leadership decisions
Decision 1
The model drafts; it never decides anything that changes care.
Instead of: Letting the LLM infer the treatment pathway and data-readiness from context — faster to ship, and invisible when it is wrong.
The pathway carries real clinical meaning: it determines which of two structurally different assessments a member receives. So it became a deterministic rule, run once and handed to the model, that can be tested and read by a clinician. The same went for readiness — no partial report the moment survey data arrives; nothing is drafted until survey and labs are both in.
Decision 2
Make the shared data the integration point, not the APIs between teams.
Instead of: Having the EMR and patient app call each other's services across two separate identity systems.
Cross-system API calls would have made patient safety depend on every team getting every future change right. Instead each system reads and writes shared storage through its own access controls, so each one's blast radius stays its own. The patient app can't show an unreviewed draft because the only field it reads doesn't exist until a clinician publishes — a guarantee that survives future changes elsewhere, rather than one that relies on them being careful.
Decision 3
Keep what the model said separate from what the physician decided.
Instead of: Ordinary version history, the default for any editing tool.
The moment a clinician first edits a draft, the model's original is preserved, and every later edit is snapshotted. That isn't an audit-log habit borrowed from software: when a physician changes a generated clinical claim, the organization needs to be able to reconstruct what the model said, what the physician changed, and when. That provenance is what clinical governance of an AI system rests on.
The solution
Intake and lab results flow in automatically. Deterministic rules settle pathway and readiness; the model then drafts the four sections of the report in parallel, producing an editable narrative for the clinician and a structured version for the product. A physician reviews and, where needed, edits — then publishes. Publishing freezes what the member sees, so a later edit can never silently change a report they've already read.
A status-flag bug can't leak a draft, because there's no code path that reads the draft location from the member's side at all.
Impact
- Physicians review instead of writing from scratch. Individualized assessments are no longer limited by how many a clinician can author by hand.
- No manual data handling on the way in. Members' intake and labs flow into the pipeline without an administrator uploading anything — what used to be a manual step is gone.
- A safety guarantee that doesn't depend on discipline. A patient cannot see a report before a physician has reviewed and published it, because the data enforces it — not a status check a future change could bypass.
- An auditable record of clinical judgment. Every report carries what the model drafted and what the physician changed, ready for clinical governance and review.
Under the hood — the technical detail
Nothing clinically important is decided inside a model call. Intake is normalized to stable internal keys before anything downstream touches it, and the pathway is derived by a pure function, run once, and passed to generation rather than re-derived by the model. Every generation produces both an HTML narrative a clinician can edit and a structured JSON bundle from a single pass.
The three systems — assessment generation, the clinician EMR, and the patient app — are separately deployed repositories on two separate Cognito user pools. Rather than calling each other's APIs, each reads and writes the same DynamoDB tables and S3 bucket directly, through its own Lambdas and its own pool. The member-facing read endpoint only ever fetches a publishedReportS3Key field that is null until the publish step sets it.
A DynamoDB Streams handler normalizes raw intake answers and writes them to the patient's record; a second stream handler fires only once both normalized intake and a completed lab evaluation exist, atomically claims an assessment ID, and enqueues to SQS. The consumer runs the canonical pathway classifier, creates an assessment shell, and fans out four section-generation jobs to a second queue, each calling Claude via the Anthropic SDK, streamed. An assembler waits for all four, builds both the HTML and JSON report, and uploads to S3.
S3 holds the report at each stage of its lifecycle — canonical (mutable during review), .original.html (preserved on first edit), timestamped edit snapshots, and .published.html (the only thing the member app can read). State transitions are enforced with DynamoDB conditional writes rather than application logic, so two clinicians publishing at once can't corrupt state — at worst they over-increment a version counter, which is explicitly tolerated. Every log line follows a PHI-safe convention: no answer content, no lab values, no report text — only IDs, statuses, and S3 keys.
- Regulated AI system design
- Human-in-the-loop LLM review
- AWS serverless (Lambda, DynamoDB Streams, SQS, S3)
- Cross-repo, cross-identity-pool architecture
- HIPAA-aware system design