A reference architecture for unifying fragmented caregiving data — so the warning that no single device can raise becomes the one that does.
A family caregiver looking after an aging parent juggles a fall pendant, a smart pillbox, motion sensors, a blood-pressure cuff, a wearable, and a care-plan portal. Each product works. None of them talk to each other. The result is that the most important signals — the ones that live in the relationship between sources — are invisible, because no single app can see across the others.
CareUnify is a working prototype that demonstrates the fix. It is not another app. It is a data model: push every vendor to the edge, resolve everything into one canonical stream tied to one person, and let dashboards and alerts read that single stream instead of a dozen disconnected feeds.
A note on the data. Every value in this repo is synthetic. The formats are real — the Apple Health ingestion parses Apple's actual
export.xmlstructure, quirks and all. The point being demonstrated is the architecture and the parsing, not the numbers.
The danger isn't in any one data source. It's in the combination. CareUnify makes that combination computable:
Vendor adapters → Code map → Observation stream → Unified view & alerts
(the edge) (translate) (canonical core) (consume)
- Vendor adapters — each device speaks its own format at the boundary (Apple's verbose XML, a pillbox webhook, a sensor event). An adapter's only job is to normalize.
- Code map — vendor-specific codes resolve to a shared vocabulary of observation types. This is where
HKQuantityTypeIdentifierHeartRatebecomes a canonicalheart_rate. - Observation stream — one table. Every reading, every event, every vendor, tied to one care recipient and vendor-blind from here on.
- Unified view & alerts — the dashboard and the rule engine read the canonical stream and never touch an individual vendor.
The payoff: onboarding a new IoT product is a configuration change — one source-system row and a handful of code-map rows — not a rebuild. Everything downstream keeps working untouched.
| File | What it is |
|---|---|
careunify_schema.sql |
The canonical data model (PostgreSQL). 14 tables + a unified-feed view. The architectural heart of the project. |
careunify_pipeline.py |
The ingest transform + cross-source alert engine. Runs the full arc against a live in-memory database. |
careunify_apple_adapter.py |
A real-format parser for Apple Health export.xml — handles Apple's malformed device attributes, distinguishes structurally-identical record types, and skips unmapped data cleanly. |
careunify_gen_apple_export.py |
Generates a format-faithful synthetic export.xml for the demo. |
careunify_apple_pipeline_demo.py |
Ties the Apple adapter into the pipeline and runs vitals + pillbox + motion end to end. |
apple_export_sample.xml |
A sample synthetic Apple export to run against. |
CareUnify.jsx |
The family-caregiver dashboard — a single pane of glass over all sources. |
Requires Python 3.11+ and two packages:
pip install duckdbThe base pipeline — three vendors with three completely different payloads, unified into one stream, then alerts:
python3 careunify_pipeline.pyThe Apple Health demo — generates a synthetic export, parses Apple's real format, and runs it through to a cross-source alert:
python3 careunify_gen_apple_export.py # writes apple_export.xml
python3 careunify_apple_pipeline_demo.pyYou'll see the ingest normalize the data, the canonical stream with heart rate kept separate from resting heart rate, and two alerts fire — one threshold warning on the watch data, and one urgent cross-source alert combining motion and medication that neither vendor's app could ever raise alone.
The schema is written for PostgreSQL; the demos load it into DuckDB in-memory so there's zero setup. The transform and engine logic is portable as-is.
A few choices that separate this from a toy demo:
- Apple's export isn't well-formed XML. The
deviceattribute embeds raw angle brackets inside a quoted value, which crashes a strict parser. The adapter sanitizes them before parsing — a real-world quirk, not a synthetic one. HeartRateandRestingHeartRateare structurally identical records, distinguished only by thetypeattribute. The adapter maps them to separate canonical types so resting heart rate never pollutes the live-heart-rate stream.- Alert fatigue is a design problem. A sustained heart-rate spike collapses into a single alert citing the peak and duration, not ten near-identical notifications — the difference between something a caregiver acts on and something they mute.
- Raw payloads are preserved. Nothing is lost in translation; the original vendor payload lands in
raw_ingestbefore normalization, so the canonical stream is always reconstructable.
The most important signals rarely sit in the obvious number. In an earlier analysis of CMS nursing-home data, staff turnover — not raw staffing hours — was what predicted fall rates. Here, risk lives in the combination of low activity and a missed dose, not in either alone.
Same lesson, every time: connect the sources nobody is connecting, and the answer is usually already in the data.
Ben Biddick · North Bridge Solutions · Madison, WI Data analytics for aging services — transforming fragmented data into leadership success.