Resources & Tech · RPM Solution

A device that retries should never see an error.

Remote monitoring is a distributed-systems problem wearing clinical clothes. Devices transmit over unreliable links, retry blindly, and buffer readings until they can send. The interesting engineering is not the dashboard — it is idempotency, timezone-correct day counting, a freeze window that makes last month's number stop moving, and an alert path that never pages someone about a reading a rollback removed.

Overview

The engineering record behind device ingest, alert rules, monthly compliance and the readings a clinician sees in the chart.

Technology stack by layer
LayerWhat runs thereWhy
Program front endNext.js 14 App Router, React 18, TypeScript, TanStack Query, Zustand, Tailwind, RechartsOne application container serves both the monitoring and chronic-care programs, with the program mode chosen from the request host rather than a build flag.
Session isolationA host-only session cookie with no domain attributeA login on one program hostname is a genuinely separate session from the other, and from the network portal. That is a deliberate isolation property, not an oversight.
Device ingest APIA dedicated FastAPI route with pre-shared per-device keys, bcrypt-hashedDevices do not carry user sessions. This is a separate trust boundary from both clinician and patient authentication, and the only route that deliberately skips the standard entity dependency.
Patient app APIA separate mobile API surface with its own portal token typePatients authenticate as patients, not as staff. Entity and patient identity resolve from the token's user row, never from request input.
BackendFastAPI, async throughout, SQLAlchemy 2.0 async sessionsOne batch of readings shares a single outer transaction — alert evaluation and audit writes included — so a failure rolls the whole batch back together.
DatabaseMySQL 8 with purpose-built covering and composite indexesA latest-reading-per-patient covering index took that lookup from seconds to milliseconds on a readings table of a couple of million rows, as recorded in the model comment that introduced it.
Background jobsAPScheduler — nightly monthly recompute, daily compliance scan, daily notification pruneThe compliance scan runs after the monthly cycle so it reads settled data, and the prune deletes in bounded batches so it cannot hold long locks.
Rate limitingIn-process token buckets — per device, per patient for the panic button, and shared buckets for patient loginThe device limit is checked before any cryptographic work, so a flood costs a counter increment rather than a hash — the cheap check first, deliberately.
CacheRedis, sixty-second TTL keyed by entity and programThe program dashboard only. Nothing on the clinical path is cached.
ExportsServer-streamed CSV, plus a browser-native print routeNo PDF library is bundled. The print view is a real route with its own print stylesheet, so what the clinician sees on screen is what prints.
Architecture

How the engine actually works

Each section describes a mechanism that exists in the codebase today, not a pattern we admire.

Ingest

Four defences before a reading is stored

The device route checks its per-device rate limit before doing any cryptographic work — the cheap check first, so a flood cannot force expensive hashing. Authentication then resolves the device row and verifies the key with bcrypt in a thread pool, because bcrypt is CPU-bound and would otherwise stall the event loop for everyone.

Idempotency

A duplicate is a success, not a conflict

Deduplication is enforced twice: a pre-flight check on device and measurement time, and a unique constraint at the database as the concurrency-safe backstop. A race that slips past the check raises an integrity error, which the service catches, rolls back, re-fetches the existing row, and returns as success with a deduplicated flag.

Compliance

Counting days across midnight, correctly

The monthly summary counts distinct local calendar days on which at least one qualifying device reading was recorded. Getting that right across a timezone boundary is fiddly, and the implementation does the fiddly thing: it widens the database query window by a day on either side of the month boundary, converts each timestamp into the program's local calendar date, then filters back to the target month.

Freezing

Making last month stop moving

Every night the current month is recomputed for every active and paused enrollment. While the program-local day of the month is four or lower, the prior month is recomputed too — because devices buffer, and a store-and-forward reading can legitimately arrive days late.

Devices

Provision once, show the key once

Staff provision a device against a patient. A key is generated, hashed with bcrypt for storage, and returned to the caller exactly once — afterwards only an eight-character prefix is retained for identification, and no read endpoint ever returns the hash. If the key is lost, a new device credential is issued; it cannot be recovered.

Alerts

Two layers, one rule that always wins whole

Two alerting mechanisms run side by side. The older layer is a simple per-patient, per-metric bound producing its own alert stream. The newer rules engine is richer: threshold breaches per metric, weight change as a percentage from the previous reading, missed measurement over a window of days, and a run of consecutive days out of range.

