TicVision: from DynamoDB to FHIR-safe in a weekend

illustrative rebuild — our own dogfood, not a production migration

A Tourette's symptom-tracking app we built ourselves the normal way: bespoke DynamoDB tables, ~15 CRUD Lambdas, a hand-rolled HIPAA audit log, and data that could never talk to a real EHR. Here's the same app rebuilt on bonfireDB — less code, and FHIR, audit and interop for free.

The subject

What TicVision is

TicVision is one of our own apps for people living with Tourette's and tic disorders — built by the Simplest Healthcare team, the same team behind bonfireDB. Patients log individual tics, fill in a weekly check-in, and optionally connect a doctor who can view their data. Strip away the domain and it's a familiar shape: auth plus CRUD over a symptom time-series, with a clinician read-side and HIPAA on top.

A React Native (Expo) mobile app — offline-first, syncs when it reconnects.
Patients record tics: type, intensity (1–10), time of day, mood, trigger, a note.
A weekly check-in captures adherence across the seven days of the week.
Doctors connect to a patient by request, then read that patient's profile, tics and check-ins.
HIPAA throughout — every PHI access is meant to be audited.

This is an illustrative rebuild — our own dogfood, grounded in TicVision's actual backend (SAM template, Lambda handlers, docs). Because we build both, the rebuild is us porting our own app, not a customer migration. bonfireDB is early-stage; the "after" reflects bonfire's design and primitives, not a production migration we ran.

Before

The DynamoDB build: every screen earns its own table

TicVision's backend is well-built and conventional — and that's the point. To ship symptom tracking, the team had to design a data model around access patterns, hand-write CRUD, and stand up HIPAA plumbing themselves. None of it is wrong. All of it is work you do instead of the product.

Four bespoke tables, shaped by access pattern

  • UserProfile — single-key items keyed USER#<id>.
  • TicsuserId + ticId, plus a UserDateIndex GSI just to read by date.
  • WeeklyCheckinuserId + weekStart, with a boolean per weekday.
  • AuditLogeventId + three GSIs (by user, by resource type, by timestamp) and a 6-year TTL, all hand-built for HIPAA.

~15 Lambdas of glue

  • Tic create / bulk-create / delete / get; weekly-checkin upsert.
  • A common/audit_logging.py module every handler imports to write audit rows.
  • Doctor access control coded by hand: verify the request belongs to this doctor, check status is APPROVED, then fetch each table.
  • Analytics by full-table scan() over Tics and WeeklyCheckin, with a hardcoded list of test-user IDs to skip.
  • An offline GET /sync endpoint diffing changes since last_sync_time.

And the cost that never shows up in a demo: zero interoperability. The data lives in a proprietary single-team DynamoDB shape. A tic is a row with an intensity number — not an Observation with a coded concept. There is no Bundle to hand to an EHR, no standard a clinician's system can read. The shape is locked in by construction.

The hidden tax: a HIPAA audit log you built and operate

TicVision's audit story is an entire subsystem. A 200-line logging module, a dedicated table with three GSIs and a six-year TTL, and a try/except around every audit call in every handler so a logging failure can't take down a read.

  • Every handler imports the module and remembers to call it on every PHI touch.
  • A doctor read alone fires audit events for the request, the profile, the tics, the check-in, and the overall access — by hand.
  • Miss one call site and you have a compliance gap nobody sees until an auditor does.
get_patient_data.py · before
# hand-coded access control + manual audit, per handler
if request_item["doctorId"] != doctor_id:
    audit_logger.log_access_event(action="READ",
        user_id=doctor_id, resource_type="PATIENT_DATA",
        status="DENIED", ...)
    return resp(403, "not assigned to you")

if request_item["status"] != "APPROVED":
    return resp(403, "not approved")

# then: get_item profile, query tics GSI, query checkin,
# and a log_data_access(...) call after every single read
After

The bonfireDB build: the same app, mapped to primitives

On bonfire, you don't design tables — you write to app-native clinical primitives. The same data becomes standard FHIR R4 resources underneath, and the plumbing TicVision hand-built becomes built-in behavior.

TicVision on DynamoDB TicVision on bonfireDB
UserProfile table (patient) / Doctor tablePatient / Practitioner (generated underneath)
Tics table + UserDateIndex GSIclinical.observations.record(...) → an Observation (tic type code + severity value + effectiveDateTime)
WeeklyCheckin table (boolean per weekday)clinical.assessments.record(...)QuestionnaireResponse + scored Observation
Doctor portal: hand-coded request / APPROVED / owner checksClinical authorization — assigned patients, minimum-necessary, enforced for you
AuditLog table + 3 GSIs + audit_logging.py + per-call try/exceptDeleted. Automatic AuditEvent / provenance on every read and write
Analytics via full-table scan() + hardcoded test-user filterApp-native aggregate query over a committed projection
GET /sync diff + offline-first client logicThe reactive query + freshness layer — fresh on commit
Cognito UserPool + ClientKeep it — bring your own auth
The whole thing

The Lambdas, replaced by a handful of primitive calls

