
Project Story
Taking over a live clinical app and rebuilding it for HIPAA
Attunement makes clinical software for neuropsychology clinics. In December 2025 we took over its live app and kept it running for the clinics already on it, then rebuilt it in Attunement's own GitHub organization with Next.js, tRPC and Prisma, two-factor sign-in, a versioned intake form engine and a document pipeline that splits scanned packets for clinician review. Later we built a batch de-identification tool for its clinical documents.
- Client
- Attunement
- Industry
- Healthcare
- When
- 2025 to 2026
- Status
- First production deploy in March 2026
- Next.js
- TypeScript
- tRPC
- Prisma
- Extend
- react-pdf
- Playwright
- Amazon S3
- Claude API
Keeping it running first
Attunement’s app was already in use by clinics when we picked it up in December 2025. It had a Python backend and a React front end, and some things clinic staff relied on were broken. Clinic users could only see their own patients instead of everyone’s under the shared tab. Document download and delete didn’t work. Password reset wasn’t sending its link, which we traced to the app still using its provider’s development-only email service.
We fixed those for the clinics on the live app before touching anything bigger. HQ signed a business associate agreement (BAA) with Attunement the same month, since we’d be working in a system that holds patient records.
A new platform in Attunement's own GitHub
In January 2026 we started the rebuild in Attunement’s GitHub organization, so the code, history and pull requests have been theirs from the first commit. It’s Next.js and TypeScript, with tRPC for the API, Prisma for the database and Amazon S3 for uploaded files. By February 19 the test suite had 388 passing tests.
The security work went in early:
- two-factor sign-in with an authenticator app (TOTP, the six-digit codes that change every 30 seconds), with setup and verification flows, its own tRPC router and an enforcement guard
- rate limiting on the public intake endpoints (10 requests per IP address per minute to start), since patients reach the form by link without logging in
- a referrer policy so the token in a patient’s form link can’t leak to another site
- status checks on every form link, so a submitted, revoked or expired link is turned away with a plain message for the patient
- stored file keys that never include the uploaded file’s name, at Attunement’s request, because a file name can contain a patient’s name
Server-side validation doesn’t trust the form either. Field formats such as email and phone are checked on both client and server, values are normalized on submit (trimmed, lowercased where it applies, phone numbers converted to E.164), answers to questions a condition rule hid are stripped before saving, and every submitted question ID is checked against the form definition.
An intake form clinics can change without breaking it for patients
Before an evaluation, patients fill out a long intake form. Clinics need to change it over time, and a patient may take days to finish it. So the form is data. A definition has three levels (section, subsection, question), and condition rules show a question only when earlier answers call for it. Rules can be compound, joined with and/or, using equals, not equals, in a list and not empty. Each definition moves from Draft to Active to Archived, and the engine refuses to start a new session from an archived one. The engine had 282 passing tests when it merged on February 14.
The patient’s answers autosave as they go. Saves are debounced on the client and use optimistic concurrency: each save carries the revision number it was based on, so two saves that collide (two tabs, say) get caught and shown to the patient instead of one silently overwriting the other, and a patient who comes back resumes where they left off. A failed autosave puts up a warning banner that stays on screen with a retry button, and the browser warns before closing the tab with unsaved changes.
It’s built to WCAG 2.1 AA, with semantic HTML, ARIA attributes and screen reader announcements. Every control works from the keyboard, with arrow keys moving within a radio or checkbox group (a roving tabindex). A failed submit scrolls to the first error and moves focus there.
Honestly, one piece isn’t finished. Pinning each patient’s session to the form version that was active when it was created, so publishing a new version mid-session can’t change what they’re filling out, is shown below as designed. Pinning also keeps old submissions readable against the exact definition they were filled out on. We had a draft up in late May 2026. It needed checking against production data our team didn’t have access to, and Attunement refined how it should behave, so we can’t call it done.
From an uploaded packet to checked records
Clinics receive assessments as one scanned packet of rating scales and questionnaires, sometimes from more than one informant (a parent and a teacher, say). We connected the platform to Extend, a document AI service, which splits the packet into separate documents and pulls out each one’s fields, and added support for several informants per patient.
A clinician then reviews each split next to its source pages in a PDF viewer built on react-pdf. The review flow was written up as seven GitHub issues before any of it was built. The clinician can confirm a split, change its assessment type, move its page range, label a split the service couldn’t identify, add an assessment it missed or remove one it shouldn’t have made. The happy path and every alternative shipped on February 22, 2026, in five pull requests. Attunement’s own engineer took on the last integration layer.
Extraction schemas changed over time, and in May one schema’s newer field structure, a nested array, made the edit mode for extracted data collapse and corrupt its local state before anything reached the server. We rewrote edit mode to handle nested structures and fixed a couple of race conditions that could briefly show stale extracted data.
Tests and the first deploy
The first production deploy went out March 3 to 5, 2026, with a handful of fixes alongside it: a higher intake rate limit, a fix for archived form definitions, a script to bootstrap the first super admin and a clinic ID shown in the admin. A localhost link in invite emails was caught and fixed the same day.
Playwright end-to-end tests merged on March 6 in four parts: shared setup and utilities, sign-in and admin flows, patient intake, and document processing. They run across Chromium, Firefox and Safari’s WebKit. Because that was a big batch of code at once, it went into a separate e2e-stage branch first and only moved to staging once both teams were happy with it.
De-identifying clinical documents in batches
In May 2026 we built a second version of a de-identification tool, which strips protected health information (PHI) out of clinical documents before they’re used anywhere else. An open-source de-identification model does the first pass and a second check built on Claude reviews it. Names the earlier QA round flagged as missed are now caught, and the names of neuropsychological tests (Wechsler, the Boston Naming Test) are no longer redacted as if they were people.
Clinicians keep two term lists: names to always redact, and terms to never redact. The interface takes batches of up to 100 documents with progress shown per page. A single page can be rerun, and a clinician can apply “redact this term” across one document or the whole batch. Output replaces each piece of PHI with a typed marker such as [NAME], [DATE] or [PHONE], so whoever uses the text next knows what was there without seeing it. Each batch comes with a manifest.json recording the pipeline and model versions, every forced redaction with who added it and when, and per-page status. HIPAA doesn’t strictly require that manifest, and we said so, but it gives an auditor something concrete to look at.
On its first test, 10 documents and 108 pages, it ran with zero errors and caught every name the QA round had flagged. In the first large batch, pages took 80 to 128 seconds each. Moving it to a larger server brought that to 3 to 11 seconds, which cut the estimate for 100 documents (roughly 1,700 pages) from about 10 hours to about 2.5. Output is plain text for now. Keeping Word formatting through redaction is a meaningful build of its own, and we left it for when filing needs it.
What Attunement said
Two days after the document review paths shipped, Kyle Combes at Attunement wrote, “Thanks for all of your speedy work on getting this done ahead of schedule.” What was left on that round’s list was a hardening pass on the intake form and the flow for setting up a new clinic.
Questions about this project
HQ, a software design and development agency in Ogden, Utah, took over Attunement's live clinical app for neuropsychology clinics in December 2025. HQ fixed the issues affecting clinics on the live app, signed a business associate agreement (BAA) with Attunement, and then rebuilt the product in Attunement's own GitHub organization with two-factor sign-in, rate limiting on public endpoints, a versioned intake form engine, a WCAG 2.1 AA accessible intake form and a document review pipeline.
HQ rebuilt Attunement's clinical platform in Next.js, TypeScript, tRPC and Prisma, starting in January 2026, with its first production deploy in March 2026. The work included TOTP two-factor authentication, a versioned intake form engine with condition rules, a Draft, Active and Archived lifecycle and 282 tests at merge, autosave with conflict detection, an accessible patient intake form, Playwright end-to-end tests, and a document pipeline that uses Extend to split and extract uploaded assessment packets for clinician review. In May 2026 HQ also built a batch de-identification tool for Attunement's clinical documents.
Intake forms in the platform HQ built are stored as versioned definitions made of sections, subsections and questions, with compound condition rules that show questions based on earlier answers. Each version moves from Draft to Active to Archived, and an archived version can't start a new patient session. Pinning each in-progress session to the version it started on was drafted in May 2026 and was not confirmed finished at the time of writing.
The intake form HQ built for Attunement autosaves with debounced client-side saves and optimistic concurrency. Each save carries the revision number it was based on, so two conflicting saves, such as from two open tabs, are detected and shown to the patient instead of one overwriting the other. A failed save shows a persistent warning with a retry button, the browser warns before closing with unsaved changes, and a returning patient resumes where they left off.
An uploaded assessment packet is split into separate documents and its fields are extracted by Extend, a document AI service. A clinician then reviews each document next to its source pages in a react-pdf viewer, and can change its assessment type, adjust its page range, label documents the service couldn't identify, add missed assessments or remove extra ones. HQ shipped all of those review paths on February 22, 2026.
HQ built a batch de-identification tool that runs an open-source de-identification model and then a second check built on Anthropic's Claude. It replaces protected health information with typed markers such as [NAME] and [DATE], lets clinicians keep lists of terms to always or never redact, and writes a manifest of model versions and manual redactions for audit. On its first test of 10 documents and 108 pages it ran with zero errors and caught every name an earlier QA round had flagged.
Yes. HQ built Attunement's patient intake form to WCAG 2.1 AA, with semantic HTML, ARIA attributes, full keyboard navigation including arrow-key movement within radio and checkbox groups, screen reader announcements, and focus moved to the first error when a submission fails.
Let's build together
Whether you're looking to design, build, update, or refresh an existing app or website, we can help. Even if we're not a good fit, we will help point you to somebody who might be.
Contact us