Delivery

In-app by default, out-of-band only for emergencies

Routine alerts write a single in-app notification to the enrollment's assigned care manager and stop there. The code documents why it deviates from the platform's general-purpose helper: that helper takes a synchronous session and commits itself, which is incompatible with running inside the same transaction as the triggering reading — and doing email or SMS there would hold a database transaction open across a third-party HTTP round trip.

Reporting

One column list for the export and the print view

The monthly compliance report defines its CSV columns and its printable table columns from a single shared array. The module docstring is explicit that this exists so the two cannot drift out of column order with each other — a class of bug that is invisible in review and obvious to whoever reconciles the two outputs by hand.

Data model

The records underneath, and the rules that hold them true

Core entities and their invariants
EntityWhat it holdsRule that always holds
rpm_devicesOne provisioned device per patient — type, status, hashed key, key prefix, and an optional measurement schedule.Device identifier is unique per entity. There is no delete; retirement is a status transition, and the hash is never returned by any read endpoint.
patient_vitals_readingsOne measurement, manual or device-sourced, with typed value columns and an optional link to an encounter.Unique on device and measurement time — the ingest idempotency guarantee. No delete; corrections are status transitions.
patient_vitals_thresholdsThe simpler per-patient, per-metric alert bound layer.No global defaults are seeded — this layer is per-patient override only.
ccm_alert_rulesThe richer rules layer: per-metric bounds, weight-change percentage, consecutive-day and missed-measurement windows, and per-sensor enable flags.A patient row replaces the entity default wholesale. Fields are never merged across the two.
ccm_alertsA fired alert with type, severity, metric and lifecycle status.Deduplicated against any open alert of the same patient, type and metric. A composite index exists specifically for the worklist's alert-count queries.
emergency_alertsThe critical escalation stream — panic button, critical vital, or manually filed.Dispatched only after the triggering transaction commits, never inside it.
rpm_monthly_summariesThe materialized monthly snapshot: distinct reading days, per-sensor counts, clinical minutes, tier, alert counts and a freeze flag.Unique per patient, program and month. A frozen row is never recomputed.
care_time_logsAppend-only clinical time tied to a specific enrollment.Summed against the enrollment rather than the patient, so re-enrollment cannot double-count.
notificationsThe shared in-app inbox row, also the basis for cross-channel deduplication.A 24-hour dedup window per user, alert type and source, backed by a dedicated composite index after a full-table text scan pinned the database on a large table.
Interfaces

What it exchanges, and in which direction

Optional integrations degrade gracefully: with no key configured the product still runs, it just does less.

Device ingest

Inbound

Vendor-neutral by construction: one authenticated endpoint and a pre-shared key per device, so anything that can post to a URL can feed the programme and nothing above the ingest boundary knows which make of device a reading came from. Accepts one to fifty readings per request in a single transaction.

Patient mobile API

Bidirectional

Vitals read and write, single and batch; device list; alert list and acknowledge, with resolution kept staff-only; medications, appointments, care-plan tasks, emergency contacts, and a panic button.

EMR chart

Outbound

The chart's monitoring tab and encounter card read the exact same reading rows the program application and the ingest API write. Read-only by design — no provisioning, no threshold tuning, no acknowledgement there.

Care-program dashboard

Outbound

Enrollment, eligibility, adherence, alert and workload blocks per entity and program. No revenue fields, by locked decision.

In-app notifications

Outbound

Alerts land in the same shared inbox as every other notification type, participating in the same deduplication window and retention prune.

Email and SMS

Outbound

Reserved for emergencies. Routine alerts never fan out to a third-party channel from inside a database transaction.

Spreadsheet import

Inbound

Bulk historical readings run through the same creation path as manual entry, so they evaluate thresholds and raise alerts identically rather than bypassing the pipeline.

Security

How access is decided and recorded

