RayHealth EVV is a production Electronic Visit Verification platform for home-care agencies. Caregivers clock in and out from a mobile app with GPS verification, coordinators run scheduling and compliance from a web console, and verified visits flow out to state Medicaid aggregators and billing. It is live at rayhealthevv.com, built for the 21st Century Cures Act rules that govern every Medicaid-funded home-care visit.
The platform is a Turborepo monorepo with four workspaces: a core package that owns the domain models and the database, an Express 5 API, the React admin console that agencies know as Ray Admin, and an Expo caregiver app. Around 40 PostgreSQL tables, roughly 190 API route handlers, and 1,081 tests across 141 test files hold it together, all behind required CI gates.
The design goal was simple to say and hard to do: make Medicaid compliance a side effect of a normal workday. Schedule a visit, clock in, clock out, and the audit trail, state submission, and claim build themselves.
Federal law requires agencies to verify who delivered care, to whom, where, when, and under which authorization, for every single visit. Miss a data point and the claim gets denied. The existing tools are fragmented across web, mobile, and state-reporting portals, and caregivers are busy caring for people, not babysitting forms.
Home care also has a fraud problem, which is why the data model treats visits as evidence: once a clock-in exists, nobody gets to quietly rewrite history. Not even the people who run the database.
The core package is the only workspace allowed to touch PostgreSQL. It holds Zod domain entities, about 30 Knex repositories, the migration runner, and the state aggregator integrations. The Express API composes those into routes, the React console and the Expo app are pure API clients. A scheduling or billing rule lives in exactly one place and behaves the same everywhere.
The database does its own enforcement. The audit log has a trigger that refuses UPDATE and DELETE outright, visit rows lock their clock-in facts after creation, and event types are guarded by CHECK constraints kept in lockstep with the domain code. The comment on the audit trigger says it best: nobody, including future us with a SQL console open, gets to edit evidence.
Express 5 with Knex instead of a heavy ORM, because the compliance guarantees live in hand-written PL/pgSQL triggers and raw SQL that an ORM cannot express. A CI check literally fails the build if string-built SQL sneaks in, so everything stays parameterized. PostgreSQL was non-negotiable: append-only triggers, timestamptz hardening, and CHECK-constraint enums are the backbone.
Expo and React Native cover the caregiver app on both iOS and Android from one codebase, with tokens in the platform keychain via SecureStore. The web console is React with Vite because coordinators live in it all day and it needs to feel instant. AI runs exclusively through Claude on AWS Bedrock: the app refuses to boot in production if a non-BAA AI key is present, because PHI only travels to vendors with a signed business associate agreement.
The mobile app authenticates with short-lived JWTs pinned to HS256, and every token carries a server-side session id that can be revoked. The web console uses HttpOnly session cookies with CSRF tokens compared in constant time. Both funnel into the same capability-based access control: 18 capabilities mapped across four roles, checked per route, so a coordinator can read billing but only an admin can write claims.
Offline is a first-class citizen. The caregiver app keeps a store-and-forward queue in AsyncStorage: a clock-in with no signal gets a local id, and when the network returns the queue replays in order and remaps the local id to the server one. Punches are treated as evidence, so only a definitive rejection ever drops one, and the server accepts offline timestamps up to 72 hours old before insisting on a formal visit correction.
A recurring schedule materializes into assignments, checked for conflicts and caregiver eligibility. At the door, the caregiver clocks in: the API resolves the assignment, pulls the client's geofence anchor, and runs a Haversine distance check against a 150 meter radius. Outside the fence gets a 422 with the exact distance, plus an audit event. Inside, the visit row snapshots every Cures Act data point at that moment.
Clock-out re-runs the geofence, detects exceptions, and marks the visit verified or flagged. From there the Sandata integration takes over: clients, employees, and visits are submitted as batches over REST, polled for per-record accept or reject, with monotonic sequence numbers on resubmission because Sandata demands them. Verified visits then feed claim generation, which applies the CMS 8-minute rule, scores denial risk, and emits a structurally valid 837P EDI file that goes to the clearinghouse over SFTP. Remittances come back as 835 files and post against the claims. That whole sentence used to be somebody's entire week of manual work.
The 837P generator does not lean on a library. X12 is a rigid, positional text format from a decades-old EDI spec (ASC X12N 005010X222A1), so the code builds the interchange segment by segment: the ISA and GS envelope, the billing provider, then for each claim the subscriber, diagnosis codes, and every service line. It refuses to emit a file at all if required billing fields are missing, so a broken claim never leaves the building. The 835 side is the mirror image, a parser that reads whatever a payer sends back, auto-detecting the delimiter characters from the ISA envelope because trading partners do not all agree on one.
Matching a remittance to the original claim turns out to be simpler than the acronyms suggest. Every claim carries a control number when it goes out, and the payer echoes that same number back on the remittance, so posting a payment is just a lookup on that id. Submission itself runs through a pluggable transport, SFTP for real clearinghouses, an HTTP option for the ones that support it, and a sandbox simulator so the whole claim loop is demoable with zero live credentials. Incoming remittance files are deduped by content hash before they are posted, so a scheduled pull can never process the same file twice.
PHI columns like Medicaid numbers are encrypted cell by cell with AES-256-GCM before they hit the database, with a random IV per write. Rate limits are tiered per surface, login attempts are capped, TOTP two-factor is available, and super-admin access uses WebAuthn passkeys. The audit middleware records every PHI read, write, and export with a server-generated correlation id, and the retention sweep archives seven years of history, which is longer than HIPAA's own floor.
The AI copilot gets the same treatment. Every prompt is logged as a hash, never as text, because prompts can contain PHI. And when the copilot proposes an action, like enrolling a caregiver in training, it returns a typed, schema-validated proposal that a human must confirm before anything executes. The AI suggests. People decide.
GPS is noisy and people are creative. The geofence check deliberately ignores the phone's self-reported accuracy value, because trusting it would let someone borrow 50 meters by spoofing low confidence. On the flip side, the check fails open when a client has no geocoded address yet, because a new client's first visit should not be blocked by missing setup, and the gap is still audited.
Immutability creates its own puzzles. The retention sweep cannot DELETE through its own append-only trigger, so system maintenance briefly and explicitly bypasses triggers inside a transaction, chunked and logged. And since visit rows refuse edits, all corrections flow through a dedicated maintenance workflow with a seven-day window, exactly how Pennsylvania wants it. The HHAeXchange integration is honest about its state: automated submission is not implemented yet, so the code says so and offers a portal-ready CSV export instead of faking success.
This app is the part of the platform that is mine alone. I designed it and built it, every screen and every flow, while the web console was shared work. The piece I care most about is location: the app tracks where the caregiver is against the client's registered geofence, and anyone who drifts outside the radius during a visit is flagged for a coordinator to review rather than silently accepted. The decision is made on the server, so a modified phone cannot talk its way inside the fence.
Compliance is what the agency needs; it is not what makes a caregiver open the app. So the mobile surface grew into the whole workday. Shifts announce themselves with a soft repeating alarm and a full-screen overlay rather than a single notification that gets missed, delivered by server-driven push with SMS behind a per-user channel preference. Caregivers see what their verified visits are worth, log mileage for agency approval, submit availability and time-off requests, and message their agency without leaving the app.
Training lives there too: an in-app course player with resume-where-you-left-off, including a Pennsylvania Chapter 611 Direct Care Worker competency course of eight modules and a 25-question exam, with the completion evidence attached to the per-visit audit packet. Clock-out captures a verification-of-service e-signature and structured task and note documentation, so the evidence packet is complete the moment the visit ends.
The newest layer is identity. RayVerify adds a consented selfie identity check at clock-in, so the record shows not just that someone's phone was at the right address, but that the right person was holding it. It refuses the capture clearly when storage is not configured rather than silently degrading, because a verification feature that quietly stops verifying is worse than one that is switched off.
RayHealth EVV is live in production at rayhealthevv.com, aligned to Pennsylvania DHS requirements with all Cures Act data elements captured, Sandata submission wired end to end on a schedule, and real 837P and 835 EDI handling. Adding the next state is a registry entry, not a refactor: New Jersey already exists in the codebase as a config object waiting for its production flag.
The delivery pipeline runs seven required CI checks including type checking, security scanning, and a job that verifies the audit triggers still refuse mutations. 1,081 tests keep the compliance math honest, and the operational side is written down rather than improvised: deploy, monitoring, mobile and App Store release runbooks, a risk register, incident response, data retention, and encryption verification all live in the repo.
