TicVision: an illustrative rebuild on FHIR R4
illustrative rebuild — our own dogfood, not a production migration
A Tourette’s symptom-tracking app we built ourselves the normal way: custom DynamoDB tables, ~15 CRUD Lambdas, a hand-rolled HIPAA audit log, and data that could never talk to a real EHR. This page maps that real backend onto bonfireDB’s target contract: less code, with FHIR, audit and interop built in. It is a design exercise, not a production migration.
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.
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.
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.
The core custom tables, shaped by access pattern
- UserProfile — single-key items keyed
USER#<id>. - Tics —
userId+ticId, plus aUserDateIndexGSI just to read by date. - WeeklyCheckin —
userId+weekStart, with a boolean per weekday. - AuditLog —
eventId+ 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.pymodule 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 /syncendpoint diffing changes sincelast_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.
# 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
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 table | Patient / Practitioner (canonical FHIR R4) |
| Tics table + UserDateIndex GSI | clinical.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 checks | Clinical authorization — assigned patients, minimum-necessary, enforced for you |
AuditLog table + 3 GSIs + audit_logging.py + per-call try/except | Deleted. Automatic AuditEvent / provenance on every read and write |
Analytics via full-table scan() + hardcoded test-user filter | App-native aggregate query over a committed projection |
GET /sync diff + offline-first client logic | The reactive query + freshness layer — fresh on commit |
| Cognito UserPool + Client | Keep it — bring your own auth |
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.
// 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.
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 custom 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
AuditEventon 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
UserDateIndexGSI and the read-by-date glue around it. - The analytics full-table
scan()and its hardcoded exclusions. - The
/syncdiff endpoint and offline reconciliation bookkeeping.
The design removes app-specific tables, indexes, and audit glue by moving those responsibilities into the platform contract. The code reduction is illustrative until the migration is built and measured.
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.
// 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 time on tables, GSIs, and audit plumbing. Bonfire is being designed to move those responsibilities into a reusable clinical data layer.
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 keeps the canonical 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, not your identity provider, so you bring your own auth and keep your Cognito UserPool. The case study keeps Cognito and only replaces the custom 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 above your canonical FHIR R4 (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 AI-built clinical apps who want interop without designing tables. The core is planned as open source and the product is pre-launch, so evaluate it against your needs in early access.