App Overview
POTS Check — Application Facts
- Full name
- POTS Check
- Platform
- iOS 18.0+ / watchOS 11.0+
- Status
- Production — v1.3a on the App Store since December 2025
- Developer
- Cascade Agentic Labs LLC
- Protocol
- Cascade Protocol (Swift SDK, CascadeSDK)
- Vocabulary
pots:(https://ns.cascadeprotocol.org/pots/v1#), v1.3 Stable- Data store
- Local Solid Pod (AES-256-GCM encrypted)
- Network calls
- Zero during core test protocol operation
POTS Check is a native iOS and watchOS application that guides patients through a standardized POTS screening test (the NASA Lean Test) and records the results using the Cascade Protocol. It is the first production application built on the protocol and the reference implementation for the pots: vocabulary.
This case study documents how the Cascade Protocol works in practice: where the vocabulary is applied, how provenance is tracked across different data sources within a single test session, and what the encrypted Pod export looks like.
The Clinical Problem
What is POTS?
Postural Orthostatic Tachycardia Syndrome (POTS) is a disorder of the autonomic nervous system characterized by an abnormal heart rate increase upon standing. The diagnostic hallmark is a sustained rise in heart rate of 30 bpm or more (40+ bpm in adolescents) within ten minutes of standing, without a significant drop in blood pressure.
POTS is more common than most clinicians expect. Estimates suggest 1–3 million Americans are affected, with a strong female predominance and a median age of onset in the 20s. It is frequently associated with long COVID. Despite this prevalence, POTS is often missed or misdiagnosed for years because its cardinal symptom—heart rate increase upon standing—is not routinely measured in clinical settings. A standard orthostatic vitals check in most clinical settings measures only blood pressure, not heart rate.
The NASA Lean Test
The NASA Lean Test is a simplified tilt table test designed for at-home or office use. The patient lies supine for 10 minutes (resting phase) while baseline heart rate and symptoms are recorded. The patient then stands for 10 minutes while heart rate and symptom burden are recorded at one-minute intervals.
A positive result (POTS threshold met) is defined by age-adjusted criteria:
- Adults: heart rate increase ≥30 bpm from supine baseline, sustained for at least one minute
- Adolescents (12–19): heart rate increase ≥40 bpm
The test is accessible because it requires no specialized equipment beyond a heart rate monitor—a ubiquitous capability in Apple Watch. POTS Check automates the protocol, captures Apple Watch heart rate data via HealthKit, and guides the patient through each phase with timed prompts.
Why structured data matters here
POTS diagnosis requires comparing data points across time (supine vs standing) and across multiple test sessions (trend analysis over weeks or months). The structured pots: vocabulary ensures that each heart rate reading, symptom score, and test result is stored in a format that preserves its clinical context—which phase of the test it came from, when it was recorded, and whether it was measured by the Apple Watch (device-generated) or entered by the patient (self-reported).
Without provenance tracking, a heart rate of 72 bpm in the record is ambiguous: was it the supine baseline, the standing peak, or a resting measurement from an unrelated session? The pots: vocabulary and Cascade's provenance model make this unambiguous.
Architecture
Components
| Component | Role | Location |
|---|---|---|
| POTS Check iOS App | Test protocol UX, HealthKit coordination, phase timing | User's iPhone |
| POTS Check watchOS App | Real-time heart rate display on wrist during test | User's Apple Watch |
| Apple HealthKit | Heart rate data source (from Apple Watch or other paired sensors) | On-device (iOS) |
| CascadeSDK (Swift) | Data models, Turtle serialization, Pod storage, AES-256-GCM encryption | Embedded in POTS Check iOS app |
| Local Pod | Encrypted storage of test sessions in RDF/Turtle format | On-device filesystem (iOS app sandbox) |
| Pod Export | Encrypted .pod file for sharing with clinician or personal backup |
Generated on demand, stays on device until shared |
SDK Layers
The CascadeSDK provides the data layer that POTS Check builds on. Its internal structure maps to the protocol's three-layer ontology:
- Layer 1 (Standards): SNOMED CT and LOINC codes embedded in the vocabulary for heart rate, symptoms, and test outcomes. Standard codes are attached to every data point, ensuring clinical mappability.
- Layer 2 (Domain):
pots:vocabulary defines POTS-specific concepts:pots:POTSTestSession,pots:HeartRateReading,pots:SymptomScore,pots:POTSCheckResult,pots:TestPhase. These map to Layer 1 codes. - Layer 3 (Patient-facing): The app's summary view and export report. Derived from Layer 2 data but formatted for patient and clinician readability.
Data Flow
[Apple Watch] | | Heart rate samples (HKQuantitySample) | Provenance: DeviceGenerated v [HealthKit (on-device)] | | HKSampleQuery (authorized, per-session) v [POTS Check iOS App] | | User enters symptom scores during test | Provenance: SelfReported | | POTSTestShared: assembles session model v [CascadeSDK Serializer] | | Maps POTSTestSession + HeartRateReadings + SymptomScores | to health: + pots: + prov: Turtle RDF triples | | Attaches provenance per data source: | HeartRateReadings -> cascade:DeviceGenerated | SymptomScores -> cascade:SelfReported | POTSCheckResult -> cascade:DeviceGenerated (computed from HR) v [RDF/Turtle Serialization] | | .ttl files: wellness/pots-sessions/ v [AES-256-GCM Encryption] | | Key: stored in iOS Keychain (never written to disk) | Cipher: AES-256-GCM (CryptoKit) v [Local Pod (iOS app sandbox)] | | Data stays on device unless user initiates export | +---> [Pod Export (.pod file)] | | User shares via Files, AirDrop, email, etc. v [Clinician / Personal Archive / Solid Server (future)]
The data flow is entirely local until the user explicitly initiates an export. HealthKit authorization is granted per session and is scoped to heart rate samples during the test period. No data is transmitted to external servers at any point.
The Cascade Protocol in Practice
pots: Vocabulary — Key Classes
The pots: vocabulary defines the concepts needed to represent a POTS test session. Below is a representative excerpt from the vocabulary TTL file:
@prefix pots: <https://ns.cascadeprotocol.org/pots/v1#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
# ── Core Classes ─────────────────────────────────────────────────────────────
pots:POTSTestSession a owl:Class ;
rdfs:label "POTS Test Session" ;
rdfs:comment "A single NASA Lean Test session with supine and standing phases." ;
rdfs:isDefinedBy <https://ns.cascadeprotocol.org/pots/v1> .
pots:HeartRateReading a owl:Class ;
rdfs:label "Heart Rate Reading" ;
rdfs:comment "A single heart rate measurement within a test session, tagged with test phase." ;
rdfs:isDefinedBy <https://ns.cascadeprotocol.org/pots/v1> .
pots:SymptomScore a owl:Class ;
rdfs:label "Symptom Score" ;
rdfs:comment "Patient-reported symptom burden at a point during the test (0-10 scale)." ;
rdfs:isDefinedBy <https://ns.cascadeprotocol.org/pots/v1> .
pots:POTSCheckResult a owl:Class ;
rdfs:label "POTS Check Result" ;
rdfs:comment "The computed outcome of a test session: threshold met/not met and delta HR." ;
rdfs:isDefinedBy <https://ns.cascadeprotocol.org/pots/v1> .
# ── Key Properties ────────────────────────────────────────────────────────────
pots:protocol a owl:DatatypeProperty ;
rdfs:domain pots:POTSTestSession ;
rdfs:range xsd:string ;
rdfs:label "Protocol" ;
rdfs:comment "Test protocol used. Value: 'nasaLean'." .
pots:testPhase a owl:DatatypeProperty ;
rdfs:domain pots:HeartRateReading ;
rdfs:range xsd:string ;
rdfs:comment "Phase of the test: 'supine' or 'standing'." .
pots:potsThresholdMet a owl:DatatypeProperty ;
rdfs:domain pots:POTSCheckResult ;
rdfs:range xsd:boolean ;
rdfs:comment "Whether the age-adjusted POTS HR threshold was met." .
pots:heartRateDelta a owl:DatatypeProperty ;
rdfs:domain pots:POTSCheckResult ;
rdfs:range xsd:integer ;
rdfs:comment "Peak standing HR minus supine baseline HR (bpm)." .
pots:ageAdjustedThreshold a owl:DatatypeProperty ;
rdfs:domain pots:POTSCheckResult ;
rdfs:range xsd:integer ;
rdfs:comment "The threshold applied (30 bpm adult, 40 bpm adolescent)." .
A Serialized Test Session
Below is an example of what a POTS test session looks like when serialized to Turtle. This is the actual format stored in the user's Pod:
@prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> .
@prefix health: <https://ns.cascadeprotocol.org/health/v1#> .
@prefix pots: <https://ns.cascadeprotocol.org/pots/v1#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
# ── Test Session ──────────────────────────────────────────────────────────────
<urn:uuid:4a8f7b2c-3d91-4e6f-a2b3-c4d5e6f7a8b9>
a pots:POTSTestSession ;
cascade:schemaVersion "1.3" ;
pots:protocol "nasaLean" ;
pots:date "2026-01-20T14:00:00Z"^^xsd:dateTime ;
pots:supineBaselineHR "58"^^xsd:integer ;
pots:peakStandingHR "97"^^xsd:integer ;
prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-abc> ;
prov:generatedAtTime "2026-01-20T14:45:00Z"^^xsd:dateTime .
# ── Heart Rate Readings (DeviceGenerated from Apple Watch) ────────────────────
<urn:uuid:b1c2d3e4-f5a6-7890-bcde-f01234567890>
a pots:HeartRateReading ;
health:heartRate "58"^^xsd:integer ;
health:heartRateSnomedCode "364075005" ;
pots:testPhase "supine" ;
pots:minuteOffset "10"^^xsd:integer ; # 10 min supine = baseline
health:recordedAt "2026-01-20T14:10:00Z"^^xsd:dateTime ;
cascade:dataProvenance cascade:DeviceGenerated ;
health:sourceDevice "Apple Watch Series 9" ;
prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-abc> .
<urn:uuid:c2d3e4f5-a6b7-8901-cdef-012345678901>
a pots:HeartRateReading ;
health:heartRate "97"^^xsd:integer ;
health:heartRateSnomedCode "364075005" ;
pots:testPhase "standing" ;
pots:minuteOffset "3"^^xsd:integer ; # Peak at 3 min standing
health:recordedAt "2026-01-20T14:23:00Z"^^xsd:dateTime ;
cascade:dataProvenance cascade:DeviceGenerated ;
health:sourceDevice "Apple Watch Series 9" ;
prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-abc> .
# ── Symptom Score (SelfReported by patient) ───────────────────────────────────
<urn:uuid:d3e4f5a6-b7c8-9012-defa-123456789012>
a pots:SymptomScore ;
pots:testPhase "standing" ;
pots:minuteOffset "3"^^xsd:integer ;
pots:lightheadednessScore "7"^^xsd:integer ;
pots:heartPalpitationsScore "6"^^xsd:integer ;
pots:nausea "false"^^xsd:boolean ;
pots:chestPain "false"^^xsd:boolean ;
pots:recordedAt "2026-01-20T14:23:00Z"^^xsd:dateTime ;
cascade:dataProvenance cascade:SelfReported ;
prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-abc> .
# ── Test Result (computed from DeviceGenerated HR data) ──────────────────────
<urn:uuid:e4f5a6b7-c8d9-0123-efab-234567890123>
a pots:POTSCheckResult ;
pots:potsThresholdMet "true"^^xsd:boolean ;
pots:heartRateDelta "39"^^xsd:integer ;
pots:ageAdjustedThreshold "30"^^xsd:integer ;
pots:testSession <urn:uuid:4a8f7b2c-3d91-4e6f-a2b3-c4d5e6f7a8b9> ;
cascade:dataProvenance cascade:DeviceGenerated ;
cascade:schemaVersion "1.3" ;
prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-abc> ;
prov:generatedAtTime "2026-01-20T14:45:00Z"^^xsd:dateTime .
Provenance in a Test Session
A single POTS test session produces records with three different provenance types, reflecting the actual origins of the data. This is one of the protocol's key contributions: provenance is per-record, not per-session.
Every heart rate sample comes from HealthKit, which receives it from Apple Watch (or another paired sensor). The cascade:DeviceGenerated provenance type is applied. The source device name is recorded in health:sourceDevice. An agent processing this data knows these are hardware measurements, not estimates or manual entries.
The patient rates their symptoms (lightheadedness, palpitations, nausea, chest pain) at each standing minute. These are entered directly into the iOS app. The cascade:SelfReported provenance type is applied. An agent analyzing the session treats these records with the appropriate epistemic weight—they reflect subjective experience, not sensor measurements.
The pots:POTSCheckResult is computed by the app from the device-generated heart rate data. It inherits cascade:DeviceGenerated provenance because the underlying computation is deterministic from sensor data. The pots:testSession link preserves the derivation chain: the result points to the session, which references each reading.
If a clinician reviews the test export and imports notes back into the app, those notes are tagged cascade:ClinicalGenerated. The protocol supports mixed-provenance sessions—a single session can contain device data, self-reported symptoms, and clinician commentary, each with correct provenance attribution.
This granular provenance is clinically meaningful. A physician reviewing an exported Pod can immediately distinguish: "This HR reading came from the Watch sensor during the standing phase at minute 3. This symptom score was reported by the patient at the same moment. The threshold result was calculated from the sensor data." That chain of custody matters for clinical credibility.
Encryption and Storage
All Turtle files written by POTS Check are encrypted at rest before being written to the iOS filesystem. The CascadeSDK handles this transparently:
- Key generation. On first launch, the SDK generates a unique AES-256 key for the user's Pod. This key is stored in the iOS Keychain and never written to disk.
- Encryption. Each Turtle file is encrypted with AES-256-GCM before being written to the app's sandbox directory. The GCM authentication tag ensures the file has not been tampered with.
- Decryption. When the app reads a session, the SDK retrieves the key from the Keychain and decrypts the file in memory. Decrypted data is never written back to disk in plaintext.
- Export. The Pod export packages all Turtle files (still encrypted) into a single
.podarchive. The recipient needs the key to decrypt; key exchange is managed by the user through a separate secure channel.
POTS Check stores test session data under the Pod's wellness/pots-sessions/ directory, following the Pod structure specification. Each test session is a separate Turtle file, making it straightforward to delete or export individual sessions.
What the App Demonstrates
The app exercises the full Cascade Protocol stack in a production environment: HealthKit data ingestion, Cascade vocabulary serialization, provenance attribution, AES-256-GCM encryption, Pod storage, and Solid-compatible Pod export. These are not demo features—they are the core data path for every test session run by every user.
What This Proves
POTS Check answers a specific question about the Cascade Protocol: does the protocol work in a real app, with real users, collecting real health data? The answer from v1.3a is yes.
All data required for a NASA Lean Test session is expressible with the vocabulary as defined. No ad-hoc fields were needed. The vocabulary covers phases, readings, symptom dimensions, age-adjusted thresholds, and result flags.
A single test session contains DeviceGenerated HR data and SelfReported symptom data. Both coexist in the same session record with correct per-record provenance attribution, without any data model conflicts.
The SDK handles encryption, Pod management, and serialization without requiring a server. The app works offline. User experience is not degraded by the added security layer.
Exported Turtle files are readable with any RDF viewer or text editor (after decryption). The data is self-describing: a clinician with basic RDF knowledge can verify that a heart rate reading of 97 bpm was recorded during the standing phase at minute 3, measured by the patient's Apple Watch.
The data pipeline from Apple Watch sensor to encrypted Turtle file works end-to-end in a shipping iOS app. The integration points (HealthKit authorization, HKSampleQuery, SDK serialization, keychain key management) are all exercised in production.
Layer 1 (SNOMED codes on heart rate), Layer 2 (pots: domain vocabulary), and Layer 3 (patient-facing summary view in the app) all coexist without conflict. Adding the POTS vocabulary did not require modifying the core vocabulary.
Scope and Limitations
This is a first production deployment, not a mature ecosystem
POTS Check proves that the Cascade Protocol works for one application, one developer, and one medical condition. It does not prove that the protocol is ready for enterprise clinical systems, multi-organization data exchange, or any use case beyond its own scope.
Specific limitations of this case study as evidence for the protocol:
- Single developer, single app. POTS Check was built by the same team that designed the protocol. There has been no third-party implementation yet. It is possible that the vocabulary has rough edges that only become apparent when a different team implements it.
- Single condition. The POTS vocabulary is specialized. Claims about expressiveness for other clinical domains (e.g., oncology, cardiology, genomics) are not supported by this case study.
- No enterprise integration. POTS Check does not integrate with EHR systems. The Pod export is intended for individual sharing, not institutional data exchange. FHIR alignment exists in the vocabulary, but no certified FHIR integration has been tested.
- No independent security audit. The encryption and key management have not been independently audited. The implementation follows standard practices (AES-256-GCM, CryptoKit, iOS Keychain), but this is different from a formal security review.
- Solid compatibility is partial. The Pod structure is designed to be Solid-compatible, but server sync has not been implemented or tested with any production Solid server.
These limitations are acknowledged not to undermine the case study, but because accurate scope is important for anyone evaluating whether to build on the protocol. POTS Check is a meaningful proof of concept for a specific set of protocol capabilities. It is not a comprehensive validation.