A Schema Contract Between Two Care Apps: care-event-v1

How GutPacer feeds ParkinSync through one versioned JSON Schema contract instead of a shared database or a bespoke API, verified by a round-trip test in public CI.

Synthetic data only. A data-layer integration proof of concept, not a production pipeline.

Two of the care tools in this ecosystem produce data about the same day for the same household. GutPacer records bowel events; ParkinSync keeps a 25-column daily record that a clinician or caregiver reads. The obvious way to connect them is the wrong one: share a database table, or write a GutPacer-shaped endpoint into ParkinSync. Both couple the two apps to each other’s internals forever.

The version that survives refactors is a contract. GutPacer and ParkinSync agree on one thing — care-event-v1, a versioned JSON Schema — and nothing else. That schema is the only join point.

Producer, consumer, and one shared file

The design is deliberately boring:

  • GutPacer is the producer. It exports a day’s events as care-event-v1 JSON.
  • care-event-v1.schema.json is the contract. It defines the shape; JSON Schema validation is the gate.
  • ParkinSync is the consumer. It reads the JSON and merges each event into the correct daily row.

Neither app imports the other. GutPacer doesn’t know ParkinSync’s 25 columns exist; ParkinSync doesn’t know how GutPacer stores anything. If GutPacer’s storage changes tomorrow, the only thing that has to stay stable is the export. It’s the same principle as integrating the data rather than the screens — here the data itself has a written, versioned interface.

The join key is boring on purpose

Events are joined on householdId + date (JST). The person is carried as a subjectRef — a reference, not a name — so the contract never has to move identifying data to do its job. ParkinSync takes each event and writes it to that date’s row: bowel movement, stool form (Bristol), stool volume, and Movicol dose land in their columns; a day with no event stays blank rather than being back-filled with a zero.

That last detail matters more than it looks. Missing is not the same as none. A caregiver who didn’t record anything is not a caregiver who recorded “nothing happened,” and a pipeline that quietly converts one into the other is producing fiction. The import preserves missingness instead of inventing it.

Proving it: a round-trip test in CI

A contract you don’t test is a contract you’ll break. So the proof is a round-trip, run on synthetic data in public CI as a job named care-event-integration-poc:

  1. Feed in synthetic events (a few days, a few events each).
  2. Export them as care-event-v1 and validate against the schema.
  3. Import into ParkinSync’s daily rows.
  4. Assert: event count in equals event count out, values match, and blank days stay blank.

If any of those fail, the build is red. The contract can’t “mostly hold.” Producer-side and consumer-side both validate against the same schema file, so a drift on either end is caught before merge, not in front of a user.

What this proof is — and isn’t

Keeping the claim honest is part of the design:

  • Synthetic only. No production data, no production AWS touched. The point is the data-layer contract, not a live integration.
  • Data layer, not screens. No UI merge, no realtime sync, no multi-facility fan-out. Those are later stages behind their own gates.
  • Not an analytics claim. A clean round trip proves the record moved correctly; it says nothing about what the numbers mean clinically.

Why a contract beats an API here

For two small apps owned by one person, a bespoke API between them would be more code, more coupling, and more to keep in sync — for no benefit. A versioned schema gives the same integration with three properties I actually want:

  • Decoupling. Each app can change internally as long as it honors the contract.
  • Testability. The schema is a fixture; conformance is a CI check, not a hope.
  • Extensibility. A third producer (Medication Promise, a facility feed) joins by emitting care-event-v1, not by learning ParkinSync’s schema.

It’s a proof of concept on synthetic data. But the shape is the one I’d keep at scale: one written contract, producers and consumers that never import each other, and a red build the moment the record stops surviving the trip.

ParkinSync and GutPacer are part of the care toolkit at veai.jp/apps.

Thanks for reading — built with care, for caregivers.