- **Change (breaking): telemetry hypertable rewritten to long-format (drops dedicated `heart_rate`/`steps`/`calories` columns + JSONB catch-all).** The `telemetry_data` TimescaleDB hypertable is rebuilt in the canonical long-format shape — one row per `(timestamp, device, slug)` with explicit `value Float` + `unit Text` + `patient_id UUID` columns. Replaces the asymmetric wide+JSONB hybrid (dedicated Float columns for 3 "first-class" metrics + a JSONB `data` catch-all that gave every other telemetry biomarker — SpO2, glucose, sleep stages — second-class storage). **Modularity win:** adding a new telemetry biomarker is now a row-only change — flip `BiomarkerDefinition.is_telemetry = True` and the integration sync starts emitting rows with no DDL and no service-layer branching (previously required editing ~5 files: the `_METRIC_COLUMNS` alias map, `apply_telemetry_split`'s slug→column branch, `analytics_service`'s dual-path SQL, and writing new CAgg DDL). The new `tests/test_telemetry_new_biomarker_regression.py` verifies `spo2` (a brand-new biomarker with no historical dedicated column) flows end-to-end through upload → read → summary → trends with **zero code changes**. **Continuous aggregates are generic:** the two hardcoded CAggs (`AVG(heart_rate), AVG(steps), AVG(calories)`) are replaced by three generic `GROUP BY slug` CAggs (hourly/daily/monthly — the monthly CAgg covers the `last-12-months`/`all-time` analytics buckets that previously hit the raw hypertable). One definition covers every current and future telemetry biomarker. **Patient attribution simplified:** `patient_id` is now persisted on every telemetry row at insert time, killing the fragile `device_id → UserIntegration → user_id → Patient` resolver chain in `migrate_biomarker_data` (~100 lines deleted); telemetry→FHIR migration now reads `tr.patient_id` directly. **Upload contract (breaking):** `TelemetryDataPoint` is now `{timestamp, slug, value, unit?, patient_id?}` — one point per metric/timestamp (was: one point with `heart_rate`/`steps`/`calories`/`data` fields). The integration SDK already produces one Observation per metric, so the mapping is 1:1. `upload_telemetry_data` now uses SQLAlchemy Core bulk insert with `ON CONFLICT DO NOTHING` chunked at 5000 rows. **Hot-path indexes:** `(tenant_id, slug, timestamp)` for analytics + `(tenant_id, device_id, timestamp)` for per-device reads; compression segmented by `tenant_id, device_id, slug`. **Files:** new migration `t1e2l3o4n5g6_telemetry_long_format` + baseline `8ddb7ef7ca4d` updated so fresh installs match; `models/telemetry_model.py`, `schemas/telemetry.py`, `services/telemetry_service.py`, `services/integration_sync_service.py`, `services/analytics_service.py`, `services/import_service.py`, `workers/tasks.py`, `api/v1/endpoints/telemetry.py` rewritten; `scripts/migrate_heart_rate.py` + `scripts/delete_migrated_heart_rate.py` deleted (superseded); 4 test files rewritten + 1 new regression test + 3 collateral test fixes. 2715 backend tests pass; ruff clean. See `dev/plans/telemetry-long-format-2026-07-28.md` for the full plan.
0 commit comments