This is a submission for the MLH x DEV Writing Challenge
What I Built
Haven is a post-mastectomy rehab coach that lives at home with the patient. After breast cancer surgery, most shoulder rehab happens alone, and between visits a physical therapist can't see how high the arm got, whether it hurt, or whether a limit was crossed. In one randomized trial, 85.7% of patients on an early, tailored exercise program regained full shoulder strength within six months, compared with 17.9% on usual care (Min et al., JAMA Surgery, 2024). Haven is built to bring that kind of guidance home.
A laptop camera and a wearable armband guide each exercise, keep the patient inside the limits their PT set, and give the PT a clear picture of recovery. I built Haven with Mohamed Abdelmaksoud, who led the ESP32 armband hardware and firmware, and Mario Sinclair, who led the in-browser computer vision and dashboard. I focused on the backend: a Node.js, Express, and TypeScript API deployed as a single Vercel function, the data layer on Tiger Cloud, the Gemini written reports, and Auth0 sign-in.
The biggest lesson was that health tech runs on trust. Patients need to trust their feedback, clinicians need to trust that their limits reach the patient, and everyone needs to trust the AI. So deterministic code raises every safety flag, and Gemini only writes the wording.
Demo
- Live site: havenn.health
- Devpost: devpost.com/software/haven-xuzh06
- Code: github.com/MarioSinclair/Haven
Partner Technologies
Tiger Data (TimescaleDB on Tiger Cloud)
Haven is time series data at heart. The armband streams arm angle at 20 Hz, and every rep and every minute of daily arm use has a timestamp. That is roughly 20 rows a second per patient, so the schema had to handle growth from day one.
Hypertables and the columnstore. Reps, daily use, and the raw band stream are all hypertables. The band stream is the bulk of the data, so it moves to the columnstore after a day, segmented by patient so each patient's history stays contiguous, which is exactly how the dashboard reads it.
select create_hypertable('band_samples', by_range('t', interval '1 day'));
alter table band_samples set (
timescaledb.enable_columnstore = true,
timescaledb.segmentby = 'patient_id',
timescaledb.orderby = 't desc'
);
call add_columnstore_policy('band_samples', after => interval '1 day');
reps and daily_use use seven day chunks, and daily_use moves to the columnstore after a week.
Continuous aggregates in real-time mode. The dashboard never scans raw reps. It reads rom_daily, a continuous aggregate that rolls every rep up by patient, exercise, arm, and the patient's local day:
create materialized view rom_daily
with (timescaledb.continuous, timescaledb.materialized_only = false) as
select
patient_id,
exercise_id,
side,
time_bucket(interval '1 day', performed_at, 'America/New_York') as day,
max(peak_deg) as best_peak_deg,
avg(peak_deg) as mean_peak_deg,
count(*) as reps,
sum(case when over_ceiling then 1 else 0 end) as reps_over_ceiling,
sum(case when shoulder_hike or trunk_lean or elbow_bend then 1 else 0 end) as reps_with_compensation,
avg(abs(peak_deg - imu_peak_deg)) as camera_band_gap_deg
from reps
where kind = 'exercise'
group by patient_id, exercise_id, side, time_bucket(interval '1 day', performed_at, 'America/New_York')
with no data;
select add_continuous_aggregate_policy('rom_daily',
start_offset => interval '90 days',
end_offset => interval '1 hour',
schedule_interval => interval '15 minutes');
Setting materialized_only = false is what makes it real-time: the aggregate merges in rows newer than the last refresh, so a session shows up on the dashboard the moment it is saved. A second aggregate, daily_use_daily, does the same for at-home arm use.
Using Tiger Data meant the PT's dashboard stays fast as data grows, without me building a separate analytics pipeline.
Also used
- Gemini writes the patient summary and the PT report from computed stats. It never sees video and never decides a flag. The server extracts every number from its text and checks it against the source data, sends the draft back once if anything is unsupported, and shows the PT anything still unverified. Gemini text to speech also recorded our voice coach clips.
- Auth0 handles sign-in. Clinicians come from an allowlist, patients are linked to their care team by verified email, and the server takes limits from the PT's plan rather than from the request, so an edited request can't hide a limit break.
Hackathon Experience
We built Haven at SwampHacks XII, hosted by the University of Florida, with 483 hackers and 141 projects submitted. It was a weekend of hardware and software coming together: soldering and 3D printing on one side, API and database work on the other. What I'll remember most is the moment the camera, the armband, and the dashboard all worked together right before demo time, and then hearing Tiger Data had picked Haven for the Best Use of Tiger Data prize.