Record a tic. Record a check-in. Read the timeline (reactively, fresh on commit). Export FHIR. The access control, the audit trail, and the FHIR shadow are not in this file — they're the platform.

ticvision.ts · after
// A tic is an Observation: coded type + severity value + when
clinical.observations.record({
  patientId,
  code: "tic-severity",
  value: intensity,        // 1–10
  effective: occurredAt,
});

// The weekly check-in is a scored assessment
clinical.assessments.record("weekly-checkin", { patientId, answers });
// -> QuestionnaireResponse + a scored Observation, kept in sync

// The patient timeline — reactive, fresh the instant a tic commits
const timeline = useClinicalQuery(api.observations.listByPatient, { patientId });

// Interop on demand — a clean FHIR R4 Bundle
const bundle = await clinical.fhir.export(patientId);

No audit_logging.py. No UserDateIndex. No scan() to count tics. No last_sync_time bookkeeping. The doctor read-side becomes clinical authorization — assigned patients, minimum-necessary — instead of an if doctorId != sub by hand. Want the raw resource graph? It's an escape hatch, not your day job.

The payoff

And now it's FHIR-safe

The DynamoDB version could never do this. Because tics are Observations and check-ins are QuestionnaireResponses — coded concepts, not bespoke columns — TicVision can export to and interoperate with a real EHR. The data is no longer trapped in a single-team shape.

What the rebuild gets for free

  • Interop: a FHIR R4 Bundle on demand — shareable with an EHR a Dynamo row never could be.
  • Audit + provenance: automatic AuditEvent on every read and write — the hand-built AuditLog subsystem is gone.
  • Authorization: assigned-patient, minimum-necessary access enforced by the platform, not by per-handler ifs.
  • Freshness: the timeline updates on commit — no sync diff to maintain.

What you no longer write or operate

  • The AuditLog table, its three GSIs, the TTL, and audit_logging.py.
  • The UserDateIndex GSI and the read-by-date glue around it.
  • The analytics full-table scan() and its hardcoded exclusions.
  • The /sync diff endpoint and offline reconciliation bookkeeping.

Same auth-plus-CRUD simplicity. Far less code. FHIR, audit and interop for free. This is the kind of app you can build in a weekend — and the reason is that you stopped hand-rolling the clinical data layer.

Honest scope

This case study is an illustrative rebuild of our own app, grounded in TicVision's real backend, not a customer migration we ran in production. bonfireDB is early-stage — we're showing the design and the primitives, told straight.

  • Keep your Cognito pool — bonfire is the clinical backend, not your identity provider.
  • "FHIR-safe" means the data is modeled as FHIR R4 / core profiles you can export — not that we're a validated US-Core or full enterprise FHIR server.
  • TicVision and bonfireDB share the same team and lineage (Simplest Healthcare), so this is us porting our own app — the wins map directly to the real TicVision tables and handlers we read to write this page.
freshness · after
// Every write returns its freshness lifecycle
{
  status: "committed",
  views: {
    notesByPatient: "fresh",
    timeline: "fresh"
  },
  indexes: {
    semanticSearch: "pending",
    agentContext: "pending"
  }
}

You build the app. Bonfire is the clinical data layer underneath.

TicVision spent its weekends on tables, GSIs, and a HIPAA audit log. Build on app-native primitives instead — and get FHIR, audit and interop for free.

FAQ

Frequently asked questions

How do you migrate a custom DynamoDB schema to FHIR?

You map each access-pattern-shaped table to an app-native clinical primitive instead of redesigning tables. In this rebuild, a Tics table becomes Observations, a WeeklyCheckin table becomes a QuestionnaireResponse plus scored Observation, and bonfireDB generates the FHIR R4 resources underneath. This is an illustrative rebuild of our own app, not a production migration we ran.

What's the difference between FHIR and a Postgres or DynamoDB table for clinical data?

A DynamoDB row stores a tic as an intensity number in a proprietary, single-team shape no EHR can read. A FHIR Observation stores the same tic as a coded concept with a severity value and effectiveDateTime, so it can export to a Bundle and interoperate. bonfireDB lets you write app-native primitives while keeping standard FHIR R4 underneath.

Can I keep my existing Cognito auth when moving to bonfireDB?

Yes. bonfireDB is the clinical data layer above the FHIR server, not your identity provider, so you bring your own auth and keep your Cognito UserPool. The case study keeps Cognito and only replaces the bespoke tables, CRUD Lambdas, and hand-rolled audit log.

Do I have to build my own HIPAA audit log on bonfireDB?

No. The TicVision rebuild deletes its hand-built AuditLog table, three GSIs, six-year TTL, and audit_logging.py module. bonfireDB is designed to write AuditEvent and provenance automatically on every read and write, so you don't wire audit calls into each handler. bonfireDB is early access; this reflects the design, not a certified system.

Is bonfireDB a HealthLake alternative for custom clinical apps?

bonfireDB is the app backend that generates FHIR underneath (not a FHIR server) — the data layer where you write app-native primitives and get FHIR R4, audit, and authorization designed in. It targets developers rebuilding custom DynamoDB or vibe-coded clinical apps who want interop without designing tables. It is open-source and pre-launch, so evaluate it against your needs in early access.