Device credentials
Generated with a cryptographic token, bcrypt-hashed at rest, shown once. Only a short prefix is retained. Verification runs off the event loop.
Credential revocation
A suspended or retired device is rejected even with an otherwise-valid key — the documented mechanism for handling a stolen credential.
Identity from the credential, never the payload
Entity and patient are resolved from the authenticated device or portal user row. A request body cannot assert whose reading it is.
Three separate auth schemes
Staff sessions, patient portal tokens and device keys are distinct trust boundaries. None can be used in place of another.
Constant-time patient login
A login for an address that does not exist still performs a real password comparison, so response timing does not reveal which accounts are real. Rate-limited per address and per address block.
Panic button limiting
Three presses per five minutes per patient — sized to prevent a stuck button from flooding while never throttling a genuine emergency into uselessness.
Cross-entity denial
Staff endpoints return 404 rather than 403 on a cross-entity lookup, so an identifier probe learns nothing.
PHI in logs
The ingest route is registered as PHI-redacted, so request bodies including the free-text notes field are never logged verbatim.

Infrastructure is designed for HIPAA obligations under a BAA. We document the controls and hand over the runbooks; we do not claim a certification that does not exist for software.

Reliability

What keeps it correct under load

Two-layer idempotency
A pre-flight check plus a database unique constraint, with the lost race handled by rollback and re-fetch. Correct under concurrency, not merely usually correct.
Cheap check before expensive work
The rate limit is evaluated before any bcrypt verification, so a flood cannot force the server into expensive hashing.
Bcrypt off the event loop
Key verification runs in a thread pool — a CPU-bound operation on an async server is a latency incident waiting for its first busy hour.
Widened query window
Reading-day counting queries a day either side of the month boundary before filtering, so a reading near local midnight is neither miscounted nor dropped.
Freeze after the grace window
Prior-month summaries stop changing on a fixed day, which is what makes an exported report trustworthy afterwards.
Queue then drain
Emergency dispatch is parked on the session and drained after commit, and swallows its own exceptions — a written-but-undelivered alert is bad, an alert that errors the caller and is never written is worse.
Grouped scans, never per-patient loops
The nightly compliance scan issues one grouped query per rule and sensor across the whole target set.
Bounded batch deletes
Notification retention deletes in batches with a commit between each and a cap per run, so a single invocation cannot hold long locks.
Indexes with a recorded reason
Each performance index in this domain carries a comment naming the query it serves and, in one case, the measured before-and-after that justified it.
Development record

The documents this was built from

Analysis, design discussion, implementation notes and QA written while the work happened. These are engineering artifacts in the product repository, not published pages — listed here so you can see what exists and ask for any of it.

Development-time documentation for RPM Solution
DocumentKindDateWhat it covers
Care-program service plan.planning/milestones/RPM-SERVICE-PLAN.mdPlan2026-08-24The full services build plan — hostname topology on one shared container, host-only cookie auth, build phases, screen map, and an owner-facing table of open decisions covering sensor scope, timezone, freeze window, tier model, device vendor and migration.
Post-implementation open items audit.planning/milestones/RPM-SERVICE-OPEN-ITEMS.mdAnalysis2026-08-24, updated 2026-08-25An audit of the plan against what actually shipped across two deployments — thirteen defects and seven plan-commitment gaps, each with fix status, plus a verified-sound list and the owner actions still outstanding.
Service analysis and implementation blueprintdocs/rpm-service-implementation.htmlAnalysis2026-08-19The diagrammed analysis that fed the service plan.
CCM/RPM module specification.planning/milestones/CCM-RPM-MODULE-SPEC.mdDesign2026-06-10The original module design — dashboard, patient management, monthly cycle, alerts and emergency alerts.
Vitals and monitoring foundation phase.planning/phases/11-vitals-rpm/Implementation2026-04-13The foundational build — devices, readings, thresholds — with six plan and summary pairs, a discussion log, UI spec, validation, verification and a deferred-items list. Its research document is what the ingest service cites for keeping bcrypt off the event loop.
Chart-side monitoring phase.planning/phases/P12-rpm/{SPEC,PLAN,USER_GUIDE}.mdDesign2026-05-29The read-only chart tab and encounter card, with a user guide that explicitly scopes out device administration, alert configuration and anything billing-adjacent.
Enrollment and time-tracking phases.planning/phases/99-ccm-rpm-enrollment-core/ · 100-ccm-rpm-time-tracking/Implementation2026-06-10The enrollment lifecycle and clinical time-logging builds, each with its own research, patterns, security, review and verification artifacts.
Care-program QA design.planning/qa/2026-08-25-ccm-rpm-qa-design.mdQA2026-08-25Why a manual QA pass was chosen for this deployment, and a frank account of the automated-test tier gap that let a database-specific query defect ship undetected.
Mobile API migration backlogdocs/eklotho-nexus_2026-08-24_mobile-api-migration-backlog.mdAnalysis2026-08-26An endpoint-by-endpoint reconciliation of what two external native client applications call against their legacy backend versus what the current mobile API exposes, with the gap quantified rather than estimated.
Before-and-after fix recorddocs/eklotho-nexus_2026-08-24_emr-rpm-fixes_before-after.htmlReport2026-08-25A batch of chart and monitoring fixes documented in before-and-after form.
Module documentation setdocs/docs-module-{vitals,alerts,patient,emr-rpm,emr-ccm}.htmlReference2026-04-27 to 2026-06-04Written documentation for vitals, alerts, patient management and the two chart-embedded program surfaces.
Early organisation notedocs/superpowers/plans/2026-04-21-ccm-rpm-organization.mdDiscussion2026-04-21The earliest note on how to structure the care-program build, written before the phase work began.
Glossary

Terms used on this page

Remote patient monitoring
A programme where a patient's home device transmits vital readings that a care team reviews for compliance and clinical alerts.
Reading day
A local calendar day on which at least one qualifying device reading was recorded — the unit the monthly threshold counts.
Distinct reading days
The count of unique reading days in a calendar month for one patient.
Grace window
The first days of a new month during which the prior month can still be recomputed for late store-and-forward readings.
Frozen summary
A monthly summary past its grace window, never recomputed again — which is what makes an exported report stable.
Store and forward
A device buffering readings locally and transmitting them later, which is why late arrivals are normal rather than suspicious.
Idempotency
The property that a repeated submission has the same effect as one — here, a duplicate returns success with a flag rather than an error.
Entity-default rule
An alert rule with no patient set, applying to every enrolled patient at that entity unless overridden.
Wholesale resolution
Using a patient's override rule in full rather than merging its fields onto the entity default.
Fire immediately
A rule flag making it evaluate synchronously against each new reading rather than only in the nightly scan.
Emergency band
A fixed value range that upgrades an alert from warning to critical and opens an emergency record.
Device staleness
A computed flag based on how long since a device last transmitted — a warning after a week, critical after two.
Panic button
The patient application endpoint that raises an emergency alert directly, rate-limited but never throttled into uselessness.
Host-only cookie
A session cookie with no domain attribute, so each hostname is a separate session.
Time tier
An inclusive bucketing of a patient's monthly clinical minutes, shown for compliance rather than payment.
Token bucket
A rate-limiting scheme allowing bursts up to a capacity that refills over a window.
Covering index
An index containing every column a query needs, so the query is answered without touching the table.
Questions

Asked by the people who evaluate this

Why return success for a duplicate reading instead of a conflict?

Because the client is a device on a bad connection, not a browser with a user watching. Firmware that receives an error retries harder or drops the reading; firmware that receives success stops. The correct outcome — stored exactly once — should also be the outcome the device understands, and a flag in the response tells the server-side reader what happened.

Why does last month's compliance number stop changing?

Because a number someone has acted on should not move. Devices buffer, so late readings are legitimate for a few days, and the prior month is recomputed during that window. After it closes, the row is frozen and the computation refuses to touch it. Otherwise an exported report and the screen it came from disagree, and nobody can tell which is right.

How do our devices connect?

Through one authenticated ingest endpoint. Each device is provisioned with its own key and posts readings directly, which means a device or gateway that can be configured to send to a URL works without any code from us. Where a vendor needs a dedicated adapter, that is a bounded piece of work rather than a rewrite, because nothing above the ingest boundary knows or cares which vendor a reading came from. Tell us which devices your programme uses and we will tell you which of the two it is.

Why are routine alerts in-app only?

Two reasons, both in the code comments. Sending email or SMS from inside the transaction that stored the reading would hold a database transaction open across a third-party HTTP round trip. And a care manager who gets a text for every threshold crossing stops reading texts. Emergencies do fan out to every channel and deliberately ignore notification preferences — a digest of panic-button presses helps nobody.

How do you know an alert is never sent for a reading that gets rolled back?

Because dispatch is structurally separated from the write. Emergency alerts are queued on the session and drained only after the commit returns. The alert cannot physically be sent before the reading is durable, which is a stronger guarantee than ordering the calls carefully and hoping.