# Cascade Protocol — Complete Technical Reference # llms-full.txt # Vocabularies as of: 2026-08-20 # # This file contains the complete technical specification of the Cascade Protocol: # ontologies (OWL/Turtle), SHACL validation shapes, serialization spec, # pod structure, conformance suite docs, SDK docs (TypeScript, Python), # agent examples, blog posts, security guide, case studies, and schema reference. # # Use this file as context for AI assistants (e.g., Google NotebookLM, Claude) # to answer detailed technical questions about the Cascade Protocol. # # Structure: # Section 1: Overview, Namespaces, and Design Principles # Section 2: OWL Ontologies (full TTL) # Section 3: SHACL Validation Shapes (full TTL) # Section 4: Schema Reference (cascade-protocol-schemas.md) # Section 5: Serialization Specification # Section 6: Pod Structure Specification # Section 7: Conformance Test Suite # Section 8: SDK Documentation (TypeScript, Python) # Section 9: Agent Examples # Section 10: Security & Compliance Guide # Section 11: Protocol Comparison # Section 12: Technical Blog Posts # Section 13: Case Studies # ============================================================================ # SECTION 1: OVERVIEW, NAMESPACES, AND DESIGN PRINCIPLES # ============================================================================ # Cascade Protocol Specification [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) [![Spec Status](https://img.shields.io/badge/status-stable-green.svg)]() The canonical specification for the Cascade Protocol: ontology files (OWL/Turtle), SHACL validation shapes, serialization rules, and JSON-LD contexts. ## Overview Cascade Protocol defines a three-layer semantic vocabulary for health data: | Layer | Namespace | Prefix | Purpose | |---|---|---|---| | Core | `https://ns.cascadeprotocol.org/core/v1#` | `cascade:` | Identity, provenance, Pod structure | | Health | `https://ns.cascadeprotocol.org/health/v1#` | `health:` | Wellness and device data | | Clinical | `https://ns.cascadeprotocol.org/clinical/v1#` | `clinical:` | EHR/clinical record data | | Coverage | `https://ns.cascadeprotocol.org/coverage/v1#` | `coverage:` | Insurance and coverage | | Checkup | `https://ns.cascadeprotocol.org/checkup/v1#` | `checkup:` | Patient-facing health summaries | | POTS | `https://ns.cascadeprotocol.org/pots/v1#` | `pots:` | POTS-specific test data | Each vocabulary maps to established standards (FHIR, SNOMED CT, LOINC, RxNorm) while providing patient-owned, local-first semantics. ## Repository Structure ``` spec/ ontologies/ core/v1/ core.ttl # Core ontology (OWL) core.shapes.ttl # SHACL validation shapes health/v1/ health.ttl # Health ontology health.shapes.ttl # SHACL shapes clinical/v1/ clinical.ttl # Clinical ontology clinical.shapes.ttl # SHACL shapes coverage/v1/ coverage.ttl # Coverage ontology coverage.shapes.ttl # SHACL shapes checkup/v1/ checkup.ttl # Checkup ontology checkup.shapes.ttl # SHACL shapes pots/v1/ pots.ttl # POTS ontology pots.shapes.ttl # SHACL shapes serialization/ turtle-rules.md # Turtle serialization conventions pod-structure.md # Pod directory layout spec validation/ index.md # Validation Profile: the entailment regime shapes assume contexts/ cascade-v1.jsonld # JSON-LD context for all vocabularies CHANGELOG.md ``` ## Vocabulary Versions | Vocabulary | Current Version | Classes | Properties | |---|---|---|---| | Core | 2.8 | PatientProfile, Address, EmergencyContact, PharmacyInfo, Pod, Container | ~25 | | Health | 2.3 | HealthProfile, WellnessStatistic | ~15 | | Clinical | 1.7 | ClinicalDocument, ClinicalSection, ClinicalNarrative, ProgressNote, DischargeSummary, ConsultationNote, LaboratoryReport, ImagingReport, VisitSummary, Medication, Allergy, LabResult, Condition, Immunization, Procedure, VitalSign, MedicationUseEpisode, Supplement, LabTestSeries, CoverageRecord (deprecated), Encounter, MedicationAdministration, ImplantedDevice, ImagingStudy | ~40 | | Coverage | 1.3 | InsurancePlan, Deductible, CopaySchedule, ClaimRecord, BenefitStatement, DenialNotice, AppealRecord, DenialReasonCode | ~12 | | Checkup | 3.2 | CheckupSummary | ~10 | | POTS | 1.4 | POTSTest, HeartRateMeasurement | ~8 | ## SHACL Shapes Each vocabulary includes SHACL shapes that define validation constraints: - **Required fields** (`sh:minCount 1`) -- e.g., every MedicationRecord must have a `medicationName` - **Value enumerations** (`sh:in`) -- e.g., `dataProvenance` must be one of `ClinicalGenerated`, `DeviceGenerated`, `SelfReported`, `AIGenerated` - **Pattern constraints** (`sh:pattern`) -- e.g., `schemaVersion` must match `^[0-9]+\.[0-9]+$` - **Datatype constraints** (`sh:datatype`) -- e.g., `isActive` must be `xsd:boolean` Validate data against shapes using the [Cascade CLI](https://github.com/the-cascade-protocol/cli): ```bash cascade validate record.ttl ``` **Shapes are entailment-independent.** SHACL resolves class membership over the data graph, so a shape that reached a class only through an `rdfs:subClassOf` axiom would fire in a validator that merges ontologies and stay silent in one that does not, so the same file would be valid under one implementation and invalid under another. Every class these shapes constrain therefore carries an explicit `sh:targetClass`, and constraint inheritance is stated with `sh:node` rather than inferred. [`validation/index.md`](validation/index.md) states the rule normatively; `scripts/check-shape-targets.py` enforces it on every change. ## Namespace Prefixes All Cascade Protocol data uses these canonical prefixes: ```turtle @prefix cascade: . @prefix health: . @prefix clinical: . @prefix coverage: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix rxnorm: . @prefix xsd: . @prefix prov: . ``` ## Links - [Full Documentation](https://cascadeprotocol.org/docs/) - [Schema Reference](https://cascadeprotocol.org/docs/cascade-protocol-schemas/) - [Conformance Test Suite](https://github.com/the-cascade-protocol/conformance) - [CLI Tool](https://github.com/the-cascade-protocol/cli) ## License This specification is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). # ============================================================================ # SECTION 2: OWL ONTOLOGIES (Full TTL Source) # ============================================================================ ## Ontology: core ## File: core.ttl @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix prov: . @prefix foaf: . @prefix vcard: . @prefix dcat: . @prefix void: . # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; dct:title "Cascade Protocol Core Vocabulary"@en ; dct:description "Core vocabulary for schema versioning, data provenance, identity management, and patient demographics across all Cascade Protocol applications"@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2025-10-31"^^xsd:date ; dct:modified "2026-08-14"^^xsd:date ; owl:versionInfo "3.6" ; rdfs:comment "This vocabulary defines cross-cutting concepts used by all Cascade Protocol health data applications: schema versioning, data provenance, patient demographics (PatientProfile), and supporting identity classes (Address, PharmacyInfo, AdvanceDirectives). Domain-specific vocabularies (e.g., pots:, ecg:, glucose:) import this core vocabulary."@en ; rdfs:seeAlso . # ============================================================================ # Data Provenance Classes # ============================================================================ cascade:DataProvenance a owl:Class ; rdfs:label "Data Provenance"@en ; rdfs:comment "Classification of how health data was generated or collected"@en ; rdfs:subClassOf prov:Entity . # --- Broad DataProvenance Categories (promoted from individuals to classes) --- cascade:ConsumerGenerated a owl:Class ; rdfs:subClassOf cascade:DataProvenance ; rdfs:label "Consumer-Generated"@en ; rdfs:comment "Data generated directly by the user through personal devices (non-clinical setting)."@en ; owl:disjointWith cascade:ClinicalGenerated . cascade:ClinicalGenerated a owl:Class ; rdfs:subClassOf cascade:DataProvenance ; rdfs:label "Clinical-Generated"@en ; rdfs:comment "Data generated in a clinical setting under healthcare provider supervision."@en ; owl:disjointWith cascade:ConsumerGenerated . # --- ConsumerGenerated Subclasses --- cascade:DeviceGenerated a owl:Class ; rdfs:subClassOf cascade:ConsumerGenerated ; rdfs:label "Device-Generated"@en ; rdfs:comment "Data generated by a consumer health device (Apple Watch, blood pressure cuff, etc.) without clinical supervision."@en . cascade:SelfReported a owl:Class ; rdfs:subClassOf cascade:ConsumerGenerated ; rdfs:label "Self-Reported"@en ; rdfs:comment "Data manually entered by the patient (symptom logs, medication adherence, intake form fields)."@en . cascade:ConsumerWellness a owl:Class ; rdfs:subClassOf cascade:ConsumerGenerated ; rdfs:label "Consumer Wellness"@en ; rdfs:comment "Aggregated wellness data from consumer platforms (Apple Health, Google Fit)."@en . # --- ClinicalGenerated Subclasses --- cascade:EHRVerified a owl:Class ; rdfs:subClassOf cascade:ClinicalGenerated ; rdfs:label "EHR-Verified"@en ; rdfs:comment "Data imported from a verified Electronic Health Record system (Epic MyChart, Cerner, etc.)."@en . cascade:ScannedDocument a owl:Class ; rdfs:subClassOf cascade:ClinicalGenerated ; rdfs:label "Scanned Document"@en ; rdfs:comment "Data extracted from a scanned or photographed clinical document."@en . cascade:AIExtracted a owl:Class ; rdfs:subClassOf cascade:ClinicalGenerated ; rdfs:label "AI-Extracted"@en ; rdfs:comment "Data extracted from clinical documents using AI/NLP processing."@en . # ============================================================================ # Data Properties # ============================================================================ # Schema Versioning cascade:schemaVersion a owl:DatatypeProperty ; rdfs:label "Schema Version"@en ; rdfs:comment "Semantic version of the ontology schema used to serialize this data (format: major.minor.patch)"@en ; rdfs:range xsd:string ; rdfs:seeAlso . # Data Provenance cascade:dataProvenance a owl:ObjectProperty ; rdfs:label "Data Provenance"@en ; rdfs:comment "Classification of how this data was generated (consumer vs clinical)"@en ; rdfs:range cascade:DataProvenance . # Identity cascade:creatorWebID a owl:ObjectProperty ; rdfs:label "Creator WebID"@en ; rdfs:comment "Solid WebID URI of the user who created this data"@en ; rdfs:subPropertyOf prov:wasAttributedTo ; rdfs:range rdfs:Resource ; rdfs:seeAlso . # ============================================================================ # Data Source Identity (v1.1) # ============================================================================ # Design rationale: DataSource is placed in core: (not health:) because source # identity is cross-cutting infrastructure — it applies equally to clinical data # (which EHR system, which lab) and consumer wellness data (which device, which app). # Subclasses prov:Agent per W3C PROV-O: sources are agents that produce entities. # # Relationship to FHIR Device: FHIR Device is for clinical device registries with # regulatory identifiers. cascade:DataSource is broader — it covers apps, manual entry, # EHR systems, and consumer devices. When FHIR interop is needed, a DataSource can # reference a FHIR Device via rdfs:seeAlso. # # Reconciliation strategy (what happens when two sources disagree on the same metric) # is deferred to post-MVP. For now, source attribution is displayed per-metric and # users can view which source produced each reading. # # Future extension point: cascade:sourceTransitionEvent for tracking device migrations # (e.g., Fitbit → Apple Watch) including date ranges and data continuity notes. cascade:DataSource a owl:Class ; rdfs:label "Data Source"@en ; rdfs:comment "Identity and reliability metadata for a data-producing device, app, or system. Examples: Apple Watch, MyChart EHR, manual user entry."@en ; rdfs:subClassOf prov:Agent . cascade:sourceName a owl:DatatypeProperty ; rdfs:label "Source Name"@en ; rdfs:comment "Human-readable name: 'Apple Watch Series 10', 'MyChart', 'Manual Entry'"@en ; rdfs:domain cascade:DataSource ; rdfs:range xsd:string . cascade:sourceBundleId a owl:DatatypeProperty ; rdfs:label "Source Bundle Identifier"@en ; rdfs:comment "App bundle identifier (e.g., 'com.apple.health'). Maps to HKSource.bundleIdentifier on Apple platforms."@en ; rdfs:domain cascade:DataSource ; rdfs:range xsd:string . cascade:sourceReliabilityTier a owl:DatatypeProperty ; rdfs:label "Source Reliability Tier"@en ; rdfs:comment "Reliability classification: 'clinical' (lab equipment, EHR), 'medical_device' (FDA-cleared BP cuff), 'consumer_device' (smartwatch, fitness tracker), 'self_reported' (manual entry). Tiers inform display and conflict resolution — they do not imply clinical validity."@en ; rdfs:domain cascade:DataSource ; rdfs:range xsd:string . cascade:sourceMetricCoverage a owl:ObjectProperty ; rdfs:label "Source Metric Coverage"@en ; rdfs:comment "A wellness metric this source provides. Use multiple triples for multiple metrics (e.g., two triples linking to health:restingHeartRate and health:heartRateVariability). Preferred over comma-separated strings for proper RDF graph traversal."@en ; rdfs:domain cascade:DataSource . # ============================================================================ # Provenance Detail Properties (v2.3) # ============================================================================ # Lightweight provenance detail properties for record-level attribution. # These properties are placed on record instances directly (not on DataSource # objects) for display in Health Map provenance badges without requiring # full DataSource graph traversal. # # cascade:sourceOrganization — populated from FHIR meta.source or contained # Organization reference during clinical record import. Examples: "Kaiser # Permanente", "Mayo Clinic". # # cascade:sourceDeviceName — populated from HealthKit HKSource contributing # app/device name. Examples: "Apple Watch Series 9", "Omron BP Monitor". cascade:sourceOrganization a owl:DatatypeProperty ; rdfs:label "Source Organization"@en ; rdfs:comment "Organization that provided this record (e.g., 'Kaiser Permanente', 'Mayo Clinic'). Populated from FHIR meta.source or contained Organization reference during clinical record import."@en ; rdfs:range xsd:string . cascade:sourceDeviceName a owl:DatatypeProperty ; rdfs:label "Source Device Name"@en ; rdfs:comment "Device or app that recorded this measurement (e.g., 'Apple Watch Series 9', 'Omron BP Monitor'). Populated from HealthKit HKSource contributing app/device name."@en ; rdfs:range xsd:string . # ============================================================================ # Vocabulary Organization (v1.3) # ============================================================================ cascade:subConcern a owl:AnnotationProperty ; rdfs:label "Sub-Concern"@en ; rdfs:comment "Classifies a vocabulary class by its functional role within a vocabulary. Values are vocabulary-specific strings (e.g., 'summary', 'intake', 'analytics', 'identity', 'app-config', 'structural', 'deprecated'). Used for documentation generation, migration planning, and vocabulary organization."@en ; rdfs:domain owl:Class ; rdfs:range xsd:string . # ============================================================================ # Schema Metadata Annotations (v2.1 — PF1-R) # ============================================================================ cascade:addedInSchemaVersion a owl:AnnotationProperty ; rdfs:label "Added in Schema Version"@en ; rdfs:comment "Records which schema version introduced this data element. Used by serializers to annotate when a class or property was first emitted."@en ; rdfs:range xsd:string . cascade:cascadeUri a owl:DatatypeProperty ; rdfs:label "Cascade URI"@en ; rdfs:comment """Stable identifier URI for a Cascade data element. Used across vocabularies to mint persistent identifiers for supplements, medications, and other resources. CANONICAL FORM OF A MULTI-VALUED IDENTITY INPUT (v3.6, NORMATIVE). An implementation that derives a record's identifier from the record's own content hashes a set of named content fields. Since health v2.6 and clinical v1.14 made the code fields (icd10Code, snomedCode, testCode, labCategory) 0..* to match FHIR R4 CodeableConcept.coding, one of those fields can hold several values, and a hash taken over an unordered field is only stable if the field is first put into a canonical form. Without one, two exports of the SAME record that list the same codings in a different order mint two different identifiers and the record silently splits in two. The canonical form of a multi-valued identity input is: 1. Discard members that are null or empty after trimming; trim each surviving member. 2. Deduplicate the survivors. 3. Sort ascending by Unicode code point. (Code point, not locale collation: a locale-dependent order would make identity depend on the machine.) 4. Join with a single separator character that the implementation fixes and documents. U+002C COMMA is the RECOMMENDED separator and new implementations MUST use it. 5. A one-element sequence MUST canonicalize to exactly the bare scalar form. 6. A sequence with no surviving member is treated as an absent field, exactly as a null scalar is. THE THREE INVARIANTS, which are normative independently of the separator and are what the conformance vectors check: ORDER INDEPENDENCE Shuffling the members of a multi-valued identity input MUST NOT change the identifier. SCALAR AGREEMENT A field holding exactly one value MUST mint the same identifier whether it is spelled as a scalar or as a one-element sequence. This is what keeps every identifier written before the fields became 0..* exactly where it is. DUPLICATE INDEPENDENCE Repeating a member MUST NOT change the identifier. SCOPE, AND THE ONE PLACE THIS RULE MUST NOT BE APPLIED. It governs identity inputs whose source element is a SET: FHIR CodeableConcept.coding and the repeating code fields named above. It MUST NOT be applied to an input whose source order carries meaning, because sorting there merges records that the source deliberately distinguished. FHIR name[0] is the primary name, and a component or note list is a sequence, not a set. An implementation that already hashes such a list in document order MUST keep doing so; widening this rule to cover it is a data change, not a canonicalization. MIGRATION. An implementation that already ships a fixed separator other than U+002C at an existing identity site KEEPS that separator. Changing it re-mints every identifier the site has ever produced, which is precisely the harm this rule exists to prevent; the cross-implementation contract is the three invariants above, not the byte string. Adding the dedupe and the sort where they are missing moves an identifier only for a record that carried duplicate or unsorted members, which is the defect being corrected."""@en ; rdfs:range xsd:anyURI ; owl:versionInfo "Canonical form of multi-valued identity inputs stated in core v3.6" . # ============================================================================ # Shared Preference Properties (v1.3) # ============================================================================ # Cross-app user preferences. These properties are used in /settings/shared/ # preferences.ttl for preferences that apply across all Cascade apps. # App-specific settings use their own namespace (e.g., checkup:, pots:). cascade:notificationsEnabled a owl:DatatypeProperty ; rdfs:label "Notifications Enabled"@en ; rdfs:comment "Whether the user has enabled push notifications across Cascade apps."@en ; rdfs:range xsd:boolean . cascade:preferredUnits a owl:DatatypeProperty ; rdfs:label "Preferred Units"@en ; rdfs:comment "User's preferred unit system: 'metric' or 'imperial'."@en ; rdfs:range xsd:string . # ============================================================================ # Patient Profile (v2.0) # ============================================================================ # Health-specific patient demographics, designed to complement a Solid-compatible # foaf:Agent identity profile. The general identity profile (/profile/card.ttl) # uses foaf:/vcard:/dct: vocabularies. This health profile (/profile/health.ttl) # uses cascade: for health-relevant demographics that are always private. # # Two-document profile model (Solid WebID Profile pattern): # /profile/card.ttl -- foaf:Agent with foaf:givenName, foaf:familyName, # vcard:hasEmail, vcard:hasTelephone, dct:language # (linked via rdfs:seeAlso to health profile) # /profile/health.ttl -- cascade:PatientProfile with health-specific data # # Design rationale: Separate documents enable different access control policies. # The general profile may be shared; the health profile is always private. # See Schema Refactoring Plan WS6 for full architectural discussion. cascade:PatientProfile a owl:Class ; rdfs:label "Patient Profile"@en ; rdfs:comment "Health-specific patient demographics and identity extensions. Designed to complement a Solid-compatible foaf:Agent identity profile (linked via rdfs:seeAlso). Stored at /profile/health.ttl. Contains health-relevant demographic data that is always private."@en . # --- PatientProfile Properties --- cascade:dateOfBirth a owl:DatatypeProperty ; rdfs:label "Date of Birth"@en ; rdfs:comment "Patient date of birth for age calculation and screening eligibility. FHIR-aligned: fhir:Patient.birthDate."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:date . cascade:genderIdentity a owl:DatatypeProperty ; rdfs:label "Gender Identity"@en ; rdfs:comment "Patient-reported gender identity. Values: woman, man, non_binary, other, prefer_not_to_say. SNOMED CT mapped via US Core genderIdentity extension."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:biologicalSex a owl:DatatypeProperty ; rdfs:label "Biological Sex"@en ; rdfs:comment "Biological sex for clinical calculations and screening recommendations. Values: male, female, intersex."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:ageGroup a owl:DatatypeProperty ; rdfs:label "Age Group"@en ; rdfs:comment "Computed demographic classification for screening recommendations. Values: pediatric (0-17), young_adult (18-39), adult (40-64), senior (65+). Computed from dateOfBirth at serialization time."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:emergencyContactName a owl:DatatypeProperty ; rdfs:label "Emergency Contact Name"@en ; rdfs:comment "Full name of the patient's emergency contact person."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:emergencyContactRelationship a owl:DatatypeProperty ; rdfs:label "Emergency Contact Relationship"@en ; rdfs:comment "Relationship of emergency contact to the patient. Values: spouse, parent, child, sibling, partner, friend, caregiver, other."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:emergencyContactPhone a owl:DatatypeProperty ; rdfs:label "Emergency Contact Phone"@en ; rdfs:comment "Phone number of the patient's emergency contact person."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:hasAddress a owl:ObjectProperty ; rdfs:label "Has Address"@en ; rdfs:comment "Links to a structured postal address. Used by PatientProfile and PharmacyInfo."@en ; rdfs:range cascade:Address . cascade:maritalStatus a owl:DatatypeProperty ; rdfs:label "Marital Status"@en ; rdfs:comment "Patient's marital status. Values: single, married, domesticPartnership, divorced, separated, widowed, preferNotToSay. FHIR-mapped via HL7 v3 MaritalStatus value set (MaritalStatus.fromFHIR())."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:raceEthnicity a owl:DatatypeProperty ; rdfs:label "Race/Ethnicity"@en ; rdfs:comment "Patient's self-reported race/ethnicity using OMB standard categories. Multi-value: each selected value becomes a separate RDF triple with the same predicate (e.g. cascade:raceEthnicity \"asian\", \"white\"). Values: americanIndianAlaskaNative, asian, blackAfricanAmerican, hispanicLatino, nativeHawaiianPacificIslander, white, other, preferNotToSay. Elevated PHI sensitivity. FHIR-mapped via US Core race/ethnicity OMB category codes."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:hasPreferredPharmacy a owl:ObjectProperty ; rdfs:label "Has Preferred Pharmacy"@en ; rdfs:comment "Links to the patient's preferred pharmacy for prescriptions. Deprecated v2.2 — use cascade:preferredPharmacy (blank-node linking) instead."@en ; owl:deprecated true ; rdfs:domain cascade:PatientProfile ; rdfs:range cascade:PharmacyInfo . cascade:hasAdvanceDirectives a owl:ObjectProperty ; rdfs:label "Has Advance Directives"@en ; rdfs:comment "Links to the patient's advance directive declarations. Elevated PHI sensitivity. Deprecated v2.2 — use cascade:advanceDirectives (blank-node linking) instead."@en ; owl:deprecated true ; rdfs:domain cascade:PatientProfile ; rdfs:range cascade:AdvanceDirectives . # PatientProfile additional properties (v2.2, Phase 3.5 ONT2) cascade:computedAge a owl:DatatypeProperty ; rdfs:label "Computed Age"@en ; rdfs:comment "Patient age in years, computed from dateOfBirth at serialization time. App-layer convenience property."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:integer . cascade:profileId a owl:DatatypeProperty ; rdfs:label "Profile ID"@en ; rdfs:comment "Unique identifier (UUID) for the patient profile instance. Used for traceability and deduplication."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . cascade:pharmacyAddress a owl:DatatypeProperty ; rdfs:label "Pharmacy Address"@en ; rdfs:comment "Pharmacy address as a single text string. Simplified alternative to using a nested cascade:Address blank node for PharmacyInfo."@en ; rdfs:domain cascade:PharmacyInfo ; rdfs:range xsd:string . cascade:EmergencyContact a owl:Class ; rdfs:label "Emergency Contact"@en ; rdfs:comment "A patient's emergency contact person. Used as a blank node within cascade:PatientProfile. Properties: cascade:contactName, cascade:contactRelationship, cascade:contactPhone."@en . cascade:contactName a owl:DatatypeProperty ; rdfs:label "Contact Name"@en ; rdfs:comment "Full name of a contact person. Used within cascade:EmergencyContact blank nodes."@en ; rdfs:domain cascade:EmergencyContact ; rdfs:range xsd:string . cascade:contactRelationship a owl:DatatypeProperty ; rdfs:label "Contact Relationship"@en ; rdfs:comment "Relationship of the contact to the patient: spouse, parent, child, sibling, partner, friend, caregiver, other."@en ; rdfs:domain cascade:EmergencyContact ; rdfs:range xsd:string . cascade:contactPhone a owl:DatatypeProperty ; rdfs:label "Contact Phone"@en ; rdfs:comment "Phone number for a contact person. Used within cascade:EmergencyContact blank nodes and also for general contact use on PatientProfile."@en ; rdfs:range xsd:string . cascade:emergencyContact a owl:ObjectProperty ; rdfs:label "Emergency Contact"@en ; rdfs:comment "Links a PatientProfile to an EmergencyContact blank node."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range cascade:EmergencyContact . cascade:advanceDirectives a owl:ObjectProperty ; rdfs:label "Advance Directives"@en ; rdfs:comment "Links a PatientProfile to an AdvanceDirectives blank node."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range cascade:AdvanceDirectives . cascade:preferredPharmacy a owl:ObjectProperty ; rdfs:label "Preferred Pharmacy"@en ; rdfs:comment "Links a PatientProfile to a PharmacyInfo blank node."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range cascade:PharmacyInfo . cascade:address a owl:ObjectProperty ; rdfs:label "Address"@en ; rdfs:comment "Links an entity to an Address blank node. Used by PatientProfileSerializer."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range cascade:Address . cascade:preferredLanguage a owl:DatatypeProperty ; rdfs:label "Preferred Language"@en ; rdfs:comment "Patient's preferred language for medical communication. NOTE: In the two-document Solid WebID Profile model, this should be emitted as dct:language in /profile/card.ttl (IdentityProfileSerializer), NOT in /profile/health.ttl (PatientProfileSerializer). This property is defined for backward compatibility but SER1 will remove it from PatientProfileSerializer."@en ; rdfs:domain cascade:PatientProfile ; rdfs:range xsd:string . # ============================================================================ # Address (v2.0) # ============================================================================ # Structured postal address. Reuses FHIR Address semantics. # Used by PatientProfile (home address) and PharmacyInfo (pharmacy location). cascade:Address a owl:Class ; rdfs:label "Address"@en ; rdfs:comment "Structured postal address reusing FHIR Address semantics. Used by PatientProfile and PharmacyInfo."@en . cascade:streetAddress a owl:DatatypeProperty ; rdfs:label "Street Address"@en ; rdfs:comment "Street line(s) of the address. Simplified alias of cascade:addressLine for flat serialization patterns."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:city a owl:DatatypeProperty ; rdfs:label "City"@en ; rdfs:comment "City name. Simplified alias of cascade:addressCity for flat serialization patterns."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:state a owl:DatatypeProperty ; rdfs:label "State"@en ; rdfs:comment "State or province. Simplified alias of cascade:addressState for flat serialization patterns."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:postalCode a owl:DatatypeProperty ; rdfs:label "Postal Code"@en ; rdfs:comment "ZIP or postal code. Simplified alias of cascade:addressPostalCode for flat serialization patterns."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:country a owl:DatatypeProperty ; rdfs:label "Country"@en ; rdfs:comment "Country name or ISO 3166-1 code. Simplified alias of cascade:addressCountry for flat serialization patterns."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . # FHIR-aligned Address properties (v2.2, Phase 3.5 ONT1) # These mirror the FHIR Address datatype fields used by the SDK Address struct. # The simpler properties (streetAddress, city, state, postalCode, country) are # retained as aliases for CheckupSerializer backward compatibility. cascade:addressUse a owl:DatatypeProperty ; rdfs:label "Address Use"@en ; rdfs:comment "Purpose of the address: home, work, temp, old. Aligns with FHIR Address.use."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressType a owl:DatatypeProperty ; rdfs:label "Address Type"@en ; rdfs:comment "Type of address: postal, physical, both. Aligns with FHIR Address.type."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressText a owl:DatatypeProperty ; rdfs:label "Address Text"@en ; rdfs:comment "Full address as a single text string. Aligns with FHIR Address.text."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressLine a owl:DatatypeProperty ; rdfs:label "Address Line"@en ; rdfs:comment "Street address line. Repeatable for multi-line addresses. Aligns with FHIR Address.line."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressCity a owl:DatatypeProperty ; rdfs:label "Address City"@en ; rdfs:comment "City name. FHIR-aligned equivalent of cascade:city. Aligns with FHIR Address.city."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressDistrict a owl:DatatypeProperty ; rdfs:label "Address District"@en ; rdfs:comment "District or county. Aligns with FHIR Address.district."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressState a owl:DatatypeProperty ; rdfs:label "Address State"@en ; rdfs:comment "State or province. FHIR-aligned equivalent of cascade:state. Aligns with FHIR Address.state."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressPostalCode a owl:DatatypeProperty ; rdfs:label "Address Postal Code"@en ; rdfs:comment "Postal or ZIP code. FHIR-aligned equivalent of cascade:postalCode. Aligns with FHIR Address.postalCode."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . cascade:addressCountry a owl:DatatypeProperty ; rdfs:label "Address Country"@en ; rdfs:comment "Country name or ISO code. FHIR-aligned equivalent of cascade:country. Aligns with FHIR Address.country."@en ; rdfs:domain cascade:Address ; rdfs:range xsd:string . # ============================================================================ # Pharmacy Info (v2.0) # ============================================================================ # Preferred pharmacy for prescriptions. Manual entry only (no Apple import source). cascade:PharmacyInfo a owl:Class ; rdfs:label "Pharmacy Info"@en ; rdfs:comment "Preferred pharmacy for prescriptions. Manual entry only (no Apple Health import source). Linked from PatientProfile via cascade:hasPreferredPharmacy."@en . cascade:pharmacyName a owl:DatatypeProperty ; rdfs:label "Pharmacy Name"@en ; rdfs:comment "Name of the pharmacy."@en ; rdfs:domain cascade:PharmacyInfo ; rdfs:range xsd:string . cascade:pharmacyPhone a owl:DatatypeProperty ; rdfs:label "Pharmacy Phone"@en ; rdfs:comment "Phone number of the pharmacy."@en ; rdfs:domain cascade:PharmacyInfo ; rdfs:range xsd:string . # Note: PharmacyInfo reuses cascade:hasAddress for its location. # ============================================================================ # Advance Directives (v2.0) # ============================================================================ # Patient-reported advance directive declarations. Elevated PHI sensitivity. cascade:AdvanceDirectives a owl:Class ; rdfs:label "Advance Directives"@en ; rdfs:comment "Patient-reported advance directive declarations. Elevated PHI sensitivity. Linked from PatientProfile via cascade:hasAdvanceDirectives."@en . cascade:hasLivingWill a owl:DatatypeProperty ; rdfs:label "Has Living Will"@en ; rdfs:comment "Whether the patient has a living will on file."@en ; rdfs:domain cascade:AdvanceDirectives ; rdfs:range xsd:boolean . cascade:hasPowerOfAttorney a owl:DatatypeProperty ; rdfs:label "Has Power of Attorney"@en ; rdfs:comment "Whether the patient has a healthcare power of attorney on file."@en ; rdfs:domain cascade:AdvanceDirectives ; rdfs:range xsd:boolean . cascade:hasDNR a owl:DatatypeProperty ; rdfs:label "Has DNR"@en ; rdfs:comment "Whether the patient has a do-not-resuscitate order on file."@en ; rdfs:domain cascade:AdvanceDirectives ; rdfs:range xsd:boolean . cascade:advanceDirectiveNotes a owl:DatatypeProperty ; rdfs:label "Advance Directive Notes"@en ; rdfs:comment "Optional free-text notes about advance directives."@en ; rdfs:domain cascade:AdvanceDirectives ; rdfs:range xsd:string . # ============================================================================ # Reconciliation Properties (v2.4) # ============================================================================ # Used by the Cascade reconciliation pipeline to annotate merged records with # provenance and conflict resolution metadata. # ---------------------------------------------------------------------------- # THE THREE SOURCE AXES (v3.5) # ---------------------------------------------------------------------------- # A record carries three different answers to three different questions, and # collapsing any two of them is a defect that has been measured on real pods. # # ORIGIN cascade:sourceIdentity WHICH ORGANIZATION the record came from, # as a canonical token that is the SAME # whatever transport carried it. This is # the only one of the three that may be # used as a reconciliation key. # # LABEL clinical:sourceEHR what to CALL that organization on screen. # Human-readable, source-worded, and not an # identity: two spellings of one # organization are two labels. # # INGESTION cascade:sourceSystem HOW AND WHEN the data entered the Pod: # the import batch. Explicitly NEVER an # origin. One batch routinely carries # several organizations (a consumer health # app exports every connected account under # one label), and one organization # routinely arrives in many batches. # # The failure this section exists to end: a reconciler that keys "are these two # records from the same source?" on the INGESTION axis answers "yes" for every # pair on a single-batch Pod, so no duplicate is ever compared, and answers "no" # for two exports of one organization, so one organization occupies two rows of # any source view. cascade:sourceSystem a owl:DatatypeProperty ; rdfs:label "Source System"@en ; rdfs:comment "INGESTION AXIS. The import batch a record entered the Pod through (e.g. 'Apple Health export', 'primary-care'). It records HOW AND WHEN data arrived, NOT where it came from, and it MUST NOT be used as a source identity: one batch commonly carries records from several organizations, and one organization commonly arrives in many batches. Use cascade:sourceIdentity for origin."@en ; rdfs:domain owl:Thing ; rdfs:range xsd:string . cascade:sourceIdentity a owl:DatatypeProperty ; rdfs:label "Source Identity"@en ; rdfs:comment """ORIGIN AXIS. The canonical, transport-independent identity of the organization a record came from. Two records that a FHIR export and a C-CDA document of the SAME health system produced carry the same value here, which is what makes it usable as a reconciliation key where clinical:sourceEHR (a display label) and cascade:sourceSystem (an ingestion batch) are not. VALUE FORM. A scheme-prefixed token, so a consumer can always tell how much the producer actually knew: org:{slug} an organization was derivable. {slug} is the normalized form below. ns:{namespace} no organization was derivable, but the record's identifiers have an assigning authority: the FHIR server base URL, or the C-CDA root OID. Records agree on origin only if they agree on the namespace. transport:{label} LAST RESORT. Nothing in the document named or located an organization, so the value restates cascade:sourceSystem, honestly prefixed. It is not an origin claim, and a consumer must treat two transport: values as 'origin unknown' rather than as evidence of a shared source. THE SLUG NORMALIZATION, which both transports must implement identically: 1. The input is either an organization NAME (a C-CDA custodian/author organization name, a FHIR Organization.name or an institution-looking performer display) or a HOST (the registrable domain of the source FHIR endpoint). 2. HOST: lowercase; drop a leading 'www.'; drop the public-suffix label(s), keeping the registrable name label ('fhir.meridianhealth.example' -> 'meridianhealth'). Then repeatedly strip any generic organization word that forms a whole prefix or suffix of that label ('meridianhealth' -> 'meridian'), never stripping to fewer than three characters. 3. NAME: lowercase; fold diacritics to ASCII; replace every run of non-alphanumeric characters with a single space; split on spaces. 4. Drop generic organization words, legal-form words and pure-number tokens from the token list. Generic means institution-or-specialty vocabulary that names no particular organization: health, healthcare, system(s), service(s), medical, medicine, center/centre, hospital(s), clinic(s), care, group, associates, association, network, partners, physicians, practice, regional, community, memorial, university, institute, foundation, laboratory/laboratories/lab(s), diagnostics, imaging, radiology, pathology, family, urgent, primary, specialty, and the specialty names cardiology, oncology, orthopedics, pediatrics; legal-form means inc, llc, llp, ltd, plc, corp, corporation, co, pc, pa, pllc; and the English stopwords of, and, the, for, at, in, on, a, an. 5. If no token survives step 4, use the whole normalized string with separators removed, so an organization named only in generic words still gets a stable identity. 6. The slug is the FIRST surviving token. 'Meridian Health System' and 'meridianhealth.example' both give 'meridian'; 'Providence Health and Services Washington and Montana' and 'providence.org' both give 'providence'. WHY THE LEADING TOKEN AND NOT ALL OF THEM. The two failure directions are not symmetric. Collapsing two different organizations onto one identity suppresses comparisons between their records, so duplicates simply remain in the Pod: visible, and recoverable later. Splitting ONE organization across two identities lets records the organization deliberately kept apart be compared and merged, which destroys content. So the normalization is deliberately biased toward collapsing, and regional and specialty qualifiers ('... Washington and Montana', '... Cardiology') are dropped rather than being allowed to split a system. WHAT IT IS NOT. It is not a registry key and does not attempt to resolve two organizations that name themselves differently in different documents with no shared token; it canonicalizes what the documents themselves state. It is not a display value: show clinical:sourceEHR. It is not the transport: that is cascade:sourceSystem."""@en ; rdfs:domain owl:Thing ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.5" . cascade:reconciliationStatus a owl:ObjectProperty ; rdfs:label "Reconciliation Status"@en ; rdfs:comment "The outcome of reconciliation for this record. Value is one of the cascade:ReconciliationStatus named individuals: cascade:Canonical, cascade:Merged, cascade:ConflictResolved, cascade:UnresolvedConflict."@en ; rdfs:domain owl:Thing ; rdfs:range cascade:ReconciliationStatus . cascade:ReconciliationStatus a owl:Class ; rdfs:label "Reconciliation Status"@en ; rdfs:comment "Closed enumeration of possible reconciliation outcomes."@en . cascade:Canonical a owl:NamedIndividual, cascade:ReconciliationStatus ; rdfs:label "Canonical"@en ; rdfs:comment "Record is unique — no matching record found across other source systems."@en . cascade:Merged a owl:NamedIndividual, cascade:ReconciliationStatus ; rdfs:label "Merged"@en ; rdfs:comment "Record was deduplicated from two or more identical or near-identical source records."@en . cascade:ConflictResolved a owl:NamedIndividual, cascade:ReconciliationStatus ; rdfs:label "Conflict Resolved"@en ; rdfs:comment "Conflicting values across source records were resolved automatically using a trust-priority or merge strategy."@en . cascade:UnresolvedConflict a owl:NamedIndividual, cascade:ReconciliationStatus ; rdfs:label "Unresolved Conflict"@en ; rdfs:comment "Conflicting values could not be automatically resolved (trust scores too similar). Requires manual clinical review."@en . cascade:WithinSourceDuplicate a owl:NamedIndividual, cascade:ReconciliationStatus ; rdfs:label "Within-Source Duplicate"@en ; rdfs:comment "Record is a duplicate of another record within the same source system (e.g. same condition recorded across multiple clinical encounters). Suppressed from cross-system reconciliation output during Pass 1 deduplication."@en . cascade:mergedFrom a owl:ObjectProperty ; rdfs:label "Merged From"@en ; rdfs:comment "Links a reconciled record to the source record URIs that were merged into it. Used together with prov:wasDerivedFrom for semantic interoperability."@en ; rdfs:domain owl:Thing ; rdfs:range owl:Thing ; rdfs:subPropertyOf prov:wasDerivedFrom . cascade:conflictResolution a owl:ObjectProperty ; rdfs:label "Conflict Resolution Strategy"@en ; rdfs:comment "The strategy used to resolve a conflict. Value is one of the cascade:ConflictResolutionStrategy named individuals: cascade:TrustPriority, cascade:MergeValues, cascade:FlagUnresolved."@en ; rdfs:domain owl:Thing ; rdfs:range cascade:ConflictResolutionStrategy . cascade:ConflictResolutionStrategy a owl:Class ; rdfs:label "Conflict Resolution Strategy"@en ; rdfs:comment "Closed enumeration of reconciliation resolution strategies."@en . cascade:TrustPriority a owl:NamedIndividual, cascade:ConflictResolutionStrategy ; rdfs:label "Trust Priority"@en ; rdfs:comment "The record from the highest-trust source system is selected as canonical."@en . cascade:MergeValues a owl:NamedIndividual, cascade:ConflictResolutionStrategy ; rdfs:label "Merge Values"@en ; rdfs:comment "The canonical record is built by taking the winner's fields and filling any gaps from lower-trust sources."@en . cascade:FlagUnresolved a owl:NamedIndividual, cascade:ConflictResolutionStrategy ; rdfs:label "Flag Unresolved"@en ; rdfs:comment "The conflict could not be automatically resolved. Record is tagged cascade:UnresolvedConflict for manual clinical review."@en . cascade:conflictField a owl:ObjectProperty ; rdfs:label "Conflict Field"@en ; rdfs:comment "The predicate URI on which a conflict was detected during reconciliation (e.g. health:status, health:resultValue). Stored as a URI reference, not a prefixed name string."@en ; rdfs:domain owl:Thing ; rdfs:range rdfs:Resource . cascade:conflictDescription a owl:DatatypeProperty ; rdfs:label "Conflict Description"@en ; rdfs:comment "Removed v2.7. Was informational prose describing conflicting values per source system. Replaced by cascade:conflictDetail structured blank node (v2.5), which uses rdfs:comment on the blank node for human readability."@en ; owl:deprecated true ; rdfs:domain owl:Thing ; rdfs:range xsd:string . cascade:conflictDetail a owl:ObjectProperty ; rdfs:label "Conflict Detail"@en ; rdfs:comment "Links a reconciled record to a structured cascade:ConflictDetail blank node describing the per-system values that conflicted. The blank node carries an rdfs:comment with a human-readable summary."@en ; rdfs:domain owl:Thing ; rdfs:range cascade:ConflictDetail . cascade:ConflictDetail a owl:Class ; rdfs:label "Conflict Detail"@en ; rdfs:comment "A structured representation of conflicting values across source systems for a single property. Used as a blank node linked from cascade:conflictDetail."@en . cascade:conflictFor a owl:ObjectProperty ; rdfs:label "Conflict For"@en ; rdfs:comment "The predicate URI on which the conflict was detected (e.g. ). Links a cascade:ConflictDetail to the conflicting property."@en ; rdfs:domain cascade:ConflictDetail ; rdfs:range rdfs:Resource . cascade:systemValue a owl:ObjectProperty ; rdfs:label "System Value"@en ; rdfs:comment "Links a cascade:ConflictDetail to a blank node representing one source system's value for the conflicting property. Each participating source system has its own cascade:systemValue blank node."@en ; rdfs:domain cascade:ConflictDetail . cascade:forSystem a owl:DatatypeProperty ; rdfs:label "For System"@en ; rdfs:comment "The name of the source EHR system (e.g. 'primary-care', 'specialist') within a cascade:systemValue blank node."@en ; rdfs:domain cascade:ConflictDetail ; rdfs:range xsd:string . # ============================================================================ # Data Absence (v3.6) # ============================================================================ # WHY THIS EXISTS. A source document distinguishes several reasons a value is # missing, and every one of them currently arrives in a Pod as the same thing: # nothing. A C-CDA ("we will know this later"), a # ("nobody asked") and a # ("we asked, the patient did not know") are three different clinical facts, and # an importer that drops all three writes one indistinguishable blank. A reader # then cannot tell a test that was never ordered from one whose result is still # pending, and re-asking is the only recovery. # # The distinction is already ratified twice over, so none of it is invented # here: HL7 v3 NullFlavor (http://terminology.hl7.org/CodeSystem/v3-NullFlavor, # OID 2.16.840.1.113883.5.1008) is what C-CDA writes, and FHIR # data-absent-reason # (http://terminology.hl7.org/CodeSystem/data-absent-reason, value set # http://hl7.org/fhir/ValueSet/data-absent-reason) is the harmonized flat set # FHIR R4 binds Observation.dataAbsentReason to. # # Cascade binds to data-absent-reason, for three reasons stated here so the # choice is not re-litigated: (1) it is already the code system this vocabulary # cites — health:interpretation and clinical:interpretation have accepted its # "unknown" code since health v2.6 / clinical v1.14, so binding elsewhere would # put two absence vocabularies in one record; (2) its 15 codes are all # selectable, whereas v3-NullFlavor marks NI, UNK, OTH and INV abstract, and # this repository's own Validation Profile treats abstract concepts as hierarchy # nodes rather than values; (3) FHIR is the transport Cascade converts from on # both paths. The C-CDA importer maps nullFlavor to data-absent-reason on the # way in; the mapping is stated on the property below. cascade:dataAbsentReason a owl:DatatypeProperty ; rdfs:label "Data Absent Reason"@en ; rdfs:comment """Why this record's primary value is absent. Semantics are exactly FHIR R4 Observation.dataAbsentReason (https://hl7.org/fhir/R4/observation-definitions.html#Observation.dataAbsentReason): it explains the absence of the record's VALUE, and it is meaningful only when that value is in fact absent. A record that carries a value MUST NOT also carry this property. VALUE SET. One of the 15 codes of http://terminology.hl7.org/CodeSystem/data-absent-reason, as gathered by the value set http://hl7.org/fhir/ValueSet/data-absent-reason: unknown, asked-unknown, temp-unknown, not-asked, asked-declined, masked, not-applicable, unsupported, as-text, error, not-a-number, negative-infinity, positive-infinity, not-performed, not-permitted. MAPPING FROM HL7 v3 NullFlavor, which is what a C-CDA document writes (http://terminology.hl7.org/CodeSystem/v3-NullFlavor, OID 2.16.840.1.113883.5.1008). An importer reading a nullFlavor attribute MUST map it as follows and MUST NOT write the raw nullFlavor code into this property: UNK -> unknown a proper value applies but is not known ASKU -> asked-unknown the source was asked and did not know NASK -> not-asked the information was never sought NAV -> temp-unknown not available now, expected later NAVU -> unknown not available, with no expectation of later MSK -> masked withheld for security or privacy NA -> not-applicable known to have no proper value OTH -> unsupported the real value is outside the permitted value domain NI -> unknown no information, and no reason given NINF -> negative-infinity PINF -> positive-infinity Any other nullFlavor maps to "unknown", which is the code that asserts only that a value was expected and is missing. WHAT THIS IS NOT. It is not an interpretation, and it is not a substitute for one: an absent interpretation on a record whose value IS present is recorded on the interpretation property itself, whose value set accepts the same data-absent-reason codes. It is not a validation finding: absence of this property means only that the producer said nothing about why a value is missing."""@en ; rdfs:domain owl:Thing ; rdfs:range xsd:string ; rdfs:seeAlso ; rdfs:seeAlso ; owl:versionInfo "Added in core v3.6" . # ============================================================================ # Alignment with W3C PROV-O # ============================================================================ # Cascade Protocol extends PROV-O for provenance tracking # All health data entities are prov:Entity instances # creatorWebID links to prov:Agent (the user) # Generation activities tracked via prov:wasGeneratedBy # Example usage: # <#pots-check-123> a pots:POTSCheckResult ; # cascade:schemaVersion "1.2" ; # cascade:dataProvenance cascade:ConsumerGenerated ; # prov:wasAttributedTo ; # prov:wasGeneratedBy [ # a prov:Activity ; # prov:wasAssociatedWith ; # prov:startedAtTime "2025-10-31T10:00:00Z"^^xsd:dateTime # ] . # ============================================================================ # Changelog # # Version 3.6 (2026-08-14) # - Added cascade:dataAbsentReason (owl:DatatypeProperty, domain owl:Thing, # range xsd:string): why a record's primary VALUE is absent, bound to the 15 # codes of http://terminology.hl7.org/CodeSystem/data-absent-reason. Semantics # are exactly FHIR R4 Observation.dataAbsentReason. The nullFlavor-to- # data-absent-reason mapping a C-CDA importer must implement is stated on the # property, so the four distinctions a real document draws (UNK, NAV, NASK, # ASKU) stop collapsing into one indistinguishable blank on import. Nothing is # invented: both code systems are ratified and cited by canonical URL. # Binding to data-absent-reason rather than to v3-NullFlavor is deliberate and # its three reasons are recorded above the property. # - Stated the CANONICAL FORM of a multi-valued identity input on # cascade:cascadeUri. Vocabulary-wise this file is unchanged by it: no term is # added, and the statement constrains implementations rather than data. It is # here because health v2.6 and clinical v1.14 made four code fields 0..* and # an identifier hashed over an unordered field is not stable until the field # has a canonical form. The rule is dedupe, sort by code point, join with a # fixed separator, and a one-element sequence canonicalizes to the bare # scalar, so no identifier written before those fields became repeatable # moves. The three invariants (order independence, scalar agreement, # duplicate independence) are normative independently of the separator, which # is what lets an implementation that already ships a different separator # comply without re-minting. The scope limit is as load-bearing as the rule: # it applies to inputs that are SETS and must never be applied to an input # whose source order carries meaning, because sorting there merges records the # source deliberately distinguished. # - COMPATIBILITY. Both changes are additive. cascade:DataAbsentReasonShape # targets sh:targetSubjectsOf, so it evaluates only records that CARRY the new # property and every pod written before v3.6 validates exactly as it did. No # class or property was removed, renamed or deprecated. # # Version 3.5 (2026-08-09) # - Added cascade:sourceIdentity (owl:DatatypeProperty, range xsd:string): the # ORIGIN axis. A canonical, transport-independent identity for the # organization a record came from, so that a FHIR export and a C-CDA document # of one health system agree. The full value form and the slug normalization # both transports must implement are stated on the property itself; the short # version is a scheme-prefixed token, org: / ns: / transport:, where # transport: is an honestly-labelled last resort and NOT an origin claim. # - Stated the three source axes explicitly in this file, because two of them # were already here and being used interchangeably: ORIGIN # (cascade:sourceIdentity, new), LABEL (clinical:sourceEHR, unchanged, a # display value), INGESTION (cascade:sourceSystem, the import batch). # cascade:sourceSystem's comment now says what it is NOT: it records how and # when data arrived, never where it came from, and it must not be used as a # reconciliation key. One batch commonly carries several organizations, and # one organization commonly arrives in many batches; a reconciler keyed on it # compares nothing on a single-batch Pod and splits one system across two # rows on a mixed one. No property was removed or renamed and no emitted # value changes meaning, so existing pods keep reading correctly. # - COMPATIBILITY. Absence of cascade:sourceIdentity is NOT a validation # finding at any severity in this version. The shape that constrains it # (cascade:SourceIdentityShape) targets sh:targetSubjectsOf # cascade:sourceIdentity, so it evaluates only records that CARRY the # property: every pod written before v3.5 continues to validate exactly as it # did. The alternative considered was sh:minCount 1 at sh:Warning on each # record shape, which would have turned every previously-valid pod amber on # upgrade for a property no producer had yet had the chance to write, and # would have taught readers to ignore the warning. The ratchet path is # deliberate and stated here so the next step is not a judgement call: once # the reference producers emit the property, add sh:minCount 1 at sh:Warning # to the record shapes in health: and clinical:, and only after a release in # which that warning is observably absent from conforming output, raise it to # sh:Violation. Each step is its own vocabulary version. # # Version 3.4 (2026-08-03) # - Defined the pod export manifest vocabulary. 32 cascade: terms appearing in # a conforming pod had no definition in this file at all; all 32 are now # defined, so a manifest is describable rather than only parseable. # - cascade:ExportManifest is modelled as rdfs:subClassOf dcat:Dataset (DCAT 3, # https://www.w3.org/TR/vocab-dcat-3/). A pod export is a dataset with a # title, a description, a creation date and a publisher, which is what DCAT # already standardises; inventing a parallel set of terms for it would have # been novelty for its own sake. # - cascade:RecordSummary is modelled as rdfs:subClassOf void:Dataset (VoID, # https://www.w3.org/TR/void/). Its ~15 per-domain count properties are # declared rdfs:subPropertyOf void:entities and each is paired with the # void:class it counts, so a VoID-aware consumer can read Cascade record # counts with no Cascade-specific code. The Cascade-specific spellings are # retained because they are what is emitted. # - cascade:InteractionScenario is kept as genuinely novel. It describes a # cross-provenance clinical interaction that an agent must correlate across # several resources; no ratified vocabulary covers it, so it is defined here # rather than bent onto something that does not fit. # - Also defined the reading-level terms cascade:date, cascade:sampleCount and # cascade:loincCode, which are emitted on every history-container entry and # are constrained by the health v2.5 daily-snapshot shapes. # # ============================================================================ # v3.2 (2026-05-06): Forward reference closure for advisory applier. # Added cascade:appliedTriplesCount (DatatypeProperty, range xsd:nonNegativeInteger, # domain cascade:AdvisoryApplicationActivity). Records the number of triples # inserted into the pod by a single advisory application — auditable post-hoc # verification of CAP profile constraint C5 (≤ 64 inserted triples per match). # The Phase 4 advisory applier (cascade-cli TASK-4.5) was already emitting this # property as a documented forward reference; v3.2 retroactively declares it. # v3.1 (2026-05-05): Genomics & Advisory provenance (TASK-0.0). # Added cascade:AdvisoryApplicationActivity (prov:Activity subclass) for # recording application of a Cascade Advisory Patch to a pod; advisory # and matched record are linked via prov:used. # Added cascade:AIGenerationActivity (prov:Activity subclass), sibling of # cascade:AIExtractionActivity, for LLM-generated narrative chunks # (e.g., checkup:VariantNarrative). Reuses extractionModel, # extractionConfidence, sourceNarrativeSection, requiresUserReview from # AIExtractionActivity; adds cascade:promptVersion, cascade:generationTemperature. # Added cascade:trigger ObjectProperty (domain AIGenerationActivity, range # cascade:GenerationTrigger) with three named individuals: # cascade:InitialGeneration, cascade:RegenerationAfterReclassification, # cascade:AudienceRetargeting. Decision: a single AIGenerationActivity class # with a trigger property is preferred over multiple subclasses (e.g., a # separate AIRegenerationActivity) — avoids over-modeling. # v3.0 (2026-03-28): AI extraction provenance + abstract HealthRecord base class. # Added cascade:HealthRecord abstract base class for all patient health records # (enables cross-domain consent scoping and pod path policies). # Added cascade:AIExtractionActivity (prov:Activity subclass) — distinct from # existing cascade:AIExtracted DataProvenance marker — with properties: # extractionConfidence, extractionModel, sourceNarrativeSection, requiresUserReview, # requiresLLMExtraction. # Added cascade:AIDiscardedExtraction for low-confidence extraction audit log # (confidence < 0.50), with properties: discardedEntityType, discardedFrom, # discardedAt, discardConfidence. # Added cascade:SocialHistoryConsent named individual for 42 CFR Part 2 compliant # social history consent scoping. # v2.9 (2026-03-27): EHR Import Reconciliation (P1-C) — Added conflict resolution # persistence classes: cascade:UserResolution (patient's recorded decision for # resolving a data conflict) and cascade:PendingConflict (unresolved conflict # awaiting user resolution). Added conflict resolution properties: conflictId, # resolution, keptRecord, discardedRecords, userNote, candidateRecords, # detectedAt, autoResolved. Added document provenance properties: documentType, # sectionCode, sourceRecordId. # v2.8 (2026-03-12): Layer Promotion Status vocabulary for FHIR round-trip support. # Added cascade:LayerPromotionStatusValue class, cascade:layerPromotionStatus # ObjectProperty, cascade:FullyMapped and cascade:PendingLayerTwoPromotion named # individuals. Added cascade:fhirJson (lossless FHIR JSON passthrough), # cascade:fhirResourceType, cascade:sourceRecordDate properties. # v2.7 (2026-03-12): Removed cascade:conflictDescription (deprecated since v2.5). # cascade:conflictDetail blank nodes now carry an rdfs:comment with a human-readable # summary of the conflicting values, replacing the separate prose property. # v2.6 (2026-03-11): Within-source deduplication vocabulary. Added # cascade:WithinSourceDuplicate named individual for cascade:ReconciliationStatus. # Represents encounter-instance duplicate records suppressed during Pass 1 # within-source deduplication. Phase 6 of reconciliation pipeline. # v2.5 (2026-03-11): Structured conflict representation. Added cascade:ConflictDetail # class and 4 supporting properties: cascade:conflictDetail (ObjectProperty, # links record to ConflictDetail blank node), cascade:conflictFor (ObjectProperty, # the conflicting predicate URI), cascade:systemValue (ObjectProperty, per-system # value blank node), cascade:forSystem (DatatypeProperty, system name string). # Deprecated cascade:conflictDescription (informational prose, replaced by # cascade:conflictDetail structured blank node). Phase 5 of reconciliation pipeline. # v2.4 (2026-03-11): Added reconciliation vocabulary for multi-system EHR data # merging. Properties: cascade:sourceSystem (EHR origin tag, xsd:string), # cascade:reconciliationStatus (ObjectProperty, range cascade:ReconciliationStatus), # cascade:mergedFrom (ObjectProperty, subPropertyOf prov:wasDerivedFrom), # cascade:conflictResolution (ObjectProperty, range cascade:ConflictResolutionStrategy), # cascade:conflictField (ObjectProperty, range rdfs:Resource, stores full predicate URI), # cascade:conflictDescription (DatatypeProperty, informational prose). # Declared named individuals: cascade:Canonical, cascade:Merged, # cascade:ConflictResolved, cascade:UnresolvedConflict (ReconciliationStatus); # cascade:TrustPriority, cascade:MergeValues, cascade:FlagUnresolved # (ConflictResolutionStrategy). Note: cascade:mergedSources (dropped — # redundant with mergedFrom+sourceSystem) and cascade:conflictValues # (renamed to cascade:conflictDescription) were in the initial design but # corrected before the TTL was finalized. Supports CLI v0.2.5 reconcile command. # v2.3 (2026-02-22): Added 2 provenance detail properties for record-level # attribution: cascade:sourceOrganization (organization that provided a # record, populated from FHIR meta.source or contained Organization # reference during clinical import) and cascade:sourceDeviceName (device # or app that recorded a measurement, populated from HealthKit HKSource # contributing app/device name). Used across all 11 CheckupShared record # models for Health Map provenance display. Part of Health Map v3 # Pre-Phase Track A (Task A4). # v2.2 (2026-02-18): FHIR-aligned Address expansion and PatientProfile # completeness (Phase 3.5 ONT1 + ONT2). # ONT1: Added 9 FHIR Address.* aligned properties (addressUse, addressType, # addressText, addressLine, addressCity, addressDistrict, addressState, # addressPostalCode, addressCountry). Existing simple properties (streetAddress, # city, state, postalCode, country) retained as aliases. Updated simple # property comments to note alias relationship. # ONT2: Added PatientProfile properties emitted by SDK serializer: # computedAge, profileId, pharmacyAddress, preferredLanguage. Added # EmergencyContact class with contactName, contactRelationship, contactPhone. # Added object properties: emergencyContact, advanceDirectives, # preferredPharmacy, address (blank-node linking variants used by # PatientProfileSerializer). # v2.1 (2026-02-17): Added cascade:addedInSchemaVersion annotation property # and cascade:cascadeUri datatype property. Previously dark vocabulary # emitted by CheckupSerializer (supplement section). Part of Schema # Refactoring Plan Phase 3 (PF1-R). # v1.0 (2025-10-31): Initial release. Schema versioning, data provenance # (ConsumerGenerated / ClinicalGenerated), identity (creatorWebID), # W3C PROV-O alignment. # v1.1 (2026-02-01): DataSource class — device/app/system identity with # reliability tiers (clinical, medical_device, consumer_device, # self_reported), bundle identifier, and metric coverage. Subclasses # prov:Agent. Reconciliation strategy deferred to post-MVP. # v2.0 (2026-02-17): Patient demographics and identity classes. Added # cascade:PatientProfile class with 12 properties (dateOfBirth, # genderIdentity, biologicalSex, ageGroup, emergencyContactName, # emergencyContactRelationship, emergencyContactPhone, hasAddress, # maritalStatus, raceEthnicity, hasPreferredPharmacy, # hasAdvanceDirectives). Added 4 supporting classes: Address (5 properties), # PharmacyInfo (2 properties + reuses hasAddress), AdvanceDirectives # (4 properties). Designed as Solid WebID Profile extension with # two-document model (foaf:Agent card.ttl + cascade:PatientProfile # health.ttl linked via rdfs:seeAlso). Added foaf: and vcard: prefix # declarations. Part of Schema Refactoring Plan Phase 3 (WS6a). # v1.3 (2026-02-17): Added cascade:subConcern annotation property for # classifying vocabulary classes by functional role (summary, intake, # analytics, identity, app-config, structural, deprecated). Added shared # preference properties: notificationsEnabled, preferredUnits. Part of # Schema Refactoring Plan Phase 1 (WS2 + WS5). # v1.2 (2026-02-10): Promoted ConsumerGenerated and ClinicalGenerated from # OWL individuals to owl:Class subclasses of DataProvenance. Added 6 # DataProvenance subclasses: DeviceGenerated, SelfReported, # ConsumerWellness (under ConsumerGenerated); EHRVerified, ScannedDocument, # AIExtracted (under ClinicalGenerated). Aligns with SDK DataProvenance # enum (6 cases). Backward-compatible: existing data using # cascade:ConsumerGenerated/ClinicalGenerated remains valid. # ============================================================================ # Layer Promotion Status (FHIR Conversion) # Added in v2.8 — supports lossless FHIR ↔ Cascade round-trip conversion # ============================================================================ cascade:LayerPromotionStatusValue a owl:Class ; rdfs:label "Layer Promotion Status Value"@en ; rdfs:comment "Closed enumeration of Layer 2 promotion states for converted FHIR records."@en . cascade:layerPromotionStatus a owl:ObjectProperty ; rdfs:label "Layer Promotion Status"@en ; rdfs:comment "Indicates whether a record has been fully mapped to Cascade Layer 2 vocabulary or is preserved as a Layer 1 FHIR passthrough pending future mapping."@en ; rdfs:range cascade:LayerPromotionStatusValue . cascade:FullyMapped a owl:NamedIndividual, cascade:LayerPromotionStatusValue ; rdfs:label "Fully Mapped"@en ; rdfs:comment "Record has been fully mapped to Cascade Layer 2 vocabulary."@en . cascade:PendingLayerTwoPromotion a owl:NamedIndividual, cascade:LayerPromotionStatusValue ; rdfs:label "Pending Layer 2 Promotion"@en ; rdfs:comment "Record is preserved as Layer 1 FHIR passthrough. Full Layer 2 vocabulary mapping has not yet been defined. Original FHIR JSON is retained in cascade:fhirJson for round-trip export."@en . cascade:fhirJson a owl:DatatypeProperty ; rdfs:label "Original FHIR JSON"@en ; rdfs:comment "Preserves the complete original FHIR resource JSON for Layer 1 passthrough records. Always stored as plain xsd:string (FHIR JSON is always valid UTF-8). Used for lossless round-trip export. Self-contained — survives reconciliation without sidecar file dependencies."@en ; rdfs:range xsd:string . cascade:fhirResourceType a owl:DatatypeProperty ; rdfs:label "FHIR Resource Type"@en ; rdfs:comment "FHIR R4 resourceType of the source record. Populated on Layer 1 passthrough records."@en ; rdfs:range xsd:string . cascade:sourceRecordDate a owl:DatatypeProperty ; rdfs:label "Source Record Date"@en ; rdfs:comment "Minimal date projection triple for Layer 1 passthrough records. Copied from effectiveDateTime, date, period.start, or created field if present."@en ; rdfs:range xsd:dateTime . # ============================================================================ # Conflict Resolution (v2.9) # ============================================================================ ### Conflict Resolution Classes cascade:UserResolution a owl:Class ; rdfs:label "User Resolution"@en ; rdfs:comment "A patient's recorded decision for resolving a data conflict between two source records."@en ; owl:versionInfo "Added in core v2.9" . cascade:PendingConflict a owl:Class ; rdfs:label "Pending Conflict"@en ; rdfs:comment "An unresolved conflict between two or more records from different sources, awaiting user resolution."@en ; owl:versionInfo "Added in core v2.9" . ### Conflict Resolution Properties cascade:conflictId a owl:DatatypeProperty ; rdfs:label "Conflict ID"@en ; rdfs:comment "A deterministic string identifier for a conflict, derived from the record type and identity fields. Stable across re-imports."@en ; rdfs:domain [ owl:unionOf (cascade:UserResolution cascade:PendingConflict) ] ; rdfs:range xsd:string . cascade:resolution a owl:DatatypeProperty ; rdfs:label "Resolution"@en ; rdfs:comment "The user's resolution decision. One of: kept-source-a, kept-source-b, kept-both, manual-edit."@en ; rdfs:domain cascade:UserResolution ; rdfs:range xsd:string . cascade:keptRecord a owl:ObjectProperty ; rdfs:label "Kept Record"@en ; rdfs:comment "The record that was kept as the canonical version after conflict resolution."@en ; rdfs:domain cascade:UserResolution . cascade:discardedRecords a owl:ObjectProperty ; rdfs:label "Discarded Records"@en ; rdfs:comment "Records that were discarded during conflict resolution."@en ; rdfs:domain cascade:UserResolution . cascade:userNote a owl:DatatypeProperty ; rdfs:label "User Note"@en ; rdfs:comment "Optional free-text note from the patient about their resolution decision."@en ; rdfs:domain cascade:UserResolution ; rdfs:range xsd:string . cascade:candidateRecords a owl:ObjectProperty ; rdfs:label "Candidate Records"@en ; rdfs:comment "The conflicting records in a PendingConflict, awaiting user resolution."@en ; rdfs:domain cascade:PendingConflict . cascade:detectedAt a owl:DatatypeProperty ; rdfs:label "Detected At"@en ; rdfs:comment "When the conflict was first detected during import."@en ; rdfs:domain cascade:PendingConflict ; rdfs:range xsd:dateTime . cascade:autoResolved a owl:DatatypeProperty ; rdfs:label "Auto Resolved"@en ; rdfs:comment "True when a conflict was automatically resolved by applying a stored user resolution preference."@en ; rdfs:range xsd:boolean . ### Document Provenance Properties cascade:documentType a owl:DatatypeProperty ; rdfs:label "Document Type"@en ; rdfs:comment "The type of clinical document. Values: summarization, progress-note, discharge-summary, consultation-note, operative-note, history-physical."@en ; rdfs:range xsd:string . cascade:sectionCode a owl:DatatypeProperty ; rdfs:label "Section Code"@en ; rdfs:comment "LOINC code of the originating C-CDA section for this record."@en ; rdfs:range xsd:string . cascade:sourceRecordId a owl:DatatypeProperty ; rdfs:label "Source Record ID"@en ; rdfs:comment "The original source system record identifier (e.g., C-CDA element value) preserved for provenance."@en ; rdfs:range xsd:string . # ============================================================================ # Abstract Health Record Base Class (v3.0) # ============================================================================ # Abstract base class for all patient health records (v3.0) cascade:HealthRecord a owl:Class ; rdfs:label "Health Record"@en ; rdfs:comment "Abstract base class for all patient health records. Both clinical:ClinicalRecord and clinical:SocialHistoryRecord subclass this. Enables cross-domain consent scoping and pod path policies."@en ; owl:versionInfo "Added in core v3.0" . # ============================================================================ # AI Extraction Provenance (v3.0) # ============================================================================ # NOTE: cascade:AIExtracted (DataProvenance subclass) already exists above. # The following classes are DISTINCT provenance infrastructure for the P5.3 # LLM extraction pipeline — they track activities and discard logs, not # the provenance classification of individual records. # AI Extraction Activity (v3.0) — DISTINCT from cascade:AIExtracted (existing DataProvenance marker) cascade:AIExtractionActivity a owl:Class ; rdfs:subClassOf prov:Activity ; rdfs:label "AI Extraction Activity"@en ; rdfs:comment "An LLM-powered extraction activity that produced one or more clinical records from unstructured narrative text. Link from record to activity via prov:wasGeneratedBy. The record itself carries cascade:dataProvenance cascade:AIExtracted (existing DataProvenance marker)."@en ; owl:versionInfo "Added in core v3.0" . cascade:extractionConfidence a owl:DatatypeProperty ; rdfs:domain cascade:AIExtractionActivity ; rdfs:range xsd:decimal ; rdfs:comment "Confidence score 0.0-1.0 from the extraction model."@en . cascade:extractionModel a owl:DatatypeProperty ; rdfs:domain cascade:AIExtractionActivity ; rdfs:range xsd:string ; rdfs:comment "Model identifier used for extraction, e.g. 'qwen3.5-4b-q4_k_m'."@en . cascade:sourceNarrativeSection a owl:DatatypeProperty ; rdfs:domain cascade:AIExtractionActivity ; rdfs:range xsd:string ; rdfs:comment "The C-CDA section name from which the narrative text was extracted (e.g. 'medications', 'problems')."@en . cascade:requiresUserReview a owl:DatatypeProperty ; rdfs:domain cascade:AIExtractionActivity ; rdfs:range xsd:boolean ; rdfs:comment "True if extraction confidence < 0.85 and record should enter the patient review queue."@en . cascade:requiresLLMExtraction a owl:DatatypeProperty ; rdfs:range xsd:boolean ; rdfs:comment "True if a C-CDA section contains only narrative text (no structured entries); signals the P5.3 LLM extraction pipeline."@en . # Discard log entry for low-confidence extractions cascade:AIDiscardedExtraction a owl:Class ; rdfs:label "AI Discarded Extraction"@en ; rdfs:comment "A low-confidence extraction (confidence < 0.50) retained in the patient's pod at pod/analysis/discarded-extractions.ttl. Not auto-imported. Patient can review and rescue into the review queue."@en ; owl:versionInfo "Added in core v3.0" . cascade:discardedEntityType a owl:DatatypeProperty ; rdfs:domain cascade:AIDiscardedExtraction ; rdfs:range xsd:string ; rdfs:comment "Type of entity that was discarded: medication | condition | lab | socialHistory | vital."@en . cascade:discardedFrom a owl:DatatypeProperty ; rdfs:domain cascade:AIDiscardedExtraction ; rdfs:range xsd:string ; rdfs:comment "C-CDA section name from which the discarded extraction originated."@en . cascade:discardedAt a owl:DatatypeProperty ; rdfs:domain cascade:AIDiscardedExtraction ; rdfs:range xsd:dateTime ; rdfs:comment "Timestamp when the extraction was discarded by the pipeline."@en . cascade:discardConfidence a owl:DatatypeProperty ; rdfs:domain cascade:AIDiscardedExtraction ; rdfs:range xsd:decimal ; rdfs:comment "Confidence score that caused the discard (< 0.50)."@en . # ============================================================================ # Consent Scopes (v3.0) # ============================================================================ # Consent scope for social history records cascade:SocialHistoryConsent a owl:NamedIndividual ; rdfs:label "Social History Consent"@en ; rdfs:comment "Consent scope for social history data. Patient can share clinical/ pod without exposing social-history/. Subject to 42 CFR Part 2 for substance use disorder records."@en . # ============================================================================ # Genomics & Advisory Provenance (v3.1) # ============================================================================ # Two new prov:Activity subclasses supporting the Genomics & Advisory v0.1 # workstream: # - cascade:AdvisoryApplicationActivity records the application of a Cascade # Advisory Patch to a pod, joining the patch and the matched record into # a single auditable event. # - cascade:AIGenerationActivity is a sibling of cascade:AIExtractionActivity # for LLM-generated narrative content (e.g., checkup:VariantNarrative # chunks). It captures the model + prompt + temperature used to generate # the content and a cascade:trigger value distinguishing initial generation # from regeneration after reclassification or audience retargeting. # Advisory Application Activity (v3.1) — records application of an advisory patch cascade:AdvisoryApplicationActivity a owl:Class ; rdfs:subClassOf prov:Activity ; rdfs:label "Advisory Application Activity"@en ; rdfs:comment "An activity that applied a Cascade Advisory Patch to a pod. The activity joins to advisory provenance via prov:used and prov:used , recording both the patch source and the record it was matched against. Use prov:wasGeneratedBy on records produced or modified by the patch."@en ; owl:versionInfo "Added in core v3.1" . # AI Generation Activity (v3.1) — sibling of AIExtractionActivity for LLM-generated content cascade:AIGenerationActivity a owl:Class ; rdfs:subClassOf prov:Activity ; rdfs:label "AI Generation Activity"@en ; rdfs:comment "An LLM-powered generation activity that produced narrative or summary content (e.g., checkup:VariantNarrative chunks). Sibling of cascade:AIExtractionActivity: extraction pulls structured records out of unstructured text, generation produces new prose. Records the model identifier, prompt version, temperature, and a cascade:trigger value indicating why the generation ran. Link from generated content to activity via prov:wasGeneratedBy."@en ; owl:versionInfo "Added in core v3.1" . # AIGenerationActivity reuses the cascade:extractionModel, cascade:extractionConfidence, # cascade:sourceNarrativeSection, and cascade:requiresUserReview properties already # declared on AIExtractionActivity (extending their domain via the additions below). # It also introduces cascade:promptVersion, cascade:generationTemperature, and # cascade:trigger. cascade:promptVersion a owl:DatatypeProperty ; rdfs:label "Prompt Version"@en ; rdfs:domain cascade:AIGenerationActivity ; rdfs:range xsd:string ; rdfs:comment "Identifier of the prompt template version used for this generation, e.g. 'variant-narrative-v2.1'. Pair with cascade:extractionModel for full reproducibility."@en . cascade:generationTemperature a owl:DatatypeProperty ; rdfs:label "Generation Temperature"@en ; rdfs:domain cascade:AIGenerationActivity ; rdfs:range xsd:decimal ; rdfs:comment "Sampling temperature used during generation (typically 0.0-1.0). Recorded for reproducibility and to distinguish deterministic from creative outputs."@en . cascade:trigger a owl:ObjectProperty ; rdfs:label "Trigger"@en ; rdfs:domain cascade:AIGenerationActivity ; rdfs:range cascade:GenerationTrigger ; rdfs:comment "The reason this generation activity ran. One of the cascade:GenerationTrigger named individuals: cascade:InitialGeneration, cascade:RegenerationAfterReclassification, cascade:AudienceRetargeting."@en . cascade:GenerationTrigger a owl:Class ; rdfs:label "Generation Trigger"@en ; rdfs:comment "Closed enumeration of reasons an AIGenerationActivity may run."@en . cascade:InitialGeneration a owl:NamedIndividual, cascade:GenerationTrigger ; rdfs:label "Initial Generation"@en ; rdfs:comment "First-time generation of a narrative or summary chunk. No prior version existed."@en . cascade:RegenerationAfterReclassification a owl:NamedIndividual, cascade:GenerationTrigger ; rdfs:label "Regeneration After Reclassification"@en ; rdfs:comment "Regeneration triggered because an upstream classification (e.g., variant pathogenicity, condition status) changed and the prior narrative is now stale."@en . cascade:AudienceRetargeting a owl:NamedIndividual, cascade:GenerationTrigger ; rdfs:label "Audience Retargeting"@en ; rdfs:comment "Regeneration triggered to retarget existing content for a different audience (e.g., patient-facing vs. clinician-facing) without any underlying classification change."@en . # Applied Triples Count (v3.2) — auditable size of an advisory application cascade:appliedTriplesCount a owl:DatatypeProperty ; rdfs:label "Applied Triples Count"@en ; rdfs:domain cascade:AdvisoryApplicationActivity ; rdfs:range xsd:nonNegativeInteger ; rdfs:comment "Total number of triples the advisory's CAP body inserted into the pod for this single match. Lets a downstream auditor verify CAP profile constraint C5 (≤ 64 inserted triples per match) at activity-record-read time without re-parsing the patch body. Recorded by the cascade-cli applier (Phase 4 TASK-4.5) on every successful application."@en ; owl:versionInfo "Added in core v3.2" . # ============================================================================ # Cascade Workbench support (v3.3) — ungrounded-AI provenance + caregiver-proxy # ============================================================================ # AIAsserted (v3.3) — provenance leaf for content surfaced by a general-purpose # AI assistant in a patient-directed conversation. NOT clinically generated. cascade:AIAsserted a owl:Class ; rdfs:subClassOf cascade:ConsumerGenerated ; rdfs:label "AI-Asserted"@en ; rdfs:comment "Content surfaced by a general-purpose AI assistant in a patient-directed conversation (e.g. ChatGPT, Claude), NOT grounded in clinical sources. MUST be applied to evidence:Assertion instances imported from AI conversations so they can never be mistaken for clinically-extracted data (cascade:AIExtracted) or verified records (cascade:EHRVerified). Marks content that must be evidence-checked before any reliance."@en ; owl:versionInfo "Added in core v3.3" . # ProxyAgent (v3.3) — a person operating a patient's Pod on the patient's behalf # (e.g. a parent for a minor child). Closes the caregiver-proxy gap. cascade:ProxyAgent a owl:Class ; rdfs:subClassOf prov:Agent ; rdfs:label "Proxy Agent"@en ; rdfs:comment "A person operating a patient's Pod on the patient's behalf (e.g. a parent for a minor child, a caregiver for a dependent adult). Distinct from the patient (cascade:PatientProfile). Records who is acting and under what authority and scope."@en ; owl:versionInfo "Added in core v3.3" . cascade:actsForPatient a owl:ObjectProperty ; rdfs:subPropertyOf prov:actedOnBehalfOf ; rdfs:label "Acts For Patient"@en ; rdfs:comment "The patient (WebID) on whose behalf the proxy acts."@en ; rdfs:domain cascade:ProxyAgent ; rdfs:range rdfs:Resource ; owl:versionInfo "Added in core v3.3" . cascade:proxyWebID a owl:ObjectProperty ; rdfs:label "Proxy WebID"@en ; rdfs:domain cascade:ProxyAgent ; rdfs:range rdfs:Resource ; owl:versionInfo "Added in core v3.3" . cascade:proxyRelationship a owl:DatatypeProperty ; rdfs:label "Proxy Relationship"@en ; rdfs:comment "Relationship of the proxy to the patient. Reuses the cascade:contactRelationship value space: parent, guardian, caregiver, spouse, child, other."@en ; rdfs:domain cascade:ProxyAgent ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.3" . cascade:proxyScope a owl:DatatypeProperty ; rdfs:label "Proxy Scope"@en ; rdfs:comment "Authority scope, e.g. 'full', 'read-only', 'investigation-only'. Composable with cascade:consentScope (data-sensitivity), which it does not replace."@en ; rdfs:domain cascade:ProxyAgent ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.3" . cascade:proxyGrantedAt a owl:DatatypeProperty ; rdfs:label "Proxy Granted At"@en ; rdfs:domain cascade:ProxyAgent ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in core v3.3" . cascade:proxyRevokedAt a owl:DatatypeProperty ; rdfs:label "Proxy Revoked At"@en ; rdfs:domain cascade:ProxyAgent ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in core v3.3" . # ============================================================================ # Pod Export Manifest (v3.4) # ============================================================================ # # Every term in this section is emitted by a conforming pod export and none of # them was defined before v3.4. Defining them is additive: no serializer # changes and no manifest is rewritten. # # Modelling decisions, and the reasoning, because "we invented a term" is the # expensive default: # # cascade:ExportManifest -> rdfs:subClassOf dcat:Dataset # A pod export is a published dataset with a title, description, # creation date and publisher. DCAT 3 standardises exactly that and is a # W3C Recommendation. https://www.w3.org/TR/vocab-dcat-3/ # # cascade:RecordSummary -> rdfs:subClassOf void:Dataset # A record summary is a statistical description of a subset of a # dataset: how many instances of each class it contains. That is what # VoID is for. Each count property below is declared # rdfs:subPropertyOf void:entities and paired with the void:class it # counts, so a VoID-aware consumer gets the counts for free. # https://www.w3.org/TR/void/ # # cascade:InteractionScenario -> novel, deliberately # A cross-provenance clinical interaction that an agent must detect by # correlating resources of different provenance. No ratified vocabulary # models this. It stays Cascade-specific rather than being forced onto # something that does not fit. cascade:ExportManifest a owl:Class ; rdfs:label "Export Manifest"@en ; rdfs:comment "Provenance and completeness metadata for a complete pod export: when it was generated, which schema versions it uses, how many records of each kind it contains, and which provenance layers are represented. Consumers should read this before processing individual resources."@en ; rdfs:subClassOf dcat:Dataset ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso ; owl:versionInfo "Added in core v3.4; emitted since schema 1.3" . cascade:RecordSummary a owl:Class ; rdfs:label "Record Summary"@en ; rdfs:comment "Per-domain record counts for one partition of a pod export. Modelled on void:Dataset because it is a statistical description of a dataset subset; each count property is a subproperty of void:entities scoped to a particular void:class."@en ; rdfs:subClassOf void:Dataset ; rdfs:seeAlso ; owl:versionInfo "Added in core v3.4; emitted since schema 1.3" . cascade:InteractionScenario a owl:Class ; rdfs:label "Interaction Scenario"@en ; rdfs:comment "A clinically significant interaction that can only be detected by correlating resources of differing provenance, for example an EHR-prescribed drug against a self-reported supplement against a lab value. Cascade-specific: no ratified vocabulary models cross-provenance correlation as a first-class thing."@en ; rdfs:subClassOf prov:Entity ; owl:versionInfo "Added in core v3.4; emitted since schema 1.3" . # --- Manifest structure ----------------------------------------------------- cascade:patientProfileVersion a owl:DatatypeProperty ; rdfs:label "Patient Profile Version"@en ; rdfs:comment "Version of the patient profile structure used in this export, independent of cascade:schemaVersion."@en ; rdfs:domain cascade:ExportManifest ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . cascade:provenanceLayers a owl:ObjectProperty ; rdfs:label "Provenance Layers"@en ; rdfs:comment "Ordered list of the cascade:DataProvenance values represented anywhere in this export. Lets a consumer tell, before reading any resource, whether the export contains device data, EHR data, self-reported data, or a mixture."@en ; rdfs:domain cascade:ExportManifest ; rdfs:range rdf:List ; owl:versionInfo "Added in core v3.4" . cascade:clinicalSummary a owl:ObjectProperty ; rdfs:label "Clinical Summary"@en ; rdfs:comment "Record counts for the clinical partition of this export."@en ; rdfs:domain cascade:ExportManifest ; rdfs:range cascade:RecordSummary ; owl:versionInfo "Added in core v3.4" . cascade:wellnessSummary a owl:ObjectProperty ; rdfs:label "Wellness Summary"@en ; rdfs:comment "Record counts for the wellness partition of this export."@en ; rdfs:domain cascade:ExportManifest ; rdfs:range cascade:RecordSummary ; owl:versionInfo "Added in core v3.4" . cascade:deviceSources a owl:ObjectProperty ; rdfs:label "Device Sources"@en ; rdfs:comment "Ordered list of the devices that contributed data to this export, each a prov:Agent."@en ; rdfs:domain cascade:ExportManifest ; rdfs:range rdf:List ; owl:versionInfo "Added in core v3.4" . cascade:interactionScenarios a owl:ObjectProperty ; rdfs:label "Interaction Scenarios"@en ; rdfs:comment "Ordered list of cascade:InteractionScenario entries flagged in this export."@en ; rdfs:domain cascade:ExportManifest ; rdfs:range rdf:List ; owl:versionInfo "Added in core v3.4" . # --- Record summary --------------------------------------------------------- cascade:domain a owl:DatatypeProperty ; rdfs:label "Domain"@en ; rdfs:comment "Which partition of the export this summary describes, for example 'clinical' or 'wellness'."@en ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . cascade:notes a owl:DatatypeProperty ; rdfs:label "Notes"@en ; rdfs:comment "Free-text commentary on a summary or manifest section."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . # Counts. Each is a subproperty of void:entities paired with the void:class it # counts, so a VoID-aware consumer reads these without Cascade-specific code. cascade:conditionCount a owl:DatatypeProperty ; rdfs:label "Condition Count"@en ; rdfs:comment "Number of condition records in this partition."@en ; rdfs:subPropertyOf void:entities ; void:class ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:medicationCount a owl:DatatypeProperty ; rdfs:label "Medication Count"@en ; rdfs:comment "Number of medication records in this partition."@en ; rdfs:subPropertyOf void:entities ; void:class ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:allergyCount a owl:DatatypeProperty ; rdfs:label "Allergy Count"@en ; rdfs:comment "Number of allergy records in this partition."@en ; rdfs:subPropertyOf void:entities ; void:class ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:labResultCount a owl:DatatypeProperty ; rdfs:label "Lab Result Count"@en ; rdfs:comment "Number of laboratory result records in this partition."@en ; rdfs:subPropertyOf void:entities ; void:class ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:immunizationCount a owl:DatatypeProperty ; rdfs:label "Immunization Count"@en ; rdfs:comment "Number of immunization records in this partition."@en ; rdfs:subPropertyOf void:entities ; void:class ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:coverageCount a owl:DatatypeProperty ; rdfs:label "Coverage Count"@en ; rdfs:comment "Number of insurance coverage records in this partition."@en ; rdfs:subPropertyOf void:entities ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:supplementCount a owl:DatatypeProperty ; rdfs:label "Supplement Count"@en ; rdfs:comment "Number of supplement records in this partition."@en ; rdfs:subPropertyOf void:entities ; void:class ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . # Day counts. These count DAYS COVERED, not entities, so they are deliberately # NOT subproperties of void:entities: a 30-day heart rate history may hold many # more readings than 30. Conflating the two would make the VoID reading wrong. cascade:vitalSignDays a owl:DatatypeProperty ; rdfs:label "Vital Sign Days"@en ; rdfs:comment "Number of distinct days covered by vital sign records in this partition. A day count, not a record count."@en ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:heartRateDays a owl:DatatypeProperty ; rdfs:label "Heart Rate Days"@en ; rdfs:comment "Number of distinct days covered by heart rate history. A day count, not a record count."@en ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:bloodPressureDays a owl:DatatypeProperty ; rdfs:label "Blood Pressure Days"@en ; rdfs:comment "Number of distinct days covered by blood pressure history. A day count, not a record count."@en ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:activityDays a owl:DatatypeProperty ; rdfs:label "Activity Days"@en ; rdfs:comment "Number of distinct days covered by activity history. A day count, not a record count."@en ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:sleepDays a owl:DatatypeProperty ; rdfs:label "Sleep Days"@en ; rdfs:comment "Number of distinct nights covered by sleep history. A night count, not a record count."@en ; rdfs:domain cascade:RecordSummary ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . # --- Interaction scenario --------------------------------------------------- cascade:involvedResources a owl:ObjectProperty ; rdfs:label "Involved Resources"@en ; rdfs:comment "Ordered list of the pod resources an agent must read together to detect this interaction."@en ; rdfs:domain cascade:InteractionScenario ; rdfs:range rdf:List ; owl:versionInfo "Added in core v3.4" . cascade:severity a owl:DatatypeProperty ; rdfs:label "Severity"@en ; rdfs:comment "Severity of the flagged interaction: low, moderate, high, or critical."@en ; rdfs:domain cascade:InteractionScenario ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . cascade:requiresCrossProvenance a owl:DatatypeProperty ; rdfs:label "Requires Cross-Provenance"@en ; rdfs:comment "True when detecting this interaction requires correlating resources of different cascade:dataProvenance. The distinguishing property of this class: a single-source consumer cannot find it."@en ; rdfs:domain cascade:InteractionScenario ; rdfs:range xsd:boolean ; owl:versionInfo "Added in core v3.4" . # --- Device sources --------------------------------------------------------- cascade:sourceType a owl:DatatypeProperty ; rdfs:label "Source Type"@en ; rdfs:comment "Mechanism a reading arrived through, for example healthKit, bluetoothDevice, manualEntry. Describes the transport, NOT the trustworthiness of the data; that is cascade:dataProvenance."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . cascade:dataTypes a owl:DatatypeProperty ; rdfs:label "Data Types"@en ; rdfs:comment "Comma-separated list of the data types a device contributed, for example 'heartRate, activity, sleep'."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . cascade:version a owl:DatatypeProperty ; rdfs:label "Version"@en ; rdfs:comment "Version string of a software agent or generator recorded in provenance."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in core v3.4" . # ============================================================================ # Reading-level Terms (v3.4) # ============================================================================ # # Emitted on entries inside the health: history containers. Defined here rather # than in health: because they are not wellness-specific: any Cascade time # series entry carries a date and, where aggregated, a sample count. cascade:date a owl:DatatypeProperty ; rdfs:label "Date"@en ; rdfs:comment "Timestamp a reading or snapshot applies to. Note that health:date is a second, equivalent spelling emitted by a different serializer for the same purpose; readers of history containers must handle both."@en ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in core v3.4" . cascade:sampleCount a owl:DatatypeProperty ; rdfs:label "Sample Count"@en ; rdfs:comment "Number of underlying samples aggregated into a single reading. A daily resting heart rate of 68 derived from 142 samples is a stronger observation than one derived from 2, and consumers should be able to tell the difference."@en ; rdfs:range xsd:integer ; owl:versionInfo "Added in core v3.4" . cascade:loincCode a owl:ObjectProperty ; rdfs:label "LOINC Code"@en ; rdfs:comment "LOINC reference for an observation, as an IRI."@en ; rdfs:seeAlso ; owl:versionInfo "Added in core v3.4" . ## Ontology: health ## File: health.ttl @prefix health: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix prov: . @prefix ucum: . # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports ; dct:title "Cascade Protocol Health & Wellness Ontology"@en ; dct:description "Vocabulary for consumer-generated wellness observations from wearable devices and HealthKit. Maps Cascade wellness properties to established SNOMED CT and LOINC codes following the three-layer ontology architecture."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-01-29"^^xsd:date ; dct:modified "2026-08-14"^^xsd:date ; owl:versionInfo "2.7" ; rdfs:comment """Three-layer ontology for wellness data: Layer 1 (Established standards): SNOMED CT concept IDs + LOINC observation codes Layer 2 (Cascade health vocabulary): health: namespace properties linking to Layer 1 Layer 3 (Checkup summary): checkup: namespace patient-facing aggregations"""@en ; rdfs:seeAlso . # Changelog: # v2.7 (2026-08-14): Let a source's absence and a source's unrecognised code # both survive import. Additive: one new property, one widened value set, # and nothing that validated under v2.6 stops validating. # # 1. health:interpretation's value set is widened from the single # data-absent-reason code "unknown" to all 15 codes of # http://terminology.hl7.org/CodeSystem/data-absent-reason (value set # http://hl7.org/fhir/ValueSet/data-absent-reason). v2.6 already # accepted one code from that system for the case of a source that # carried no interpretation; accepting only one of the fifteen made # every reason for the absence identical. FHIR's own pattern for a # CodeableConcept element that is absent for a stated reason is a # data-absent-reason coding in that element, so this is the ratified # spelling rather than a Cascade convention. It is what lets a C-CDA # ("nobody asked") arrive # distinguishable from nullFlavor="ASKU" ("asked, did not know") and # nullFlavor="NAV" ("known later"); the nullFlavor mapping table is # stated once, on cascade:dataAbsentReason in core v3.6. # 2. NEW health:interpretationSourceCode: the source's verbatim # interpretation code, written ONLY when that code is a member of # neither bound value set. The alternative considered and rejected was # accepting the loss with a warning. A closed value set plus a lossy # importer means a laboratory's local flag is either dropped on the # floor or written into a field that then fails validation, and in both # cases the Pod can no longer answer what the source actually said. # Unconstrained by design: it is a verbatim carbon copy, not a code. # 3. NOT changed, and stated here so its absence is not read as an # oversight: no interpretation constraint was added to # health:DailyVitalReadingShape. health:interpretation's rdfs:domain is # health:LabResultRecord and health's only vital class, # health:DailyVitalReading, carries no interpretation predicate at all, # so a shape there would constrain a triple no conforming record can # write. The vital-sign interpretation binding this release adds lives # in clinical v1.15 on clinical:VitalSignShape, which is the vital # shape that actually exists. # # v2.6 (2026-08-08): Align the lab-result constraints to the ratified standards # they claim to follow. Shapes-only; no class or property is added, removed # or renamed, and nothing that validated under v2.5 stops validating. # # Motivation: real-world Epic FHIR exports fail validation on records that # are correct FHIR. Four constraints on health:LabResultRecordShape and # health:ConditionRecordShape were narrower than the resource definitions # the data is converted from. # # 1. health:interpretation was constrained to a five-member set Cascade # invented (normal/high/low/abnormal/critical). It is now the 49 # selectable codes of the HL7 v3 ObservationInterpretation code system, # http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation # (version 3.0.0) — the code system FHIR R4 binds # Observation.interpretation to — plus the data-absent-reason code # "unknown" (http://terminology.hl7.org/CodeSystem/data-absent-reason) # for the very common case of a source Observation that carried no # interpretation at all. The ten v2.5 words are retained so existing # data keeps validating; they are not recommended for new writes. # The eight abstract (notSelectable) concepts of the code system are # excluded: they are hierarchy nodes, not values. # 2. health:labCategory is now multi-valued. FHIR R4 Observation.category # is 0..* (https://hl7.org/fhir/R4/observation-definitions.html#Observation.category) # and real exports categorise one result several ways at once. # 3. health:testCode is now multi-valued, and # 4. health:icd10Code and health:snomedCode are now multi-valued, all three # because FHIR R4 CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). Dual coding # of a problem-list entry, and multiple LOINC codings of one test, are # the normal shape of EHR output. # # Deliberately NOT changed: no sh:pattern is introduced on any code # property. ICD-10-CM permits a letter in any character position (per the # ICD-10-CM Official Guidelines for Coding and Reporting, Section I.A.2: # "Characters for categories, subcategories and codes may be either a letter # or a number"), and SNOMED CT identifiers are 6-18 digit integers, so a # pattern would reject valid codes for no gain. Format stays with the code # system that defines it. # # v2.5 (2026-08-03): Clinical record classes. Five classes that serializers have # emitted since v1.3 but that this ontology never defined are now defined: # health:LabResultRecord, health:ConditionRecord, health:AllergyRecord, # health:ImmunizationRecord, health:FamilyHistoryRecord. Adds the 40 # properties they use (previously undefined vocabulary), 4 sleep-quality # named individuals, and 6 wellness container classes # (health:ActivityData, SleepData, HeartRateData, BloodPressureData, # HRVData, BodyMeasurements) as rdfs:subClassOf health:HealthProfile. # The subclass declarations make the existing rdfs:domain health:HealthProfile # on the eight history container properties true rather than contradicted, # and bring those containers under health:HealthProfileShape through # SHACL subclass-aware sh:targetClass. Adds a namespace-boundary note # explaining that health: vs clinical: is historical, not a provenance # signal. No serializer changes; every term here was already being emitted. # # v2.4 (2026-03-27): EHR Import Reconciliation (P1-C) — Added health:SocialHistoryRecord # class for social history observations (smoking, alcohol, exercise, occupation). # Added 4 properties: health:smokingStatus, health:alcoholUse, # health:exerciseFrequency, health:occupationalExposure. # # v2.3 (2026-02-28): BUG-006 — Added 6 Pattern A flat-property vocabulary terms # for VitalSignReading blank nodes: health:value (generic numeric value), # health:date (observation timestamp), health:sdnn (HRV SDNN measurement), # health:systolic and health:diastolic (blood pressure components), # health:walkingSteadinessLevel (enum ObjectProperty). Also added # health:WalkingSteadinessUnknown individual. These replace the Pattern B # fhir:Observation nesting in HealthProfileSerializer. # # v2.2 (2026-02-17): Added 2 container linking ObjectProperties: # health:hasHRVStatistics, health:hasBPStatistics. These link a wellness # profile or intake form to its statistical summary nodes. Previously dark # vocabulary emitted by CheckupSerializer. Part of Schema Refactoring # Plan Phase 3 (PF1-R). # # v2.1 (2026-02-17): Added health:SelfReport base class and 5 base properties # (reportDate, reportType, completionStatus, reportContext, reportNotes) # for patient-reported daily check-ins. (Phase 3, WS7) # # v2.0 (2026-02-18): Schema Refactoring WS3b — Migrated HealthProfileSerializer # from cascade: to health: namespace. Added 8 history container properties # (restingHeartRateHistory, walkingHeartRateHistory, hrvHistory, # bloodPressureHistory, vo2MaxHistory, bodyMassHistory, dailyActivityHistory, # dailySleepHistory) and 7 supporting classes (HealthProfile, DailyVitalReading, # HRVReading, BloodPressureReading, VitalSignReading, DailyActivitySnapshot, # DailySleepSnapshot). These properties capture time-series data emitted by # HealthProfileSerializer for 90-day, 30-day, and 7-day lookback windows. # # v1.9 (2026-02-17): Added health:bloodType property for ABO blood group and # Rh factor. Values: aPositive, aNegative, bPositive, bNegative, abPositive, # abNegative, oPositive, oNegative. Sourced from HealthKit # (HKCharacteristicType.bloodType) or manual entry. Previously dark vocabulary # emitted in QR payload. Part of Schema Refactoring Plan Phase 2.5 (PF4). # # v1.8 (2026-02-10): Added 6 body measurement properties with SNOMED/LOINC # annotations (bodyMass, bodyHeight, bodyMassIndex, bodyTemperature, # oxygenSaturation, bloodGlucose). Added health:BPStatistics class with # 10 properties for blood pressure statistical summaries. Added shared # temporal properties (periodStart, periodEnd). Completed HRVStatistics # property definitions (hrvSampleCount, hrvDaysCovered, hrvMean, hrvMedian, # hrvStdDev, hrvMin, hrvMax, hrvPercentile25, hrvPercentile75, # hrvTrendDirection). # # v1.7 (2026-02-03): Added health:VO2MaxStatistics and health:HRVStatistics # composite classes for statistical trend analysis. Added 9 new properties # for VO2 Max statistics: vo2Mean, vo2Min, vo2MaxValue, vo2SampleCount, # vo2DaysCovered, vo2TrendDirection, vo2PercentageChange, fitnessClassification, # and isSparseData. These support the dual-path UI pattern where rich statistics # are preferred when available, with fallback to single values. Part of the # VO2 Max Statistics Implementation Plan Phase 5 (Documentation). # # v1.6 (2026-02-02): Added health:trendPolarity annotation property to all # measurable wellness metrics. Defines whether an increase is generally # favorable (higher_is_better), unfavorable (lower_is_better), or # context-dependent (neutral). Used by presentation layers for trend arrow # color semantics. Part of Visit Prep Feature Plan Phase 1.5. # # v1.5 (2026-02-01): Added health:MetricTrend class for time-bounded wellness # trends with direction, magnitude, confidence, and baseline/current values. # Follows the pattern established by clinical:LabTestSeries but designed for # continuous daily observations from consumer devices (vs discrete clinical samples). # Shares trendDirection/trendConfidence vocabulary with clinical: for cross-domain # consistency. Added as part of Ontology Improvement Plan (Work Stream 2, Task 2.1). # # v1.4 (2026-01-29): Initial release. Wellness vital signs, activity, and sleep # properties with SNOMED CT and LOINC standard code mappings for all metrics. # Created as part of the Wellness Data Pipeline implementation (Tasks 1-7). # ============================================================================ # Layer 2: Wellness Observation Properties # ============================================================================ # Each property maps a cascade: wellness term to its SNOMED CT and LOINC equivalents. # These properties are used by HealthProfileSerializer to produce interoperable TTL. # ---------------------------------------------------------------------------- # Vital Signs — Cardiac # ---------------------------------------------------------------------------- health:restingHeartRate a owl:DatatypeProperty ; rdfs:label "Resting Heart Rate"@en ; rdfs:comment "Average resting heart rate from wearable device"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:364075005 ; # Heart rate health:loincCode loinc:40443-4 ; # Heart rate --resting health:unit "beats/min" ; health:ucumCode "/min" ; health:trendPolarity "lower_is_better" . health:walkingHeartRate a owl:DatatypeProperty ; rdfs:label "Walking Heart Rate"@en ; rdfs:comment "Average heart rate during walking from wearable device"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:364075005 ; # Heart rate health:loincCode loinc:89270-3 ; # Heart rate --W exercise health:unit "beats/min" ; health:ucumCode "/min" ; health:trendPolarity "lower_is_better" . health:heartRateVariability a owl:DatatypeProperty ; rdfs:label "Heart Rate Variability (SDNN)"@en ; rdfs:comment "Standard deviation of normal-to-normal R-R intervals (SDNN) from wearable device"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:80404004 ; # Heart rate variability health:loincCode loinc:80404-7 ; # R-R interval.standard deviation health:unit "ms" ; health:ucumCode "ms" ; health:trendPolarity "higher_is_better" . # ---------------------------------------------------------------------------- # Vital Signs — Cardiovascular # ---------------------------------------------------------------------------- health:bloodPressure a owl:ObjectProperty ; rdfs:label "Blood Pressure"@en ; rdfs:comment "Blood pressure panel observation with systolic and diastolic components"@en ; rdfs:domain fhir:Observation ; health:snomedCode sct:75367002 ; # Blood pressure health:loincCode loinc:85354-9 . # Blood pressure panel health:systolicBP a owl:DatatypeProperty ; rdfs:label "Systolic Blood Pressure"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:271649006 ; # Systolic blood pressure health:loincCode loinc:8480-6 ; # Systolic blood pressure health:unit "mmHg" ; health:ucumCode "mm[Hg]" ; health:trendPolarity "lower_is_better" . health:diastolicBP a owl:DatatypeProperty ; rdfs:label "Diastolic Blood Pressure"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:271650006 ; # Diastolic blood pressure health:loincCode loinc:8462-4 ; # Diastolic blood pressure health:unit "mmHg" ; health:ucumCode "mm[Hg]" ; health:trendPolarity "lower_is_better" . # ---------------------------------------------------------------------------- # Vital Signs — Respiratory & Fitness # ---------------------------------------------------------------------------- health:respiratoryRate a owl:DatatypeProperty ; rdfs:label "Respiratory Rate"@en ; rdfs:comment "Average respiratory rate from wearable device"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:86290005 ; # Respiratory rate health:loincCode loinc:9279-1 ; # Respiratory rate health:unit "breaths/min" ; health:ucumCode "/min" ; health:trendPolarity "neutral" . health:vo2Max a owl:DatatypeProperty ; rdfs:label "VO2 Max"@en ; rdfs:comment "Estimated maximal oxygen consumption (cardiorespiratory fitness)"@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:251880009 ; # Aerobic capacity health:loincCode loinc:60842-2 ; # VO2 max health:unit "mL/kg/min" ; health:ucumCode "mL/kg/min" ; health:trendPolarity "higher_is_better" . # ---------------------------------------------------------------------------- # Walking Steadiness # ---------------------------------------------------------------------------- health:walkingSteadiness a owl:DatatypeProperty ; rdfs:label "Walking Steadiness"@en ; rdfs:comment "Apple Health walking steadiness classification (OK / Low / Very Low). Maps to nearest SNOMED balance concept. No LOINC equivalent exists for this consumer-device metric."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:string ; health:snomedCode sct:364832000 ; # Balance finding rdfs:comment "SNOMED mapping note: 364832000 (Balance finding) is the closest available concept. Alternatives considered: 250043000 (Gait finding), 282097004 (Ability to walk). Apple's OK/Low/Very Low classification is proprietary."@en . # Valid values for walkingSteadiness (legacy named individuals) # health:WalkingSteadinessUnknown also defined in Pattern A section (v2.3) health:WalkingSteadinessOK a health:WalkingSteadinessLevel ; rdfs:label "OK" . health:WalkingSteadinessLow a health:WalkingSteadinessLevel ; rdfs:label "Low" . health:WalkingSteadinessVeryLow a health:WalkingSteadinessLevel ; rdfs:label "Very Low" . # ---------------------------------------------------------------------------- # Body Measurements (v1.8) # ---------------------------------------------------------------------------- health:bodyMass a owl:DatatypeProperty ; rdfs:label "Body Mass"@en ; rdfs:comment "Body weight measurement."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:27113001 ; health:loincCode loinc:29463-7 ; health:unit "kg" ; health:ucumCode "kg" ; health:trendPolarity "neutral" . health:bodyHeight a owl:DatatypeProperty ; rdfs:label "Body Height"@en ; rdfs:comment "Body height measurement."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:50373000 ; health:loincCode loinc:8302-2 ; health:unit "cm" ; health:ucumCode "cm" ; health:trendPolarity "neutral" . health:bodyMassIndex a owl:DatatypeProperty ; rdfs:label "Body Mass Index"@en ; rdfs:comment "Computed body mass index (weight/height squared)."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:60621009 ; health:loincCode loinc:39156-5 ; health:unit "kg/m2" ; health:ucumCode "kg/m2" ; health:trendPolarity "neutral" . health:bodyTemperature a owl:DatatypeProperty ; rdfs:label "Body Temperature"@en ; rdfs:comment "Body temperature measurement."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:386725007 ; health:loincCode loinc:8310-5 ; health:unit "degC" ; health:ucumCode "Cel" ; health:trendPolarity "neutral" . health:oxygenSaturation a owl:DatatypeProperty ; rdfs:label "Oxygen Saturation"@en ; rdfs:comment "Blood oxygen saturation (SpO2) from pulse oximeter."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:431314004 ; health:loincCode loinc:2708-6 ; health:unit "%" ; health:ucumCode "%" ; health:trendPolarity "higher_is_better" . health:bloodGlucose a owl:DatatypeProperty ; rdfs:label "Blood Glucose"@en ; rdfs:comment "Blood glucose measurement."@en ; rdfs:domain fhir:Observation ; rdfs:range xsd:double ; health:snomedCode sct:33747003 ; health:loincCode loinc:2339-0 ; health:unit "mg/dL" ; health:ucumCode "mg/dL" ; health:trendPolarity "neutral" . # ---------------------------------------------------------------------------- # Blood Type (v1.9) # ---------------------------------------------------------------------------- health:bloodType a owl:DatatypeProperty ; rdfs:label "Blood Type"@en ; rdfs:comment "ABO blood group and Rh factor. Values: aPositive, aNegative, bPositive, bNegative, abPositive, abNegative, oPositive, oNegative. Sourced from HealthKit (HKCharacteristicType.bloodType) or manual entry."@en ; rdfs:domain health:HealthProfile ; rdfs:range xsd:string ; health:snomedCode sct:365637002 ; health:loincCode loinc:882-1 . # Note: health:HealthProfile is not formally defined in this ontology file # but refers to the SDK's HealthProfile model stored at /wellness/. The # bloodType property is a characteristic (not a time-series observation) # so it does not use fhir:Observation as its domain. # ---------------------------------------------------------------------------- # Activity Metrics # ---------------------------------------------------------------------------- health:averageDailySteps a owl:DatatypeProperty ; rdfs:label "Average Daily Steps"@en ; rdfs:comment "Average number of steps per day over measurement period"@en ; rdfs:domain health:ActivitySnapshot ; rdfs:range xsd:integer ; health:snomedCode sct:68130003 ; # Physical activity health:loincCode loinc:41950-7 ; # Number of steps in 24 hour Measured health:unit "steps" ; health:trendPolarity "higher_is_better" . health:activeEnergyBurnedKcal a owl:DatatypeProperty ; rdfs:label "Active Energy Burned"@en ; rdfs:comment "Active energy expenditure in kilocalories (excludes basal metabolic rate)"@en ; rdfs:domain health:ActivitySnapshot ; rdfs:range xsd:decimal ; health:snomedCode sct:251833007 ; # Energy expenditure health:loincCode loinc:41981-2 ; # Calories burned health:unit "kcal" ; health:ucumCode "kcal" . health:exerciseMinutesWeekly a owl:DatatypeProperty ; rdfs:label "Exercise Minutes (Weekly)"@en ; rdfs:comment "Total minutes of exercise activity per week"@en ; rdfs:domain health:ActivitySnapshot ; rdfs:range xsd:integer ; health:snomedCode sct:68130003 ; # Physical activity health:loincCode loinc:73985-4 ; # Exercise activity health:unit "min" ; health:ucumCode "min" ; health:trendPolarity "higher_is_better" . health:standHoursDaily a owl:DatatypeProperty ; rdfs:label "Stand Hours (Daily)"@en ; rdfs:comment "Number of hours per day with at least one minute of standing. Cascade-proprietary metric — no SNOMED CT or LOINC equivalent exists. Apple Health-specific activity ring metric."@en ; rdfs:domain health:ActivitySnapshot ; rdfs:range xsd:integer ; health:unit "hours" . # ---------------------------------------------------------------------------- # Sleep Metrics # ---------------------------------------------------------------------------- health:averageDurationHours a owl:DatatypeProperty ; rdfs:label "Average Sleep Duration"@en ; rdfs:comment "Average sleep duration in hours over measurement period"@en ; rdfs:domain health:SleepSnapshot ; rdfs:range xsd:decimal ; health:snomedCode sct:248263006 ; # Duration of sleep health:loincCode loinc:93832-4 ; # Sleep duration health:unit "hours" ; health:ucumCode "h" ; health:trendPolarity "neutral" . health:sleepQuality a owl:DatatypeProperty ; rdfs:label "Sleep Quality"@en ; rdfs:comment "Qualitative sleep quality assessment derived from wearable sleep analysis"@en ; rdfs:domain health:SleepSnapshot ; rdfs:range xsd:string . # ============================================================================ # Composite Classes # ============================================================================ health:ActivitySnapshot a owl:Class ; rdfs:label "Activity Snapshot"@en ; rdfs:comment "Aggregated activity metrics over a measurement period (typically 7 days)"@en ; rdfs:subClassOf prov:Entity . health:SleepSnapshot a owl:Class ; rdfs:label "Sleep Snapshot"@en ; rdfs:comment "Aggregated sleep metrics over a measurement period (typically 7 days)"@en ; rdfs:subClassOf prov:Entity . health:VO2MaxStatistics a owl:Class ; rdfs:label "VO2 Max Statistics"@en ; rdfs:comment "Statistical summary of VO2 Max readings over a measurement period (typically 180 days). Provides trend analysis, fitness classification, and min/max/mean calculations. Designed for dual-path UI display: rich statistics view when available, fallback to single VO2 Max value otherwise."@en ; rdfs:subClassOf prov:Entity . health:HRVStatistics a owl:Class ; rdfs:label "HRV Statistics"@en ; rdfs:comment "Statistical summary of Heart Rate Variability (SDNN) readings over a measurement period. Provides trend analysis and min/max/mean calculations."@en ; rdfs:subClassOf prov:Entity . health:BPStatistics a owl:Class ; rdfs:label "Blood Pressure Statistics"@en ; rdfs:comment "Statistical summary of blood pressure readings over a measurement period. Provides mean, min, max for systolic and diastolic values, plus AHA category classification."@en ; rdfs:subClassOf prov:Entity . health:HealthProfile a owl:Class ; rdfs:label "Health Profile"@en ; rdfs:comment "Top-level wellness profile aggregating vital signs, body measurements, activity, and sleep data from consumer devices. Stored at /wellness/ in the Cascade Pod."@en ; rdfs:subClassOf prov:Entity . health:DailyVitalReading a owl:Class ; rdfs:label "Daily Vital Reading"@en ; rdfs:comment "A single day's aggregated vital sign reading with value, unit, date, and sample count. Used in 90-day and 30-day history containers."@en ; rdfs:subClassOf fhir:Observation . health:HRVReading a owl:Class ; rdfs:label "HRV Reading"@en ; rdfs:comment "A single heart rate variability (SDNN) observation with timestamp and optional source."@en ; rdfs:subClassOf fhir:Observation . health:BloodPressureReading a owl:Class ; rdfs:label "Blood Pressure Reading"@en ; rdfs:comment "A single blood pressure observation with systolic and diastolic components, timestamp, and optional source."@en ; rdfs:subClassOf fhir:Observation . health:VitalSignReading a owl:Class ; rdfs:label "Vital Sign Reading"@en ; rdfs:comment "A single vital sign observation with value, unit, measurement period, timestamp, and source. Used for baseline readings and VO2 Max history entries."@en ; rdfs:subClassOf fhir:Observation . health:DailyActivitySnapshot a owl:Class ; rdfs:label "Daily Activity Snapshot"@en ; rdfs:comment "A single day's activity data: steps, active energy, exercise minutes, and stand hours."@en ; rdfs:subClassOf prov:Entity . health:DailySleepSnapshot a owl:Class ; rdfs:label "Daily Sleep Snapshot"@en ; rdfs:comment "A single night's sleep data: duration in hours and quality classification."@en ; rdfs:subClassOf prov:Entity . # ============================================================================ # History Container Properties (v2.0) # ============================================================================ # Time-series containers emitted by HealthProfileSerializer for trend analysis. # Each property links a HealthProfile to an ordered list of readings. health:restingHeartRateHistory a owl:ObjectProperty ; rdfs:label "Resting Heart Rate History"@en ; rdfs:comment "Time series of daily resting heart rate readings (90-day lookback). Each entry is a DailyVitalReading with date and value."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:DailyVitalReading . health:walkingHeartRateHistory a owl:ObjectProperty ; rdfs:label "Walking Heart Rate History"@en ; rdfs:comment "Time series of daily walking heart rate readings (90-day lookback)."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:DailyVitalReading . health:hrvHistory a owl:ObjectProperty ; rdfs:label "HRV History"@en ; rdfs:comment "Time series of heart rate variability (SDNN) readings (7-day lookback)."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:HRVReading . health:bloodPressureHistory a owl:ObjectProperty ; rdfs:label "Blood Pressure History"@en ; rdfs:comment "Time series of blood pressure readings (30-day lookback). Each entry has systolic and diastolic values."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:BloodPressureReading . health:vo2MaxHistory a owl:ObjectProperty ; rdfs:label "VO2 Max History"@en ; rdfs:comment "Time series of VO2 Max readings (180-day lookback)."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:VitalSignReading . health:bodyMassHistory a owl:ObjectProperty ; rdfs:label "Body Mass History"@en ; rdfs:comment "Time series of daily body mass readings (30-day lookback)."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:DailyVitalReading . health:dailyActivityHistory a owl:ObjectProperty ; rdfs:label "Daily Activity History"@en ; rdfs:comment "Time series of daily activity snapshots (90-day lookback). Steps, exercise, calories, stand hours per day."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:DailyActivitySnapshot . health:dailySleepHistory a owl:ObjectProperty ; rdfs:label "Daily Sleep History"@en ; rdfs:comment "Time series of daily sleep snapshots (90-day lookback). Duration and quality per night."@en ; rdfs:domain health:HealthProfile ; rdfs:range health:DailySleepSnapshot . # ============================================================================ # Self-Report Base Class (v2.1) # ============================================================================ # # Base class for patient-reported daily check-ins and self-assessments. # Apps extend this with domain-specific response data. health:SelfReport a owl:Class ; rdfs:label "Self Report"@en ; rdfs:comment "Base class for patient-reported daily check-ins and self-assessments. Provides common structure for timestamped self-reports across health domains. Apps extend this with domain-specific response data. Examples: medication adherence, symptom tracking, mood logging, sleep records."@en ; rdfs:subClassOf prov:Entity . health:reportDate a owl:DatatypeProperty ; rdfs:label "Report Date"@en ; rdfs:comment "When the self-report was recorded."@en ; rdfs:domain health:SelfReport ; rdfs:range xsd:dateTime . health:reportType a owl:DatatypeProperty ; rdfs:label "Report Type"@en ; rdfs:comment "Category of self-report (e.g., medication_adherence, symptom_tracking, mood, sleep)."@en ; rdfs:domain health:SelfReport ; rdfs:range xsd:string . health:completionStatus a owl:DatatypeProperty ; rdfs:label "Completion Status"@en ; rdfs:comment "Whether the report is complete, partial, or skipped."@en ; rdfs:domain health:SelfReport ; rdfs:range xsd:string . health:reportContext a owl:DatatypeProperty ; rdfs:label "Report Context"@en ; rdfs:comment "What tracking plan or list this report relates to (free-form reference)."@en ; rdfs:domain health:SelfReport ; rdfs:range xsd:string . health:reportNotes a owl:DatatypeProperty ; rdfs:label "Report Notes"@en ; rdfs:comment "Optional free-text notes from the patient."@en ; rdfs:domain health:SelfReport ; rdfs:range xsd:string . # ============================================================================ # Annotation Properties # ============================================================================ health:snomedCode a owl:AnnotationProperty ; rdfs:label "SNOMED CT Code"@en ; rdfs:comment "Links a wellness property to its SNOMED CT concept"@en . health:loincCode a owl:AnnotationProperty ; rdfs:label "LOINC Code"@en ; rdfs:comment "Links a wellness property to its LOINC observation code"@en . health:unit a owl:AnnotationProperty ; rdfs:label "Display Unit"@en ; rdfs:comment "Human-readable unit of measurement"@en . health:ucumCode a owl:AnnotationProperty ; rdfs:label "UCUM Code"@en ; rdfs:comment "Unified Code for Units of Measure code"@en . health:trendPolarity a owl:AnnotationProperty ; rdfs:label "Trend Polarity"@en ; rdfs:comment "Whether an increase in this metric is generally favorable ('higher_is_better'), unfavorable ('lower_is_better'), or context-dependent ('neutral'). Used by presentation layers to determine trend arrow color semantics. This is a general heuristic — individual clinical context may differ."@en . # ============================================================================ # Pattern A Flat Observation Properties (v2.3 — BUG-006) # ============================================================================ # These properties are used in Pattern A flat blank nodes emitted by # HealthProfileSerializer. They replace the fhir:Observation nesting # (Pattern B) for all wellness vital sign readings. health:value a owl:DatatypeProperty ; rdfs:label "Observation Value"@en ; rdfs:comment "Generic numeric value for a wellness observation blank node. Used in Pattern A flat serialization where the observation type is identified by the enclosing property (e.g., health:restingHeartRate). The unit is carried by health:unit on the same blank node."@en ; rdfs:range xsd:double . health:date a owl:DatatypeProperty ; rdfs:label "Observation Date"@en ; rdfs:comment "Timestamp of a wellness observation in Pattern A flat serialization. Equivalent in role to fhir:effectiveDateTime but scoped to health: namespace blank nodes."@en ; rdfs:range xsd:dateTime . health:sdnn a owl:DatatypeProperty ; rdfs:label "SDNN (HRV)"@en ; rdfs:comment "Standard deviation of normal-to-normal R-R intervals. The primary HRV scalar emitted in Pattern A HRVReading blank nodes."@en ; rdfs:range xsd:double ; health:snomedCode sct:80404004 ; health:loincCode loinc:80404-7 ; health:unit "ms" . health:systolic a owl:DatatypeProperty ; rdfs:label "Systolic Blood Pressure (reading)"@en ; rdfs:comment "Systolic component value in a Pattern A BloodPressureReading blank node. Distinct from health:systolicBP which is a domain-level property on fhir:Observation."@en ; rdfs:range xsd:double ; health:snomedCode sct:271649006 ; health:loincCode loinc:8480-6 ; health:unit "mmHg" . health:diastolic a owl:DatatypeProperty ; rdfs:label "Diastolic Blood Pressure (reading)"@en ; rdfs:comment "Diastolic component value in a Pattern A BloodPressureReading blank node. Distinct from health:diastolicBP which is a domain-level property on fhir:Observation."@en ; rdfs:range xsd:double ; health:snomedCode sct:271650006 ; health:loincCode loinc:8462-4 ; health:unit "mmHg" . health:walkingSteadinessLevel a owl:ObjectProperty ; rdfs:label "Walking Steadiness Level"@en ; rdfs:comment "Links a Pattern A WalkingSteadiness blank node to its level individual (health:WalkingSteadinessOK, health:WalkingSteadinessLow, health:WalkingSteadinessVeryLow, health:WalkingSteadinessUnknown)."@en ; rdfs:range health:WalkingSteadinessLevel . # health:WalkingSteadinessUnknown added v2.3 (previously only OK/Low/VeryLow defined) health:WalkingSteadinessUnknown a health:WalkingSteadinessLevel ; rdfs:label "Unknown" . # ============================================================================ # VO2 Max Statistics Properties (v1.7) # ============================================================================ # Statistical analysis of VO2 Max readings with trend detection and fitness # classification. Part of the dual-path wellness data pattern where rich # statistics are preferred when available, with fallback to single values. health:vo2Mean a owl:DatatypeProperty ; rdfs:label "Mean VO2 Max"@en ; rdfs:comment "Average VO2 Max over measurement period (mL/kg/min)"@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:double ; health:unit "mL/kg/min" ; health:ucumCode "mL/kg/min" . health:vo2Min a owl:DatatypeProperty ; rdfs:label "Minimum VO2 Max"@en ; rdfs:comment "Lowest VO2 Max reading in measurement period"@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:double ; health:unit "mL/kg/min" . health:vo2MaxValue a owl:DatatypeProperty ; rdfs:label "Maximum VO2 Max"@en ; rdfs:comment "Highest VO2 Max reading in measurement period"@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:double ; health:unit "mL/kg/min" . health:vo2SampleCount a owl:DatatypeProperty ; rdfs:label "Sample Count"@en ; rdfs:comment "Number of VO2 Max measurements in the period"@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:integer . health:vo2DaysCovered a owl:DatatypeProperty ; rdfs:label "Days Covered"@en ; rdfs:comment "Number of unique days with VO2 Max measurements"@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:integer . health:vo2TrendDirection a owl:DatatypeProperty ; rdfs:label "VO2 Trend Direction"@en ; rdfs:comment "Trend direction based on comparison of first half vs second half of readings: 'improving', 'declining', 'stable', or 'unknown'. Uses 3% threshold (1 MET ≈ 3.5 mL/kg/min is clinically meaningful)."@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:string . health:vo2PercentageChange a owl:DatatypeProperty ; rdfs:label "Percentage Change"@en ; rdfs:comment "Percentage change from oldest to newest reading period"@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:double . health:fitnessClassification a owl:DatatypeProperty ; rdfs:label "Fitness Classification"@en ; rdfs:comment "Cardiorespiratory fitness level based on mean VO2 Max: 'veryPoor' (<20), 'poor' (20-29), 'fair' (30-34), 'good' (35-44), 'excellent' (45-54), 'superior' (≥55). Currently age/sex agnostic; future versions may use demographics for personalized classification."@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:string . health:isSparseData a owl:DatatypeProperty ; rdfs:label "Sparse Data Indicator"@en ; rdfs:comment "True when measurements are limited (< 3 samples or < 30 days covered). Used to trigger messaging about VO2 Max measurement requirements."@en ; rdfs:domain health:VO2MaxStatistics ; rdfs:range xsd:boolean . # ============================================================================ # Shared Statistics Temporal Properties (v1.8) # ============================================================================ health:periodStart a owl:DatatypeProperty ; rdfs:label "Period Start"@en ; rdfs:comment "Start date of the statistical measurement period."@en ; rdfs:range xsd:dateTime . health:periodEnd a owl:DatatypeProperty ; rdfs:label "Period End"@en ; rdfs:comment "End date of the statistical measurement period."@en ; rdfs:range xsd:dateTime . # ============================================================================ # HRV Statistics Properties (v1.8) # ============================================================================ health:hrvSampleCount a owl:DatatypeProperty ; rdfs:label "HRV Sample Count"@en ; rdfs:comment "Number of HRV readings in the period."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:integer . health:hrvDaysCovered a owl:DatatypeProperty ; rdfs:label "HRV Days Covered"@en ; rdfs:comment "Number of unique days with HRV measurements."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:integer . health:hrvMean a owl:DatatypeProperty ; rdfs:label "Mean HRV"@en ; rdfs:comment "Average HRV (SDNN) over the measurement period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvMedian a owl:DatatypeProperty ; rdfs:label "Median HRV"@en ; rdfs:comment "Median HRV (SDNN) over the measurement period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvStdDev a owl:DatatypeProperty ; rdfs:label "HRV Standard Deviation"@en ; rdfs:comment "Standard deviation of HRV readings over the measurement period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvMin a owl:DatatypeProperty ; rdfs:label "Minimum HRV"@en ; rdfs:comment "Lowest HRV reading in the period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvMax a owl:DatatypeProperty ; rdfs:label "Maximum HRV"@en ; rdfs:comment "Highest HRV reading in the period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvPercentile25 a owl:DatatypeProperty ; rdfs:label "HRV 25th Percentile"@en ; rdfs:comment "25th percentile of HRV readings over the measurement period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvPercentile75 a owl:DatatypeProperty ; rdfs:label "HRV 75th Percentile"@en ; rdfs:comment "75th percentile of HRV readings over the measurement period (ms)."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:double ; health:unit "ms" . health:hrvTrendDirection a owl:DatatypeProperty ; rdfs:label "HRV Trend Direction"@en ; rdfs:comment "Trend direction for HRV: improving, declining, stable, or unknown."@en ; rdfs:domain health:HRVStatistics ; rdfs:range xsd:string . # ============================================================================ # Blood Pressure Statistics Properties (v1.8) # ============================================================================ health:bpSampleCount a owl:DatatypeProperty ; rdfs:label "BP Sample Count"@en ; rdfs:comment "Number of blood pressure readings in the period."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:integer . health:bpMeanSystolic a owl:DatatypeProperty ; rdfs:label "Mean Systolic BP"@en ; rdfs:comment "Average systolic blood pressure over the measurement period (mmHg)."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:double ; health:unit "mmHg" . health:bpMeanDiastolic a owl:DatatypeProperty ; rdfs:label "Mean Diastolic BP"@en ; rdfs:comment "Average diastolic blood pressure over the measurement period (mmHg)."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:double ; health:unit "mmHg" . health:bpMinSystolic a owl:DatatypeProperty ; rdfs:label "Minimum Systolic BP"@en ; rdfs:comment "Lowest systolic blood pressure reading in the period (mmHg)."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:double ; health:unit "mmHg" . health:bpMaxSystolic a owl:DatatypeProperty ; rdfs:label "Maximum Systolic BP"@en ; rdfs:comment "Highest systolic blood pressure reading in the period (mmHg)."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:double ; health:unit "mmHg" . health:bpMinDiastolic a owl:DatatypeProperty ; rdfs:label "Minimum Diastolic BP"@en ; rdfs:comment "Lowest diastolic blood pressure reading in the period (mmHg)."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:double ; health:unit "mmHg" . health:bpMaxDiastolic a owl:DatatypeProperty ; rdfs:label "Maximum Diastolic BP"@en ; rdfs:comment "Highest diastolic blood pressure reading in the period (mmHg)."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:double ; health:unit "mmHg" . health:bpCategory a owl:DatatypeProperty ; rdfs:label "BP Category"@en ; rdfs:comment "AHA blood pressure category based on mean values: normal, elevated, hypertension_stage1, hypertension_stage2, hypertensive_crisis."@en ; rdfs:domain health:BPStatistics ; rdfs:range xsd:string . # ============================================================================ # Temporal Trend Classes (v1.5) # ============================================================================ # Design rationale: clinical:LabTestSeries captures discrete lab samples over # clinical visits as a long-lived container. health:MetricTrend is a computed # observation over a bounded time window from continuous daily device data — # more like a derived fact than a container. They share trendDirection and # trendConfidence vocabulary intentionally for cross-domain consistency. # Values use objective direction (increasing/decreasing) rather than # value-laden terms (improving/declining) — the ontology describes *what* # happened; the presentation layer interprets whether it's good or bad. health:MetricTrend a owl:Class ; rdfs:label "Metric Trend" ; rdfs:comment "A time-bounded trend for a single wellness metric, computed from a series of observations. Captures direction, magnitude, and comparison period." ; rdfs:subClassOf prov:Entity . # --- MetricTrend Properties --- health:trendMetric a owl:ObjectProperty ; rdfs:label "Trend Metric" ; rdfs:comment "The wellness property this trend describes (e.g., health:restingHeartRate)" ; rdfs:domain health:MetricTrend . health:trendDirection a owl:DatatypeProperty ; rdfs:label "Trend Direction" ; rdfs:comment "Direction of change: increasing, decreasing, stable, or insufficient_data. Matches clinical:trendDirection values for cross-domain consistency. Semantic interpretation (whether 'increasing' is good or bad) is metric-specific and handled at the presentation layer, not the ontology layer." ; rdfs:domain health:MetricTrend ; rdfs:range xsd:string . health:trendMagnitude a owl:DatatypeProperty ; rdfs:label "Trend Magnitude" ; rdfs:comment "Percentage change between comparison periods (e.g., -5.6 means a 5.6% decrease)" ; rdfs:domain health:MetricTrend ; rdfs:range xsd:decimal . health:trendPeriodStart a owl:DatatypeProperty ; rdfs:label "Trend Period Start" ; rdfs:domain health:MetricTrend ; rdfs:range xsd:dateTime . health:trendPeriodEnd a owl:DatatypeProperty ; rdfs:label "Trend Period End" ; rdfs:domain health:MetricTrend ; rdfs:range xsd:dateTime . health:trendBaselineValue a owl:DatatypeProperty ; rdfs:label "Baseline Value" ; rdfs:comment "The reference value (e.g., 90-day average) this trend is compared against" ; rdfs:domain health:MetricTrend ; rdfs:range xsd:decimal . health:trendCurrentValue a owl:DatatypeProperty ; rdfs:label "Current Value" ; rdfs:comment "The current period average (e.g., 7-day average)" ; rdfs:domain health:MetricTrend ; rdfs:range xsd:decimal . health:trendConfidence a owl:DatatypeProperty ; rdfs:label "Trend Confidence" ; rdfs:comment "Confidence in the trend: high (>30 days data), medium (14-30 days), low (<14 days)" ; rdfs:domain health:MetricTrend ; rdfs:range xsd:string . health:trendSource a owl:ObjectProperty ; rdfs:label "Trend Data Source" ; rdfs:comment "The data source (device, app, or system) that produced the underlying observations. Links to cascade:DataSource when available; for inline use, a blank node with cascade:sourceName is acceptable." ; rdfs:domain health:MetricTrend ; rdfs:range cascade:DataSource . # ============================================================================ # Statistics Container Linking Properties (v2.2) # ============================================================================ # ObjectProperties used by CheckupSerializer to link an intake form or # wellness profile to its statistical summary nodes. health:hasHRVStatistics a owl:ObjectProperty ; rdfs:label "has HRV statistics"@en ; rdfs:comment "Links a wellness profile to its HRV statistical summary."@en ; rdfs:range health:HRVStatistics . health:hasBPStatistics a owl:ObjectProperty ; rdfs:label "has BP statistics"@en ; rdfs:comment "Links a wellness profile to its blood pressure statistical summary."@en ; rdfs:range health:BPStatistics . # Named individuals for walkingSteadinessLevel (Pattern A short-form, v2.3) health:OK a health:WalkingSteadinessLevel ; rdfs:label "OK" . health:Low a health:WalkingSteadinessLevel ; rdfs:label "Low" . health:VeryLow a health:WalkingSteadinessLevel ; rdfs:label "Very Low" . health:Unknown a health:WalkingSteadinessLevel ; rdfs:label "Unknown" . # ============================================================================ # Standard Code Reference Table (for documentation) # ============================================================================ # # | Metric | Health Property | SNOMED CT | LOINC | # |-----------------------|------------------------------|-------------|----------| # | Resting Heart Rate | health:restingHeartRate | 364075005 | 40443-4 | # | Walking Heart Rate | health:walkingHeartRate | 364075005 | 89270-3 | # | HRV (SDNN) | health:heartRateVariability | 80404004 | 80404-7 | # | Blood Pressure | health:bloodPressure | 75367002 | 85354-9 | # | Systolic BP | (component) | 271649006 | 8480-6 | # | Diastolic BP | (component) | 271650006 | 8462-4 | # | Respiratory Rate | health:respiratoryRate | 86290005 | 9279-1 | # | VO2 Max | health:vo2Max | 251880009 | 60842-2 | # | Walking Steadiness | health:walkingSteadiness | 364832000 | --- | # | Average Daily Steps | health:averageDailySteps | 68130003 | 41950-7 | # | Active Energy Burned | health:activeEnergyBurnedKcal| 251833007 | 41981-2 | # | Exercise Minutes | health:exerciseMinutesWeekly | 68130003 | 73985-4 | # | Stand Hours | health:standHoursDaily | --- | --- | # | Sleep Duration | health:averageDurationHours | 248263006 | 93832-4 | # ============================================================================ # Social History (v2.4) # ============================================================================ ### Social History Record health:SocialHistoryRecord a owl:Class ; rdfs:label "Social History Record"@en ; rdfs:comment "A social history observation about a patient, including smoking status, alcohol use, exercise, occupation, and other lifestyle factors."@en ; owl:versionInfo "Added in health v2.4" . health:smokingStatus a owl:DatatypeProperty ; rdfs:label "Smoking Status"@en ; rdfs:comment "Patient's smoking status (e.g., current-smoker, former-smoker, never-smoker)."@en ; rdfs:domain health:SocialHistoryRecord ; rdfs:range xsd:string . health:alcoholUse a owl:DatatypeProperty ; rdfs:label "Alcohol Use"@en ; rdfs:comment "Patient's alcohol consumption description."@en ; rdfs:domain health:SocialHistoryRecord ; rdfs:range xsd:string . health:exerciseFrequency a owl:DatatypeProperty ; rdfs:label "Exercise Frequency"@en ; rdfs:comment "Patient's reported exercise frequency."@en ; rdfs:domain health:SocialHistoryRecord ; rdfs:range xsd:string . health:occupationalExposure a owl:DatatypeProperty ; rdfs:label "Occupational Exposure"@en ; rdfs:comment "Occupational exposures relevant to patient health."@en ; rdfs:domain health:SocialHistoryRecord ; rdfs:range xsd:string . # ============================================================================ # Namespace Boundary Note (v2.5) # ============================================================================ # # The split between the health: and clinical: namespaces is HISTORICAL, not # semantic. It is not a provenance signal and must not be read as one. # # health: was introduced for consumer- and device-generated wellness data and # clinical: for EHR-sourced records, but serializers have never honoured that # division. Records typed health:LabResultRecord, health:ConditionRecord, # health:AllergyRecord and health:ImmunizationRecord carry EHR-sourced data, # and clinical:VitalSign records carry consumer-device data. # # PROVENANCE IS CARRIED BY cascade:dataProvenance, AND ONLY BY IT. # Consumers deciding whether a record came from an EHR, a device, or the # patient MUST key on cascade:dataProvenance (ClinicalGenerated, EHRVerified, # DeviceGenerated, PatientReported, SelfReported, AIExtracted). Keying on the # namespace of the rdf:type will give the wrong answer on real pods. # # Both namespaces are ratified and neither is being migrated. The clinical: # classes that duplicate the five classes defined below are deprecated in # clinical v1.13 (owl:deprecated true, rdfs:seeAlso pointing here) but are NOT # removed: an existing export path still emits them and existing pods contain # them. Readers must accept both spellings; writers should prefer these. # # --------------------------------------------------------------------------- # SocialHistoryRecord: two classes, deliberately kept # --------------------------------------------------------------------------- # # health:SocialHistoryRecord (v2.4, above) and clinical:SocialHistoryRecord # (clinical v1.9) both exist and both are retained. The asserted # consumer-versus-EHR division does not describe what is emitted: # # - clinical:SocialHistoryRecord is emitted, by the free-text LLM extraction # path only. # - health:SocialHistoryRecord is emitted by nothing. The structured C-CDA # Social History path that appears to write it reports the entries it read # but writes no records at all, so the class is defined and shaped but # never instantiated. # # Neither class is therefore a reliable indicator of how a social-history # observation was obtained. Consumers should accept both types and read # cascade:dataProvenance for the actual source. Reconciling the two is # deferred rather than guessed at. # ============================================================================ # Clinical Record Classes (v2.5) # ============================================================================ # # These five classes have been emitted by Cascade serializers since schema # version 1.3 but were never defined in this ontology. Defining them is purely # additive: no serializer changes, no rdf:type changes, no pod migration. It # makes existing data describable and, with health.shapes.ttl v1.2, checkable. # # Layer 1 alignment: each class states the FHIR R4 resource it corresponds to. # Layer 2: the properties below. Layer 3: checkup:/pots: aggregations. health:LabResultRecord a owl:Class ; rdfs:label "Lab Result Record"@en ; rdfs:comment "A laboratory test result: the test performed, its value and unit, the reference range it is read against, and the specimen it came from. Emitted for both EHR-imported and patient-entered results; read cascade:dataProvenance to tell them apart."@en ; rdfs:subClassOf fhir:Observation ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; emitted since schema 1.3" . health:ConditionRecord a owl:Class ; rdfs:label "Condition Record"@en ; rdfs:comment "A diagnosed condition or problem-list entry, with its clinical status, onset, and coding. Emitted for both EHR-imported and self-reported conditions; read cascade:dataProvenance to tell them apart."@en ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; emitted since schema 1.3" . health:AllergyRecord a owl:Class ; rdfs:label "Allergy Record"@en ; rdfs:comment "An allergy or intolerance: the allergen, the reaction it provokes, and the severity of that reaction. Emitted for both EHR-imported and self-reported allergies; read cascade:dataProvenance to tell them apart."@en ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; emitted since schema 1.3" . health:ImmunizationRecord a owl:Class ; rdfs:label "Immunization Record"@en ; rdfs:comment "An administered vaccine dose, with product identification, lot, route, site and administering party."@en ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; emitted since schema 1.3" . health:FamilyHistoryRecord a owl:Class ; rdfs:label "Family History Record"@en ; rdfs:comment "A condition recorded for a relative of the patient, with the relationship and the relative's age at onset. The relationship is expressed with clinical:relationship, which is shared with coverage records."@en ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; emitted since schema 1.3" . # ---------------------------------------------------------------------------- # Properties shared across the record classes # ---------------------------------------------------------------------------- # Domains are unions rather than a single class. A single-class rdfs:domain on # a property that several classes use is an assertion the data falsifies, which # is the defect this release is correcting elsewhere; do not reintroduce it. health:notes a owl:DatatypeProperty ; rdfs:label "Notes"@en ; rdfs:comment "Free-text clinical or patient commentary attached to a record."@en ; rdfs:domain [ a owl:Class ; owl:unionOf ( health:LabResultRecord health:ConditionRecord health:AllergyRecord health:ImmunizationRecord health:FamilyHistoryRecord ) ] ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:sourceRecordId a owl:DatatypeProperty ; rdfs:label "Source Record Identifier"@en ; rdfs:comment "Identifier of this record in the system it was imported from. Opaque to Cascade; used for reconciliation against the source system, not as a Cascade identity."@en ; rdfs:domain [ a owl:Class ; owl:unionOf ( health:LabResultRecord health:ConditionRecord health:AllergyRecord health:ImmunizationRecord health:FamilyHistoryRecord ) ] ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:status a owl:DatatypeProperty ; rdfs:label "Status"@en ; rdfs:comment "Lifecycle status of the record. The permitted values differ by class and are constrained per-shape, not here: condition records use the FHIR R4 ConditionClinicalStatusCodes value set, immunization records use ImmunizationStatusCodes."@en ; rdfs:domain [ a owl:Class ; owl:unionOf ( health:ConditionRecord health:ImmunizationRecord ) ] ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:onsetDate a owl:DatatypeProperty ; rdfs:label "Onset Date"@en ; rdfs:comment "When the condition or allergy began, or was first recorded as beginning."@en ; rdfs:domain [ a owl:Class ; owl:unionOf ( health:ConditionRecord health:AllergyRecord ) ] ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in health v2.5" . health:conditionName a owl:DatatypeProperty ; rdfs:label "Condition Name"@en ; rdfs:comment "Human-readable name of the condition. Used both for the patient's own conditions and for conditions recorded against a relative."@en ; rdfs:domain [ a owl:Class ; owl:unionOf ( health:ConditionRecord health:FamilyHistoryRecord ) ] ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . # ---------------------------------------------------------------------------- # Lab result properties # ---------------------------------------------------------------------------- health:testName a owl:DatatypeProperty ; rdfs:label "Test Name"@en ; rdfs:comment "Human-readable name of the laboratory test performed."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:resultValue a owl:DatatypeProperty ; rdfs:label "Result Value"@en ; rdfs:comment "The measured value. Carried as a string because lab results are not uniformly numeric: ratios, titres, qualitative results ('Negative'), and bounded values ('<0.01') all occur. Exactly one value per record."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:resultUnit a owl:DatatypeProperty ; rdfs:label "Result Unit"@en ; rdfs:comment "Unit of measure for health:resultValue, as reported by the performing laboratory."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5" . health:interpretation a owl:DatatypeProperty ; rdfs:label "Interpretation"@en ; rdfs:comment "Categorical reading of the result, as a code from the HL7 v3 ObservationInterpretation code system: normality (N, A, H, L, HH, LL, ...), susceptibility (S, I, R, SDD, ...), detection (POS, NEG, DET, ND, IND), reactivity (RR, WR, NR), change (B, D, U, W) and the exception codes. Any of the 15 data-absent-reason codes is also accepted, for a source observation whose interpretation element was absent or null-flavoured; that is FHIR's own spelling for a CodeableConcept that is missing for a stated reason, and it is what keeps 'nobody asked' distinguishable from 'asked, did not know'. A source code that is a member of neither value set is carried verbatim on health:interpretationSourceCode instead of being forced into this one. At most one value per record."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; rdfs:seeAlso ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; bound to HL7 v3 ObservationInterpretation in health v2.6; data-absent-reason codes admitted in health v2.7" . health:interpretationSourceCode a owl:DatatypeProperty ; rdfs:label "Interpretation Source Code"@en ; rdfs:comment """The interpretation code the SOURCE wrote, copied verbatim, for the case where that code is a member of neither value set health:interpretation is bound to (HL7 v3 ObservationInterpretation, and data-absent-reason). A laboratory's local abnormality flag is the ordinary case. WRITE IT ONLY THEN. A code that IS in one of the bound value sets belongs on health:interpretation and must not be duplicated here; this property is the escape hatch for the residue, not a parallel spelling. It is deliberately unconstrained: no value set, no pattern, no case folding. The whole point is that the value is whatever the source said, so a reader can recover the original assertion and a later mapping can be built from real observed codes rather than guesses. Constraining it would recreate the loss it exists to prevent. A producer that recognises the source code's INTENT should also write its best ratified equivalent on health:interpretation, so that a consumer reading only the bound property still gets a usable reading. The pair 'interpretation A, interpretationSourceCode elevated' says: the source said "elevated", and the nearest ratified code is A (Abnormal)."""@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; rdfs:seeAlso health:interpretation ; owl:versionInfo "Added in health v2.7" . health:performedDate a owl:DatatypeProperty ; rdfs:label "Performed Date"@en ; rdfs:comment "When the specimen was collected or the test performed. Distinct from health:reportedDate."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in health v2.5" . health:reportedDate a owl:DatatypeProperty ; rdfs:label "Reported Date"@en ; rdfs:comment "When the laboratory released the result. Later than health:performedDate."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in health v2.5" . health:testCode a owl:ObjectProperty ; rdfs:label "Test Code"@en ; rdfs:comment "LOINC reference for the test performed, as an IRI. Repeatable: FHIR R4 CodeableConcept.coding is 0..*, and one test is often carried under more than one LOINC coding."@en ; rdfs:domain health:LabResultRecord ; rdfs:seeAlso ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; made repeatable in health v2.6" . health:labCategory a owl:DatatypeProperty ; rdfs:label "Lab Category"@en ; rdfs:comment "Laboratory department or panel grouping (for example Chemistry, Hematology). Free text: laboratories do not agree on a common set. Repeatable, mirroring FHIR R4 Observation.category, which is 0..*."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; made repeatable in health v2.6" . health:referenceRange a owl:DatatypeProperty ; rdfs:label "Reference Range"@en ; rdfs:comment "The normal range this result is read against, as printed by the performing laboratory (for example '70 - 100'). Not parsed."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:specimenType a owl:DatatypeProperty ; rdfs:label "Specimen Type"@en ; rdfs:comment "The specimen the test was run on (for example Serum, Whole Blood, Urine)."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:orderingProvider a owl:DatatypeProperty ; rdfs:label "Ordering Provider"@en ; rdfs:comment "Name of the clinician who ordered the test."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:performingLab a owl:DatatypeProperty ; rdfs:label "Performing Laboratory"@en ; rdfs:comment "Name of the laboratory that ran the test."@en ; rdfs:domain health:LabResultRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . # ---------------------------------------------------------------------------- # Condition properties # ---------------------------------------------------------------------------- health:icd10Code a owl:ObjectProperty ; rdfs:label "ICD-10-CM Code"@en ; rdfs:comment "ICD-10-CM reference for the condition, as an IRI. Repeatable: FHIR R4 CodeableConcept.coding is 0..*, and problem-list entries are routinely dual coded. Note that ICD-10-CM permits a letter in any character position (C4A, D3A and M1A are ordinary categories), so no lexical pattern is imposed here."@en ; rdfs:domain health:ConditionRecord ; rdfs:seeAlso ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5; made repeatable in health v2.6" . health:conditionClass a owl:DatatypeProperty ; rdfs:label "Condition Class"@en ; rdfs:comment "Body-system grouping used for patient-facing organisation (for example cardiovascular, endocrine, respiratory). A presentation aid, not a clinical classification; do not use it for decision support."@en ; rdfs:domain health:ConditionRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:monitoredVitalSigns a owl:ObjectProperty ; rdfs:label "Monitored Vital Signs"@en ; rdfs:comment "Ordered list of vital-sign names a patient or app tracks because of this condition (for example bloodPressure, heartRate). An rdf:List of string literals."@en ; rdfs:domain health:ConditionRecord ; rdfs:range rdf:List ; owl:versionInfo "Added in health v2.5" . # ---------------------------------------------------------------------------- # Allergy properties # ---------------------------------------------------------------------------- health:allergen a owl:DatatypeProperty ; rdfs:label "Allergen"@en ; rdfs:comment "The substance the patient reacts to. Required: an allergy record without an allergen carries no safety information."@en ; rdfs:domain health:AllergyRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:allergyCategory a owl:DatatypeProperty ; rdfs:label "Allergy Category"@en ; rdfs:comment "Category of the allergen, using the FHIR R4 AllergyIntoleranceCategory value set: food, medication, environment, biologic."@en ; rdfs:domain health:AllergyRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5" . health:reaction a owl:DatatypeProperty ; rdfs:label "Reaction"@en ; rdfs:comment "Description of the manifestation the allergen produces (for example 'Hives (urticaria)')."@en ; rdfs:domain health:AllergyRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:allergySeverity a owl:DatatypeProperty ; rdfs:label "Allergy Severity"@en ; rdfs:comment "Severity of the reaction as a whole, using the FHIR R4 AllergyIntoleranceSeverity value set: mild, moderate, severe."@en ; rdfs:domain health:AllergyRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5" . # ---------------------------------------------------------------------------- # Immunization properties # ---------------------------------------------------------------------------- health:vaccineName a owl:DatatypeProperty ; rdfs:label "Vaccine Name"@en ; rdfs:comment "Human-readable name of the vaccine product administered."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:administrationDate a owl:DatatypeProperty ; rdfs:label "Administration Date"@en ; rdfs:comment "When the dose was administered. Corresponds to FHIR R4 Immunization.occurrence, which is 1..1."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:dateTime ; owl:versionInfo "Added in health v2.5" . health:vaccineCode a owl:DatatypeProperty ; rdfs:label "Vaccine Code"@en ; rdfs:comment "Coded identifier for the vaccine product, typically a CVX code."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5" . health:manufacturer a owl:DatatypeProperty ; rdfs:label "Manufacturer"@en ; rdfs:comment "Manufacturer of the administered vaccine product."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:lotNumber a owl:DatatypeProperty ; rdfs:label "Lot Number"@en ; rdfs:comment "Manufacturer lot number of the administered dose. Required for recall tracing."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:doseQuantity a owl:DatatypeProperty ; rdfs:label "Dose Quantity"@en ; rdfs:comment "Amount administered, value and unit together as printed (for example '0.5 mL')."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:route a owl:DatatypeProperty ; rdfs:label "Route"@en ; rdfs:comment "Route of administration (for example intramuscular, oral, intranasal)."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Added in health v2.5" . health:site a owl:DatatypeProperty ; rdfs:label "Administration Site"@en ; rdfs:comment "Body site the dose was administered at (for example 'Left deltoid')."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:administeringProvider a owl:DatatypeProperty ; rdfs:label "Administering Provider"@en ; rdfs:comment "Name of the person who administered the dose."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . health:administeringLocation a owl:DatatypeProperty ; rdfs:label "Administering Location"@en ; rdfs:comment "Facility where the dose was administered."@en ; rdfs:domain health:ImmunizationRecord ; rdfs:range xsd:string ; owl:versionInfo "Added in health v2.5" . # ---------------------------------------------------------------------------- # Family history properties # ---------------------------------------------------------------------------- health:onsetAge a owl:DatatypeProperty ; rdfs:label "Onset Age"@en ; rdfs:comment "Age in years at which the relative's condition began. Corresponds to FHIR R4 FamilyMemberHistory.condition.onsetAge."@en ; rdfs:domain health:FamilyHistoryRecord ; rdfs:range xsd:integer ; owl:versionInfo "Added in health v2.5" . # ============================================================================ # Wellness Container Classes (v2.5) # ============================================================================ # # Six containers that HealthProfileSerializer has always emitted at /wellness/. # Each is a partition of a health profile carrying one family of history # containers. Declaring them subclasses of health:HealthProfile does two things: # # 1. It makes the rdfs:domain health:HealthProfile already asserted on the # eight history container properties above TRUE. Before this release those # domain statements were contradicted by every pod ever written, because # health:dailyActivityHistory is emitted on a health:ActivityData subject, # never on a health:HealthProfile subject. # 2. It brings these subjects under health:HealthProfileShape, whose # sh:targetClass is subclass-aware per SHACL 2.1.3.1. # # No serializer changes. No rdf:type changes. These subjects were already # being written with exactly these types. health:ActivityData a owl:Class ; rdfs:label "Activity Data"@en ; rdfs:comment "Wellness container for daily activity history: steps, active energy, exercise minutes and stand hours."@en ; rdfs:subClassOf health:HealthProfile ; owl:versionInfo "Added in health v2.5; emitted since health v2.0" . health:SleepData a owl:Class ; rdfs:label "Sleep Data"@en ; rdfs:comment "Wellness container for nightly sleep history: duration and quality."@en ; rdfs:subClassOf health:HealthProfile ; owl:versionInfo "Added in health v2.5; emitted since health v2.0" . health:HeartRateData a owl:Class ; rdfs:label "Heart Rate Data"@en ; rdfs:comment "Wellness container for resting and walking heart rate history."@en ; rdfs:subClassOf health:HealthProfile ; owl:versionInfo "Added in health v2.5; emitted since health v2.0" . health:BloodPressureData a owl:Class ; rdfs:label "Blood Pressure Data"@en ; rdfs:comment "Wellness container for blood pressure history from home monitors."@en ; rdfs:subClassOf health:HealthProfile ; owl:versionInfo "Added in health v2.5; emitted since health v2.0" . health:HRVData a owl:Class ; rdfs:label "HRV Data"@en ; rdfs:comment "Wellness container for heart rate variability (SDNN) history."@en ; rdfs:subClassOf health:HealthProfile ; owl:versionInfo "Added in health v2.5; emitted since health v2.0" . health:BodyMeasurements a owl:Class ; rdfs:label "Body Measurements"@en ; rdfs:comment "Wellness container for body mass and related anthropometric history."@en ; rdfs:subClassOf health:HealthProfile ; owl:versionInfo "Added in health v2.5; emitted since health v2.0" . # ============================================================================ # Daily Snapshot Properties (v2.5) # ============================================================================ # # Emitted on health:DailyActivitySnapshot and health:DailySleepSnapshot entries # inside the history containers. These are the single-day forms. The similarly # named health:activeEnergyBurnedKcal, health:exerciseMinutesWeekly and # health:standHoursDaily above are the 7-day aggregate forms carried on # health:ActivitySnapshot; the two sets are distinct and both are emitted. health:steps a owl:DatatypeProperty ; rdfs:label "Steps"@en ; rdfs:comment "Step count for a single day."@en ; rdfs:domain health:DailyActivitySnapshot ; rdfs:range xsd:integer ; owl:versionInfo "Added in health v2.5" . health:activeEnergyKcal a owl:DatatypeProperty ; rdfs:label "Active Energy (kcal)"@en ; rdfs:comment "Active energy burned in kilocalories for a single day."@en ; rdfs:domain health:DailyActivitySnapshot ; rdfs:range xsd:decimal ; owl:versionInfo "Added in health v2.5" . health:exerciseMinutes a owl:DatatypeProperty ; rdfs:label "Exercise Minutes"@en ; rdfs:comment "Minutes of exercise recorded for a single day."@en ; rdfs:domain health:DailyActivitySnapshot ; rdfs:range xsd:integer ; owl:versionInfo "Added in health v2.5" . health:standHours a owl:DatatypeProperty ; rdfs:label "Stand Hours"@en ; rdfs:comment "Hours in which standing was recorded for a single day. Bounded 0-24."@en ; rdfs:domain health:DailyActivitySnapshot ; rdfs:range xsd:integer ; owl:versionInfo "Added in health v2.5" . health:durationHours a owl:DatatypeProperty ; rdfs:label "Sleep Duration (hours)"@en ; rdfs:comment "Total sleep duration in hours for a single night."@en ; rdfs:domain health:DailySleepSnapshot ; rdfs:range xsd:decimal ; owl:versionInfo "Added in health v2.5" . # ---------------------------------------------------------------------------- # Sleep quality individuals (v2.5) # ---------------------------------------------------------------------------- # # health:sleepQuality is emitted with an IRI object, not a string literal: # HealthProfileSerializer writes `health:sleepQuality health:Good` and # HealthProfileDeserializer parses exactly that form. The four individuals it # ranges over were never defined. They are defined here. # # health:sleepQuality itself is left declared as it was (see above). Its # rdfs:range xsd:string does not describe what is emitted, and no known emitter # produces the string form, but correcting the declaration is a separate change # with its own compatibility question and is deferred rather than bundled here. # health:DailySleepSnapshotShape therefore constrains this property's # cardinality and permitted values but asserts no datatype. health:SleepQuality a owl:Class ; rdfs:label "Sleep Quality"@en ; rdfs:comment "Qualitative classification of a night's sleep, as derived by the recording device."@en ; owl:versionInfo "Added in health v2.5" . health:Excellent a owl:NamedIndividual, health:SleepQuality ; rdfs:label "Excellent"@en ; rdfs:comment "Sleep quality classified as excellent."@en . health:Good a owl:NamedIndividual, health:SleepQuality ; rdfs:label "Good"@en ; rdfs:comment "Sleep quality classified as good."@en . health:Fair a owl:NamedIndividual, health:SleepQuality ; rdfs:label "Fair"@en ; rdfs:comment "Sleep quality classified as fair."@en . health:Poor a owl:NamedIndividual, health:SleepQuality ; rdfs:label "Poor"@en ; rdfs:comment "Sleep quality classified as poor."@en . ## Ontology: clinical ## File: clinical.ttl @prefix clinical: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix prov: . @prefix health: . # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports ; dct:title "Cascade Protocol Clinical Document Ontology"@en ; dct:description "Vocabulary for clinical documents, narratives, and structured notes imported from EHR systems via Apple HealthKit"@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2025-12-26"^^xsd:date ; dct:modified "2026-08-14"^^xsd:date ; owl:versionInfo "1.15" ; rdfs:comment "Domain-specific vocabulary for clinical documentation and structured records imported from patient portals (MyChart, Epic, etc.). Contains EHR-verified clinical data."@en ; rdfs:seeAlso . # ============================================================================ # Core Classes # ============================================================================ clinical:ClinicalDocument a owl:Class ; rdfs:label "Clinical Document"@en ; rdfs:comment "Clinical documentation imported from EHR systems, including narratives and structured data"@en ; rdfs:subClassOf fhir:DocumentReference ; rdfs:subClassOf prov:Entity ; rdfs:subClassOf [ a owl:Restriction ; owl:onProperty clinical:importedAt ; owl:cardinality 1 ] ; rdfs:subClassOf [ a owl:Restriction ; owl:onProperty clinical:sourceEHR ; owl:cardinality 1 ] . clinical:ClinicalSection a owl:Class ; rdfs:label "Clinical Section"@en ; rdfs:comment "Section within a clinical document (e.g., History of Present Illness, Assessment, Plan)"@en . clinical:ClinicalNarrative a owl:Class ; rdfs:label "Clinical Narrative"@en ; rdfs:comment "Free-text clinical narrative extracted from FHIR text.div element"@en . # ============================================================================ # Document Type Classes (LOINC-aligned) # ============================================================================ clinical:ProgressNote a owl:Class ; rdfs:label "Progress Note"@en ; rdfs:comment "Provider progress note documenting a patient encounter"@en ; rdfs:subClassOf clinical:ClinicalDocument . clinical:DischargeSummary a owl:Class ; rdfs:label "Discharge Summary"@en ; rdfs:comment "Hospital discharge summary document"@en ; rdfs:subClassOf clinical:ClinicalDocument . clinical:ConsultationNote a owl:Class ; rdfs:label "Consultation Note"@en ; rdfs:comment "Specialist consultation note"@en ; rdfs:subClassOf clinical:ClinicalDocument . clinical:LaboratoryReport a owl:Class ; rdfs:label "Laboratory Report"@en ; rdfs:comment "Laboratory test results with narrative interpretation"@en ; rdfs:subClassOf clinical:ClinicalDocument ; rdfs:subClassOf fhir:DiagnosticReport . clinical:ImagingReport a owl:Class ; rdfs:label "Imaging Report"@en ; rdfs:comment "Radiology/imaging report with findings and impressions"@en ; rdfs:subClassOf clinical:ClinicalDocument . clinical:VisitSummary a owl:Class ; rdfs:label "Visit Summary"@en ; rdfs:comment "After-visit summary provided to patient"@en ; rdfs:subClassOf clinical:ClinicalDocument . # ============================================================================ # Structured Clinical Record Classes # ============================================================================ clinical:Medication a owl:Class ; rdfs:label "Medication"@en ; rdfs:comment "Medication record from EHR MedicationStatement or MedicationRequest resource"@en ; rdfs:subClassOf fhir:MedicationStatement ; rdfs:subClassOf prov:Entity . clinical:Allergy a owl:Class ; rdfs:label "Allergy"@en ; rdfs:comment "Allergy or intolerance record from EHR AllergyIntolerance resource. DEPRECATED in clinical v1.13: use health:AllergyRecord, which is the type every import path already emits. Retained, not removed: the pod export path is still the sole emitter of this class and existing pods contain it. Readers must continue to accept both."@en ; rdfs:subClassOf fhir:AllergyIntolerance ; rdfs:subClassOf prov:Entity ; owl:deprecated true ; rdfs:seeAlso ; owl:versionInfo "Deprecated in clinical v1.13; superseded by health:AllergyRecord" . clinical:LabResult a owl:Class ; rdfs:label "Lab Result"@en ; rdfs:comment "Laboratory test result from EHR Observation resource. DEPRECATED in clinical v1.13: use health:LabResultRecord, which is the type every import path already emits. Retained, not removed: the pod export path is still the sole emitter of this class and existing pods contain it. Readers must continue to accept both."@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; owl:deprecated true ; rdfs:seeAlso ; owl:versionInfo "Deprecated in clinical v1.13; superseded by health:LabResultRecord" . clinical:Condition a owl:Class ; rdfs:label "Condition"@en ; rdfs:comment "Medical condition or diagnosis from EHR Condition resource. DEPRECATED in clinical v1.13: use health:ConditionRecord, which is the type every import path already emits. Retained, not removed: the pod export path is still the sole emitter of this class and existing pods contain it. Readers must continue to accept both."@en ; rdfs:subClassOf fhir:Condition ; rdfs:subClassOf prov:Entity ; owl:deprecated true ; rdfs:seeAlso ; owl:versionInfo "Deprecated in clinical v1.13; superseded by health:ConditionRecord" . clinical:Immunization a owl:Class ; rdfs:label "Immunization"@en ; rdfs:comment "Vaccination record from EHR Immunization resource. DEPRECATED in clinical v1.13: use health:ImmunizationRecord, which is the type every import path already emits. Retained, not removed: the pod export path is still the sole emitter of this class and existing pods contain it. Readers must continue to accept both."@en ; rdfs:subClassOf fhir:Immunization ; rdfs:subClassOf prov:Entity ; owl:deprecated true ; rdfs:seeAlso ; owl:versionInfo "Deprecated in clinical v1.13; superseded by health:ImmunizationRecord" . clinical:Procedure a owl:Class ; rdfs:label "Procedure"@en ; rdfs:comment "Medical procedure record from EHR Procedure resource"@en ; rdfs:subClassOf fhir:Procedure ; rdfs:subClassOf prov:Entity . clinical:VitalSign a owl:Class ; rdfs:label "Vital Sign"@en ; rdfs:comment "Vital sign observation (heart rate, blood pressure, etc.) from EHR"@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity . # ============================================================================ # Longitudinal Record Classes (v1.2) # ============================================================================ clinical:MedicationUseEpisode a owl:Class ; rdfs:label "Medication Use Episode"@en ; rdfs:comment "Canonical longitudinal record of patient's relationship with a medication over time. Derived from one or more source records via reconciliation. Episodes represent continuous periods of use - a gap beyond threshold creates a new episode."@en ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso . clinical:Supplement a owl:Class ; rdfs:label "Supplement"@en ; rdfs:comment "Dietary supplement, OTC product, or herbal remedy with explicit regulatory status. Separate from Medication class due to different regulatory status and evidence requirements."@en ; rdfs:subClassOf prov:Entity . # ============================================================================ # Longitudinal Lab Classes (v1.4) # ============================================================================ clinical:LabTestSeries a owl:Class ; rdfs:label "Lab Test Series"@en ; rdfs:comment """Longitudinal record of repeated measurements of the same laboratory test (identified by LOINC code) over time. Derived from one or more clinical:LabResult source records via reconciliation. Carries trending analysis, inferred status, and source linkage. The lab equivalent of MedicationUseEpisode. Unlike medication episodes where a 90-day gap creates a new episode, lab tests have no gap concept — one series per LOINC code per patient."""@en ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso . # ============================================================================ # Coverage Record Class (v1.5) # ============================================================================ clinical:CoverageRecord a owl:Class ; rdfs:label "Coverage Record"@en ; rdfs:comment "DEPRECATED (v1.5+): Use coverage:InsurancePlan from coverage.ttl for new insurance data. This class remains for backward compatibility with existing EHR import pipeline data. Insurance coverage record imported from EHR. Layer 2 representation of patient insurance data with FHIR Coverage alignment."@en ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso fhir:Coverage ; owl:deprecated "true"^^xsd:boolean ; cascade:subConcern "deprecated" . # ============================================================================ # Data Properties - LabTestSeries (v1.4) # ============================================================================ # --- Identity --- clinical:canonicalTestName a owl:DatatypeProperty ; rdfs:label "Canonical Test Name"@en ; rdfs:comment "Normalized test name for the lab test series (e.g., 'Hemoglobin A1C')"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:testLoincCode a owl:DatatypeProperty ; rdfs:label "Test LOINC Code"@en ; rdfs:comment "LOINC code identifying this test series (e.g., '4548-4' for HbA1c). All results in the series share this code."@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:testConceptUri a owl:ObjectProperty ; rdfs:label "Test Concept URI"@en ; rdfs:comment "Stable LOINC IRI for the test (e.g., loinc:4548-4). Enables linked-data interoperability."@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range rdfs:Resource . clinical:seriesCategory a owl:DatatypeProperty ; rdfs:label "Series Category"@en ; rdfs:comment "Lab category for the series (e.g., hematology, metabolic, lipid, thyroid). Maps to observation-category vocabulary."@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . # --- Longitudinal Tracking --- clinical:firstResultDate a owl:DatatypeProperty ; rdfs:label "First Result Date"@en ; rdfs:comment "Earliest clinical date from any source result in this series"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:dateTime . clinical:lastResultDate a owl:DatatypeProperty ; rdfs:label "Last Result Date"@en ; rdfs:comment "Most recent clinical date from any source result in this series"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:dateTime . clinical:resultCount a owl:DatatypeProperty ; rdfs:label "Result Count"@en ; rdfs:comment "Number of individual results in this series"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:integer . # --- Current State (Most Recent Result) --- clinical:latestValue a owl:DatatypeProperty ; rdfs:label "Latest Value"@en ; rdfs:comment "Value of the most recent result in the series"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:latestUnit a owl:DatatypeProperty ; rdfs:label "Latest Unit"@en ; rdfs:comment "Unit of the most recent result (UCUM code)"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:latestInterpretation a owl:DatatypeProperty ; rdfs:label "Latest Interpretation"@en ; rdfs:comment "Interpretation code of the most recent result (FHIR v3-ObservationInterpretation: H, L, N, HH, LL, A, AA)"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . # --- Trend Inference --- clinical:trendDirection a owl:DatatypeProperty ; rdfs:label "Trend Direction"@en ; rdfs:comment "Inferred trend direction: increasing, decreasing, stable, or insufficient_data"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:trendConfidence a owl:DatatypeProperty ; rdfs:label "Trend Confidence"@en ; rdfs:comment "Confidence in the trend inference: high, medium, or low"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:trendReason a owl:DatatypeProperty ; rdfs:label "Trend Reason"@en ; rdfs:comment "Explainable reason for the trend inference (e.g., '3-point upward trend over 18 months')"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . # --- Abnormal History --- clinical:hasAbnormalHistory a owl:DatatypeProperty ; rdfs:label "Has Abnormal History"@en ; rdfs:comment "Whether any result in the series was interpreted as abnormal or critical"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:boolean . # --- Current Reference Range --- clinical:currentReferenceRange a owl:DatatypeProperty ; rdfs:label "Current Reference Range"@en ; rdfs:comment "Most recent reference range text (e.g., '4.0-5.6 %')"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:string . clinical:currentReferenceRangeLow a owl:DatatypeProperty ; rdfs:label "Current Reference Range Low"@en ; rdfs:comment "Lower bound of the most recent reference range"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:decimal . clinical:currentReferenceRangeHigh a owl:DatatypeProperty ; rdfs:label "Current Reference Range High"@en ; rdfs:comment "Upper bound of the most recent reference range"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range xsd:decimal . # --- Panel Membership --- clinical:seriesPanelMembership a owl:ObjectProperty ; rdfs:label "Series Panel Membership"@en ; rdfs:comment "Reference to a panel (e.g., CMP, CBC) this test series is typically part of"@en ; rdfs:domain clinical:LabTestSeries ; rdfs:range rdfs:Resource . # Note: derivedFromRecord, sourceExternalId, and hasUnresolvedConflicts are # reused from MedicationUseEpisode (domains broadened in v1.4 to apply to # all longitudinal record types). # ============================================================================ # Data Properties - CoverageRecord (v1.5) # ============================================================================ clinical:providerName a owl:DatatypeProperty ; rdfs:label "Provider Name"@en ; rdfs:comment "Insurance company or plan provider name."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:memberId a owl:DatatypeProperty ; rdfs:label "Member ID"@en ; rdfs:comment "Insurance member/subscriber identifier."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:groupNumber a owl:DatatypeProperty ; rdfs:label "Group Number"@en ; rdfs:comment "Employer group number for employer-sponsored plans."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:planName a owl:DatatypeProperty ; rdfs:label "Plan Name"@en ; rdfs:comment "Name of the insurance plan."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:planType a owl:DatatypeProperty ; rdfs:label "Plan Type"@en ; rdfs:comment "Type of insurance plan: hmo, ppo, epo, pos, hdhp, medicare, medicaid, tricare, other."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:coverageType a owl:DatatypeProperty ; rdfs:label "Coverage Type"@en ; rdfs:comment "Classification of coverage: medical, dental, vision, prescription, mental_health."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:relationship a owl:DatatypeProperty ; rdfs:label "Relationship"@en ; rdfs:comment "Beneficiary relationship to subscriber: self, spouse, child, parent, other."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:effectivePeriodStart a owl:DatatypeProperty ; rdfs:label "Effective Period Start"@en ; rdfs:comment "Start date of coverage period."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:date . clinical:effectivePeriodEnd a owl:DatatypeProperty ; rdfs:label "Effective Period End"@en ; rdfs:comment "End date of coverage period."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:date . clinical:payorName a owl:DatatypeProperty ; rdfs:label "Payor Name"@en ; rdfs:comment "Name of the organization paying for coverage."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . clinical:subscriberId a owl:DatatypeProperty ; rdfs:label "Subscriber ID"@en ; rdfs:comment "Identifier for the policy holder from the insurer."@en ; rdfs:domain clinical:CoverageRecord ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Document Metadata # ============================================================================ clinical:importedAt a owl:DatatypeProperty ; rdfs:label "Imported At"@en ; rdfs:comment "ISO 8601 timestamp when document was imported from Apple Health"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:dateTime . clinical:sourceEHR a owl:DatatypeProperty ; rdfs:label "Source EHR"@en ; rdfs:comment "Name of the source EHR system (e.g., 'Epic MyChart', 'Cerner'). Applicable to ClinicalDocument, LabResult, and other EHR-sourced records."@en ; rdfs:range xsd:string . clinical:sourceBundleId a owl:DatatypeProperty ; rdfs:label "Source Bundle ID"@en ; rdfs:comment "iOS app bundle identifier of the source (e.g., 'com.epic.mychart')"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:string . clinical:fhirResourceId a owl:DatatypeProperty ; rdfs:label "FHIR Resource ID"@en ; rdfs:comment "Original FHIR resource identifier from the source system"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:string . clinical:fhirResourceType a owl:DatatypeProperty ; rdfs:label "FHIR Resource Type"@en ; rdfs:comment "FHIR resource type (e.g., 'DiagnosticReport', 'Composition')"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:string . clinical:documentDate a owl:DatatypeProperty ; rdfs:label "Document Date"@en ; rdfs:comment "Date the clinical document was created in the EHR"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:dateTime . clinical:encounterDate a owl:DatatypeProperty ; rdfs:label "Encounter Date"@en ; rdfs:comment "Date of the clinical encounter this document relates to"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:dateTime . clinical:displayName a owl:DatatypeProperty ; rdfs:label "Display Name"@en ; rdfs:comment "Human-readable display name from the EHR"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Narrative Content # ============================================================================ clinical:narrativeText a owl:DatatypeProperty ; rdfs:label "Narrative Text"@en ; rdfs:comment "Plain text extracted from FHIR text.div element (HTML tags stripped)"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:string . clinical:narrativeXHTML a owl:DatatypeProperty ; rdfs:label "Narrative XHTML"@en ; rdfs:comment "Original XHTML content from FHIR Narrative element"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:string . clinical:rawFHIRData a owl:DatatypeProperty ; rdfs:label "Raw FHIR Data"@en ; rdfs:comment "Base64-encoded original FHIR JSON/XML for lossless preservation"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range xsd:base64Binary . # ============================================================================ # Object Properties - Sections # ============================================================================ clinical:hasSection a owl:ObjectProperty ; rdfs:label "Has Section"@en ; rdfs:comment "Links a clinical document to its component sections"@en ; rdfs:domain clinical:ClinicalDocument ; rdfs:range clinical:ClinicalSection . clinical:sectionCode a owl:DatatypeProperty ; rdfs:label "Section Code"@en ; rdfs:comment "LOINC code identifying the section type"@en ; rdfs:domain clinical:ClinicalSection ; rdfs:range xsd:string . clinical:sectionTitle a owl:DatatypeProperty ; rdfs:label "Section Title"@en ; rdfs:comment "Human-readable section title (e.g., 'History of Present Illness')"@en ; rdfs:domain clinical:ClinicalSection ; rdfs:range xsd:string . clinical:sectionContent a owl:DatatypeProperty ; rdfs:label "Section Content"@en ; rdfs:comment "Plain text content of the section"@en ; rdfs:domain clinical:ClinicalSection ; rdfs:range xsd:string . clinical:sectionOrder a owl:DatatypeProperty ; rdfs:label "Section Order"@en ; rdfs:comment "Integer indicating section order within the document"@en ; rdfs:domain clinical:ClinicalSection ; rdfs:range xsd:integer . # ============================================================================ # Data Properties - Structured Records (Shared) # ============================================================================ clinical:recordDate a owl:DatatypeProperty ; rdfs:label "Record Date"@en ; rdfs:comment "Date the clinical record was created or observed"@en ; rdfs:range xsd:dateTime . clinical:status a owl:DatatypeProperty ; rdfs:label "Status"@en ; rdfs:comment """Lifecycle status of the record. Permitted values depend on the record class and are constrained per-shape, not on this property. INTENDED VALUE SETS (FHIR R4). Recorded here in v1.13 because they are only partly enforced, and the gap should be visible in the vocabulary rather than only in the shapes: clinical:Immunization ImmunizationStatusCodes completed | entered-in-error | not-done -- ENFORCED by clinical:ImmunizationShape clinical:Procedure EventStatus -- ENFORCED by clinical:ProcedureShape clinical:Medication MedicationRequest.status active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown -- NOT ENFORCED by clinical:MedicationShape The omission on medications is deliberate as of v1.13, not an oversight. Emitted data uses at least one value outside the FHIR set ("discontinued"; the FHIR equivalents are "stopped" and "ended"), so adding sh:in would be a BREAKING change for existing pods, not a clarification: records that validate today would start failing without any emitter having been corrected first. The ordered fix is to map emitters onto the FHIR value set, then add the constraint in a release explicitly flagged as tightening."""@en ; rdfs:seeAlso ; rdfs:range xsd:string . clinical:loincCode a owl:DatatypeProperty ; rdfs:label "LOINC Code"@en ; rdfs:comment "LOINC code for the clinical concept"@en ; rdfs:range xsd:string . clinical:snomedCode a owl:DatatypeProperty ; rdfs:label "SNOMED Code"@en ; rdfs:comment "SNOMED CT code for the clinical concept"@en ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Medication # ============================================================================ clinical:drugName a owl:DatatypeProperty ; rdfs:label "Drug Name"@en ; rdfs:comment "Name of the medication"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:dosage a owl:DatatypeProperty ; rdfs:label "Dosage"@en ; rdfs:comment "Dosage information for the medication"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:route a owl:DatatypeProperty ; rdfs:label "Route"@en ; rdfs:comment "Route of administration (oral, IV, etc.)"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:frequency a owl:DatatypeProperty ; rdfs:label "Frequency"@en ; rdfs:comment "How often medication is taken"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:rxNormCode a owl:DatatypeProperty ; rdfs:label "RxNorm Code"@en ; rdfs:comment "RxNorm code for the medication"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:ndcCode a owl:DatatypeProperty ; rdfs:label "NDC Code"@en ; rdfs:comment "National Drug Code for the medication"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:prescriber a owl:DatatypeProperty ; rdfs:label "Prescriber"@en ; rdfs:comment "Name of the prescribing physician"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:indication a owl:DatatypeProperty ; rdfs:label "Indication"@en ; rdfs:comment "Clinical reason for the medication (why prescribed)"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:courseOfTherapyType a owl:DatatypeProperty ; rdfs:label "Course of Therapy Type"@en ; rdfs:comment "Prescribing workflow classification: acute (one-time/short), continuous (ongoing), or unknown"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:asNeeded a owl:DatatypeProperty ; rdfs:label "As Needed (PRN)"@en ; rdfs:comment "Whether medication is taken as needed rather than on a fixed schedule"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:boolean . clinical:medicationForm a owl:DatatypeProperty ; rdfs:label "Medication Form"@en ; rdfs:comment "Physical form of the medication (tablet, capsule, liquid, injection, etc.)"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:activeIngredient a owl:DatatypeProperty ; rdfs:label "Active Ingredient"@en ; rdfs:comment "Primary active ingredient name"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:ingredientStrength a owl:DatatypeProperty ; rdfs:label "Ingredient Strength"@en ; rdfs:comment "Strength of the active ingredient (e.g. '5 mg per tablet')"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:provenanceClass a owl:DatatypeProperty ; rdfs:label "Provenance Class"@en ; rdfs:comment "Source classification for editability: healthKitFHIR, userTracked, pharmacyClaim, or imported. Applicable to Medication, LabResult, and other clinical records."@en ; rdfs:range xsd:string . clinical:sourceFhirResourceType a owl:DatatypeProperty ; rdfs:label "Source FHIR Resource Type"@en ; rdfs:comment "Which FHIR resource type this record originated from"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:clinicalIntent a owl:DatatypeProperty ; rdfs:label "Clinical Intent"@en ; rdfs:comment "Clinical intent derived from source: reportedUse, prescribed, dispensed, or administered"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:refillsAllowed a owl:DatatypeProperty ; rdfs:label "Refills Allowed"@en ; rdfs:comment "Number of prescription refills authorized"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:integer . clinical:supplyDurationDays a owl:DatatypeProperty ; rdfs:label "Supply Duration Days"@en ; rdfs:comment "Number of days each fill is intended to supply"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:integer . clinical:dispensedQuantity a owl:DatatypeProperty ; rdfs:label "Dispensed Quantity"@en ; rdfs:comment "Quantity dispensed per fill (e.g. '90 tablets')"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:prescriptionCategory a owl:DatatypeProperty ; rdfs:label "Prescription Category"@en ; rdfs:comment "Prescribing context: community, inpatient, or discharge"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:administrationRoute a owl:DatatypeProperty ; rdfs:label "Administration Route"@en ; rdfs:comment "Route of administration: oral, intravenous, subcutaneous, intramuscular, topical, inhaled, etc."@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Allergy # ============================================================================ clinical:allergen a owl:DatatypeProperty ; rdfs:label "Allergen"@en ; rdfs:comment "Name of the allergen or substance"@en ; rdfs:domain clinical:Allergy ; rdfs:range xsd:string . clinical:reaction a owl:DatatypeProperty ; rdfs:label "Reaction"@en ; rdfs:comment "Description of the allergic reaction"@en ; rdfs:domain clinical:Allergy ; rdfs:range xsd:string . clinical:severity a owl:DatatypeProperty ; rdfs:label "Severity"@en ; rdfs:comment "Severity of the allergy (mild, moderate, severe)"@en ; rdfs:domain clinical:Allergy ; rdfs:range xsd:string . clinical:allergyCategory a owl:DatatypeProperty ; rdfs:label "Allergy Category"@en ; rdfs:comment "Category of allergy (food, medication, environment, biologic)"@en ; rdfs:domain clinical:Allergy ; rdfs:range xsd:string . clinical:criticality a owl:DatatypeProperty ; rdfs:label "Criticality"@en ; rdfs:comment "Potential clinical harm (low, high, unable-to-assess)"@en ; rdfs:domain clinical:Allergy ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Lab Result # ============================================================================ clinical:testName a owl:DatatypeProperty ; rdfs:label "Test Name"@en ; rdfs:comment "Name of the laboratory test"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:value a owl:DatatypeProperty ; rdfs:label "Value"@en ; rdfs:comment "Numeric or string value of the result"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:unit a owl:DatatypeProperty ; rdfs:label "Unit"@en ; rdfs:comment "Unit of measurement for the value"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:referenceRange a owl:DatatypeProperty ; rdfs:label "Reference Range"@en ; rdfs:comment "Normal reference range for the test"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:interpretation a owl:DatatypeProperty ; rdfs:label "Interpretation"@en ; rdfs:comment """Categorical reading of an observation against its reference range, as a code from the HL7 v3 ObservationInterpretation code system, the code system FHIR R4 binds Observation.interpretation to. Any of the 15 data-absent-reason codes is also accepted, for a source whose interpretation element was absent or null-flavoured. The ten English words of the pre-v1.14 enum are retained so older data keeps validating and are not recommended for new writes. ONE VALUE SET ACROSS BOTH OBSERVATION SHAPES, as of v1.15. Through v1.14, clinical:LabResultShape bound this property with sh:in and clinical:VitalSignShape did not, so the same predicate on the same kind of value was checked on labs and unchecked on vitals. That gap was recorded as intentional because emitted vital data used "elevated", which is in neither ratified set, and constraining vitals would have failed those records. v1.15 closes it the way core v3.5 wrote down for exactly this situation: the value set is applied to clinical:VitalSignShape at sh:Warning, not sh:Violation, so the records that carry a non-ratified word are reported rather than rejected, and it is raised to sh:Violation in a later clinical version only after a release in which the warning is observably absent from conforming output. "elevated" migrates to the ratified code plus a verbatim clinical:interpretationSourceCode. A source code in NEITHER value set is carried verbatim on clinical:interpretationSourceCode rather than being forced into this property or dropped."""@en ; rdfs:seeAlso ; rdfs:seeAlso ; rdfs:seeAlso ; rdfs:range xsd:string . clinical:interpretationSourceCode a owl:DatatypeProperty ; rdfs:label "Interpretation Source Code"@en ; rdfs:comment """The interpretation code the SOURCE wrote, copied verbatim, for the case where that code is a member of neither value set clinical:interpretation is bound to (HL7 v3 ObservationInterpretation, and data-absent-reason). Kept semantically identical to health:interpretationSourceCode, for the reason recorded on the interpretation shapes: these are two spellings of one record, and letting them drift is how one spelling silently becomes the lenient one. WRITE IT ONLY THEN. A code that IS in one of the bound value sets belongs on clinical:interpretation and must not be duplicated here. It is deliberately unconstrained: no value set, no pattern, no case folding. A producer that recognises the source code's intent should ALSO write its nearest ratified equivalent on clinical:interpretation, so a consumer reading only the bound property still gets a usable reading."""@en ; rdfs:range xsd:string ; rdfs:seeAlso clinical:interpretation ; rdfs:seeAlso health:interpretationSourceCode ; owl:versionInfo "Added in clinical v1.15" . clinical:specimenType a owl:DatatypeProperty ; rdfs:label "Specimen Type"@en ; rdfs:comment "Type of specimen collected (blood, urine, etc.)"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Condition # ============================================================================ clinical:conditionName a owl:DatatypeProperty ; rdfs:label "Condition Name"@en ; rdfs:comment "Name of the medical condition or diagnosis"@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:string . clinical:onsetDate a owl:DatatypeProperty ; rdfs:label "Onset Date"@en ; rdfs:comment "Date when the condition began"@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:dateTime . clinical:abatementDate a owl:DatatypeProperty ; rdfs:label "Abatement Date"@en ; rdfs:comment "Date when the condition resolved"@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:dateTime . clinical:clinicalStatus a owl:DatatypeProperty ; rdfs:label "Clinical Status"@en ; rdfs:comment "Clinical status (active, recurrence, relapse, inactive, remission, resolved)"@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:string . clinical:verificationStatus a owl:DatatypeProperty ; rdfs:label "Verification Status"@en ; rdfs:comment "Verification status (unconfirmed, provisional, differential, confirmed, refuted)"@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:string . clinical:icd10Code a owl:DatatypeProperty ; rdfs:label "ICD-10 Code"@en ; rdfs:comment "ICD-10-CM diagnosis code"@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:string . clinical:linkedConditionIds a owl:DatatypeProperty ; rdfs:label "Linked Condition IDs"@en ; rdfs:comment "DEPRECATED (v1.10): use the clinical:linkedCondition ObjectProperty instead, which links to related conditions by IRI and is traversable RDF. This property encoded links as space-separated UUIDs in a single literal, which no graph query can follow. Retained for backward compatibility with existing Checkup data that may carry it; not emitted for new data."@en ; rdfs:domain clinical:Condition ; rdfs:range xsd:string ; owl:deprecated "true"^^xsd:boolean ; rdfs:seeAlso clinical:linkedCondition . # ============================================================================ # Data Properties - Immunization # ============================================================================ clinical:vaccineName a owl:DatatypeProperty ; rdfs:label "Vaccine Name"@en ; rdfs:comment "Name of the vaccine administered"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:vaccineCode a owl:DatatypeProperty ; rdfs:label "Vaccine Code"@en ; rdfs:comment "CVX code for the vaccine (legacy — use clinical:cvxCode for new serialization; v1.6)"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:lotNumber a owl:DatatypeProperty ; rdfs:label "Lot Number"@en ; rdfs:comment "Vaccine lot number"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:site a owl:DatatypeProperty ; rdfs:label "Site"@en ; rdfs:comment "Body site where vaccine was administered"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:doseNumber a owl:DatatypeProperty ; rdfs:label "Dose Number"@en ; rdfs:comment "Dose number in a series (e.g., 1, 2, booster)"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:manufacturer a owl:DatatypeProperty ; rdfs:label "Manufacturer"@en ; rdfs:comment "Vaccine manufacturer"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:expirationDate a owl:DatatypeProperty ; rdfs:label "Expiration Date"@en ; rdfs:comment "Vaccine expiration date"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:date . # ============================================================================ # Data Properties - Procedure # ============================================================================ clinical:procedureName a owl:DatatypeProperty ; rdfs:label "Procedure Name"@en ; rdfs:comment """Name of the procedure performed. This is the CANONICAL and only defined spelling: it is the property whose rdfs:domain is clinical:Procedure, the one clinical:ProcedureShape names, and the one the JSON-LD context maps. THE health: SPELLING IS NOT A SECOND SPELLING (v1.15). A C-CDA import path has been writing the procedure name to a `health:procedureName` predicate that the health vocabulary does not define, on records it types clinical:Procedure. The result was a record that CARRIES a name, fails clinical:ProcedureShape's name requirement as though it had none, and carries that name on a predicate no shape targets, so the name itself is validated by nothing. This is resolved in favour of clinical:procedureName rather than by blessing the health: spelling, for three reasons. There is no health-namespace procedure vocabulary at all: no health:ProcedureRecord, no health:procedureName, so ratifying the emission would mint an undefined-term family of exactly the kind health v2.5 spent a release eliminating. The record is typed clinical:Procedure, so a health: name predicate on it is a namespace mismatch inside a single record rather than a coherent alternative serialization. And the repository's one dual-spelling precedent, health:DailyVitalReadingShape's sh:or over cascade:date and health:date, exists because BOTH serializations are live and defined; here only one spelling is defined, so that precedent does not reach this case. MIGRATION, and what a reader must re-query. clinical:ProcedureShape satisfies its name requirement through sh:or over both spellings from v1.15, so a Pod already holding health:procedureName triples stops failing immediately and no data has to be rewritten to become valid. A separate warning-severity shape fires wherever the health: spelling is present. Producers write clinical:procedureName only. Both the sh:or alternative and the warning shape are removed in a later clinical version, once the warning is observably absent from conforming output; each step is its own vocabulary version. A consumer querying health:procedureName must add clinical:procedureName to its query now and can drop the health: term at that later version."""@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string ; owl:versionInfo "Canonical spelling restated, and the health: emission given a migration window, in clinical v1.15" . clinical:procedureDate a owl:DatatypeProperty ; rdfs:label "Procedure Date"@en ; rdfs:comment "Date when the procedure was performed"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:dateTime . clinical:bodySite a owl:DatatypeProperty ; rdfs:label "Body Site"@en ; rdfs:comment "Body site where procedure was performed"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . clinical:performer a owl:DatatypeProperty ; rdfs:label "Performer"@en ; rdfs:comment "Name or identifier of the procedure performer"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . clinical:cptCode a owl:DatatypeProperty ; rdfs:label "CPT Code"@en ; rdfs:comment "CPT procedure code"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . clinical:outcome a owl:DatatypeProperty ; rdfs:label "Outcome"@en ; rdfs:comment "Outcome of the procedure"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . clinical:procedureStatus a owl:DatatypeProperty ; rdfs:label "Procedure Status"@en ; rdfs:comment "FHIR event-status: preparation, in-progress, not-done, on-hold, stopped, completed, entered-in-error, unknown."@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . clinical:procedureCategory a owl:DatatypeProperty ; rdfs:label "Procedure Category"@en ; rdfs:comment "Classification category of the procedure (e.g., surgical, diagnostic, counseling)."@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . clinical:procedureSnomedCode a owl:DatatypeProperty ; rdfs:label "Procedure SNOMED Code"@en ; rdfs:comment "SNOMED CT concept code for the procedure."@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Vital Sign # ============================================================================ clinical:vitalType a owl:DatatypeProperty ; rdfs:label "Vital Type"@en ; rdfs:comment "Type of vital sign (heart_rate, blood_pressure, temperature, etc.)"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:systolicValue a owl:DatatypeProperty ; rdfs:label "Systolic Value"@en ; rdfs:comment "Systolic blood pressure value"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:decimal . clinical:diastolicValue a owl:DatatypeProperty ; rdfs:label "Diastolic Value"@en ; rdfs:comment "Diastolic blood pressure value"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:decimal . clinical:deviceName a owl:DatatypeProperty ; rdfs:label "Device Name"@en ; rdfs:comment "Name of the measuring device"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:measurementMethod a owl:DatatypeProperty ; rdfs:label "Measurement Method"@en ; rdfs:comment "Method used for measurement"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:valueString a owl:DatatypeProperty ; rdfs:label "Value String"@en ; rdfs:comment "String representation of a value for non-numeric results"@en ; rdfs:range xsd:string . clinical:effectiveDate a owl:DatatypeProperty ; rdfs:label "Effective Date"@en ; rdfs:comment "Date/time when measurement or observation was taken"@en ; rdfs:range xsd:dateTime . # ============================================================================ # Data Properties - Additional Medication # ============================================================================ clinical:instructions a owl:DatatypeProperty ; rdfs:label "Instructions"@en ; rdfs:comment "Patient instructions for taking the medication"@en ; rdfs:domain clinical:Medication ; rdfs:range xsd:string . clinical:startDate a owl:DatatypeProperty ; rdfs:label "Start Date"@en ; rdfs:comment "Start date for medication or treatment"@en ; rdfs:range xsd:dateTime . clinical:endDate a owl:DatatypeProperty ; rdfs:label "End Date"@en ; rdfs:comment "End date for medication or treatment"@en ; rdfs:range xsd:dateTime . # ============================================================================ # Data Properties - Additional Allergy # ============================================================================ clinical:allergyType a owl:DatatypeProperty ; rdfs:label "Allergy Type"@en ; rdfs:comment "Type of allergy (allergy vs intolerance)"@en ; rdfs:domain clinical:Allergy ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Additional Lab Result # ============================================================================ clinical:orderingProvider a owl:DatatypeProperty ; rdfs:label "Ordering Provider"@en ; rdfs:comment "Name of the clinician who ordered the laboratory test"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:performingLab a owl:DatatypeProperty ; rdfs:label "Performing Lab"@en ; rdfs:comment "Name of the laboratory that performed the test"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:observationStatus a owl:DatatypeProperty ; rdfs:label "Observation Status"@en ; rdfs:comment "FHIR observation-status code: final, preliminary, amended, corrected, cancelled, entered-in-error. See http://hl7.org/fhir/observation-status"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:sourceRecordId a owl:DatatypeProperty ; rdfs:label "Source Record ID"@en ; rdfs:comment "FHIR resource identifier from the source system for traceability"@en ; rdfs:range xsd:string . clinical:panelMembership a owl:ObjectProperty ; rdfs:label "Panel Membership"@en ; rdfs:comment "Back-reference from an individual lab result to the panel container (e.g., CMP, CBC) it belongs to. Uses fhir:hasMember on the panel side; this is the inverse link on the member side."@en ; rdfs:domain clinical:LabResult ; rdfs:range rdfs:Resource . clinical:referenceRangeLow a owl:DatatypeProperty ; rdfs:label "Reference Range Low"@en ; rdfs:comment "Lower bound of normal reference range"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:decimal . clinical:referenceRangeHigh a owl:DatatypeProperty ; rdfs:label "Reference Range High"@en ; rdfs:comment "Upper bound of normal reference range"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:decimal . clinical:referenceRangeText a owl:DatatypeProperty ; rdfs:label "Reference Range Text"@en ; rdfs:comment "Reference range as descriptive text"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:string . clinical:issuedDate a owl:DatatypeProperty ; rdfs:label "Issued Date"@en ; rdfs:comment "Date when the result was issued"@en ; rdfs:domain clinical:LabResult ; rdfs:range xsd:dateTime . clinical:category a owl:DatatypeProperty ; rdfs:label "Category"@en ; rdfs:comment "Category classification of the record"@en ; rdfs:range xsd:string . clinical:notes a owl:DatatypeProperty ; rdfs:label "Notes"@en ; rdfs:comment "Additional notes or comments"@en ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Additional Immunization # ============================================================================ clinical:occurrenceDate a owl:DatatypeProperty ; rdfs:label "Occurrence Date"@en ; rdfs:comment "Date when the immunization was administered"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:dateTime . clinical:doseQuantity a owl:DatatypeProperty ; rdfs:label "Dose Quantity"@en ; rdfs:comment "Dose amount administered"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . clinical:cvxCode a owl:DatatypeProperty ; rdfs:label "CVX Code"@en ; rdfs:comment "CDC CVX vaccine code"@en ; rdfs:domain clinical:Immunization ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Additional Procedure # ============================================================================ clinical:performedDate a owl:DatatypeProperty ; rdfs:label "Performed Date"@en ; rdfs:comment "Date when the procedure was performed"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:dateTime . clinical:performedEndDate a owl:DatatypeProperty ; rdfs:label "Performed End Date"@en ; rdfs:comment "End date for procedures that span a period"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:dateTime . clinical:location a owl:DatatypeProperty ; rdfs:label "Location"@en ; rdfs:comment "Location where procedure was performed"@en ; rdfs:domain clinical:Procedure ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Vital Sign Components # ============================================================================ clinical:component1Name a owl:DatatypeProperty ; rdfs:label "Component 1 Name"@en ; rdfs:comment "Name of first vital sign component (e.g., Systolic)"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:component1Value a owl:DatatypeProperty ; rdfs:label "Component 1 Value"@en ; rdfs:comment "Value of first vital sign component"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:decimal . clinical:component1Unit a owl:DatatypeProperty ; rdfs:label "Component 1 Unit"@en ; rdfs:comment "Unit for first vital sign component"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:component1LoincCode a owl:DatatypeProperty ; rdfs:label "Component 1 LOINC Code"@en ; rdfs:comment "LOINC code for first component"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:component2Name a owl:DatatypeProperty ; rdfs:label "Component 2 Name"@en ; rdfs:comment "Name of second vital sign component (e.g., Diastolic)"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:component2Value a owl:DatatypeProperty ; rdfs:label "Component 2 Value"@en ; rdfs:comment "Value of second vital sign component"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:decimal . clinical:component2Unit a owl:DatatypeProperty ; rdfs:label "Component 2 Unit"@en ; rdfs:comment "Unit for second vital sign component"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . clinical:component2LoincCode a owl:DatatypeProperty ; rdfs:label "Component 2 LOINC Code"@en ; rdfs:comment "LOINC code for second component"@en ; rdfs:domain clinical:VitalSign ; rdfs:range xsd:string . # ============================================================================ # Data Properties - MedicationUseEpisode (v1.2) # ============================================================================ clinical:canonicalName a owl:DatatypeProperty ; rdfs:label "Canonical Name"@en ; rdfs:comment "Normalized drug name for the medication episode"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:episodeFirstSeenDate a owl:DatatypeProperty ; rdfs:label "Episode First Seen Date"@en ; rdfs:comment "Earliest CLINICAL date from source records (not import time)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:dateTime . clinical:episodeLastSeenDate a owl:DatatypeProperty ; rdfs:label "Episode Last Seen Date"@en ; rdfs:comment "Most recent CLINICAL date from source records"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:dateTime . clinical:currentDose a owl:DatatypeProperty ; rdfs:label "Current Dose"@en ; rdfs:comment "Current dosage for the medication (most recent from sources)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:currentFrequency a owl:DatatypeProperty ; rdfs:label "Current Frequency"@en ; rdfs:comment "Current frequency of medication use"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:currentRoute a owl:DatatypeProperty ; rdfs:label "Current Route"@en ; rdfs:comment "Current route of administration"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:inferredStatus a owl:DatatypeProperty ; rdfs:label "Inferred Status"@en ; rdfs:comment "Inferred active/inactive status (active, inactive)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:statusConfidence a owl:DatatypeProperty ; rdfs:label "Status Confidence"@en ; rdfs:comment "Confidence level for inferred status (high, medium, low)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:statusReason a owl:DatatypeProperty ; rdfs:label "Status Reason"@en ; rdfs:comment "Reason for status inference (explicitStatus, hasRecentEndDate, noRecentActivity, inferredFromRefills, userOverride)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:usePattern a owl:DatatypeProperty ; rdfs:label "Use Pattern"@en ; rdfs:comment "Pattern of medication use (chronic, shortCourse, oneTime, asNeeded, unknown)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:therapeuticClass a owl:DatatypeProperty ; rdfs:label "Therapeutic Class"@en ; rdfs:comment "Therapeutic classification from ClinicalClassifier"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:string . clinical:conceptUri a owl:ObjectProperty ; rdfs:label "Concept URI"@en ; rdfs:comment "Stable URI for the medication concept (RxNorm URI, NDC URI, or minted Cascade URI)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range rdfs:Resource . clinical:drugCode a owl:ObjectProperty ; rdfs:label "Drug Code"@en ; rdfs:comment "Drug code URI from any terminology system (RxNorm, NDC, SNOMED, ATC)"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range rdfs:Resource . clinical:derivedFromRecord a owl:ObjectProperty ; rdfs:label "Derived From Record"@en ; rdfs:comment "Links a longitudinal record (MedicationUseEpisode or LabTestSeries) to the source record(s) it was derived from"@en ; rdfs:range prov:Entity ; rdfs:subPropertyOf prov:wasDerivedFrom . clinical:sourceExternalId a owl:DatatypeProperty ; rdfs:label "Source External ID"@en ; rdfs:comment "External identifier from source system (FHIR resource ID, claim ID, etc.). Applicable to MedicationUseEpisode, LabTestSeries, and other longitudinal records."@en ; rdfs:range xsd:string . clinical:hasUnresolvedConflicts a owl:DatatypeProperty ; rdfs:label "Has Unresolved Conflicts"@en ; rdfs:comment "Flag indicating longitudinal record has data conflicts requiring user attention. Applicable to MedicationUseEpisode and LabTestSeries."@en ; rdfs:range xsd:boolean . clinical:durationDays a owl:DatatypeProperty ; rdfs:label "Duration Days"@en ; rdfs:comment "Duration of the episode in days"@en ; rdfs:domain clinical:MedicationUseEpisode ; rdfs:range xsd:integer . # ============================================================================ # Data Properties - Supplement (v1.2) # ============================================================================ clinical:supplementName a owl:DatatypeProperty ; rdfs:label "Supplement Name"@en ; rdfs:comment "Name of the supplement or OTC product"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:regulatoryStatus a owl:DatatypeProperty ; rdfs:label "Regulatory Status"@en ; rdfs:comment "Regulatory classification (dietarySupplement, otcDrug, homeopathic, herbalRemedy, unknown)"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:evidenceStrength a owl:DatatypeProperty ; rdfs:label "Evidence Strength"@en ; rdfs:comment "Level of clinical evidence (strongEvidence, moderateEvidence, limitedEvidence, traditionalUse, noEvidence, unknown)"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:brand a owl:DatatypeProperty ; rdfs:label "Brand"@en ; rdfs:comment "Brand name of the supplement"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:form a owl:DatatypeProperty ; rdfs:label "Form"@en ; rdfs:comment "Physical form (capsule, tablet, softgel, liquid, powder, gummy, spray, patch, tea, tincture, other)"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:dsldId a owl:DatatypeProperty ; rdfs:label "DSLD ID"@en ; rdfs:comment "NIH Dietary Supplement Label Database identifier"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:upc a owl:DatatypeProperty ; rdfs:label "UPC"@en ; rdfs:comment "Universal Product Code (barcode)"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:reasonForUse a owl:DatatypeProperty ; rdfs:label "Reason For Use"@en ; rdfs:comment "User-provided reason for taking the supplement"@en ; rdfs:domain clinical:Supplement ; rdfs:range xsd:string . clinical:cascadeUri a owl:ObjectProperty ; rdfs:label "Cascade URI"@en ; rdfs:comment "Stable Cascade Protocol identifier for the supplement"@en ; rdfs:domain clinical:Supplement ; rdfs:range rdfs:Resource . clinical:dose a owl:DatatypeProperty ; rdfs:label "Dose"@en ; rdfs:comment "Dosage amount"@en ; rdfs:range xsd:string . clinical:isActive a owl:DatatypeProperty ; rdfs:label "Is Active"@en ; rdfs:comment "Whether the medication/supplement is currently active"@en ; rdfs:range xsd:boolean . # ============================================================================ # Alignment with Healthcare Standards # ============================================================================ # LOINC Document Type Mappings (from C-CDA) # - Progress note: loinc:11506-3 # - Discharge summary: loinc:18842-5 # - Consultation note: loinc:11488-4 # - History and physical: loinc:34117-2 # - Procedure note: loinc:28570-0 # LOINC Section Codes (common C-CDA sections) # - Chief complaint: loinc:10154-3 # - History of present illness: loinc:10164-2 # - Past medical history: loinc:11348-0 # - Medications: loinc:10160-0 # - Allergies: loinc:48765-2 # - Assessment: loinc:51848-0 # - Plan: loinc:18776-5 # - Results: loinc:30954-2 # SNOMED CT Mappings # - Clinical finding: sct:404684003 # - Clinical document: sct:734163000 # ============================================================================ # Changelog # ============================================================================ # # Version 1.15 (2026-08-14) # - ONE new property, clinical:interpretationSourceCode: the source's verbatim # interpretation code, written only when that code is a member of neither # value set clinical:interpretation is bound to. Kept semantically identical # to health:interpretationSourceCode (health v2.7). The alternative # considered and rejected was accepting the loss with a warning: a closed # value set plus a lossy importer means a laboratory's local flag is either # dropped or written into a field that then fails, and either way the Pod can # no longer answer what the source actually said. # - clinical:VitalSignShape's interpretation is bound to the same value set the # lab shapes use, at sh:Warning. Through v1.14 the lab shapes carried the # sh:in and the vital shape carried none, so the identical predicate was # checked on one record type and unchecked on the other. sh:Warning first, # sh:Violation in a later version once the warning is observably absent from # conforming output, is the ratchet core v3.5 wrote down for precisely this # case: a value that existing data carries is reported, not rejected. # - clinical:interpretation's value set (both shapes) gains the fourteen # data-absent-reason codes it did not already carry. v1.14 accepted "unknown" # alone, which made every reason for an absent interpretation identical; a # C-CDA nullFlavor of NASK, ASKU and NAV are three different clinical facts. # Byte-identical to the health:interpretation list, as before. # - clinical:ProcedureShape's name requirement moves from a bare sh:minCount on # clinical:procedureName to an sh:or over clinical:procedureName and the # undefined health:procedureName that a C-CDA import path emits, plus a # warning-severity shape that fires on the health: spelling. This is a # MIGRATION WINDOW, not a ratified dual spelling; the rationale, the ratchet # out of it, and what a consumer must re-query are stated on # clinical:procedureName above. Every previously-valid graph stays valid and # a class of graph that was failing for carrying a name now passes. # - COMPATIBILITY. Additive and strictly widening. Every graph that validated # under v1.14 validates under v1.15 at the same severity, with one deliberate # exception at sh:Warning only: a vital sign carrying an interpretation # outside the ratified set is now reported. No Violation is added anywhere. # # Version 1.14 (2026-08-08) # - SHACL only; no class or property added, removed, renamed or deprecated. # Aligns four constraint families to the ratified standards they describe, and # shapes a class that had no shape. Full detail in clinical.shapes.ttl's own # changelog; summarised here because the version number lives in this file. # - clinical:interpretation bound to the HL7 v3 ObservationInterpretation code # system (http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation, # version 3.0.0), the code system FHIR R4 binds Observation.interpretation to, # plus the data-absent-reason code "unknown" # (http://terminology.hl7.org/CodeSystem/data-absent-reason). The previous # five-member enum was invented here; real exports carry susceptibility, # detection, reactivity and change codes that have no member of it. # - clinical:snomedCode and clinical:icd10Code made multi-valued, per FHIR R4 # CodeableConcept.coding 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). # - clinical:cptCode pattern widened from five digits to the full AMA CPT code # set structure: five digits (Category I) or four digits plus F, T or U # (Category II, Category III, Proprietary Laboratory Analyses). # - Source-carried date properties (encounterDate, documentDate, onsetDate, # procedureDate) now accept xsd:date alongside xsd:dateTime, because FHIR's # dateTime primitive is explicitly partial-precision # (https://build.fhir.org/datatypes.html) and C-CDA effectiveTime is commonly # date-precision. Cascade-generated timestamps are unchanged. # - Added clinical:EncounterShape and clinical:EncounterTemporalShape. # clinical:Encounter has been defined since v1.7 and targeted by no shape, so # validating an encounter checked nothing and returned PASS. Encounters vary # widely, so the shape constrains cardinality, datatype, provenance and the # IRI requirement that clinical:hasEncounter implies, and leaves the kind of # visit unconstrained. # # Version 1.13 (2026-08-03) # - Deprecated 4 classes: clinical:LabResult, clinical:Condition, # clinical:Allergy, clinical:Immunization. Each carries owl:deprecated true # and rdfs:seeAlso pointing at its health: equivalent, which is the type # every import path already emits and which health v2.5 now defines and # shapes. NOT removed: the pod export path remains the sole emitter of these # four classes, and existing pods contain them. Readers must accept both # spellings; writers should prefer the health: forms. No emitter changed in # this release. # - Documented the intended FHIR R4 value sets on clinical:status and # clinical:interpretation, and recorded WHY two of them are not enforced. # clinical:MedicationShape has no sh:in on status and clinical:VitalSignShape # has none on interpretation, while the sibling shapes do. Emitted data uses # "discontinued" for medication status and "elevated" for vital-sign # interpretation, neither of which is in the corresponding constrained set, # so adding the constraints would be breaking for existing pods rather than # clarifying. The gap is now stated in the vocabulary instead of being # visible only as an absence in the shapes. # - Added clinical:ConsultationNoteShape. clinical:ConsultationNote is one of # six rdfs:subClassOf clinical:ClinicalDocument; the other five each carry an # explicit sh:targetClass and this one did not. A subclass axiom does not # inherit a shape (SHACL resolves sh:targetClass over SHACL-instances in the # DATA graph, so an axiom in this file does not expand the target set), so # consultation notes were validating vacuously while every sibling document # type was checked. Consequence: a consultation note missing importedAt, # sourceEHR or fhirResourceId passed before and fails now. # # Version 1.12 (2026-07-20) # - Added clinical:parsedIndicationReference ObjectProperty, a # rdfs:subPropertyOf clinical:indicationReference. Records an indication edge # the importer DERIVED by parsing a coded/free-text reason on the record # (FHIR reasonCode, or a clinical:indication / clinical:reasonForUse literal) # and matching it to a condition record in the same pod, as distinct from # clinical:indicationReference proper, which restates an explicit # reasonReference the source carried. Modeled as a subproperty so traversal # over the superproperty returns both families while the predicate remains # the machine-readable basis label; no reification and no RDF-star, keeping # the edge a plain triple every existing consumer can already read. # Motivation (M1 literal-lifting census, 2026-07-20): a real Apple Health # provider export carried 0 reasonReference but 50 reasonCode instances on # medication/procedure records, 25 of which resolve unambiguously to a # condition record by exact coding identity. Those relations are dropped # entirely today. Matching is code-first with a normalized-name fallback and # unambiguous-only; ambiguous/unmatched are counted, never guessed. # Deliberately NOT modeled with a confidence score: this is a deterministic # parse of what the record says, not structural/temporal inference (which # stays out of the pod and is computed at query time). # # Version 1.11 (2026-07-16) # - Widened clinical:indicationReference: dropped the restrictive # rdfs:domain clinical:Medication in favor of the broad-domain comment + # SHACL pattern the other cross-class edges already use. Reason: FHIR carries # reasonReference on Procedure, MedicationRequest, MedicationAdministration, # Encounter, and other event resources, not only medications. The importer # (slice R3) materializes indication edges from Procedure (the common case in # Synthea) and MedicationAdministration as well as MedicationRequest; the old # domain would have made an OWL reasoner infer those Procedures are Medications. # No SHACL change: IndicationReferenceEdgeShape never encoded the domain (it # only checks sh:nodeKind sh:IRI), so existing pods are unaffected and the edge # was already valid data. Purely a vocabulary-correctness fix. # # Version 1.10 (2026-07-16) # - Added clinical:hasEncounter ObjectProperty (range clinical:Encounter). The # record-to-encounter edge for grouping clinical events by visit context. # FHIR alignment: the .encounter Reference(Encounter) element on Observation, # MedicationRequest, Condition, Procedure, DiagnosticReport, DocumentReference. # Domain left broad (comment + SHACL, no restrictive rdfs:domain union). # - Added clinical:indicationReference ObjectProperty (range rdfs:Resource). The # medication-to-condition indication edge alongside the retained free-text # clinical:indication / clinical:reasonForUse literals. FHIR alignment: # MedicationRequest.reasonReference (R4; reason CodeableReference in R5). # Range left open because FHIR allows Condition or Observation; the importer # materializes clinical:Condition. # - Added clinical:linkedCondition ObjectProperty (Condition to Condition) and # deprecated clinical:linkedConditionIds (owl:deprecated true). The old # property packed related-condition UUIDs into one space-separated literal # that no graph query can traverse; linkedCondition is the real RDF edge. # linkedConditionIds is retained (not removed) for backward compatibility # with existing Checkup data; data migration off it is tracked in the Checkup # backlog. # - Fixed clinical:hasLabResult rdfs:range: was clinical:LabResult, now # health:LabResultRecord. Reasoning: both importer paths (FHIR and C-CDA) type # panel members health:LabResultRecord, never clinical:LabResult, so the # declared range described a class the data never uses. This was internally # inconsistent (an OWL reasoner would have inferred members are # clinical:LabResult) though non-breaking (no SHACL shape constrains the edge # target). Corrected to match the serialized data. Note: health:LabResultRecord # is emitted by the importer but not yet defined as a class in the health # vocabulary; that gap is filed as a follow-up (health vocab), and the # range here follows the same reference-an-external-class style this file # already uses for fhir:/sct:/loinc: classes. # # Version 1.9 (2026-06-17) # - SHACL: added cascade:AIExtracted to every dataProvenance sh:in enum in # clinical.shapes.ttl. AIExtracted (ClinicalGenerated subclass, core v3.3) # was defined and referenced by the v3.0 AI-extraction infrastructure # (AIExtractionActivity, extractionConfidence, sourceNarrativeSection, # requiresUserReview) but the validation enums omitted it, so a clinical # record carrying the correct provenance for AI-extracted data failed # validation. No ontology classes/properties changed (shapes-only). # # Version 1.8 (2026-03-28) # - Added clinical:SocialHistoryRecord class (EHR-extracted social history) # - Subclass of cascade:HealthRecord (new abstract base in core v3.0) # - Distinct from health:SocialHistoryRecord (consumer-reported) # - Separate pod path: social-history/; consent scope: cascade:SocialHistoryConsent # - Subject to 42 CFR Part 2 for substance use disorder records # - Reuses health:smokingStatus, health:alcoholUse from health: namespace # - Adds clinical-specific: socialHistoryCategory, packsPerYear, # alcoholDrinksPerWeek, occupationTitle # # Version 1.7 (2026-03-12) # - Added clinical:Encounter class and properties for FHIR Encounter conversion # - Added clinical:MedicationAdministration class and properties for # FHIR MedicationAdministration conversion # - Added clinical:DeviceRecord class and properties for FHIR Device conversion # - Added clinical:ImagingStudy class and properties for FHIR ImagingStudy conversion # - Added clinical:hasLabResult, clinical:panelName, clinical:reportCategory # on clinical:LaboratoryReport for DiagnosticReport panel grouping # # Version 1.6 (2026-02-22) # - Added clinical:linkedConditionIds DatatypeProperty on clinical:Condition # for linking related ConditionSummary records via space-separated UUIDs. # Enables graph traversal between related diagnoses (e.g., complication to # root condition). Part of Health Map v3 Pre-Phase Track A (Task A2). # - Annotated clinical:vaccineCode as legacy in favor of clinical:cvxCode # (both defined; cvxCode added v1.1, vaccineCode retained for backward # compatibility). # # Version 1.5 (2026-02-10) # - Added CoverageRecord class (11 properties) for insurance coverage data # Maps to fhir:Coverage. Layer 2 complement to checkup:InsuranceInfo (Layer 3). # Properties: providerName, memberId, groupNumber, planName, planType, # coverageType, relationship, effectivePeriodStart, effectivePeriodEnd, # payorName, subscriberId # - Added 3 new Procedure properties: procedureStatus, procedureCategory, # procedureSnomedCode # - Added administrationRoute property for Medication # # Version 1.4 (2026-01-28) # - Added 5 new lab result properties: orderingProvider, performingLab, # observationStatus, sourceRecordId, panelMembership # - Added LabTestSeries class for longitudinal lab result tracking # - 20 properties for identity, trending, reference ranges, source linkage # - Lab equivalent of MedicationUseEpisode # - Extended sourceEHR and provenanceClass domains to include LabResult # - Extended derivedFromRecord range to include LabResult # # Version 1.3 (2026-01-26) # - Added 14 medication enrichment properties from FHIR pipeline: # - Tier 1: prescriber, indication, courseOfTherapyType, asNeeded, # medicationForm, activeIngredient, ingredientStrength, # provenanceClass, sourceFhirResourceType, clinicalIntent # - Tier 2: refillsAllowed, supplyDurationDays, dispensedQuantity, # prescriptionCategory # - Full FHIR extraction pipeline: JSON → MedicationSummary → MedicationRecord → TTL # - All new fields serialized to TTL via ClinicalRDFSerializer # - SHACL shapes added with enumerated value constraints # # Version 1.2 (2026-01-15) # - Added MedicationUseEpisode class for longitudinal medication tracking # - Represents continuous periods of medication use # - Links to source MedicationRecord(s) via derivedFromRecord # - Includes inferred status with explainable confidence/reason # - Supports multi-system drug coding (RxNorm, NDC, SNOMED, ATC) # - Added Supplement class for dietary supplements and OTC products # - Explicit regulatoryStatus (dietarySupplement, otcDrug, homeopathic, herbalRemedy) # - evidenceStrength property for clinical evidence level # - DSLD integration for NIH supplement database # - Added ~25 new data properties for episode and supplement serialization # - Episode identity rules: gap > 90 days creates new episode # - Supplements intentionally do NOT create episodes (different regulatory status) # # Version 1.1 (2026-01-01) # - Added 7 structured clinical record classes: Medication, Allergy, LabResult, # Condition, Immunization, Procedure, VitalSign # - Added ~50 data properties for structured records # - Added vital sign component properties for multi-value observations (BP) # - Added SHACL shapes for all structured record types # - Validated with OWL-RL reasoner (no inconsistencies) # - Validated with Apache Jena SHACL validator # # Version 1.0 (2025-12-26) # - Initial release with document-centric vocabulary # - ClinicalDocument and subtypes (ProgressNote, DischargeSummary, etc.) # - ClinicalSection for document structure # - Document metadata properties (importedAt, sourceEHR, etc.) # - Narrative content properties # # ============================================================================ # Notes # ============================================================================ # Data Classification: Clinical-generated (EHR-verified) # All documents imported via clinical: vocabulary use cascade:ClinicalGenerated provenance # Source: Apple HealthKit HKClinicalRecord API # Privacy: Documents may contain PHI - encrypted storage required # ============================================================================ # New Class: clinical:Encounter # Added in v1.7 — supports FHIR Encounter conversion # ============================================================================ clinical:Encounter a owl:Class ; rdfs:label "Clinical Encounter"@en ; rdfs:comment "A clinical encounter — any interaction between a patient and a healthcare provider. Enables grouping of clinical events (procedures, observations, prescriptions) that occurred in the same visit context."@en ; rdfs:subClassOf prov:Entity . clinical:encounterClass a owl:DatatypeProperty ; rdfs:domain clinical:Encounter ; rdfs:range xsd:string ; rdfs:label "Encounter Class"@en ; rdfs:comment "Values: ambulatory, emergency, inpatient, home-health"@en . clinical:encounterStatus a owl:DatatypeProperty ; rdfs:domain clinical:Encounter ; rdfs:range xsd:string ; rdfs:label "Encounter Status"@en ; rdfs:comment "Values: finished, in-progress, cancelled"@en . clinical:encounterType a owl:DatatypeProperty ; rdfs:domain clinical:Encounter ; rdfs:range xsd:string ; rdfs:label "Encounter Type"@en ; rdfs:comment "Display name from SNOMED/FHIR code"@en . clinical:encounterStart a owl:DatatypeProperty ; rdfs:domain clinical:Encounter ; rdfs:range xsd:dateTime ; rdfs:label "Encounter Start"@en . clinical:encounterEnd a owl:DatatypeProperty ; rdfs:domain clinical:Encounter ; rdfs:range xsd:dateTime ; rdfs:label "Encounter End"@en . clinical:facilityName a owl:DatatypeProperty ; rdfs:domain clinical:Encounter ; rdfs:range xsd:string ; rdfs:label "Facility Name"@en . # ============================================================================ # New Class: clinical:MedicationAdministration # Added in v1.7 — supports FHIR MedicationAdministration conversion # ============================================================================ clinical:MedicationAdministration a owl:Class ; rdfs:label "Medication Administration"@en ; rdfs:comment "A single administration event of a medication given at a specific time by a provider (e.g., IV antibiotics administered pre-surgery, vaccine injection at visit). Semantically distinct from clinical:Medication (ongoing prescription regimens): represents a one-time event, not an ongoing regimen."@en ; rdfs:subClassOf prov:Entity . clinical:administeredDate a owl:DatatypeProperty ; rdfs:domain clinical:MedicationAdministration ; rdfs:range xsd:dateTime ; rdfs:label "Administered Date"@en . clinical:administeredDose a owl:DatatypeProperty ; rdfs:domain clinical:MedicationAdministration ; rdfs:range xsd:string ; rdfs:label "Administered Dose"@en ; rdfs:comment "e.g., '1g', '500mg'"@en . clinical:administeredRoute a owl:DatatypeProperty ; rdfs:domain clinical:MedicationAdministration ; rdfs:range xsd:string ; rdfs:label "Administered Route"@en ; rdfs:comment "Values: oral, IV, IM, subcutaneous, topical"@en . clinical:administrationStatus a owl:DatatypeProperty ; rdfs:domain clinical:MedicationAdministration ; rdfs:range xsd:string ; rdfs:label "Administration Status"@en ; rdfs:comment "Values: completed, not-done, in-progress"@en . # ============================================================================ # New Class: clinical:ImplantedDevice # Added in v1.7 — supports FHIR Device conversion # ============================================================================ clinical:ImplantedDevice a owl:Class ; rdfs:label "Implanted Device"@en ; rdfs:comment "A medical device implanted in or attached to the patient as a permanent part of their health profile (e.g., pacemaker, cardiac stent, cochlear implant, insulin pump). Presence affects medication safety, imaging eligibility (MRI contraindications), and surgical risk."@en ; rdfs:subClassOf prov:Entity . clinical:deviceType a owl:DatatypeProperty ; rdfs:domain clinical:ImplantedDevice ; rdfs:range xsd:string ; rdfs:label "Device Type"@en ; rdfs:comment "e.g., pacemaker, stent, cochlear-implant"@en . clinical:implantDate a owl:DatatypeProperty ; rdfs:domain clinical:ImplantedDevice ; rdfs:range xsd:dateTime ; rdfs:label "Implant Date"@en . clinical:deviceManufacturer a owl:DatatypeProperty ; rdfs:domain clinical:ImplantedDevice ; rdfs:range xsd:string ; rdfs:label "Device Manufacturer"@en . clinical:udiCarrier a owl:DatatypeProperty ; rdfs:domain clinical:ImplantedDevice ; rdfs:range xsd:string ; rdfs:label "UDI Carrier"@en ; rdfs:comment "Unique Device Identifier"@en . clinical:deviceStatus a owl:DatatypeProperty ; rdfs:domain clinical:ImplantedDevice ; rdfs:range xsd:string ; rdfs:label "Device Status"@en ; rdfs:comment "Values: active, inactive, entered-in-error"@en . # ============================================================================ # New Class: clinical:ImagingStudy # Added in v1.7 — supports FHIR ImagingStudy conversion # ============================================================================ clinical:ImagingStudy a owl:Class ; rdfs:label "Imaging Study"@en ; rdfs:comment "Metadata record for a diagnostic imaging study (CT, MRI, X-ray, ultrasound). Records that imaging exists and its key characteristics without requiring access to DICOM images. Useful for clinical timeline, pre-procedure review, and agent reasoning about the patient's imaging history."@en ; rdfs:subClassOf prov:Entity . clinical:imagingModality a owl:DatatypeProperty ; rdfs:domain clinical:ImagingStudy ; rdfs:range xsd:string ; rdfs:label "Imaging Modality"@en ; rdfs:comment "Values: CT, MR, DX, US, NM"@en . clinical:studyDescription a owl:DatatypeProperty ; rdfs:domain clinical:ImagingStudy ; rdfs:range xsd:string ; rdfs:label "Study Description"@en . clinical:numberOfSeries a owl:DatatypeProperty ; rdfs:domain clinical:ImagingStudy ; rdfs:range xsd:integer ; rdfs:label "Number of Series"@en . clinical:studyDate a owl:DatatypeProperty ; rdfs:domain clinical:ImagingStudy ; rdfs:range xsd:dateTime ; rdfs:label "Study Date"@en . clinical:dicomStudyUid a owl:DatatypeProperty ; rdfs:domain clinical:ImagingStudy ; rdfs:range xsd:string ; rdfs:label "DICOM Study UID"@en . clinical:retrieveUrl a owl:DatatypeProperty ; rdfs:domain clinical:ImagingStudy ; rdfs:range xsd:anyURI ; rdfs:label "Retrieve URL"@en ; rdfs:comment "URL to DICOM server if available"@en . # ============================================================================ # New Properties on existing class: clinical:LaboratoryReport # Added in v1.7 — supports DiagnosticReport panel grouping # ============================================================================ clinical:hasLabResult a owl:ObjectProperty ; rdfs:label "Has Lab Result"@en ; rdfs:comment "Links a LaboratoryReport panel to its constituent lab-result observations. Enables grouping of lab values (e.g., CBC components) under the ordering panel. FHIR alignment: fhir:DiagnosticReport.result. Range is health:LabResultRecord (v1.10): both importer paths (FHIR and C-CDA) type panel members health:LabResultRecord, so the range is corrected to match the serialized data rather than the never-emitted clinical:LabResult class. See the v1.10 changelog for the reasoning."@en ; rdfs:domain clinical:LaboratoryReport ; rdfs:range health:LabResultRecord . clinical:panelName a owl:DatatypeProperty ; rdfs:domain clinical:LaboratoryReport ; rdfs:range xsd:string ; rdfs:label "Panel Name"@en ; rdfs:comment "e.g., 'Complete Blood Count'"@en . clinical:reportCategory a owl:DatatypeProperty ; rdfs:domain clinical:LaboratoryReport ; rdfs:range xsd:string ; rdfs:label "Report Category"@en ; rdfs:comment "Values: LAB, RAD, PATH"@en . # ============================================================================ # Social History Classes and Properties (v1.8) # ============================================================================ # Social History (v1.8) — EHR-extracted social history with 42 CFR Part 2 compliance clinical:SocialHistoryRecord a owl:Class ; rdfs:subClassOf cascade:HealthRecord ; rdfs:label "Social History Record"@en ; rdfs:comment "An EHR-extracted social or behavioral history observation from a C-CDA Social History section. Distinct from health:SocialHistoryRecord (consumer-reported). Requires separate consent scope (cascade:SocialHistoryConsent) and pod path (social-history/). Subject to 42 CFR Part 2 for substance use records."@en ; owl:versionInfo "Added in clinical v1.8" . clinical:socialHistoryCategory a owl:DatatypeProperty ; rdfs:domain clinical:SocialHistoryRecord ; rdfs:range xsd:string ; rdfs:comment "Category of social history observation. Values: smokingStatus | alcoholUse | substanceUse | occupation | exercise | diet | sexualHistory | other"@en . # NOTE: health:smokingStatus and health:alcoholUse are REUSED from health: namespace (no domain restriction). # clinical: adds only properties with no equivalent in health: namespace: clinical:packsPerYear a owl:DatatypeProperty ; rdfs:domain clinical:SocialHistoryRecord ; rdfs:range xsd:decimal ; rdfs:comment "Pack-years of tobacco use (packs/day x years smoked). EHR-specific quantitative field."@en . clinical:alcoholDrinksPerWeek a owl:DatatypeProperty ; rdfs:domain clinical:SocialHistoryRecord ; rdfs:range xsd:decimal ; rdfs:comment "Average alcoholic drinks per week (quantitative EHR field)."@en . clinical:occupationTitle a owl:DatatypeProperty ; rdfs:domain clinical:SocialHistoryRecord ; rdfs:range xsd:string ; rdfs:comment "Patient-reported occupation title from EHR social history section."@en . # ============================================================================ # Graph Edge Object Properties (v1.10) # Traversable RDF edges the importer (R3) will materialize: encounter grouping, # medication indication, and condition-to-condition links. Each is FHIR-aligned. # ============================================================================ clinical:hasEncounter a owl:ObjectProperty ; rdfs:label "Has Encounter"@en ; rdfs:comment "Links a clinical record to the clinical:Encounter (visit context) it occurred within. FHIR alignment: the .encounter element (Reference(Encounter)) carried by Observation, MedicationRequest, Condition, Procedure, DiagnosticReport, and DocumentReference. The domain is intentionally broad (any encounter-scoped clinical record), so it is left unrestricted here and constrained by SHACL rather than a restrictive rdfs:domain union, matching the style of other cross-class properties in this vocabulary."@en ; rdfs:range clinical:Encounter . clinical:indicationReference a owl:ObjectProperty ; rdfs:label "Indication Reference"@en ; rdfs:comment "Links a clinical record to the condition (or observation) that is the clinical reason for it: the traversable edge alongside the free-text clinical:indication and clinical:reasonForUse literals, which are retained. FHIR alignment: the reasonReference element (Reference(Condition|Observation|...)), which FHIR R4 carries on Procedure, MedicationRequest, MedicationAdministration, Encounter, and other event resources (see https://hl7.org/fhir/R4/procedure-definitions.html#Procedure.reasonReference), folded into a .reason CodeableReference in R5. The domain is intentionally broad (any record that carries a clinical reason), so it is left unrestricted here and constrained by SHACL rather than a restrictive rdfs:domain union, matching clinical:hasEncounter and the other cross-class properties in this vocabulary. The range is left open (rdfs:Resource) because FHIR permits either a Condition or an Observation as the reason; the common case, and what the importer materializes, is a clinical:Condition. SHACL constrains the object to an IRI without committing the class."@en ; rdfs:range rdfs:Resource . clinical:linkedCondition a owl:ObjectProperty ; rdfs:label "Linked Condition"@en ; rdfs:comment "Links a condition to a related condition by IRI (e.g. a complication to its root condition). The traversable RDF replacement for the deprecated clinical:linkedConditionIds literal. FHIR R4 core has no first-class condition-to-condition reference; the closest precedents are the condition-dueTo and condition-occurredFollowing extensions (http://hl7.org/fhir/StructureDefinition/condition-dueTo). This property keeps the generic linked semantics of its predecessor; a more specific relation can be added later if a use case needs it."@en ; rdfs:domain clinical:Condition ; rdfs:range clinical:Condition . clinical:parsedIndicationReference a owl:ObjectProperty ; rdfs:label "Parsed Indication Reference"@en ; rdfs:comment "An indication edge the importer DERIVED by parsing a coded or free-text reason the record carries (FHIR reasonCode, or a clinical:indication / clinical:reasonForUse literal) and matching it to a condition record in the same pod. It is a subproperty of clinical:indicationReference, so one traversal over the superproperty returns both stated and parsed indications, while the predicate itself remains the machine-readable basis: clinical:indicationReference proper restates an explicit reference the SOURCE carried (FHIR reasonReference), whereas this property records a match the importer computed. Consumers must present the two differently (e.g. 'Indication' vs 'Indication (from record text)') because a parsed match is only as good as the code or wording it matched on. Matching is code-first (exact coding-system + code identity against the condition's own code) with a normalized-name fallback, and an edge is written only when exactly one candidate matches; ambiguous and unmatched reasons are counted in the import report, never guessed. This property carries no confidence score: it is a deterministic parse of what the record already says, NOT an inference from structure, timing, or co-occurrence, which remain out of scope for the importer and are computed at query time instead."@en ; rdfs:subPropertyOf clinical:indicationReference ; rdfs:range rdfs:Resource . ## Ontology: coverage ## File: coverage.ttl @prefix coverage: . @prefix cascade: . @prefix fhir: . @prefix prov: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports ; dct:title "Cascade Protocol Coverage Vocabulary"@en ; dct:description "Domain vocabulary for patient-owned insurance, benefits, and financial health data. Layer 2 vocabulary resolving the overlap between checkup:InsuranceInfo (patient-reported) and clinical:CoverageRecord (EHR-imported) with a unified, standardized representation."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-02-18"^^xsd:date ; dct:modified "2026-08-08"^^xsd:date ; owl:versionInfo "1.4" ; rdfs:comment "Insurance and benefits data is cross-cutting: not clinical (it's administrative/financial), not app-specific (any health app benefits from knowing coverage), and contributed by both patient self-report and EHR import. This vocabulary provides a single Layer 2 domain model that both checkup: (Layer 3, patient-reported) and clinical: (Layer 2, EHR-imported) can reference via the mixed namespace pattern. Recommended Pod storage path: /coverage/"@en ; rdfs:seeAlso . # ============================================================================ # Classes # ============================================================================ coverage:InsurancePlan a owl:Class ; rdfs:label "Insurance Plan"@en ; rdfs:comment "An insurance plan the patient is enrolled in. Unifies patient-reported data (from checkup:InsuranceInfo) and EHR-imported data (from clinical:CoverageRecord) into a single standardized representation. Recommended Pod storage: /coverage/plans/{plan-id}.ttl"@en ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso fhir:Coverage . coverage:Deductible a owl:Class ; rdfs:label "Deductible"@en ; rdfs:comment "Annual deductible status and progress tracking for an insurance plan. Enables patients to monitor their progress toward meeting annual deductible limits. Properties for this class will be defined in a future version."@en ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso fhir:ExplanationOfBenefit . coverage:CopaySchedule a owl:Class ; rdfs:label "Copay Schedule"@en ; rdfs:comment "Copay amounts by service category for an insurance plan. Helps patients understand their cost-sharing responsibilities for different types of care. Properties for this class will be defined in a future version."@en ; rdfs:subClassOf prov:Entity . # ============================================================================ # Data Properties - Core Insurance (from checkup:InsuranceInfo + clinical:CoverageRecord) # ============================================================================ coverage:providerName a owl:DatatypeProperty ; rdfs:label "Provider Name"@en ; rdfs:comment "Name of insurance company or plan provider (e.g., Aetna, Blue Cross, Medicare). Unifies checkup:insuranceProvider and clinical:providerName."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:memberId a owl:DatatypeProperty ; rdfs:label "Member ID"@en ; rdfs:comment "Insurance policy member/subscriber ID number. Present in both patient-reported and EHR-imported data."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:groupNumber a owl:DatatypeProperty ; rdfs:label "Group Number"@en ; rdfs:comment "Employer group number for employer-sponsored plans. Present in both patient-reported and EHR-imported data."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:planName a owl:DatatypeProperty ; rdfs:label "Plan Name"@en ; rdfs:comment "Name of the insurance plan (e.g., 'Gold PPO 500'). Originally from clinical:CoverageRecord."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:planType a owl:DatatypeProperty ; rdfs:label "Plan Type"@en ; rdfs:comment "Type of insurance plan: hmo, ppo, epo, pos, hdhp, medicare, medicaid, tricare, other."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:coverageType a owl:DatatypeProperty ; rdfs:label "Coverage Type"@en ; rdfs:comment "Classification of coverage. Maps to FHIR R4 Coverage.type, whose binding to http://hl7.org/fhir/ValueSet/coverage-type is EXTENSIBLE: a payer-local code is permitted where no listed code applies. The four values this property carried through v1.3 (primary, secondary, dental, vision) are still accepted; note that primary/secondary actually describe FHIR Coverage.order rather than Coverage.type."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Value constraint relaxed to Warning in coverage v1.4, matching the extensible FHIR binding" . coverage:effectiveStart a owl:DatatypeProperty ; rdfs:label "Effective Start"@en ; rdfs:comment "Start date of coverage period. Maps to clinical:effectivePeriodStart and checkup:effectiveDate."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:date . coverage:effectiveEnd a owl:DatatypeProperty ; rdfs:label "Effective End"@en ; rdfs:comment "End date of coverage period. Maps to clinical:effectivePeriodEnd and checkup:expirationDate (for insurance usage)."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:date . coverage:subscriberId a owl:DatatypeProperty ; rdfs:label "Subscriber ID"@en ; rdfs:comment "Identifier for the policy holder from the insurer. From clinical:CoverageRecord."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Subscriber (from Intake Form, 2026-02-16) # ============================================================================ coverage:subscriberName a owl:DatatypeProperty ; rdfs:label "Subscriber Name"@en ; rdfs:comment "Name of insurance subscriber (the primary policyholder). From patient-reported intake form data."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:subscriberRelationship a owl:DatatypeProperty ; rdfs:label "Subscriber Relationship"@en ; rdfs:comment "Relationship of the patient to the subscriber, from the HL7 SubscriberPolicyholder code system that FHIR R4 Coverage.relationship binds to: child, parent, spouse, common, other, self, injured. Unifies clinical:relationship and checkup:subscriberRelationship."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string ; rdfs:seeAlso ; owl:versionInfo "Completed to the full SubscriberPolicyholder code system in coverage v1.4 (added common, injured)" . # ============================================================================ # Data Properties - Pharmacy Benefits (from InsuranceInfo model) # ============================================================================ coverage:rxBin a owl:DatatypeProperty ; rdfs:label "Rx BIN"@en ; rdfs:comment "Pharmacy benefit BIN (Bank Identification Number). Used for prescription drug claims processing."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:rxPcn a owl:DatatypeProperty ; rdfs:label "Rx PCN"@en ; rdfs:comment "Pharmacy benefit PCN (Processor Control Number). Used for prescription drug claims routing."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . coverage:rxGroup a owl:DatatypeProperty ; rdfs:label "Rx Group"@en ; rdfs:comment "Pharmacy benefit group number. Identifies the patient's pharmacy benefit group for claims processing."@en ; rdfs:domain coverage:InsurancePlan ; rdfs:range xsd:string . # ============================================================================ # Alignment with Healthcare Standards # ============================================================================ # FHIR Coverage Resource Mapping # - coverage:InsurancePlan -> fhir:Coverage # - coverage:providerName -> fhir:Coverage.payor (display) # - coverage:memberId -> fhir:Coverage.identifier # - coverage:groupNumber -> fhir:Coverage.class (group) # - coverage:planName -> fhir:Coverage.class (plan) # - coverage:planType -> fhir:Coverage.type # - coverage:coverageType -> fhir:Coverage.class (category) # - coverage:effectiveStart -> fhir:Coverage.period.start # - coverage:effectiveEnd -> fhir:Coverage.period.end # - coverage:subscriberId -> fhir:Coverage.subscriberId # - coverage:subscriberRelationship -> fhir:Coverage.relationship # Future companion vocabulary: billing: (Layer 2) # - billing:Claim, billing:Bill, billing:Payment, billing:ExplanationOfBenefits # - Enables patient healthcare spending tracking and cost management # - Not in scope for this version # ============================================================================ # Changelog # ============================================================================ # # v1.3 (2026-03-17): Added coverage:deniedClinicalContext (ObjectProperty linking DenialNotice to clinical evidence in the Pod), # coverage:outcomeReason (free-text explanation of appeal outcome), coverage:reversalAmount (reimbursement if approved), # coverage:DenialReasonCode named individuals (standard-mapped enum values for denialReasonCode), # rdfs:seeAlso mappings to HL7 ClaimResponse.adjudication and CARIN IG adjudication codes. # # v1.2 (2026-03-16): Added coverage:DenialNotice class, coverage:AppealRecord class, and 11 new properties for denial/appeal workflow. # # Version 1.4 (2026-08-08) # - SHACL only; no class or property added, removed or renamed. # - coverage:subscriberRelationship enum completed to the full HL7 # SubscriberPolicyholder code system # (http://terminology.hl7.org/CodeSystem/subscriber-relationship), which is # what FHIR R4 Coverage.relationship binds to. "common" (Common Law Spouse) # and "injured" (Injured Party) were missing, so a conformant export warned. # - coverage:coverageType no longer fails a record for carrying a code from # outside a four-member Cascade list. FHIR R4 binds Coverage.type EXTENSIBLY # (https://hl7.org/fhir/R4/terminologies.html#extensible), so a payer sending # EHCPOL or a local code is conformant. Presence stays a Violation; the value # is now an advisory Warning against the FHIR value set plus the retained # Cascade values. # # Version 1.0 (2026-02-18) # - Initial release as part of Schema Refactoring Plan Phase 2 (WS4) # - Created to resolve overlap between checkup:InsuranceInfo (L3, patient-reported) # and clinical:CoverageRecord (L2, EHR-imported) # - InsurancePlan class with 14 properties: # - 8 core properties (providerName, memberId, groupNumber, planName, planType, # coverageType, effectiveStart, effectiveEnd) # - 3 subscriber properties (subscriberId, subscriberName, subscriberRelationship) # - 3 pharmacy benefit properties (rxBin, rxPcn, rxGroup) # - Deductible and CopaySchedule classes defined (properties deferred to future version) # - FHIR Coverage resource alignment documented # - Layer 2 domain vocabulary per three-layer architecture # - Recommended Pod storage: /coverage/plans/{plan-id}.ttl # # ============================================================================ # New Classes: ClaimRecord and BenefitStatement # Added in v1.1 — supports FHIR Claim and ExplanationOfBenefit conversion # ============================================================================ coverage:ClaimRecord a owl:Class ; rdfs:label "Claim Record"@en ; rdfs:comment "A medical claim submitted to an insurer for reimbursement. FHIR alignment: fhir:Claim"@en . coverage:BenefitStatement a owl:Class ; rdfs:label "Benefit Statement (EOB)"@en ; rdfs:comment "An Explanation of Benefits document — the insurer's formal response to a claim, including adjudication, denial reasons, and patient cost responsibility. FHIR alignment: fhir:ExplanationOfBenefit. CMS mandates patient access via CARIN Alliance FHIR IG."@en . coverage:DenialNotice a owl:Class ; rdfs:label "Denial Notice"@en ; rdfs:comment "A formal denial of coverage issued by an insurer. Distinct from BenefitStatement — a DenialNotice initiates an appeal process, not a payment record. Recommended Pod storage: /coverage/denials/{id}.ttl"@en ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso fhir:ExplanationOfBenefit . coverage:AppealRecord a owl:Class ; rdfs:label "Appeal Record"@en ; rdfs:comment "A formal appeal filing contesting a denial notice. One record per appeal level. Recommended Pod storage: /coverage/appeals/{id}.ttl"@en ; rdfs:subClassOf prov:Activity ; rdfs:seeAlso fhir:Task . # ============================================================================ # New Properties: coverage:Deductible # ============================================================================ coverage:deductibleAmount a owl:DatatypeProperty ; rdfs:domain coverage:Deductible ; rdfs:range xsd:decimal ; rdfs:label "Deductible Amount"@en . coverage:deductibleCurrency a owl:DatatypeProperty ; rdfs:domain coverage:Deductible ; rdfs:range xsd:string ; rdfs:label "Deductible Currency"@en . coverage:deductibleMetAmount a owl:DatatypeProperty ; rdfs:domain coverage:Deductible ; rdfs:range xsd:decimal ; rdfs:label "Deductible Met Amount"@en . coverage:deductiblePeriod a owl:DatatypeProperty ; rdfs:domain coverage:Deductible ; rdfs:range xsd:string ; rdfs:label "Deductible Period"@en . coverage:deductibleType a owl:DatatypeProperty ; rdfs:domain coverage:Deductible ; rdfs:range xsd:string ; rdfs:label "Deductible Type"@en ; rdfs:comment "Values: individual, family, in-network, out-of-network"@en . # ============================================================================ # New Properties: coverage:CopaySchedule # ============================================================================ coverage:copayAmount a owl:DatatypeProperty ; rdfs:domain coverage:CopaySchedule ; rdfs:range xsd:decimal ; rdfs:label "Copay Amount"@en . coverage:copayCurrency a owl:DatatypeProperty ; rdfs:domain coverage:CopaySchedule ; rdfs:range xsd:string ; rdfs:label "Copay Currency"@en . coverage:copayType a owl:DatatypeProperty ; rdfs:domain coverage:CopaySchedule ; rdfs:range xsd:string ; rdfs:label "Copay Type"@en ; rdfs:comment "Values: primary-care, specialist, emergency, prescription"@en . # ============================================================================ # New Properties: coverage:ClaimRecord # ============================================================================ coverage:claimDate a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:dateTime ; rdfs:label "Claim Date"@en . coverage:claimTotal a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:decimal ; rdfs:label "Claim Total"@en . coverage:claimStatus a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:string ; rdfs:label "Claim Status"@en ; rdfs:comment "Values: active, cancelled, draft, entered-in-error"@en . coverage:claimType a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:string ; rdfs:label "Claim Type"@en ; rdfs:comment "Values: professional, institutional, oral, pharmacy, vision"@en . coverage:billingProvider a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:string ; rdfs:label "Billing Provider"@en . coverage:hasDiagnosis a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:string ; rdfs:label "Has Diagnosis"@en ; rdfs:comment "ICD-10 code URI"@en . coverage:hasProcedure a owl:DatatypeProperty ; rdfs:domain coverage:ClaimRecord ; rdfs:range xsd:string ; rdfs:label "Has Procedure"@en ; rdfs:comment "CPT/SNOMED code URI"@en . coverage:sourceRecordId a owl:DatatypeProperty ; rdfs:range xsd:string ; rdfs:label "Source Record ID"@en ; rdfs:comment "Original source system record identifier. No domain restriction — shared by ClaimRecord and BenefitStatement."@en . # ============================================================================ # New Properties: coverage:BenefitStatement # ============================================================================ coverage:adjudicationDate a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:dateTime ; rdfs:label "Adjudication Date"@en . coverage:adjudicationStatus a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:string ; rdfs:label "Adjudication Status"@en ; rdfs:comment "Values: completed, cancelled, entered-in-error"@en . coverage:outcomeCode a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:string ; rdfs:label "Outcome Code"@en ; rdfs:comment "Values: queued, complete, error, partial"@en . coverage:denialReason a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:string ; rdfs:label "Denial Reason"@en . coverage:totalBilled a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:decimal ; rdfs:label "Total Billed"@en . coverage:totalAllowed a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:decimal ; rdfs:label "Total Allowed"@en . coverage:totalPaid a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:decimal ; rdfs:label "Total Paid"@en . coverage:patientResponsibility a owl:DatatypeProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range xsd:decimal ; rdfs:label "Patient Responsibility"@en . coverage:relatedClaim a owl:ObjectProperty ; rdfs:domain coverage:BenefitStatement ; rdfs:range coverage:ClaimRecord ; rdfs:label "Related Claim"@en ; rdfs:comment "Links an ExplanationOfBenefit to its corresponding Claim."@en . # ============================================================================ # v1.2 DenialNotice Properties # ============================================================================ coverage:deniedProcedureCode a owl:DatatypeProperty ; rdfs:label "Denied Procedure Code"@en ; rdfs:comment "CPT or HCPCS code of the denied service (e.g., E0787 for CGM)."@en ; rdfs:domain coverage:DenialNotice ; rdfs:range xsd:string . coverage:denialReasonCode a owl:DatatypeProperty ; rdfs:label "Denial Reason Code"@en ; rdfs:comment "Structured coded denial reason. Recommended values are coverage:DenialReasonCode named individuals (coverage:NotMedicallyNecessary, coverage:LCDCriteriaNotMet, coverage:NCDCriteriaNotMet, coverage:ServiceNotCovered, coverage:PriorAuthorizationRequired, coverage:DuplicateClaim, coverage:InformationRequested). Distinct from the free-text coverage:denialReason. Aligns with HL7 ClaimResponse adjudication and CARIN IG Blue Button STU2."@en ; rdfs:domain coverage:DenialNotice ; rdfs:range xsd:string ; rdfs:seeAlso , . coverage:denialLetterDate a owl:DatatypeProperty ; rdfs:label "Denial Letter Date"@en ; rdfs:comment "Date the denial notice was issued. The appeal deadline clock starts from this date."@en ; rdfs:domain coverage:DenialNotice ; rdfs:range xsd:date . coverage:appealDeadline a owl:DatatypeProperty ; rdfs:label "Appeal Deadline"@en ; rdfs:comment "Computed deadline for filing an appeal, based on the denial date, plan type, and applicable jurisdiction rules."@en ; rdfs:domain coverage:DenialNotice ; rdfs:range xsd:date . coverage:coveragePolicyReference a owl:DatatypeProperty ; rdfs:label "Coverage Policy Reference"@en ; rdfs:comment "The LCD, NCD, or coverage policy cited as the basis for denial (e.g., LCD L33822)."@en ; rdfs:domain coverage:DenialNotice ; rdfs:range xsd:string . # ============================================================================ # v1.2 AppealRecord Properties # ============================================================================ coverage:appealLevel a owl:DatatypeProperty ; rdfs:label "Appeal Level"@en ; rdfs:comment "The administrative level of this appeal. Values: redetermination, qio_reconsideration, alj_hearing, mac_review, federal_court, internal_level1, internal_level2, external_review, state_insurance_dept, litigation."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range xsd:string . coverage:appealFiledDate a owl:DatatypeProperty ; rdfs:label "Appeal Filed Date"@en ; rdfs:comment "Date the appeal was formally filed."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range xsd:date . coverage:appealOutcome a owl:DatatypeProperty ; rdfs:label "Appeal Outcome"@en ; rdfs:comment "Outcome of the appeal. Values: approved, denied, partial, withdrawn, pending."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range xsd:string . coverage:appealOutcomeDate a owl:DatatypeProperty ; rdfs:label "Appeal Outcome Date"@en ; rdfs:comment "Date the appeal outcome was received."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range xsd:date . coverage:relatedDenialNotice a owl:ObjectProperty ; rdfs:label "Related Denial Notice"@en ; rdfs:comment "Links an AppealRecord to the DenialNotice it contests."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range coverage:DenialNotice . # ============================================================================ # v1.3 Properties # ============================================================================ coverage:deniedClinicalContext a owl:ObjectProperty ; rdfs:label "Denied Clinical Context"@en ; rdfs:comment "References clinical or health records in the Pod that are relevant to this denial — procedures, lab results, diagnoses, device readings. Enables appeals to cite specific clinical evidence against policy criteria. Range is intentionally open (rdfs:Resource) to allow linking across clinical:, health:, and pots: namespaces."@en ; rdfs:domain coverage:DenialNotice ; rdfs:range rdfs:Resource . coverage:outcomeReason a owl:DatatypeProperty ; rdfs:label "Outcome Reason"@en ; rdfs:comment "Free-text explanation of why the appeal was approved, denied, or partially approved. Captures the insurer's stated rationale for the outcome."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range xsd:string . coverage:reversalAmount a owl:DatatypeProperty ; rdfs:label "Reversal Amount"@en ; rdfs:comment "Reimbursement amount granted if the appeal was approved or partially approved. Expressed as a decimal in the currency of the related InsurancePlan."@en ; rdfs:domain coverage:AppealRecord ; rdfs:range xsd:decimal . # ============================================================================ # v1.3 Named Individuals: DenialReasonCode # Standard-mapped enumeration values for coverage:denialReasonCode # Source mappings: # HL7 ClaimResponse.adjudication: http://terminology.hl7.org/CodeSystem/adjudication # CARIN IG for Blue Button: http://hl7.org/fhir/us/carin-bb/STU2/ # CMS Medicare denial codes: https://www.cms.gov/medicare/coding-billing/place-of-service-codes # ============================================================================ coverage:NotMedicallyNecessary a coverage:DenialReasonCode ; rdfs:label "Not Medically Necessary"@en ; rdfs:comment "Service denied as not meeting medical necessity criteria for the diagnosis. HL7 alignment: adjudication code 'noauth'. CMS reason: 50 (not medically necessary)."@en ; rdfs:seeAlso . coverage:LCDCriteriaNotMet a coverage:DenialReasonCode ; rdfs:label "LCD Criteria Not Met"@en ; rdfs:comment "Service denied because the patient does not meet the criteria in the applicable Local Coverage Determination (LCD). CMS LCD search: https://www.cms.gov/medicare-coverage-database/"@en ; rdfs:seeAlso . coverage:NCDCriteriaNotMet a coverage:DenialReasonCode ; rdfs:label "NCD Criteria Not Met"@en ; rdfs:comment "Service denied because the patient does not meet the criteria in the applicable National Coverage Determination (NCD)."@en ; rdfs:seeAlso . coverage:ServiceNotCovered a coverage:DenialReasonCode ; rdfs:label "Service Not Covered"@en ; rdfs:comment "The requested service is explicitly excluded from the patient's plan benefits. HL7 alignment: adjudication code 'benefit'."@en ; rdfs:seeAlso . coverage:PriorAuthorizationRequired a coverage:DenialReasonCode ; rdfs:label "Prior Authorization Required"@en ; rdfs:comment "The service requires prior authorization which was not obtained before service delivery. HL7 alignment: adjudication code 'noauth'. CARIN IG: X12 835 reason code 4."@en ; rdfs:seeAlso . coverage:DuplicateClaim a coverage:DenialReasonCode ; rdfs:label "Duplicate Claim"@en ; rdfs:comment "Claim denied as a duplicate of a previously processed claim. X12 835 reason code 18."@en . coverage:InformationRequested a coverage:DenialReasonCode ; rdfs:label "Additional Information Requested"@en ; rdfs:comment "Claim suspended or denied pending submission of additional clinical documentation. X12 835 reason code 16."@en . # DenialReasonCode type declaration (supports named individuals above) coverage:DenialReasonCode a owl:Class ; rdfs:label "Denial Reason Code"@en ; rdfs:comment "Enumeration class for structured denial reason codes. Use named individuals (coverage:NotMedicallyNecessary, etc.) as values for coverage:denialReasonCode. Aligns with HL7 ClaimResponse adjudication codes and CARIN IG for Blue Button (STU2)."@en ; rdfs:seeAlso , . ## Ontology: checkup ## File: checkup.ttl @prefix checkup: . @prefix cascade: . @prefix clinical: . @prefix health: . @prefix fhir: . @prefix sct: . @prefix prov: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix sh: . @prefix dct: . # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; dct:title "Cascade Checkup Vocabulary"@en ; dct:description "Structured health data for patient intake forms and pre-visit preparation. Enables aggregation of EHR, HealthKit, and manually entered health data."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-01-03"^^xsd:date ; dct:modified "2026-08-08"^^xsd:date ; owl:versionInfo "3.3" ; rdfs:seeAlso . # ============================================================================ # CORE CLASSES (27 active, 0 deprecated) # ============================================================================ checkup:IntakeFormData a owl:Class ; rdfs:label "Intake Form Data" ; rdfs:comment "Aggregated patient data formatted for healthcare intake forms. Contains all sections needed for a complete pre-visit profile including demographics, medications, allergies, conditions, and visit concerns." ; cascade:subConcern "intake" . checkup:InsuranceInfo a owl:Class ; rdfs:label "Insurance Information" ; rdfs:comment "Patient-reported insurance information for visit preparation. Uses coverage: namespace for standardized insurance properties (providerName, memberId, groupNumber, coverageType, effectiveStart/End, subscriberName/Relationship, rxBin/rxPcn/rxGroup); adds checkup-specific PCP information (primaryCareProvider, pcpPhone). See coverage.ttl for property definitions." ; cascade:subConcern "financial" . checkup:MedicationSummary a owl:Class ; rdfs:label "Medication Summary" ; rdfs:comment "Current medication with enriched adherence and tolerability data. Links to clinical:Medication source records and tracks self-reported adherence barriers and side effects." ; cascade:subConcern "summary" . checkup:AllergySummary a owl:Class ; rdfs:label "Allergy Summary" ; rdfs:comment "Known allergen with reaction type and severity. Tracks data provenance (EHR verified, self-reported) and links to clinical:AllergyIntolerance source records." ; cascade:subConcern "summary" . checkup:ConditionSummary a owl:Class ; rdfs:label "Condition Summary" ; rdfs:comment "Active or resolved health condition with ICD-10 coding. Includes onset information and current status for clinical context." ; cascade:subConcern "summary" . checkup:FamilyHistoryEntry a owl:Class ; rdfs:label "Family History Entry" ; rdfs:comment "Single family member health condition for risk assessment. Captures relative type, condition, and age at diagnosis for inherited condition screening." ; cascade:subConcern "summary" . checkup:ScreeningStatus a owl:Class ; rdfs:label "Screening Status" ; rdfs:comment "Patient status for recommended preventive screening. Tracks last completed date, next due date, and applicable USPSTF/ACS guidelines." ; cascade:subConcern "structural" . checkup:VisitIssue a owl:Class ; rdfs:label "Visit Issue" ; rdfs:comment "Health issue or symptom the patient wants to discuss at their visit (chief complaint). Includes severity, duration, and user-assigned priority for visit planning." ; cascade:subConcern "intake" . checkup:SuggestedQuestion a owl:Class ; rdfs:label "Suggested Question" ; rdfs:comment "AI-generated or template question for the patient to ask their provider. Based on conditions, medications, symptoms, or health trends identified in their profile." ; cascade:subConcern "analytics" . checkup:DiagnosticTestResult a owl:Class ; rdfs:label "Diagnostic Test Result" ; rdfs:comment "Reference to a diagnostic test from an external Cascade app (e.g., POTS Check, glucose monitoring). Provides summary for intake forms and links to detailed results in pod." ; cascade:subConcern "intake" . checkup:SupplementSummary a owl:Class ; rdfs:label "Supplement Summary" ; rdfs:comment "Dietary supplement, OTC product, or herbal remedy with regulatory classification and evidence strength. Separate from MedicationSummary to reflect different regulatory status and data requirements. Links to clinical:Supplement source records." ; cascade:subConcern "summary" . # --- Clinical Summary Classes (v1.7) --- checkup:ImmunizationSummary a owl:Class ; rdfs:label "Immunization Summary" ; rdfs:comment "Patient-facing immunization record for intake forms. Uses mixed namespace pattern: checkup: properties for app-layer enrichment (vaccineCategory) alongside clinical data fields." ; cascade:subConcern "summary" ; rdfs:subClassOf prov:Entity . checkup:LabResultSummary a owl:Class ; rdfs:label "Lab Result Summary" ; rdfs:comment "Patient-facing lab result summary for intake forms. Uses mixed namespace pattern: checkup:labCategory for app-layer categorization, clinical: properties for standardized lab data." ; cascade:subConcern "summary" ; rdfs:subClassOf prov:Entity . checkup:ProcedureSummary a owl:Class ; rdfs:label "Procedure Summary" ; rdfs:comment "Patient-facing procedure record for intake forms. Uses mixed namespace pattern: checkup: properties for app-layer fields (procedureName, procedureCategory, procedureLocation), clinical: properties for standardized clinical data (procedureStatus, performedDate, bodySite, performer, outcome, snomedCode, cptCode)." ; cascade:subConcern "summary" ; rdfs:subClassOf prov:Entity . # --- Reference Classes (v1.7) --- checkup:WellnessProfileReference a owl:Class ; rdfs:label "Wellness Profile Reference" ; rdfs:comment "Lightweight pointer to the patient's full wellness profile stored in the SDK HealthProfile. Uses rdfs:seeAlso to link to the wellness data file." ; cascade:subConcern "structural" ; rdfs:subClassOf prov:Entity . # ============================================================================ # INSURANCE INFORMATION PROPERTIES # ============================================================================ checkup:insuranceProvider a rdf:Property ; rdfs:label "Insurance Provider" ; rdfs:comment "Name of insurance company: Aetna, Blue Cross, Medicare, Medicaid, etc." ; rdfs:domain checkup:InsuranceInfo ; rdfs:range xsd:string . checkup:memberId a rdf:Property ; rdfs:label "Member ID" ; rdfs:comment "Insurance policy member/subscriber ID number." ; rdfs:domain checkup:InsuranceInfo ; rdfs:range xsd:string . checkup:groupNumber a rdf:Property ; rdfs:label "Group Number" ; rdfs:comment "Employer group number for employer-sponsored plans." ; rdfs:domain checkup:InsuranceInfo ; rdfs:range xsd:string . checkup:coverageType a rdf:Property ; rdfs:label "Coverage Type" ; rdfs:comment "Type of coverage: hmo, ppo, epo, pos, medicare, medicaid, tricare, self_pay." ; rdfs:domain checkup:InsuranceInfo ; rdfs:range xsd:string . checkup:primaryCareProvider a rdf:Property ; rdfs:label "Primary Care Provider" ; rdfs:comment "Name of assigned primary care provider on file with insurance." ; rdfs:domain checkup:InsuranceInfo ; rdfs:range xsd:string . checkup:pcpPhone a rdf:Property ; rdfs:label "PCP Phone" ; rdfs:comment "Primary care provider office phone for referral verification." ; rdfs:domain checkup:InsuranceInfo ; rdfs:range xsd:string . # ============================================================================ # MEDICATION SUMMARY PROPERTIES # ============================================================================ checkup:medicationName a rdf:Property ; rdfs:label "Medication Name" ; rdfs:comment "Name of the medication (generic or brand)." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:dose a rdf:Property ; rdfs:label "Dose" ; rdfs:comment "Medication dose with units (e.g., '10mg', '500mg')." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:frequency a rdf:Property ; rdfs:label "Frequency" ; rdfs:comment "How often medication is taken: once_daily, twice_daily, three_times_daily, as_needed, weekly, monthly." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:adherenceStatus a rdf:Property ; rdfs:label "Adherence Status" ; rdfs:comment "Self-reported adherence: taking_as_prescribed, usually_take, sometimes_miss, often_miss, stopped." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:adherenceBarrier a rdf:Property ; rdfs:label "Adherence Barrier" ; rdfs:comment "Reason for non-adherence if applicable: cost, side_effects, forgetfulness, access, complexity, other." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:sideEffects a rdf:Property ; rdfs:label "Side Effects" ; rdfs:comment "List of reported side effects as comma-separated string." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:perceivedEffectiveness a rdf:Property ; rdfs:label "Perceived Effectiveness" ; rdfs:comment "Patient assessment of medication effectiveness: very_effective, somewhat_effective, not_sure, not_effective." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:lastRefillDate a rdf:Property ; rdfs:label "Last Refill Date" ; rdfs:comment "Date of most recent prescription refill." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:date . checkup:prescriber a rdf:Property ; rdfs:label "Prescriber" ; rdfs:comment "Name of prescribing provider." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:sourceRecord a rdf:Property ; rdfs:label "Source Record" ; rdfs:comment "Link to clinical:Medication source record in pod." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:anyURI . # ============================================================================ # MEDICATION SUMMARY PROPERTIES - Phase 3 Extensions (v1.2) # ============================================================================ checkup:episodeId a rdf:Property ; rdfs:label "Episode ID" ; rdfs:comment "Link to canonical MedicationUseEpisode in CascadeSDK. Enables longitudinal tracking." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:startDate a rdf:Property ; rdfs:label "Start Date" ; rdfs:comment "Date when patient started taking this medication." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:date . checkup:indication a rdf:Property ; rdfs:label "Indication" ; rdfs:comment "Why the patient takes this medication (patient's understanding of the reason)." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:patientCost a rdf:Property ; rdfs:label "Patient Cost" ; rdfs:comment "Patient's out-of-pocket cost for medication or supplement." ; rdfs:range xsd:decimal . checkup:costFrequency a rdf:Property ; rdfs:label "Cost Frequency" ; rdfs:comment "Frequency of cost: per_month, per_refill, per_dose, per_bottle, annual." ; rdfs:range xsd:string . checkup:functionalImpact a rdf:Property ; rdfs:label "Functional Impact" ; rdfs:comment "How this medication helps the patient functionally (e.g., 'helps me sleep')." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:concernsForDoctor a rdf:Property ; rdfs:label "Concerns For Doctor" ; rdfs:comment "Concerns the patient wants to discuss with their healthcare provider." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:needsVerification a owl:DatatypeProperty ; rdfs:label "Needs Verification" ; rdfs:comment "Whether this medication needs patient verification." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:boolean . checkup:verificationReason a owl:DatatypeProperty ; rdfs:label "Verification Reason" ; rdfs:comment "Why verification is needed: historical, duplicate, conflicting_sources." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:lastVerifiedDate a owl:DatatypeProperty ; rdfs:label "Last Verified Date" ; rdfs:comment "When the patient last verified this medication." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:dateTime . checkup:importSessionId a owl:ObjectProperty ; rdfs:label "Import Session ID" ; rdfs:comment "Link to the import session that created this medication record." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:anyURI . # ============================================================================ # SUPPLEMENT SUMMARY PROPERTIES (v1.1) # ============================================================================ checkup:supplementName a rdf:Property ; rdfs:label "Supplement Name" ; rdfs:comment "Name of the supplement or OTC product." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:supplementDose a rdf:Property ; rdfs:label "Supplement Dose" ; rdfs:comment "Dose with units (e.g., '2000 IU', '500mg')." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:supplementFrequency a rdf:Property ; rdfs:label "Supplement Frequency" ; rdfs:comment "How often supplement is taken: once_daily, twice_daily, as_needed, weekly." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:supplementBrand a rdf:Property ; rdfs:label "Supplement Brand" ; rdfs:comment "Brand name if known (e.g., 'Nature Made', 'Garden of Life')." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:supplementForm a rdf:Property ; rdfs:label "Supplement Form" ; rdfs:comment "Physical form: capsule, tablet, softgel, liquid, powder, gummy, spray, patch, tea, tincture, other." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:regulatoryStatus a rdf:Property ; rdfs:label "Regulatory Status" ; rdfs:comment "Regulatory classification distinguishing from FDA-approved medications: dietarySupplement (DSHEA), otcDrug (FDA OTC monograph), homeopathic, herbalRemedy, unknown." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:evidenceStrength a rdf:Property ; rdfs:label "Evidence Strength" ; rdfs:comment "Level of clinical evidence: strongEvidence, moderateEvidence, limitedEvidence, traditionalUse, noEvidence, unknown." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:reasonForUse a rdf:Property ; rdfs:label "Reason For Use" ; rdfs:comment "User-provided reason for taking supplement (e.g., 'bone health', 'sleep support')." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:supplementStartDate a rdf:Property ; rdfs:label "Supplement Start Date" ; rdfs:comment "Date when patient started taking the supplement." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:date . checkup:supplementIsActive a rdf:Property ; rdfs:label "Supplement Is Active" ; rdfs:comment "Whether patient is currently taking the supplement." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:boolean . checkup:dsldId a rdf:Property ; rdfs:label "DSLD ID" ; rdfs:comment "NIH Dietary Supplement Label Database identifier for lookup." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . checkup:supplementSourceRecord a rdf:Property ; rdfs:label "Supplement Source Record" ; rdfs:comment "Link to clinical:Supplement source record in pod." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:anyURI . checkup:doctorAware a rdf:Property ; rdfs:label "Doctor Aware" ; rdfs:comment "Whether the patient's healthcare provider knows about this supplement. Used to identify supplements that should be discussed at visits." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:boolean . # ============================================================================ # IMMUNIZATION SUMMARY PROPERTIES (v1.7) # ============================================================================ checkup:vaccineName a owl:DatatypeProperty ; rdfs:label "Vaccine Name" ; rdfs:comment "Name of the vaccine administered." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:administrationDate a owl:DatatypeProperty ; rdfs:label "Administration Date" ; rdfs:comment "Date the vaccine was administered." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:dateTime . checkup:immunizationStatus a owl:DatatypeProperty ; rdfs:label "Immunization Status" ; rdfs:comment "Status: completed, entered-in-error, not-done." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:vaccineCode a owl:DatatypeProperty ; rdfs:label "Vaccine Code" ; rdfs:comment "CVX vaccine code for standardized identification." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:vaccineCategory a owl:DatatypeProperty ; rdfs:label "Vaccine Category" ; rdfs:comment "Categorization for intake form display: routine, travel, occupational, other." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:manufacturer a owl:DatatypeProperty ; rdfs:label "Manufacturer" ; rdfs:comment "Vaccine manufacturer name." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:lotNumber a owl:DatatypeProperty ; rdfs:label "Lot Number" ; rdfs:comment "Vaccine lot number." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:expirationDate a owl:DatatypeProperty ; rdfs:label "Expiration Date" ; rdfs:comment "Vaccine expiration date." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:date . checkup:doseNumber a owl:DatatypeProperty ; rdfs:label "Dose Number" ; rdfs:comment "Dose sequence number in multi-dose series." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:integer . checkup:doseQuantity a owl:DatatypeProperty ; rdfs:label "Dose Quantity" ; rdfs:comment "Dose quantity administered." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:administrationRoute a owl:DatatypeProperty ; rdfs:label "Administration Route" ; rdfs:comment "Route of vaccine administration: intramuscular, subcutaneous, oral, etc." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:administrationSite a owl:DatatypeProperty ; rdfs:label "Administration Site" ; rdfs:comment "Body site where vaccine was administered: left arm, right arm, etc." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:administeringProvider a owl:DatatypeProperty ; rdfs:label "Administering Provider" ; rdfs:comment "Name of the provider who administered the vaccine." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:administeringLocation a owl:DatatypeProperty ; rdfs:label "Administering Location" ; rdfs:comment "Location where vaccine was administered." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:immunizationNotes a owl:DatatypeProperty ; rdfs:label "Notes" ; rdfs:comment "Additional notes about the immunization." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . checkup:immunizationSourceRecord a owl:ObjectProperty ; rdfs:label "Source Record" ; rdfs:comment "Link to the source clinical:Immunization record." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:anyURI . # ============================================================================ # LAB RESULT SUMMARY PROPERTIES (v1.7) # ============================================================================ checkup:labCategory a owl:DatatypeProperty ; rdfs:label "Lab Category" ; rdfs:comment "Intake form display category: metabolic, hematology, lipid, thyroid, vitamin, other." ; rdfs:domain checkup:LabResultSummary ; rdfs:range xsd:string . # ============================================================================ # PROCEDURE SUMMARY PROPERTIES (v1.8) # ============================================================================ # Mixed namespace pattern: checkup: for app-layer fields, clinical: for # standardized clinical data fields. checkup:procedureName a owl:DatatypeProperty ; rdfs:label "Procedure Name" ; rdfs:comment "Name of the medical procedure." ; rdfs:domain checkup:ProcedureSummary ; rdfs:range xsd:string . checkup:procedureCategory a owl:DatatypeProperty ; rdfs:label "Procedure Category" ; rdfs:comment "Categorization for intake form display: surgical, diagnostic, therapeutic, other." ; rdfs:domain checkup:ProcedureSummary ; rdfs:range xsd:string . checkup:procedureLocation a owl:DatatypeProperty ; rdfs:label "Procedure Location" ; rdfs:comment "Facility or location where the procedure was performed." ; rdfs:domain checkup:ProcedureSummary ; rdfs:range xsd:string . checkup:procedureSourceRecord a owl:ObjectProperty ; rdfs:label "Source Record" ; rdfs:comment "Link to the source clinical:Procedure record." ; rdfs:domain checkup:ProcedureSummary ; rdfs:range xsd:anyURI . # The following clinical: properties are used by ProcedureSummary via the # mixed namespace pattern (defined in clinical.ttl): # - clinical:procedureStatus (string) # - clinical:performedDate (dateTime) # - clinical:bodySite (string) # - clinical:performer (string) # - clinical:outcome (string) # - clinical:snomedCode (string) # - clinical:cptCode (string) # ============================================================================ # ALLERGY SUMMARY PROPERTIES # ============================================================================ checkup:allergen a rdf:Property ; rdfs:label "Allergen" ; rdfs:comment "Name of the allergenic substance (drug, food, environmental)." ; rdfs:domain checkup:AllergySummary ; rdfs:range xsd:string . checkup:reaction a rdf:Property ; rdfs:label "Reaction" ; rdfs:comment "Type of allergic reaction: hives, rash, swelling, difficulty_breathing, anaphylaxis, nausea, other." ; rdfs:domain checkup:AllergySummary ; rdfs:range xsd:string . checkup:allergySeverity a rdf:Property ; rdfs:label "Allergy Severity" ; rdfs:comment "Severity of allergic reaction: mild, moderate, severe, life_threatening." ; rdfs:domain checkup:AllergySummary ; rdfs:range xsd:string . checkup:allergyCategory a rdf:Property ; rdfs:label "Allergy Category" ; rdfs:comment "Category of allergen: medication, food, environmental, latex, contrast, other." ; rdfs:domain checkup:AllergySummary ; rdfs:range xsd:string . # ============================================================================ # CONDITION SUMMARY PROPERTIES # ============================================================================ checkup:conditionName a rdf:Property ; rdfs:label "Condition Name" ; rdfs:comment "Name of the health condition or diagnosis." ; rdfs:domain checkup:ConditionSummary ; rdfs:range xsd:string . checkup:icd10Code a rdf:Property ; rdfs:label "ICD-10 Code" ; rdfs:comment "ICD-10-CM diagnosis code if available." ; rdfs:domain checkup:ConditionSummary ; rdfs:range xsd:string . checkup:snomedCode a rdf:Property ; rdfs:label "SNOMED Code" ; rdfs:comment "SNOMED CT concept ID if available." ; rdfs:domain checkup:ConditionSummary ; rdfs:range xsd:string . checkup:onsetDate a rdf:Property ; rdfs:label "Onset Date" ; rdfs:comment "Date when condition was first diagnosed or symptoms began." ; rdfs:domain checkup:ConditionSummary ; rdfs:range xsd:date . checkup:onsetYear a rdf:Property ; rdfs:label "Onset Year" ; rdfs:comment "Year of condition onset if exact date unknown." ; rdfs:domain checkup:ConditionSummary ; rdfs:range xsd:gYear . checkup:conditionStatus a rdf:Property ; rdfs:label "Condition Status" ; rdfs:comment "Current status: active, resolved, remission, recurrence, inactive." ; rdfs:domain checkup:ConditionSummary ; rdfs:range xsd:string . # ============================================================================ # FAMILY HISTORY PROPERTIES # ============================================================================ checkup:relativeType a rdf:Property ; rdfs:label "Relative Type" ; rdfs:comment "Relationship to patient: mother, father, sibling, maternal_grandmother, maternal_grandfather, paternal_grandmother, paternal_grandfather, aunt, uncle, child." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:string . checkup:relativeSex a rdf:Property ; rdfs:label "Relative Sex" ; rdfs:comment "Biological sex of relative: male, female." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:string . checkup:familyCondition a rdf:Property ; rdfs:label "Family Condition" ; rdfs:comment "Name of health condition the relative has/had." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:string . checkup:familyConditionCode a rdf:Property ; rdfs:label "Family Condition Code" ; rdfs:comment "ICD-10 or SNOMED code for the family condition." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:string . checkup:ageAtDiagnosis a rdf:Property ; rdfs:label "Age at Diagnosis" ; rdfs:comment "Relative's age when condition was diagnosed (if known)." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:integer . checkup:ageAtDeath a rdf:Property ; rdfs:label "Age at Death" ; rdfs:comment "Relative's age at death if deceased." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:integer . checkup:isDeceased a rdf:Property ; rdfs:label "Is Deceased" ; rdfs:comment "Whether the relative is deceased." ; rdfs:domain checkup:FamilyHistoryEntry ; rdfs:range xsd:boolean . # ============================================================================ # SCREENING STATUS PROPERTIES # ============================================================================ checkup:screeningType a rdf:Property ; rdfs:label "Screening Type" ; rdfs:comment "Type of preventive screening: mammogram, colonoscopy, pap_smear, psa, bone_density, aaa_ultrasound, lung_ct, diabetes_a1c, lipid_panel, colorectal." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:string . checkup:screeningStatus a rdf:Property ; rdfs:label "Screening Status" ; rdfs:comment "Current status: not_due, due, overdue, completed, declined, not_applicable." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:string . checkup:lastCompletedDate a rdf:Property ; rdfs:label "Last Completed Date" ; rdfs:comment "Date when screening was last completed." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:date . checkup:nextDueDate a rdf:Property ; rdfs:label "Next Due Date" ; rdfs:comment "Date when screening is next due." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:date . checkup:guidelineSource a rdf:Property ; rdfs:label "Guideline Source" ; rdfs:comment "Source of recommendation: USPSTF, ACS, AAP, ACOG, ADA." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:string . checkup:gradeLevel a rdf:Property ; rdfs:label "Grade Level" ; rdfs:comment "USPSTF grade if applicable: A, B, C, D, I." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:string . checkup:applicableAgeMin a rdf:Property ; rdfs:label "Applicable Age Min" ; rdfs:comment "Minimum age for this screening recommendation." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:integer . checkup:applicableAgeMax a rdf:Property ; rdfs:label "Applicable Age Max" ; rdfs:comment "Maximum age for this screening recommendation." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:integer . checkup:applicableSex a rdf:Property ; rdfs:label "Applicable Sex" ; rdfs:comment "Sex this screening applies to: male, female, all." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:string . checkup:frequencyMonths a rdf:Property ; rdfs:label "Frequency Months" ; rdfs:comment "Recommended screening frequency in months." ; rdfs:domain checkup:ScreeningStatus ; rdfs:range xsd:integer . # ============================================================================ # VISIT ISSUE (CHIEF COMPLAINT) PROPERTIES # ============================================================================ checkup:issueCategory a rdf:Property ; rdfs:label "Issue Category" ; rdfs:comment "Category of health issue: pain, skin, digestive, respiratory, cardiovascular, mental_health, neurological, musculoskeletal, urinary, medication, screening, general, other." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:string . checkup:issueDescription a rdf:Property ; rdfs:label "Issue Description" ; rdfs:comment "Free text description of the health issue." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:string . checkup:issueDuration a rdf:Property ; rdfs:label "Issue Duration" ; rdfs:comment "How long the issue has been present: days, weeks, months, years." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:string . checkup:issueSeverity a rdf:Property ; rdfs:label "Issue Severity" ; rdfs:comment "Severity of the issue: mild, moderate, severe." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:string . checkup:issueFrequency a rdf:Property ; rdfs:label "Issue Frequency" ; rdfs:comment "How often the issue occurs: constant, intermittent, occasional, rare." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:string . checkup:affectsDaily a rdf:Property ; rdfs:label "Affects Daily Activities" ; rdfs:comment "Whether the issue impacts daily activities." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:boolean . checkup:relatedConditions a rdf:Property ; rdfs:label "Related Conditions" ; rdfs:comment "Links to existing condition entries that may be related." ; rdfs:domain checkup:VisitIssue ; rdfs:range checkup:ConditionSummary . checkup:issuePriority a rdf:Property ; rdfs:label "Issue Priority" ; rdfs:comment "User-ranked importance (1 = highest priority)." ; rdfs:domain checkup:VisitIssue ; rdfs:range xsd:integer . # ============================================================================ # SUGGESTED QUESTION PROPERTIES # ============================================================================ checkup:questionText a rdf:Property ; rdfs:label "Question Text" ; rdfs:comment "The suggested question to ask the provider." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range xsd:string . checkup:questionCategory a rdf:Property ; rdfs:label "Question Category" ; rdfs:comment "Category: treatment_options, side_effects, prevention, lifestyle, referral, diagnosis, prognosis, medication, screening." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range xsd:string . checkup:basedOnCondition a rdf:Property ; rdfs:label "Based On Condition" ; rdfs:comment "Link to condition that triggered this question." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range checkup:ConditionSummary . checkup:basedOnMedication a rdf:Property ; rdfs:label "Based On Medication" ; rdfs:comment "Link to medication that triggered this question." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range checkup:MedicationSummary . checkup:basedOnIssue a rdf:Property ; rdfs:label "Based On Issue" ; rdfs:comment "Link to visit issue that triggered this question." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range checkup:VisitIssue . checkup:basedOnTrend a rdf:Property ; rdfs:label "Based On Trend" ; rdfs:comment "Link to a health metric trend that triggered this question." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range health:MetricTrend . checkup:basedOnTemplate a owl:DatatypeProperty ; rdfs:label "Based On Template" ; rdfs:comment "Template name the question was generated from." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range xsd:string . checkup:basedOnScreening a owl:ObjectProperty ; rdfs:label "Based On Screening" ; rdfs:comment "Link to the screening record the question relates to." ; rdfs:domain checkup:SuggestedQuestion ; rdfs:range xsd:anyURI . # ============================================================================ # DIAGNOSTIC TEST RESULT PROPERTIES # ============================================================================ checkup:testType a rdf:Property ; rdfs:label "Test Type" ; rdfs:comment "Type of diagnostic test: pots_check, blood_pressure_series, glucose_series, ecg, sleep_analysis, other." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:string . checkup:testDate a rdf:Property ; rdfs:label "Test Date" ; rdfs:comment "Date and time when the test was performed." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:dateTime . checkup:sourceApp a rdf:Property ; rdfs:label "Source App" ; rdfs:comment "Name of the app that generated this result: POTS Check, Cascade Checkup, etc." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:string . checkup:sourceAppVersion a rdf:Property ; rdfs:label "Source App Version" ; rdfs:comment "Version of the source app." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:string . checkup:resultSummary a rdf:Property ; rdfs:label "Result Summary" ; rdfs:comment "Brief text summary suitable for intake forms." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:string . checkup:resultDetails a rdf:Property ; rdfs:label "Result Details" ; rdfs:comment "Link to full result in pod (e.g., pots-check-uuid.ttl)." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:anyURI . checkup:flaggedForDiscussion a rdf:Property ; rdfs:label "Flagged For Discussion" ; rdfs:comment "Whether user wants to discuss this result with provider." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:boolean . # POTS Check specific properties checkup:potsThresholdMet a rdf:Property ; rdfs:label "POTS Threshold Met" ; rdfs:comment "Whether the POTS diagnostic threshold was met (HR increase >= 30 bpm)." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:boolean . checkup:maxHeartRateDelta a rdf:Property ; rdfs:label "Max Heart Rate Delta" ; rdfs:comment "Maximum heart rate change during standing test (bpm)." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:double . checkup:orthostaticFlags a rdf:Property ; rdfs:label "Orthostatic Flags" ; rdfs:comment "Orthostatic findings: hypotension_suspected, hypertension_suspected, none." ; rdfs:domain checkup:DiagnosticTestResult ; rdfs:range xsd:string . # ============================================================================ # INTAKE FORM DATA AGGREGATE PROPERTIES # ============================================================================ checkup:generatedAt a rdf:Property ; rdfs:label "Generated At" ; rdfs:comment "Timestamp when intake form data was generated." ; rdfs:domain checkup:IntakeFormData ; rdfs:range xsd:dateTime . checkup:schemaVersion a rdf:Property ; rdfs:label "Schema Version" ; rdfs:comment "Version of the checkup vocabulary used." ; rdfs:domain checkup:IntakeFormData ; rdfs:range xsd:string . checkup:hasPatientProfile a rdf:Property ; rdfs:label "Has Patient Profile" ; rdfs:comment "DEPRECATED (v3.0): checkup:PatientProfile class removed. Use cascade:PatientProfile from core.ttl for patient demographics." ; owl:deprecated "true"^^xsd:boolean ; rdfs:domain checkup:IntakeFormData . checkup:hasInsurance a rdf:Property ; rdfs:label "Has Insurance" ; rdfs:comment "Link to insurance information." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:InsuranceInfo . checkup:hasMedication a rdf:Property ; rdfs:label "Has Medication" ; rdfs:comment "Link to medication summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:MedicationSummary . checkup:hasSupplement a rdf:Property ; rdfs:label "Has Supplement" ; rdfs:comment "Link to supplement summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:SupplementSummary . checkup:hasAllergy a rdf:Property ; rdfs:label "Has Allergy" ; rdfs:comment "Link to allergy summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:AllergySummary . checkup:hasCondition a rdf:Property ; rdfs:label "Has Condition" ; rdfs:comment "Link to condition summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:ConditionSummary . checkup:hasFamilyHistory a rdf:Property ; rdfs:label "Has Family History" ; rdfs:comment "Link to family history entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:FamilyHistoryEntry . checkup:hasVitalsTrend a rdf:Property ; rdfs:label "Has Vitals Trend" ; rdfs:comment "DEPRECATED (v3.0): checkup:VitalSignsTrend class removed. Use health:MetricTrend (Layer 2) and checkup:WellnessSummary (Layer 3) instead." ; owl:deprecated "true"^^xsd:boolean ; rdfs:domain checkup:IntakeFormData . checkup:hasScreening a rdf:Property ; rdfs:label "Has Screening" ; rdfs:comment "Link to screening status entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:ScreeningStatus . checkup:hasVisitIssue a rdf:Property ; rdfs:label "Has Visit Issue" ; rdfs:comment "Link to visit issue/chief complaint." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:VisitIssue . checkup:hasSuggestedQuestion a rdf:Property ; rdfs:label "Has Suggested Question" ; rdfs:comment "Link to generated question for provider." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:SuggestedQuestion . checkup:hasDiagnosticResult a rdf:Property ; rdfs:label "Has Diagnostic Result" ; rdfs:comment "Link to diagnostic test result from external app." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:DiagnosticTestResult . checkup:hasWellnessSummary a rdf:Property ; rdfs:label "Has Wellness Summary" ; rdfs:comment "Link to wellness summary with trends and baselines." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:WellnessSummary . checkup:hasTrendInsight a rdf:Property ; rdfs:label "Has Trend Insight" ; rdfs:comment "Link to a patient-readable trend insight." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:TrendInsight . checkup:hasCorrelationInsight a rdf:Property ; rdfs:label "Has Correlation Insight" ; rdfs:comment "Link to a temporal correlation insight." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:CorrelationInsight . checkup:hasKeyIndicator a rdf:Property ; rdfs:label "Has Key Indicator" ; rdfs:comment "Link to a key health indicator for dashboard display." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:KeyIndicator . checkup:hasAttentionItem a rdf:Property ; rdfs:label "Has Attention Item" ; rdfs:comment "Link to an item requiring patient attention." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:AttentionItem . checkup:hasVisitPrepSummary a rdf:Property ; rdfs:label "Has Visit Prep Summary" ; rdfs:comment "Link to a curated visit preparation summary." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:VisitPrepSummary . # ============================================================================ # DATA PROVENANCE PROPERTIES (shared across record types) # ============================================================================ checkup:hasProvenance a rdf:Property ; rdfs:label "Has Provenance" ; rdfs:comment "Link to provenance information for any record." ; rdfs:range cascade:DataProvenance . checkup:provenanceType a rdf:Property ; rdfs:label "Provenance Type" ; rdfs:comment "Source type: ehr_verified, self_reported, scanned, device, ai_extracted, imported." ; rdfs:range xsd:string . checkup:provenanceSource a rdf:Property ; rdfs:label "Provenance Source" ; rdfs:comment "Specific source identifier: MyChart, Apple Health, manual entry, etc." ; rdfs:range xsd:string . checkup:provenanceDate a rdf:Property ; rdfs:label "Provenance Date" ; rdfs:comment "Date when data was recorded or verified at source." ; rdfs:range xsd:dateTime . # ============================================================================ # EHR PROVENANCE PROPERTIES (v1.3 - Medication Experience Phase A) # ============================================================================ # Extended provenance properties for tracking medication data from EHR systems. # These properties enable any Cascade-compatible app to know where medication # data originated, supporting data sovereignty across applications. checkup:ehrSystemName a rdf:Property ; rdfs:label "EHR System Name" ; rdfs:comment "Name of the EHR patient portal or system: MyChart, Swedish Connect, Cerner, etc." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:ehrOrganization a rdf:Property ; rdfs:label "EHR Organization" ; rdfs:comment "Healthcare organization name associated with the EHR record: Virginia Mason, Swedish Medical, etc." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:fhirResourceType a rdf:Property ; rdfs:label "FHIR Resource Type" ; rdfs:comment "FHIR resource type the medication was derived from: MedicationStatement, MedicationDispense, MedicationRequest, MedicationAdministration." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:lastSyncedFromSource a rdf:Property ; rdfs:label "Last Synced From Source" ; rdfs:comment "Timestamp when this medication record was last synchronized from the EHR source." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:dateTime . # ============================================================================ # MEDICATION CLASSIFICATION PROPERTIES (v1.3 - Medication Experience Phase A) # ============================================================================ # Properties for classifying medications into relevance tiers and tracking # brand/generic name mappings for display purposes. checkup:relevanceTier a rdf:Property ; rdfs:label "Relevance Tier" ; rdfs:comment "Classification tier for medication relevance. Values: 'current' (actively taking), 'needsVerification' (status unclear), 'historical' (past medication), 'filtered' (procedure/IV meds)." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:brandName a rdf:Property ; rdfs:label "Brand Name" ; rdfs:comment "Common brand name for the medication if different from generic name. Stored in pod for portability across apps." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:commonBrandNames a rdf:Property ; rdfs:label "Common Brand Names" ; rdfs:comment "Comma-separated list of common brand names for reference data." ; rdfs:range xsd:string . checkup:therapeuticClass a rdf:Property ; rdfs:label "Therapeutic Class" ; rdfs:comment "Therapeutic/pharmacological class: Beta Blocker, ACE Inhibitor, SSRI, etc." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . checkup:tierClassificationReason a rdf:Property ; rdfs:label "Tier Classification Reason" ; rdfs:comment "Human-readable explanation for why a medication was classified into its tier." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:string . # ============================================================================ # INSIGHT & CORRELATION CLASSES (v1.4) # ============================================================================ # Layer 3 classes that synthesize across data domains to produce patient-facing # insights. These enable MVP features F4 (Medication-Metric Timeline), # F5 (Health Score), F8 (Attention Items), and F9 (Visit Prep). checkup:WellnessSummary a owl:Class ; rdfs:label "Wellness Summary" ; rdfs:comment "Layer 3 patient-facing summary of wellness data. Aggregates trends and baselines from health: Layer 2 properties into a patient-readable format." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:TrendInsight a owl:Class ; rdfs:label "Trend Insight" ; rdfs:comment "A patient-readable observation derived from trend data. Example: 'Your resting heart rate has dropped 5 bpm since you started walking 10k steps/day.'" ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:CorrelationInsight a owl:Class ; rdfs:label "Correlation Insight" ; rdfs:comment "Links two data domains with an observed temporal relationship. Example: medication start date correlates with metric change. IMPORTANT: Temporal correlation only — never implies causation." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:KeyIndicator a owl:Class ; rdfs:label "Key Indicator" ; rdfs:comment "A distilled, patient-friendly health metric chosen for its predictive value and simplicity. Used for dashboard summaries and health scores." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:AttentionItem a owl:Class ; rdfs:label "Attention Item" ; rdfs:comment "Flags when a metric deviates significantly from personal baseline, data has gone stale, or a trend crosses a threshold. Rule-based, not clinical advice." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . # --- WellnessSummary Properties --- checkup:wellnessTrend a owl:ObjectProperty ; rdfs:label "Wellness Trend" ; rdfs:comment "Link to a health:MetricTrend for a wellness metric" ; rdfs:domain checkup:WellnessSummary ; rdfs:range health:MetricTrend . checkup:wellnessBaselineDate a owl:DatatypeProperty ; rdfs:label "Baseline Date" ; rdfs:comment "Date of the baseline snapshot this summary compares against" ; rdfs:domain checkup:WellnessSummary ; rdfs:range xsd:dateTime . # --- TrendInsight Properties --- checkup:insightText a owl:DatatypeProperty ; rdfs:label "Insight Text" ; rdfs:comment "Patient-readable description of the insight" ; rdfs:domain checkup:TrendInsight ; rdfs:range xsd:string . checkup:insightSourceTrend a owl:ObjectProperty ; rdfs:label "Source Trend" ; rdfs:comment "The health:MetricTrend this insight is derived from" ; rdfs:domain checkup:TrendInsight ; rdfs:range health:MetricTrend . checkup:insightGeneratedBy a owl:ObjectProperty ; rdfs:label "Generated By" ; rdfs:comment "The agent that produced this insight. Should link to a prov:Agent (e.g., a cascade:DataSource for 'cascade-rule-engine', or a provider agent). Aligns with W3C PROV-O prov:wasGeneratedBy pattern." ; rdfs:domain checkup:TrendInsight ; rdfs:range prov:Agent . # --- CorrelationInsight Properties --- checkup:correlationMetric a owl:ObjectProperty ; rdfs:label "Correlated Metric" ; rdfs:comment "The wellness metric involved in the correlation" ; rdfs:domain checkup:CorrelationInsight ; rdfs:range health:MetricTrend . checkup:correlationEvent a owl:ObjectProperty ; rdfs:label "Correlated Event" ; rdfs:comment "The clinical event (medication start/stop, condition onset) involved in the correlation" ; rdfs:domain checkup:CorrelationInsight . checkup:correlationText a owl:DatatypeProperty ; rdfs:label "Correlation Text" ; rdfs:comment "Patient-readable description. Must use temporal language only ('started approximately after'), never causal language ('caused by')." ; rdfs:domain checkup:CorrelationInsight ; rdfs:range xsd:string . checkup:correlationTimeLag a owl:DatatypeProperty ; rdfs:label "Time Lag" ; rdfs:comment "Days between the clinical event and the observed metric change onset" ; rdfs:domain checkup:CorrelationInsight ; rdfs:range xsd:integer . # --- KeyIndicator Properties --- checkup:indicatorMetric a owl:DatatypeProperty ; rdfs:label "Indicator Metric Key" ; rdfs:comment "The metric key this indicator represents (e.g., 'restingHeartRate')" ; rdfs:domain checkup:KeyIndicator ; rdfs:range xsd:string . checkup:indicatorValue a owl:DatatypeProperty ; rdfs:domain checkup:KeyIndicator ; rdfs:range xsd:decimal . checkup:indicatorUnit a owl:DatatypeProperty ; rdfs:domain checkup:KeyIndicator ; rdfs:range xsd:string . checkup:indicatorLabel a owl:DatatypeProperty ; rdfs:label "Patient-Friendly Label" ; rdfs:comment "e.g., 'Your resting heart rate' instead of 'RHR'" ; rdfs:domain checkup:KeyIndicator ; rdfs:range xsd:string . checkup:indicatorStatus a owl:DatatypeProperty ; rdfs:label "Status" ; rdfs:comment "Traffic light: 'good', 'watch', 'attention'" ; rdfs:domain checkup:KeyIndicator ; rdfs:range xsd:string . # --- AttentionItem Properties --- checkup:attentionReason a owl:DatatypeProperty ; rdfs:label "Attention Reason" ; rdfs:comment "Why this needs attention: 'baseline_deviation', 'stale_data', 'threshold_crossed'" ; rdfs:domain checkup:AttentionItem ; rdfs:range xsd:string . checkup:attentionMetric a owl:DatatypeProperty ; rdfs:label "Attention Metric" ; rdfs:domain checkup:AttentionItem ; rdfs:range xsd:string . checkup:attentionText a owl:DatatypeProperty ; rdfs:label "Attention Text" ; rdfs:comment "Patient-readable explanation: 'Your blood pressure hasn't been recorded in 60 days'" ; rdfs:domain checkup:AttentionItem ; rdfs:range xsd:string . checkup:attentionSeverity a owl:DatatypeProperty ; rdfs:label "Severity" ; rdfs:comment "'info', 'watch', 'action'" ; rdfs:domain checkup:AttentionItem ; rdfs:range xsd:string . # ============================================================================ # VISIT PREP SUMMARY (v1.4) # ============================================================================ # A curated clinical summary for appointments. Combines wellness trends, # medication context, and patient questions into a one-page document. checkup:VisitPrepSummary a owl:Class ; rdfs:label "Visit Prep Summary" ; rdfs:comment "A curated, one-page clinical summary designed for a 15-minute appointment. Auto-generated from metric changes, medication context, and patient questions, then user-edited before export." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:visitPrepDate a owl:DatatypeProperty ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range xsd:dateTime . checkup:visitPrepComparisonDate a owl:DatatypeProperty ; rdfs:comment "Date of the previous snapshot being compared against" ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range xsd:dateTime . checkup:visitPrepChange a owl:ObjectProperty ; rdfs:comment "A metric change included in this visit prep" ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range health:MetricTrend . checkup:visitPrepCorrelation a owl:ObjectProperty ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range checkup:CorrelationInsight . checkup:visitPrepAttention a owl:ObjectProperty ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range checkup:AttentionItem . checkup:visitPrepUserNotes a owl:DatatypeProperty ; rdfs:comment "Patient-authored questions and concerns for the visit" ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range xsd:string . checkup:noteContent a owl:DatatypeProperty ; rdfs:label "Note Content" ; rdfs:comment "Free-text notes for visit preparation. Merges previously separate VisitPrepNotes content into VisitPrepSummary." ; rdfs:domain checkup:VisitPrepSummary ; rdfs:range xsd:string . # ============================================================================ # ADHERENCE & DISCUSSION CLASSES (v1.5) # ============================================================================ # Patient-reported adherence tracking and visit discussion preparation. # Supports Bug 6/11 export overhaul Phase 4B serialization. checkup:DailyCheckIn a owl:Class ; rdfs:label "Daily Check-in" ; rdfs:comment "Patient-reported daily medication adherence check-in capturing response status, missed medications, and optional notes." ; cascade:subConcern "self-report" ; rdfs:subClassOf health:SelfReport . checkup:DiscussionTopic a owl:Class ; rdfs:label "Discussion Topic" ; rdfs:comment "AI-generated or user-created topic for discussion during a healthcare visit. Topics are derived from medication data, adherence patterns, supplement interactions, or user input." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:CheckInSettings a owl:Class ; rdfs:label "Check-in Settings" ; rdfs:comment "Application configuration for daily check-in notification scheduling and day-of-week preferences." ; cascade:subConcern "app-config" . # --- DailyCheckIn Properties --- checkup:checkInDate a owl:DatatypeProperty ; rdfs:label "Check-in Date" ; rdfs:comment "Date of the check-in (normalized to midnight)" ; rdfs:domain checkup:DailyCheckIn ; rdfs:range xsd:dateTime . checkup:checkInResponse a owl:DatatypeProperty ; rdfs:label "Check-in Response" ; rdfs:comment "Adherence response: allTaken, missedSome, skippedToday" ; rdfs:domain checkup:DailyCheckIn ; rdfs:range xsd:string . checkup:missedMedicationIds a owl:DatatypeProperty ; rdfs:label "Missed Medication IDs" ; rdfs:comment "Comma-separated UUIDs of missed medications (when response is missedSome)" ; rdfs:domain checkup:DailyCheckIn ; rdfs:range xsd:string . checkup:checkInNotes a owl:DatatypeProperty ; rdfs:label "Check-in Notes" ; rdfs:comment "Optional patient notes about the day" ; rdfs:domain checkup:DailyCheckIn ; rdfs:range xsd:string . checkup:submittedAt a owl:DatatypeProperty ; rdfs:label "Submitted At" ; rdfs:comment "Timestamp when the check-in was submitted" ; rdfs:domain checkup:DailyCheckIn ; rdfs:range xsd:dateTime . # --- DiscussionTopic Properties --- checkup:topicText a owl:DatatypeProperty ; rdfs:label "Topic Text" ; rdfs:comment "The discussion topic question or statement" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:string . checkup:topicCategory a owl:DatatypeProperty ; rdfs:label "Topic Category" ; rdfs:comment "Category: durationBased, recentChange, adherenceConcern, sideEffectReport, multipleForSame, supplementDoctor, interactionRisk, refillDue, effectivenessConcern, costConcern, custom" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:string . checkup:topicSource a owl:DatatypeProperty ; rdfs:label "Topic Source" ; rdfs:comment "Source type: medication, supplement, medicationPair, supplementMedicationPair, adherencePattern, custom" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:string . checkup:relevanceScore a owl:DatatypeProperty ; rdfs:label "Relevance Score" ; rdfs:comment "AI-computed relevance score (0.0-1.0)" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:double . checkup:isSelected a owl:DatatypeProperty ; rdfs:label "Is Selected" ; rdfs:comment "Whether the topic is active for the next visit" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:boolean . checkup:relatedMedicationName a owl:DatatypeProperty ; rdfs:label "Related Medication Name" ; rdfs:comment "Optional medication name providing context" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:string . checkup:detailText a owl:DatatypeProperty ; rdfs:label "Detail Text" ; rdfs:comment "Optional supplementary detail text" ; rdfs:domain checkup:DiscussionTopic ; rdfs:range xsd:string . # --- CheckInSettings Properties --- checkup:checkInEnabled a owl:DatatypeProperty ; rdfs:label "Check-in Enabled" ; rdfs:comment "Whether daily check-ins are active" ; rdfs:domain checkup:CheckInSettings ; rdfs:range xsd:boolean . checkup:notificationHour a owl:DatatypeProperty ; rdfs:label "Notification Hour" ; rdfs:comment "Notification hour in 24-hour format (0-23)" ; rdfs:domain checkup:CheckInSettings ; rdfs:range xsd:integer . checkup:notificationMinute a owl:DatatypeProperty ; rdfs:label "Notification Minute" ; rdfs:comment "Notification minute (0-59)" ; rdfs:domain checkup:CheckInSettings ; rdfs:range xsd:integer . checkup:enabledDays a owl:DatatypeProperty ; rdfs:label "Enabled Days" ; rdfs:comment "Comma-separated enabled days: sunday, monday, tuesday, wednesday, thursday, friday, saturday" ; rdfs:domain checkup:CheckInSettings ; rdfs:range xsd:string . # --- IntakeFormData aggregate links (v1.5) --- checkup:hasDailyCheckIn a owl:ObjectProperty ; rdfs:label "Has Daily Check-in" ; rdfs:comment "Link to a daily medication adherence check-in record." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:DailyCheckIn . checkup:hasDiscussionTopic a owl:ObjectProperty ; rdfs:label "Has Discussion Topic" ; rdfs:comment "Link to a discussion topic for a healthcare visit." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:DiscussionTopic . checkup:hasCheckInSettings a owl:ObjectProperty ; rdfs:label "Has Check-in Settings" ; rdfs:comment "Link to check-in notification configuration." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:CheckInSettings . # ============================================================================ # INTAKE QUESTIONNAIRE CLASSES (v1.6) # ============================================================================ # FHIR-inspired intake questionnaire response model using native Cascade # Protocol serialization. Uses LOINC panel codes as questionnaire identifiers # and SNOMED CT for coded answers. Supports template-driven intake form # expansion (Kaiser eCheck-In gap analysis: surgical history, social history, # advanced care planning, pharmacy preference). # # Design rationale: Full FHIR QuestionnaireResponse was evaluated and # rejected — ~5-6x more verbose in RDF/Turtle due to fhir:v blank-node # wrapping for every primitive value, creates two incompatible RDF patterns # in the same Pod, and no standard FHIR Questionnaires combine surgical + # social + safety screening into a single intake form. This hybrid approach # uses LOINC/SNOMED as the actual vocabulary with checkup: providing only # the minimal container. A mechanical transformation to FHIR # QuestionnaireResponse JSON is preserved for clinical system export. # # See: https://hl7.org/fhir/questionnaireresponse.html checkup:IntakeQuestionnaireResponse a owl:Class ; rdfs:label "Intake Questionnaire Response" ; rdfs:comment "Response to a standardized intake questionnaire section. Structure inspired by FHIR QuestionnaireResponse but uses native Cascade Protocol serialization. Uses LOINC panel codes as questionnaire identifiers and SNOMED CT codes for standardized answers." ; cascade:subConcern "intake" ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso fhir:QuestionnaireResponse . checkup:ResponseItem a owl:Class ; rdfs:label "Response Item" ; rdfs:comment "A question-answer pair within an intake questionnaire response. Uses LOINC item codes as link identifiers and SNOMED CT codes for coded answers where available. Supports nested sub-items via childItem for hierarchical questionnaire structures." ; cascade:subConcern "intake" ; rdfs:subClassOf prov:Entity . # --- IntakeQuestionnaireResponse Properties --- checkup:questionnaireId a owl:DatatypeProperty ; rdfs:label "Questionnaire ID" ; rdfs:comment "LOINC panel code identifying the questionnaire section (e.g., 71421-2 for Social History, 10187-3 for Review of Systems)" ; rdfs:domain checkup:IntakeQuestionnaireResponse ; rdfs:range xsd:string . checkup:questionnaireTitle a owl:DatatypeProperty ; rdfs:label "Questionnaire Title" ; rdfs:comment "Human-readable title of the questionnaire section" ; rdfs:domain checkup:IntakeQuestionnaireResponse ; rdfs:range xsd:string . checkup:responseItem a owl:ObjectProperty ; rdfs:label "Response Item" ; rdfs:comment "Link to a ResponseItem containing a question-answer pair" ; rdfs:domain checkup:IntakeQuestionnaireResponse ; rdfs:range checkup:ResponseItem . checkup:completedAt a owl:DatatypeProperty ; rdfs:label "Completed At" ; rdfs:comment "Timestamp when the questionnaire response was completed" ; rdfs:domain checkup:IntakeQuestionnaireResponse ; rdfs:range xsd:dateTime . # --- ResponseItem Properties --- checkup:itemLinkId a owl:DatatypeProperty ; rdfs:label "Item Link ID" ; rdfs:comment "LOINC item code identifying the question (e.g., 72166-2 for Tobacco smoking status, 63512-8 for Alcohol use)" ; rdfs:domain checkup:ResponseItem ; rdfs:range xsd:string . checkup:itemText a owl:DatatypeProperty ; rdfs:label "Item Text" ; rdfs:comment "Human-readable question display text" ; rdfs:domain checkup:ResponseItem ; rdfs:range xsd:string . checkup:itemAnswer a owl:DatatypeProperty ; rdfs:label "Item Answer" ; rdfs:comment "Free-text answer value" ; rdfs:domain checkup:ResponseItem ; rdfs:range xsd:string . checkup:itemAnswerCode a owl:DatatypeProperty ; rdfs:label "Item Answer Code" ; rdfs:comment "Coded answer value from a standard terminology (SNOMED CT or LOINC answer code)" ; rdfs:domain checkup:ResponseItem ; rdfs:range xsd:string . checkup:itemAnswerCodeSystem a owl:DatatypeProperty ; rdfs:label "Item Answer Code System" ; rdfs:comment "Code system URI for the coded answer (e.g., http://snomed.info/sct/, http://loinc.org/)" ; rdfs:domain checkup:ResponseItem ; rdfs:range xsd:anyURI . checkup:childItem a owl:ObjectProperty ; rdfs:label "Child Item" ; rdfs:comment "Nested sub-item for hierarchical questionnaire structures (e.g., follow-up questions based on answers)" ; rdfs:domain checkup:ResponseItem ; rdfs:range checkup:ResponseItem . # --- IntakeFormData aggregate link (v1.6) --- checkup:hasQuestionnaireResponse a owl:ObjectProperty ; rdfs:label "Has Questionnaire Response" ; rdfs:comment "Link to a completed intake questionnaire response section." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:IntakeQuestionnaireResponse . # --- IntakeFormData aggregate links (v1.7) --- checkup:hasImmunization a owl:ObjectProperty ; rdfs:label "Has Immunization" ; rdfs:comment "Link to an immunization summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:ImmunizationSummary . checkup:hasLabResult a owl:ObjectProperty ; rdfs:label "Has Lab Result" ; rdfs:comment "Link to a lab result summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:LabResultSummary . checkup:hasWellnessBaseline a owl:ObjectProperty ; rdfs:label "Has Wellness Baseline" ; rdfs:comment "DEPRECATED (v3.0): checkup:WellnessBaseline class removed. Use checkup:WellnessProfileReference to link to SDK HealthProfile." ; owl:deprecated "true"^^xsd:boolean ; rdfs:domain checkup:IntakeFormData . checkup:hasActivitySummary a owl:ObjectProperty ; rdfs:label "Has Activity Summary" ; rdfs:comment "DEPRECATED (v3.0): checkup:ActivitySummary class removed. Use checkup:WellnessProfileReference to link to SDK HealthProfile." ; owl:deprecated "true"^^xsd:boolean ; rdfs:domain checkup:IntakeFormData . # --- IntakeFormData aggregate links (v1.8) --- checkup:hasProcedure a owl:ObjectProperty ; rdfs:label "Has Procedure" ; rdfs:comment "Link to a procedure summary entry." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:ProcedureSummary . # ============================================================================ # MEDICATION CHANGE RECORD (v1.9) # ============================================================================ # Tracks changes to medications and supplements between visit snapshots. # Consumed by report generators (PlainTextReportGenerator, PatientBriefSections, # ProviderViews) for the "Changes Since Last Visit" section. checkup:MedicationChangeRecord a owl:Class ; rdfs:label "Medication Change Record" ; rdfs:comment "A recorded change to a medication or supplement since the last visit snapshot. Tracked for the Changes Since Last Visit report section." ; cascade:subConcern "analytics" ; rdfs:subClassOf prov:Entity . checkup:changeType a owl:DatatypeProperty ; rdfs:label "Change Type" ; rdfs:comment "Type of change: added, stopped, doseChanged, frequencyChanged, adherenceChanged, statusChanged." ; rdfs:domain checkup:MedicationChangeRecord ; rdfs:range xsd:string . checkup:changedField a owl:DatatypeProperty ; rdfs:label "Changed Field" ; rdfs:comment "Name of the field that was changed (e.g., dose, frequency)." ; rdfs:domain checkup:MedicationChangeRecord ; rdfs:range xsd:string . checkup:changeRecordPreviousValue a owl:DatatypeProperty ; rdfs:label "Previous Value" ; rdfs:comment "Value of the field before the change." ; rdfs:domain checkup:MedicationChangeRecord ; rdfs:range xsd:string . checkup:changeRecordNewValue a owl:DatatypeProperty ; rdfs:label "New Value" ; rdfs:comment "Value of the field after the change." ; rdfs:domain checkup:MedicationChangeRecord ; rdfs:range xsd:string . checkup:changeDate a owl:DatatypeProperty ; rdfs:label "Change Date" ; rdfs:comment "Date when the change occurred." ; rdfs:domain checkup:MedicationChangeRecord ; rdfs:range xsd:dateTime . checkup:hasChange a owl:ObjectProperty ; rdfs:label "Has Change" ; rdfs:comment "Link from a MedicationSummary or SupplementSummary to a change record." ; rdfs:range checkup:MedicationChangeRecord . # --- IntakeFormData aggregate links (v1.9) --- # (hasChange is defined on MedicationSummary/SupplementSummary, not IntakeFormData) # ============================================================================ # VISIT PREP TRAY (v3.1 — Task A7) # ============================================================================ checkup:VisitPrepTray a owl:Class ; rdfs:label "Visit Prep Tray" ; rdfs:comment "Per-appointment visit preparation container grouping issues, discussion topics, questions, and notes. Uses a typed payload wrapper (VisitItemPayload) to preserve existing type richness from VisitIssue, DiscussionTopic, and SuggestedQuestion." ; cascade:subConcern "intake" ; rdfs:subClassOf prov:Entity . checkup:VisitTrayItem a owl:Class ; rdfs:label "Visit Tray Item" ; rdfs:comment "A typed item within a VisitPrepTray. Wraps VisitIssue, DiscussionTopic, SuggestedQuestion, note, or custom content with intent category and resolution tracking." ; rdfs:subClassOf prov:Entity . # --- VisitPrepTray Properties --- checkup:prepTitle a owl:DatatypeProperty ; rdfs:label "Prep Title" ; rdfs:comment "Display title for the visit prep (e.g., 'Rheumatologist - Feb 28')." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range xsd:string . checkup:appointmentDate a owl:DatatypeProperty ; rdfs:label "Appointment Date" ; rdfs:comment "Scheduled appointment date and time." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range xsd:dateTime . checkup:prepProviderName a owl:DatatypeProperty ; rdfs:label "Prep Provider Name" ; rdfs:comment "Name of the healthcare provider for this appointment." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range xsd:string . checkup:specialty a owl:DatatypeProperty ; rdfs:label "Specialty" ; rdfs:comment "Provider specialty (e.g., 'Rheumatology', 'Cardiology')." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range xsd:string . checkup:visitPrepStatus a owl:DatatypeProperty ; rdfs:label "Visit Prep Status" ; rdfs:comment "Tray status: preparing, ready, completed." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range xsd:string . checkup:prepCompletedDate a owl:DatatypeProperty ; rdfs:label "Prep Completed Date" ; rdfs:comment "When the visit was marked completed." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range xsd:dateTime . checkup:hasTrayItem a owl:ObjectProperty ; rdfs:label "Has Tray Item" ; rdfs:comment "Link from a VisitPrepTray to a VisitTrayItem." ; rdfs:domain checkup:VisitPrepTray ; rdfs:range checkup:VisitTrayItem . # --- VisitTrayItem Properties --- checkup:payloadType a owl:DatatypeProperty ; rdfs:label "Payload Type" ; rdfs:comment "Payload discriminator: issue, topic, question, note, custom." ; rdfs:domain checkup:VisitTrayItem ; rdfs:range xsd:string . checkup:intentCategory a owl:DatatypeProperty ; rdfs:label "Intent Category" ; rdfs:comment "Intent of the tray item: discuss, schedule, inform, ask." ; rdfs:domain checkup:VisitTrayItem ; rdfs:range xsd:string . checkup:isResolved a owl:DatatypeProperty ; rdfs:label "Is Resolved" ; rdfs:comment "Whether the tray item has been addressed or resolved." ; rdfs:domain checkup:VisitTrayItem ; rdfs:range xsd:boolean . checkup:sourceSection a owl:DatatypeProperty ; rdfs:label "Source Section" ; rdfs:comment "Health Map section this item originated from (e.g., 'medications', 'conditions')." ; rdfs:domain checkup:VisitTrayItem ; rdfs:range xsd:string . checkup:sourceRecordId a owl:DatatypeProperty ; rdfs:label "Source Record ID" ; rdfs:comment "UUID linking to the source record that prompted this tray item." ; rdfs:domain checkup:VisitTrayItem ; rdfs:range xsd:anyURI . # --- IntakeFormData aggregate links (v3.1) --- checkup:hasVisitPrep a owl:ObjectProperty ; rdfs:label "Has Visit Prep" ; rdfs:comment "Link from IntakeFormData to a VisitPrepTray." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:VisitPrepTray . checkup:activeVisitPrepId a owl:DatatypeProperty ; rdfs:label "Active Visit Prep ID" ; rdfs:comment "UUID of the currently active visit prep tray on IntakeFormData." ; rdfs:domain checkup:IntakeFormData ; rdfs:range xsd:string . # ============================================================================ # DARK VOCABULARY REMEDIATION (v2.2 — PF1-R) # ============================================================================ # Properties previously emitted by CheckupSerializer but not formally defined. # Corrected property names and new definitions added in Schema Refactoring # Plan Phase 3, PF1-R. # --- IntakeFormData aggregate link --- checkup:hasLabResults a owl:ObjectProperty ; rdfs:label "Has Lab Results" ; rdfs:comment "Link to a lab result summary entry. Replaces dark vocabulary checkup:hasLabResultSummary." ; rdfs:domain checkup:IntakeFormData ; rdfs:range checkup:LabResultSummary . # --- Structural / reference properties --- checkup:wellnessProfileRef a owl:ObjectProperty ; rdfs:label "Wellness Profile Reference" ; rdfs:comment "URI reference to the patient's HealthProfile wellness data file." ; rdfs:domain checkup:IntakeFormData . checkup:lastSnapshotDate a owl:DatatypeProperty ; rdfs:label "Last Snapshot Date" ; rdfs:comment "When the medication or supplement data was last snapshotted from clinical records." ; rdfs:range xsd:dateTime . # --- Renamed medication property --- checkup:medicationEndDate a owl:DatatypeProperty ; rdfs:label "Medication End Date" ; rdfs:comment "End date for a medication prescription. Replaces dark vocabulary checkup:endDate (disambiguated from other end-date uses). NOTE: This reflects the administrative EHR order expiry date, which may not match the actual supply exhaustion date. Use checkup:supplyEndDate for the computed supply end date." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:date . checkup:supplyEndDate a owl:DatatypeProperty ; rdfs:label "Supply End Date" ; rdfs:comment "Computed date when the dispensed medication supply is expected to be exhausted, calculated as startDate + supplyDurationDays. Distinguishes clinical supply exhaustion from administrative EHR order expiry (checkup:medicationEndDate). Consumers should use this property — rather than medicationEndDate — to determine whether a medication is likely still active when adherenceStatus is absent." ; rdfs:domain checkup:MedicationSummary ; rdfs:range xsd:date . # --- Renamed immunization property --- checkup:immunizationNotes a owl:DatatypeProperty ; rdfs:label "Immunization Notes" ; rdfs:comment "Free-text notes about an immunization record. Replaces dark vocabulary checkup:notes (disambiguated from clinical:notes used in lab results)." ; rdfs:domain checkup:ImmunizationSummary ; rdfs:range xsd:string . # --- Renamed supplement properties --- checkup:supplementIsActive a owl:DatatypeProperty ; rdfs:label "Supplement Is Active" ; rdfs:comment "Whether the patient is currently taking this supplement. Replaces dark vocabulary checkup:isActive (disambiguated from generic boolean use)." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:boolean . checkup:supplementUPC a owl:DatatypeProperty ; rdfs:label "Supplement UPC" ; rdfs:comment "Universal Product Code for supplement identification. Replaces dark vocabulary checkup:upc (disambiguated with supplement prefix)." ; rdfs:domain checkup:SupplementSummary ; rdfs:range xsd:string . # ============================================================================ # (Legacy Compatibility section removed in v3.0 — ClinicalSummary, HealthRecord, # ReviewOfSystems, DataProvenance classes deleted per CLN1 cleanup) # ============================================================================ # (Tier 3 PatientProfile properties removed in v3.0 — preferredLanguage, # maritalStatus, raceEthnicity, preferredPharmacy, hasLivingWill, # hasPowerOfAttorney, hasDNR, advanceDirectiveNotes deleted per CLN1 cleanup. # Migrated to cascade:/dct: namespace in core.ttl.) # (Insurance migration properties removed in v3.0 — subscriberName, # subscriberRelationship, effectiveDate, rxBin, rxPcn, rxGroup deleted per # CLN1 cleanup. Migrated to coverage: namespace.) # ============================================================================ # CHANGELOG # ============================================================================ # # v3.3 (2026-08-08): SHACL only; no class or property added, removed or # renamed. The checkup:icd10Code sh:pattern required a digit in the second and # third characters of an ICD-10-CM code. The ICD-10-CM Official Guidelines for # Coding and Reporting, Section I.A.2, state that "Characters for categories, # subcategories and codes may be either a letter or a number" and that codes may # be 3, 4, 5, 6 or 7 characters. Live categories with a letter in the third # position (C4A Merkel cell carcinoma, D3A benign neuroendocrine tumors, M1A # chronic gout) and every code beneath them failed the pattern. Corrected to # "^[A-Z][0-9A-Z]{2}(\\.[0-9A-Z]{1,4})?$". Info severity, so this fixes an # advisory that fired on valid codes rather than a rejection. # # v3.2 (2026-02-26, BUG-002): Added checkup:supplyEndDate DatatypeProperty on # MedicationSummary. Computed as startDate + supplyDurationDays, this field # allows consumers to distinguish administrative EHR order expiry # (checkup:medicationEndDate) from actual supply exhaustion. Updated # checkup:medicationEndDate rdfs:comment to clarify the distinction. # Active classes: 28. Properties added: 1. # # v3.1 (2026-02-22, Task A7): Added VisitPrepTray class (8 properties) and # VisitTrayItem class (5 properties) for per-appointment visit preparation # containers. VisitPrepTray groups VisitIssue, DiscussionTopic, # SuggestedQuestion, and note items with typed payload wrappers and intent # categories. Added hasTrayItem ObjectProperty linking tray to items. Added # hasVisitPrep and activeVisitPrepId aggregate links on IntakeFormData. # Active classes: 28. Part of Health Map v3 Pre-Phase Track A. # # v3.0 (2026-02-18, Phase 4 CLN1): Removed 8 deprecated classes (PatientProfile, # VitalSignsTrend, WellnessBaseline, ActivitySummary, ClinicalSummary, HealthRecord, # ReviewOfSystems, DataProvenance) and their associated properties. Removed 9 core # PatientProfile properties (patientName, dateOfBirth, computedAge, ageGroup, # biologicalSex, genderIdentity, contactPhone, contactEmail, emergencyContact). # Removed 8 deprecated Tier 3 PatientProfile properties (migrated to cascade:/dct: # namespace). Removed 6 deprecated insurance properties (migrated to coverage: # namespace). Removed deprecated checkup:cascadeUri (superseded by cascade:cascadeUri). # Removed VitalSignsTrend properties (8), WellnessBaseline properties (8), # ActivitySummary properties (8). Deprecated hasPatientProfile, hasVitalsTrend, # hasWellnessBaseline, hasActivitySummary aggregate links. Updated basedOnTrend # range to health:MetricTrend. Clean break per guiding constraint #1. # Active classes: 27. Total properties removed: ~50. # # v2.3 (2026-02-18): Deprecated checkup:cascadeUri in favor of # cascade:cascadeUri from core.ttl (Phase 3.5 ONT3). The checkup-specific # definition is superseded by the cross-vocabulary property in core. # # v2.2 (2026-02-17): Dark Vocabulary Remediation (PF1-R) # - Added hasLabResults aggregate link (replaces dark hasLabResultSummary) # - Added wellnessProfileRef ObjectProperty for intake form -> wellness link # - Added lastSnapshotDate DatatypeProperty for medication/supplement snapshot timestamps # - Added medicationEndDate (replaces dark checkup:endDate, disambiguated) # - Added immunizationNotes (replaces dark checkup:notes in immunization context) # - Added supplementIsActive (replaces dark checkup:isActive in supplement context) # - Added cascadeUri ObjectProperty for stable supplement identifiers # - Added supplementUPC (replaces dark checkup:upc, disambiguated with prefix) # - Total new properties: 8 (all previously dark vocabulary) # # v2.1 (2026-02-17): DailyCheckIn now rdfs:subClassOf health:SelfReport. # Base self-report properties (reportDate, reportType, completionStatus, # reportContext, reportNotes) promoted to health: namespace. (Phase 3, WS7) # # v2.0 (2026-02-17): Schema Refactoring Phase 3 — PatientProfile Migration # - Deprecated checkup:PatientProfile (use cascade:PatientProfile from core.ttl) # - Deprecated 8 Tier 3 patient profile properties with cascade:/dct: replacements: # preferredLanguage (-> dct:language), maritalStatus (-> cascade:maritalStatus), # raceEthnicity (-> cascade:raceEthnicity), preferredPharmacy (-> cascade:hasPreferredPharmacy), # hasLivingWill, hasPowerOfAttorney, hasDNR, advanceDirectiveNotes # (-> cascade:AdvanceDirectives class properties) # - Total deprecated classes: 8 (PatientProfile + WellnessBaseline + ActivitySummary + # VitalSignsTrend + HealthRecord + ClinicalSummary + ReviewOfSystems + ScreeningStatus-like) # # v1.11 (2026-02-18): Updated InsuranceInfo class comment to reference coverage: # namespace for mixed namespace pattern. Part of Schema Refactoring Plan # Phase 2 (WS4). # # v1.10 (2026-02-17): Schema Refactoring Phase 1 — Taxonomy & Dark Vocabulary # - Added cascade:subConcern annotations to all 35 classes (31 active + 4 # already-deprecated; summary, analytics, intake, self-report, identity, # financial, app-config, structural, deprecated) # - Deprecated WellnessBaseline and ActivitySummary (use WellnessProfileReference) # - Deprecated ReviewOfSystems (use IntakeQuestionnaireResponse with LOINC 10187-3) # - Defined 14 dark vocabulary properties with migration annotations: # - 8 Tier 3 patient profile properties (preferredLanguage, maritalStatus, # raceEthnicity, preferredPharmacy, hasLivingWill, hasPowerOfAttorney, # hasDNR, advanceDirectiveNotes) — migrating to cascade:/dct: in WS6 # - 6 insurance properties (subscriberName, subscriberRelationship, # effectiveDate, rxBin, rxPcn, rxGroup) — migrating to coverage: in WS4 # - Zero dark vocabulary: every serializer-emitted property now defined in TTL # # v1.9 (2026-02-10): Medication Change Records # - Added MedicationChangeRecord class for tracking medication/supplement # changes between visit snapshots (5 properties + hasChange link) # - Enables "Changes Since Last Visit" report section persistence # - hasChange link defined without domain constraint — used by both # MedicationSummary and SupplementSummary # - Property names prefixed to avoid collision with VitalSignsTrend # previousValue/newValue: changeRecordPreviousValue, changeRecordNewValue # - Total classes: 17 (was 16) + 2 questionnaire classes # # v1.8 (2026-02-10): Procedure Summary & Serializer Alignment # - Added ProcedureSummary class with mixed namespace pattern # (4 checkup: properties + 7 clinical: cross-references) # - Added hasProcedure aggregate link on IntakeFormData # - Total classes: 16 (was 15) + 2 questionnaire classes # # v1.7 (2026-02-10): Wellness Data & Clinical Summary Classes # - Added 5 new classes: WellnessBaseline, ActivitySummary, # ImmunizationSummary, LabResultSummary, WellnessProfileReference # - WellnessBaseline: aggregated vital sign baseline (8 properties) # - ActivitySummary: combined activity/sleep metrics (8 properties) # - ImmunizationSummary: patient-facing vaccine record (16 properties) # using mixed namespace pattern # - LabResultSummary: patient-facing lab summary with labCategory # (uses clinical: properties for standardized data) # - WellnessProfileReference: lightweight pointer to full wellness profile # - Added checkup:noteContent to VisitPrepSummary (merges VisitPrepNotes) # - Added 4 MedicationSummary verification properties: needsVerification, # verificationReason, lastVerifiedDate, importSessionId # - Added 2 SuggestedQuestion properties: basedOnTemplate, basedOnScreening # - Added 4 IntakeFormData aggregate links: hasImmunization, hasLabResult, # hasWellnessBaseline, hasActivitySummary # # v1.6 (2026-02-09) - Intake Questionnaire Data Model # - Added 2 new classes: IntakeQuestionnaireResponse, ResponseItem # - IntakeQuestionnaireResponse: FHIR-inspired questionnaire container using # LOINC panel codes as identifiers and SNOMED CT for coded answers # - ResponseItem: question-answer pair with nested sub-items support # - Added 10 new properties: questionnaireId, questionnaireTitle, responseItem, # completedAt, itemLinkId, itemText, itemAnswer, itemAnswerCode, # itemAnswerCodeSystem, childItem # - Added hasQuestionnaireResponse link from IntakeFormData # - Design follows "established vocabularies first" principle: # LOINC/SNOMED as actual vocabulary, checkup: as minimal container # - Mechanical transformation path to FHIR QuestionnaireResponse JSON # preserved for clinical system interoperability # - Supports template-driven intake form expansion (Kaiser eCheck-In # gap analysis: surgical history, social history, advanced care planning) # # v1.5 (2026-02-07) - Adherence & Discussion Classes # - Added 3 new classes: DailyCheckIn, DiscussionTopic, CheckInSettings # - DailyCheckIn: patient-reported medication adherence with per-medication # tracking (LOINC 79474-4, SNOMED 225390008/418633004) # - DiscussionTopic: AI-generated or user-created visit discussion items # with 11 categories, relevance scoring, and medication context # - CheckInSettings: application configuration for notification scheduling # - Added 17 new properties for check-in, discussion, and settings # - Added hasDailyCheckIn, hasDiscussionTopic, hasCheckInSettings links # from IntakeFormData # - Supports Bug 6/11 export overhaul Phase 4B serialization # # v1.4 (2026-02-01) - Insight & Correlation Classes, Visit Prep # - Added 5 new Layer 3 classes: WellnessSummary, TrendInsight, # CorrelationInsight, KeyIndicator, AttentionItem # - Added VisitPrepSummary class for curated clinical visit preparation # - Added aggregate links to IntakeFormData: hasWellnessSummary, # hasTrendInsight, hasCorrelationInsight, hasKeyIndicator, # hasAttentionItem, hasVisitPrepSummary # - Deprecated VitalSignsTrend (superseded by health:MetricTrend at # Layer 2 + checkup:WellnessSummary at Layer 3) # - CorrelationInsight enforces temporal-only language (no causation) # - insightGeneratedBy uses prov:Agent range for both software and # human agent provenance # - Added health: prefix import for cross-layer references # # v1.3 (2026-01-17) - Medication Experience Phase A: EHR Provenance # - Added EHR Provenance Properties section: # - ehrSystemName: EHR patient portal identifier # - ehrOrganization: Healthcare org name # - fhirResourceType: FHIR resource type origin # - lastSyncedFromSource: Sync timestamp # - Added Medication Classification Properties section: # - relevanceTier: current, needsVerification, historical, filtered # - brandName: Brand name mapping for display # - commonBrandNames: Comma-separated brand names for reference # - therapeuticClass: Pharmacological class # - tierClassificationReason: Human-readable classification explanation # - These properties enable cross-app medication data portability # # v1.2 (2026-01-15) - Phase 7: SHACL Shapes + Validation # - Added Phase 3 MedicationSummary properties (episodeId, indication, cost, functionalImpact) # - Added doctorAware property for SupplementSummary # - Updated to align with checkup.shapes.ttl v1.2 # - SHACL shapes now validate: # - MedicationSummary with episode linkage # - SupplementSummary with required regulatoryStatus # # v1.1 (2026-01-15) # - Added SupplementSummary class for dietary supplements and OTC products # - Added 12 supplement-specific properties (regulatoryStatus, evidenceStrength, etc.) # - Added hasSupplement property to IntakeFormData # - Supplements explicitly separated from medications due to different regulatory status # # v1.0 (2026-01-03) # - Initial release with 12 core classes # - IntakeFormData, PatientProfile, InsuranceInfo, MedicationSummary, # AllergySummary, ConditionSummary, FamilyHistoryEntry, VitalSignsTrend, # ScreeningStatus, VisitIssue, SuggestedQuestion, DiagnosticTestResult # - POTS Check integration via DiagnosticTestResult # - Data provenance properties for source tracking # # End of Cascade Checkup Vocabulary v3.1 ## Ontology: pots ## File: pots.ttl @prefix pots: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix ucum: . @prefix prov: . # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports ; dct:title "Cascade Protocol POTS Check Ontology"@en ; dct:description "Vocabulary for POTS (Postural Orthostatic Tachycardia Syndrome) home screening checks using the NASA Lean Test protocol"@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2025-10-31"^^xsd:date ; dct:modified "2026-02-26"^^xsd:date ; owl:versionInfo "1.4" ; rdfs:comment "Layer 2 domain-specific vocabulary for POTS Check app. Defines the POTS screening test protocol, raw measurement classes, and computed results. Describes consumer-generated wellness data from guided orthostatic screening checks performed with Apple Watch and iPhone."@en ; rdfs:seeAlso . # ============================================================================ # Core Classes # ============================================================================ pots:POTSCheckResult a owl:Class ; rdfs:label "POTS Check Result"@en ; rdfs:comment "Complete result bundle from a guided home orthostatic screening check using Apple Watch heart rate monitoring and iPhone posture detection"@en ; rdfs:subClassOf [ a owl:Restriction ; owl:onProperty pots:date ; owl:cardinality 1 ] ; rdfs:subClassOf [ a owl:Restriction ; owl:onProperty pots:protocol ; owl:cardinality 1 ] ; rdfs:subClassOf prov:Entity . pots:HeartRateMeasurement a owl:Class ; rdfs:label "Heart Rate Measurement"@en ; rdfs:comment "Timed heart rate observation captured during standing phase"@en ; rdfs:subClassOf fhir:Observation . pots:BloodPressureMeasurement a owl:Class ; rdfs:label "Blood Pressure Measurement"@en ; rdfs:comment "Timed blood pressure observation (systolic/diastolic) captured during check"@en ; rdfs:subClassOf fhir:Observation . pots:SymptomEvent a owl:Class ; rdfs:label "Symptom Event"@en ; rdfs:comment "User-reported symptom captured during the standing phase of the check"@en . pots:PostureStability a owl:Class ; rdfs:label "Posture Stability Sample"@en ; rdfs:comment "Posture quality measurement from Core Motion sensors during standing phase"@en . # ============================================================================ # Test Protocol Classes # ============================================================================ pots:TestProtocol a owl:Class ; rdfs:label "Test Protocol"@en ; rdfs:comment "Standardized orthostatic test protocol"@en . pots:NASALean a pots:TestProtocol ; rdfs:label "NASA Lean Test"@en ; rdfs:comment "10 minutes supine followed by 10 minutes standing against wall, as specified by NASA research protocols"@en ; rdfs:seeAlso . # ============================================================================ # Data Properties - Test Metadata # ============================================================================ pots:date a owl:DatatypeProperty ; rdfs:label "Check Date"@en ; rdfs:comment "ISO 8601 timestamp when the check was performed"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:dateTime . pots:protocol a owl:DatatypeProperty ; rdfs:label "Test Protocol"@en ; rdfs:comment "Protocol used for the check (currently only 'nasaLean')"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:string . pots:baselineDurationSeconds a owl:DatatypeProperty ; rdfs:label "Baseline Duration (seconds)"@en ; rdfs:comment "Duration of the supine baseline phase in seconds (typically 600 for 10 minutes)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:integer . pots:uprightDurationSeconds a owl:DatatypeProperty ; rdfs:label "Upright Duration (seconds)"@en ; rdfs:comment "Duration of the standing phase in seconds (typically 600 for 10 minutes)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:integer . # ============================================================================ # Data Properties - Heart Rate # ============================================================================ pots:supineHeartRate a owl:ObjectProperty ; rdfs:label "Supine Heart Rate"@en ; rdfs:comment "Baseline heart rate observation from final 2 minutes of supine phase"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range pots:HeartRateMeasurement ; owl:equivalentProperty sct:78564009 . # Heart rate at rest pots:standingHeartRates a owl:ObjectProperty ; rdfs:label "Standing Heart Rates"@en ; rdfs:comment "Ordered list of heart rate measurements captured during standing phase"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range rdf:List . pots:recoveryHeartRate a owl:DatatypeProperty ; rdfs:label "Recovery Heart Rate"@en ; rdfs:comment "Optional heart rate measurement after returning to supine position (v1.1+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:double . pots:maxHeartRateDelta a owl:DatatypeProperty ; rdfs:label "Maximum Heart Rate Delta"@en ; rdfs:comment "Pre-computed maximum heart rate increase from baseline using 30-second rolling window average during standing phase (v1.2+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:double . # ============================================================================ # Data Properties - Blood Pressure # ============================================================================ pots:supineBloodPressure a owl:ObjectProperty ; rdfs:label "Supine Blood Pressure"@en ; rdfs:comment "Blood pressure measurement taken during or just before supine phase"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range pots:BloodPressureMeasurement . pots:standingBloodPressures a owl:ObjectProperty ; rdfs:label "Standing Blood Pressures"@en ; rdfs:comment "List of blood pressure measurements captured at specific minutes during standing phase (typically minutes 1, 3, 5, 7, 10)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range rdf:List . # ============================================================================ # Data Properties - Symptoms # ============================================================================ pots:symptomEvents a owl:ObjectProperty ; rdfs:label "Symptom Events"@en ; rdfs:comment "List of symptoms reported by user during the check"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range rdf:List . pots:symptom a owl:DatatypeProperty ; rdfs:label "Symptom Type"@en ; rdfs:comment "Type of symptom experienced (e.g., 'Dizziness', 'Palpitations')"@en ; rdfs:domain pots:SymptomEvent ; rdfs:range xsd:string . pots:notes a owl:DatatypeProperty ; rdfs:label "Symptom Notes"@en ; rdfs:comment "Optional user-provided notes describing the symptom (max 100 characters)"@en ; rdfs:domain pots:SymptomEvent ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Clinical Flags # ============================================================================ pots:potsThresholdMet a owl:DatatypeProperty ; rdfs:label "POTS Threshold Met"@en ; rdfs:comment "True if heart rate increase meets POTS screening criteria (age-adjusted: >=30 bpm for adults 20+, >=40 bpm for pediatric 13-19)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:boolean . pots:orthostaticHypotensionSuspected a owl:DatatypeProperty ; rdfs:label "Orthostatic Hypotension Suspected"@en ; rdfs:comment "True if blood pressure drop suggests orthostatic hypotension (systolic >=20 mmHg or diastolic >=10 mmHg drop)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:boolean . pots:orthostaticHypertensionSuspected a owl:DatatypeProperty ; rdfs:label "Orthostatic Hypertension Suspected"@en ; rdfs:comment "True if blood pressure increase suggests orthostatic hypertension (systolic >=20 mmHg increase) (v1.1+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:boolean . pots:isIncomplete a owl:DatatypeProperty ; rdfs:label "Is Incomplete"@en ; rdfs:comment "True if the check was terminated early or did not complete all phases"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:boolean . pots:earlyTerminationReason a owl:DatatypeProperty ; rdfs:label "Early Termination Reason"@en ; rdfs:comment "Human-readable reason why the check was terminated early (v1.1+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:string . # ============================================================================ # Data Properties - Posture Quality (v1.1+) # ============================================================================ pots:postureQualityScore a owl:DatatypeProperty ; rdfs:label "Posture Quality Score"@en ; rdfs:comment "Letter grade (A-D) indicating overall posture stability during standing phase (v1.1+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:string . pots:movementArtifactPercentage a owl:DatatypeProperty ; rdfs:label "Movement Artifact Percentage"@en ; rdfs:comment "Percentage of standing phase with excessive movement artifacts (0.0 to 100.0) (v1.1+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:double . pots:postureStability a owl:ObjectProperty ; rdfs:label "Posture Stability"@en ; rdfs:comment "List of posture stability measurements from Core Motion during standing phase (v1.1+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range rdf:List . pots:isStable a owl:DatatypeProperty ; rdfs:label "Is Stable"@en ; rdfs:comment "Boolean indicating whether posture was stable at this timestamp"@en ; rdfs:domain pots:PostureStability ; rdfs:range xsd:boolean . pots:stabilityScore a owl:DatatypeProperty ; rdfs:label "Stability Score"@en ; rdfs:comment "Numerical stability score from 0.0 (unstable) to 1.0 (perfectly stable)"@en ; rdfs:domain pots:PostureStability ; rdfs:range xsd:double . # ============================================================================ # Data Properties - User Context (v1.2+) # ============================================================================ pots:userAgeAtTest a owl:DatatypeProperty ; rdfs:label "User Age at Test"@en ; rdfs:comment "User's age in years at the time of the check, used for age-based threshold calculation (v1.2+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:integer . # ============================================================================ # Data Properties - Timing # ============================================================================ pots:atSeconds a owl:DatatypeProperty ; rdfs:label "Seconds Since Standing"@en ; rdfs:comment "Time offset in seconds from the start of the standing phase"@en ; rdfs:range xsd:integer . pots:secondsSinceStand a owl:DatatypeProperty ; rdfs:label "Seconds Since Stand"@en ; rdfs:comment "Time offset in seconds from the moment the user stood up. Used in standingHeartRates and standingBloodPressures list items for temporal positioning."@en ; rdfs:range xsd:double . pots:timestampSeconds a owl:DatatypeProperty ; rdfs:label "Timestamp Seconds"@en ; rdfs:comment "Absolute timestamp in seconds, used in SymptomEvent items for precise event timing."@en ; rdfs:range xsd:double . # ============================================================================ # Apple Health Import Properties (v1.3) # ============================================================================ pots:importedBloodPressureHistory a owl:ObjectProperty ; rdfs:label "Imported Blood Pressure History"@en ; rdfs:comment "Collection of blood pressure readings imported from Apple Health for longitudinal context. Each item is a fhir:Observation with systolic/diastolic components."@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range rdf:List . pots:importedHRVHistory a owl:ObjectProperty ; rdfs:label "Imported HRV History"@en ; rdfs:comment "Collection of HRV readings imported from Apple Health for longitudinal context. Each item is a fhir:Observation with value in milliseconds."@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range rdf:List . pots:averageImportedBloodPressure a owl:DatatypeProperty ; rdfs:label "Average Imported Blood Pressure"@en ; rdfs:comment "Computed average of imported blood pressure readings from Apple Health, formatted as 'systolic/diastolic' string."@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:string . pots:averageImportedHRV a owl:DatatypeProperty ; rdfs:label "Average Imported HRV"@en ; rdfs:comment "Computed average of imported HRV readings from Apple Health (ms)."@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:double . # ============================================================================ # V2 Test Configuration & Patient Context (v1.4+) # ============================================================================ pots:baselineDurationMinutes a owl:DatatypeProperty ; rdfs:label "Baseline Duration (minutes)"@en ; rdfs:comment "User-selected supine baseline duration in whole minutes. One of: 5, 10, or 15. Captures the deliberate configuration choice (distinct from pots:baselineDurationSeconds which records actual elapsed time). Values below 10 minutes carry a clinical advisory per Bateman Horne Center guidance. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:integer . pots:testNotes a owl:DatatypeProperty ; rdfs:label "Test Notes"@en ; rdfs:comment "Optional free-text notes entered by the patient about this check session (e.g., current symptoms, recent activity, medication timing). Maximum 500 characters. Distinct from pots:notes which is scoped to individual pots:SymptomEvent items. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:string . pots:bpProvided a owl:DatatypeProperty ; rdfs:label "Blood Pressure Provided"@en ; rdfs:comment "True if blood pressure cuff data was entered during this check session. False if the user proceeded without a blood pressure cuff. Used by result views and PDF generator to gate BP-dependent sections and display appropriate limitation warnings. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:boolean . # ============================================================================ # Proportional Pulse Pressure — Per-Minute Values (v1.4+) # ============================================================================ # PPP = (SBP - DBP) / SBP. Values stored as ratios (0.0 to 1.0). # Clinical reference: PPP < 0.25 (25%) may indicate failure to constrict # on standing (Bateman Horne Center clinical guidance). # Predicates are absent when no BP reading exists at that timepoint. # Timepoints mirror the standing BP collection schedule: 1, 3, 5, 7, 10 minutes. pots:pppMinute1 a owl:DatatypeProperty ; rdfs:label "Proportional Pulse Pressure at 1 Minute"@en ; rdfs:comment "Proportional Pulse Pressure ratio (SBP-DBP)/SBP at 1 minute of standing. Stored as a decimal ratio 0.0-1.0. Absent if no blood pressure reading was taken at this timepoint. Reference: values >= 0.25 (25%) are considered normal per Bateman Horne Center clinical guidance. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:decimal . pots:pppMinute3 a owl:DatatypeProperty ; rdfs:label "Proportional Pulse Pressure at 3 Minutes"@en ; rdfs:comment "Proportional Pulse Pressure ratio (SBP-DBP)/SBP at 3 minutes of standing. Stored as a decimal ratio 0.0-1.0. Absent if no blood pressure reading was taken at this timepoint. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:decimal . pots:pppMinute5 a owl:DatatypeProperty ; rdfs:label "Proportional Pulse Pressure at 5 Minutes"@en ; rdfs:comment "Proportional Pulse Pressure ratio (SBP-DBP)/SBP at 5 minutes of standing. Stored as a decimal ratio 0.0-1.0. Absent if no blood pressure reading was taken at this timepoint. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:decimal . pots:pppMinute7 a owl:DatatypeProperty ; rdfs:label "Proportional Pulse Pressure at 7 Minutes"@en ; rdfs:comment "Proportional Pulse Pressure ratio (SBP-DBP)/SBP at 7 minutes of standing. Stored as a decimal ratio 0.0-1.0. Absent if no blood pressure reading was taken at this timepoint. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:decimal . pots:pppMinute10 a owl:DatatypeProperty ; rdfs:label "Proportional Pulse Pressure at 10 Minutes"@en ; rdfs:comment "Proportional Pulse Pressure ratio (SBP-DBP)/SBP at 10 minutes of standing. Stored as a decimal ratio 0.0-1.0. Absent if no blood pressure reading was taken at this timepoint. (v1.4+)"@en ; rdfs:domain pots:POTSCheckResult ; rdfs:range xsd:decimal . # ============================================================================ # Alignment with Healthcare Standards # ============================================================================ # SNOMED CT Mappings # - Heart rate: sct:364075005 (Heart rate) # - Supine position: sct:40199007 (Supine body position) # - Standing position: sct:10904000 (Orthostatic body position) # - POTS: sct:703273007 (Postural orthostatic tachycardia syndrome) # - Orthostatic hypotension: sct:28651003 # LOINC Mappings # - Heart rate: loinc:8867-4 (Heart rate) # - Systolic BP: loinc:8480-6 (Systolic blood pressure) # - Diastolic BP: loinc:8462-4 (Diastolic blood pressure) # FHIR Observation Pattern # All heart rate and blood pressure measurements use fhir:Observation structure # with fhir:code, fhir:valueQuantity, and ucum: units # ============================================================================ # Notes # ============================================================================ # Data Classification: Consumer wellness data (non-diagnostic) # Target Age Range: 13+ (COPPA compliant) # Devices: Apple Watch (heart rate), iPhone (posture tracking, UI) # Protocol: NASA Lean Test (10 min supine + 10 min standing against wall) # Diagnostic Disclaimer: Results are for informational purposes only and should # be discussed with a healthcare provider. This is NOT a medical diagnostic tool. # ============================================================================ # Changelog # ============================================================================ # v1.4 (2026-02-26): Added pots:baselineDurationMinutes (user-selected baseline # duration in whole minutes, distinct from pots:baselineDurationSeconds). # Added pots:testNotes (free-text patient notes on the result, distinct from # per-symptom pots:notes). Added pots:bpProvided (boolean flag for BP cuff # presence). Added per-minute PPP predicates pots:pppMinute1, pots:pppMinute3, # pots:pppMinute5, pots:pppMinute7, pots:pppMinute10 for Proportional Pulse # Pressure values (ratio 0.0-1.0) at each standard BP timepoint. # v1.3 (2026-02-10): Added timing properties (secondsSinceStand, timestampSeconds) # for fine-grained temporal resolution in measurement lists. Added Apple Health # import properties (importedBloodPressureHistory, importedHRVHistory, # averageImportedBloodPressure, averageImportedHRV) for longitudinal context. # Renamed postureStabilitySamples to postureStability to match app serializer. # v1.2 (2025-12-15): Added userAgeAtTest, maxHeartRateDelta. # v1.1 (2025-11-15): Added recoveryHeartRate, orthostaticHypertensionSuspected, # earlyTerminationReason, posture quality (postureQualityScore, # movementArtifactPercentage, PostureStability class). # v1.0 (2025-10-31): Initial release. POTSCheckResult, HeartRateMeasurement, # BloodPressureMeasurement, SymptomEvent, TestProtocol, NASALean. ## Ontology: advisory (DRAFT — v1-draft) ## File: advisory.ttl @prefix advisory: . @prefix genomics: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix prov: . @prefix vs: . # ============================================================================ # Changelog # ============================================================================ # v1.0-draft.0.2 (2026-08-20) — Per-term maturity. Every term now carries # vs:term_status (W3C SemWeb Vocabulary Status, originating in FOAF): # "unstable" for draft terms, "archaic" for terms already marked # owl:deprecated. The convention is per-TERM by design, so a vocabulary # can promote individual terms in place without a namespace change; # Cascade previously stated maturity once per vocabulary, in prose no # machine could read. No term definition changed. # v1.0-draft.0.1 (2026-05-05) — Initial draft. # Defines the Cascade Advisory Patch (CAP) profile envelope, the six # AdvisoryClass named individuals, the Cadence enum, the per-pod # TrustedIssuer class with TrustSourceEnum provenance, the AutoApplyPolicy # class with AutoApplyScope, and the detached-JWS signature property # (per D-Q4). References cascade:AdvisoryApplicationActivity from core # v3.1; does not redeclare it. # ============================================================================ # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports ; dct:title "Cascade Protocol Advisory Vocabulary"@en ; dct:description "Vocabulary for the Cascade Advisory Patch (CAP) profile — a constrained subset of W3C LDPatch carrying clinical knowledge updates (variant reclassifications, PGx dosing changes, surveillance guideline updates, etc.) into a Cascade pod with author-readable, statically-auditable, bounded-execution semantics."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-05-05"^^xsd:date ; dct:modified "2026-08-20"^^xsd:date ; owl:versionInfo "1.0-draft.0.2" ; rdfs:comment "DRAFT — vocabulary is under development and subject to change before v1.0 stable release. Architecture: (1) constrained-LDPatch envelope (advisory:CascadeAdvisoryPatch) with required human-readable summary, advisory class, issuer, and issuance time; (2) tiered cadence — SafetyCritical advisories check on every app open, VariantReclassification monthly, SurveillanceGuidelineUpdate quarterly-to-annual (advisory:Cadence enum); (3) per-pod trust — each pod chooses which issuers to trust via advisory:TrustedIssuer records (advisory:TrustSourceEnum tracks how each was added: cascade-cli starter list vs user-added vs imported-from-registry vs verified-via-DID); (4) detached JWS Ed25519 signatures (RFC 7515) for issuer authentication (advisory:signature plus issuer claims); (5) opt-in auto-apply scoped per (issuer x advisoryClass) pair via advisory:AutoApplyPolicy. The application of an advisory to a pod is recorded using cascade:AdvisoryApplicationActivity (declared in core v3.1, not redeclared here)."@en ; rdfs:seeAlso . # ============================================================================ # Profile envelope: CascadeAdvisoryPatch # ============================================================================ advisory:CascadeAdvisoryPatch a owl:Class ; rdfs:label "Cascade Advisory Patch"@en ; rdfs:comment "The envelope of a CAP file — a constrained-LDPatch document delivering a clinical knowledge update to a Cascade pod. Subclass of prov:Entity so applications can be linked via prov:used / prov:wasGeneratedBy. The application of a CAP to a pod is recorded as a cascade:AdvisoryApplicationActivity (defined in core v3.1) which uses prov:used to point at both the patch and the matched record. Every CascadeAdvisoryPatch MUST carry advisory:humanSummary, advisory:advisoryClass, advisory:issuer, and advisory:issuedAt; SHOULD carry advisory:signature; MAY carry advisory:supersedes, advisory:applicableUntil, advisory:advisoryId, advisory:issuerName, advisory:evidenceUrl, advisory:profileVersion, and advisory:appliesToActiveOnly."@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . # ---------------------------------------------------------------------------- # Required envelope properties # ---------------------------------------------------------------------------- advisory:humanSummary a owl:DatatypeProperty ; rdfs:label "Human Summary"@en ; rdfs:comment "Plain-text, one-paragraph, patient-readable summary of what this advisory means and what (if anything) the user should do. REQUIRED on every advisory — the applier MUST reject patches without it. This is the string the queue UI shows the user when prompting for approval."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:advisoryClass a owl:ObjectProperty ; rdfs:label "Advisory Class"@en ; rdfs:comment "The taxonomic category of this advisory, drawn from the six published advisory:AdvisoryClass named individuals. Drives the cadence with which the pod checks for new advisories of this class and the default queue-vs-auto-apply behaviour."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range advisory:AdvisoryClass ; vs:term_status "unstable" . advisory:issuer a owl:ObjectProperty ; rdfs:label "Issuer"@en ; rdfs:comment "The IRI (or DID) identifying the entity that signed this advisory. The pod compares this to its local advisory:TrustedIssuer graph to decide whether to honour the patch silently, queue it for explicit user trust review, or reject it. Range is advisory:TrustedIssuer when the issuer record has been resolved into the pod; xsd:anyURI when the patch is being parsed before resolution."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; vs:term_status "unstable" . advisory:issuedAt a owl:DatatypeProperty ; rdfs:label "Issued At"@en ; rdfs:comment "ISO 8601 dateTime when the issuer signed this advisory. REQUIRED. Used both for ordering (later issuance supersedes earlier in a chain) and for signature freshness checks."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . # ---------------------------------------------------------------------------- # Optional envelope properties # ---------------------------------------------------------------------------- advisory:advisoryId a owl:ObjectProperty ; rdfs:label "Advisory Identifier"@en ; rdfs:comment "Stable identifier IRI for this specific advisory. Used as the target of advisory:supersedes in later advisories that revise or replace this one. Optional but strongly recommended for any advisory that may be superseded later."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; vs:term_status "unstable" . advisory:supersedes a owl:ObjectProperty ; rdfs:label "Supersedes"@en ; rdfs:comment "IRI of a prior advisory:CascadeAdvisoryPatch that this one revises or replaces. Forms a supersession chain. The pod MAY mark records produced by the prior advisory as superseded but MUST NOT delete them — supersession is monotonic and roll-forward auditable per CAP profile constraint C1."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range advisory:CascadeAdvisoryPatch ; vs:term_status "unstable" . advisory:applicableUntil a owl:DatatypeProperty ; rdfs:label "Applicable Until"@en ; rdfs:comment "ISO 8601 dateTime after which this advisory should no longer be applied to new matches. The pod SHOULD still honour applications that occurred before this time. Used by both worked examples (BRCA2 reclassification, CPIC CYP2C19/warfarin) — the canonical name."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . advisory:profileVersion a owl:DatatypeProperty ; rdfs:label "Profile Version"@en ; rdfs:comment "Version of the Cascade Advisory Patch profile this advisory conforms to (e.g. \"0.1\"). Lets the applier reject patches written against a profile version it does not implement."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:issuerName a owl:DatatypeProperty ; rdfs:label "Issuer Display Name"@en ; rdfs:comment "Human-readable display name for the issuer (e.g. \"ClinGen HBOP-VCEP\", \"CPIC\"). Convenience field for UI; the authoritative identity is advisory:issuer."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:evidenceUrl a owl:ObjectProperty ; rdfs:label "Evidence URL"@en ; rdfs:comment "IRI of the publicly-citable evidence supporting this advisory (a ClinVar evidence-repo entry, a CPIC guideline page, a published VCEP statement, etc.). Shown to the user in the queue UI as a 'learn more' link."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; vs:term_status "unstable" . advisory:appliesToActiveOnly a owl:DatatypeProperty ; rdfs:label "Applies To Active Records Only"@en ; rdfs:comment "If true, the advisory should only fire when the matched record is currently 'active' according to the pod's domain semantics (e.g. a medication still on the active list, a condition not marked resolved). Used in PGx dosing advisories so the pod does not re-surface guidance for drugs the user discontinued."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:boolean ; vs:term_status "unstable" . advisory:cadence a owl:ObjectProperty ; rdfs:label "Cadence"@en ; rdfs:comment "Optional override of the default check-cadence for this advisory class. If absent, the pod uses the cadence implied by advisory:advisoryClass."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range advisory:Cadence ; vs:term_status "unstable" . # ============================================================================ # Signature (per D-Q4: detached JWS Ed25519, RFC 7515) # ============================================================================ advisory:signature a owl:DatatypeProperty ; rdfs:label "Signature"@en ; rdfs:comment "Detached JWS in RFC 7515 compact serialization carrying an Ed25519 signature over the canonicalized patch body. The protected header MUST include the JWS claims advisory:signatureIssuer (iss), advisory:signatureIssuedAt (iat), and advisory:signatureContentType (cty, fixed to 'application/x-cascade-advisory-patch'); MAY include advisory:signatureExpiresAt (exp). The signature is verified against the public key on the matching advisory:TrustedIssuer record. Per D-Q4, W3C VC 2.0 envelope wrapping is deferred to a later profile version."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:signatureIssuer a owl:DatatypeProperty ; rdfs:label "Signature Issuer (iss)"@en ; rdfs:comment "JWS 'iss' claim — the issuer URI asserted inside the signed envelope. The applier MUST verify this matches advisory:issuer on the unsigned envelope."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:signatureIssuedAt a owl:DatatypeProperty ; rdfs:label "Signature Issued At (iat)"@en ; rdfs:comment "JWS 'iat' claim — Unix epoch seconds at which the signature was produced. The applier MUST verify this is consistent with advisory:issuedAt within an implementation-defined skew tolerance."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:long ; vs:term_status "unstable" . advisory:signatureExpiresAt a owl:DatatypeProperty ; rdfs:label "Signature Expires At (exp)"@en ; rdfs:comment "JWS 'exp' claim — Unix epoch seconds after which the signature itself is no longer valid (independent of advisory:applicableUntil, which scopes the clinical applicability)."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:long ; vs:term_status "unstable" . advisory:signatureContentType a owl:DatatypeProperty ; rdfs:label "Signature Content Type (cty)"@en ; rdfs:comment "JWS 'cty' claim — fixed to 'application/x-cascade-advisory-patch' for v0.1. Lets generic JWS verifiers reject signatures intended for other media types."@en ; rdfs:domain advisory:CascadeAdvisoryPatch ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # AdvisoryClass taxonomy (six named individuals) # ============================================================================ # The cadence comments below reference the tiered-cadence guidance in # drafts/REPORT-genomics-and-advisory.md (Mersch et al. 2018 ~25% of VUSes # reclassify within 9 years, lumpy with VCEP batches; SafetyCritical must # fire fast or it isn't safety-critical; carrier-frequency updates are # slow-moving population-genetics deltas). # ============================================================================ advisory:AdvisoryClass a owl:Class ; rdfs:label "Advisory Class"@en ; rdfs:comment "Closed taxonomy of advisory categories. The class drives the recommended check cadence and the default queue-vs-auto-apply UX. v0.1 ships six named individuals; new classes require a vocabulary version bump."@en ; vs:term_status "unstable" . advisory:SafetyCritical a owl:NamedIndividual, advisory:AdvisoryClass ; rdfs:label "Safety-Critical"@en ; rdfs:comment "An advisory that, if not surfaced quickly, may put the user at material clinical risk (e.g. a drug recall, a black-box warning added to a medication the user is taking, an urgent revocation of a prior reclassification). Recommended cadence: every app open. Default UX: surface as an in-app banner immediately on next open; do not silently auto-apply unless the user has explicitly opted in for this issuer."@en ; vs:term_status "unstable" . advisory:VariantReclassification a owl:NamedIndividual, advisory:AdvisoryClass ; rdfs:label "Variant Reclassification"@en ; rdfs:comment "An advisory that updates the ACMG (or comparable) clinical classification of a previously interpreted variant — typically VUS to Likely Pathogenic, Likely Pathogenic to Pathogenic, or downgrade in the opposite direction. Recommended cadence: monthly. Default UX: queue for user review with the prior interpretation shown alongside the proposed new one; encourage re-engagement with a genetic counselor."@en ; vs:term_status "unstable" . advisory:DrugInteraction a owl:NamedIndividual, advisory:AdvisoryClass ; rdfs:label "Drug Interaction / PGx Update"@en ; rdfs:comment "An advisory carrying new or updated pharmacogenetic dosing guidance (e.g. CPIC-style guideline revisions tied to a star allele or diplotype). Recommended cadence: monthly. Default UX: queue for review when the relevant medication is on the user's active list; suppress when not (see advisory:appliesToActiveOnly)."@en ; vs:term_status "unstable" . advisory:LabReferenceRangeUpdate a owl:NamedIndividual, advisory:AdvisoryClass ; rdfs:label "Lab Reference Range Update"@en ; rdfs:comment "An advisory that updates the reference range, units, or interpretation thresholds for a lab analyte (e.g. a new age-specific HbA1c band, a unit harmonization). Recommended cadence: quarterly. Default UX: silent auto-apply when the issuer is on the auto-apply policy for this class, since the change is typically a non-clinical re-display."@en ; vs:term_status "unstable" . advisory:SurveillanceGuidelineUpdate a owl:NamedIndividual, advisory:AdvisoryClass ; rdfs:label "Surveillance Guideline Update"@en ; rdfs:comment "An advisory carrying changes to recommended surveillance protocols (e.g. NCCN updating screening intervals for BRCA1/2 carriers, ACMG secondary-finding actionability list edits). Recommended cadence: quarterly to annual. Default UX: queue for review and link to the published guideline."@en ; vs:term_status "unstable" . advisory:CarrierFrequencyUpdate a owl:NamedIndividual, advisory:AdvisoryClass ; rdfs:label "Carrier Frequency Update"@en ; rdfs:comment "An advisory updating population carrier-frequency or allele-frequency estimates used in residual-risk calculations (e.g. gnomAD release deltas, ancestry-specific frequency revisions). Recommended cadence: annual. Default UX: silent auto-apply when the issuer is on the auto-apply policy, since these are population-level reference-data refreshes."@en ; vs:term_status "unstable" . # ============================================================================ # Cadence enum # ============================================================================ advisory:Cadence a owl:Class ; rdfs:label "Cadence"@en ; rdfs:comment "Closed enumeration of recommended check-cadence values for advisories. Used as the range of advisory:cadence on a CascadeAdvisoryPatch (per-advisory override) and as the implied cadence of each advisory:AdvisoryClass."@en ; vs:term_status "unstable" . advisory:EveryAppOpen a owl:NamedIndividual, advisory:Cadence ; rdfs:label "Every App Open"@en ; rdfs:comment "Pod checks for new advisories of this class on every foregrounding of the app. Reserved for SafetyCritical."@en ; vs:term_status "unstable" . advisory:Daily a owl:NamedIndividual, advisory:Cadence ; rdfs:label "Daily"@en ; rdfs:comment "Pod checks at most once per calendar day."@en ; vs:term_status "unstable" . advisory:Weekly a owl:NamedIndividual, advisory:Cadence ; rdfs:label "Weekly"@en ; rdfs:comment "Pod checks at most once per calendar week."@en ; vs:term_status "unstable" . advisory:Monthly a owl:NamedIndividual, advisory:Cadence ; rdfs:label "Monthly"@en ; rdfs:comment "Pod checks at most once per calendar month. Default cadence for VariantReclassification and DrugInteraction."@en ; vs:term_status "unstable" . advisory:Quarterly a owl:NamedIndividual, advisory:Cadence ; rdfs:label "Quarterly"@en ; rdfs:comment "Pod checks at most once per calendar quarter. Default cadence for LabReferenceRangeUpdate and the lower bound for SurveillanceGuidelineUpdate."@en ; vs:term_status "unstable" . advisory:Annually a owl:NamedIndividual, advisory:Cadence ; rdfs:label "Annually"@en ; rdfs:comment "Pod checks at most once per calendar year. Default cadence for CarrierFrequencyUpdate and the upper bound for SurveillanceGuidelineUpdate."@en ; vs:term_status "unstable" . # ============================================================================ # TrustedIssuer (per D-Q3: per-pod, lives in /trust/issuers.ttl) # ============================================================================ advisory:TrustedIssuer a owl:Class ; rdfs:label "Trusted Issuer"@en ; rdfs:comment "An issuer the pod has chosen to trust. Lives per-pod (typically at /trust/issuers.ttl) — the protocol does NOT define a centralized registry. How the pod populates this graph (cascade-cli starter list, user-added in-app, imported from a community-driven registry URL, or verified via DID) is operational and is captured per-record by advisory:trustSource. Per D-Q3 the protocol is positioned as an extensible framework rather than a gatekeeper; community-driven issuer evaluation and certification is a separate, parallel workstream."@en ; vs:term_status "unstable" . advisory:trustedIssuerId a owl:DatatypeProperty ; rdfs:label "Trusted Issuer Identifier"@en ; rdfs:comment "The IRI (HTTPS URL or DID) that uniquely identifies this issuer. MUST equal the value of advisory:issuer on any patch this issuer signs."@en ; rdfs:domain advisory:TrustedIssuer ; rdfs:range xsd:anyURI ; vs:term_status "unstable" . advisory:trustedIssuerName a owl:DatatypeProperty ; rdfs:label "Trusted Issuer Display Name"@en ; rdfs:comment "Human-readable name shown in pod UI when describing this issuer (e.g. 'ClinGen HBOP-VCEP', 'CPIC')."@en ; rdfs:domain advisory:TrustedIssuer ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:trustedIssuerPublicKey a owl:DatatypeProperty ; rdfs:label "Trusted Issuer Public Key"@en ; rdfs:comment "The Ed25519 public key (per D-Q4) used to verify advisory:signature on patches issued by this issuer. Encoded as a JWK (RFC 7517) JSON string or as multibase-encoded raw key bytes; SHACL shapes constrain the exact form."@en ; rdfs:domain advisory:TrustedIssuer ; rdfs:range xsd:string ; vs:term_status "unstable" . advisory:trustSource a owl:ObjectProperty ; rdfs:label "Trust Source"@en ; rdfs:comment "Provenance flag indicating how this issuer was added to the pod's trust graph. Used by UI to differentiate cascade-recommended starters from user-curated additions and to surface 'imported from community registry' provenance."@en ; rdfs:domain advisory:TrustedIssuer ; rdfs:range advisory:TrustSourceEnum ; vs:term_status "unstable" . advisory:trustAddedAt a owl:DatatypeProperty ; rdfs:label "Trust Added At"@en ; rdfs:comment "ISO 8601 dateTime when this issuer was added to the pod's trust graph."@en ; rdfs:domain advisory:TrustedIssuer ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . advisory:TrustSourceEnum a owl:Class ; rdfs:label "Trust Source Enumeration"@en ; rdfs:comment "Closed enumeration of the ways an advisory:TrustedIssuer record may have entered the pod's trust graph."@en ; vs:term_status "unstable" . advisory:RecommendedStarterList a owl:NamedIndividual, advisory:TrustSourceEnum ; rdfs:label "Recommended Starter List"@en ; rdfs:comment "Issuer was seeded by `cascade-cli init` from the cascade-recommended starter list (e.g. ClinGen, CPIC, NCCN). The list is a non-binding suggestion; the user retains authority to remove any of these issuers."@en ; vs:term_status "unstable" . advisory:UserAdded a owl:NamedIndividual, advisory:TrustSourceEnum ; rdfs:label "User Added"@en ; rdfs:comment "Issuer was explicitly added by the user in-app, typically by pasting an issuer URI or scanning a QR code provided by the issuer."@en ; vs:term_status "unstable" . advisory:ImportedFromRegistry a owl:NamedIndividual, advisory:TrustSourceEnum ; rdfs:label "Imported From Registry"@en ; rdfs:comment "Issuer was imported from a community-driven issuer registry URL the user chose to consult. Forward-compatibility hook for the parallel issuer-certification workstream."@en ; vs:term_status "unstable" . advisory:VerifiedViaDID a owl:NamedIndividual, advisory:TrustSourceEnum ; rdfs:label "Verified Via DID"@en ; rdfs:comment "Issuer identifier was resolved as a Decentralized Identifier and the DID document chain was verified at trust-add time. Forward-compatibility hook for DID-based issuer identity."@en ; vs:term_status "unstable" . # ============================================================================ # AutoApplyPolicy # ============================================================================ advisory:AutoApplyPolicy a owl:Class ; rdfs:label "Auto-Apply Policy"@en ; rdfs:comment "A per-pod policy declaring which (issuer x advisoryClass) pairs the pod will apply silently rather than queueing for explicit user approval. Auto-apply is opt-in by default; the user adds policy records explicitly. Each record points at one or more advisory:AutoApplyScope tuples."@en ; vs:term_status "unstable" . advisory:appliesTo a owl:ObjectProperty ; rdfs:label "Applies To (Auto-Apply Scope)"@en ; rdfs:comment "Links a policy to one or more advisory:AutoApplyScope records, each pairing an issuer with an advisory class. A patch matches the policy when its (advisory:issuer, advisory:advisoryClass) tuple equals at least one of the linked scopes."@en ; rdfs:domain advisory:AutoApplyPolicy ; rdfs:range advisory:AutoApplyScope ; vs:term_status "unstable" . advisory:effectiveFrom a owl:DatatypeProperty ; rdfs:label "Effective From"@en ; rdfs:comment "ISO 8601 dateTime from which the policy is active. Patches issued or applied before this time fall back to queue-for-approval."@en ; rdfs:domain advisory:AutoApplyPolicy ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . advisory:effectiveUntil a owl:DatatypeProperty ; rdfs:label "Effective Until"@en ; rdfs:comment "ISO 8601 dateTime after which the policy is no longer active. Optional — absence means the policy stays active until the user revokes it."@en ; rdfs:domain advisory:AutoApplyPolicy ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . advisory:requiresQuorum a owl:DatatypeProperty ; rdfs:label "Requires Quorum"@en ; rdfs:comment "Forward-compatibility flag for v1.0 counter-signing. When true, the policy will only auto-apply if the patch carries signatures from a quorum of trusted issuers (rather than just one). Declared in v0.1 for forward compatibility but unused — the v0.1 applier MUST treat this as if absent and ignore counter-signatures."@en ; rdfs:domain advisory:AutoApplyPolicy ; rdfs:range xsd:boolean ; vs:term_status "unstable" . advisory:AutoApplyScope a owl:Class ; rdfs:label "Auto-Apply Scope"@en ; rdfs:comment "One (issuer, advisoryClass) tuple authorizing silent auto-application. Reusable across multiple AutoApplyPolicy records."@en ; vs:term_status "unstable" . advisory:scopeIssuer a owl:ObjectProperty ; rdfs:label "Scope Issuer"@en ; rdfs:comment "The trusted issuer this scope authorizes."@en ; rdfs:domain advisory:AutoApplyScope ; rdfs:range advisory:TrustedIssuer ; vs:term_status "unstable" . advisory:scopeAdvisoryClass a owl:ObjectProperty ; rdfs:label "Scope Advisory Class"@en ; rdfs:comment "The advisory class this scope authorizes for the paired issuer. Use one of the six published advisory:AdvisoryClass named individuals."@en ; rdfs:domain advisory:AutoApplyScope ; rdfs:range advisory:AdvisoryClass ; vs:term_status "unstable" . # ============================================================================ # Cross-references (declared in core v3.1, NOT redeclared here) # ============================================================================ # - cascade:AdvisoryApplicationActivity (rdfs:subClassOf prov:Activity) records # the application of a CAP to a pod. Use: # a cascade:AdvisoryApplicationActivity ; # prov:used ; # prov:used ; # prov:startedAtTime ^^xsd:dateTime . # Records produced by the patch carry prov:wasGeneratedBy . # ============================================================================ # Pod Storage Paths (Recommended) # ============================================================================ # /trust/issuers.ttl - advisory:TrustedIssuer graph (per D-Q3) # /trust/auto-apply.ttl - advisory:AutoApplyPolicy + Scope graph # /advisories/queued/.ldpatch - patches awaiting user approval # /advisories/applied/.ldpatch - patches that have been applied # /advisories/inapplicable/.ldpatch - patches with zero Bind matches # /provenance/advisory-applications/.ttl # - cascade:AdvisoryApplicationActivity # records, one per application # ============================================================================ # Notes # ============================================================================ # Data Classification: Clinical knowledge updates (issuer-curated) # Underlying syntax: W3C LDPatch (Recommendation, 2015), constrained per # drafts/advisory-v1/PROFILE.md # Signing: Detached JWS Ed25519 (RFC 7515) per D-Q4; W3C VC 2.0 wrapping # deferred to a later profile version. # Trust model: Per-pod (D-Q3). The protocol ships a recommended starter list # via cascade-cli but does not operate a centralized registry. # DRAFT STATUS: This vocabulary is under active development. Property names, # ranges, and structure may change before v1.0 stable release. ## Ontology: diabetes (DRAFT — v1-draft) ## File: diabetes.ttl @prefix diabetes: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix ucum: . @prefix prov: . @prefix vs: . # ============================================================================ # Changelog # ============================================================================ # v1.0-draft.0.2 (2026-08-20) — Per-term maturity. Every term now carries # vs:term_status (W3C SemWeb Vocabulary Status, originating in FOAF): # "unstable" for draft terms, "archaic" for terms already marked # owl:deprecated. The convention is per-TERM by design, so a vocabulary # can promote individual terms in place without a namespace change; # Cascade previously stated maturity once per vocabulary, in prose no # machine could read. No term definition changed. # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports ; dct:title "Cascade Protocol Diabetes Vocabulary"@en ; dct:description "Unified vocabulary for diabetes and pre-diabetes management, aggregating data from CGMs, glucose meters, insulin delivery systems, and lifestyle tracking devices"@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2025-12-12"^^xsd:date ; dct:modified "2026-08-20"^^xsd:date ; owl:versionInfo "1.0-draft.0.2" ; rdfs:comment "DRAFT - This vocabulary is under development and subject to change. Domain-specific vocabulary for diabetes wellness tracking. Designed to unify data from multiple wearables and apps into a coherent, analyzable format."@en ; rdfs:seeAlso . # ============================================================================ # Diabetes Classification # ============================================================================ diabetes:DiabetesType a owl:Class ; rdfs:label "Diabetes Type"@en ; rdfs:comment "Classification of diabetes or metabolic condition"@en ; vs:term_status "unstable" . diabetes:Type1 a diabetes:DiabetesType ; rdfs:label "Type 1 Diabetes"@en ; rdfs:comment "Autoimmune destruction of insulin-producing beta cells"@en ; owl:sameAs sct:46635009 . diabetes:Type2 a diabetes:DiabetesType ; rdfs:label "Type 2 Diabetes"@en ; rdfs:comment "Insulin resistance with relative insulin deficiency"@en ; owl:sameAs sct:44054006 . diabetes:Prediabetes a diabetes:DiabetesType ; rdfs:label "Pre-diabetes"@en ; rdfs:comment "Impaired glucose tolerance or impaired fasting glucose"@en ; owl:sameAs sct:714628002 . diabetes:GestationalDiabetes a diabetes:DiabetesType ; rdfs:label "Gestational Diabetes"@en ; rdfs:comment "Diabetes diagnosed during pregnancy"@en ; owl:sameAs sct:11687002 . diabetes:LADA a diabetes:DiabetesType ; rdfs:label "Latent Autoimmune Diabetes in Adults"@en ; rdfs:comment "Slowly progressing form of autoimmune diabetes in adults"@en . # ============================================================================ # Core Classes - Glucose Measurements # ============================================================================ diabetes:GlucoseReading a owl:Class ; rdfs:label "Glucose Reading"@en ; rdfs:comment "Single blood glucose measurement from any source (CGM, finger stick, lab)"@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:GlucoseMeasurementType a owl:Class ; rdfs:label "Glucose Measurement Type"@en ; rdfs:comment "Classification of glucose measurement method"@en ; vs:term_status "unstable" . diabetes:CGMReading a diabetes:GlucoseMeasurementType ; rdfs:label "Continuous Glucose Monitor"@en ; rdfs:comment "Interstitial glucose from CGM sensor (e.g., Dexcom, Libre)"@en . diabetes:FingerStick a diabetes:GlucoseMeasurementType ; rdfs:label "Finger Stick (Capillary)"@en ; rdfs:comment "Capillary blood glucose from test strip meter"@en . diabetes:VenousLab a diabetes:GlucoseMeasurementType ; rdfs:label "Venous Lab"@en ; rdfs:comment "Laboratory plasma glucose from venous blood draw"@en . # ============================================================================ # Glucose Reading Properties # ============================================================================ diabetes:glucoseValue a owl:DatatypeProperty ; rdfs:label "Glucose Value"@en ; rdfs:comment "Glucose concentration (see glucoseUnit for measurement unit)"@en ; rdfs:domain diabetes:GlucoseReading ; rdfs:range xsd:double ; owl:equivalentProperty loinc:15074-8 ; vs:term_status "unstable" . diabetes:glucoseUnit a owl:DatatypeProperty ; rdfs:label "Glucose Unit"@en ; rdfs:comment "Unit of measurement: 'mg/dL' (US) or 'mmol/L' (international)"@en ; rdfs:domain diabetes:GlucoseReading ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:measurementType a owl:ObjectProperty ; rdfs:label "Measurement Type"@en ; rdfs:comment "Method used to obtain glucose reading"@en ; rdfs:domain diabetes:GlucoseReading ; rdfs:range diabetes:GlucoseMeasurementType ; vs:term_status "unstable" . diabetes:measurementTimestamp a owl:DatatypeProperty ; rdfs:label "Measurement Timestamp"@en ; rdfs:comment "ISO 8601 timestamp when reading was taken"@en ; rdfs:domain diabetes:GlucoseReading ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . diabetes:trendArrow a owl:DatatypeProperty ; rdfs:label "Trend Arrow"@en ; rdfs:comment "CGM trend indicator: rapidlyFalling, falling, steady, rising, rapidlyRising"@en ; rdfs:domain diabetes:GlucoseReading ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:trendRate a owl:DatatypeProperty ; rdfs:label "Trend Rate"@en ; rdfs:comment "Rate of glucose change (mg/dL/min or mmol/L/min)"@en ; rdfs:domain diabetes:GlucoseReading ; rdfs:range xsd:double ; vs:term_status "unstable" . # ============================================================================ # CGM Session # ============================================================================ diabetes:CGMSession a owl:Class ; rdfs:label "CGM Session"@en ; rdfs:comment "A continuous glucose monitoring session (typically 10-14 days per sensor)"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:sensorType a owl:DatatypeProperty ; rdfs:label "Sensor Type"@en ; rdfs:comment "CGM sensor model (e.g., 'Dexcom G7', 'Freestyle Libre 3')"@en ; rdfs:domain diabetes:CGMSession ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:sensorSerialNumber a owl:DatatypeProperty ; rdfs:label "Sensor Serial Number"@en ; rdfs:domain diabetes:CGMSession ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:sessionStart a owl:DatatypeProperty ; rdfs:label "Session Start"@en ; rdfs:comment "Timestamp when sensor was applied/activated"@en ; rdfs:domain diabetes:CGMSession ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . diabetes:sessionEnd a owl:DatatypeProperty ; rdfs:label "Session End"@en ; rdfs:comment "Timestamp when sensor expired or was removed"@en ; rdfs:domain diabetes:CGMSession ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . diabetes:sensorWearLocation a owl:DatatypeProperty ; rdfs:label "Sensor Wear Location"@en ; rdfs:comment "Body location: abdomen, arm, thigh, etc."@en ; rdfs:domain diabetes:CGMSession ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Insulin Delivery # ============================================================================ diabetes:InsulinDose a owl:Class ; rdfs:label "Insulin Dose"@en ; rdfs:comment "Record of insulin delivery from pump, pen, or injection"@en ; rdfs:subClassOf fhir:MedicationAdministration ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:InsulinType a owl:Class ; rdfs:label "Insulin Type"@en ; rdfs:comment "Classification of insulin by action profile"@en ; vs:term_status "unstable" . diabetes:RapidActing a diabetes:InsulinType ; rdfs:label "Rapid Acting"@en ; rdfs:comment "Onset 15 min, peak 1-2 hr, duration 3-5 hr (e.g., Humalog, NovoLog, Fiasp)"@en . diabetes:ShortActing a diabetes:InsulinType ; rdfs:label "Short Acting"@en ; rdfs:comment "Onset 30 min, peak 2-4 hr, duration 5-8 hr (e.g., Regular)"@en . diabetes:IntermediateActing a diabetes:InsulinType ; rdfs:label "Intermediate Acting"@en ; rdfs:comment "Onset 1-2 hr, peak 4-12 hr, duration 12-18 hr (e.g., NPH)"@en . diabetes:LongActing a diabetes:InsulinType ; rdfs:label "Long Acting"@en ; rdfs:comment "Onset 1-2 hr, no peak, duration 20-24+ hr (e.g., Lantus, Tresiba)"@en . diabetes:DoseType a owl:Class ; rdfs:label "Dose Type"@en ; rdfs:comment "Purpose of insulin dose"@en ; vs:term_status "unstable" . diabetes:Bolus a diabetes:DoseType ; rdfs:label "Bolus"@en ; rdfs:comment "Meal bolus to cover carbohydrate intake"@en . diabetes:CorrectionBolus a diabetes:DoseType ; rdfs:label "Correction Bolus"@en ; rdfs:comment "Bolus to correct elevated glucose"@en . diabetes:Basal a diabetes:DoseType ; rdfs:label "Basal"@en ; rdfs:comment "Background insulin for fasting glucose control"@en . # Insulin dose properties diabetes:insulinUnits a owl:DatatypeProperty ; rdfs:label "Insulin Units"@en ; rdfs:comment "Amount of insulin delivered in units"@en ; rdfs:domain diabetes:InsulinDose ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:insulinType a owl:ObjectProperty ; rdfs:label "Insulin Type"@en ; rdfs:domain diabetes:InsulinDose ; rdfs:range diabetes:InsulinType ; vs:term_status "unstable" . diabetes:doseType a owl:ObjectProperty ; rdfs:label "Dose Type"@en ; rdfs:domain diabetes:InsulinDose ; rdfs:range diabetes:DoseType ; vs:term_status "unstable" . diabetes:insulinBrand a owl:DatatypeProperty ; rdfs:label "Insulin Brand"@en ; rdfs:comment "Specific insulin product (e.g., 'Humalog', 'Tresiba')"@en ; rdfs:domain diabetes:InsulinDose ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:deliveryMethod a owl:DatatypeProperty ; rdfs:label "Delivery Method"@en ; rdfs:comment "How insulin was delivered: pump, pen, syringe"@en ; rdfs:domain diabetes:InsulinDose ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:deliveryDevice a owl:DatatypeProperty ; rdfs:label "Delivery Device"@en ; rdfs:comment "Specific device (e.g., 'Tandem t:slim X2', 'Omnipod 5')"@en ; rdfs:domain diabetes:InsulinDose ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Carbohydrate/Meal Tracking # ============================================================================ diabetes:CarbEntry a owl:Class ; rdfs:label "Carbohydrate Entry"@en ; rdfs:comment "Record of carbohydrate intake for bolus calculation"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:MealEvent a owl:Class ; rdfs:label "Meal Event"@en ; rdfs:comment "Complete meal record with timing, macros, and context"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:MealType a owl:Class ; rdfs:label "Meal Type"@en ; vs:term_status "unstable" . diabetes:Breakfast a diabetes:MealType ; rdfs:label "Breakfast"@en . diabetes:Lunch a diabetes:MealType ; rdfs:label "Lunch"@en . diabetes:Dinner a diabetes:MealType ; rdfs:label "Dinner"@en . diabetes:Snack a diabetes:MealType ; rdfs:label "Snack"@en . # Carb/meal properties diabetes:carbGrams a owl:DatatypeProperty ; rdfs:label "Carbohydrate Grams"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:fiberGrams a owl:DatatypeProperty ; rdfs:label "Fiber Grams"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:sugarGrams a owl:DatatypeProperty ; rdfs:label "Sugar Grams"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:proteinGrams a owl:DatatypeProperty ; rdfs:label "Protein Grams"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:fatGrams a owl:DatatypeProperty ; rdfs:label "Fat Grams"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:calories a owl:DatatypeProperty ; rdfs:label "Calories"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:glycemicIndex a owl:DatatypeProperty ; rdfs:label "Glycemic Index"@en ; rdfs:comment "GI value (0-100) if known"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:integer ; vs:term_status "unstable" . diabetes:foodDescription a owl:DatatypeProperty ; rdfs:label "Food Description"@en ; rdfs:domain diabetes:CarbEntry ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:mealType a owl:ObjectProperty ; rdfs:label "Meal Type"@en ; rdfs:domain diabetes:MealEvent ; rdfs:range diabetes:MealType ; vs:term_status "unstable" . # ============================================================================ # Daily Summary - Time in Range Metrics # ============================================================================ diabetes:DailyGlucoseSummary a owl:Class ; rdfs:label "Daily Glucose Summary"@en ; rdfs:comment "Aggregated glucose metrics for a single day following ADA/EASD consensus standards"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:summaryDate a owl:DatatypeProperty ; rdfs:label "Summary Date"@en ; rdfs:comment "The date this summary covers"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:date ; vs:term_status "unstable" . # Time in Range metrics (ADA/EASD consensus) diabetes:timeInRange a owl:DatatypeProperty ; rdfs:label "Time in Range (%)"@en ; rdfs:comment "Percentage of time glucose was 70-180 mg/dL (target >70%)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:timeBelowRange a owl:DatatypeProperty ; rdfs:label "Time Below Range (%)"@en ; rdfs:comment "Percentage of time glucose was <70 mg/dL (target <4%)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:timeAboveRange a owl:DatatypeProperty ; rdfs:label "Time Above Range (%)"@en ; rdfs:comment "Percentage of time glucose was >180 mg/dL (target <25%)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:timeInTightRange a owl:DatatypeProperty ; rdfs:label "Time in Tight Range (%)"@en ; rdfs:comment "Percentage of time glucose was 70-140 mg/dL (pregnancy target)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:timeSeverelyLow a owl:DatatypeProperty ; rdfs:label "Time Severely Low (%)"@en ; rdfs:comment "Percentage of time glucose was <54 mg/dL (target <1%)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:timeSeverelyHigh a owl:DatatypeProperty ; rdfs:label "Time Severely High (%)"@en ; rdfs:comment "Percentage of time glucose was >250 mg/dL (target <5%)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . # Statistical metrics diabetes:meanGlucose a owl:DatatypeProperty ; rdfs:label "Mean Glucose"@en ; rdfs:comment "Average glucose value for the period"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:standardDeviation a owl:DatatypeProperty ; rdfs:label "Standard Deviation"@en ; rdfs:comment "Glucose variability measure (lower is more stable)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:coefficientOfVariation a owl:DatatypeProperty ; rdfs:label "Coefficient of Variation (%)"@en ; rdfs:comment "CV = SD/Mean * 100 (target <36% for stable glucose)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:gmi a owl:DatatypeProperty ; rdfs:label "Glucose Management Indicator"@en ; rdfs:comment "GMI (estimated HbA1c from CGM data): GMI = 3.31 + (0.02392 * mean glucose mg/dL)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:minGlucose a owl:DatatypeProperty ; rdfs:label "Minimum Glucose"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:maxGlucose a owl:DatatypeProperty ; rdfs:label "Maximum Glucose"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:sensorActivePercentage a owl:DatatypeProperty ; rdfs:label "Sensor Active (%)"@en ; rdfs:comment "Percentage of day with CGM data available (target >70% for valid metrics)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . # Daily totals diabetes:totalDailyInsulin a owl:DatatypeProperty ; rdfs:label "Total Daily Insulin (units)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:totalBasalInsulin a owl:DatatypeProperty ; rdfs:label "Total Basal Insulin (units)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:totalBolusInsulin a owl:DatatypeProperty ; rdfs:label "Total Bolus Insulin (units)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:totalCarbs a owl:DatatypeProperty ; rdfs:label "Total Carbohydrates (g)"@en ; rdfs:domain diabetes:DailyGlucoseSummary ; rdfs:range xsd:double ; vs:term_status "unstable" . # ============================================================================ # Pattern Detection # ============================================================================ diabetes:GlucosePattern a owl:Class ; rdfs:label "Glucose Pattern"@en ; rdfs:comment "Identified recurring pattern in glucose data"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:PatternType a owl:Class ; rdfs:label "Pattern Type"@en ; vs:term_status "unstable" . diabetes:DawnPhenomenon a diabetes:PatternType ; rdfs:label "Dawn Phenomenon"@en ; rdfs:comment "Early morning glucose rise (4-7 AM) due to hormonal changes"@en . diabetes:PostprandialSpike a diabetes:PatternType ; rdfs:label "Post-prandial Spike"@en ; rdfs:comment "Glucose rise 1-3 hours after meals"@en . diabetes:ExerciseResponse a diabetes:PatternType ; rdfs:label "Exercise Response"@en ; rdfs:comment "Glucose changes during and after physical activity"@en . diabetes:NocturnalHypoglycemia a diabetes:PatternType ; rdfs:label "Nocturnal Hypoglycemia"@en ; rdfs:comment "Low glucose during sleep (12-6 AM)"@en . diabetes:InsulinSensitivityPattern a diabetes:PatternType ; rdfs:label "Insulin Sensitivity Pattern"@en ; rdfs:comment "Time-of-day variations in insulin effectiveness"@en . diabetes:patternType a owl:ObjectProperty ; rdfs:label "Pattern Type"@en ; rdfs:domain diabetes:GlucosePattern ; rdfs:range diabetes:PatternType ; vs:term_status "unstable" . diabetes:patternConfidence a owl:DatatypeProperty ; rdfs:label "Pattern Confidence"@en ; rdfs:comment "Statistical confidence in pattern detection (0.0 to 1.0)"@en ; rdfs:domain diabetes:GlucosePattern ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:patternTimeWindow a owl:DatatypeProperty ; rdfs:label "Pattern Time Window"@en ; rdfs:comment "Time of day pattern typically occurs (e.g., '04:00-07:00')"@en ; rdfs:domain diabetes:GlucosePattern ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:patternSeverity a owl:DatatypeProperty ; rdfs:label "Pattern Severity"@en ; rdfs:comment "Severity level: mild, moderate, severe"@en ; rdfs:domain diabetes:GlucosePattern ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:suggestedAction a owl:DatatypeProperty ; rdfs:label "Suggested Action"@en ; rdfs:comment "Recommended adjustment for discussion with healthcare provider"@en ; rdfs:domain diabetes:GlucosePattern ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Hypo/Hyper Events # ============================================================================ diabetes:HypoglycemicEvent a owl:Class ; rdfs:label "Hypoglycemic Event"@en ; rdfs:comment "Episode of low blood glucose (<70 mg/dL)"@en ; rdfs:subClassOf prov:Entity ; owl:sameAs sct:302866003 ; vs:term_status "unstable" . diabetes:HyperglycemicEvent a owl:Class ; rdfs:label "Hyperglycemic Event"@en ; rdfs:comment "Episode of high blood glucose (>250 mg/dL)"@en ; rdfs:subClassOf prov:Entity ; owl:sameAs sct:80394007 ; vs:term_status "unstable" . diabetes:eventStart a owl:DatatypeProperty ; rdfs:label "Event Start"@en ; rdfs:comment "When glucose crossed threshold"@en ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . diabetes:eventEnd a owl:DatatypeProperty ; rdfs:label "Event End"@en ; rdfs:comment "When glucose returned to range"@en ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . diabetes:eventDurationMinutes a owl:DatatypeProperty ; rdfs:label "Event Duration (minutes)"@en ; rdfs:range xsd:integer ; vs:term_status "unstable" . diabetes:nadir a owl:DatatypeProperty ; rdfs:label "Nadir"@en ; rdfs:comment "Lowest glucose value during hypoglycemic event"@en ; rdfs:domain diabetes:HypoglycemicEvent ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:peak a owl:DatatypeProperty ; rdfs:label "Peak"@en ; rdfs:comment "Highest glucose value during hyperglycemic event"@en ; rdfs:domain diabetes:HyperglycemicEvent ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:requiresAssistance a owl:DatatypeProperty ; rdfs:label "Requires Assistance"@en ; rdfs:comment "Whether third-party assistance was needed (Level 3 severe hypo)"@en ; rdfs:domain diabetes:HypoglycemicEvent ; rdfs:range xsd:boolean ; vs:term_status "unstable" . diabetes:treatmentCarbs a owl:DatatypeProperty ; rdfs:label "Treatment Carbs"@en ; rdfs:comment "Carbohydrates consumed to treat hypoglycemia"@en ; rdfs:domain diabetes:HypoglycemicEvent ; rdfs:range xsd:double ; vs:term_status "unstable" . # ============================================================================ # Lab Results # ============================================================================ diabetes:LabResult a owl:Class ; rdfs:label "Lab Result"@en ; rdfs:comment "Clinical laboratory test result"@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:HbA1cResult a owl:Class ; rdfs:label "HbA1c Result"@en ; rdfs:comment "Hemoglobin A1c test result (3-month glucose average)"@en ; rdfs:subClassOf diabetes:LabResult ; vs:term_status "unstable" . diabetes:hba1cValue a owl:DatatypeProperty ; rdfs:label "HbA1c Value (%)"@en ; rdfs:comment "HbA1c in percentage (NGSP/DCCT standard)"@en ; rdfs:domain diabetes:HbA1cResult ; rdfs:range xsd:double ; owl:equivalentProperty loinc:4548-4 ; vs:term_status "unstable" . diabetes:hba1cMmolMol a owl:DatatypeProperty ; rdfs:label "HbA1c (mmol/mol)"@en ; rdfs:comment "HbA1c in IFCC units"@en ; rdfs:domain diabetes:HbA1cResult ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:fastingGlucose a owl:DatatypeProperty ; rdfs:label "Fasting Glucose"@en ; rdfs:comment "Fasting plasma glucose in mg/dL"@en ; rdfs:domain diabetes:LabResult ; rdfs:range xsd:double ; owl:equivalentProperty loinc:1558-6 ; vs:term_status "unstable" . # ============================================================================ # Diabetes Profile # ============================================================================ diabetes:DiabetesProfile a owl:Class ; rdfs:label "Diabetes Profile"@en ; rdfs:comment "User's diabetes-specific health profile and treatment settings"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:diabetesType a owl:ObjectProperty ; rdfs:label "Diabetes Type"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range diabetes:DiabetesType ; vs:term_status "unstable" . diabetes:diagnosisDate a owl:DatatypeProperty ; rdfs:label "Diagnosis Date"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:date ; vs:term_status "unstable" . diabetes:diagnosisAge a owl:DatatypeProperty ; rdfs:label "Diagnosis Age"@en ; rdfs:comment "Age at diagnosis in years"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:integer ; vs:term_status "unstable" . # Target ranges (personalizable per user/provider) diabetes:targetRangeLow a owl:DatatypeProperty ; rdfs:label "Target Range Low"@en ; rdfs:comment "Lower bound of target range in mg/dL (default 70)"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:targetRangeHigh a owl:DatatypeProperty ; rdfs:label "Target Range High"@en ; rdfs:comment "Upper bound of target range in mg/dL (default 180)"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:hypoglycemiaThreshold a owl:DatatypeProperty ; rdfs:label "Hypoglycemia Threshold"@en ; rdfs:comment "Alert threshold for low glucose in mg/dL (default 70)"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:hyperglycemiaThreshold a owl:DatatypeProperty ; rdfs:label "Hyperglycemia Threshold"@en ; rdfs:comment "Alert threshold for high glucose in mg/dL (default 250)"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . # Insulin settings diabetes:carbRatio a owl:DatatypeProperty ; rdfs:label "Carb Ratio"@en ; rdfs:comment "Grams of carbs covered by 1 unit of insulin (I:C ratio)"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:correctionFactor a owl:DatatypeProperty ; rdfs:label "Correction Factor"@en ; rdfs:comment "Expected glucose drop per unit of insulin (ISF) in mg/dL"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:insulinOnBoardDuration a owl:DatatypeProperty ; rdfs:label "Insulin on Board Duration"@en ; rdfs:comment "Active insulin time in hours (typically 3-5)"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:double ; vs:term_status "unstable" . # Device information diabetes:activeCGM a owl:DatatypeProperty ; rdfs:label "Active CGM"@en ; rdfs:comment "Currently used CGM system"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:activeInsulinPump a owl:DatatypeProperty ; rdfs:label "Active Insulin Pump"@en ; rdfs:comment "Currently used insulin pump"@en ; rdfs:domain diabetes:DiabetesProfile ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Activity Correlation # ============================================================================ diabetes:ActivityCorrelation a owl:Class ; rdfs:label "Activity Correlation"@en ; rdfs:comment "Correlation between physical activity and glucose response"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:activityType a owl:DatatypeProperty ; rdfs:label "Activity Type"@en ; rdfs:comment "Type of exercise (walking, running, cycling, strength, etc.)"@en ; rdfs:domain diabetes:ActivityCorrelation ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:activityDurationMinutes a owl:DatatypeProperty ; rdfs:label "Activity Duration"@en ; rdfs:comment "Duration of activity in minutes"@en ; rdfs:domain diabetes:ActivityCorrelation ; rdfs:range xsd:integer ; vs:term_status "unstable" . diabetes:activityIntensity a owl:DatatypeProperty ; rdfs:label "Activity Intensity"@en ; rdfs:comment "Intensity level: low, moderate, high, vigorous"@en ; rdfs:domain diabetes:ActivityCorrelation ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:preActivityGlucose a owl:DatatypeProperty ; rdfs:label "Pre-Activity Glucose"@en ; rdfs:domain diabetes:ActivityCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:postActivityGlucose a owl:DatatypeProperty ; rdfs:label "Post-Activity Glucose"@en ; rdfs:domain diabetes:ActivityCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:glucoseChangeRate a owl:DatatypeProperty ; rdfs:label "Glucose Change Rate"@en ; rdfs:comment "Average glucose change per minute during activity"@en ; rdfs:domain diabetes:ActivityCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . # ============================================================================ # Sleep Correlation # ============================================================================ diabetes:SleepCorrelation a owl:Class ; rdfs:label "Sleep Correlation"@en ; rdfs:comment "Correlation between sleep and glucose patterns"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:sleepDurationHours a owl:DatatypeProperty ; rdfs:label "Sleep Duration"@en ; rdfs:comment "Total sleep time in hours"@en ; rdfs:domain diabetes:SleepCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:sleepQualityScore a owl:DatatypeProperty ; rdfs:label "Sleep Quality Score"@en ; rdfs:comment "Sleep quality from wearable (0-100)"@en ; rdfs:domain diabetes:SleepCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:overnightMeanGlucose a owl:DatatypeProperty ; rdfs:label "Overnight Mean Glucose"@en ; rdfs:domain diabetes:SleepCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:overnightGlucoseSD a owl:DatatypeProperty ; rdfs:label "Overnight Glucose SD"@en ; rdfs:comment "Glucose variability during sleep"@en ; rdfs:domain diabetes:SleepCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . diabetes:wakingGlucose a owl:DatatypeProperty ; rdfs:label "Waking Glucose"@en ; rdfs:comment "First glucose reading upon waking"@en ; rdfs:domain diabetes:SleepCorrelation ; rdfs:range xsd:double ; vs:term_status "unstable" . # ============================================================================ # Device Data Sources # ============================================================================ diabetes:DeviceDataSource a owl:Class ; rdfs:label "Device Data Source"@en ; rdfs:comment "Registered device or app providing diabetes-related data"@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . diabetes:deviceManufacturer a owl:DatatypeProperty ; rdfs:label "Device Manufacturer"@en ; rdfs:domain diabetes:DeviceDataSource ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:deviceModel a owl:DatatypeProperty ; rdfs:label "Device Model"@en ; rdfs:domain diabetes:DeviceDataSource ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:deviceSerialNumber a owl:DatatypeProperty ; rdfs:label "Device Serial Number"@en ; rdfs:domain diabetes:DeviceDataSource ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:firmwareVersion a owl:DatatypeProperty ; rdfs:label "Firmware Version"@en ; rdfs:domain diabetes:DeviceDataSource ; rdfs:range xsd:string ; vs:term_status "unstable" . diabetes:lastSyncTimestamp a owl:DatatypeProperty ; rdfs:label "Last Sync"@en ; rdfs:comment "When data was last synced from this device"@en ; rdfs:domain diabetes:DeviceDataSource ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . # ============================================================================ # Healthcare Standards Alignment Reference # ============================================================================ # SNOMED CT Mappings # - Type 1 Diabetes: sct:46635009 # - Type 2 Diabetes: sct:44054006 # - Pre-diabetes: sct:714628002 # - Gestational Diabetes: sct:11687002 # - Hypoglycemia: sct:302866003 # - Hyperglycemia: sct:80394007 # - Glucose measurement: sct:33747003 # LOINC Mappings # - Glucose in Blood: loinc:15074-8 # - HbA1c: loinc:4548-4 # - Fasting Glucose: loinc:1558-6 # - 2-hour post-glucose: loinc:4679-7 # FHIR Resources # - GlucoseReading -> fhir:Observation # - InsulinDose -> fhir:MedicationAdministration # - DiabetesProfile -> fhir:Condition + fhir:CarePlan # ============================================================================ # Pod Storage Paths (Recommended) # ============================================================================ # /wellness/diabetes/ # profile.ttl - DiabetesProfile # daily-summaries/YYYY-MM-DD.ttl - DailyGlucoseSummary # glucose-readings/YYYY-MM-DD.ttl - GlucoseReading batch # insulin-doses/YYYY-MM-DD.ttl - InsulinDose records # meals/YYYY-MM-DD.ttl - MealEvent/CarbEntry # patterns/detected.ttl - GlucosePattern analysis # events/hypo.ttl - HypoglycemicEvent log # events/hyper.ttl - HyperglycemicEvent log # labs/results.ttl - HbA1c and other labs # ============================================================================ # Notes # ============================================================================ # Data Classification: Consumer wellness data (non-diagnostic) # Target Audience: Type 1, Type 2, Pre-diabetic, Gestational diabetes patients # Primary Devices: CGMs (Dexcom, Libre), insulin pumps, glucose meters # Regulatory Status: Consumer health app, not medical device # Diagnostic Disclaimer: Data is for informational purposes only. Treatment # decisions should be made in consultation with healthcare providers. # DRAFT STATUS: This vocabulary is under active development. Property names, # ranges, and structure may change before v1.0 stable release. ## Ontology: evidence (DRAFT — v1-draft) ## File: evidence.ttl # Cascade Evidence Vocabulary (Layer 2, cross-cutting) — v1-draft # Authored in spec/. Per draft policy (cf. genomics:, advisory:), NOT registered # in spec/VOCAB_VERSIONS until v1.0 graduation. # # CHANGELOG # v1-draft.0.2 (2026-07-01) — Verdict taxonomy v2: the facet model (Workbench # grounding plan §4.1 / platform plan §4.6). The grounding outcome is now # expressed as orthogonal facets on the Assertion — evidence:direction, # evidence:basis, evidence:strength, evidence:settled, evidence:reason # (object properties over new closed enumerations DirectionValue, BasisValue, # StrengthValue, SettledValue, NeedsEvidenceReasonValue) plus # evidence:confidence (xsd:decimal, [0,1]). The FACETS are the canonical # serialized form. The flat evidence:verdict property and the VerdictValue # individuals are DEPRECATED, not removed: draft-period data and fixtures # already carry evidence:verdict, and the SHACL shapes keep a legacy branch # so that data still validates during the one-release transition. Both are # scheduled for removal at v1.0 graduation (see spec/PENDING_DOWNSTREAM_SYNC.md). # Individual reuse: evidence:Supports and evidence:Contradicts are typed as # both StanceValue (per-link) and DirectionValue (aggregate); evidence:None # is both a DirectionValue and a BasisValue; evidence:NeedsLiterature is both # a deprecated VerdictValue and a NeedsEvidenceReasonValue. Local names match # the code mirror (the consuming application's grounding contract) exactly. # v1-draft.0.1 (2026-06-16) — Initial draft: Assertion / EvidenceLink / # Citation / GroundingActivity, flat VerdictValue + StanceValue enumerations, # grounding invariant. @prefix evidence: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix prov: . @prefix vs: . # ============================================================================ # Changelog # ============================================================================ # v1.0-draft.0.3 (2026-08-20) — Per-term maturity. Every term now carries # vs:term_status (W3C SemWeb Vocabulary Status, originating in FOAF): # "unstable" for draft terms, "archaic" for terms already marked # owl:deprecated. The convention is per-TERM by design, so a vocabulary # can promote individual terms in place without a namespace change; # Cascade previously stated maturity once per vocabulary, in prose no # machine could read. No term definition changed. # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; dct:title "Cascade Protocol Evidence Vocabulary"@en ; dct:description "Layer 2 cross-cutting vocabulary for grounding assertions in cited evidence with explicit verdicts."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-06-16"^^xsd:date ; dct:modified "2026-08-20"^^xsd:date ; owl:versionInfo "1.0-draft.0.3" ; rdfs:comment "Generalizes the assertion -> evidence -> verdict pattern needed by Cascade Workbench (checking claims pulled from AI conversations against a patient's records and literature), the Cascade Appeal system (citing clinical evidence against policy criteria, cf. coverage:deniedClinicalContext), and PGx/genomics decision support. Reuses core provenance primitives (cascade:extractionModel, cascade:extractionConfidence, cascade:requiresUserReview, prov:wasDerivedFrom) rather than redefining them. Uses 'Assertion' (not 'Claim') to avoid collision with coverage:ClaimRecord. Draft: not in VOCAB_VERSIONS until v1.0 graduation."@en ; rdfs:seeAlso . # ============================================================================ # Classes # ============================================================================ evidence:Assertion a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Assertion"@en ; rdfs:comment "A discrete, checkable statement extracted from a source (e.g. an AI conversation) or entered by the user, evaluated against evidence in the Pod and/or literature. Carries exactly one verdict and zero-or-more evidence links. The extraction of an Assertion from its source SHOULD be recorded with a cascade:AIExtractionActivity; an assertion surfaced by a general-purpose AI assistant SHOULD carry cascade:dataProvenance cascade:AIAsserted."@en ; owl:versionInfo "Added in evidence v1-draft.0.1" ; vs:term_status "unstable" . evidence:EvidenceLink a owl:Class ; rdfs:label "Evidence Link"@en ; rdfs:comment "A reified link from an Assertion to one piece of evidence (a Pod record or an external Citation), carrying a stance (supports / contradicts / contextual). Modeled as a node so a single Assertion can carry multiple, differently-directed pieces of evidence. Pattern mirrors cascade:ConflictDetail."@en ; owl:versionInfo "Added in evidence v1-draft.0.1" ; vs:term_status "unstable" . evidence:Citation a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Citation"@en ; rdfs:comment "An external bibliographic source (e.g. a research paper) used as evidence, identified by DOI and/or PMID. When retrieved into the Pod it is a first-class resource (recommended path literature/) so an evidence chip can resolve to it exactly as it resolves to a clinical record."@en ; owl:versionInfo "Added in evidence v1-draft.0.1" ; vs:term_status "unstable" . evidence:GroundingActivity a owl:Class ; rdfs:subClassOf prov:Activity ; rdfs:label "Grounding Activity"@en ; rdfs:comment "A PROV-O Activity representing the model pass that graded an Assertion against evidence and produced its verdict. Distinct from cascade:AIExtractionActivity (which extracts structured records from documents); grounding grades free-text assertions. Reuses cascade:extractionModel to record the model."@en ; owl:versionInfo "Added in evidence v1-draft.0.1" ; vs:term_status "unstable" . evidence:VerdictValue a owl:Class ; owl:deprecated true ; rdfs:label "Verdict Value (deprecated)"@en ; rdfs:comment "DEPRECATED in v1-draft.0.2: the flat verdict enumeration is superseded by the facet model (evidence:direction / evidence:basis / evidence:strength / evidence:settled / evidence:reason). Retained one release so draft-period data carrying evidence:verdict still validates; clients derive the flat value from the facets in code and MUST NOT serialize it going forward. Scheduled for removal at v1.0 graduation."@en ; owl:versionInfo "Added in evidence v1-draft.0.1; deprecated in v1-draft.0.2" ; vs:term_status "archaic" . evidence:StanceValue a owl:Class ; rdfs:label "Stance Value"@en ; rdfs:comment "Closed enumeration of the direction of an EvidenceLink. Instances are the named individuals below."@en ; owl:versionInfo "Added in evidence v1-draft.0.1" ; vs:term_status "unstable" . # ── Facet enumeration classes (verdict taxonomy v2, grounding plan §4.1) ───── evidence:DirectionValue a owl:Class ; rdfs:label "Direction Value"@en ; rdfs:comment "Closed enumeration of what the evidence, in aggregate, says about the Assertion: Supports | Contradicts | Mixed | None. The per-link analogue is StanceValue; evidence:Supports and evidence:Contradicts are shared individuals of both."@en ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:BasisValue a owl:Class ; rdfs:label "Basis Value"@en ; rdfs:comment "Closed enumeration of where the evidence is: Record | Literature | RecordAndLiterature | None. New bases (guideline, clinician annotation) slot in here without breaking any flat enum."@en ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:StrengthValue a owl:Class ; rdfs:label "Strength Value"@en ; rdfs:comment "Closed enumeration of the quantity/quality of the evidence: Strong | Moderate | Weak. On basis Record, strength is a pure deterministic function of the match (guardrail G-2), never model-emitted."@en ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:SettledValue a owl:Class ; rdfs:label "Settled Value"@en ; rdfs:comment "Closed enumeration of whether the Assertion is resolved: Settled | NeedsEvidence."@en ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:NeedsEvidenceReasonValue a owl:Class ; rdfs:label "Needs-Evidence Reason Value"@en ; rdfs:comment "Closed enumeration of why an Assertion still needs evidence, when it does: NoRecord | NeedsLiterature | NotCheckableByNature."@en ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . # ============================================================================ # Named Individuals (enumerations) # ============================================================================ # Deprecated flat verdicts (v1-draft.0.2): retained one release for draft-period # data; derive from the facets in code, do not serialize. Removal at v1.0. evidence:Supported a owl:NamedIndividual, evidence:VerdictValue ; owl:deprecated true ; rdfs:label "Supported (deprecated)"@en ; rdfs:comment "DEPRECATED: use evidence:settled evidence:Settled + evidence:direction evidence:Supports. The patient's records and/or literature substantiate the assertion."@en ; vs:term_status "archaic" . evidence:Contradicted a owl:NamedIndividual, evidence:VerdictValue ; owl:deprecated true ; rdfs:label "Contradicted (deprecated)"@en ; rdfs:comment "DEPRECATED: use evidence:settled evidence:Settled + evidence:direction evidence:Contradicts. The patient's records and/or literature directly conflict with the assertion."@en ; vs:term_status "archaic" . evidence:Unverifiable a owl:NamedIndividual, evidence:VerdictValue ; owl:deprecated true ; rdfs:label "Unverifiable (deprecated)"@en ; rdfs:comment "DEPRECATED: use evidence:settled evidence:NeedsEvidence + evidence:reason evidence:NoRecord. No evidence available in the current Pod to support or refute. NOT a judgment that the assertion is false."@en ; vs:term_status "archaic" . evidence:Supports a owl:NamedIndividual, evidence:StanceValue, evidence:DirectionValue ; rdfs:label "Supports"@en ; rdfs:comment "As a StanceValue: this one link supports. As a DirectionValue (v1-draft.0.2): the evidence in aggregate supports."@en ; vs:term_status "unstable" . evidence:Contradicts a owl:NamedIndividual, evidence:StanceValue, evidence:DirectionValue ; rdfs:label "Contradicts"@en ; rdfs:comment "As a StanceValue: this one link contradicts. As a DirectionValue (v1-draft.0.2): the evidence in aggregate contradicts."@en ; vs:term_status "unstable" . evidence:Contextual a owl:NamedIndividual, evidence:StanceValue ; rdfs:label "Contextual"@en ; rdfs:comment "Relevant background that neither supports nor refutes."@en ; vs:term_status "unstable" . # ── Facet individuals (v1-draft.0.2) ───────────────────────────────────────── evidence:Mixed a owl:NamedIndividual, evidence:DirectionValue ; rdfs:label "Mixed"@en ; rdfs:comment "The evidence points in both directions; see the individual links."@en ; vs:term_status "unstable" . evidence:None a owl:NamedIndividual, evidence:DirectionValue, evidence:BasisValue ; rdfs:label "None"@en ; rdfs:comment "As a DirectionValue: the evidence says nothing. As a BasisValue: there is no evidence base. Shared individual; the property it appears on disambiguates."@en ; vs:term_status "unstable" . evidence:Record a owl:NamedIndividual, evidence:BasisValue ; rdfs:label "Record"@en ; rdfs:comment "The evidence is the patient's own Pod records (deterministic path, G-2)."@en ; vs:term_status "unstable" . evidence:Literature a owl:NamedIndividual, evidence:BasisValue ; rdfs:label "Literature"@en ; rdfs:comment "The evidence is published research; it has not been checked against the patient's records."@en ; vs:term_status "unstable" . evidence:RecordAndLiterature a owl:NamedIndividual, evidence:BasisValue ; rdfs:label "Record and Literature"@en ; rdfs:comment "Both the patient's records and published research contribute evidence."@en ; vs:term_status "unstable" . evidence:Strong a owl:NamedIndividual, evidence:StrengthValue ; rdfs:label "Strong"@en ; rdfs:comment "On the record path: exact code + value/negation match."@en ; vs:term_status "unstable" . evidence:Moderate a owl:NamedIndividual, evidence:StrengthValue ; rdfs:label "Moderate"@en ; rdfs:comment "On the record path: code match with value absent or approximate."@en ; vs:term_status "unstable" . evidence:Weak a owl:NamedIndividual, evidence:StrengthValue ; rdfs:label "Weak"@en ; rdfs:comment "On the record path: lexical / recall-fallback match only."@en ; vs:term_status "unstable" . evidence:Settled a owl:NamedIndividual, evidence:SettledValue ; rdfs:label "Settled"@en ; rdfs:comment "The claim is resolved by the cited evidence."@en ; vs:term_status "unstable" . evidence:NeedsEvidence a owl:NamedIndividual, evidence:SettledValue ; rdfs:label "Needs Evidence"@en ; rdfs:comment "The claim is not resolved; evidence:reason says why. A NeedsEvidence assertion carries direction None (a grounded direction here is facet-inconsistent and fails validation)."@en ; vs:term_status "unstable" . evidence:NoRecord a owl:NamedIndividual, evidence:NeedsEvidenceReasonValue ; rdfs:label "No Record"@en ; rdfs:comment "Nothing in the patient's records speaks to this. That does not make it wrong."@en ; vs:term_status "unstable" . evidence:NotCheckableByNature a owl:NamedIndividual, evidence:NeedsEvidenceReasonValue ; rdfs:label "Not Checkable By Nature"@en ; rdfs:comment "Subjective, personal, or otherwise unverifiable by its nature (instruction, opinion, tautology, meta)."@en ; vs:term_status "unstable" . # evidence:NeedsLiterature doubles as a NeedsEvidenceReasonValue (below) and the # deprecated VerdictValue of the same local name; the code mirror uses the same # string for both, so one shared individual is exact. evidence:NeedsLiterature a owl:NamedIndividual, evidence:VerdictValue, evidence:NeedsEvidenceReasonValue ; rdfs:label "Needs Literature"@en ; rdfs:comment "As a NeedsEvidenceReasonValue (v1-draft.0.2): the patient's records cannot settle this; it needs published evidence. As a VerdictValue it is DEPRECATED (use evidence:settled evidence:NeedsEvidence + evidence:reason evidence:NeedsLiterature)."@en ; vs:term_status "unstable" . # ============================================================================ # Properties — Assertion # ============================================================================ evidence:assertionText a owl:DatatypeProperty ; rdfs:label "assertion text"@en ; rdfs:comment "The normalized, atomic statement being evaluated."@en ; rdfs:domain evidence:Assertion ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:verdict a owl:ObjectProperty ; owl:deprecated true ; rdfs:label "verdict (deprecated)"@en ; rdfs:comment "DEPRECATED in v1-draft.0.2: superseded by the facet properties (direction / basis / strength / settled / reason / confidence), which are the canonical serialized form. Retained one release so draft-period data validates; derive the flat value from the facets in code, do not serialize it. Removal at v1.0 graduation."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:VerdictValue ; vs:term_status "archaic" . # ── Facet properties (verdict taxonomy v2, v1-draft.0.2) ───────────────────── # The canonical grounding outcome. Grounding invariant (SHACL Core, see # evidence.shapes.ttl): a grounded result — settled Settled, direction one of # Supports/Contradicts/Mixed, basis not None — of EITHER basis requires at # least one evidence:hasEvidenceLink. evidence:direction a owl:ObjectProperty ; rdfs:label "direction"@en ; rdfs:comment "What the evidence, in aggregate, says about the assertion."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:DirectionValue ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:basis a owl:ObjectProperty ; rdfs:label "basis"@en ; rdfs:comment "Where the evidence is. Tells the UI which regime produced the result: Record is the deterministic path (G-2), Literature is model+search."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:BasisValue ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:strength a owl:ObjectProperty ; rdfs:label "strength"@en ; rdfs:comment "Quantity/quality of the evidence. On basis Record this is a pure deterministic function of the match (G-2), never model-emitted."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:StrengthValue ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:settled a owl:ObjectProperty ; rdfs:label "settled"@en ; rdfs:comment "Whether the assertion is resolved by the cited evidence."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:SettledValue ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:reason a owl:ObjectProperty ; rdfs:label "reason"@en ; rdfs:comment "Why the assertion still needs evidence, when settled is NeedsEvidence."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:NeedsEvidenceReasonValue ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:confidence a owl:DatatypeProperty ; rdfs:label "confidence"@en ; rdfs:comment "Confidence in the grounding result, [0.0, 1.0]. On basis Record this is a fixed function of match quality, never a model probability (G-2); on the literature path it may reflect model certainty. Distinct from evidence:groundingConfidence, which is the GroundingActivity's raw model confidence."@en ; rdfs:domain evidence:Assertion ; rdfs:range xsd:decimal ; owl:versionInfo "Added in evidence v1-draft.0.2" ; vs:term_status "unstable" . evidence:rationale a owl:DatatypeProperty ; rdfs:label "rationale"@en ; rdfs:comment "One-line plain-language explanation of the verdict."@en ; rdfs:domain evidence:Assertion ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:disconfirming a owl:DatatypeProperty ; rdfs:label "disconfirming consideration"@en ; rdfs:comment "A 'what would argue against this' consideration. Multiple permitted. Required by product design for any material assertion (anti-confirmation-bias)."@en ; rdfs:domain evidence:Assertion ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:hasEvidenceLink a owl:ObjectProperty ; rdfs:label "has evidence link"@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:EvidenceLink ; vs:term_status "unstable" . evidence:groundedBy a owl:ObjectProperty ; rdfs:subPropertyOf prov:wasGeneratedBy ; rdfs:label "grounded by"@en ; rdfs:comment "Links an Assertion to the GroundingActivity that produced its verdict."@en ; rdfs:domain evidence:Assertion ; rdfs:range evidence:GroundingActivity ; vs:term_status "unstable" . evidence:assertedAt a owl:DatatypeProperty ; rdfs:label "asserted at"@en ; rdfs:domain evidence:Assertion ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . # Reuse (NOT redefined here): cascade:extractionModel, cascade:extractionConfidence, # and cascade:requiresUserReview carry the extraction provenance of an Assertion. # ============================================================================ # Properties — EvidenceLink # ============================================================================ evidence:evidenceStance a owl:ObjectProperty ; rdfs:label "evidence stance"@en ; rdfs:domain evidence:EvidenceLink ; rdfs:range evidence:StanceValue ; vs:term_status "unstable" . evidence:citesRecord a owl:ObjectProperty ; rdfs:subPropertyOf prov:wasDerivedFrom ; rdfs:label "cites record"@en ; rdfs:comment "Points to a Pod record (clinical:, health:, pots:, genomics: ...) used as evidence. Range intentionally open (rdfs:Resource) for cross-namespace linking, mirroring coverage:deniedClinicalContext."@en ; rdfs:domain evidence:EvidenceLink ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . evidence:citesSource a owl:ObjectProperty ; rdfs:label "cites source"@en ; rdfs:comment "Points to an external Citation (research paper) used as evidence."@en ; rdfs:domain evidence:EvidenceLink ; rdfs:range evidence:Citation ; vs:term_status "unstable" . evidence:evidenceWeight a owl:DatatypeProperty ; rdfs:label "evidence weight"@en ; rdfs:comment "Optional 0.0-1.0 strength of this evidence link toward the verdict."@en ; rdfs:domain evidence:EvidenceLink ; rdfs:range xsd:decimal ; vs:term_status "unstable" . # ============================================================================ # Properties — Citation # ============================================================================ evidence:doi a owl:DatatypeProperty ; rdfs:label "DOI"@en ; rdfs:domain evidence:Citation ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:pmid a owl:DatatypeProperty ; rdfs:label "PMID"@en ; rdfs:domain evidence:Citation ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:citationTitle a owl:DatatypeProperty ; rdfs:label "title"@en ; rdfs:domain evidence:Citation ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:citationYear a owl:DatatypeProperty ; rdfs:label "year"@en ; rdfs:domain evidence:Citation ; rdfs:range xsd:gYear ; vs:term_status "unstable" . evidence:citationSourceName a owl:DatatypeProperty ; rdfs:label "source name"@en ; rdfs:comment "Retrieval source, e.g. 'PubMed', 'Europe PMC'."@en ; rdfs:domain evidence:Citation ; rdfs:range xsd:string ; vs:term_status "unstable" . evidence:retrievedAt a owl:DatatypeProperty ; rdfs:label "retrieved at"@en ; rdfs:domain evidence:Citation ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . # ============================================================================ # Properties — GroundingActivity # ============================================================================ evidence:groundingConfidence a owl:DatatypeProperty ; rdfs:label "grounding confidence"@en ; rdfs:comment "Model confidence in the produced verdict, [0.0, 1.0]."@en ; rdfs:domain evidence:GroundingActivity ; rdfs:range xsd:decimal ; vs:term_status "unstable" . # Reuses cascade:extractionModel to record which model performed the grounding. ## Ontology: genomics (DRAFT — v1-draft) ## File: genomics.ttl @prefix genomics: . @prefix cascade: . @prefix clinical: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix prov: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix hpo: . @prefix mondo: . @prefix so: . @prefix hgnc: . @prefix omim: . @prefix orpha: . @prefix clinvar: . @prefix vs: . # ============================================================================ # Changelog # ============================================================================ # v1.0-draft.0.5 (2026-08-20) — Per-term maturity. Every term now carries # vs:term_status (W3C SemWeb Vocabulary Status, originating in FOAF): # "unstable" for draft terms, "archaic" for terms already marked # owl:deprecated. The convention is per-TERM by design, so a vocabulary # can promote individual terms in place without a namespace change; # Cascade previously stated maturity once per vocabulary, in prose no # machine could read. No term definition changed. # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; owl:imports , ; dct:title "Cascade Protocol Genomics Ontology"@en ; dct:description "Layer 2 vocabulary for sequence variants, variant interpretations, haplotypes/diplotypes, copy number variants, sequencing-run metadata, raw-file pointers, pedigrees, and genetic test reports/orders. Bridges Layer 1 standards (HGVS, ClinVar, VRS, HGNC, Sequence Ontology, HPO, MONDO/OMIM/ORDO, ACMG/AMP via LOINC answer codes, ClinGen review-status taxonomy) to Layer 3 patient-facing genetic counseling summaries in checkup:."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-05-05"^^xsd:date ; dct:modified "2026-08-20"^^xsd:date ; owl:versionInfo "1.0-draft.0.5" ; rdfs:comment """DRAFT — pre-stable v1 ontology. Property names, ranges, and structure may change before v1.0 stable release. Three-layer ontology for genomics: Layer 1 (Established): HGVS nomenclature, VRS, ClinVar, HGNC, Sequence Ontology, HPO, MONDO/OMIM/ORDO, ACMG/AMP (encoded via LOINC), ClinGen review-status taxonomy, GA4GH htsget streaming. Layer 2 (genomics:): Variant, VariantInterpretation, SubmitterAssertion, Haplotype, Diplotype, CopyNumberVariant, SequencingRun, RawFile, GeneticTest, GeneticTestOrder, Pedigree, PedigreeMember — RDF resources binding Layer 1 codes to a patient and family with provenance and quality tier. Layer 3 (checkup:): GeneticCounselingSummary, VariantNarrative — patient-facing aggregations and regeneratable narrative (authored separately in the checkup vocabulary)."""@en ; rdfs:seeAlso . # Changelog: # v1.0-draft.0.2 (2026-05-05): Phase 1 evolution candidates landed after the FHIR # Genomics IG importer session surfaced gaps. All four are additive (no breaking # changes): # - genomics:reportedRecord — generic GeneticTest → record predicate (no # rdfs:range, deliberately broad). Resolves the HLA tie-break (TASK-1.9): # existing genomics:variantsObserved has rdfs:range genomics:Variant and # cannot represent non-Variant report links (Diplotypes, Haplotypes, PGx # implications). # - genomics:refAllele, genomics:altAllele, genomics:genomicStartEnd — # VCF-style coordinate properties for FHIR Genomics IG variants that lack # HGVS but carry LOINC 69547-8 / 69551-0 / 81254-5 directly. Required for # the Phase 3 VCF importer. # - genomics:somaticStatus ObjectProperty + genomics:SomaticStatus enum # (Germline / Somatic / UnknownSomaticStatus). Maps LOINC 48002-0. # - genomics:variantAlleleFrequency (xsd:decimal, SHACL-bounded 0.0–1.0). # Distinct from genomics:mosaicismFraction: VAF is a sequencing-evidence # fraction; mosaicism is the clinical conclusion that the variant is # present in only a subset of cells. Phase 1 importer was shoehorning # VAF into mosaicismFraction; importers should now emit VAF here. # v1.0-draft (2026-05-05): Initial production version of genomics/v1-draft authored # in spec/ from the v0.1 sketch (cascadeprotocol.org/drafts/genomics-v1/genomics.ttl). # Folds in 9 gaps from GAP-ANALYSIS.md (Haplotype, Diplotype, CopyNumberVariant, # SubmitterAssertion, GeneticTestOrder, mosaicismFraction, interpretationStatus + # CausalityStatus, reviewStatus + 7-value ReviewStatus), directory-session additions # (SequencingRun, RawFile, sequencing/coverage/laboratory metadata, dataProvenance # enum), and the data-quality tier model per D-QUALITY-TIER (dataQualityTier enum + # requiresConfirmation property). Per D-PATH, this draft is NOT yet registered in # spec/VOCAB_VERSIONS — it lands there at v1.0 graduation. # ============================================================================ # Core Classes — Variant Identity # ============================================================================ genomics:Variant a owl:Class ; rdfs:label "Sequence Variant"@en ; rdfs:comment "A specific sequence variant observed in a patient sample. Carries the four parallel descriptions of variant identity (HGVS for human readability, ClinVar/VRS for stable computable identity, Sequence Ontology for consequence type, gene for biological context). Distinct from VariantInterpretation, which assigns clinical significance."@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; rdfs:subClassOf [ a owl:Restriction ; owl:onProperty genomics:geneSymbol ; owl:cardinality 1 ] ; vs:term_status "unstable" . genomics:VariantInterpretation a owl:Class ; rdfs:label "Variant Interpretation"@en ; rdfs:comment "A clinical-significance assertion linking a Variant to a single condition (per D-Q5 — multi-condition cases produce multiple Interpretation instances), with ACMG/AMP classification, supporting criteria, and an interpreting laboratory or expert panel. Reclassifications produce new Interpretation instances linked to the prior via prov:wasRevisionOf — interpretations are versioned, not edited. SHACL enforces genomics:condition and genomics:variantInterpreted both at cardinality 1..1 (see genomics.shapes.ttl, TASK-0.2)."@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:GeneticTest a owl:Class ; rdfs:label "Genetic Test Report"@en ; rdfs:comment "Result envelope from a clinical genetic test (panel, exome, single-gene). Aggregates Variant observations, gene panel scope, methodology, and ordering provider. Maps to FHIR DiagnosticReport. For test orders that have not yet resulted, see genomics:GeneticTestOrder."@en ; rdfs:subClassOf fhir:DiagnosticReport ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:GeneticTestOrder a owl:Class ; rdfs:label "Genetic Test Order"@en ; rdfs:comment "An order/request for a genetic test, distinct from the resulting GeneticTest. Maps to FHIR ServiceRequest. Counseling workflows track 'ordered but not yet resulted' state via this class. Once the order is fulfilled, link the resulting GeneticTest via genomics:resultedIn."@en ; rdfs:subClassOf fhir:ServiceRequest ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:Gene a owl:Class ; rdfs:label "Gene"@en ; rdfs:comment "A gene reference, identified by HGNC approved symbol and ID. Used as the target of variant observations and gene-disease associations."@en ; vs:term_status "unstable" . # ============================================================================ # Haplotype, Diplotype, Copy Number Variants # ============================================================================ # Haplotype/Diplotype superClass decision (resolved 2026-05-05): # FHIR Genomics IG ships `haplotype` and `genotype` profiles, both extending # `Observation` (verified against Bundle-bundle-pgxexample.json — both # resourceType "Observation"). We adopt the same anchor for consistency with # Variant. See RATIONALE.md. genomics:Haplotype a owl:Class ; rdfs:label "Haplotype"@en ; rdfs:comment "A multi-variant unit travelling together on one chromosome — the unit of clinical interpretation in pharmacogenomics (PharmVar star alleles like CYP2C19*2, CYP2D6*4) and HLA typing (HLA-DQB1*02:01). Cannot be expressed as a single Variant. Anchored on fhir:Observation per FHIR Genomics IG haplotype profile."@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:Diplotype a owl:Class ; rdfs:label "Diplotype"@en ; rdfs:comment "A pair of haplotypes (e.g., '*1/*2'), required for any pharmacogenomics or HLA typing use case. Anchored on fhir:Observation per FHIR Genomics IG genotype profile."@en ; rdfs:subClassOf fhir:Observation ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:CopyNumberVariant a owl:Class ; rdfs:label "Copy Number Variant"@en ; rdfs:comment "A structural variant defined by deletion or duplication of a genomic interval, expressed as integer copy number plus interval coordinates rather than HGVS. Required for structural-variant cases (e.g., 13q14 deletion in retinoblastoma). subClassOf genomics:Variant — inherits stable-ID and zygosity properties where applicable."@en ; rdfs:subClassOf genomics:Variant ; vs:term_status "unstable" . # ============================================================================ # Sequencing Run & Raw File Metadata (per D-DIRECTORY) # ============================================================================ # Cascade pods do NOT ingest raw genomic file bytes (BAM/CRAM/FASTQ at # 50–100GB+). RawFile carries pointer-and-hash metadata only — bytes live # wherever the user keeps them (lab cloud, local disk, NAS, htsget-served). # See Critical conventions #9 in the Genomics & Advisory implementation plan. genomics:SequencingRun a owl:Class ; rdfs:label "Sequencing Run"@en ; rdfs:comment "Metadata for a single sequencing run that produced one or more variants. Captures coverage depth, technology, variant-caller version, laboratory certification, and key dates (sample collection, sequencing, file generation). Used to populate the data-quality tier on derived Variants and to trace provenance back to lab process."@en ; rdfs:subClassOf prov:Activity ; vs:term_status "unstable" . genomics:RawFile a owl:Class ; rdfs:label "Raw Genomic File"@en ; rdfs:comment "Pointer-and-hash record for a raw genomic file (BAM, CRAM, FASTQ, gVCF, VCF, BCF). Cascade does NOT ingest the bytes — only the manifest. The bytes live wherever the user keeps them (lab cloud, local disk, NAS, htsget-served). genomics:fileLocation may be opaque (s3://, https://, file://, or a vendor-specific URI) and is not validated by Cascade."@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . # ============================================================================ # Variant Identity Properties — HGVS Nomenclature # ============================================================================ # Per HGVS: a description is incomplete without its reference sequence. Always # store the full reference:type.position+change string, never a bare position. genomics:hgvsCDot a owl:DatatypeProperty ; rdfs:label "HGVS Coding-DNA Description"@en ; rdfs:comment "Variant described against a coding-sequence (transcript) reference, e.g. 'NM_000059.4:c.5946delT'. Includes transcript accession with version. This is the form most often cited in clinical reports."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:48004-6 ; vs:term_status "unstable" . # DNA change (c.HGVS) genomics:hgvsPDot a owl:DatatypeProperty ; rdfs:label "HGVS Protein Description"@en ; rdfs:comment "Variant described at the protein level, e.g. 'NP_000050.3:p.Ser1982Argfs*22'. Use parentheses (p.(Ser1982Argfs*22)) when the protein consequence is predicted from the DNA change rather than directly observed."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:48005-3 ; vs:term_status "unstable" . # Amino acid change (p.HGVS) genomics:hgvsGDot a owl:DatatypeProperty ; rdfs:label "HGVS Genomic Description"@en ; rdfs:comment "Variant described against a genomic reference, e.g. 'NC_000013.11:g.32340301del'. Required for cross-transcript joins and for variants outside coding regions."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:81290-9 ; vs:term_status "unstable" . # Genomic ref allele # Added in v1-draft.0.2: VCF-style coordinate properties. FHIR Genomics IG # variants frequently carry the LOINC 69547-8 / 69551-0 / 81254-5 components # without HGVS strings; the Phase 3 VCF importer relies on these directly. genomics:refAllele a owl:DatatypeProperty ; rdfs:label "Reference Allele"@en ; rdfs:comment "The reference base sequence at the variant position, as defined by the reference genome assembly. ATCG[N], typically 1–N bases. Per VCF REF / FHIR Genomics IG LOINC 69547-8."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:69547-8 ; vs:term_status "unstable" . # Genomic ref allele [ID] genomics:altAllele a owl:DatatypeProperty ; rdfs:label "Alternate Allele"@en ; rdfs:comment "The variant allele sequence at the variant position. ATCG[N] for substitutions/insertions; '-' or empty for deletions; structured forms like ''/'' for symbolic alleles. Per VCF ALT / FHIR Genomics IG LOINC 69551-0."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:69551-0 ; vs:term_status "unstable" . # Genomic alt allele [ID] genomics:genomicStartEnd a owl:DatatypeProperty ; rdfs:label "Genomic Start-End Coordinates"@en ; rdfs:comment "The 1-based inclusive start-end coordinates of the variant on the reference assembly, formatted as 'chrN:start-end' (e.g., 'chr17:43124027-43124028'). Pair with genomics:referenceGenome for full anchoring. Per FHIR Genomics IG LOINC 81254-5."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:81254-5 ; vs:term_status "unstable" . # Genomic allele start-end genomics:hgvsProteinObserved a owl:DatatypeProperty ; rdfs:label "Protein Consequence Directly Observed"@en ; rdfs:comment "True when the p.HGVS description was directly observed at the protein level (e.g. mass spec, functional assay). False or absent means the protein consequence was predicted from the DNA change. Conventionally encoded in HGVS by parentheses around the p. description."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:boolean ; vs:term_status "unstable" . genomics:transcriptRef a owl:DatatypeProperty ; rdfs:label "Reference Transcript"@en ; rdfs:comment "RefSeq transcript accession with version, e.g. 'NM_000059.4'. Stored separately from the HGVS string so that downstream tools can reason about transcript version drift, the most common source of variant ambiguity in clinical handoffs."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:genomeAssembly a owl:DatatypeProperty ; rdfs:label "Reference Genome Assembly"@en ; rdfs:comment "Reference genome build, e.g. 'GRCh38' or 'GRCh37'. Required when interpreting g.HGVS coordinates."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Variant Identity Properties — Stable Identifiers # ============================================================================ # HGVS strings are descriptive but normalization-fragile. Stable IDs are what # join across databases. Carry as many as the source provides. genomics:clinvarVariationId a owl:DatatypeProperty ; rdfs:label "ClinVar Variation ID"@en ; rdfs:comment "ClinVar VCV accession identifying the variant (not the variant-condition pair). Example: 'VCV000051062' for BRCA2 c.5946delT."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:81252-9 ; vs:term_status "unstable" . # Discrete genetic variant genomics:clinvarRcvId a owl:DatatypeProperty ; rdfs:label "ClinVar RCV ID"@en ; rdfs:comment "ClinVar RCV accession identifying a variant-condition assertion. Multiple RCVs typically attach to one VCV. Lives on VariantInterpretation, not Variant."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:dbsnpRsId a owl:DatatypeProperty ; rdfs:label "dbSNP rsID"@en ; rdfs:comment "NCBI dbSNP reference SNP identifier, e.g. 'rs80359550'. Primary join key for consumer-array (23andMe / AncestryDNA / etc.) imports."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:caId a owl:DatatypeProperty ; rdfs:label "ClinGen Allele Registry CAid"@en ; rdfs:comment "ClinGen Allele Registry canonical allele identifier. Stable across HGVS reformulations and assembly changes — the recommended primary join key when available."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:vrsId a owl:DatatypeProperty ; rdfs:label "GA4GH VRS Identifier"@en ; rdfs:comment "Deterministic computable hash identifier per GA4GH Variation Representation Spec. Computed from the normalized sequence-location-state object; identical variants always hash to the same VRS ID regardless of how they were originally described."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:vrsObject a owl:DatatypeProperty ; rdfs:label "VRS Allele Object (JSON-LD)"@en ; rdfs:comment "Full VRS Allele JSON-LD payload as serialized string. Carries sequence reference, location interval, and state. Permits round-trip to other VRS-aware systems without re-parsing the HGVS."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Gene & Consequence Properties # ============================================================================ genomics:geneSymbol a owl:DatatypeProperty ; rdfs:label "Gene Symbol (HGNC Approved)"@en ; rdfs:comment "HGNC approved gene symbol, e.g. 'BRCA2'. Always use the approved symbol, never an alias."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; genomics:loincCode loinc:48018-6 ; vs:term_status "unstable" . # Gene studied genomics:hgncId a owl:DatatypeProperty ; rdfs:label "HGNC ID"@en ; rdfs:comment "HGNC numeric identifier with prefix, e.g. 'HGNC:1101' for BRCA2. Stable across symbol changes."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:consequenceTerm a owl:ObjectProperty ; rdfs:label "Sequence Ontology Consequence"@en ; rdfs:comment "Sequence Ontology term URI describing the molecular consequence of the variant, e.g. so:0001589 (frameshift_variant), so:0001583 (missense_variant), so:0001587 (stop_gained)."@en ; rdfs:domain genomics:Variant ; rdfs:range owl:Class ; vs:term_status "unstable" . genomics:consequenceLabel a owl:DatatypeProperty ; rdfs:label "Consequence Label"@en ; rdfs:comment "Human-readable consequence label paired with the SO URI for legibility, e.g. 'frameshift_variant'."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:zygosity a owl:ObjectProperty ; rdfs:label "Zygosity"@en ; rdfs:comment "Zygosity of the observed variant. Named-individual values: genomics:Heterozygous, Homozygous, Hemizygous, CompoundHeterozygous, MosaicLow, MosaicHigh. For mosaic variants, also populate genomics:mosaicismFraction with the actual VAF."@en ; rdfs:domain genomics:Variant ; rdfs:range genomics:ZygosityValue ; genomics:loincCode loinc:53034-5 ; vs:term_status "unstable" . # Allelic state genomics:mosaicismFraction a owl:DatatypeProperty ; rdfs:label "Mosaicism Fraction (VAF)"@en ; rdfs:comment "Variant allele fraction (0.0–1.0) for mosaic variants. Distinct from zygosity, which is categorical. The continuous VAF is the load-bearing number for clinical decisions in somatic mosaicism scenarios. NOTE (v1-draft.0.2): for the broader 'fraction of reads supporting alt allele' use genomics:variantAlleleFrequency; mosaicismFraction is reserved for the clinical conclusion that the variant is present in only a subset of cells."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:decimal ; vs:term_status "unstable" . # Added in v1-draft.0.2: VAF as a distinct property from mosaicismFraction. # VAF is the broader concept (any sequencing read fraction supporting the alt # allele); mosaicism is the specific clinical interpretation. Phase 1 importer # was shoehorning LOINC 81258-6 into mosaicismFraction; the correct fix is # authoring VAF as its own property. genomics:variantAlleleFrequency a owl:DatatypeProperty ; rdfs:label "Variant Allele Frequency"@en ; rdfs:comment "Fraction of sequencing reads at the variant position that support the alternate allele. Range 0.0–1.0. Per FHIR Genomics IG LOINC 81258-6. Distinct from genomics:mosaicismFraction — VAF is a sequencing-evidence fraction; mosaicism is the clinical conclusion that the variant is present in only a subset of cells (which often, but not always, manifests as low VAF)."@en ; rdfs:domain genomics:Variant ; rdfs:range xsd:decimal ; genomics:loincCode loinc:81258-6 ; vs:term_status "unstable" . # Sample variant allelic frequency [NFr] # Added in v1-draft.0.2: variant origin (germline vs. somatic). LOINC 48002-0 # is one of the most clinically important variant attributes. Critical for # cancer interpretation and inheritance reasoning. genomics:somaticStatus a owl:ObjectProperty ; rdfs:label "Somatic Status"@en ; rdfs:comment "Whether the variant is germline (inherited / present in all cells) or somatic (acquired / tissue-specific). Critical for cancer interpretation and inheritance reasoning. Per FHIR Genomics IG LOINC 48002-0."@en ; rdfs:domain genomics:Variant ; rdfs:range genomics:SomaticStatus ; genomics:loincCode loinc:48002-0 ; vs:term_status "unstable" . # Genomic source class genomics:phase a owl:ObjectProperty ; rdfs:label "Allelic Phase"@en ; rdfs:comment "For compound heterozygous calls in the same gene: whether the two variants are in cis (same chromosome) or trans (opposite chromosomes). Values: genomics:Cis, Trans, PhaseUnknown. Critical for autosomal recessive inheritance interpretation."@en ; rdfs:domain genomics:Variant ; rdfs:range genomics:PhaseValue ; vs:term_status "unstable" . genomics:phasedWith a owl:ObjectProperty ; rdfs:label "Phased With Variant"@en ; rdfs:comment "Links to the partner Variant when phase has been determined for a compound-heterozygous pair. Symmetric (each variant phasedWith the other)."@en ; rdfs:domain genomics:Variant ; rdfs:range genomics:Variant ; vs:term_status "unstable" . # ============================================================================ # Haplotype & Diplotype Properties # ============================================================================ genomics:hasComponent a owl:ObjectProperty ; rdfs:label "Haplotype Component Variant"@en ; rdfs:comment "Links a Haplotype to one of its component Variants. A haplotype is composed of multiple variants that travel together on one chromosome."@en ; rdfs:domain genomics:Haplotype ; rdfs:range genomics:Variant ; vs:term_status "unstable" . genomics:starAlleleSymbol a owl:DatatypeProperty ; rdfs:label "Star Allele Symbol"@en ; rdfs:comment "PharmVar/HLA-style nomenclature, e.g. 'CYP2C19*2', 'HLA-DQB1*02:01'. The clinically actionable identifier in pharmacogenomics and HLA typing."@en ; rdfs:domain genomics:Haplotype ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:diplotypeNotation a owl:DatatypeProperty ; rdfs:label "Diplotype Notation"@en ; rdfs:comment "Compact paired-haplotype notation, e.g. '*1/*2'. Computed from hapA + hapB; carried as a string for human readability and direct match against PGx guideline tables."@en ; rdfs:domain genomics:Diplotype ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:hapA a owl:ObjectProperty ; rdfs:label "Haplotype A"@en ; rdfs:comment "First haplotype of a diplotype pair. Order (A vs B) is arbitrary but must be stable within a record."@en ; rdfs:domain genomics:Diplotype ; rdfs:range genomics:Haplotype ; vs:term_status "unstable" . genomics:hapB a owl:ObjectProperty ; rdfs:label "Haplotype B"@en ; rdfs:comment "Second haplotype of a diplotype pair."@en ; rdfs:domain genomics:Diplotype ; rdfs:range genomics:Haplotype ; vs:term_status "unstable" . # ============================================================================ # Copy Number Variant Properties # ============================================================================ genomics:copyNumber a owl:DatatypeProperty ; rdfs:label "Copy Number"@en ; rdfs:comment "Integer copy number observed for the genomic interval, e.g. 1 (heterozygous deletion), 0 (homozygous deletion), 3 (single duplication), 4+ (amplification). Distinct from zygosity."@en ; rdfs:domain genomics:CopyNumberVariant ; rdfs:range xsd:integer ; vs:term_status "unstable" . genomics:cnvIntervalStart a owl:DatatypeProperty ; rdfs:label "CNV Interval Start"@en ; rdfs:comment "Start coordinate (1-based, inclusive) of the CNV interval on the reference indicated by cnvIntervalRef."@en ; rdfs:domain genomics:CopyNumberVariant ; rdfs:range xsd:integer ; vs:term_status "unstable" . genomics:cnvIntervalEnd a owl:DatatypeProperty ; rdfs:label "CNV Interval End"@en ; rdfs:comment "End coordinate (1-based, inclusive) of the CNV interval."@en ; rdfs:domain genomics:CopyNumberVariant ; rdfs:range xsd:integer ; vs:term_status "unstable" . genomics:cnvIntervalRef a owl:DatatypeProperty ; rdfs:label "CNV Interval Reference"@en ; rdfs:comment "Genomic reference for the interval coordinates, e.g. 'NC_000013.11' (chromosome 13 in GRCh38) or a chromosome name. Required for downstream analyses to locate the interval."@en ; rdfs:domain genomics:CopyNumberVariant ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Variant Interpretation Properties (ACMG/AMP) # ============================================================================ genomics:variantInterpreted a owl:ObjectProperty ; rdfs:label "Variant Interpreted"@en ; rdfs:comment "The Variant being assessed by this VariantInterpretation. SHACL enforces cardinality 1..1 (per D-Q5)."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:Variant ; vs:term_status "unstable" . genomics:acmgClassification a owl:ObjectProperty ; rdfs:label "ACMG/AMP Classification"@en ; rdfs:comment "Five-tier ACMG/AMP classification. Named-individual values map 1:1 to LOINC answer codes: Pathogenic (LA6668-3), LikelyPathogenic (LA26332-9), VUS (LA26333-7), LikelyBenign (LA26334-5), Benign (LA6675-8)."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:AcmgClass ; genomics:loincCode loinc:53037-8 ; vs:term_status "unstable" . # Genetic variation clinical significance genomics:acmgCriteria a owl:DatatypeProperty ; rdfs:label "ACMG Evidence Criteria"@en ; rdfs:comment "Space-separated ACMG/AMP evidence criteria codes applied in this classification, e.g. 'PVS1 PM2 PP5'. Order is not significant."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:condition a owl:ObjectProperty ; rdfs:label "Associated Condition"@en ; rdfs:comment "Disease or condition associated with this interpretation. Range is a MONDO/OMIM/ORDO term URI. Per D-Q5, each VariantInterpretation has cardinality 1..1 on this property — multi-condition cases produce multiple interpretation instances."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range owl:Class ; vs:term_status "unstable" . genomics:mondoId a owl:DatatypeProperty ; rdfs:label "MONDO Disease ID"@en ; rdfs:comment "MONDO disease ontology ID, e.g. 'MONDO:0007254' for Hereditary breast carcinoma."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:omimId a owl:DatatypeProperty ; rdfs:label "OMIM Phenotype/Gene ID"@en ; rdfs:comment "OMIM identifier (six-digit) for a Mendelian phenotype or gene, e.g. '604370' for Breast-ovarian cancer, familial, susceptibility to, 2."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:orphaCode a owl:DatatypeProperty ; rdfs:label "Orphanet Code"@en ; rdfs:comment "Orphanet rare disease code, e.g. 'ORPHA:145' for Hereditary breast and ovarian cancer syndrome."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:inheritanceMode a owl:ObjectProperty ; rdfs:label "Mode of Inheritance"@en ; rdfs:comment "GenCC-aligned mode of inheritance for the gene-disease relationship. Named-individual values: AutosomalDominant, AutosomalRecessive, XLinkedDominant, XLinkedRecessive, Mitochondrial, YLinked, MultifactorialPolygenic, UnknownInheritance."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:InheritanceMode ; vs:term_status "unstable" . genomics:allelicRequirement a owl:ObjectProperty ; rdfs:label "Allelic Requirement"@en ; rdfs:comment "GenCC allelic requirement: monoallelic, biallelic, etc. Captures whether one or two copies of a pathogenic variant are required for the disease phenotype."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:AllelicRequirement ; vs:term_status "unstable" . genomics:interpretedBy a owl:DatatypeProperty ; rdfs:label "Interpreting Laboratory or Expert Panel"@en ; rdfs:comment "Name of the lab, ClinGen Variant Curation Expert Panel (VCEP), or curator who issued this interpretation. Free-text for now; will be replaced with a fhir:Organization reference in v1.0."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:interpretedDate a owl:DatatypeProperty ; rdfs:label "Interpretation Date"@en ; rdfs:comment "Date this interpretation was issued. Reclassifications create new Interpretation instances; the date here is the date of THIS classification, not the original."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:date ; vs:term_status "unstable" . # Reclassifications — use prov:wasRevisionOf to chain interpretation versions. # Example: a VUS that becomes Likely Pathogenic links the new Interpretation # to the old via prov:wasRevisionOf. Past patient-facing narratives generated # from the old interpretation remain valid as historical record. # ============================================================================ # Submitter Assertions (ClinVar SCV pattern) # ============================================================================ # A VariantInterpretation's authoritative classification can aggregate dozens # of individual submitter assertions (ClinVar SCVs). Track per-submitter # detail here so disagreement and review-status arithmetic is preserved. genomics:SubmitterAssertion a owl:Class ; rdfs:label "Submitter Assertion"@en ; rdfs:comment "A single submitter's assertion about a variant-condition pair, e.g. one ClinVar SCV record. Aggregated assertions roll up into a VariantInterpretation via genomics:aggregatedFrom. Individual submissions may disagree with the aggregate (most agree, but ENIGMA, Mayo, Ambry, etc. can each carry distinct opinions)."@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:aggregatedFrom a owl:ObjectProperty ; rdfs:label "Aggregated From"@en ; rdfs:comment "Links a VariantInterpretation to the SubmitterAssertions it aggregates. The aggregate classification is computed from these per-submitter records; preserving the constituents lets downstream tools recompute or audit the rollup."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:SubmitterAssertion ; vs:term_status "unstable" . genomics:assertedClassification a owl:ObjectProperty ; rdfs:label "Asserted ACMG Classification"@en ; rdfs:comment "The ACMG class assigned by this individual submitter. May differ from the aggregate classification on the parent VariantInterpretation."@en ; rdfs:domain genomics:SubmitterAssertion ; rdfs:range genomics:AcmgClass ; vs:term_status "unstable" . genomics:submitter a owl:DatatypeProperty ; rdfs:label "Submitter Name"@en ; rdfs:comment "Name of the submitting laboratory, consortium, or expert panel, e.g. 'ENIGMA', 'Ambry Genetics', 'Mayo Clinic'."@en ; rdfs:domain genomics:SubmitterAssertion ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:submitterOrgId a owl:DatatypeProperty ; rdfs:label "Submitter Organization ID"@en ; rdfs:comment "ClinVar OrgID (or equivalent submitter registry identifier) for the submitting organization."@en ; rdfs:domain genomics:SubmitterAssertion ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:submitterCategory a owl:ObjectProperty ; rdfs:label "Submitter Category"@en ; rdfs:comment "Class of submitter: laboratory, consortium, expert-panel, research-group, single-clinician, etc. Drives review-status weighting."@en ; rdfs:domain genomics:SubmitterAssertion ; rdfs:range genomics:SubmitterCategory ; vs:term_status "unstable" . genomics:scvAccession a owl:DatatypeProperty ; rdfs:label "SCV Accession"@en ; rdfs:comment "ClinVar SCV (Submission) accession identifying this individual submission, e.g. 'SCV000123456'."@en ; rdfs:domain genomics:SubmitterAssertion ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:contributesToAggregate a owl:DatatypeProperty ; rdfs:label "Contributes to Aggregate"@en ; rdfs:comment "True when this submission is included in the aggregate classification. Some submissions are excluded (withdrawn, superseded, flagged) but retained for audit."@en ; rdfs:domain genomics:SubmitterAssertion ; rdfs:range xsd:boolean ; vs:term_status "unstable" . genomics:assertionEvidenceLevel a owl:ObjectProperty ; rdfs:label "Assertion Evidence Level"@en ; rdfs:comment "Strength-of-evidence label per the submitter's review process. Range is a vocabulary-extensible class — no fixed enum to allow lab-specific gradings."@en ; rdfs:domain genomics:SubmitterAssertion ; vs:term_status "unstable" . # ============================================================================ # Review Status (ClinGen / ClinVar 7-tier taxonomy) # ============================================================================ # Mirrors the de-facto industry standard 0–4 star rating users see in ClinVar # UI. Author 7 named individuals (the rating is shared across two pairs). genomics:reviewStatus a owl:ObjectProperty ; rdfs:label "Review Status"@en ; rdfs:comment "ClinGen / ClinVar review-status classification of this VariantInterpretation, mapping to the 0–4 star confidence rating. Drives advisory engine trust decisions."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:ReviewStatus ; vs:term_status "unstable" . genomics:starRating a owl:DatatypeProperty ; rdfs:label "Star Rating"@en ; rdfs:comment "Integer 0–4 star rating associated with a ReviewStatus, matching ClinVar UI."@en ; rdfs:domain genomics:ReviewStatus ; rdfs:range xsd:integer ; vs:term_status "unstable" . # ============================================================================ # Causality Status (Phenopacket interpretationStatus) # ============================================================================ genomics:interpretationStatus a owl:ObjectProperty ; rdfs:label "Interpretation Status (Causality)"@en ; rdfs:comment "Whether this variant explains THIS patient's phenotype. Distinct from ACMG classification: ACMG classifies the variant; interpretationStatus classifies the variant's role in the patient's clinical picture. Aligned with GA4GH Phenopackets v2 InterpretationStatus enum."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range genomics:CausalityStatus ; vs:term_status "unstable" . # ============================================================================ # Genetic Test Properties # ============================================================================ genomics:testType a owl:ObjectProperty ; rdfs:label "Genetic Test Type"@en ; rdfs:comment "Test methodology category. Named-individual values: SingleGeneTest, GenePanelTest, ExomeSequencing, GenomeSequencing, ChromosomalMicroarray, KaryotypeAnalysis, MLPA, RepeatExpansionTest, MethylationStudy, RNASequencing."@en ; rdfs:domain genomics:GeneticTest ; rdfs:range genomics:TestType ; vs:term_status "unstable" . genomics:genePanel a owl:DatatypeProperty ; rdfs:label "Gene Panel Members"@en ; rdfs:comment "Space-separated HGNC symbols for the genes included in this test's analytical scope. Used to distinguish 'no variant found in BRCA1/2' from 'no variant found across a 47-gene cancer panel'."@en ; rdfs:domain genomics:GeneticTest ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:variantsObserved a owl:ObjectProperty ; rdfs:label "Variants Observed"@en ; rdfs:comment "Variants reported by this test. Empty list with non-empty genePanel = clinically meaningful negative result."@en ; rdfs:domain genomics:GeneticTest ; rdfs:range genomics:Variant ; vs:term_status "unstable" . # Added in v1-draft.0.2: generic GeneticTest → record predicate. Resolves the # HLA tie-break (cascade-coordination/tie-breaks/2026-05-05-task-1.9-hla-variantsObserved.md): # genomics:variantsObserved has rdfs:range genomics:Variant and cannot # represent non-Variant report links (Diplotypes, Haplotypes, PGx # implications). Importers should still emit the most specific predicate # available (variantsObserved for Variants). genomics:reportedRecord a owl:ObjectProperty ; rdfs:label "Reported Record"@en ; rdfs:comment "A genomics record (Variant, Haplotype, Diplotype, VariantInterpretation, or other genomics:* class) referenced by this GeneticTest. Use this predicate when the test result is a Diplotype, Haplotype, or PGx implication that has no Variant-typed range. For point-Variant references, prefer the type-specific genomics:variantsObserved (rdfs:range genomics:Variant)."@en ; rdfs:domain genomics:GeneticTest ; vs:term_status "unstable" . # No rdfs:range — deliberately broad. SHACL won't constrain the object class # so any genomics: record can be referenced. genomics:orderingProvider a owl:DatatypeProperty ; rdfs:label "Ordering Provider"@en ; rdfs:comment "Name of clinician who ordered the test. Will become a fhir:Practitioner reference in v1.0."@en ; rdfs:domain genomics:GeneticTest ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:performingLab a owl:DatatypeProperty ; rdfs:label "Performing Laboratory"@en ; rdfs:comment "Name of the laboratory that performed the analysis. Free-text for now; fhir:Organization reference in v1.0."@en ; rdfs:domain genomics:GeneticTest ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Genetic Test Order Properties # ============================================================================ genomics:orderedAt a owl:DatatypeProperty ; rdfs:label "Ordered At"@en ; rdfs:comment "Timestamp when the test was ordered."@en ; rdfs:domain genomics:GeneticTestOrder ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . genomics:orderStatus a owl:ObjectProperty ; rdfs:label "Order Status"@en ; rdfs:comment "Status of the test order: Pending, InProgress, Resulted, Cancelled."@en ; rdfs:domain genomics:GeneticTestOrder ; rdfs:range genomics:OrderStatus ; vs:term_status "unstable" . genomics:resultedIn a owl:ObjectProperty ; rdfs:label "Resulted In"@en ; rdfs:comment "Links a fulfilled GeneticTestOrder to the resulting GeneticTest."@en ; rdfs:domain genomics:GeneticTestOrder ; rdfs:range genomics:GeneticTest ; vs:term_status "unstable" . # ============================================================================ # Sequencing Run Properties (per D-DIRECTORY) # ============================================================================ genomics:coverageDepth a owl:DatatypeProperty ; rdfs:label "Coverage Depth"@en ; rdfs:comment "Mean coverage depth across the targeted region, e.g. 30.0 for typical clinical WGS. ClinicalGrade tier requires >=30x for WGS or validated panel-specific equivalents."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range xsd:decimal ; vs:term_status "unstable" . genomics:sequencingTechnology a owl:ObjectProperty ; rdfs:label "Sequencing Technology"@en ; rdfs:comment "Sequencing platform/technology category. Named-individual values: ShortReadIllumina, LongReadONT, LongReadPacBio, Mixed, GenotypingArray."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range genomics:SequencingTechnology ; vs:term_status "unstable" . genomics:variantCallerVersion a owl:DatatypeProperty ; rdfs:label "Variant Caller Version"@en ; rdfs:comment "Variant caller name and version, e.g. 'GATK 4.5.0', 'DeepVariant 1.6'. Important for reproducibility and for distinguishing caller-driven differences from biological ones."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:laboratoryCertification a owl:ObjectProperty ; rdfs:label "Laboratory Certification"@en ; rdfs:comment "Regulatory certification of the performing laboratory. Named-individual values: CLIA, CAP, ISO15189, Uncertified."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range genomics:LaboratoryCertification ; vs:term_status "unstable" . genomics:sampleCollectionDate a owl:DatatypeProperty ; rdfs:label "Sample Collection Date"@en ; rdfs:comment "Date the biological sample was collected from the patient."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range xsd:date ; vs:term_status "unstable" . genomics:sequencingDate a owl:DatatypeProperty ; rdfs:label "Sequencing Date"@en ; rdfs:comment "Date sequencing was performed on the sample."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range xsd:date ; vs:term_status "unstable" . genomics:fileGenerationDate a owl:DatatypeProperty ; rdfs:label "File Generation Date"@en ; rdfs:comment "Date the raw genomic file (BAM/CRAM/VCF/etc.) was generated. May post-date sequencingDate by re-alignments or re-calls."@en ; rdfs:domain genomics:SequencingRun ; rdfs:range xsd:date ; vs:term_status "unstable" . # ============================================================================ # Raw File Properties (pointer-and-hash representation) # ============================================================================ genomics:fileFormat a owl:ObjectProperty ; rdfs:label "File Format"@en ; rdfs:comment "Raw genomic file format. Named-individual values: BAM, CRAM, FASTQ, gVCF, VCF, BCF, Other. Hot tier (in-pod, encrypted): VCF, gVCF, BCF. Cold tier (out-of-pod, referenced): BAM, CRAM, FASTQ."@en ; rdfs:domain genomics:RawFile ; rdfs:range genomics:FileFormat ; vs:term_status "unstable" . genomics:fileSizeBytes a owl:DatatypeProperty ; rdfs:label "File Size (Bytes)"@en ; rdfs:comment "Size of the raw file in bytes. xsd:long because BAM/CRAM/FASTQ files routinely exceed 2^31 bytes (50–100 GB+)."@en ; rdfs:domain genomics:RawFile ; rdfs:range xsd:long ; vs:term_status "unstable" . genomics:fileHashSHA256 a owl:DatatypeProperty ; rdfs:label "File SHA-256 Hash"@en ; rdfs:comment "Lowercase hex-encoded SHA-256 digest of the file contents. Integrity check enabling the pod to verify the bytes-on-disk match the manifest."@en ; rdfs:domain genomics:RawFile ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:fileLocation a owl:DatatypeProperty ; rdfs:label "File Location URI"@en ; rdfs:comment "Opaque pointer to where the bytes live (s3://, https://, file://, vendor-specific). Cascade does NOT validate or fetch this URI; the bytes live wherever the user keeps them. See Critical conventions #9."@en ; rdfs:domain genomics:RawFile ; rdfs:range xsd:anyURI ; vs:term_status "unstable" . genomics:referenceGenome a owl:DatatypeProperty ; rdfs:label "Reference Genome"@en ; rdfs:comment "Reference assembly the file is aligned against, e.g. 'GRCh38', 'GRCh37', 'T2T-CHM13'. Required for any downstream coordinate-based analysis."@en ; rdfs:domain genomics:RawFile ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:htsgetEndpoint a owl:DatatypeProperty ; rdfs:label "htsget Endpoint"@en ; rdfs:comment "Optional GA4GH htsget endpoint URI for streaming BAM/CRAM regions on demand without ingesting the full file. Letters labs and cloud-genomics services typically expose htsget; consumer providers usually do not."@en ; rdfs:domain genomics:RawFile ; rdfs:range xsd:anyURI ; vs:term_status "unstable" . # ============================================================================ # Data Provenance (genomics-specific enum) # ============================================================================ # Note: distinct from cascade:dataProvenance (which classifies consumer vs # clinical data sources). genomics:dataProvenance captures the genomics- # specific origin lane that drives importer routing and quality-tier inference. genomics:dataProvenance a owl:ObjectProperty ; rdfs:label "Genomic Data Provenance"@en ; rdfs:comment "Origin lane of the genomic data, driving importer routing and quality-tier inference. Named-individual values: ConsumerArray, ClinicalSequencing, ResearchSequencing, Imported, AIExtracted. Distinct from the broader cascade:dataProvenance (consumer-vs-clinical classification) — this is genomics-specific because consumer-array imports (Phase 2C) need a distinct lane that auto-tags ConsumerGrade quality."@en ; rdfs:range genomics:DataProvenance ; vs:term_status "unstable" . # ============================================================================ # Data Quality Tier (per D-QUALITY-TIER) # ============================================================================ # Quality is a first-class vocabulary citizen. Every Variant carries a # dataQualityTier; SHACL (TASK-0.2) enforces that any Pathogenic / # LikelyPathogenic VariantInterpretation must either reference a # ClinicalGrade Variant OR carry requiresConfirmation true. This is a # safety constraint, not a quality nudge. genomics:dataQualityTier a owl:ObjectProperty ; rdfs:label "Data Quality Tier"@en ; rdfs:comment "Quality tier of the genomic data underlying this Variant, driving downstream advisory and narrative subsystems. Named-individual values: ClinicalGrade, ResearchGrade, ConsumerGrade, UnknownQuality. Aligns with cascadeprotocol.org sequencing-provider directory's three quality criteria (>=30x coverage + raw files + CLIA-certified)."@en ; rdfs:domain genomics:Variant ; rdfs:range genomics:DataQualityTier ; vs:term_status "unstable" . genomics:requiresConfirmation a owl:DatatypeProperty ; rdfs:label "Requires Confirmation"@en ; rdfs:comment "True when this VariantInterpretation requires confirmatory clinical-grade re-testing before it can drive care decisions. SHACL (TASK-0.2): any Pathogenic / LikelyPathogenic interpretation MUST either reference a ClinicalGrade Variant OR carry requiresConfirmation true. The advisory engine MUST NOT auto-apply care recommendations to confirmation-required interpretations regardless of issuer trust."@en ; rdfs:domain genomics:VariantInterpretation ; rdfs:range xsd:boolean ; vs:term_status "unstable" . # ============================================================================ # Phenotype Properties (HPO) # ============================================================================ genomics:hpoTerm a owl:DatatypeProperty ; rdfs:label "HPO Phenotype Term"@en ; rdfs:comment "Human Phenotype Ontology term identifier, e.g. 'HP:0003002' (Breast carcinoma). Multiple HPO terms per patient or relative are expected."@en ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:phenotypeOnsetAge a owl:DatatypeProperty ; rdfs:label "Phenotype Onset Age"@en ; rdfs:comment "Age in years when the phenotype was first observed. Use HPO onset terms (HP:0011462 Young adult onset, etc.) when only categorical onset is known."@en ; rdfs:range xsd:integer ; vs:term_status "unstable" . # ============================================================================ # Pedigree Classes # ============================================================================ genomics:Pedigree a owl:Class ; rdfs:label "Pedigree"@en ; rdfs:comment "A family pedigree: structured representation of relatives with conditions, ages of onset, and (where available) genotypes. Designed to round-trip with HL7 v3 Family History/Pedigree IG and FHIR FamilyMemberHistory while normalizing to Cascade's three-layer model."@en ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . genomics:PedigreeMember a owl:Class ; rdfs:label "Pedigree Member"@en ; rdfs:comment "A single relative within a pedigree. Distinct from checkup:FamilyHistoryEntry, which is the patient-facing Layer 3 summary; PedigreeMember is the Layer 2 structured record carrying full HPO phenotype set, MONDO disease codes, age of onset/death, sex, and (optionally) carrier or affected status for specific variants."@en ; rdfs:subClassOf fhir:FamilyMemberHistory ; rdfs:subClassOf prov:Entity ; vs:term_status "unstable" . # ============================================================================ # Pedigree Properties # ============================================================================ genomics:proband a owl:ObjectProperty ; rdfs:label "Proband"@en ; rdfs:comment "The index patient — the family member who brought the family to clinical attention. Required on every Pedigree."@en ; rdfs:domain genomics:Pedigree ; rdfs:range genomics:PedigreeMember ; vs:term_status "unstable" . genomics:hasMember a owl:ObjectProperty ; rdfs:label "Pedigree Member"@en ; rdfs:comment "A relative included in the pedigree. Includes the proband."@en ; rdfs:domain genomics:Pedigree ; rdfs:range genomics:PedigreeMember ; vs:term_status "unstable" . genomics:relativeRole a owl:ObjectProperty ; rdfs:label "Relationship to Proband"@en ; rdfs:comment "HL7 v3 RoleCode-aligned relationship, e.g. MTH (mother), FTH (father), SIS (sister), MGRMTH (maternal grandmother). Cardinal relations are first-class; complex relations expressed via nested PedigreeMember chains."@en ; rdfs:domain genomics:PedigreeMember ; rdfs:range genomics:RelationshipRole ; vs:term_status "unstable" . genomics:relativeSex a owl:DatatypeProperty ; rdfs:label "Biological Sex"@en ; rdfs:comment "Biological sex of the relative as relevant to inheritance: 'male', 'female', 'unknown'. Distinct from gender identity, which is not modeled here."@en ; rdfs:domain genomics:PedigreeMember ; rdfs:range xsd:string ; vs:term_status "unstable" . genomics:isDeceased a owl:DatatypeProperty ; rdfs:label "Deceased"@en ; rdfs:domain genomics:PedigreeMember ; rdfs:range xsd:boolean ; vs:term_status "unstable" . genomics:ageAtDeath a owl:DatatypeProperty ; rdfs:label "Age at Death"@en ; rdfs:domain genomics:PedigreeMember ; rdfs:range xsd:integer ; vs:term_status "unstable" . genomics:carrierStatus a owl:ObjectProperty ; rdfs:label "Carrier Status for Variant"@en ; rdfs:comment "Tested status of this relative for a specific variant (typically the family's known pathogenic variant). Named-individual values: PositiveAffected, PositiveUnaffected, Negative, NotTested, Obligate."@en ; rdfs:domain genomics:PedigreeMember ; rdfs:range genomics:CarrierStatus ; vs:term_status "unstable" . genomics:testedForVariant a owl:ObjectProperty ; rdfs:label "Tested for Variant"@en ; rdfs:comment "The specific Variant for which this relative's carrierStatus is reported. Allows a relative to have distinct statuses for different variants in the same family."@en ; rdfs:domain genomics:PedigreeMember ; rdfs:range genomics:Variant ; vs:term_status "unstable" . # ============================================================================ # Named Individuals — ACMG Classification # ============================================================================ genomics:Pathogenic a genomics:AcmgClass ; rdfs:label "Pathogenic"@en ; genomics:loincAnswerCode "LA6668-3" . genomics:LikelyPathogenic a genomics:AcmgClass ; rdfs:label "Likely Pathogenic"@en ; genomics:loincAnswerCode "LA26332-9" . genomics:VUS a genomics:AcmgClass ; rdfs:label "Uncertain Significance"@en ; genomics:loincAnswerCode "LA26333-7" . genomics:LikelyBenign a genomics:AcmgClass ; rdfs:label "Likely Benign"@en ; genomics:loincAnswerCode "LA26334-5" . genomics:Benign a genomics:AcmgClass ; rdfs:label "Benign"@en ; genomics:loincAnswerCode "LA6675-8" . # ============================================================================ # Named Individuals — Zygosity, Phase, Inheritance # ============================================================================ genomics:Heterozygous a genomics:ZygosityValue ; rdfs:label "Heterozygous"@en . genomics:Homozygous a genomics:ZygosityValue ; rdfs:label "Homozygous"@en . genomics:Hemizygous a genomics:ZygosityValue ; rdfs:label "Hemizygous"@en . genomics:CompoundHeterozygous a genomics:ZygosityValue ; rdfs:label "Compound Heterozygous"@en . genomics:MosaicLow a genomics:ZygosityValue ; rdfs:label "Mosaic (low VAF)"@en . genomics:MosaicHigh a genomics:ZygosityValue ; rdfs:label "Mosaic (high VAF)"@en . genomics:Cis a genomics:PhaseValue ; rdfs:label "Cis (same chromosome)"@en . genomics:Trans a genomics:PhaseValue ; rdfs:label "Trans (opposite chromosomes)"@en . genomics:PhaseUnknown a genomics:PhaseValue ; rdfs:label "Phase Unknown"@en . genomics:AutosomalDominant a genomics:InheritanceMode ; rdfs:label "Autosomal Dominant"@en . genomics:AutosomalRecessive a genomics:InheritanceMode ; rdfs:label "Autosomal Recessive"@en . genomics:XLinkedDominant a genomics:InheritanceMode ; rdfs:label "X-Linked Dominant"@en . genomics:XLinkedRecessive a genomics:InheritanceMode ; rdfs:label "X-Linked Recessive"@en . genomics:Mitochondrial a genomics:InheritanceMode ; rdfs:label "Mitochondrial"@en . genomics:YLinked a genomics:InheritanceMode ; rdfs:label "Y-Linked"@en . genomics:MultifactorialPolygenic a genomics:InheritanceMode ; rdfs:label "Multifactorial / Polygenic"@en . genomics:UnknownInheritance a genomics:InheritanceMode ; rdfs:label "Unknown Inheritance"@en . genomics:Monoallelic a genomics:AllelicRequirement ; rdfs:label "Monoallelic"@en . genomics:Biallelic a genomics:AllelicRequirement ; rdfs:label "Biallelic"@en . # ============================================================================ # Named Individuals — Test Type, Carrier Status, Order Status # ============================================================================ genomics:SingleGeneTest a genomics:TestType ; rdfs:label "Single Gene Test"@en . genomics:GenePanelTest a genomics:TestType ; rdfs:label "Gene Panel"@en . genomics:ExomeSequencing a genomics:TestType ; rdfs:label "Whole Exome Sequencing"@en . genomics:GenomeSequencing a genomics:TestType ; rdfs:label "Whole Genome Sequencing"@en . genomics:ChromosomalMicroarray a genomics:TestType ; rdfs:label "Chromosomal Microarray"@en . genomics:KaryotypeAnalysis a genomics:TestType ; rdfs:label "Karyotype"@en . genomics:MLPA a genomics:TestType ; rdfs:label "MLPA"@en . genomics:RepeatExpansionTest a genomics:TestType ; rdfs:label "Repeat Expansion Test"@en . genomics:MethylationStudy a genomics:TestType ; rdfs:label "Methylation Study"@en . genomics:RNASequencing a genomics:TestType ; rdfs:label "RNA Sequencing"@en . genomics:PositiveAffected a genomics:CarrierStatus ; rdfs:label "Positive — Affected"@en . genomics:PositiveUnaffected a genomics:CarrierStatus ; rdfs:label "Positive — Unaffected"@en . genomics:Negative a genomics:CarrierStatus ; rdfs:label "Negative"@en . genomics:NotTested a genomics:CarrierStatus ; rdfs:label "Not Tested"@en . genomics:Obligate a genomics:CarrierStatus ; rdfs:label "Obligate Carrier"@en . genomics:OrderPending a genomics:OrderStatus ; rdfs:label "Pending"@en . genomics:OrderInProgress a genomics:OrderStatus ; rdfs:label "In Progress"@en . genomics:OrderResulted a genomics:OrderStatus ; rdfs:label "Resulted"@en . genomics:OrderCancelled a genomics:OrderStatus ; rdfs:label "Cancelled"@en . # ============================================================================ # Named Individuals — Pedigree Relationship Roles (HL7 v3 RoleCode-aligned) # ============================================================================ # Cardinal first-degree, second-degree, and proband-marker relations are # first-class. Complex relations are expressed via nested PedigreeMember # chains. Codes follow HL7 v3 PersonalRelationshipRoleType (RoleCode). genomics:Proband a genomics:RelationshipRole ; rdfs:label "Proband"@en ; rdfs:comment "The index patient who brought the family to clinical attention. Marker, not a kinship relation."@en . genomics:MTH a genomics:RelationshipRole ; rdfs:label "Mother"@en . genomics:FTH a genomics:RelationshipRole ; rdfs:label "Father"@en . genomics:SIS a genomics:RelationshipRole ; rdfs:label "Sister"@en . genomics:BRO a genomics:RelationshipRole ; rdfs:label "Brother"@en . genomics:DAU a genomics:RelationshipRole ; rdfs:label "Daughter"@en . genomics:SON a genomics:RelationshipRole ; rdfs:label "Son"@en . genomics:MAUNT a genomics:RelationshipRole ; rdfs:label "Maternal Aunt"@en . genomics:MUNCLE a genomics:RelationshipRole ; rdfs:label "Maternal Uncle"@en . genomics:PAUNT a genomics:RelationshipRole ; rdfs:label "Paternal Aunt"@en . genomics:PUNCLE a genomics:RelationshipRole ; rdfs:label "Paternal Uncle"@en . genomics:MGRMTH a genomics:RelationshipRole ; rdfs:label "Maternal Grandmother"@en . genomics:MGRFTH a genomics:RelationshipRole ; rdfs:label "Maternal Grandfather"@en . genomics:PGRMTH a genomics:RelationshipRole ; rdfs:label "Paternal Grandmother"@en . genomics:PGRFTH a genomics:RelationshipRole ; rdfs:label "Paternal Grandfather"@en . genomics:MCOUSN a genomics:RelationshipRole ; rdfs:label "Maternal Cousin"@en . genomics:PCOUSN a genomics:RelationshipRole ; rdfs:label "Paternal Cousin"@en . genomics:NIECE a genomics:RelationshipRole ; rdfs:label "Niece"@en . genomics:NEPHEW a genomics:RelationshipRole ; rdfs:label "Nephew"@en . # ============================================================================ # Named Individuals — Submitter Categories # ============================================================================ genomics:SubmitterLaboratory a genomics:SubmitterCategory ; rdfs:label "Clinical Laboratory"@en . genomics:SubmitterConsortium a genomics:SubmitterCategory ; rdfs:label "Consortium"@en ; rdfs:comment "Multi-institution curation consortium (e.g., ENIGMA for BRCA1/2)."@en . genomics:SubmitterExpertPanel a genomics:SubmitterCategory ; rdfs:label "Expert Panel"@en ; rdfs:comment "ClinGen Variant Curation Expert Panel (VCEP)."@en . genomics:SubmitterResearch a genomics:SubmitterCategory ; rdfs:label "Research Group"@en . genomics:SubmitterClinician a genomics:SubmitterCategory ; rdfs:label "Single Clinician"@en . # ============================================================================ # Named Individuals — Review Status (ClinGen/ClinVar 7-tier taxonomy) # ============================================================================ genomics:NoAssertionProvided a genomics:ReviewStatus ; rdfs:label "No Assertion Provided"@en ; genomics:starRating 0 . genomics:CriteriaNotProvided a genomics:ReviewStatus ; rdfs:label "Criteria Not Provided"@en ; genomics:starRating 0 . genomics:SingleSubmitter a genomics:ReviewStatus ; rdfs:label "Single Submitter (criteria provided)"@en ; genomics:starRating 1 . genomics:ConflictingSubmissions a genomics:ReviewStatus ; rdfs:label "Conflicting Submissions"@en ; genomics:starRating 1 . genomics:MultipleSubmittersNoConflict a genomics:ReviewStatus ; rdfs:label "Multiple Submitters, No Conflict"@en ; genomics:starRating 2 . genomics:ExpertPanelReviewed a genomics:ReviewStatus ; rdfs:label "Expert Panel Reviewed"@en ; genomics:starRating 3 . genomics:PracticeGuideline a genomics:ReviewStatus ; rdfs:label "Practice Guideline"@en ; genomics:starRating 4 . # ============================================================================ # Named Individuals — Causality Status (Phenopacket-aligned) # ============================================================================ genomics:Causative a genomics:CausalityStatus ; rdfs:label "Causative"@en ; rdfs:comment "This variant is the direct cause of the patient's phenotype."@en . genomics:Contributory a genomics:CausalityStatus ; rdfs:label "Contributory"@en ; rdfs:comment "This variant contributes to but is not solely responsible for the phenotype."@en . genomics:UncertainCausality a genomics:CausalityStatus ; rdfs:label "Uncertain Causality"@en ; rdfs:comment "Causal role is unclear; further investigation needed."@en . genomics:Rejected a genomics:CausalityStatus ; rdfs:label "Rejected"@en ; rdfs:comment "This variant has been ruled out as the cause."@en . # ============================================================================ # Named Individuals — Somatic Status (germline vs. somatic origin) # ============================================================================ # Added in v1-draft.0.2. Maps LOINC 48002-0 (Genomic source class). genomics:Germline a owl:NamedIndividual, genomics:SomaticStatus ; rdfs:label "Germline"@en ; rdfs:comment "Variant present in the germline; inherited or de novo at conception; present in all cells of the individual."@en ; vs:term_status "unstable" . genomics:Somatic a owl:NamedIndividual, genomics:SomaticStatus ; rdfs:label "Somatic"@en ; rdfs:comment "Variant acquired post-conception in a specific tissue / cell lineage; not heritable. Common in tumor samples."@en ; vs:term_status "unstable" . genomics:UnknownSomaticStatus a owl:NamedIndividual, genomics:SomaticStatus ; rdfs:label "Unknown Somatic Status"@en ; rdfs:comment "Origin not determined by the reporting workflow. Use when the source data is silent on germline-vs-somatic classification rather than guessing."@en ; vs:term_status "unstable" . # ============================================================================ # Named Individuals — Sequencing Technology # ============================================================================ genomics:ShortReadIllumina a genomics:SequencingTechnology ; rdfs:label "Short-read Illumina"@en ; rdfs:comment "Illumina short-read sequencing (NovaSeq, NextSeq, etc.). Dominant clinical platform."@en . genomics:LongReadONT a genomics:SequencingTechnology ; rdfs:label "Long-read Oxford Nanopore"@en ; rdfs:comment "Oxford Nanopore Technologies long-read sequencing (PromethION, MinION, etc.)."@en . genomics:LongReadPacBio a genomics:SequencingTechnology ; rdfs:label "Long-read PacBio HiFi"@en ; rdfs:comment "Pacific Biosciences HiFi/CCS long-read sequencing (Revio, Sequel)."@en . genomics:Mixed a genomics:SequencingTechnology ; rdfs:label "Mixed Technology"@en ; rdfs:comment "Combined short-read + long-read or hybrid sequencing strategies."@en . genomics:GenotypingArray a genomics:SequencingTechnology ; rdfs:label "Genotyping Array"@en ; rdfs:comment "SNP genotyping array (Illumina Global Screening Array, Affymetrix Axiom, etc.). Used by 23andMe / AncestryDNA / MyHeritage / FTDNA — auto-tagged ConsumerGrade in the importer."@en . # ============================================================================ # Named Individuals — Laboratory Certification # ============================================================================ genomics:CLIA a genomics:LaboratoryCertification ; rdfs:label "CLIA"@en ; rdfs:comment "Clinical Laboratory Improvement Amendments — US federal regulatory certification of clinical laboratories."@en . genomics:CAP a genomics:LaboratoryCertification ; rdfs:label "CAP"@en ; rdfs:comment "College of American Pathologists accreditation."@en . genomics:ISO15189 a genomics:LaboratoryCertification ; rdfs:label "ISO 15189"@en ; rdfs:comment "ISO 15189 medical laboratory accreditation (international)."@en . genomics:Uncertified a genomics:LaboratoryCertification ; rdfs:label "Uncertified"@en ; rdfs:comment "Laboratory holds no clinical certification (research lab, consumer DTC service)."@en . # ============================================================================ # Named Individuals — File Format # ============================================================================ genomics:BAM a genomics:FileFormat ; rdfs:label "BAM"@en ; rdfs:comment "Binary Alignment/Map format. Cold tier (out-of-pod, referenced)."@en . genomics:CRAM a genomics:FileFormat ; rdfs:label "CRAM"@en ; rdfs:comment "Compressed Reference-aligned Alignment/Map format. Cold tier."@en . genomics:FASTQ a genomics:FileFormat ; rdfs:label "FASTQ"@en ; rdfs:comment "Raw sequencing reads with quality scores. Cold tier."@en . genomics:gVCF a genomics:FileFormat ; rdfs:label "gVCF"@en ; rdfs:comment "Genomic VCF — per-position records including reference-confidence blocks. Hot tier."@en . genomics:VCF a genomics:FileFormat ; rdfs:label "VCF"@en ; rdfs:comment "Variant Call Format. Hot tier."@en . genomics:BCF a genomics:FileFormat ; rdfs:label "BCF"@en ; rdfs:comment "Binary Call Format (binary VCF). Hot tier."@en . genomics:OtherFileFormat a genomics:FileFormat ; rdfs:label "Other"@en ; rdfs:comment "File format not enumerated above (e.g., methylation tracks, structural-variant-specific formats)."@en . # ============================================================================ # Named Individuals — Genomic Data Provenance # ============================================================================ genomics:ConsumerArray a genomics:DataProvenance ; rdfs:label "Consumer Array"@en ; rdfs:comment "Direct-to-consumer genotyping-array data (23andMe, AncestryDNA, MyHeritage, FTDNA). Phase 2C importer auto-tags every variant from this lane as genomics:ConsumerGrade with genomics:requiresConfirmation true on any pathogenic interpretation."@en . genomics:ClinicalSequencing a genomics:DataProvenance ; rdfs:label "Clinical Sequencing"@en ; rdfs:comment "Clinical-grade sequencing performed by a CLIA/CAP/ISO15189-certified laboratory."@en . genomics:ResearchSequencing a genomics:DataProvenance ; rdfs:label "Research Sequencing"@en ; rdfs:comment "Research-quality sequencing without clinical certification (university cores, biobanks, research consortia)."@en . genomics:Imported a genomics:DataProvenance ; rdfs:label "Imported"@en ; rdfs:comment "Variant data imported from a third-party report, bundle, or pre-existing record where the original sequencing context is incomplete."@en . genomics:AIExtractedGenomics a genomics:DataProvenance ; rdfs:label "AI Extracted"@en ; rdfs:comment "Variant extracted by an LLM from unstructured narrative text (e.g., scanned PDF report). Treat as Imported provenance with elevated confirmation requirement."@en . # ============================================================================ # Named Individuals — Data Quality Tier (per D-QUALITY-TIER) # ============================================================================ genomics:DataQualityTier a owl:Class ; rdfs:label "Data Quality Tier"@en ; rdfs:comment "Tier classification of the quality of genomic data, used by the advisory engine to gate auto-applied recommendations and by narrative subsystems to drive disclosure language."@en ; vs:term_status "unstable" . genomics:ClinicalGrade a genomics:DataQualityTier ; rdfs:label "Clinical Grade"@en ; rdfs:comment "Sequencing performed by a CLIA-, CAP-, or ISO15189-certified laboratory using a clinical-grade method (>=30x mean coverage WGS, validated clinical panel, or equivalent), with raw files (BAM/CRAM/VCF) retained or accessible. The only tier from which Pathogenic / LikelyPathogenic interpretations may drive auto-applied care recommendations without confirmatory re-testing."@en . genomics:ResearchGrade a genomics:DataQualityTier ; rdfs:label "Research Grade"@en ; rdfs:comment "Research-quality sequencing without clinical certification — typical of university cores, biobanks, and research consortia. Adequate for hypothesis generation but requires confirmatory clinical-grade re-testing before driving care decisions."@en . genomics:ConsumerGrade a genomics:DataQualityTier ; rdfs:label "Consumer Grade"@en ; rdfs:comment "Direct-to-consumer genotyping arrays (23andMe, AncestryDNA, MyHeritage, FTDNA) and consumer DTC sequencing without clinical certification. Genotyping arrays in particular interrogate only ~600k–900k SNP positions and have non-trivial false-positive rates for pathogenic variant calls. ALL Phase 2C consumer-array imports auto-tag this tier; Pathogenic / LikelyPathogenic interpretations on ConsumerGrade variants MUST carry requiresConfirmation true (SHACL-enforced)."@en . genomics:UnknownQuality a genomics:DataQualityTier ; rdfs:label "Unknown Quality"@en ; rdfs:comment "Quality tier could not be determined from available metadata (no certification, coverage, or method information). Treated as ConsumerGrade by the downstream safety constraint — assume the worst until promoted."@en . # ============================================================================ # Annotation Properties (parallel to health:snomedCode / health:loincCode) # ============================================================================ genomics:loincCode a owl:AnnotationProperty ; rdfs:label "LOINC Code Annotation"@en ; rdfs:comment "LOINC code or answer-list URI annotated on a property or named individual to record its Layer 1 standard mapping."@en ; vs:term_status "unstable" . genomics:loincAnswerCode a owl:AnnotationProperty ; rdfs:label "LOINC Answer Code Annotation"@en ; rdfs:comment "LOINC answer code (LA-prefixed string) for an enumerated value, e.g. LA6668-3 = Pathogenic."@en ; vs:term_status "unstable" . # ============================================================================ # Class Declarations for Named-Individual Ranges # ============================================================================ genomics:AcmgClass a owl:Class ; rdfs:label "ACMG Classification"@en ; vs:term_status "unstable" . genomics:ZygosityValue a owl:Class ; rdfs:label "Zygosity Value"@en ; vs:term_status "unstable" . genomics:PhaseValue a owl:Class ; rdfs:label "Allelic Phase Value"@en ; vs:term_status "unstable" . genomics:InheritanceMode a owl:Class ; rdfs:label "Mode of Inheritance"@en ; vs:term_status "unstable" . genomics:AllelicRequirement a owl:Class ; rdfs:label "Allelic Requirement"@en ; vs:term_status "unstable" . genomics:TestType a owl:Class ; rdfs:label "Genetic Test Type"@en ; vs:term_status "unstable" . genomics:CarrierStatus a owl:Class ; rdfs:label "Carrier Status"@en ; vs:term_status "unstable" . genomics:RelationshipRole a owl:Class ; rdfs:label "Pedigree Relationship Role"@en ; vs:term_status "unstable" . genomics:OrderStatus a owl:Class ; rdfs:label "Order Status"@en ; vs:term_status "unstable" . genomics:SubmitterCategory a owl:Class ; rdfs:label "Submitter Category"@en ; vs:term_status "unstable" . genomics:ReviewStatus a owl:Class ; rdfs:label "Review Status"@en ; vs:term_status "unstable" . genomics:CausalityStatus a owl:Class ; rdfs:label "Causality Status"@en ; vs:term_status "unstable" . genomics:SomaticStatus a owl:Class ; rdfs:label "Somatic Status"@en ; rdfs:comment "Closed enumeration of variant origin classifications (germline / somatic / unknown). Added in v1-draft.0.2."@en ; vs:term_status "unstable" . genomics:SequencingTechnology a owl:Class ; rdfs:label "Sequencing Technology"@en ; vs:term_status "unstable" . genomics:LaboratoryCertification a owl:Class ; rdfs:label "Laboratory Certification"@en ; vs:term_status "unstable" . genomics:FileFormat a owl:Class ; rdfs:label "File Format"@en ; vs:term_status "unstable" . genomics:DataProvenance a owl:Class ; rdfs:label "Genomic Data Provenance"@en ; vs:term_status "unstable" . # ============================================================================ # Phenotypic Observation (v1-draft.0.4) — index-patient HPO profile # ============================================================================ genomics:PhenotypicObservation a owl:Class ; rdfs:label "Phenotypic Observation"@en ; rdfs:comment "A single HPO-coded phenotypic feature observed in the index patient, captured outside the pedigree/variant context (e.g. during conversational phenotype intake). Supports negated findings (phenotype explicitly absent), essential for narrowing a differential. Provenance is typically cascade:SelfReported. Reuses genomics:hpoTerm for the HPO linkage."@en ; owl:versionInfo "Added in genomics v1-draft.0.4" ; vs:term_status "unstable" . genomics:PhenotypePresenceValue a owl:Class ; rdfs:label "Phenotype Presence Value"@en ; rdfs:comment "Closed enumeration: whether the phenotype is present, absent, or resolved."@en ; owl:versionInfo "Added in genomics v1-draft.0.4" ; vs:term_status "unstable" . genomics:Present a owl:NamedIndividual, genomics:PhenotypePresenceValue ; rdfs:label "Present"@en ; vs:term_status "unstable" . genomics:Absent a owl:NamedIndividual, genomics:PhenotypePresenceValue ; rdfs:label "Absent"@en ; rdfs:comment "Phenotype explicitly assessed and NOT present (negated finding)."@en ; vs:term_status "unstable" . genomics:Resolved a owl:NamedIndividual, genomics:PhenotypePresenceValue ; rdfs:label "Resolved"@en ; vs:term_status "unstable" . genomics:phenotypePresence a owl:ObjectProperty ; rdfs:label "Phenotype Presence"@en ; rdfs:domain genomics:PhenotypicObservation ; rdfs:range genomics:PhenotypePresenceValue ; owl:versionInfo "Added in genomics v1-draft.0.4" ; vs:term_status "unstable" . genomics:phenotypeOnsetDate a owl:DatatypeProperty ; rdfs:label "Phenotype Onset Date"@en ; rdfs:comment "Calendar date the phenotype was first observed. Use genomics:phenotypeOnsetAge for age-based onset."@en ; rdfs:domain genomics:PhenotypicObservation ; rdfs:range xsd:date ; owl:versionInfo "Added in genomics v1-draft.0.4" ; vs:term_status "unstable" . genomics:phenotypeSeverity a owl:DatatypeProperty ; rdfs:label "Phenotype Severity"@en ; rdfs:comment "Optional HPO severity modifier: mild, moderate, severe, profound (HP:0012823 modifier space)."@en ; rdfs:domain genomics:PhenotypicObservation ; rdfs:range xsd:string ; owl:versionInfo "Added in genomics v1-draft.0.4" ; vs:term_status "unstable" . genomics:phenotypeNote a owl:DatatypeProperty ; rdfs:label "Phenotype Note"@en ; rdfs:domain genomics:PhenotypicObservation ; rdfs:range xsd:string ; owl:versionInfo "Added in genomics v1-draft.0.4" ; vs:term_status "unstable" . ## Ontology: workbench (DRAFT — v1-draft) ## File: workbench.ttl # Cascade Workbench Vocabulary (Layer 3, app-specific) — v1-draft # Authored in spec/. Per draft policy, NOT registered in spec/VOCAB_VERSIONS # until v1.0 graduation. # # Changelog # v1-draft.0.6 (2026-08-18) # - ADDED the curation axes: three flag properties that carry a user's # curation of EFFECTIVE VIEWS as append-only overlay state, never a # mutation of the records themselves. workbench:problemListCuration # ("included"/"excluded") records the user's ruling on whether a # condition cluster belongs on the effective problem list; # workbench:recordVisibility ("hidden"/"visible") hides a record from # effective views non-destructively, with unhide as a later overlay; # workbench:recordImportance ("important"/"normal") marks a record as # salient, usable as a retrieval key by downstream queries. # - Standards-first check: W3C Web Annotation motivations (oa:bookmarking, # oa:moderating) were considered and not used. These flags are not # annotation CONTENT; they are projection state read by the # effective-view resolver, and they ride the existing record-amendment # overlay machinery (workbench:Annotation with annotationProperty / # annotationValue, PROV attribution on the overlay), exactly like # workbench:userSourceLabel in v1-draft.0.4. No oa: term is redeclared. # - Resolution rule shared by all three: latest overlay by dct:created # wins. Merge behavior is per-axis: recordImportance follows the # surviving record; recordVisibility is discharged when its record is # superseded (the survivor's own visibility governs, so a hidden # duplicate never silently hides the record the user chose to keep); # problemListCuration resolves across a cluster's member records, so it # follows by construction. # - No SHACL change: the overlays reuse the already-shaped string # predicates workbench:annotationProperty and workbench:annotationValue. # - problemListCuration formalizes a term the shipping app already emits # under the draft-term policy; the visibility and importance terms land # together with the app work that emits them. # v1-draft.0.5 (2026-07-15) # - ADDED the notes / flags / follow-ups substrate as W3C WEB ANNOTATIONS # ([NOTES-ANNOTATION-VOCAB]; proposal 2026-07-13-notes-as-web-annotations # in the Workbench vocab-proposals). Caregiver notes, "needs research" # flags, and follow-ups are ONE artifact — an oa:Annotation over one or # more graph nodes — distinguished by oa:motivatedBy (oa:commenting / # oa:questioning / workbench:followUp). Layer-1 reuse, nothing # redeclared: oa: carries body, multi-target, motivation, and span # selectors (oa:TextQuoteSelector / oa:TextPositionSelector, the same # byte-verified-span idea the Claims Ledger uses); PROV-O carries # attribution (prov:wasAttributedTo — the caregiver, distinct from the # patient and from any agent) and prov:generatedAtTime. Follow-ups are # dual-typed cal:Vtodo and reuse W3C RDF Calendar ical:due + # ical:status (RFC 5545 VTODO statuses) — schema:dueDate was considered # and REJECTED because it does not exist (verified 404 at # schema.org/dueDate; schema.org has only paymentDueDate). # - MINTED exactly one term: workbench:followUp, an oa:Motivation with # skos:broader oa:questioning, per the OA model's extension rule for # custom motivations. # - REMOVED workbench:InvestigationNote, workbench:hasNote, and # workbench:noteText (draft removal; never emitted by shipping code — # only type declarations existed in the Workbench contracts package). # Superseded by the oa: substrate: an investigation-scoped note is an # oa:Annotation whose oa:hasTarget is the workbench:Investigation. The # contracts-type cleanup rides with shell Phase 9. # - Pod placement: annotations live in a top-level notes/ container (see # pod-structure.md §5.2), separate from annotations/ (which holds the # record-amendment OVERLAYS below — machinery, not user content). # v1-draft.0.4 (2026-06-28) # - ADDED the filing/organization axis (workbench:userSourceLabel), the # user-chosen label for the SOURCE a record is filed under in the # Workbench "filing cabinet". It is a filing PREFERENCE attributed to the # user: carried on a workbench:Annotation overlay (annotationProperty = # "workbench:userSourceLabel", annotationValue = the label) bearing # cascade:SelfReported provenance. It MUST NOT overwrite the objective # imported origin clinical:sourceEHR, which is preserved and shown # alongside; the effective grouping source prefers this label, else falls # back to clinical:sourceEHR / the import-batch tag. Orthogonal axis with # an open domain, mirroring how workbench:verificationStatus was added in # v1-draft.0.3, so it can file any record. No new SHACL shape required: # the Annotation overlay reuses the already-shaped string predicates # workbench:annotationProperty and workbench:annotationValue. # v1-draft.0.3 (2026-06-23) # - Tombstone: REMOVED workbench:contentHash. A SHA-256 of an erased # record's triples is still pseudonymised personal data (EDPB Guidelines # 01/2025; WP29 WP216 Table 5): health content is low-entropy and # enumerable, so the hash is brute-forceable and re-creates the very # erasure obligation the hard-erase discharged. A content-free event # marker is the correct residue. # - Tombstone: ADDED workbench:erasureAction (the action verb), so the # Tombstone is a self-describing, content-free audit event # (opaque id + actor + timestamp + action), not a content fingerprint. # - ADDED the verification axis (workbench:verificationStatus + # VerificationStatusValue {Unverified, Confirmed, Refuted}), orthogonal # to the source axis (cascade:dataProvenance). Patient self-entered data # is self-SOURCED and defaults to UNVERIFIED; this mirrors FHIR # AllergyIntolerance/Condition.verificationStatus and is a candidate to # graduate to clinical:/Layer 2 if it proves broadly useful. @prefix workbench: . @prefix evidence: . @prefix cascade: . @prefix owl: . @prefix rdf: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix prov: . @prefix oa: . @prefix skos: . @prefix ical: . @prefix vs: . # ============================================================================ # Changelog # ============================================================================ # v1.0-draft.0.7 (2026-08-20) — Per-term maturity. Every term now carries # vs:term_status (W3C SemWeb Vocabulary Status, originating in FOAF): # "unstable" for draft terms, "archaic" for terms already marked # owl:deprecated. The convention is per-TERM by design, so a vocabulary # can promote individual terms in place without a namespace change; # Cascade previously stated maturity once per vocabulary, in prose no # machine could read. No term definition changed. # ============================================================================ # Ontology Metadata # ============================================================================ a owl:Ontology ; dct:title "Cascade Protocol Workbench Vocabulary"@en ; dct:description "Layer 3 vocabulary for the Cascade Workbench desktop investigation app."@en ; dct:creator "Cascade Agentic Labs" ; dct:created "2026-06-16"^^xsd:date ; dct:modified "2026-08-20"^^xsd:date ; owl:versionInfo "1.0-draft.0.7" ; rdfs:comment "Holds the app-specific workspace objects only: Investigation, ImportedConversation, Hypothesis, Pin, InvestigationNote, plus the append-only record-amendment overlays (Amendment, Annotation, Retraction, Tombstone). The assertion/evidence/verdict grounding model is deliberately NOT here (it is cross-cutting Layer 2 evidence:). Phenotype capture is NOT here (it extends genomics:). Per the Layer 3 graduation rule, any class here that proves useful to other apps should graduate to Layer 2. Draft: not in VOCAB_VERSIONS until v1.0 graduation."@en ; rdfs:seeAlso . # ============================================================================ # Classes # ============================================================================ workbench:Investigation a owl:Class ; rdfs:label "Investigation"@en ; rdfs:comment "A named, longitudinal inquiry over one patient's Pod: the top-level workspace object. Aggregates imported conversations, hypotheses, pinned evidence, and notes over time. Only the Workbench UI touches this class directly."@en ; owl:versionInfo "Added in workbench v1-draft.0.1" ; vs:term_status "unstable" . workbench:ImportedConversation a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Imported Conversation"@en ; rdfs:comment "A conversation the user had with a general-purpose AI assistant (ChatGPT, Claude, Gemini, etc.), imported into an Investigation as a source. Discrete assertions extracted from it are evidence:Assertion instances produced by a cascade:AIExtractionActivity; those assertions carry cascade:dataProvenance cascade:AIAsserted (ungrounded by construction until graded)."@en ; owl:versionInfo "Added in workbench v1-draft.0.1" ; vs:term_status "unstable" . workbench:Hypothesis a owl:Class ; rdfs:label "Hypothesis"@en ; rdfs:comment "A candidate explanation under active consideration in an Investigation, with a lifecycle status. Designed to support widening the differential and explicitly retiring or excluding hypotheses, not just confirming one."@en ; owl:versionInfo "Added in workbench v1-draft.0.1" ; vs:term_status "unstable" . workbench:Pin a owl:Class ; rdfs:label "Pin"@en ; rdfs:comment "A curated bookmark within an Investigation pointing at any resource (a record, an assertion, a citation) with an optional note. The mechanism by which the user assembles evidence NotebookLM-style."@en ; owl:versionInfo "Added in workbench v1-draft.0.1" ; vs:term_status "unstable" . workbench:InvestigationStatusValue a owl:Class ; rdfs:label "Investigation Status Value"@en ; rdfs:comment "Closed enumeration of Investigation lifecycle states."@en ; owl:versionInfo "Added in workbench v1-draft.0.1" ; vs:term_status "unstable" . workbench:HypothesisStatusValue a owl:Class ; rdfs:label "Hypothesis Status Value"@en ; rdfs:comment "Closed enumeration of Hypothesis lifecycle states."@en ; owl:versionInfo "Added in workbench v1-draft.0.1" ; vs:term_status "unstable" . # ============================================================================ # Named Individuals (enumerations) # ============================================================================ workbench:Active a owl:NamedIndividual, workbench:InvestigationStatusValue ; rdfs:label "Active"@en ; vs:term_status "unstable" . workbench:Paused a owl:NamedIndividual, workbench:InvestigationStatusValue ; rdfs:label "Paused"@en ; vs:term_status "unstable" . workbench:Resolved a owl:NamedIndividual, workbench:InvestigationStatusValue ; rdfs:label "Resolved"@en ; vs:term_status "unstable" . workbench:Archived a owl:NamedIndividual, workbench:InvestigationStatusValue ; rdfs:label "Archived"@en ; vs:term_status "unstable" . workbench:Proposed a owl:NamedIndividual, workbench:HypothesisStatusValue ; rdfs:label "Proposed"@en ; vs:term_status "unstable" . workbench:Supported a owl:NamedIndividual, workbench:HypothesisStatusValue ; rdfs:label "Supported"@en ; vs:term_status "unstable" . workbench:Retired a owl:NamedIndividual, workbench:HypothesisStatusValue ; rdfs:label "Retired"@en ; rdfs:comment "No longer pursued; kept for the record (not deleted)."@en ; vs:term_status "unstable" . workbench:Excluded a owl:NamedIndividual, workbench:HypothesisStatusValue ; rdfs:label "Excluded"@en ; rdfs:comment "Actively ruled out by evidence."@en ; vs:term_status "unstable" . workbench:Unverified a owl:NamedIndividual, workbench:VerificationStatusValue ; rdfs:label "Unverified"@en ; rdfs:comment "Not corroborated by a clinician or authoritative source. The default for patient self-entered data. Maps to FHIR verificationStatus 'unconfirmed'."@en ; vs:term_status "unstable" . workbench:Confirmed a owl:NamedIndividual, workbench:VerificationStatusValue ; rdfs:label "Confirmed"@en ; rdfs:comment "Corroborated. Maps to FHIR verificationStatus 'confirmed'."@en ; vs:term_status "unstable" . workbench:Refuted a owl:NamedIndividual, workbench:VerificationStatusValue ; rdfs:label "Refuted"@en ; rdfs:comment "Disproved on review. Maps to FHIR verificationStatus 'refuted'."@en ; vs:term_status "unstable" . # ============================================================================ # Properties — Investigation # ============================================================================ workbench:investigationTitle a owl:DatatypeProperty ; rdfs:label "investigation title"@en ; rdfs:domain workbench:Investigation ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:aboutPatient a owl:ObjectProperty ; rdfs:subPropertyOf prov:wasAttributedTo ; rdfs:label "about patient"@en ; rdfs:comment "The patient (profile/card.ttl WebID) this Investigation concerns. With caregiver-proxy, the operating agent is recorded separately via cascade:ProxyAgent."@en ; rdfs:domain workbench:Investigation ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . workbench:investigationStatus a owl:ObjectProperty ; rdfs:label "investigation status"@en ; rdfs:domain workbench:Investigation ; rdfs:range workbench:InvestigationStatusValue ; vs:term_status "unstable" . workbench:createdAt a owl:DatatypeProperty ; rdfs:label "created at"@en ; rdfs:domain workbench:Investigation ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . workbench:updatedAt a owl:DatatypeProperty ; rdfs:label "updated at"@en ; rdfs:domain workbench:Investigation ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . workbench:containsConversation a owl:ObjectProperty ; rdfs:label "contains conversation"@en ; rdfs:domain workbench:Investigation ; rdfs:range workbench:ImportedConversation ; vs:term_status "unstable" . workbench:hasHypothesis a owl:ObjectProperty ; rdfs:label "has hypothesis"@en ; rdfs:domain workbench:Investigation ; rdfs:range workbench:Hypothesis ; vs:term_status "unstable" . workbench:hasPin a owl:ObjectProperty ; rdfs:label "has pin"@en ; rdfs:domain workbench:Investigation ; rdfs:range workbench:Pin ; vs:term_status "unstable" . # (workbench:hasNote / workbench:InvestigationNote removed in v1-draft.0.5: # an investigation-scoped note is an oa:Annotation targeting the # Investigation — see the Web Annotation substrate section below.) # ============================================================================ # Properties — ImportedConversation # ============================================================================ workbench:conversationSource a owl:DatatypeProperty ; rdfs:label "conversation source"@en ; rdfs:comment "Originating assistant, e.g. 'chatgpt', 'claude', 'gemini', 'other'."@en ; rdfs:domain workbench:ImportedConversation ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:importedAt a owl:DatatypeProperty ; rdfs:label "imported at"@en ; rdfs:domain workbench:ImportedConversation ; rdfs:range xsd:dateTime ; vs:term_status "unstable" . workbench:messageCount a owl:DatatypeProperty ; rdfs:label "message count"@en ; rdfs:domain workbench:ImportedConversation ; rdfs:range xsd:integer ; vs:term_status "unstable" . workbench:rawContentLocation a owl:DatatypeProperty ; rdfs:label "raw content location"@en ; rdfs:comment "Pod-relative path to the stored raw conversation (recommended path sources/)."@en ; rdfs:domain workbench:ImportedConversation ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:extractedAssertion a owl:ObjectProperty ; rdfs:label "extracted assertion"@en ; rdfs:comment "An evidence:Assertion decomposed from this conversation."@en ; rdfs:domain workbench:ImportedConversation ; rdfs:range evidence:Assertion ; vs:term_status "unstable" . # ============================================================================ # Properties — Hypothesis # ============================================================================ workbench:hypothesisLabel a owl:DatatypeProperty ; rdfs:label "hypothesis label"@en ; rdfs:domain workbench:Hypothesis ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:hypothesisStatus a owl:ObjectProperty ; rdfs:label "hypothesis status"@en ; rdfs:domain workbench:Hypothesis ; rdfs:range workbench:HypothesisStatusValue ; vs:term_status "unstable" . workbench:supportedByAssertion a owl:ObjectProperty ; rdfs:label "supported by assertion"@en ; rdfs:domain workbench:Hypothesis ; rdfs:range evidence:Assertion ; vs:term_status "unstable" . workbench:refutedByAssertion a owl:ObjectProperty ; rdfs:label "refuted by assertion"@en ; rdfs:domain workbench:Hypothesis ; rdfs:range evidence:Assertion ; vs:term_status "unstable" . # ============================================================================ # Properties — Pin # ============================================================================ workbench:pinnedResource a owl:ObjectProperty ; rdfs:label "pinned resource"@en ; rdfs:comment "Any resource pinned into the Investigation. Open range for cross-namespace linking."@en ; rdfs:domain workbench:Pin ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . workbench:pinNote a owl:DatatypeProperty ; rdfs:label "pin note"@en ; rdfs:domain workbench:Pin ; rdfs:range xsd:string ; vs:term_status "unstable" . # ============================================================================ # Notes, research flags, and follow-ups — the W3C Web Annotation substrate # ---------------------------------------------------------------------------- # Added in workbench v1-draft.0.5 ([NOTES-ANNOTATION-VOCAB]). Caregiver notes # ("more tired than usual"), "needs research" flags on claims, and follow-ups # ("recheck TSH in 3 months") are ONE artifact: an oa:Annotation over one or # more graph nodes, distinguished by oa:motivatedBy. This section deliberately # REDECLARES NOTHING from oa:/ical:/prov: — it documents the usage profile and # mints the single term the standards lack. # # The profile (enforced by workbench.shapes.ttl): # - a workbench note IS an oa:Annotation with >= 1 oa:hasTarget (targets may # be any Pod resource: records, conditions, evidence:Assertion instances, # a workbench:Investigation, or an oa:SpecificResource carrying an # oa:TextQuoteSelector / oa:TextPositionSelector to pin a passage inside a # document or transcript — the same byte-verified-span idea the Claims # Ledger uses); # - body text is an oa:TextualBody (rdf:value carries the text); # - motivation mapping: caregiver note = oa:commenting; research flag = # oa:questioning (target is typically an assertion/claim node); # follow-up / open loop = workbench:followUp (below); # - attribution is REQUIRED: prov:wasAttributedTo (who wrote it — the # caregiver, distinct from the patient and from any agent) and # prov:generatedAtTime. A note that flows into grounding as # caregiver-reported evidence carries this provenance with it. # - a follow-up is ADDITIONALLY typed cal:Vtodo and reuses W3C RDF Calendar # terms: ical:due (optional expected-completion date) and ical:status # (REQUIRED; the RFC 5545 VTODO enum "NEEDS-ACTION" / "IN-PROCESS" / # "COMPLETED" / "CANCELLED"). No due-date or status property is minted: # a follow-up genuinely is a to-do. (schema:dueDate was considered and # rejected — it does not exist; schema.org defines only paymentDueDate.) # # Pod placement: notes/ top-level container (pod-structure.md §5.2), separate # from annotations/ (the record-amendment overlays above are edit MACHINERY; # notes/ is user-authored content). # ============================================================================ workbench:followUp a owl:NamedIndividual, oa:Motivation ; rdfs:label "follow up"@en ; skos:prefLabel "follow up"@en ; skos:broader oa:questioning ; rdfs:comment "The motivation of a follow-up / open loop: an annotation that names something the caregiver intends to resolve later (e.g. \"recheck TSH in 3 months\"). Minted per the Web Annotation model's extension rule (a custom motivation SHOULD be a skos:Concept with skos:broader to a standard one); oa:questioning is the nearest standard motivation — an open question awaiting resolution. An annotation motivated by workbench:followUp is dual-typed cal:Vtodo and carries ical:status (required) + ical:due (optional). Candidate to graduate to a shared layer if other Cascade apps adopt the open-loops worklist."@en ; owl:versionInfo "Added in workbench v1-draft.0.5" ; vs:term_status "unstable" . # ============================================================================ # Append-only record amendment overlays # ---------------------------------------------------------------------------- # Original records in a Pod are immutable. Every edit/delete is a NEW overlay # resource (Amendment / Annotation / Retraction / Tombstone) written to the # Pod's annotations/ directory and discovered at query time. The overlays carry # cascade:dataProvenance cascade:SelfReported, an optional prov:wasAttributedTo # actor, and a dct:created timestamp. Added in workbench v1-draft.0.2. # ============================================================================ workbench:Amendment a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Amendment"@en ; rdfs:comment "An append-only overlay that overrides one property value on an existing record without mutating the original. Names the amended record, the predicate CURIE being overridden, and the new value. Discovered at query time so the effective value can be projected over the immutable original."@en ; owl:versionInfo "Added in workbench v1-draft.0.2" ; vs:term_status "unstable" . workbench:Annotation a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Annotation"@en ; rdfs:comment "An append-only overlay that ADDS a note or an extra attribute to an existing record without overriding any of its values. Use Amendment to override; use Annotation to augment."@en ; owl:versionInfo "Added in workbench v1-draft.0.2" ; vs:term_status "unstable" . workbench:Retraction a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Retraction"@en ; rdfs:comment "An append-only soft-delete / supersede overlay (the entered-in-error pattern). The retracted record's bytes are retained; the Retraction marks it as withdrawn and may point at the kept record when merging duplicates."@en ; owl:versionInfo "Added in workbench v1-draft.0.2" ; vs:term_status "unstable" . workbench:Tombstone a owl:Class ; rdfs:subClassOf prov:Entity ; rdfs:label "Tombstone"@en ; rdfs:comment "A content-free hard-erase audit marker. Unlike a Retraction, the erased record's bytes are gone from its bucket file; the Tombstone records only the EVENT of erasure — the erased record's opaque id, the actor (prov:wasAttributedTo), the timestamp (dct:created), and the action (workbench:erasureAction) — so the deletion is provably logged WITHOUT retaining any content of the erased record. It deliberately holds no content hash: a hash of an erased record's triples is still pseudonymised personal data (low-entropy health content is brute-forceable), which would re-create the erasure obligation the hard-erase just discharged."@en ; owl:versionInfo "Added in workbench v1-draft.0.2; contentHash removed, erasureAction added in v1-draft.0.3" ; vs:term_status "unstable" . workbench:VerificationStatusValue a owl:Class ; rdfs:label "Verification Status Value"@en ; rdfs:comment "Closed enumeration of a record's clinical verification state — the VERIFICATION axis, orthogonal to the SOURCE axis (cascade:dataProvenance). Mirrors FHIR AllergyIntolerance/Condition.verificationStatus. Candidate to graduate to clinical:/Layer 2."@en ; owl:versionInfo "Added in workbench v1-draft.0.3" ; vs:term_status "unstable" . # ── Properties shared by all overlay classes ──────────────────────────────── # (cascade:dataProvenance, prov:wasAttributedTo, and dct:created are reused # from core/PROV-O/Dublin Core; no new predicates are minted for those.) # ── Properties — Amendment ────────────────────────────────────────────────── workbench:amendsRecord a owl:ObjectProperty ; rdfs:label "amends record"@en ; rdfs:comment "The existing record whose property value this Amendment overrides."@en ; rdfs:domain workbench:Amendment ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . workbench:amendsProperty a owl:DatatypeProperty ; rdfs:label "amends property"@en ; rdfs:comment "The predicate CURIE being overridden, e.g. \"clinical:dosage\"."@en ; rdfs:domain workbench:Amendment ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:amendedValue a owl:DatatypeProperty ; rdfs:label "amended value"@en ; rdfs:comment "The new value that supersedes the original value of amendsProperty."@en ; rdfs:domain workbench:Amendment ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:amendmentReason a owl:DatatypeProperty ; rdfs:label "amendment reason"@en ; rdfs:comment "Optional free-text rationale for the amendment."@en ; rdfs:domain workbench:Amendment ; rdfs:range xsd:string ; vs:term_status "unstable" . # ── Properties — Annotation ───────────────────────────────────────────────── workbench:annotatesRecord a owl:ObjectProperty ; rdfs:label "annotates record"@en ; rdfs:comment "The existing record this Annotation augments."@en ; rdfs:domain workbench:Annotation ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . workbench:annotationText a owl:DatatypeProperty ; rdfs:label "annotation text"@en ; rdfs:comment "Optional free-text note attached to the record."@en ; rdfs:domain workbench:Annotation ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:annotationProperty a owl:DatatypeProperty ; rdfs:label "annotation property"@en ; rdfs:comment "Optional predicate CURIE of an extra attribute being added (paired with annotationValue)."@en ; rdfs:domain workbench:Annotation ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:annotationValue a owl:DatatypeProperty ; rdfs:label "annotation value"@en ; rdfs:comment "Optional value of the extra attribute named by annotationProperty."@en ; rdfs:domain workbench:Annotation ; rdfs:range xsd:string ; vs:term_status "unstable" . # ── Properties — Retraction ───────────────────────────────────────────────── workbench:retractsRecord a owl:ObjectProperty ; rdfs:label "retracts record"@en ; rdfs:comment "The existing record this Retraction soft-deletes / supersedes."@en ; rdfs:domain workbench:Retraction ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . workbench:retractionReason a owl:DatatypeProperty ; rdfs:label "retraction reason"@en ; rdfs:comment "Optional free-text rationale (e.g. \"entered in error\")."@en ; rdfs:domain workbench:Retraction ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:supersededBy a owl:ObjectProperty ; rdfs:label "superseded by"@en ; rdfs:comment "Optional pointer to the kept record when merging duplicates."@en ; rdfs:domain workbench:Retraction ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . # ── Properties — Tombstone ────────────────────────────────────────────────── workbench:erasedRecord a owl:ObjectProperty ; rdfs:label "erased record"@en ; rdfs:comment "The id of the record whose bytes were hard-erased from its bucket file."@en ; rdfs:domain workbench:Tombstone ; rdfs:range rdfs:Resource ; vs:term_status "unstable" . workbench:erasedType a owl:DatatypeProperty ; rdfs:label "erased type"@en ; rdfs:comment "Optional rdf:type CURIE of the erased record (a category, e.g. \"clinical:Medication\"), retained for audit. Category-level only — it names the kind of record erased, never its content."@en ; rdfs:domain workbench:Tombstone ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:erasureAction a owl:DatatypeProperty ; rdfs:label "erasure action"@en ; rdfs:comment "The action this Tombstone records, e.g. \"hard-erase\". Makes the Tombstone a self-describing, content-free audit event."@en ; rdfs:domain workbench:Tombstone ; rdfs:range xsd:string ; vs:term_status "unstable" . workbench:erasureReason a owl:DatatypeProperty ; rdfs:label "erasure reason"@en ; rdfs:comment "Optional free-text rationale for the hard erasure (author-supplied; keep content-free to preserve the erasure)."@en ; rdfs:domain workbench:Tombstone ; rdfs:range xsd:string ; vs:term_status "unstable" . # ── Properties — Verification axis (orthogonal to cascade:dataProvenance) ───── workbench:verificationStatus a owl:ObjectProperty ; rdfs:label "verification status"@en ; rdfs:comment "A record's clinical verification state (VERIFICATION axis), independent of who reported it (SOURCE axis, cascade:dataProvenance). Open domain so it can tag any record; patient self-entered records default to workbench:Unverified."@en ; rdfs:range workbench:VerificationStatusValue ; owl:versionInfo "Added in workbench v1-draft.0.3" ; vs:term_status "unstable" . # ── Properties — Filing / organization axis (orthogonal to the imported origin) ─ workbench:userSourceLabel a owl:DatatypeProperty ; rdfs:label "user source label"@en ; rdfs:comment "The user-chosen FILING label for the SOURCE a record is organized under in the Workbench 'filing cabinet' (the ORGANIZATION axis). It is a filing PREFERENCE attributed to the user, carried on a workbench:Annotation (annotationProperty = \"workbench:userSourceLabel\", annotationValue = the chosen label) whose overlay bears cascade:SelfReported provenance. It MUST NOT alter the objective imported origin clinical:sourceEHR, which is preserved and displayed alongside. The effective source the UI groups records by prefers this label when present, else falls back to the imported origin (clinical:sourceEHR, else the import-batch tag). Open domain (like workbench:verificationStatus) so it can file any record."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in workbench v1-draft.0.4" ; vs:term_status "unstable" . # ── Properties — Curation axes (effective-view projection state) ──────────── # Added in workbench v1-draft.0.6. Three flags carried on workbench:Annotation # overlays (annotationProperty = the CURIE below, annotationValue = the token), # with cascade:SelfReported provenance and PROV attribution # (prov:wasAttributedTo, dct:created) on the overlay. The latest overlay by # dct:created wins. These are projection state for effective views: they never # alter or erase the underlying record, which stays discoverable with # receipts. Merge behavior is per-axis and is stated on each property: a mark # of salience follows the surviving record, a mark of noise is discharged with # the copy it marked. workbench:problemListCuration a owl:DatatypeProperty ; rdfs:label "problem list curation"@en ; rdfs:comment "The user's ruling on whether a condition cluster belongs on the effective problem list. Closed token set: \"included\" / \"excluded\". Targets a member record of the cluster; resolution is the latest overlay by dct:created across all of the cluster's member records. Distinct from clinical truth: an excluded condition's records remain intact and visible in receipts views."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in workbench v1-draft.0.6" ; vs:term_status "unstable" . workbench:recordVisibility a owl:DatatypeProperty ; rdfs:label "record visibility"@en ; rdfs:comment "A user's non-destructive visibility preference for one record in effective views. Closed token set: \"hidden\" / \"visible\". Hiding is an append-only overlay and unhiding is a later overlay, never a deletion; the record remains in its bucket and in receipts views. Distinct from workbench:Retraction (entered in error, superseded) and workbench:Tombstone (erasure): a hidden record is still asserted as true, the user just does not want it in the way. Discharged when its record is superseded by a duplicate merge: hiding is a statement about the copy that was hidden, and the surviving record's own visibility governs, so a hidden duplicate never silently hides the record the user chose to keep."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in workbench v1-draft.0.6" ; vs:term_status "unstable" . workbench:recordImportance a owl:DatatypeProperty ; rdfs:label "record importance"@en ; rdfs:comment "A user salience flag on one record. Closed token set: \"important\" / \"normal\". Marks the record as a priority retrieval key for downstream queries and agent searches; it carries no further semantics. Follows the surviving record across a duplicate merge: salience is about the fact, not the copy."@en ; rdfs:range xsd:string ; owl:versionInfo "Added in workbench v1-draft.0.6" ; vs:term_status "unstable" . # ============================================================================ # SECTION 3: SHACL VALIDATION SHAPES (Full TTL Source) # ============================================================================ ## SHACL Shapes: core ## File: core.shapes.ttl @prefix sh: . @prefix cascade: . @prefix xsd: . @prefix rdfs: . @prefix dct: . # ============================================================================ # Cascade Protocol -- Core Vocabulary SHACL Validation Shapes # ============================================================================ # Version: 1.6 (2026-08-14) # Validates: cascade:PatientProfile, cascade:Address, cascade:EmergencyContact, # cascade:PharmacyInfo, cascade:AdvanceDirectives, cascade:ProxyAgent, # cascade:ExportManifest, cascade:RecordSummary, # cascade:InteractionScenario, and the values of # cascade:sourceIdentity and cascade:dataAbsentReason wherever # they appear # # Severity levels: # sh:Violation -- Must fix (blocks operations) # sh:Warning -- Should address (shown prominently) # sh:Info -- Suggestion (nice to have) # # These shapes validate core patient demographics and identity structures # defined in core.ttl v2.2. They are used by all Cascade Protocol applications # that serialize PatientProfile data (e.g., Cascade Checkup intake forms, # POTS Check patient context). # ============================================================================ # ============================================================================ # Shape: Patient Profile # ============================================================================ cascade:PatientProfileShape a sh:NodeShape ; sh:targetClass cascade:PatientProfile ; rdfs:label "Patient Profile Shape"@en ; rdfs:comment "Validation constraints for patient demographics and identity extensions"@en ; # VIOLATION: dateOfBirth required for age calculation and screening eligibility sh:property [ sh:path cascade:dateOfBirth ; sh:datatype xsd:date ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Date of Birth"@en ; sh:message "Patient profile must have a date of birth for age calculation and screening eligibility"@en ] ; # VIOLATION: biologicalSex required for clinical calculations sh:property [ sh:path cascade:biologicalSex ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( "male" "female" "intersex" ) ; sh:severity sh:Violation ; sh:name "Biological Sex"@en ; sh:message "Patient profile must specify biological sex (male, female, or intersex) for clinical calculations"@en ] ; # VIOLATION: dataProvenance required for source tracking sh:property [ sh:path cascade:dataProvenance ; sh:minCount 1 ; sh:severity sh:Violation ; sh:name "Data Provenance"@en ; sh:message "Patient profile must have at least one data provenance classification"@en ] ; # WARNING: genderIdentity recommended for patient-centered care sh:property [ sh:path cascade:genderIdentity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ( "woman" "man" "non_binary" "other" "prefer_not_to_say" ) ; sh:severity sh:Warning ; sh:name "Gender Identity"@en ; sh:message "Gender identity is recommended for patient-centered care. Values: woman, man, non_binary, other, prefer_not_to_say"@en ] ; # WARNING: emergencyContact recommended for safety sh:property [ sh:path cascade:emergencyContact ; sh:class cascade:EmergencyContact ; sh:severity sh:Warning ; sh:name "Emergency Contact"@en ; sh:message "An emergency contact is recommended for patient safety"@en ] ; # INFO: ageGroup for screening classification sh:property [ sh:path cascade:ageGroup ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ( "pediatric" "young_adult" "adult" "senior" ) ; sh:severity sh:Info ; sh:name "Age Group"@en ; sh:message "Age group helps classify patients for screening recommendations (pediatric, young_adult, adult, senior)"@en ] ; # INFO: computedAge for convenience sh:property [ sh:path cascade:computedAge ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 150 ; sh:severity sh:Info ; sh:name "Computed Age"@en ; sh:message "Computed age should be between 0 and 150 years"@en ] ; # INFO: maritalStatus for demographic context sh:property [ sh:path cascade:maritalStatus ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ( "single" "married" "domestic_partnership" "divorced" "separated" "widowed" "prefer_not_to_say" ) ; sh:severity sh:Info ; sh:name "Marital Status"@en ; sh:message "Marital status provides demographic context for care planning"@en ] ; # INFO: raceEthnicity for health disparity tracking sh:property [ sh:path cascade:raceEthnicity ; sh:datatype xsd:string ; sh:in ( "american_indian_alaska_native" "asian" "black_african_american" "hispanic_latino" "native_hawaiian_pacific_islander" "white" "other" "prefer_not_to_say" ) ; sh:severity sh:Info ; sh:name "Race/Ethnicity"@en ; sh:message "Race/ethnicity data supports health equity tracking and risk assessment"@en ] ; # INFO: address for contact information sh:property [ sh:path cascade:address ; sh:class cascade:Address ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Address"@en ; sh:message "A postal address is helpful for care coordination and correspondence"@en ] ; # INFO: preferredPharmacy for prescription routing sh:property [ sh:path cascade:preferredPharmacy ; sh:class cascade:PharmacyInfo ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Preferred Pharmacy"@en ; sh:message "A preferred pharmacy helps streamline prescription fulfillment"@en ] ; # INFO: advanceDirectives for care planning sh:property [ sh:path cascade:advanceDirectives ; sh:class cascade:AdvanceDirectives ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Advance Directives"@en ; sh:message "Advance directives document patient wishes for end-of-life care"@en ] ; # INFO: profileId for traceability sh:property [ sh:path cascade:profileId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Profile ID"@en ; sh:message "A profile ID (UUID) enables traceability and deduplication"@en ] . # ============================================================================ # Shape: Address # ============================================================================ cascade:AddressShape a sh:NodeShape ; sh:targetClass cascade:Address ; rdfs:label "Address Shape"@en ; rdfs:comment "Validation constraints for structured postal addresses. Accepts both simplified aliases (city, state) and FHIR-aligned properties (addressCity, addressState)."@en ; # WARNING: city or addressCity recommended for location identification sh:property [ sh:path cascade:city ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "City"@en ; sh:message "City is recommended for meaningful address identification"@en ] ; sh:property [ sh:path cascade:addressCity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Address City (FHIR-aligned)"@en ; sh:message "City (FHIR-aligned) is recommended for meaningful address identification"@en ] ; # WARNING: state or addressState recommended for location identification sh:property [ sh:path cascade:state ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "State"@en ; sh:message "State is recommended for meaningful address identification"@en ] ; sh:property [ sh:path cascade:addressState ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Address State (FHIR-aligned)"@en ; sh:message "State (FHIR-aligned) is recommended for meaningful address identification"@en ] ; # INFO: streetAddress or addressLine sh:property [ sh:path cascade:streetAddress ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Street Address"@en ; sh:message "Street address provides complete location details"@en ] ; sh:property [ sh:path cascade:addressLine ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:name "Address Line (FHIR-aligned)"@en ; sh:message "Address line (FHIR-aligned) provides complete location details"@en ] ; # INFO: postalCode or addressPostalCode sh:property [ sh:path cascade:postalCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Postal Code"@en ; sh:message "Postal code helps with geographic identification"@en ] ; sh:property [ sh:path cascade:addressPostalCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Address Postal Code (FHIR-aligned)"@en ; sh:message "Postal code (FHIR-aligned) helps with geographic identification"@en ] ; # INFO: country or addressCountry sh:property [ sh:path cascade:country ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Country"@en ; sh:message "Country is helpful for international patients"@en ] ; sh:property [ sh:path cascade:addressCountry ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Address Country (FHIR-aligned)"@en ; sh:message "Country (FHIR-aligned) is helpful for international patients"@en ] ; # INFO: addressUse for address purpose sh:property [ sh:path cascade:addressUse ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ( "home" "work" "temp" "old" ) ; sh:severity sh:Info ; sh:name "Address Use"@en ; sh:message "Address use should be home, work, temp, or old"@en ] ; # INFO: addressType for address classification sh:property [ sh:path cascade:addressType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ( "postal" "physical" "both" ) ; sh:severity sh:Info ; sh:name "Address Type"@en ; sh:message "Address type should be postal, physical, or both"@en ] . # ============================================================================ # Shape: Emergency Contact # ============================================================================ cascade:EmergencyContactShape a sh:NodeShape ; sh:targetClass cascade:EmergencyContact ; rdfs:label "Emergency Contact Shape"@en ; rdfs:comment "Validation constraints for patient emergency contact persons"@en ; # VIOLATION: contactName required sh:property [ sh:path cascade:contactName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Contact Name"@en ; sh:message "Emergency contact must have a name"@en ] ; # VIOLATION: contactPhone required sh:property [ sh:path cascade:contactPhone ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Contact Phone"@en ; sh:message "Emergency contact must have a phone number"@en ] ; # WARNING: contactRelationship recommended sh:property [ sh:path cascade:contactRelationship ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ( "spouse" "parent" "child" "sibling" "partner" "friend" "caregiver" "other" ) ; sh:severity sh:Warning ; sh:name "Contact Relationship"@en ; sh:message "Emergency contact relationship is recommended (spouse, parent, child, sibling, partner, friend, caregiver, other)"@en ] . # ============================================================================ # Shape: Pharmacy Info # ============================================================================ cascade:PharmacyInfoShape a sh:NodeShape ; sh:targetClass cascade:PharmacyInfo ; rdfs:label "Pharmacy Info Shape"@en ; rdfs:comment "Validation constraints for preferred pharmacy information"@en ; # VIOLATION: pharmacyName required sh:property [ sh:path cascade:pharmacyName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Pharmacy Name"@en ; sh:message "Pharmacy must have a name"@en ] ; # INFO: pharmacyAddress for location sh:property [ sh:path cascade:pharmacyAddress ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Pharmacy Address"@en ; sh:message "Pharmacy address helps identify the correct location"@en ] ; # INFO: pharmacyPhone for contact sh:property [ sh:path cascade:pharmacyPhone ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Pharmacy Phone"@en ; sh:message "Pharmacy phone number is helpful for prescription coordination"@en ] . # ============================================================================ # Shape: Advance Directives # ============================================================================ cascade:AdvanceDirectivesShape a sh:NodeShape ; sh:targetClass cascade:AdvanceDirectives ; rdfs:label "Advance Directives Shape"@en ; rdfs:comment "Validation constraints for patient advance directive declarations. Elevated PHI sensitivity."@en ; # VIOLATION: hasLivingWill required sh:property [ sh:path cascade:hasLivingWill ; sh:datatype xsd:boolean ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Has Living Will"@en ; sh:message "Advance directives must declare whether a living will is on file"@en ] ; # VIOLATION: hasPowerOfAttorney required sh:property [ sh:path cascade:hasPowerOfAttorney ; sh:datatype xsd:boolean ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Has Power of Attorney"@en ; sh:message "Advance directives must declare whether a healthcare power of attorney is on file"@en ] ; # VIOLATION: hasDNR required sh:property [ sh:path cascade:hasDNR ; sh:datatype xsd:boolean ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Has DNR"@en ; sh:message "Advance directives must declare whether a do-not-resuscitate order is on file"@en ] ; # INFO: advanceDirectiveNotes for additional context sh:property [ sh:path cascade:advanceDirectiveNotes ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Advance Directive Notes"@en ; sh:message "Notes can provide additional context about advance directive wishes"@en ] . # ============================================================================ # Shapes: UserResolution and PendingConflict (v2.9) # ============================================================================ cascade:UserResolutionShape a sh:NodeShape ; sh:targetClass cascade:UserResolution ; rdfs:label "User Resolution Shape"@en ; sh:property [ sh:path cascade:conflictId ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Conflict ID"@en ; sh:message "UserResolution requires a conflictId"@en ] ; sh:property [ sh:path cascade:resolution ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Resolution"@en ; sh:message "UserResolution requires a resolution decision"@en ] . cascade:PendingConflictShape a sh:NodeShape ; sh:targetClass cascade:PendingConflict ; rdfs:label "Pending Conflict Shape"@en ; sh:property [ sh:path cascade:conflictId ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Conflict ID"@en ; sh:message "PendingConflict requires a conflictId"@en ] . # ============================================================================ # Shape: AI Extraction Activity (v3.0) # ============================================================================ cascade:AIExtractionActivityShape a sh:NodeShape ; sh:targetClass cascade:AIExtractionActivity ; rdfs:label "AI Extraction Activity Shape"@en ; # Required: extractionConfidence (0.0 to 1.0) sh:property [ sh:path cascade:extractionConfidence ; sh:datatype xsd:decimal ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ; sh:severity sh:Violation ; sh:name "Extraction Confidence"@en ; sh:message "AIExtractionActivity requires extractionConfidence between 0.0 and 1.0"@en ] ; # Required: extractionModel sh:property [ sh:path cascade:extractionModel ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Extraction Model"@en ; sh:message "AIExtractionActivity requires an extractionModel identifier"@en ] ; # Optional: sourceNarrativeSection sh:property [ sh:path cascade:sourceNarrativeSection ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Source Narrative Section"@en ] ; # Optional: requiresUserReview sh:property [ sh:path cascade:requiresUserReview ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Requires User Review"@en ] . # ============================================================================ # Shape: AI Generation Activity (v3.1) # ============================================================================ cascade:AIGenerationActivityShape a sh:NodeShape ; sh:targetClass cascade:AIGenerationActivity ; rdfs:label "AI Generation Activity Shape"@en ; # Required: extractionModel (reused from AIExtractionActivity) sh:property [ sh:path cascade:extractionModel ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Generation Model"@en ; sh:message "AIGenerationActivity requires an extractionModel identifier"@en ] ; # Required: trigger sh:property [ sh:path cascade:trigger ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Trigger"@en ; sh:message "AIGenerationActivity requires a cascade:trigger value (InitialGeneration, RegenerationAfterReclassification, or AudienceRetargeting)"@en ] ; # Optional: promptVersion sh:property [ sh:path cascade:promptVersion ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Prompt Version"@en ; sh:message "Prompt version is recommended for reproducibility"@en ] ; # Optional: generationTemperature (0.0 - 2.0 covers typical model ranges) sh:property [ sh:path cascade:generationTemperature ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:maxInclusive 2.0 ; sh:severity sh:Warning ; sh:name "Generation Temperature"@en ; sh:message "Generation temperature should be between 0.0 and 2.0"@en ] ; # Optional: extractionConfidence (reused; not all generations score themselves) sh:property [ sh:path cascade:extractionConfidence ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ; sh:severity sh:Info ; sh:name "Generation Confidence"@en ] . # ============================================================================ # Shape: Advisory Application Activity (v3.1) # ============================================================================ cascade:AdvisoryApplicationActivityShape a sh:NodeShape ; sh:targetClass cascade:AdvisoryApplicationActivity ; rdfs:label "Advisory Application Activity Shape"@en ; # Required: prov:used must appear at least twice (advisory IRI + matched record IRI) sh:property [ sh:path ; sh:minCount 2 ; sh:severity sh:Violation ; sh:name "Used (advisory + matched record)"@en ; sh:message "AdvisoryApplicationActivity must record both the advisory IRI and the matched record IRI via prov:used"@en ] ; # Optional but recommended: appliedTriplesCount stamps the application size sh:property [ sh:path cascade:appliedTriplesCount ; sh:datatype xsd:nonNegativeInteger ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Applied Triples Count"@en ; sh:message "Recommended: stamp cascade:appliedTriplesCount with the number of triples inserted by this advisory application (≤64 per CAP C5)"@en ] . # ============================================================================ # Shape: Source Identity (core v3.5) # ============================================================================ # The ORIGIN axis. See cascade:sourceIdentity in core.ttl for the value form and # the slug normalization; this shape checks that a producer wrote a well-formed # one. # # WHY sh:targetSubjectsOf AND NOT sh:targetClass, AND WHY ABSENCE IS UNCHECKED # --------------------------------------------------------------------------- # This is an open-world property shape: it evaluates a record only if that record # ALREADY carries cascade:sourceIdentity. A pod written before core v3.5 carries # it nowhere, so this shape reports nothing on one, and every previously-valid # pod stays valid. # # That is a deliberate choice over the alternative, sh:minCount 1 at sh:Warning # on each record shape in health: and clinical:. Requiring presence the moment # the property is defined turns every existing pod amber for a property no # producer has yet had the opportunity to write, and a warning that fires on # correct historical data is a warning readers learn to skip. The ratchet is # stated in core.ttl's v3.5 changelog rather than left to judgement: presence # becomes a Warning on the record shapes once the reference producers emit it, # and a Violation only after a release in which that Warning is observably absent # from conforming output. # # What IS checked, whenever the property is present: exactly one value, a string, # and one of the three declared schemes. An unprefixed value is the specific # error this catches, because it is what a producer writes when it has stamped a # display label or an import-batch label into the origin axis, which is the # confusion the axis exists to end. cascade:SourceIdentityShape a sh:NodeShape ; sh:targetSubjectsOf cascade:sourceIdentity ; rdfs:label "Source Identity Shape"@en ; rdfs:comment "Constrains the ORIGIN axis on any record that carries it. Open-world: absence is not a finding in core v3.5."@en ; sh:property [ sh:path cascade:sourceIdentity ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^(org:[a-z0-9]+|ns:.+|transport:.+)$" ; sh:name "Source Identity"@en ; sh:message "cascade:sourceIdentity must be a single scheme-prefixed token: 'org:{slug}' for a derivable organization, 'ns:{namespace}' for an identifier assigning authority, or 'transport:{label}' as the honestly-labelled last resort. An unprefixed value is usually a display label or an import-batch label written into the origin axis."@en ; sh:severity sh:Violation ] . # ============================================================================ # Shape: Data Absent Reason (core v3.6) # ============================================================================ # Open-world, exactly like SourceIdentityShape above: sh:targetSubjectsOf, so it # constrains the VALUE wherever cascade:dataAbsentReason appears and never # requires the property's presence. Every pod written before core v3.6 validates # unchanged, because none of them carries the predicate. # # The sh:in list is the 15 codes of # http://terminology.hl7.org/CodeSystem/data-absent-reason, verbatim, as # gathered by the value set http://hl7.org/fhir/ValueSet/data-absent-reason. No # code is added and none is dropped. In particular the raw HL7 v3 NullFlavor # codes (UNK, NAV, NASK, ASKU, ...) are deliberately NOT accepted here: an # importer maps them on the way in, per the mapping table stated on the property # in core.ttl, and accepting both spellings would give every absence two # encodings and make "is this the same absence?" a string-comparison question. # # sh:Violation rather than sh:Warning, and that is not a ratchet skipped. The # ratchet in core v3.5 was about REQUIRING a property no producer had written # yet. Here nothing is required; the constraint only says that a producer which # chooses to write this brand-new property must write a code from the value set # it was defined against. There is no existing data to be lenient toward. cascade:DataAbsentReasonShape a sh:NodeShape ; sh:targetSubjectsOf cascade:dataAbsentReason ; rdfs:label "Data Absent Reason Shape"@en ; rdfs:comment "Constrains the value of cascade:dataAbsentReason on any record that carries it. Open-world: absence is not a finding."@en ; sh:property [ sh:path cascade:dataAbsentReason ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("unknown" "asked-unknown" "temp-unknown" "not-asked" "asked-declined" "masked" "not-applicable" "unsupported" "as-text" "error" "not-a-number" "negative-infinity" "positive-infinity" "not-performed" "not-permitted") ; sh:name "Data Absent Reason"@en ; sh:message "cascade:dataAbsentReason must be a single code from http://terminology.hl7.org/CodeSystem/data-absent-reason. A raw HL7 v3 NullFlavor code (UNK, NAV, NASK, ASKU, ...) is not accepted: map it on import using the table on the property in core.ttl."@en ; sh:severity sh:Violation ] . # ============================================================================ # Changelog # ============================================================================ # # Version 1.6 (2026-08-14) # - Added DataAbsentReasonShape for core v3.6's cascade:dataAbsentReason. # sh:targetSubjectsOf, so it constrains the value wherever the property # appears and never requires its presence; pods written before v3.6 validate # unchanged. VIOLATION for a non-string, a repeated value, or a code outside # the 15-member data-absent-reason value set, which includes a raw HL7 v3 # NullFlavor code written through unmapped. # # Version 1.5 (2026-08-09) # - Added SourceIdentityShape for core v3.5's cascade:sourceIdentity. # sh:targetSubjectsOf, so it constrains the VALUE wherever the property # appears and never requires its presence; pods written before v3.5 validate # unchanged. VIOLATION for a non-string, a repeated value, or a value that # does not carry one of the org: / ns: / transport: schemes. # # Version 1.4 (2026-05-06) # - Added cascade:appliedTriplesCount Info-severity property shape on # AdvisoryApplicationActivityShape (recommended; auditable size of an # advisory application — pairs with CAP profile constraint C5). # # Version 1.3 (2026-05-05) # - Added AIGenerationActivityShape for LLM-generated narrative provenance # - REQUIRED: extractionModel, trigger # - WARNING: promptVersion, generationTemperature # - INFO: extractionConfidence # - Added AdvisoryApplicationActivityShape for advisory patch application # - REQUIRED: prov:used (minCount 2 — advisory IRI and matched record IRI) # # Version 1.2 (2026-03-28) # - Added AIExtractionActivityShape for AI extraction provenance tracking # - REQUIRED: extractionConfidence (xsd:decimal, 0.0-1.0), extractionModel # - OPTIONAL: sourceNarrativeSection, requiresUserReview # # Version 1.1 (2026-03-27) # - Added UserResolutionShape: VIOLATION for conflictId, resolution # - Added PendingConflictShape: VIOLATION for conflictId # # Version 1.0 (2026-02-18) # - Initial release of core vocabulary SHACL shapes (Phase 4) # - PatientProfileShape: VIOLATION for dateOfBirth, biologicalSex, # dataProvenance; WARNING for genderIdentity, emergencyContact; # INFO for ageGroup, computedAge, maritalStatus, raceEthnicity, # address, preferredPharmacy, advanceDirectives, profileId # - AddressShape: WARNING for city/addressCity, state/addressState; # INFO for streetAddress/addressLine, postalCode/addressPostalCode, # country/addressCountry, addressUse, addressType # - EmergencyContactShape: VIOLATION for contactName, contactPhone; # WARNING for contactRelationship # - PharmacyInfoShape: VIOLATION for pharmacyName; # INFO for pharmacyAddress, pharmacyPhone # - AdvanceDirectivesShape: VIOLATION for hasLivingWill, # hasPowerOfAttorney, hasDNR; INFO for advanceDirectiveNotes # # ProxyAgentShape (v3.3) — caregiver-proxy actor cascade:ProxyAgentShape a sh:NodeShape ; sh:targetClass cascade:ProxyAgent ; sh:property [ sh:path cascade:actsForPatient ; sh:minCount 1 ; sh:message "A ProxyAgent must declare the patient it acts for (cascade:actsForPatient)." ] ; sh:property [ sh:path cascade:proxyRelationship ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path cascade:proxyGrantedAt ; sh:minCount 1 ; sh:datatype xsd:dateTime ] . # ============================================================================ # Pod Export Manifest Shapes (v1.1 -- core v3.4) # ============================================================================ # # The manifest is the first thing a consuming agent reads and the thing it # trusts to decide whether an export is complete. Until core v3.4 none of its # classes was defined, so nothing targeted them and validating a manifest # checked nothing at all. cascade:ExportManifestShape a sh:NodeShape ; sh:targetClass cascade:ExportManifest ; rdfs:label "Export Manifest Shape"@en ; rdfs:comment "Validation constraints for a pod export manifest. cascade:ExportManifest is a dcat:Dataset, so the DCAT-standard descriptive terms are the required ones."@en ; # REQUIRED: a title. An untitled dataset cannot be presented to a user. sh:property [ sh:path dct:title ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Title"@en ; sh:message "Export manifest must carry exactly one non-empty dct:title"@en ; sh:severity sh:Violation ] ; # REQUIRED: creation timestamp. Without it a consumer cannot tell whether # the export is current, which is the manifest's main job. sh:property [ sh:path dct:created ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Created"@en ; sh:message "Export manifest must carry exactly one dct:created timestamp"@en ; sh:severity sh:Violation ] ; # REQUIRED: schema version, so a consumer can refuse an export it cannot read sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Export manifest must declare exactly one schemaVersion in major.minor form"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:patientProfileVersion ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Patient Profile Version"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:clinicalSummary ; sh:maxCount 1 ; sh:name "Clinical Summary"@en ; sh:message "Export manifest must not carry more than one clinical summary"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:wellnessSummary ; sh:maxCount 1 ; sh:name "Wellness Summary"@en ; sh:message "Export manifest must not carry more than one wellness summary"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path dct:description ; sh:maxCount 1 ; sh:name "Description"@en ; sh:severity sh:Violation ] . cascade:RecordSummaryShape a sh:NodeShape ; sh:targetClass cascade:RecordSummary ; rdfs:label "Record Summary Shape"@en ; rdfs:comment "Validation constraints for per-domain record counts. Every count must be a single non-negative integer: a negative or repeated count silently corrupts any completeness check built on it."@en ; sh:property [ sh:path cascade:domain ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Domain"@en ; sh:message "Record summary must name exactly one domain"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:conditionCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Condition Count"@en ; sh:message "conditionCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:medicationCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Medication Count"@en ; sh:message "medicationCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:allergyCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Allergy Count"@en ; sh:message "allergyCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:labResultCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Lab Result Count"@en ; sh:message "labResultCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:immunizationCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Immunization Count"@en ; sh:message "immunizationCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:coverageCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Coverage Count"@en ; sh:message "coverageCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:supplementCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Supplement Count"@en ; sh:message "supplementCount must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; # Day counts are bounded above as well: a "days covered" figure larger than # a decade of daily readings is a unit error, not a long history. sh:property [ sh:path cascade:vitalSignDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Vital Sign Days"@en ; sh:message "vitalSignDays must be a single integer between 0 and 36500"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:heartRateDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Heart Rate Days"@en ; sh:message "heartRateDays must be a single integer between 0 and 36500"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:bloodPressureDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Blood Pressure Days"@en ; sh:message "bloodPressureDays must be a single integer between 0 and 36500"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:activityDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Activity Days"@en ; sh:message "activityDays must be a single integer between 0 and 36500"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:sleepDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Sleep Days"@en ; sh:message "sleepDays must be a single integer between 0 and 36500"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:dataProvenance ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:severity sh:Violation ] . cascade:InteractionScenarioShape a sh:NodeShape ; sh:targetClass cascade:InteractionScenario ; rdfs:label "Interaction Scenario Shape"@en ; rdfs:comment "Validation constraints for a flagged cross-provenance interaction. A scenario that names no resources is not actionable, so involvedResources is required."@en ; sh:property [ sh:path dct:title ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Title"@en ; sh:message "Interaction scenario must carry exactly one non-empty dct:title"@en ; sh:severity sh:Violation ] ; # REQUIRED: the resources to correlate. Without them the scenario states a # risk exists but gives a consumer nothing to check it against. sh:property [ sh:path cascade:involvedResources ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Involved Resources"@en ; sh:message "Interaction scenario must name exactly one list of involved resources"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:severity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("low" "moderate" "high" "critical") ; sh:name "Severity"@en ; sh:message "Interaction scenario severity must be low, moderate, high or critical"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:requiresCrossProvenance ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:name "Requires Cross-Provenance"@en ; sh:message "requiresCrossProvenance must be a single boolean"@en ; sh:severity sh:Violation ] . ## SHACL Shapes: health ## File: health.shapes.ttl @prefix sh: . @prefix health: . @prefix cascade: . @prefix clinical: . @prefix xsd: . @prefix rdfs: . @prefix fhir: . # ============================================================================ # Cascade Protocol — Health & Wellness Vocabulary SHACL Validation Shapes # ============================================================================ # Version: 1.4 (2026-08-14) # Validates: health:SelfReport, health:VO2MaxStatistics, health:HRVStatistics, # health:BPStatistics, health:MetricTrend, health:ActivitySnapshot, # health:SleepSnapshot, health:HealthProfile, # health:SocialHistoryRecord, # health:LabResultRecord, health:ConditionRecord, # health:AllergyRecord, health:ImmunizationRecord, # health:FamilyHistoryRecord, # health:DailyVitalReading, health:DailyActivitySnapshot, # health:DailySleepSnapshot # # SEVERITY LEVELS: # - sh:Violation = Must fix (data invalid without) - e.g., missing required stats # - sh:Warning = Should address (important for completeness) - e.g., missing period # - sh:Info = Nice to have (suggested enrichment) - e.g., optional context fields # # Validates against: health.ttl v2.6 (2026-08-08) # ============================================================================ # ============================================================================ # Shape 1: Self Report # ============================================================================ health:SelfReportShape a sh:NodeShape ; sh:targetClass health:SelfReport ; rdfs:label "Self Report Shape"@en ; rdfs:comment "Validation constraints for patient-reported daily check-ins and self-assessments. Ensures required timestamping, type classification, and completion tracking."@en ; # REQUIRED: reportDate (when the self-report was recorded) sh:property [ sh:path health:reportDate ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Report Date"@en ; sh:message "Self-report must have exactly one reportDate timestamp"@en ; sh:severity sh:Violation ] ; # REQUIRED: reportType (category of self-report) sh:property [ sh:path health:reportType ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Report Type"@en ; sh:message "Self-report must specify a reportType (e.g., medication_adherence, symptom_tracking, mood, sleep)"@en ; sh:severity sh:Violation ] ; # REQUIRED: completionStatus (whether report is complete) sh:property [ sh:path health:completionStatus ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("complete" "partial" "skipped") ; sh:name "Completion Status"@en ; sh:message "Self-report must have a completionStatus of complete, partial, or skipped"@en ; sh:severity sh:Violation ] ; # INFO: reportContext (what tracking plan this relates to) sh:property [ sh:path health:reportContext ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Report Context"@en ; sh:message "Consider adding reportContext to link this report to a tracking plan"@en ; sh:severity sh:Info ] ; # INFO: reportNotes (free-text patient notes) sh:property [ sh:path health:reportNotes ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Report Notes"@en ; sh:message "Consider adding reportNotes for additional patient context"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 2: VO2 Max Statistics # ============================================================================ health:VO2MaxStatisticsShape a sh:NodeShape ; sh:targetClass health:VO2MaxStatistics ; rdfs:label "VO2 Max Statistics Shape"@en ; rdfs:comment "Validation constraints for VO2 Max statistical summaries. Ensures core statistics, fitness classification, and trend analysis fields are present. Supports the dual-path UI pattern where rich statistics are preferred when available."@en ; # REQUIRED: vo2Mean (average VO2 Max over period) sh:property [ sh:path health:vo2Mean ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Mean VO2 Max"@en ; sh:message "VO2 Max statistics must have a mean value (mL/kg/min, >= 0)"@en ; sh:severity sh:Violation ] ; # REQUIRED: vo2SampleCount (number of measurements) sh:property [ sh:path health:vo2SampleCount ; sh:datatype xsd:integer ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 1 ; sh:name "VO2 Sample Count"@en ; sh:message "VO2 Max statistics must have at least 1 sample"@en ; sh:severity sh:Violation ] ; # REQUIRED: fitnessClassification (cardiorespiratory fitness level) sh:property [ sh:path health:fitnessClassification ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("veryPoor" "poor" "fair" "good" "excellent" "superior") ; sh:name "Fitness Classification"@en ; sh:message "VO2 Max statistics must have a fitnessClassification (veryPoor, poor, fair, good, excellent, or superior)"@en ; sh:severity sh:Violation ] ; # WARNING: periodStart (measurement window start) sh:property [ sh:path health:periodStart ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:name "Period Start"@en ; sh:message "VO2 Max statistics should specify the measurement period start date"@en ; sh:severity sh:Warning ] ; # WARNING: periodEnd (measurement window end) sh:property [ sh:path health:periodEnd ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:name "Period End"@en ; sh:message "VO2 Max statistics should specify the measurement period end date"@en ; sh:severity sh:Warning ] ; # INFO: vo2Min (lowest reading) sh:property [ sh:path health:vo2Min ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Minimum VO2 Max"@en ; sh:message "Consider including minimum VO2 Max for range analysis"@en ; sh:severity sh:Info ] ; # INFO: vo2MaxValue (highest reading) sh:property [ sh:path health:vo2MaxValue ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Maximum VO2 Max"@en ; sh:message "Consider including maximum VO2 Max for range analysis"@en ; sh:severity sh:Info ] ; # INFO: vo2TrendDirection (trend over period) sh:property [ sh:path health:vo2TrendDirection ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("improving" "declining" "stable" "unknown") ; sh:name "VO2 Trend Direction"@en ; sh:message "Consider including vo2TrendDirection for fitness trend analysis"@en ; sh:severity sh:Info ] ; # INFO: isSparseData (data quality indicator) sh:property [ sh:path health:isSparseData ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:name "Sparse Data Indicator"@en ; sh:message "Consider setting isSparseData to flag limited measurement periods"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 3: HRV Statistics # ============================================================================ health:HRVStatisticsShape a sh:NodeShape ; sh:targetClass health:HRVStatistics ; rdfs:label "HRV Statistics Shape"@en ; rdfs:comment "Validation constraints for Heart Rate Variability (SDNN) statistical summaries. Ensures core statistics and measurement period are present for trend analysis."@en ; # REQUIRED: hrvMean (average HRV over period) sh:property [ sh:path health:hrvMean ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Mean HRV"@en ; sh:message "HRV statistics must have a mean value (ms, >= 0)"@en ; sh:severity sh:Violation ] ; # REQUIRED: hrvSampleCount (number of readings) sh:property [ sh:path health:hrvSampleCount ; sh:datatype xsd:integer ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 1 ; sh:name "HRV Sample Count"@en ; sh:message "HRV statistics must have at least 1 sample"@en ; sh:severity sh:Violation ] ; # REQUIRED: periodStart (measurement window start) sh:property [ sh:path health:periodStart ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Period Start"@en ; sh:message "HRV statistics must specify the measurement period start date"@en ; sh:severity sh:Violation ] ; # REQUIRED: periodEnd (measurement window end) sh:property [ sh:path health:periodEnd ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Period End"@en ; sh:message "HRV statistics must specify the measurement period end date"@en ; sh:severity sh:Violation ] ; # INFO: hrvMedian (median HRV) sh:property [ sh:path health:hrvMedian ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Median HRV"@en ; sh:message "Consider including median HRV for distribution analysis"@en ; sh:severity sh:Info ] ; # INFO: hrvStdDev (standard deviation) sh:property [ sh:path health:hrvStdDev ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "HRV Standard Deviation"@en ; sh:message "Consider including HRV standard deviation for variability analysis"@en ; sh:severity sh:Info ] ; # INFO: hrvMin (lowest reading) sh:property [ sh:path health:hrvMin ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Minimum HRV"@en ; sh:message "Consider including minimum HRV for range analysis"@en ; sh:severity sh:Info ] ; # INFO: hrvMax (highest reading) sh:property [ sh:path health:hrvMax ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Maximum HRV"@en ; sh:message "Consider including maximum HRV for range analysis"@en ; sh:severity sh:Info ] ; # INFO: hrvTrendDirection (trend over period) sh:property [ sh:path health:hrvTrendDirection ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("improving" "declining" "stable" "unknown") ; sh:name "HRV Trend Direction"@en ; sh:message "Consider including hrvTrendDirection for autonomic nervous system trend analysis"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 4: Blood Pressure Statistics # ============================================================================ health:BPStatisticsShape a sh:NodeShape ; sh:targetClass health:BPStatistics ; rdfs:label "Blood Pressure Statistics Shape"@en ; rdfs:comment "Validation constraints for blood pressure statistical summaries. Ensures mean systolic/diastolic values and AHA category classification are present."@en ; # REQUIRED: bpMeanSystolic (average systolic BP) sh:property [ sh:path health:bpMeanSystolic ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 40 ; sh:maxInclusive 300 ; sh:name "Mean Systolic BP"@en ; sh:message "BP statistics must have a mean systolic value (mmHg, 40-300)"@en ; sh:severity sh:Violation ] ; # REQUIRED: bpMeanDiastolic (average diastolic BP) sh:property [ sh:path health:bpMeanDiastolic ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 20 ; sh:maxInclusive 200 ; sh:name "Mean Diastolic BP"@en ; sh:message "BP statistics must have a mean diastolic value (mmHg, 20-200)"@en ; sh:severity sh:Violation ] ; # REQUIRED: bpCategory (AHA classification) sh:property [ sh:path health:bpCategory ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("normal" "elevated" "hypertension_stage1" "hypertension_stage2" "hypertensive_crisis") ; sh:name "BP Category"@en ; sh:message "BP statistics must have an AHA category (normal, elevated, hypertension_stage1, hypertension_stage2, or hypertensive_crisis)"@en ; sh:severity sh:Violation ] ; # WARNING: bpSampleCount (number of readings) sh:property [ sh:path health:bpSampleCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 1 ; sh:name "BP Sample Count"@en ; sh:message "BP statistics should include a sample count for data quality assessment"@en ; sh:severity sh:Warning ] ; # INFO: bpMinSystolic (lowest systolic reading) sh:property [ sh:path health:bpMinSystolic ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Minimum Systolic BP"@en ; sh:message "Consider including minimum systolic BP for range analysis"@en ; sh:severity sh:Info ] ; # INFO: bpMaxSystolic (highest systolic reading) sh:property [ sh:path health:bpMaxSystolic ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Maximum Systolic BP"@en ; sh:message "Consider including maximum systolic BP for range analysis"@en ; sh:severity sh:Info ] ; # INFO: bpMinDiastolic (lowest diastolic reading) sh:property [ sh:path health:bpMinDiastolic ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Minimum Diastolic BP"@en ; sh:message "Consider including minimum diastolic BP for range analysis"@en ; sh:severity sh:Info ] ; # INFO: bpMaxDiastolic (highest diastolic reading) sh:property [ sh:path health:bpMaxDiastolic ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:name "Maximum Diastolic BP"@en ; sh:message "Consider including maximum diastolic BP for range analysis"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 5: Metric Trend # ============================================================================ health:MetricTrendShape a sh:NodeShape ; sh:targetClass health:MetricTrend ; rdfs:label "Metric Trend Shape"@en ; rdfs:comment "Validation constraints for time-bounded wellness metric trends. Ensures trend direction is present and encourages confidence and period metadata for proper interpretation."@en ; # REQUIRED: trendDirection (direction of change) sh:property [ sh:path health:trendDirection ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("increasing" "decreasing" "stable" "insufficient_data") ; sh:name "Trend Direction"@en ; sh:message "Metric trend must have a direction (increasing, decreasing, stable, or insufficient_data)"@en ; sh:severity sh:Violation ] ; # WARNING: trendConfidence (data quality indicator) sh:property [ sh:path health:trendConfidence ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("high" "medium" "low") ; sh:name "Trend Confidence"@en ; sh:message "Metric trend should include confidence level (high >30d, medium 14-30d, low <14d)"@en ; sh:severity sh:Warning ] ; # WARNING: trendPeriodStart (observation window start) sh:property [ sh:path health:trendPeriodStart ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:name "Trend Period Start"@en ; sh:message "Metric trend should specify the observation period start date"@en ; sh:severity sh:Warning ] ; # WARNING: trendPeriodEnd (observation window end) sh:property [ sh:path health:trendPeriodEnd ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:name "Trend Period End"@en ; sh:message "Metric trend should specify the observation period end date"@en ; sh:severity sh:Warning ] ; # INFO: trendMagnitude (percentage change) sh:property [ sh:path health:trendMagnitude ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:name "Trend Magnitude"@en ; sh:message "Consider including trendMagnitude (percentage change) for quantitative trend analysis"@en ; sh:severity sh:Info ] ; # INFO: trendBaselineValue (reference value) sh:property [ sh:path health:trendBaselineValue ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:name "Baseline Value"@en ; sh:message "Consider including trendBaselineValue for comparative analysis"@en ; sh:severity sh:Info ] ; # INFO: trendCurrentValue (current period average) sh:property [ sh:path health:trendCurrentValue ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:name "Current Value"@en ; sh:message "Consider including trendCurrentValue for comparative analysis"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 6: Activity Snapshot # ============================================================================ health:ActivitySnapshotShape a sh:NodeShape ; sh:targetClass health:ActivitySnapshot ; rdfs:label "Activity Snapshot Shape"@en ; rdfs:comment "Validation constraints for aggregated activity metrics over a measurement period (typically 7 days). All fields are optional since activity data availability depends on device capabilities."@en ; # WARNING: averageDailySteps (primary activity metric) sh:property [ sh:path health:averageDailySteps ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Average Daily Steps"@en ; sh:message "Activity snapshot should include average daily steps as a primary activity metric"@en ; sh:severity sh:Warning ] ; # INFO: exerciseMinutesWeekly (exercise activity) sh:property [ sh:path health:exerciseMinutesWeekly ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Exercise Minutes (Weekly)"@en ; sh:message "Consider including weekly exercise minutes for fitness context"@en ; sh:severity sh:Info ] ; # INFO: standHoursDaily (standing activity) sh:property [ sh:path health:standHoursDaily ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 24 ; sh:name "Stand Hours (Daily)"@en ; sh:message "Consider including daily stand hours for sedentary behavior tracking"@en ; sh:severity sh:Info ] ; # INFO: activeEnergyBurnedKcal (energy expenditure) sh:property [ sh:path health:activeEnergyBurnedKcal ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Active Energy Burned (kcal)"@en ; sh:message "Consider including active energy burned for caloric expenditure tracking"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 7: Sleep Snapshot # ============================================================================ health:SleepSnapshotShape a sh:NodeShape ; sh:targetClass health:SleepSnapshot ; rdfs:label "Sleep Snapshot Shape"@en ; rdfs:comment "Validation constraints for aggregated sleep metrics over a measurement period (typically 7 days). Sleep data availability depends on device capabilities and user behavior."@en ; # WARNING: averageDurationHours (primary sleep metric) sh:property [ sh:path health:averageDurationHours ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 24 ; sh:name "Average Sleep Duration (hours)"@en ; sh:message "Sleep snapshot should include average duration as a primary sleep metric"@en ; sh:severity sh:Warning ] ; # INFO: sleepQuality (qualitative assessment) sh:property [ sh:path health:sleepQuality ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Sleep Quality"@en ; sh:message "Consider including sleep quality assessment for comprehensive sleep analysis"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape 8: Health Profile (Top-level Container) # ============================================================================ health:HealthProfileShape a sh:NodeShape ; sh:targetClass health:HealthProfile ; # The six wellness containers are declared rdfs:subClassOf health:HealthProfile # in health.ttl v2.5, and SHACL sh:targetClass is subclass-aware. But that # aspect of targeting is evaluated against the DATA graph (SHACL 2.1.3.1 # defines "SHACL instance of" as rdf:type/rdfs:subClassOf* in the data # graph), so it only fires for a validator that loads the vocabulary # alongside the data. A validator that loads shapes only -- which is the # common case, and is what the reference implementation does -- would see # no target and check nothing, reproducing the vacuous PASS this release # exists to remove. # # These six are therefore named explicitly as well. The subclass axioms # remain the semantic statement; these make it effective today. sh:targetClass health:ActivityData ; sh:targetClass health:SleepData ; sh:targetClass health:HeartRateData ; sh:targetClass health:BloodPressureData ; sh:targetClass health:HRVData ; sh:targetClass health:BodyMeasurements ; rdfs:label "Health Profile Shape"@en ; rdfs:comment "Validation constraints for the top-level wellness profile aggregating vital signs, body measurements, activity, and sleep data from consumer devices. Stored at /wellness/ in the Cascade Pod."@en ; # WARNING: bloodType (ABO blood group characteristic) sh:property [ sh:path health:bloodType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("aPositive" "aNegative" "bPositive" "bNegative" "abPositive" "abNegative" "oPositive" "oNegative") ; sh:name "Blood Type"@en ; sh:message "Blood type should be a valid ABO/Rh value if provided"@en ; sh:severity sh:Warning ] ; # INFO: restingHeartRateHistory (90-day vital trend) sh:property [ sh:path health:restingHeartRateHistory ; sh:class health:DailyVitalReading ; sh:name "Resting Heart Rate History"@en ; sh:message "Consider populating resting heart rate history for cardiac trend analysis"@en ; sh:severity sh:Info ] ; # INFO: walkingHeartRateHistory (90-day vital trend) sh:property [ sh:path health:walkingHeartRateHistory ; sh:class health:DailyVitalReading ; sh:name "Walking Heart Rate History"@en ; sh:message "Consider populating walking heart rate history for activity-related cardiac trends"@en ; sh:severity sh:Info ] ; # INFO: hrvHistory (7-day HRV readings) sh:property [ sh:path health:hrvHistory ; sh:class health:HRVReading ; sh:name "HRV History"@en ; sh:message "Consider populating HRV history for autonomic nervous system analysis"@en ; sh:severity sh:Info ] ; # INFO: bloodPressureHistory (30-day BP readings) sh:property [ sh:path health:bloodPressureHistory ; sh:class health:BloodPressureReading ; sh:name "Blood Pressure History"@en ; sh:message "Consider populating blood pressure history for cardiovascular trend analysis"@en ; sh:severity sh:Info ] ; # INFO: vo2MaxHistory (180-day VO2 Max readings) sh:property [ sh:path health:vo2MaxHistory ; sh:class health:VitalSignReading ; sh:name "VO2 Max History"@en ; sh:message "Consider populating VO2 Max history for fitness trend analysis"@en ; sh:severity sh:Info ] ; # INFO: bodyMassHistory (30-day weight readings) sh:property [ sh:path health:bodyMassHistory ; sh:class health:DailyVitalReading ; sh:name "Body Mass History"@en ; sh:message "Consider populating body mass history for weight trend analysis"@en ; sh:severity sh:Info ] ; # INFO: dailyActivityHistory (90-day activity snapshots) sh:property [ sh:path health:dailyActivityHistory ; sh:class health:DailyActivitySnapshot ; sh:name "Daily Activity History"@en ; sh:message "Consider populating daily activity history for activity trend analysis"@en ; sh:severity sh:Info ] ; # INFO: dailySleepHistory (90-day sleep snapshots) sh:property [ sh:path health:dailySleepHistory ; sh:class health:DailySleepSnapshot ; sh:name "Daily Sleep History"@en ; sh:message "Consider populating daily sleep history for sleep pattern analysis"@en ; sh:severity sh:Info ] ; # INFO: hasHRVStatistics (link to HRV statistical summary) sh:property [ sh:path health:hasHRVStatistics ; sh:class health:HRVStatistics ; sh:maxCount 1 ; sh:node health:HRVStatisticsShape ; sh:name "HRV Statistics"@en ; sh:message "Consider linking HRV statistics for heart rate variability summary"@en ; sh:severity sh:Info ] ; # INFO: hasBPStatistics (link to BP statistical summary) sh:property [ sh:path health:hasBPStatistics ; sh:class health:BPStatistics ; sh:maxCount 1 ; sh:node health:BPStatisticsShape ; sh:name "BP Statistics"@en ; sh:message "Consider linking BP statistics for blood pressure summary"@en ; sh:severity sh:Info ] . # ============================================================================ # Shape: SocialHistoryRecord (v2.4) # ============================================================================ health:SocialHistoryRecordShape a sh:NodeShape ; sh:targetClass health:SocialHistoryRecord ; rdfs:label "Social History Record Shape"@en ; sh:property [ sh:path cascade:sourceSystem ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Source System"@en ; sh:message "Source system attribution is recommended for social history records"@en ] ; sh:property [ sh:path health:smokingStatus ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Smoking Status"@en ; sh:message "Smoking status is a key social history field"@en ] . # ============================================================================ # Changelog # ============================================================================ # # Version 1.4 (2026-08-14) — health v2.7 # - health:interpretation: the sh:in list gains the fourteen data-absent-reason # codes it did not already carry, so the list is now the 49 selectable HL7 v3 # ObservationInterpretation codes, ALL FIFTEEN codes of # http://terminology.hl7.org/CodeSystem/data-absent-reason, and the ten # retained v2.5 words: 74 values. v2.6 accepted "unknown" alone, which made # every reason for an absent interpretation identical. # - NEW: a property shape for health:interpretationSourceCode. Single string, # no value set and no pattern, by design. # - No interpretation constraint was added to DailyVitalReadingShape. See the # v2.7 changelog note in health.ttl: health:interpretation's domain is # health:LabResultRecord and health:DailyVitalReading carries no # interpretation predicate, so the constraint would be vacuous. The vital # binding is clinical:VitalSignShape in clinical v1.15. # - Strictly widening: every graph valid under v1.3 is valid under v1.4. # # Version 1.3 (2026-08-08) — health v2.6 # - health:interpretation: the sh:in list is now the 49 selectable codes of the # HL7 v3 ObservationInterpretation code system, # http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation (3.0.0), # plus the data-absent-reason code "unknown" and the ten retained v2.5 words. # The previous list was a five-member set with no standards basis, so a lab # reporting a susceptibility, detection, reactivity or change result — all # conformant FHIR — failed validation. # - health:labCategory: sh:maxCount 1 removed. FHIR R4 Observation.category is # 0..*. # - health:testCode, health:icd10Code, health:snomedCode: sh:maxCount 1 removed. # FHIR R4 CodeableConcept.coding is 0..*, and dual-coded problem-list entries # and multi-coding lab observations are ordinary EHR output. # - No sh:pattern added to any code property; see the note at # health:icd10Code for why a lexical pattern would reject valid codes. # - Strictly widening: every graph valid under v1.2 is valid under v1.3. # # Version 1.2 (2026-08-03) # - Added 5 record shapes for the classes defined in health v2.5: # LabResultRecordShape, ConditionRecordShape, AllergyRecordShape, # ImmunizationRecordShape, FamilyHistoryRecordShape. Before this version # these classes had no sh:targetClass anywhere, so validating a lab result, # condition, allergy, immunization or family history record checked nothing # and returned PASS. # - Added 3 shapes for classes that have existed since health v2.0 but were # never targeted: DailyVitalReadingShape, DailyActivitySnapshotShape, # DailySleepSnapshotShape. # - Constraint sets lifted from the corresponding clinical:* shapes and # checked against FHIR R4. Where FHIR names a required value set the sh:in # list is that value set verbatim. # - sh:Violation severity on all required fields. # - 29 conformance fixtures (7 lab, 7 condition, 6 allergy, 3 immunization, # 3 family history) become executable against real constraints. # # Version 1.1 (2026-03-27) # - Added SocialHistoryRecordShape for health:SocialHistoryRecord (health v2.4) # # Version 1.0 (2026-02-18) # - Initial release of Health & Wellness SHACL shapes (Phase 4) # - 8 shapes covering all major health.ttl v2.2 classes: # - SelfReportShape: Patient-reported check-in validation (3 required, 2 info) # - VO2MaxStatisticsShape: Fitness statistics validation (3 required, 2 warning, 4 info) # - HRVStatisticsShape: HRV statistics validation (4 required, 5 info) # - BPStatisticsShape: Blood pressure statistics validation (3 required, 1 warning, 4 info) # - MetricTrendShape: Wellness trend validation (1 required, 3 warning, 3 info) # - ActivitySnapshotShape: Activity metrics validation (1 warning, 3 info) # - SleepSnapshotShape: Sleep metrics validation (1 warning, 1 info) # - HealthProfileShape: Top-level container validation (1 warning, 10 info) # - Severity levels follow established pattern: Violation/Warning/Info # - sh:in constraints match enumerated values from health.ttl ontology # - Range constraints (minInclusive/maxInclusive) reflect physiological bounds # # ============================================================================ # Clinical Record Shapes (v1.2 — health v2.5) # ============================================================================ # # Constraint sets are LIFTED from the corresponding clinical:* shapes in # clinical.shapes.ttl (AllergyShape :526, LabResultShape :607, ConditionShape # :685, ImmunizationShape :764) so that the two spellings of the same record # are held to the same standard, then checked against the FHIR R4 resource # definitions. Where FHIR names a required value set, the sh:in list below is # that value set verbatim: # # health:allergyCategory AllergyIntoleranceCategory food | medication | environment | biologic # health:allergySeverity AllergyIntoleranceSeverity mild | moderate | severe # health:status (condition) ConditionClinicalStatusCodes active | recurrence | relapse | inactive | remission | resolved # health:status (immunization) ImmunizationStatusCodes completed | entered-in-error | not-done # # health:status is deliberately constrained per-shape rather than on the # property: the permitted values differ by record class and a single global # sh:in would be wrong for both. # # Severity is sh:Violation on every required field. A Warning-severity # constraint on a required field reproduces the problem these shapes exist to # fix (a PASS that means nothing) with extra steps. # # CONSEQUENCE, STATED PLAINLY: before this release, validating a lab result, # condition, allergy, immunization or family-history record returned PASS # because no shape targeted it and nothing was checked. Those records are now # actually checked. A validation failure appearing after upgrade means the # validator improved, not that the data got worse. # ============================================================================ # Shape 9: Lab Result Record # ============================================================================ health:LabResultRecordShape a sh:NodeShape ; sh:targetClass health:LabResultRecord ; rdfs:label "Lab Result Record Shape"@en ; rdfs:comment "Validation constraints for laboratory result records. Lifted from clinical:LabResultShape and checked against FHIR R4 Observation."@en ; # REQUIRED: testName sh:property [ sh:path health:testName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Test Name"@en ; sh:message "Lab result must have exactly one non-empty testName"@en ; sh:severity sh:Violation ] ; # REQUIRED cardinality: resultValue is at most one. # A single subject asserting two resultValues is the failure mode this # constraint exists for: two same-day readings of the same analyte merged # onto one record produce a subject with both values, which a consumer # then reads as the single string "95, 310". Unconstrained, that passes. sh:property [ sh:path health:resultValue ; sh:maxCount 1 ; sh:name "Result Value"@en ; sh:message "Lab result must not carry more than one resultValue; two readings of the same analyte are two records"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:resultUnit ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Result Unit"@en ; sh:message "Lab result must not carry more than one resultUnit"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:referenceRange ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Reference Range"@en ; sh:severity sh:Violation ] ; # Interpretation is bound to the HL7 v3 ObservationInterpretation code # system, which is the code system FHIR R4 binds Observation.interpretation # to. The previous list was a five-member set Cascade invented; laboratories # report susceptibility (S/I/R), detection (POS/NEG/DET/ND/IND), reactivity # (RR/WR/NR) and change (B/D/U/W) results that have no member of that set, # so a conformant export failed validation for being conformant. # # The list below is the 49 SELECTABLE codes of # http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation # (version 3.0.0), verbatim and in the code system's own order. The eight # abstract concepts (_GeneticObservationInterpretation, # _ObservationInterpretationChange, _ObservationInterpretationExceptions, # _ObservationInterpretationNormality, # _ObservationInterpretationSusceptibility, ObservationInterpretationDetection, # ObservationInterpretationExpectation, ReactivityObservationInterpretation) # are marked notSelectable there and are deliberately absent here: they are # hierarchy nodes, not values. Codes the code system marks deprecated # (Carrier, AC, QCF, TOX, MS, VS, HM, OBX, H>, L<) ARE accepted, because a # deprecated code is still a defined code and historical results carry them. # # Then two additions that are not from that code system and are named here # rather than left to be inferred: # - ALL FIFTEEN codes of # http://terminology.hl7.org/CodeSystem/data-absent-reason (value set # http://hl7.org/fhir/ValueSet/data-absent-reason). v2.6 admitted only # "unknown" here, for the very common source Observation that carried no # interpretation at all. health v2.7 admits the rest, because a source # that says WHY the element is absent is saying something, and one # accepted code made every reason identical: a C-CDA # (nobody asked) became # indistinguishable from nullFlavor="ASKU" (asked, did not know) and # from nullFlavor="NAV" (expected later). Putting a data-absent-reason # coding INTO the CodeableConcept that is missing is FHIR's own idiom, # not a Cascade convention, so this is deference and not invention. The # nullFlavor-to-data-absent-reason mapping an importer must implement is # stated once, on cascade:dataAbsentReason in core v3.6. # - the ten lower- and title-case English words of the previous enum, # retained so that data already written against health v2.5 keeps # validating. They are NOT recommended for new writes. # # A source code in NEITHER value set is not forced in here and is not # dropped: it goes verbatim on health:interpretationSourceCode (v2.7), and # the producer writes its nearest ratified equivalent here. sh:property [ sh:path health:interpretation ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("EX" "HM" "OBX" "CAR" "Carrier" "B" "D" "U" "W" "<" ">" "AC" "IE" "QCF" "TOX" "A" "N" "I" "MS" "NCL" "NS" "R" "S" "VS" "AA" "H" "L" "HH" "LL" "HX" "LX" "H>" "HU" "E" "L<" "LU" "ND" "IND" "NEG" "POS" "EXP" "UNE" "DET" "SYN-R" "NR" "RR" "WR" "SDD" "SYN-S" "unknown" "asked-unknown" "temp-unknown" "not-asked" "asked-declined" "masked" "not-applicable" "unsupported" "as-text" "error" "not-a-number" "negative-infinity" "positive-infinity" "not-performed" "not-permitted" "normal" "high" "low" "abnormal" "critical" "Normal" "High" "Low" "Abnormal" "Critical") ; sh:name "Interpretation"@en ; sh:message "Interpretation must be a code from the HL7 v3 ObservationInterpretation code system (http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation), a code from http://terminology.hl7.org/CodeSystem/data-absent-reason, or one of the retained health v2.5 words. A source code in none of those is carried verbatim on health:interpretationSourceCode."@en ; sh:severity sh:Violation ] ; # health:interpretationSourceCode (v2.7) is deliberately unconstrained apart # from being a single string. It carries a source code that is a member of # no bound value set, verbatim; a value set or a pattern here would recreate # exactly the loss the property exists to prevent. sh:property [ sh:path health:interpretationSourceCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Interpretation Source Code"@en ; sh:message "Interpretation source code must be a single string: the source's verbatim code, unmodified"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:performedDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Performed Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Performed Date"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:reportedDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Reported Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Reported Date"@en ; sh:severity sh:Violation ] ; # Multi-valued: FHIR R4 CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept), and an # Observation.code routinely carries more than one LOINC coding for the same # test. Importers emit one health:testCode per coding, so sh:maxCount 1 # rejected the record for preserving what the source sent. No cardinality # ceiling; each value is still a single code. sh:property [ sh:path health:testCode ; sh:name "Test Code (LOINC)"@en ; sh:severity sh:Violation ] ; # Multi-valued: FHIR R4 Observation.category is 0..* # (https://hl7.org/fhir/R4/observation-definitions.html#Observation.category). # Real exports categorise one result several ways at once (for example a # department grouping alongside "laboratory"), so a single-valued constraint # here contradicted the resource definition the data comes from. sh:property [ sh:path health:labCategory ; sh:datatype xsd:string ; sh:name "Lab Category"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:specimenType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Specimen Type"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:orderingProvider ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Ordering Provider"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:performingLab ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Performing Laboratory"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:sourceRecordId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Source Record Identifier"@en ; sh:severity sh:Violation ] ; # REQUIRED: provenance sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Lab result must declare exactly one valid cascade:dataProvenance"@en ; sh:severity sh:Violation ] ; # REQUIRED: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Lab result must declare exactly one schemaVersion in major.minor form"@en ; sh:severity sh:Violation ] . # ============================================================================ # Shape 10: Condition Record # ============================================================================ health:ConditionRecordShape a sh:NodeShape ; sh:targetClass health:ConditionRecord ; rdfs:label "Condition Record Shape"@en ; rdfs:comment "Validation constraints for condition and problem-list records. Lifted from clinical:ConditionShape and checked against FHIR R4 Condition."@en ; # REQUIRED: conditionName sh:property [ sh:path health:conditionName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Condition Name"@en ; sh:message "Condition must have exactly one non-empty conditionName"@en ; sh:severity sh:Violation ] ; # status: FHIR R4 ConditionClinicalStatusCodes sh:property [ sh:path health:status ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("active" "recurrence" "relapse" "inactive" "remission" "resolved") ; sh:name "Clinical Status"@en ; sh:message "Condition status must be one of the FHIR R4 ConditionClinicalStatusCodes: active, recurrence, relapse, inactive, remission, resolved"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:onsetDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Onset Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Onset Date"@en ; sh:severity sh:Violation ] ; # Multi-valued for the same reason as health:testCode above: FHIR R4 # CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). A Condition.code # commonly carries two ICD-10-CM codings, or an ICD-10-CM coding beside a # SNOMED CT one, and dual coding is the normal shape of EHR problem lists # rather than an anomaly. # # NOTE ON FORMAT: neither of these carries an sh:pattern, and that is # deliberate. ICD-10-CM permits a letter in any character position # (categories such as C4A and M1A are ordinary codes) and SNOMED CT # identifiers are 6-18 digit integers, so a naive pattern here would reject # valid codes. Format is left to the code system. sh:property [ sh:path health:icd10Code ; sh:name "ICD-10-CM Code"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:snomedCode ; sh:name "SNOMED CT Code"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:conditionClass ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Condition Class"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:monitoredVitalSigns ; sh:maxCount 1 ; sh:name "Monitored Vital Signs"@en ; sh:message "Monitored vital signs must be a single rdf:List, not repeated"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:sourceRecordId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Source Record Identifier"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Condition must declare exactly one valid cascade:dataProvenance"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Condition must declare exactly one schemaVersion in major.minor form"@en ; sh:severity sh:Violation ] . # ============================================================================ # Shape 11: Allergy Record # ============================================================================ health:AllergyRecordShape a sh:NodeShape ; sh:targetClass health:AllergyRecord ; rdfs:label "Allergy Record Shape"@en ; rdfs:comment "Validation constraints for allergy and intolerance records. Lifted from clinical:AllergyShape and checked against FHIR R4 AllergyIntolerance."@en ; # REQUIRED: allergen. An allergy record without an allergen carries no # safety information at all, which is worse than having no record. sh:property [ sh:path health:allergen ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Allergen"@en ; sh:message "Allergy must specify exactly one non-empty allergen"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:reaction ; sh:datatype xsd:string ; sh:name "Reaction"@en ; sh:severity sh:Violation ] ; # FHIR R4 AllergyIntoleranceSeverity sh:property [ sh:path health:allergySeverity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("mild" "moderate" "severe") ; sh:name "Allergy Severity"@en ; sh:message "Allergy severity must be one of the FHIR R4 AllergyIntoleranceSeverity codes: mild, moderate, severe"@en ; sh:severity sh:Violation ] ; # FHIR R4 AllergyIntoleranceCategory sh:property [ sh:path health:allergyCategory ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("food" "medication" "environment" "biologic") ; sh:name "Allergy Category"@en ; sh:message "Allergy category must be one of the FHIR R4 AllergyIntoleranceCategory codes: food, medication, environment, biologic"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:onsetDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Onset Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Onset Date"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:sourceRecordId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Source Record Identifier"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Allergy must declare exactly one valid cascade:dataProvenance"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Allergy must declare exactly one schemaVersion in major.minor form"@en ; sh:severity sh:Violation ] . # ============================================================================ # Shape 12: Immunization Record # ============================================================================ health:ImmunizationRecordShape a sh:NodeShape ; sh:targetClass health:ImmunizationRecord ; rdfs:label "Immunization Record Shape"@en ; rdfs:comment "Validation constraints for immunization records. Lifted from clinical:ImmunizationShape and checked against FHIR R4 Immunization."@en ; # REQUIRED: vaccineName sh:property [ sh:path health:vaccineName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Vaccine Name"@en ; sh:message "Immunization must have exactly one non-empty vaccineName"@en ; sh:severity sh:Violation ] ; # NOT required here although FHIR R4 Immunization.occurrence is 1..1. # clinical:ImmunizationShape constrains no date at all, so requiring one # would be a new constraint rather than a lifted one, and would newly fail # existing records. Promoting this to sh:minCount 1 is a candidate for a # release that is explicitly flagged as tightening. sh:property [ sh:path health:administrationDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Administration Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Administration Date"@en ; sh:severity sh:Violation ] ; # FHIR R4 ImmunizationStatusCodes sh:property [ sh:path health:status ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("completed" "entered-in-error" "not-done") ; sh:name "Status"@en ; sh:message "Immunization status must be one of the FHIR R4 ImmunizationStatusCodes: completed, entered-in-error, not-done"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:vaccineCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Vaccine Code"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:manufacturer ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Manufacturer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:lotNumber ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Lot Number"@en ; sh:message "Immunization must not carry more than one lotNumber; a lot number identifies a single administered dose"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:doseQuantity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Dose Quantity"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:route ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Route"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:site ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Administration Site"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:administeringProvider ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Administering Provider"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:administeringLocation ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Administering Location"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:sourceRecordId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Source Record Identifier"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Immunization must declare exactly one valid cascade:dataProvenance"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Immunization must declare exactly one schemaVersion in major.minor form"@en ; sh:severity sh:Violation ] . # ============================================================================ # Shape 13: Family History Record # ============================================================================ # # There is no clinical:FamilyHistory class and therefore no shape to lift from. # Constraints here are derived directly from FHIR R4 FamilyMemberHistory, whose # relationship element is 1..1 and whose condition.code is required within each # condition entry. The provenance and schemaVersion blocks follow the same # pattern as every other record shape. health:FamilyHistoryRecordShape a sh:NodeShape ; sh:targetClass health:FamilyHistoryRecord ; rdfs:label "Family History Record Shape"@en ; rdfs:comment "Validation constraints for family history records. Derived from FHIR R4 FamilyMemberHistory."@en ; # REQUIRED: conditionName (FHIR condition.code is required per entry) sh:property [ sh:path health:conditionName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Condition Name"@en ; sh:message "Family history record must name exactly one condition"@en ; sh:severity sh:Violation ] ; # REQUIRED: relationship (FHIR FamilyMemberHistory.relationship is 1..1). # A condition with no stated relative is not family history. sh:property [ sh:path clinical:relationship ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Relationship"@en ; sh:message "Family history record must state exactly one relationship to the patient"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:onsetAge ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 130 ; sh:name "Onset Age"@en ; sh:message "Onset age must be a single integer between 0 and 130"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:sourceRecordId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Source Record Identifier"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Family history record must declare exactly one valid cascade:dataProvenance"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Family history record must declare exactly one schemaVersion in major.minor form"@en ; sh:severity sh:Violation ] . # ============================================================================ # Daily Snapshot Shapes (v1.2 — health v2.5) # ============================================================================ # # These three classes were defined in health v2.0 and have been emitted ever # since, but no shape targeted them, so every reading in every history # container validated vacuously. # # health:DailyVitalReading is emitted in TWO forms that do not share their # value and date predicates: # # Pattern A (HealthProfileSerializer): health:value, health:unit, health:date # Pattern B (reference pod): fhir:valueQuantity, cascade:date # # Both are live. The shape therefore requires a date and a value through # sh:or rather than naming one spelling and failing the other. Reconciling the # two serializations is a separate change; constraining one of them here would # have silently invalidated the other. health:DailyVitalReadingShape a sh:NodeShape ; sh:targetClass health:DailyVitalReading ; rdfs:label "Daily Vital Reading Shape"@en ; rdfs:comment "Validation constraints for a single day's aggregated vital sign reading inside a history container."@en ; # REQUIRED: a timestamp, in either spelling. A reading with no date cannot # be placed in the time series it lives in. sh:or ( [ sh:property [ sh:path cascade:date ; sh:minCount 1 ] ] [ sh:property [ sh:path health:date ; sh:minCount 1 ] ] ) ; sh:message "Daily vital reading must carry a timestamp as either cascade:date or health:date"@en ; sh:severity sh:Violation ; sh:property [ sh:path cascade:date ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:name "Date"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:date ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:name "Date"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:sampleCount ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Sample Count"@en ; sh:message "Sample count must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:value ; sh:maxCount 1 ; sh:name "Value"@en ; sh:message "Daily vital reading must not carry more than one value"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:unit ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Unit"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path cascade:loincCode ; sh:maxCount 1 ; sh:name "LOINC Code"@en ; sh:severity sh:Violation ] . health:DailyActivitySnapshotShape a sh:NodeShape ; sh:targetClass health:DailyActivitySnapshot ; rdfs:label "Daily Activity Snapshot Shape"@en ; rdfs:comment "Validation constraints for a single day's activity metrics."@en ; sh:property [ sh:path cascade:date ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Date"@en ; sh:message "Daily activity snapshot must carry exactly one cascade:date"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:steps ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Steps"@en ; sh:message "Steps must be a single non-negative integer"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:activeEnergyKcal ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Active Energy (kcal)"@en ; sh:message "Active energy must be a single non-negative decimal"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:exerciseMinutes ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 1440 ; sh:name "Exercise Minutes"@en ; sh:message "Exercise minutes must be a single integer between 0 and 1440"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:standHours ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 24 ; sh:name "Stand Hours"@en ; sh:message "Stand hours must be a single integer between 0 and 24"@en ; sh:severity sh:Violation ] . health:DailySleepSnapshotShape a sh:NodeShape ; sh:targetClass health:DailySleepSnapshot ; rdfs:label "Daily Sleep Snapshot Shape"@en ; rdfs:comment "Validation constraints for a single night's sleep metrics."@en ; sh:property [ sh:path cascade:date ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Date"@en ; sh:message "Daily sleep snapshot must carry exactly one cascade:date"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path health:durationHours ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 24 ; sh:name "Sleep Duration (hours)"@en ; sh:message "Sleep duration must be a single decimal between 0 and 24 hours"@en ; sh:severity sh:Violation ] ; # No sh:datatype: health:sleepQuality is declared owl:DatatypeProperty with # rdfs:range xsd:string in health.ttl, but every known emitter writes an # IRI. Asserting either form here would contradict something real, so the # shape constrains cardinality and the permitted individuals only. sh:property [ sh:path health:sleepQuality ; sh:maxCount 1 ; sh:in (health:Excellent health:Good health:Fair health:Poor) ; sh:name "Sleep Quality"@en ; sh:message "Sleep quality must be one of health:Excellent, health:Good, health:Fair, health:Poor"@en ; sh:severity sh:Violation ] . ## SHACL Shapes: clinical ## File: clinical.shapes.ttl @prefix sh: . @prefix clinical: . @prefix cascade: . @prefix health: . @prefix xsd: . @prefix rdfs: . # ============================================================================ # SHACL Shapes for Cascade Protocol Clinical Documents # ============================================================================ # # These shapes validate clinical documents imported from EHR systems via # Apple HealthKit. Shapes accept multiple provenance values to support # different data sources (EHR, device, patient-reported). # # Changelog: # v1.15 (2026-08-14): Check what was unchecked, keep what was being dropped, and # stop failing records that carry the thing they are failing for missing. # Additive and strictly widening: no graph that validated under v1.14 stops # validating, and the only new finding anywhere is at sh:Warning. # # 1. clinical:VitalSignShape's clinical:interpretation is bound to the same # value set the lab shapes use, at sh:Warning. v1.14 put the sh:in on the # LAB shapes only, so the identical predicate was checked on a lab result # and unchecked on a vital sign. sh:Warning first and sh:Violation in a # later version is the ratchet core v3.5 wrote down for a value that # existing data carries. See the long note above the property. # 2. Both interpretation value sets gain the fourteen data-absent-reason # codes they did not carry. v1.14 accepted "unknown" alone, so every # reason for an absent interpretation was the same reason; C-CDA # nullFlavor NASK, ASKU and NAV are three different clinical facts. # Still byte-identical to health:interpretation. # 3. NEW property shapes for clinical:interpretationSourceCode on the lab # and vital shapes: a single string, no value set and no pattern, holding # a source code that is in neither bound value set, verbatim. # 4. clinical:ProcedureShape's name requirement becomes an sh:or over # clinical:procedureName and health:procedureName, plus the new # warning-severity clinical:ProcedureNameSpellingShape. A C-CDA import # path writes the name to the health: spelling on records typed # clinical:Procedure, so every converted procedure failed the name # requirement while carrying a name, on a predicate no shape targeted. # This is a MIGRATION WINDOW and both halves are removed together in a # later version; the rationale is on clinical:procedureName in # clinical.ttl. # # v1.14 (2026-08-08): Align to ratified standards; add the missing Encounter # shape. Shapes-only, and strictly widening apart from the new shape: every # graph that validated before still validates. # # Motivation: real-world Epic FHIR exports, and C-CDA documents through the # same pipeline, fail validation on records that are correct at source. # # 1. clinical:interpretation is now bound to the HL7 v3 # ObservationInterpretation code system, # http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation # (version 3.0.0) — 49 selectable codes, abstract concepts excluded — # plus the data-absent-reason code "unknown" # (http://terminology.hl7.org/CodeSystem/data-absent-reason) and the ten # retained legacy words. The previous five-member list was invented here # and had no standards basis. Kept identical to health:interpretation. # 2. clinical:snomedCode (4 shapes) and clinical:icd10Code are now # multi-valued: FHIR R4 CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). # 3. clinical:cptCode accepted only five digits, which is Category I only. # The AMA CPT code set also defines Category II (four digits + F), # Category III (four digits + T) and Proprietary Laboratory Analyses # (four digits + U) codes, all five characters. Pattern widened to # "^[0-9]{4}[0-9FTU]$". # 4. Date properties carried over from a source document — # clinical:encounterDate, clinical:documentDate, clinical:onsetDate, # clinical:procedureDate — now accept xsd:date as well as xsd:dateTime. # FHIR's dateTime primitive is explicitly partial-precision ("YYYY, # YYYY-MM, YYYY-MM-DD or YYYY-MM-DDThh:mm:ss+zz:zz", # https://build.fhir.org/datatypes.html) and C-CDA effectiveTime is # commonly date-precision, so demanding an instant forced importers to # invent a midnight the source never stated. Timestamps Cascade itself # generates (clinical:importedAt, the derived episode dates) are # unchanged and still require xsd:dateTime. # 5. NEW clinical:EncounterShape + clinical:EncounterTemporalShape. # clinical:Encounter has existed since v1.7 with no shape, so encounters # validated to PASS without a single constraint being evaluated. The new # shape is deliberately minimal — cardinality, datatype, provenance, and # an IRI requirement implied by the clinical:hasEncounter edge. See the # long note above the shape for what is deliberately NOT constrained. # # Deliberately NOT changed: no sh:pattern is introduced on snomedCode or # icd10Code. SNOMED CT identifiers are 6-18 digit integers and ICD-10-CM # permits a letter in any character position (ICD-10-CM Official Guidelines # for Coding and Reporting, Section I.A.2), so a pattern would reject valid # codes without catching anything a code system lookup would not. # # v1.10 (2026-07-16): Add open-world PropertyShapes for the three record-to-record # edges introduced in clinical v1.10 (hasEncounter, indicationReference, # linkedCondition). Each targets the subjects that use the predicate, checks # only that the object is an IRI (plus its class where the range is # committed), asserts no minCount, and reports at sh:Warning severity so no # pod, current or future, fails validation on account of these edges. # v1.9 (2026-06-17): Add cascade:AIExtracted to every dataProvenance sh:in enum. # AIExtracted (a ClinicalGenerated subclass, core v3.3) was defined and # referenced by the v3.0 AI-extraction infrastructure, but the validation # enums omitted it — so a record carrying the correct provenance for # AI-extracted clinical data failed validation. Clinical records may now be # AIExtracted (e.g. from the Workbench's document-extraction pipeline). # # ============================================================================ # ============================================================================ # Shape: Clinical Document (Base) # ============================================================================ clinical:ClinicalDocumentShape a sh:NodeShape ; sh:targetClass clinical:ClinicalDocument ; rdfs:label "Clinical Document Shape"@en ; rdfs:comment "Validation constraints for clinical documents imported from EHR"@en ; # Required: importedAt timestamp sh:property [ sh:path clinical:importedAt ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Imported At"@en ; sh:message "Clinical document must have exactly one importedAt timestamp"@en ] ; # Required: sourceEHR name sh:property [ sh:path clinical:sourceEHR ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:maxLength 100 ; sh:name "Source EHR"@en ; sh:message "Clinical document must specify the source EHR system"@en ] ; # Optional: sourceBundleId (iOS bundle identifier pattern) sh:property [ sh:path clinical:sourceBundleId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:pattern "^[a-zA-Z][a-zA-Z0-9.-]*$" ; sh:name "Source Bundle ID"@en ; sh:message "Bundle ID must be a valid iOS bundle identifier"@en ] ; # Required: fhirResourceId sh:property [ sh:path clinical:fhirResourceId ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "FHIR Resource ID"@en ; sh:message "Clinical document must have a FHIR resource ID"@en ] ; # Required: fhirResourceType sh:property [ sh:path clinical:fhirResourceType ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "FHIR Resource Type"@en ; sh:message "Clinical document must specify FHIR resource type"@en ] ; # Optional: displayName sh:property [ sh:path clinical:displayName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:maxLength 500 ; sh:name "Display Name"@en ] ; # Optional: narrativeText (unbounded length for clinical notes) sh:property [ sh:path clinical:narrativeText ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Narrative Text"@en ; sh:description "Plain text content extracted from FHIR narrative"@en ] ; # Optional: narrativeXHTML sh:property [ sh:path clinical:narrativeXHTML ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Narrative XHTML"@en ] ; # Optional: rawFHIRData (base64 encoded) sh:property [ sh:path clinical:rawFHIRData ; sh:datatype xsd:base64Binary ; sh:maxCount 1 ; sh:name "Raw FHIR Data"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Clinical documents must have valid provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Schema version must be in format major.minor"@en ] ; # Optional: sections sh:property [ sh:path clinical:hasSection ; sh:class clinical:ClinicalSection ; sh:name "Sections"@en ] . # ============================================================================ # Shape: Clinical Section # ============================================================================ clinical:ClinicalSectionShape a sh:NodeShape ; sh:targetClass clinical:ClinicalSection ; rdfs:label "Clinical Section Shape"@en ; rdfs:comment "Validation constraints for clinical document sections"@en ; # Required: sectionTitle sh:property [ sh:path clinical:sectionTitle ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:maxLength 200 ; sh:name "Section Title"@en ; sh:message "Section must have a title"@en ] ; # Required: sectionContent sh:property [ sh:path clinical:sectionContent ; sh:datatype xsd:string ; sh:minCount 1 ; sh:name "Section Content"@en ; sh:message "Section must have content"@en ] ; # Optional: sectionCode (LOINC format: 5 digits, dash, 1 digit) sh:property [ sh:path clinical:sectionCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:pattern "^[0-9]{4,5}-[0-9]$" ; sh:name "Section Code"@en ; sh:message "Section code must be a valid LOINC code (e.g., 10154-3)"@en ] ; # Optional: sectionOrder sh:property [ sh:path clinical:sectionOrder ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Section Order"@en ] . # ============================================================================ # Shape: Progress Note (extends ClinicalDocument) # ============================================================================ clinical:ProgressNoteShape a sh:NodeShape ; sh:targetClass clinical:ProgressNote ; rdfs:label "Progress Note Shape"@en ; rdfs:comment "Additional constraints for progress notes"@en ; # Inherits all ClinicalDocument constraints sh:node clinical:ClinicalDocumentShape ; # Progress notes should have encounterDate sh:property [ sh:path clinical:encounterDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Encounter Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Encounter Date"@en ; sh:message "Progress notes must specify the encounter date"@en ] . # ============================================================================ # Shape: Discharge Summary # ============================================================================ clinical:DischargeSummaryShape a sh:NodeShape ; sh:targetClass clinical:DischargeSummary ; rdfs:label "Discharge Summary Shape"@en ; rdfs:comment "Additional constraints for discharge summaries"@en ; # Inherits ClinicalDocument constraints sh:node clinical:ClinicalDocumentShape ; # Discharge summaries should have document date sh:property [ sh:path clinical:documentDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Document Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Document Date"@en ; sh:message "Discharge summary must have a document date"@en ] . # ============================================================================ # Shape: Consultation Note (v1.13) # ============================================================================ # # clinical:ConsultationNote is rdfs:subClassOf clinical:ClinicalDocument, and # its five sibling subclasses (ProgressNote, DischargeSummary, # LaboratoryReport, ImagingReport, VisitSummary) each carry an explicit # sh:targetClass. This one did not, and a subclass axiom alone does not # inherit a shape: SHACL resolves sh:targetClass over SHACL-instances in the # DATA graph, so an axiom that lives only in clinical.ttl does not expand the # target set. Consultation notes were therefore validating vacuously while # every other document type was checked, and nothing reported the difference. clinical:ConsultationNoteShape a sh:NodeShape ; sh:targetClass clinical:ConsultationNote ; rdfs:label "Consultation Note Shape"@en ; rdfs:comment "Additional constraints for specialist consultation notes"@en ; # Inherits ClinicalDocument constraints sh:node clinical:ClinicalDocumentShape ; # Consultation notes should record when the consultation happened sh:property [ sh:path clinical:encounterDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Encounter Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Encounter Date"@en ; sh:message "Consultation note must not carry more than one encounter date"@en ] . # ============================================================================ # Shape: Laboratory Report # ============================================================================ clinical:LaboratoryReportShape a sh:NodeShape ; sh:targetClass clinical:LaboratoryReport ; rdfs:label "Laboratory Report Shape"@en ; rdfs:comment "Additional constraints for laboratory reports"@en ; # Inherits ClinicalDocument constraints sh:node clinical:ClinicalDocumentShape ; # Lab reports should have documentDate (when results were finalized) sh:property [ sh:path clinical:documentDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Document Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:minCount 1 ; sh:name "Document Date"@en ; sh:message "Lab reports must have a document date"@en ] . # ============================================================================ # Shape: Imaging Report # ============================================================================ clinical:ImagingReportShape a sh:NodeShape ; sh:targetClass clinical:ImagingReport ; rdfs:label "Imaging Report Shape"@en ; rdfs:comment "Additional constraints for imaging/radiology reports"@en ; # Inherits ClinicalDocument constraints sh:node clinical:ClinicalDocumentShape ; # Imaging reports should have document date sh:property [ sh:path clinical:documentDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Document Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:minCount 1 ; sh:name "Document Date"@en ; sh:message "Imaging reports must have a document date"@en ] . # ============================================================================ # Shape: Visit Summary # ============================================================================ clinical:VisitSummaryShape a sh:NodeShape ; sh:targetClass clinical:VisitSummary ; rdfs:label "Visit Summary Shape"@en ; rdfs:comment "Additional constraints for after-visit summaries"@en ; # Inherits ClinicalDocument constraints sh:node clinical:ClinicalDocumentShape ; # Visit summaries should have encounter date sh:property [ sh:path clinical:encounterDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Encounter Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Encounter Date"@en ; sh:message "Visit summary must specify the encounter date"@en ] . # ============================================================================ # Structured Record Shapes # ============================================================================ # ============================================================================ # Shape: Medication # ============================================================================ clinical:MedicationShape a sh:NodeShape ; sh:targetClass clinical:Medication ; rdfs:label "Medication Shape"@en ; rdfs:comment "Validation constraints for medication records"@en ; # Required: drugName sh:property [ sh:path clinical:drugName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Drug Name"@en ; sh:message "Medication must have a drug name"@en ] ; # Optional: dosage sh:property [ sh:path clinical:dosage ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Dosage"@en ] ; # Optional: route sh:property [ sh:path clinical:route ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Route"@en ] ; # Optional: frequency sh:property [ sh:path clinical:frequency ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Frequency"@en ] ; # Optional: status sh:property [ sh:path clinical:status ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Status"@en ] ; # Optional: rxNormCode (can be string or URI reference) sh:property [ sh:path clinical:rxNormCode ; sh:maxCount 1 ; sh:name "RxNorm Code"@en ] ; # Optional: snomedCode (string or URI reference). Repeatable: FHIR R4 # CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). No sh:pattern: # SNOMED CT identifiers are 6-18 digit integers and format belongs to the # code system. sh:property [ sh:path clinical:snomedCode ; sh:name "SNOMED Code"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Medication records must have valid provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] ; # Optional: prescriber sh:property [ sh:path clinical:prescriber ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Prescriber"@en ] ; # Optional: indication sh:property [ sh:path clinical:indication ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Indication"@en ] ; # Optional: courseOfTherapyType sh:property [ sh:path clinical:courseOfTherapyType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("acute" "continuous" "unknown") ; sh:severity sh:Warning ; sh:name "Course of Therapy Type"@en ; sh:message "courseOfTherapyType should be acute, continuous, or unknown"@en ] ; # Optional: asNeeded (PRN) sh:property [ sh:path clinical:asNeeded ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:name "As Needed (PRN)"@en ] ; # Optional: medicationForm sh:property [ sh:path clinical:medicationForm ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Medication Form"@en ] ; # Optional: activeIngredient sh:property [ sh:path clinical:activeIngredient ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Active Ingredient"@en ] ; # Optional: ingredientStrength sh:property [ sh:path clinical:ingredientStrength ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Ingredient Strength"@en ] ; # Optional: provenanceClass sh:property [ sh:path clinical:provenanceClass ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("healthKitFHIR" "userTracked" "pharmacyClaim" "imported") ; sh:name "Provenance Class"@en ] ; # Optional: sourceFhirResourceType sh:property [ sh:path clinical:sourceFhirResourceType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("MedicationRequest" "MedicationStatement" "MedicationDispense" "MedicationAdministration") ; sh:name "Source FHIR Resource Type"@en ] ; # Optional: clinicalIntent sh:property [ sh:path clinical:clinicalIntent ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("reportedUse" "prescribed" "dispensed" "administered") ; sh:name "Clinical Intent"@en ] ; # Optional: refillsAllowed sh:property [ sh:path clinical:refillsAllowed ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Refills Allowed"@en ] ; # Optional: supplyDurationDays sh:property [ sh:path clinical:supplyDurationDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Supply Duration Days"@en ] ; # Optional: dispensedQuantity sh:property [ sh:path clinical:dispensedQuantity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Dispensed Quantity"@en ] ; # Optional: prescriptionCategory sh:property [ sh:path clinical:prescriptionCategory ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("community" "inpatient" "discharge") ; sh:name "Prescription Category"@en ] . # ============================================================================ # Shape: Allergy # ============================================================================ clinical:AllergyShape a sh:NodeShape ; sh:targetClass clinical:Allergy ; rdfs:label "Allergy Shape"@en ; rdfs:comment "Validation constraints for allergy/intolerance records"@en ; # Required: allergen sh:property [ sh:path clinical:allergen ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Allergen"@en ; sh:message "Allergy must specify the allergen"@en ] ; # Optional: reaction sh:property [ sh:path clinical:reaction ; sh:datatype xsd:string ; sh:name "Reaction"@en ] ; # Optional: severity (constrained values) sh:property [ sh:path clinical:severity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("mild" "moderate" "severe") ; sh:name "Severity"@en ; sh:message "Severity must be mild, moderate, or severe"@en ] ; # Optional: criticality sh:property [ sh:path clinical:criticality ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("low" "high" "unable-to-assess") ; sh:name "Criticality"@en ] ; # Optional: category (allergy category) sh:property [ sh:path clinical:category ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("food" "medication" "environment" "biologic") ; sh:name "Allergy Category"@en ] ; # Optional: snomedCode (string or URI reference). Repeatable: FHIR R4 # CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). No sh:pattern: # SNOMED CT identifiers are 6-18 digit integers and format belongs to the # code system. sh:property [ sh:path clinical:snomedCode ; sh:name "SNOMED Code"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Shape: Lab Result # ============================================================================ clinical:LabResultShape a sh:NodeShape ; sh:targetClass clinical:LabResult ; rdfs:label "Lab Result Shape"@en ; rdfs:comment "Validation constraints for laboratory result records"@en ; # Required: testName sh:property [ sh:path clinical:testName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Test Name"@en ; sh:message "Lab result must have a test name"@en ] ; # Optional: value (can be string or numeric) sh:property [ sh:path clinical:value ; sh:maxCount 1 ; sh:name "Value"@en ] ; # Optional: unit sh:property [ sh:path clinical:unit ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Unit"@en ] ; # Optional: referenceRange sh:property [ sh:path clinical:referenceRange ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Reference Range"@en ] ; # Optional: interpretation # # Bound to the HL7 v3 ObservationInterpretation code system, the code system # FHIR R4 binds Observation.interpretation to. This list is the 49 # SELECTABLE codes of # http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation # (version 3.0.0), verbatim; the eight abstract (notSelectable) hierarchy # concepts are excluded because they are not values. Deprecated codes are # accepted: historical results carry them. # # Plus ALL FIFTEEN codes of # http://terminology.hl7.org/CodeSystem/data-absent-reason (v1.15; v1.14 # accepted only "unknown"), for a source Observation whose interpretation # element was absent or null-flavoured, and the ten words of the previous # enum, retained so data written against earlier clinical versions keeps # validating. # # Kept byte-identical to the health:interpretation list in # health.shapes.ttl. These are two spellings of one record (clinical:LabResult # is deprecated in favour of health:LabResultRecord since clinical v1.13) and # letting them drift is how one spelling silently becomes the lenient one. sh:property [ sh:path clinical:interpretation ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("EX" "HM" "OBX" "CAR" "Carrier" "B" "D" "U" "W" "<" ">" "AC" "IE" "QCF" "TOX" "A" "N" "I" "MS" "NCL" "NS" "R" "S" "VS" "AA" "H" "L" "HH" "LL" "HX" "LX" "H>" "HU" "E" "L<" "LU" "ND" "IND" "NEG" "POS" "EXP" "UNE" "DET" "SYN-R" "NR" "RR" "WR" "SDD" "SYN-S" "unknown" "asked-unknown" "temp-unknown" "not-asked" "asked-declined" "masked" "not-applicable" "unsupported" "as-text" "error" "not-a-number" "negative-infinity" "positive-infinity" "not-performed" "not-permitted" "normal" "high" "low" "abnormal" "critical" "Normal" "High" "Low" "Abnormal" "Critical") ; sh:name "Interpretation"@en ; sh:message "Interpretation must be a code from the HL7 v3 ObservationInterpretation code system (http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation), a code from http://terminology.hl7.org/CodeSystem/data-absent-reason, or one of the retained legacy words. A source code in none of those is carried verbatim on clinical:interpretationSourceCode."@en ] ; # v1.15: the verbatim escape hatch. Single string, no value set, no pattern. sh:property [ sh:path clinical:interpretationSourceCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Interpretation Source Code"@en ; sh:message "Interpretation source code must be a single string: the source's verbatim code, unmodified"@en ] ; # Optional: loincCode (can be string or URI reference) sh:property [ sh:path clinical:loincCode ; sh:maxCount 1 ; sh:name "LOINC Code"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Shape: Condition # ============================================================================ clinical:ConditionShape a sh:NodeShape ; sh:targetClass clinical:Condition ; rdfs:label "Condition Shape"@en ; rdfs:comment "Validation constraints for condition/diagnosis records"@en ; # Required: conditionName sh:property [ sh:path clinical:conditionName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Condition Name"@en ; sh:message "Condition must have a name"@en ] ; # Optional: clinicalStatus sh:property [ sh:path clinical:clinicalStatus ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("active" "recurrence" "relapse" "inactive" "remission" "resolved") ; sh:name "Clinical Status"@en ] ; # Optional: verificationStatus sh:property [ sh:path clinical:verificationStatus ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("unconfirmed" "provisional" "differential" "confirmed" "refuted" "entered-in-error") ; sh:name "Verification Status"@en ] ; # Optional: onsetDate sh:property [ sh:path clinical:onsetDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Onset Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Onset Date"@en ] ; # Optional: snomedCode (string or URI reference). Repeatable: FHIR R4 # CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). No sh:pattern: # SNOMED CT identifiers are 6-18 digit integers and format belongs to the # code system. sh:property [ sh:path clinical:snomedCode ; sh:name "SNOMED Code"@en ] ; # Optional: icd10Code (string or URI reference). Repeatable for the same # reason as snomedCode above; problem-list entries are routinely dual coded. # No sh:pattern: ICD-10-CM permits a letter in any character position # (C4A, D3A and M1A are ordinary categories), per the ICD-10-CM Official # Guidelines for Coding and Reporting, Section I.A.2. sh:property [ sh:path clinical:icd10Code ; sh:name "ICD-10 Code"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Shape: Immunization # ============================================================================ clinical:ImmunizationShape a sh:NodeShape ; sh:targetClass clinical:Immunization ; rdfs:label "Immunization Shape"@en ; rdfs:comment "Validation constraints for immunization records"@en ; # Required: vaccineName sh:property [ sh:path clinical:vaccineName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Vaccine Name"@en ; sh:message "Immunization must have a vaccine name"@en ] ; # Optional: cvxCode (can be string or URI reference) sh:property [ sh:path clinical:cvxCode ; sh:maxCount 1 ; sh:name "CVX Code"@en ] ; # Optional: lotNumber sh:property [ sh:path clinical:lotNumber ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Lot Number"@en ] ; # Optional: site sh:property [ sh:path clinical:site ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Site"@en ] ; # Optional: doseQuantity sh:property [ sh:path clinical:doseQuantity ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Dose Quantity"@en ] ; # Optional: manufacturer sh:property [ sh:path clinical:manufacturer ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Manufacturer"@en ] ; # Optional: status sh:property [ sh:path clinical:status ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("completed" "entered-in-error" "not-done") ; sh:name "Status"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Shape: Procedure # ============================================================================ clinical:ProcedureShape a sh:NodeShape ; sh:targetClass clinical:Procedure ; rdfs:label "Procedure Shape"@en ; rdfs:comment "Validation constraints for procedure records"@en ; # Required: a name, in either spelling (v1.15 RULING). # # Through v1.14 this was a bare sh:minCount 1 on clinical:procedureName. A # C-CDA import path writes the name to health:procedureName instead, on # records it types clinical:Procedure, so every converted procedure failed # this constraint WHILE CARRYING A NAME, and carried it on a predicate no # shape targets, so the name itself was validated by nothing. Measured 2 of # 2, 7 of 7 and 1 of 1 across three public sample documents. # # This is a MIGRATION WINDOW, not a ratified dual spelling. The health: # vocabulary defines neither health:procedureName nor a procedure class, and # the record is typed clinical:Procedure, so clinical:procedureName is the # only defined spelling and the only one a producer may write. The full # rationale, and what a consumer must re-query, is on clinical:procedureName # in clinical.ttl. # # The requirement moves to a node-level sh:or so that a Pod already holding # the health: triples stops failing without any data being rewritten. The # form follows health:DailyVitalReadingShape, which requires a timestamp in # either of two spellings the same way. Per-spelling datatype, length and # cardinality constraints stay below, so neither spelling is unchecked. # # BOTH the sh:or alternative and ProcedureNameSpellingShape below are removed # in a later clinical version, once the warning is observably absent from # conforming output, restoring a bare sh:minCount 1 on clinical:procedureName. sh:or ( [ sh:property [ sh:path clinical:procedureName ; sh:minCount 1 ] ] [ sh:property [ sh:path health:procedureName ; sh:minCount 1 ] ] ) ; sh:message "Procedure must have a name, as clinical:procedureName (canonical) or health:procedureName (deprecated import spelling, accepted during the clinical v1.15 migration window)"@en ; sh:property [ sh:path clinical:procedureName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Procedure Name"@en ; sh:message "Procedure name must be a single non-empty string"@en ] ; sh:property [ sh:path health:procedureName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Procedure Name (deprecated spelling)"@en ; sh:message "Procedure name must be a single non-empty string"@en ] ; # Optional: procedureDate sh:property [ sh:path clinical:procedureDate ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Procedure Date must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Procedure Date"@en ] ; # Optional: bodySite sh:property [ sh:path clinical:bodySite ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Body Site"@en ] ; # Optional: performer sh:property [ sh:path clinical:performer ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Performer"@en ] ; # Optional: status sh:property [ sh:path clinical:status ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("preparation" "in-progress" "not-done" "on-hold" "stopped" "completed" "entered-in-error" "unknown") ; sh:name "Status"@en ] ; # Optional: cptCode # # Every CPT code is five characters, but only Category I codes are five # DIGITS. The AMA CPT code set also defines Category II codes (four digits # followed by the letter F), Category III codes (four digits followed by the # letter T) and Proprietary Laboratory Analyses codes (four digits followed # by the letter U). "^[0-9]{5}$" rejected all three of those, so an ordinary # emerging-technology or performance-measurement procedure failed validation. # See https://www.ama-assn.org/practice-management/cpt/cpt-code-set-overview sh:property [ sh:path clinical:cptCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:pattern "^[0-9]{4}[0-9FTU]$" ; sh:name "CPT Code"@en ; sh:message "CPT code must be five characters: five digits (Category I), or four digits followed by F (Category II), T (Category III) or U (Proprietary Laboratory Analyses)"@en ] ; # Optional: snomedCode (string or URI reference). Repeatable: FHIR R4 # CodeableConcept.coding is 0..* # (https://hl7.org/fhir/R4/datatypes.html#CodeableConcept). No sh:pattern: # SNOMED CT identifiers are 6-18 digit integers and format belongs to the # code system. sh:property [ sh:path clinical:snomedCode ; sh:name "SNOMED Code"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # The migration signal for the procedure-name spelling (clinical v1.15). # # Kept in its own shape so that its severity is unambiguous: this is sh:Warning, # while ProcedureShape's own constraints stay at the default sh:Violation. It # targets the SUBJECTS OF the deprecated predicate rather than the class, so it # fires exactly once per record that carries the health: spelling and never on a # record that does not, whatever that record is typed. # # It exists so that the sh:or above cannot quietly become permanent. The window # closes when this warning is observably absent from conforming output; at that # point this shape and the health: branch of the sh:or are both removed and # clinical:procedureName goes back to a bare sh:minCount 1. clinical:ProcedureNameSpellingShape a sh:NodeShape ; sh:targetSubjectsOf health:procedureName ; rdfs:label "Procedure Name Spelling Shape"@en ; rdfs:comment "Warns that a record carries the deprecated health:procedureName spelling. Accepted during the clinical v1.15 migration window; removed with the window."@en ; sh:property [ sh:path health:procedureName ; sh:maxCount 0 ; sh:name "Procedure Name (deprecated spelling)"@en ; sh:message "Procedure name is written on health:procedureName, which no Cascade vocabulary defines. Write clinical:procedureName instead: it is the property whose domain is clinical:Procedure and the one clinical:ProcedureShape names. Accepted during the clinical v1.15 migration window; this becomes a VIOLATION when the window closes."@en ; sh:severity sh:Warning ] . # ============================================================================ # Shape: Vital Sign # ============================================================================ clinical:VitalSignShape a sh:NodeShape ; sh:targetClass clinical:VitalSign ; rdfs:label "Vital Sign Shape"@en ; rdfs:comment "Validation constraints for vital sign observations"@en ; # Required: vitalType (accepts display names, snake_case, and camelCase) sh:property [ sh:path clinical:vitalType ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("Heart Rate" "Blood Pressure" "Respiratory Rate" "Body Temperature" "Oxygen Saturation" "Body Weight" "Body Height" "BMI" "heart_rate" "blood_pressure" "respiratory_rate" "body_temperature" "oxygen_saturation" "body_weight" "body_height" "bmi" "heartRate" "bloodPressure" "bloodPressureSystolic" "bloodPressureDiastolic" "respiratoryRate" "temperature" "oxygenSaturation" "bodyWeight" "bodyHeight" "bodyMassIndex") ; sh:name "Vital Type"@en ; sh:message "Vital sign must have a valid type"@en ] ; # Optional: value (can be string or numeric) sh:property [ sh:path clinical:value ; sh:maxCount 1 ; sh:name "Value"@en ] ; # Optional: unit sh:property [ sh:path clinical:unit ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Unit"@en ] ; # Optional: component values (for blood pressure, etc.) sh:property [ sh:path clinical:component1Value ; sh:maxCount 1 ; sh:name "Component 1 Value"@en ] ; sh:property [ sh:path clinical:component2Value ; sh:maxCount 1 ; sh:name "Component 2 Value"@en ] ; # Optional: interpretation # # v1.15 RULING: the lab value set now applies here too, at sh:Warning. # # Through v1.14 this property shape carried sh:datatype and sh:maxCount and # no sh:in, while the two LAB shapes (clinical:LabResultShape and # health:LabResultRecordShape) bound the identical predicate to the 49 # selectable HL7 v3 ObservationInterpretation codes. So the same # interpretation string was checked on a lab result and unchecked on a vital # sign, and the vital path was the one an importer could write anything to. # clinical.ttl v1.13 recorded that gap as intentional, because emitted vital # data uses "elevated", which is in neither ratified set, and a Violation # here would have rejected records that already exist. # # That reasoning justified not making it a VIOLATION. It never justified # leaving the property unchecked. So the binding is applied at sh:Warning, # which is exactly the ratchet core v3.5 wrote down for this situation: a # value existing data carries is REPORTED, not rejected, and the severity is # raised to sh:Violation in a later clinical version only after a release in # which the warning is observably absent from conforming output. Each step # is its own vocabulary version, so this is not a judgement call later. # # The migration for the affected records is not "pick a different word": it # is clinical:interpretation "H" plus clinical:interpretationSourceCode # "elevated", which states the ratified reading AND keeps what the source # said. That is the whole reason the source-code property is in the same # release. # # The list is byte-identical to the one on clinical:LabResultShape above and # to health:interpretation in health.shapes.ttl. Three copies of one value # set is a drift hazard and is accepted deliberately: SHACL has no value-set # include, and the alternative (a shared shape reached by sh:node) would # bring the lab shape's Violation severity with it, which is the one thing # this constraint must not do on vitals yet. sh:property [ sh:path clinical:interpretation ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("EX" "HM" "OBX" "CAR" "Carrier" "B" "D" "U" "W" "<" ">" "AC" "IE" "QCF" "TOX" "A" "N" "I" "MS" "NCL" "NS" "R" "S" "VS" "AA" "H" "L" "HH" "LL" "HX" "LX" "H>" "HU" "E" "L<" "LU" "ND" "IND" "NEG" "POS" "EXP" "UNE" "DET" "SYN-R" "NR" "RR" "WR" "SDD" "SYN-S" "unknown" "asked-unknown" "temp-unknown" "not-asked" "asked-declined" "masked" "not-applicable" "unsupported" "as-text" "error" "not-a-number" "negative-infinity" "positive-infinity" "not-performed" "not-permitted" "normal" "high" "low" "abnormal" "critical" "Normal" "High" "Low" "Abnormal" "Critical") ; sh:name "Interpretation"@en ; sh:message "Interpretation should be a code from the HL7 v3 ObservationInterpretation code system (http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation), a code from http://terminology.hl7.org/CodeSystem/data-absent-reason, or one of the retained legacy words. Carry a code the value sets do not contain verbatim on clinical:interpretationSourceCode and put the nearest ratified reading here. WARNING in clinical v1.15; this becomes a VIOLATION in a later version."@en ; sh:severity sh:Warning ] ; # v1.15: the verbatim escape hatch, same as on the lab shape. sh:Violation # (the shape's default) rather than sh:Warning, because unlike the value set # above there is no existing data to be lenient toward: the property is new # in this version, so the only records carrying it are records written # against this version. sh:property [ sh:path clinical:interpretationSourceCode ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Interpretation Source Code"@en ; sh:message "Interpretation source code must be a single string: the source's verbatim code, unmodified"@en ] ; # Optional: deviceName sh:property [ sh:path clinical:deviceName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Device Name"@en ] ; # Optional: loincCode (can be string or URI reference) sh:property [ sh:path clinical:loincCode ; sh:maxCount 1 ; sh:name "LOINC Code"@en ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Longitudinal Record Shapes (v1.2) # ============================================================================ # ============================================================================ # Shape: Medication Use Episode # ============================================================================ clinical:MedicationUseEpisodeShape a sh:NodeShape ; sh:targetClass clinical:MedicationUseEpisode ; rdfs:label "Medication Use Episode Shape"@en ; rdfs:comment "Validation constraints for longitudinal medication episodes. Ensures source linkage and explainable status inference per Cascade Protocol requirements."@en ; # REQUIRED: canonicalName (normalized drug name) sh:property [ sh:path clinical:canonicalName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Canonical Name"@en ; sh:message "Episode must have a canonical drug name"@en ; sh:severity sh:Violation ] ; # REQUIRED: episodeFirstSeenDate (earliest CLINICAL date from sources) sh:property [ sh:path clinical:episodeFirstSeenDate ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "First Seen Date"@en ; sh:message "Episode must have a first seen date (earliest clinical date from source records)"@en ; sh:severity sh:Violation ] ; # REQUIRED: episodeLastSeenDate (most recent CLINICAL date from sources) sh:property [ sh:path clinical:episodeLastSeenDate ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Last Seen Date"@en ; sh:message "Episode must have a last seen date (most recent clinical date from source records)"@en ; sh:severity sh:Violation ] ; # REQUIRED: sourceRecordIds (Task 7.3 - must link to all contributing records) sh:property [ sh:path clinical:derivedFromRecord ; sh:minCount 1 ; sh:name "Derived From Record"@en ; sh:message "Episode MUST link to at least one source MedicationRecord (sourceRecordIds required per Cascade Protocol)"@en ; sh:description "Every episode must trace back to its source records for explainability"@en ; sh:severity sh:Violation ] ; # REQUIRED: inferredStatus (Task 7.4 - must have explainable status) sh:property [ sh:path clinical:inferredStatus ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("active" "inactive") ; sh:name "Inferred Status"@en ; sh:message "Episode MUST have an inferred status (active or inactive)"@en ; sh:severity sh:Violation ] ; # REQUIRED: statusReason (Task 7.4 - must explain why we think this status) sh:property [ sh:path clinical:statusReason ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("explicitStatus" "hasRecentEndDate" "noRecentActivity" "inferredFromRefills" "userOverride") ; sh:name "Status Reason"@en ; sh:message "Episode MUST have a status reason explaining WHY system thinks this status (per No Silent Inference policy)"@en ; sh:description "Enables answering: 'Why does system think this medication is active?'"@en ; sh:severity sh:Violation ] ; # REQUIRED: statusConfidence sh:property [ sh:path clinical:statusConfidence ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("high" "medium" "low") ; sh:name "Status Confidence"@en ; sh:message "Episode must declare confidence level for status inference"@en ; sh:severity sh:Violation ] ; # Optional: conceptUri (stable URI for the medication concept) sh:property [ sh:path clinical:conceptUri ; sh:maxCount 1 ; sh:name "Concept URI"@en ; sh:description "Stable URI for the medication concept (RxNorm URI or minted Cascade URI)"@en ] ; # Optional: currentDose sh:property [ sh:path clinical:currentDose ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Current Dose"@en ] ; # Optional: currentFrequency sh:property [ sh:path clinical:currentFrequency ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Current Frequency"@en ] ; # Optional: currentRoute sh:property [ sh:path clinical:currentRoute ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Current Route"@en ] ; # Optional: usePattern sh:property [ sh:path clinical:usePattern ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("chronic" "shortCourse" "oneTime" "asNeeded" "unknown") ; sh:name "Use Pattern"@en ] ; # Optional: therapeuticClass sh:property [ sh:path clinical:therapeuticClass ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Therapeutic Class"@en ] ; # Optional: hasUnresolvedConflicts flag sh:property [ sh:path clinical:hasUnresolvedConflicts ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:name "Has Unresolved Conflicts"@en ; sh:description "Flag indicating episode has data conflicts requiring user attention"@en ] ; # Optional: durationDays sh:property [ sh:path clinical:durationDays ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Duration Days"@en ] ; # Required: provenance sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:Reconciled cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Episode must have valid provenance (typically Reconciled for derived episodes)"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Shape: Supplement (SDK-level) # ============================================================================ clinical:SupplementShape a sh:NodeShape ; sh:targetClass clinical:Supplement ; rdfs:label "Supplement Shape"@en ; rdfs:comment "Validation constraints for supplement records. Ensures regulatory status is always explicit per Cascade Protocol requirements."@en ; # REQUIRED: supplementName sh:property [ sh:path clinical:supplementName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Supplement Name"@en ; sh:message "Supplement must have a name"@en ; sh:severity sh:Violation ] ; # REQUIRED: regulatoryStatus (Task 7.5 - explicit regulatory distinction) sh:property [ sh:path clinical:regulatoryStatus ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("dietarySupplement" "otcDrug" "homeopathic" "herbalRemedy" "unknown") ; sh:name "Regulatory Status"@en ; sh:message "Supplement MUST have explicit regulatory status to distinguish from FDA-approved medications"@en ; sh:description "Required per Cascade Protocol to avoid implying clinical equivalence with prescription medications"@en ; sh:severity sh:Violation ] ; # Optional: dose sh:property [ sh:path clinical:dose ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Dose"@en ] ; # Optional: frequency sh:property [ sh:path clinical:frequency ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Frequency"@en ] ; # Optional: brand sh:property [ sh:path clinical:brand ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Brand"@en ] ; # Optional: form sh:property [ sh:path clinical:form ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("capsule" "tablet" "softgel" "liquid" "powder" "gummy" "spray" "patch" "tea" "tincture" "other") ; sh:name "Form"@en ] ; # Optional: evidenceStrength sh:property [ sh:path clinical:evidenceStrength ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("strongEvidence" "moderateEvidence" "limitedEvidence" "traditionalUse" "noEvidence" "unknown") ; sh:name "Evidence Strength"@en ; sh:description "Level of clinical evidence supporting supplement use"@en ] ; # Optional: isActive sh:property [ sh:path clinical:isActive ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:name "Is Active"@en ] ; # Optional: dsldId (NIH Dietary Supplement Label Database) sh:property [ sh:path clinical:dsldId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "DSLD ID"@en ; sh:description "NIH Dietary Supplement Label Database identifier"@en ] ; # Optional: upc (barcode) sh:property [ sh:path clinical:upc ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "UPC"@en ] ; # Optional: cascadeUri (stable identifier) sh:property [ sh:path clinical:cascadeUri ; sh:maxCount 1 ; sh:name "Cascade URI"@en ; sh:description "Stable Cascade Protocol identifier (minted when no external ID exists)"@en ] ; # Optional: reasonForUse sh:property [ sh:path clinical:reasonForUse ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Reason For Use"@en ] ; # Required: provenance sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ] . # ============================================================================ # Shape: Social History Record (v1.8) # ============================================================================ clinical:SocialHistoryRecordShape a sh:NodeShape ; sh:targetClass clinical:SocialHistoryRecord ; rdfs:label "Social History Record Shape"@en ; # Required: socialHistoryCategory (must be one of defined values) sh:property [ sh:path clinical:socialHistoryCategory ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("smokingStatus" "alcoholUse" "substanceUse" "occupation" "exercise" "diet" "sexualHistory" "other") ; sh:severity sh:Violation ; sh:name "Social History Category"@en ; sh:message "SocialHistoryRecord requires a socialHistoryCategory from the defined value set"@en ] ; # Optional: packsPerYear sh:property [ sh:path clinical:packsPerYear ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:severity sh:Warning ; sh:name "Packs Per Year"@en ] ; # Optional: alcoholDrinksPerWeek sh:property [ sh:path clinical:alcoholDrinksPerWeek ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:severity sh:Warning ; sh:name "Alcohol Drinks Per Week"@en ] ; # Optional: occupationTitle sh:property [ sh:path clinical:occupationTitle ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Occupation Title"@en ] . # ============================================================================ # Shape: Encounter (v1.14) # ============================================================================ # # clinical:Encounter has existed since clinical v1.7 and until now had NO shape, # so every encounter a pod contained was imported and stored unvalidated: # validating a file of encounters checked nothing and returned PASS. This shape # closes that hole. It is deliberately minimal. # # WHY IT IS MINIMAL, STATED SO IT IS NOT "TIGHTENED" LATER BY MISTAKE # ------------------------------------------------------------------- # Encounters vary more than any other record here: a same-day telephone note, a # three-week inpatient admission, a planned appointment that has no period at # all, and a historical visit reconstructed from a summary document are all # legitimately clinical:Encounter. So this shape constrains SHAPE, not content: # cardinality and datatype on the properties importers actually write, the # standard Cascade provenance pair, and nothing else at Violation severity. # # In particular: # - clinical:encounterClass carries NO sh:in. FHIR R4 binds Encounter.class # EXTENSIBLY to the v3-ActCode ActEncounterCode value set, and servers send # either the abbreviation (AMB, EMER, IMP, HH) or a display string. An enum # here would reject conformant data in both directions. # - clinical:encounterStatus DOES carry an sh:in, because FHIR R4 binds # Encounter.status with REQUIRED strength, and the list below is that value # set verbatim (https://hl7.org/fhir/R4/valueset-encounter-status.html). # It is sh:Warning, not sh:Violation: an unrecognised status is worth # surfacing but is not a reason to reject the record and lose the visit. # - Dates accept xsd:date as well as xsd:dateTime. FHIR's dateTime primitive # is explicitly partial-precision ("YYYY, YYYY-MM, YYYY-MM-DD or # YYYY-MM-DDThh:mm:ss+zz:zz", https://build.fhir.org/datatypes.html) and # C-CDA effectiveTime is commonly date-precision, so requiring an instant # would force importers to invent a midnight that the source never stated. # # WHAT THE clinical:hasEncounter EDGE IMPLIES, AND WHAT IT DOES NOT # ----------------------------------------------------------------- # clinical:hasEncounter is the panel-to-visit edge: a lab result, medication, # condition, procedure or report points at the visit it happened in. # HasEncounterEdgeShape (below) already requires that object to be an IRI. Two # consequences are carried here: # 1. sh:nodeKind sh:IRI on the Encounter itself. An edge can only resolve to a # node that HAS an IRI, so a blank-node encounter is unreachable by the very # edge the class exists to serve. # 2. An encounter should be placeable in time, since grouping events "by visit" # is only meaningful if the visit has one. That is expressed as a # sh:Warning requiring a start, an end or an encounter date — NOT a # Violation, because FHIR R4 Encounter.period is 0..1 and a planned or # referenced-only encounter legitimately has none. # What is deliberately NOT added is an sh:class check back along the edge; see # the v1.11 note under HasEncounterEdgeShape for why that produced only false # positives under per-file validation. clinical:EncounterShape a sh:NodeShape ; sh:targetClass clinical:Encounter ; sh:nodeKind sh:IRI ; rdfs:label "Encounter Shape"@en ; rdfs:comment "Minimal validation constraints for clinical encounters (visits). Constrains cardinality, datatype and provenance; deliberately does not constrain what kind of visit an encounter may be."@en ; sh:message "A clinical:Encounter must be identified by an IRI so that clinical:hasEncounter edges can resolve to it"@en ; # Optional: encounterClass. No enum — FHIR R4 binds Encounter.class # extensibly to v3-ActCode ActEncounterCode and servers send codes or # display strings. sh:property [ sh:path clinical:encounterClass ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Encounter Class"@en ; sh:severity sh:Violation ] ; # Optional: encounterStatus. FHIR R4 EncounterStatus, verbatim. sh:property [ sh:path clinical:encounterStatus ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("planned" "arrived" "triaged" "in-progress" "onleave" "finished" "cancelled" "entered-in-error" "unknown") ; sh:name "Encounter Status"@en ; sh:message "Encounter status should be one of the FHIR R4 EncounterStatus codes: planned, arrived, triaged, in-progress, onleave, finished, cancelled, entered-in-error, unknown"@en ; sh:severity sh:Warning ] ; # Optional: encounterType (display name from a SNOMED CT / FHIR code) sh:property [ sh:path clinical:encounterType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Encounter Type"@en ; sh:severity sh:Violation ] ; # Optional: encounterStart / encounterEnd. Date precision accepted. sh:property [ sh:path clinical:encounterStart ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Encounter Start must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Encounter Start"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path clinical:encounterEnd ; sh:or ( [ sh:datatype xsd:date ] [ sh:datatype xsd:dateTime ] ) ; sh:message "Encounter End must be an xsd:date or an xsd:dateTime. FHIR's dateTime primitive permits date precision (YYYY, YYYY-MM, YYYY-MM-DD), so a source that stated only a calendar day must not be given an invented time; an untyped or xsd:string literal is not either type."@en ; sh:maxCount 1 ; sh:name "Encounter End"@en ; sh:severity sh:Violation ] ; # Optional: snomedCode for the encounter type. Repeatable and unpatterned, # for the reasons given on the other snomedCode shapes above. sh:property [ sh:path clinical:snomedCode ; sh:name "SNOMED Code"@en ; sh:severity sh:Violation ] ; # Optional: who and where. sh:property [ sh:path clinical:providerName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Provider Name"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path clinical:facilityName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Facility Name"@en ; sh:severity sh:Violation ] ; # Optional: source linkage back to the record this was converted from. sh:property [ sh:path clinical:sourceRecordId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Source Record ID"@en ; sh:severity sh:Violation ] ; sh:property [ sh:path clinical:sourceEHR ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:maxLength 100 ; sh:name "Source EHR"@en ; sh:severity sh:Violation ] ; # Required: provenance (accepts multiple valid sources) sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported cascade:AIExtracted) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Encounter must declare exactly one valid cascade:dataProvenance"@en ; sh:severity sh:Violation ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:severity sh:Violation ] . # An encounter should be placeable in time, because grouping clinical events # "by visit" is only useful if the visit has a position in the timeline. Kept in # its own shape so that its severity is unambiguous: this is sh:Warning, while # EncounterShape's own constraints (including sh:nodeKind sh:IRI) stay at the # default sh:Violation. It is not a Violation because FHIR R4 Encounter.period # is 0..1 and a planned or merely referenced encounter legitimately has none. clinical:EncounterTemporalShape a sh:NodeShape ; sh:targetClass clinical:Encounter ; rdfs:label "Encounter Temporal Shape"@en ; sh:or ( [ sh:path clinical:encounterStart ; sh:minCount 1 ] [ sh:path clinical:encounterEnd ; sh:minCount 1 ] [ sh:path clinical:encounterDate ; sh:minCount 1 ] ) ; sh:message "Encounter should carry a start, an end or an encounter date so that records linked to it can be placed in time"@en ; sh:severity sh:Warning . # ============================================================================ # Graph Edge Property Shapes (v1.10) # Open-world, non-required constraints for the record-to-record edges added in # clinical v1.10. Each targets the subjects that USE the predicate (so nothing # fires unless the edge is present) and checks only that the object is an IRI. # Nothing is sh:closed, no minCount is asserted, and severity is sh:Warning, so # existing pods that carry none of these predicates validate exactly as before. # # v1.11: the sh:class checks on HasEncounterEdgeShape and LinkedConditionEdgeShape # were REMOVED. Cascade pods store records partitioned into per-type files and # the reference validator validates each file independently, so an edge whose # target record lives in a sibling file (a lab result in lab-results.ttl pointing # at an encounter in encounters.ttl) could never satisfy sh:class — it produced a # Warning on every well-formed, fully-resolving edge and none on a malformed one. # The constraint therefore only generated false positives. sh:nodeKind sh:IRI is # retained (it catches a genuinely malformed non-IRI object); the target class is # guaranteed at import time (edges are written only when they resolve to a real # record of the right type) and can be re-checked by a pod-wide validator later. # This mirrors IndicationReferenceEdgeShape, whose range was always left open. # ============================================================================ clinical:HasEncounterEdgeShape a sh:PropertyShape ; sh:targetSubjectsOf clinical:hasEncounter ; sh:path clinical:hasEncounter ; sh:nodeKind sh:IRI ; sh:severity sh:Warning ; sh:message "clinical:hasEncounter should reference a clinical:Encounter by IRI"@en ; sh:name "Has Encounter Edge"@en . clinical:IndicationReferenceEdgeShape a sh:PropertyShape ; sh:targetSubjectsOf clinical:indicationReference ; sh:path clinical:indicationReference ; sh:nodeKind sh:IRI ; sh:severity sh:Warning ; sh:message "clinical:indicationReference should reference the reason record (a Condition or Observation) by IRI"@en ; sh:name "Indication Reference Edge"@en . clinical:LinkedConditionEdgeShape a sh:PropertyShape ; sh:targetSubjectsOf clinical:linkedCondition ; sh:path clinical:linkedCondition ; sh:nodeKind sh:IRI ; sh:severity sh:Warning ; sh:message "clinical:linkedCondition should reference a clinical:Condition by IRI"@en ; sh:name "Linked Condition Edge"@en . clinical:ParsedIndicationReferenceEdgeShape a sh:PropertyShape ; sh:targetSubjectsOf clinical:parsedIndicationReference ; sh:path clinical:parsedIndicationReference ; sh:nodeKind sh:IRI ; sh:severity sh:Warning ; sh:message "clinical:parsedIndicationReference should reference the matched reason record (a Condition or Observation) by IRI"@en ; sh:name "Parsed Indication Reference Edge"@en . # ============================================================================ # Changelog # ============================================================================ # # Version 1.12 (2026-07-20) # - Added ParsedIndicationReferenceEdgeShape for the new # clinical:parsedIndicationReference subproperty. Warning-only and # nodeKind-only, matching IndicationReferenceEdgeShape: the target class is # deliberately NOT constrained, both because FHIR permits a Condition or an # Observation as the reason and because cascade-cli validates each per-type # pod file independently, so a cross-file sh:class check produces false # positives (the v1.11 rationale,). # # Version 1.11 (2026-07-16) # - Removed the sh:class constraint from HasEncounterEdgeShape (clinical:Encounter) # and LinkedConditionEdgeShape (clinical:Condition). Cascade stores records in # per-type files and the reference validator checks each file independently, so # an edge to a sibling-file target could never satisfy sh:class: it warned on # every well-formed, fully-resolving edge (e.g. all 181 hasEncounter edges of # the Synthea specimen) and never caught a real error. sh:nodeKind sh:IRI is # kept. Target class is enforced at import (edges written only on resolution) # and can be re-checked by a future pod-wide validator. Aligns these two shapes # with IndicationReferenceEdgeShape. Paired with the R3 edge importer. # # Version 1.10 (2026-07-16) # - Added HasEncounterEdgeShape, IndicationReferenceEdgeShape, and # LinkedConditionEdgeShape: open-world PropertyShapes (sh:targetSubjectsOf) # for the v1.10 record-to-record edges. IRI nodeKind, class where committed, # no minCount, sh:Warning severity. Purely additive. # # Version 1.8 (2026-03-28) # - Added SocialHistoryRecordShape for EHR-extracted social history records # - REQUIRED: socialHistoryCategory (enumerated value set) # - OPTIONAL: packsPerYear, alcoholDrinksPerWeek, occupationTitle # # Version 1.2 (2026-01-15) # - Added MedicationUseEpisodeShape for longitudinal medication tracking # - REQUIRED: canonicalName, episodeFirstSeenDate, episodeLastSeenDate # - REQUIRED: derivedFromRecord (sourceRecordIds) for source linkage (Task 7.3) # - REQUIRED: inferredStatus, statusReason, statusConfidence (Task 7.4) # - Ensures every episode answers "why does system think this?" # - Added SupplementShape for dietary supplements and OTC products # - REQUIRED: supplementName, regulatoryStatus (Task 7.5) # - Explicit regulatory distinction from medications # - Added cascade:Reconciled to valid provenance values for derived episodes # # Version 1.1 (2026-01-01) # - Added shapes for all structured clinical record types # # Version 1.0 (2025-12-26) # - Initial release with document shapes # ## SHACL Shapes: coverage ## File: coverage.shapes.ttl @prefix sh: . @prefix coverage: . @prefix cascade: . @prefix xsd: . @prefix rdfs: . # ============================================================================ # Cascade Protocol — Coverage Vocabulary SHACL Validation Shapes # Version: 1.0 (Phase 4, 2026-02-18) # Validates: coverage:InsurancePlan # # Layer 2 domain-specific vocabulary for patient-owned insurance and benefits data. # Aligns with FHIR Coverage resource (fhir:Coverage). # ============================================================================ # ============================================================================ # Shape: Insurance Plan # ============================================================================ coverage:InsurancePlanShape a sh:NodeShape ; sh:targetClass coverage:InsurancePlan ; rdfs:label "Insurance Plan Shape"@en ; rdfs:comment "Validation constraints for patient insurance plan records. Covers core identification, plan classification, coverage period, subscriber info, and pharmacy benefits."@en ; # ------------------------------------------------------------------ # REQUIRED (sh:Violation) — Must be present for valid plan record # ------------------------------------------------------------------ # Required: providerName sh:property [ sh:path coverage:providerName ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Provider Name"@en ; sh:message "Insurance provider name is required"@en ; sh:severity sh:Violation ] ; # Required: memberId sh:property [ sh:path coverage:memberId ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Member ID"@en ; sh:message "Member ID is required for claims processing"@en ; sh:severity sh:Violation ] ; # Required: coverageType # # PRESENCE is required. The VALUE is checked separately, at sh:Warning, by # coverage:CoverageTypeVocabularyShape below. # # WHY THE VALUE CHECK IS NOT A VIOLATION # -------------------------------------- # This property maps to FHIR R4 Coverage.type, whose binding to # http://hl7.org/fhir/ValueSet/coverage-type is EXTENSIBLE, not required. # FHIR defines extensible as: codes SHALL come from the value set if one of # them applies, and "if the value set does not cover the concept, alternate # codes MAY be used" (https://hl7.org/fhir/R4/terminologies.html#extensible). # A closed enum at Violation severity therefore contradicted the binding # strength of the element the data is converted from: a payer sending a # v3-ActCode coverage-type code such as EHCPOL, or its own local code, had a # conformant Coverage resource rejected outright. # # Note also that the original four values conflated two different FHIR # elements: "primary"/"secondary" describe Coverage.order (which plan pays # first), while "dental"/"vision" describe Coverage.type (what is covered). # Both spellings are retained below for backward compatibility. sh:property [ sh:path coverage:coverageType ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:name "Coverage Type"@en ; sh:message "Coverage type is required"@en ; sh:severity sh:Violation ] ; # ------------------------------------------------------------------ # WARNING (sh:Warning) — Important for coverage verification # ------------------------------------------------------------------ # Warning: groupNumber sh:property [ sh:path coverage:groupNumber ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Group Number"@en ; sh:message "Group number helps verify coverage"@en ; sh:severity sh:Warning ] ; # Warning: planType sh:property [ sh:path coverage:planType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("hmo" "ppo" "epo" "pos" "hdhp" "medicare" "medicaid" "tricare" "other") ; sh:name "Plan Type"@en ; sh:message "Plan type should be hmo, ppo, epo, pos, hdhp, medicare, medicaid, tricare, or other"@en ; sh:severity sh:Warning ] ; # Warning: effectiveStart sh:property [ sh:path coverage:effectiveStart ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:name "Effective Start"@en ; sh:message "Plan effective date helps verify active coverage"@en ; sh:severity sh:Warning ] ; # Warning: subscriberRelationship # # The list is the SubscriberPolicyholder code system in full and verbatim, # http://terminology.hl7.org/CodeSystem/subscriber-relationship, which is # what FHIR R4 Coverage.relationship binds to. The previous list held five # of its seven codes; "common" (Common Law Spouse) and "injured" (Injured # Party) were simply missing, so an export carrying either produced a # warning for being correct. sh:property [ sh:path coverage:subscriberRelationship ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("child" "parent" "spouse" "common" "other" "self" "injured") ; sh:name "Subscriber Relationship"@en ; sh:message "Subscriber relationship should be a code from the HL7 SubscriberPolicyholder code system (http://terminology.hl7.org/CodeSystem/subscriber-relationship): child, parent, spouse, common, other, self, injured"@en ; sh:severity sh:Warning ] ; # ------------------------------------------------------------------ # INFO (sh:Info) — Suggested for completeness # ------------------------------------------------------------------ # Info: planName sh:property [ sh:path coverage:planName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Plan Name"@en ; sh:severity sh:Info ] ; # Info: effectiveEnd sh:property [ sh:path coverage:effectiveEnd ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:name "Effective End"@en ; sh:severity sh:Info ] ; # Info: subscriberId sh:property [ sh:path coverage:subscriberId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Subscriber ID"@en ; sh:severity sh:Info ] ; # Info: subscriberName sh:property [ sh:path coverage:subscriberName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Subscriber Name"@en ; sh:severity sh:Info ] ; # Info: rxBin sh:property [ sh:path coverage:rxBin ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Rx BIN"@en ; sh:severity sh:Info ] ; # Info: rxPcn sh:property [ sh:path coverage:rxPcn ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Rx PCN"@en ; sh:severity sh:Info ] ; # Info: rxGroup sh:property [ sh:path coverage:rxGroup ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Rx Group"@en ; sh:severity sh:Info ] ; # ------------------------------------------------------------------ # Standard Cascade metadata # ------------------------------------------------------------------ # Required: provenance sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported) ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Data Provenance"@en ; sh:message "Insurance plan must have valid provenance"@en ] ; # Required: schemaVersion sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:name "Schema Version"@en ; sh:message "Schema version must be in format major.minor"@en ] . # ============================================================================ # Shape: Coverage Type vocabulary (advisory) # ============================================================================ # # Split out of InsurancePlanShape so that the REQUIREMENT (a coverage type is # present) and the RECOMMENDATION (it comes from the value set FHIR names) can # carry different severities, which a single sh:property cannot do. # # The list is the union of: # - the v3-ActCode coverage-type codes reachable from # http://hl7.org/fhir/ValueSet/coverage-type, the value set FHIR R4 binds # Coverage.type to; # - the four values this vocabulary has accepted since v1.0, retained so no # existing record starts warning. # # Severity is sh:Warning because the FHIR binding is EXTENSIBLE: alternate codes # MAY be used where the value set does not cover the concept # (https://hl7.org/fhir/R4/terminologies.html#extensible). A payer's local code # is therefore worth flagging for review and is not grounds for rejecting the # record. coverage:CoverageTypeVocabularyShape a sh:NodeShape ; sh:targetClass coverage:InsurancePlan ; rdfs:label "Coverage Type Vocabulary Shape"@en ; rdfs:comment "Advisory check that coverage:coverageType comes from the value set FHIR R4 binds Coverage.type to. Warning severity, because that binding is extensible."@en ; sh:property [ sh:path coverage:coverageType ; sh:in ( # Retained Cascade values (v1.0-v1.3). "primary" "secondary" "dental" "vision" # v3-ActCode coverage types. "EHCPOL" "HSAPOL" "AUTOPOL" "COL" "UNINSMOT" "PUBLICPOL" "DENTPRG" "DISEASEPRG" "CANPRG" "ENDRENAL" "HIVAIDS" "MANDPOL" "MENTPRG" "SAFNET" "SUBPRG" "SUBSIDIZ" "SUBSIDMC" "SUBSUPP" "WCBPOL" "DENTAL" "DISEASE" "DRUGPOL" "HIP" "LTC" "MCPOL" "POS" "HMO" "PPO" "MENTPOL" "SUBPOL" "VISPOL" ) ; sh:name "Coverage Type Vocabulary"@en ; sh:message "Coverage type should come from the value set FHIR R4 binds Coverage.type to (http://hl7.org/fhir/ValueSet/coverage-type), or one of the retained Cascade values primary, secondary, dental, vision. That binding is extensible, so a payer-local code is acceptable where no listed code applies."@en ; sh:severity sh:Warning ] . # ============================================================================ # Changelog # ============================================================================ # # Version 1.1 (2026-08-08) — coverage v1.4 # - coverage:subscriberRelationship: the sh:in list is now the HL7 # SubscriberPolicyholder code system in full, # http://terminology.hl7.org/CodeSystem/subscriber-relationship, which is what # FHIR R4 Coverage.relationship binds to. Two of its seven codes were missing: # "common" (Common Law Spouse) and "injured" (Injured Party). An export # carrying either was warned for being correct. # - coverage:coverageType: the closed four-member enum was a sh:Violation on an # element FHIR R4 binds EXTENSIBLY (Coverage.type), so a payer sending a # conformant v3-ActCode code such as EHCPOL had the record rejected. The # Violation now checks presence only; the value is checked against the FHIR # value set plus the four retained Cascade values by the new # coverage:CoverageTypeVocabularyShape, at sh:Warning. # - Strictly widening: nothing that validated under v1.0 fails under v1.1, and # two classes of previously-rejected conformant record now pass. # # Version 1.0 (2026-02-18) # - Initial release with InsurancePlanShape # - 3 REQUIRED (sh:Violation): providerName, memberId, coverageType # - 4 WARNING (sh:Warning): groupNumber, planType, effectiveStart, subscriberRelationship # - 6 INFO (sh:Info): planName, effectiveEnd, subscriberId, subscriberName, rxBin, rxPcn, rxGroup # - Standard Cascade metadata: dataProvenance, schemaVersion # - Aligns with coverage.ttl v1.0 InsurancePlan class (14 properties) # ## SHACL Shapes: checkup ## File: checkup.shapes.ttl @prefix sh: . @prefix checkup: . @prefix cascade: . @prefix health: . @prefix clinical: . @prefix xsd: . @prefix rdfs: . # ============================================================================ # CASCADE CHECKUP SHACL SHAPES - ADULT (Ages 18-64) # ============================================================================ # Version: 2.0 # Created: 2026-01-03 # Modified: 2026-02-18 (v2.0: Shapes for v1.5-v2.0 ontology classes) # # SEVERITY LEVELS: # - sh:Violation = Must fix (blocks export) - e.g., missing patient name # - sh:Warning = Should address (shown prominently) - e.g., no allergies listed # - sh:Info = Nice to have (suggestions) - e.g., incomplete family history # # PHASE 7 ADDITIONS: # - Extended MedicationSummaryShape with episode linkage and patient meaning fields # - Added SupplementSummaryShape with regulatory classification requirements # - Added hasSupplement validation to IntakeFormData # # v1.6 ADDITIONS: # - Added IntakeQuestionnaireResponseShape with LOINC panel code validation # - Added ResponseItemShape with question text validation # - Added hasQuestionnaireResponse validation to IntakeFormData # # v2.0 ADDITIONS (WS8 - Shapes for v1.5-v2.0 ontology classes): # - Added DailyCheckInShape with SelfReport inheritance via sh:node # - Added ImmunizationSummaryShape with vaccine validation # - Added LabResultSummaryShape with mixed namespace (clinical:/checkup:) # - Added ProcedureSummaryShape with mixed namespace (clinical:/checkup:) # - Added DiscussionTopicShape with category and relevance validation # - Added hasDailyCheckIn, hasDiscussionTopic, hasImmunization, # hasLabResult, hasProcedure aggregate links to IntakeFormDataShape # - Added health: and clinical: prefix declarations # ============================================================================ # ============================================================================ # INTAKE FORM DATA SHAPE (Top-level validation) # ============================================================================ checkup:IntakeFormDataShape a sh:NodeShape ; sh:targetClass checkup:IntakeFormData ; rdfs:label "Intake Form Data Validation" ; rdfs:comment "Validates completeness of intake form data for adult patients (18-64)" ; # Required: Patient Profile sh:property [ sh:path checkup:hasPatientProfile ; sh:minCount 1 ; sh:maxCount 1 ; sh:node checkup:PatientProfileShape ; sh:severity sh:Violation ; sh:message "Patient profile is required for intake forms" ] ; # Required: Schema version sh:property [ sh:path checkup:schemaVersion ; sh:minCount 1 ; sh:datatype xsd:string ; sh:severity sh:Violation ; sh:message "Schema version is required" ] ; # Required: Generation timestamp sh:property [ sh:path checkup:generatedAt ; sh:minCount 1 ; sh:datatype xsd:dateTime ; sh:severity sh:Violation ; sh:message "Generation timestamp is required" ] ; # Warning: Medications sh:property [ sh:path checkup:hasMedication ; sh:minCount 0 ; sh:node checkup:MedicationSummaryShape ; sh:severity sh:Warning ; sh:message "Medication list helps providers avoid drug interactions. Add your current medications or confirm 'None'." ] ; # Info: Supplements (Phase 3 - separate from medications) sh:property [ sh:path checkup:hasSupplement ; sh:minCount 0 ; sh:node checkup:SupplementSummaryShape ; sh:severity sh:Info ; sh:message "Supplements and OTC products can interact with medications. Consider listing supplements separately from prescription medications." ] ; # Warning: Allergies sh:property [ sh:path checkup:hasAllergy ; sh:minCount 0 ; sh:node checkup:AllergySummaryShape ; sh:severity sh:Warning ; sh:message "Allergy information helps prevent adverse reactions. Add allergies or confirm 'No Known Drug Allergies (NKDA)'." ] ; # Warning: Insurance sh:property [ sh:path checkup:hasInsurance ; sh:minCount 0 ; sh:maxCount 1 ; sh:node checkup:InsuranceInfoShape ; sh:severity sh:Warning ; sh:message "Insurance information is usually required for office visits. Add insurance or select 'Self-Pay'." ] ; # Warning: Conditions sh:property [ sh:path checkup:hasCondition ; sh:minCount 0 ; sh:node checkup:ConditionSummaryShape ; sh:severity sh:Warning ; sh:message "Active conditions help providers understand your health history. Add conditions or confirm 'None'." ] ; # Info: Family History sh:property [ sh:path checkup:hasFamilyHistory ; sh:minCount 0 ; sh:node checkup:FamilyHistoryEntryShape ; sh:severity sh:Info ; sh:message "Family history helps assess risk for certain conditions. Consider adding health history for parents and siblings." ] ; # Info: Vital Signs Trends sh:property [ sh:path checkup:hasVitalsTrend ; sh:minCount 0 ; sh:node checkup:VitalSignsTrendShape ; sh:severity sh:Info ; sh:message "Recent vital signs provide context for your current health status." ] ; # Info: Screening Status sh:property [ sh:path checkup:hasScreening ; sh:minCount 0 ; sh:node checkup:ScreeningStatusShape ; sh:severity sh:Info ; sh:message "Tracking preventive screenings helps you stay current with recommended care." ] ; # Info: Visit Issues sh:property [ sh:path checkup:hasVisitIssue ; sh:minCount 0 ; sh:node checkup:VisitIssueShape ; sh:severity sh:Info ; sh:message "Adding issues you want to discuss helps you make the most of your appointment." ] ; # Info: Diagnostic Results sh:property [ sh:path checkup:hasDiagnosticResult ; sh:minCount 0 ; sh:node checkup:DiagnosticTestResultShape ; sh:severity sh:Info ; sh:message "Including recent diagnostic tests gives your provider valuable context." ] ; # Info: Intake Questionnaires (v1.6) sh:property [ sh:path checkup:hasQuestionnaireResponse ; sh:minCount 0 ; sh:node checkup:IntakeQuestionnaireResponseShape ; sh:severity sh:Info ; sh:message "Consider completing intake questionnaires for a more complete health profile." ] ; # Info: Daily Check-ins (v2.0) sh:property [ sh:path checkup:hasDailyCheckIn ; sh:minCount 0 ; sh:node checkup:DailyCheckInShape ; sh:severity sh:Info ; sh:message "Daily medication check-ins help track adherence patterns for your provider." ] ; # Info: Discussion Topics (v2.0) sh:property [ sh:path checkup:hasDiscussionTopic ; sh:minCount 0 ; sh:node checkup:DiscussionTopicShape ; sh:severity sh:Info ; sh:message "Discussion topics help prioritize what to cover during your appointment." ] ; # Info: Immunizations (v2.0) sh:property [ sh:path checkup:hasImmunization ; sh:minCount 0 ; sh:node checkup:ImmunizationSummaryShape ; sh:severity sh:Info ; sh:message "Including immunization records helps your provider review your vaccination status." ] ; # Info: Lab Results (v2.0) sh:property [ sh:path checkup:hasLabResult ; sh:minCount 0 ; sh:node checkup:LabResultSummaryShape ; sh:severity sh:Info ; sh:message "Recent lab results give your provider context about your current health status." ] ; # Info: Procedures (v2.0) sh:property [ sh:path checkup:hasProcedure ; sh:minCount 0 ; sh:node checkup:ProcedureSummaryShape ; sh:severity sh:Info ; sh:message "Procedure history helps your provider understand your surgical and diagnostic background." ] . # ============================================================================ # PATIENT PROFILE SHAPE (Critical - blocks export if invalid) # ============================================================================ checkup:PatientProfileShape a sh:NodeShape ; sh:targetClass checkup:PatientProfile ; rdfs:label "Patient Profile Validation" ; rdfs:comment "Validates required patient demographics" ; # VIOLATION: Patient name required sh:property [ sh:path checkup:patientName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ; sh:severity sh:Violation ; sh:message "Patient name is required for intake forms" ] ; # VIOLATION: Date of birth required sh:property [ sh:path checkup:dateOfBirth ; sh:minCount 1 ; sh:datatype xsd:date ; sh:severity sh:Violation ; sh:message "Date of birth is required for intake forms" ] ; # VIOLATION: Biological sex required (for screening recommendations) sh:property [ sh:path checkup:biologicalSex ; sh:minCount 1 ; sh:datatype xsd:string ; sh:in ( "male" "female" "intersex" ) ; sh:severity sh:Violation ; sh:message "Biological sex is required for medical screening recommendations" ] ; # WARNING: Contact phone recommended sh:property [ sh:path checkup:contactPhone ; sh:minCount 0 ; sh:datatype xsd:string ; sh:severity sh:Warning ; sh:message "Contact phone number is typically required by healthcare providers" ] ; # INFO: Emergency contact suggested sh:property [ sh:path checkup:emergencyContact ; sh:minCount 0 ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Consider adding an emergency contact" ] ; # Computed age must be valid integer sh:property [ sh:path checkup:computedAge ; sh:datatype xsd:integer ; sh:minInclusive 18 ; sh:maxInclusive 64 ; sh:severity sh:Warning ; sh:message "Age should be between 18-64 for adult shapes (use senior shapes for 65+)" ] ; # Age group must be valid sh:property [ sh:path checkup:ageGroup ; sh:in ( "young_adult" "adult" ) ; sh:severity sh:Info ; sh:message "Age group should be young_adult (18-39) or adult (40-64)" ] ; # Gender identity is optional but validated if present sh:property [ sh:path checkup:genderIdentity ; sh:in ( "woman" "man" "non_binary" "other" "prefer_not_to_say" ) ; sh:severity sh:Info ; sh:message "Gender identity must be a valid option if provided" ] . # ============================================================================ # INSURANCE INFO SHAPE # ============================================================================ checkup:InsuranceInfoShape a sh:NodeShape ; sh:targetClass checkup:InsuranceInfo ; rdfs:label "Insurance Information Validation" ; rdfs:comment "Validates insurance details for intake forms" ; # WARNING: Provider name required if insurance section exists sh:property [ sh:path checkup:insuranceProvider ; sh:minCount 1 ; sh:datatype xsd:string ; sh:severity sh:Warning ; sh:message "Insurance provider name is required" ] ; # WARNING: Member ID required sh:property [ sh:path checkup:memberId ; sh:minCount 1 ; sh:datatype xsd:string ; sh:severity sh:Warning ; sh:message "Member ID / Policy number is required for insurance verification" ] ; # Coverage type must be valid sh:property [ sh:path checkup:coverageType ; sh:in ( "hmo" "ppo" "epo" "pos" "medicare" "medicaid" "tricare" "self_pay" "other" ) ; sh:severity sh:Info ; sh:message "Coverage type should be a recognized plan type" ] ; # INFO: PCP recommended sh:property [ sh:path checkup:primaryCareProvider ; sh:minCount 0 ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Primary care provider name is helpful for care coordination" ] . # ============================================================================ # MEDICATION SUMMARY SHAPE (Extended v1.2 - Phase 3 Medication Ontology) # ============================================================================ checkup:MedicationSummaryShape a sh:NodeShape ; sh:targetClass checkup:MedicationSummary ; rdfs:label "Medication Summary Validation" ; rdfs:comment "Validates medication entries with adherence data and episode linkage (Phase 3 extended)" ; # WARNING: Medication name required sh:property [ sh:path checkup:medicationName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ; sh:severity sh:Warning ; sh:message "Medication name is required" ] ; # INFO: Dose recommended sh:property [ sh:path checkup:dose ; sh:minCount 0 ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Including the dose helps prevent medication errors" ] ; # INFO: Frequency recommended sh:property [ sh:path checkup:frequency ; sh:in ( "once_daily" "twice_daily" "three_times_daily" "four_times_daily" "as_needed" "weekly" "monthly" "other" ) ; sh:severity sh:Info ; sh:message "Frequency should be a recognized dosing schedule" ] ; # Adherence status must be valid if present sh:property [ sh:path checkup:adherenceStatus ; sh:in ( "taking_as_prescribed" "usually_take" "sometimes_miss" "often_miss" "stopped" ) ; sh:severity sh:Info ; sh:message "Adherence status must be a recognized value" ] ; # Adherence barrier must be valid if present sh:property [ sh:path checkup:adherenceBarrier ; sh:in ( "cost" "side_effects" "forgetfulness" "access" "complexity" "other" "none" ) ; sh:severity sh:Info ; sh:message "Adherence barrier must be a recognized value" ] ; # Effectiveness must be valid if present sh:property [ sh:path checkup:perceivedEffectiveness ; sh:in ( "very_effective" "somewhat_effective" "not_sure" "not_effective" ) ; sh:severity sh:Info ; sh:message "Perceived effectiveness must be a recognized value" ] ; # Phase 3: Episode linkage (recommended for traceability) sh:property [ sh:path checkup:episodeId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Episode ID" ; sh:message "Linking to MedicationUseEpisode enables longitudinal tracking" ] ; # Phase 3: Start date sh:property [ sh:path checkup:startDate ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Start Date" ; sh:message "Start date helps track medication duration" ] ; # Phase 3: Indication (why patient takes it) sh:property [ sh:path checkup:indication ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Indication" ; sh:message "Understanding why patient takes medication improves clinical context" ] ; # Phase 3: Patient cost sh:property [ sh:path checkup:patientCost ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Patient Cost" ; sh:message "Cost tracking helps identify financial barriers" ] ; # Phase 3: Cost frequency sh:property [ sh:path checkup:costFrequency ; sh:in ( "per_month" "per_refill" "per_dose" "per_bottle" "annual" ) ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Cost Frequency" ; sh:message "Cost frequency should be a recognized value" ] ; # Phase 3: Functional impact sh:property [ sh:path checkup:functionalImpact ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Functional Impact" ; sh:message "How medication helps patient functionally (e.g., 'helps me sleep')" ] ; # Phase 3: Concerns for doctor sh:property [ sh:path checkup:concernsForDoctor ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Concerns For Doctor" ; sh:message "Concerns patient wants to discuss with provider" ] . # ============================================================================ # SUPPLEMENT SUMMARY SHAPE (v1.1 - Phase 3 Medication Ontology) # ============================================================================ checkup:SupplementSummaryShape a sh:NodeShape ; sh:targetClass checkup:SupplementSummary ; rdfs:label "Supplement Summary Validation" ; rdfs:comment "Validates supplement entries with regulatory classification. Explicitly separate from medications per Cascade Protocol." ; # WARNING: Supplement name required sh:property [ sh:path checkup:supplementName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ; sh:severity sh:Warning ; sh:message "Supplement name is required" ] ; # REQUIRED: Regulatory status (distinguishes from FDA-approved medications) sh:property [ sh:path checkup:regulatoryStatus ; sh:minCount 1 ; sh:in ( "dietarySupplement" "otcDrug" "homeopathic" "herbalRemedy" "unknown" ) ; sh:severity sh:Warning ; sh:name "Regulatory Status" ; sh:message "Regulatory status is required to distinguish supplements from FDA-approved medications" ] ; # INFO: Dose recommended sh:property [ sh:path checkup:supplementDose ; sh:minCount 0 ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Including the dose helps track supplement intake" ] ; # INFO: Frequency recommended sh:property [ sh:path checkup:supplementFrequency ; sh:in ( "once_daily" "twice_daily" "three_times_daily" "four_times_daily" "as_needed" "weekly" "monthly" "other" ) ; sh:severity sh:Info ; sh:message "Frequency should be a recognized dosing schedule" ] ; # Optional: Brand sh:property [ sh:path checkup:supplementBrand ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Brand helps identify specific products" ] ; # Optional: Form sh:property [ sh:path checkup:supplementForm ; sh:in ( "capsule" "tablet" "softgel" "liquid" "powder" "gummy" "spray" "patch" "tea" "tincture" "other" ) ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Physical form should be a recognized value" ] ; # Optional: Evidence strength sh:property [ sh:path checkup:evidenceStrength ; sh:in ( "strongEvidence" "moderateEvidence" "limitedEvidence" "traditionalUse" "noEvidence" "unknown" ) ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Evidence Strength" ; sh:message "Evidence strength helps set expectations about supplement efficacy" ] ; # Optional: Reason for use sh:property [ sh:path checkup:reasonForUse ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Reason for use helps providers understand patient goals" ] ; # Optional: Start date sh:property [ sh:path checkup:supplementStartDate ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Start date helps track supplement duration" ] ; # Optional: Is active sh:property [ sh:path checkup:supplementIsActive ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Active status indicates if currently taking" ] ; # Optional: DSLD ID (NIH identifier) sh:property [ sh:path checkup:dsldId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "DSLD ID" ; sh:message "NIH Dietary Supplement Label Database identifier for product lookup" ] ; # Optional: Doctor aware sh:property [ sh:path checkup:doctorAware ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Doctor Aware" ; sh:message "Indicates if healthcare provider knows about this supplement" ] ; # Optional: Patient cost sh:property [ sh:path checkup:patientCost ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:message "Cost tracking helps identify financial barriers" ] ; # Optional: Cost frequency sh:property [ sh:path checkup:costFrequency ; sh:in ( "per_month" "per_refill" "per_dose" "per_bottle" "annual" ) ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Cost frequency should be a recognized value" ] . # ============================================================================ # ALLERGY SUMMARY SHAPE # ============================================================================ checkup:AllergySummaryShape a sh:NodeShape ; sh:targetClass checkup:AllergySummary ; rdfs:label "Allergy Summary Validation" ; rdfs:comment "Validates allergy entries with reaction details" ; # WARNING: Allergen name required sh:property [ sh:path checkup:allergen ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ; sh:severity sh:Warning ; sh:message "Allergen name is required" ] ; # INFO: Reaction type recommended sh:property [ sh:path checkup:reaction ; sh:in ( "hives" "rash" "swelling" "difficulty_breathing" "anaphylaxis" "nausea" "diarrhea" "headache" "other" "unknown" ) ; sh:severity sh:Info ; sh:message "Reaction type should be a recognized value" ] ; # INFO: Severity recommended sh:property [ sh:path checkup:allergySeverity ; sh:in ( "mild" "moderate" "severe" "life_threatening" ) ; sh:severity sh:Info ; sh:message "Knowing allergy severity helps providers assess risk" ] ; # Category must be valid if present sh:property [ sh:path checkup:allergyCategory ; sh:in ( "medication" "food" "environmental" "latex" "contrast" "insect" "other" ) ; sh:severity sh:Info ; sh:message "Allergy category should be a recognized value" ] . # ============================================================================ # CONDITION SUMMARY SHAPE # ============================================================================ checkup:ConditionSummaryShape a sh:NodeShape ; sh:targetClass checkup:ConditionSummary ; rdfs:label "Condition Summary Validation" ; rdfs:comment "Validates health condition entries" ; # WARNING: Condition name required sh:property [ sh:path checkup:conditionName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ; sh:severity sh:Warning ; sh:message "Condition name is required" ] ; # Status must be valid sh:property [ sh:path checkup:conditionStatus ; sh:in ( "active" "resolved" "remission" "recurrence" "inactive" ) ; sh:severity sh:Info ; sh:message "Condition status should be a recognized value" ] ; # ICD-10-CM code pattern if present. # # The previous pattern required the second AND third characters to be # digits. The ICD-10-CM Official Guidelines for Coding and Reporting, # Section I.A.2, say the opposite: "Characters for categories, subcategories # and codes may be either a letter or a number. All categories are 3 # characters. ... Codes may be 3, 4, 5, 6 or 7 characters." Categories with # a letter in the third position are ordinary, not exotic — C4A (Merkel cell # carcinoma), D3A (benign neuroendocrine tumors) and M1A (chronic gout) are # all live ICD-10-CM categories, and every code under them was rejected. sh:property [ sh:path checkup:icd10Code ; sh:datatype xsd:string ; sh:pattern "^[A-Z][0-9A-Z]{2}(\\.[0-9A-Z]{1,4})?$" ; sh:severity sh:Info ; sh:message "ICD-10-CM code should be 3 to 7 characters: a letter, two letters-or-digits, then optionally a dot and 1 to 4 more (e.g., I10, E11.9, C4A.51, M1A.0110)" ] . # ============================================================================ # FAMILY HISTORY ENTRY SHAPE # ============================================================================ checkup:FamilyHistoryEntryShape a sh:NodeShape ; sh:targetClass checkup:FamilyHistoryEntry ; rdfs:label "Family History Entry Validation" ; rdfs:comment "Validates family health history entries" ; # INFO: Relative type required sh:property [ sh:path checkup:relativeType ; sh:minCount 1 ; sh:in ( "mother" "father" "sibling" "maternal_grandmother" "maternal_grandfather" "paternal_grandmother" "paternal_grandfather" "aunt" "uncle" "child" ) ; sh:severity sh:Info ; sh:message "Relative type is required for family history entries" ] ; # INFO: Condition required sh:property [ sh:path checkup:familyCondition ; sh:minCount 1 ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Health condition is required for family history entries" ] ; # Age at diagnosis should be reasonable sh:property [ sh:path checkup:ageAtDiagnosis ; sh:datatype xsd:integer ; sh:minInclusive 0 ; sh:maxInclusive 120 ; sh:severity sh:Info ; sh:message "Age at diagnosis should be between 0 and 120" ] ; # Age at death should be reasonable sh:property [ sh:path checkup:ageAtDeath ; sh:datatype xsd:integer ; sh:minInclusive 0 ; sh:maxInclusive 120 ; sh:severity sh:Info ; sh:message "Age at death should be between 0 and 120" ] . # ============================================================================ # VITAL SIGNS TREND SHAPE # ============================================================================ checkup:VitalSignsTrendShape a sh:NodeShape ; sh:targetClass checkup:VitalSignsTrend ; rdfs:label "Vital Signs Trend Validation" ; rdfs:comment "Validates vital signs with trend analysis" ; # Vital type must be valid sh:property [ sh:path checkup:vitalType ; sh:minCount 1 ; sh:in ( "blood_pressure" "systolic_bp" "diastolic_bp" "heart_rate" "weight" "height" "bmi" "temperature" "respiratory_rate" "oxygen_saturation" ) ; sh:severity sh:Info ; sh:message "Vital type must be a recognized measurement type" ] ; # Current value required sh:property [ sh:path checkup:currentValue ; sh:minCount 1 ; sh:severity sh:Info ; sh:message "Current value is required for vital signs" ] ; # Trend direction must be valid sh:property [ sh:path checkup:trendDirection ; sh:in ( "increasing" "decreasing" "stable" ) ; sh:severity sh:Info ; sh:message "Trend direction should be increasing, decreasing, or stable" ] ; # Data source must be valid sh:property [ sh:path checkup:dataSource ; sh:in ( "apple_health" "ehr" "manual" "device" "imported" ) ; sh:severity sh:Info ; sh:message "Data source should be a recognized value" ] . # ============================================================================ # SCREENING STATUS SHAPE # ============================================================================ checkup:ScreeningStatusShape a sh:NodeShape ; sh:targetClass checkup:ScreeningStatus ; rdfs:label "Screening Status Validation" ; rdfs:comment "Validates preventive screening status" ; # Screening type must be valid sh:property [ sh:path checkup:screeningType ; sh:minCount 1 ; sh:in ( "mammogram" "colonoscopy" "colorectal" "pap_smear" "psa" "bone_density" "aaa_ultrasound" "lung_ct" "diabetes_a1c" "lipid_panel" "hepatitis_c" "hiv" "std" "skin_cancer" ) ; sh:severity sh:Info ; sh:message "Screening type must be a recognized preventive screening" ] ; # Status must be valid sh:property [ sh:path checkup:screeningStatus ; sh:in ( "not_due" "due" "overdue" "completed" "declined" "not_applicable" ) ; sh:severity sh:Info ; sh:message "Screening status must be a recognized value" ] ; # Guideline source should be recognized sh:property [ sh:path checkup:guidelineSource ; sh:in ( "USPSTF" "ACS" "AAP" "ACOG" "ADA" "ACC_AHA" "other" ) ; sh:severity sh:Info ; sh:message "Guideline source should be a recognized organization" ] ; # Grade level for USPSTF sh:property [ sh:path checkup:gradeLevel ; sh:in ( "A" "B" "C" "D" "I" ) ; sh:severity sh:Info ; sh:message "USPSTF grade should be A, B, C, D, or I" ] . # ============================================================================ # VISIT ISSUE SHAPE # ============================================================================ checkup:VisitIssueShape a sh:NodeShape ; sh:targetClass checkup:VisitIssue ; rdfs:label "Visit Issue Validation" ; rdfs:comment "Validates chief complaints / issues to discuss" ; # Category must be valid sh:property [ sh:path checkup:issueCategory ; sh:minCount 1 ; sh:in ( "pain" "skin" "digestive" "respiratory" "cardiovascular" "mental_health" "neurological" "musculoskeletal" "urinary" "reproductive" "endocrine" "medication" "screening" "general" "other" ) ; sh:severity sh:Info ; sh:message "Issue category must be a recognized value" ] ; # Description required sh:property [ sh:path checkup:issueDescription ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 5 ; sh:severity sh:Info ; sh:message "Issue description is required and should be at least 5 characters" ] ; # Duration must be valid sh:property [ sh:path checkup:issueDuration ; sh:in ( "hours" "days" "weeks" "months" "years" ) ; sh:severity sh:Info ; sh:message "Duration should be hours, days, weeks, months, or years" ] ; # Severity must be valid sh:property [ sh:path checkup:issueSeverity ; sh:in ( "mild" "moderate" "severe" ) ; sh:severity sh:Info ; sh:message "Severity should be mild, moderate, or severe" ] ; # Frequency must be valid sh:property [ sh:path checkup:issueFrequency ; sh:in ( "constant" "intermittent" "occasional" "rare" ) ; sh:severity sh:Info ; sh:message "Frequency should be constant, intermittent, occasional, or rare" ] ; # Priority must be 1-10 sh:property [ sh:path checkup:issuePriority ; sh:datatype xsd:integer ; sh:minInclusive 1 ; sh:maxInclusive 10 ; sh:severity sh:Info ; sh:message "Priority should be between 1 (highest) and 10 (lowest)" ] . # ============================================================================ # DIAGNOSTIC TEST RESULT SHAPE # ============================================================================ checkup:DiagnosticTestResultShape a sh:NodeShape ; sh:targetClass checkup:DiagnosticTestResult ; rdfs:label "Diagnostic Test Result Validation" ; rdfs:comment "Validates external diagnostic test results" ; # Test type must be valid sh:property [ sh:path checkup:testType ; sh:minCount 1 ; sh:in ( "pots_check" "blood_pressure_series" "glucose_series" "ecg" "sleep_analysis" "hearing_test" "vision_test" "other" ) ; sh:severity sh:Info ; sh:message "Test type must be a recognized diagnostic test" ] ; # Test date required sh:property [ sh:path checkup:testDate ; sh:minCount 1 ; sh:datatype xsd:dateTime ; sh:severity sh:Info ; sh:message "Test date is required for diagnostic results" ] ; # Source app required sh:property [ sh:path checkup:sourceApp ; sh:minCount 1 ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Source app name is required for provenance" ] ; # Result summary required sh:property [ sh:path checkup:resultSummary ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 10 ; sh:severity sh:Info ; sh:message "Result summary is required and should be descriptive" ] . # ============================================================================ # SUGGESTED QUESTION SHAPE # ============================================================================ checkup:SuggestedQuestionShape a sh:NodeShape ; sh:targetClass checkup:SuggestedQuestion ; rdfs:label "Suggested Question Validation" ; rdfs:comment "Validates AI-generated or template questions" ; # Question text required sh:property [ sh:path checkup:questionText ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 10 ; sh:severity sh:Info ; sh:message "Question text is required and should be meaningful" ] ; # Category must be valid sh:property [ sh:path checkup:questionCategory ; sh:in ( "treatment_options" "side_effects" "prevention" "lifestyle" "referral" "diagnosis" "prognosis" "medication" "screening" "general" ) ; sh:severity sh:Info ; sh:message "Question category must be a recognized value" ] . # ============================================================================ # INTAKE QUESTIONNAIRE RESPONSE SHAPE (v1.6) # ============================================================================ checkup:IntakeQuestionnaireResponseShape a sh:NodeShape ; sh:targetClass checkup:IntakeQuestionnaireResponse ; rdfs:label "Intake Questionnaire Response Validation" ; rdfs:comment "Validates intake questionnaire responses using LOINC panel codes" ; # VIOLATION: Questionnaire ID (LOINC panel code) required sh:property [ sh:path checkup:questionnaireId ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ; sh:severity sh:Violation ; sh:message "Questionnaire ID (LOINC panel code) is required" ] ; # VIOLATION: Questionnaire title required sh:property [ sh:path checkup:questionnaireTitle ; sh:minCount 1 ; sh:datatype xsd:string ; sh:severity sh:Violation ; sh:message "Questionnaire title is required" ] ; # WARNING: At least one response item sh:property [ sh:path checkup:responseItem ; sh:minCount 1 ; sh:node checkup:ResponseItemShape ; sh:severity sh:Warning ; sh:message "Questionnaire should have at least one response item" ] ; # INFO: Completion timestamp sh:property [ sh:path checkup:completedAt ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "Consider adding completion timestamp" ] . # ============================================================================ # RESPONSE ITEM SHAPE (v1.6) # ============================================================================ checkup:ResponseItemShape a sh:NodeShape ; sh:targetClass checkup:ResponseItem ; rdfs:label "Response Item Validation" ; rdfs:comment "Validates individual question-answer pairs within a questionnaire response" ; # VIOLATION: Question text required sh:property [ sh:path checkup:itemText ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ; sh:severity sh:Violation ; sh:message "Question text is required for each response item" ] ; # INFO: Item link ID (LOINC code) sh:property [ sh:path checkup:itemLinkId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:message "LOINC item code helps standardize questionnaire items" ] ; # INFO: Answer text sh:property [ sh:path checkup:itemAnswer ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Consider providing an answer for this item" ] ; # INFO: Answer code (SNOMED CT or LOINC answer) sh:property [ sh:path checkup:itemAnswerCode ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Coded answer enables interoperability with clinical systems" ] ; # INFO: Answer code system URI sh:property [ sh:path checkup:itemAnswerCodeSystem ; sh:datatype xsd:anyURI ; sh:severity sh:Info ; sh:message "Code system URI identifies the terminology (e.g., SNOMED CT, LOINC)" ] ; # Nested child items (recursive structure) sh:property [ sh:path checkup:childItem ; sh:node checkup:ResponseItemShape ; sh:severity sh:Info ; sh:message "Child items support hierarchical questionnaire structures" ] . # ============================================================================ # DAILY CHECK-IN SHAPE (v2.0 - SelfReport subclass) # ============================================================================ checkup:DailyCheckInShape a sh:NodeShape ; sh:targetClass checkup:DailyCheckIn ; rdfs:label "Daily Check-in Validation" ; rdfs:comment "Validates daily medication adherence check-ins. Inherits base SelfReport validation via sh:node." ; # Inherit SelfReport base validation (reportDate, reportType, completionStatus) sh:node health:SelfReportShape ; # VIOLATION: Check-in date required sh:property [ sh:path checkup:checkInDate ; sh:minCount 1 ; sh:datatype xsd:dateTime ; sh:severity sh:Violation ; sh:message "Check-in date is required" ] ; # VIOLATION: Adherence response required sh:property [ sh:path checkup:checkInResponse ; sh:minCount 1 ; sh:in ( "allTaken" "missedSome" "skippedToday" ) ; sh:severity sh:Violation ; sh:message "Check-in response is required and must be allTaken, missedSome, or skippedToday" ] ; # INFO: Missed medication IDs (relevant when response is missedSome) sh:property [ sh:path checkup:missedMedicationIds ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Consider identifying which medications were missed for better adherence tracking" ] ; # INFO: Notes sh:property [ sh:path checkup:checkInNotes ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Notes can help explain adherence barriers or side effects" ] ; # INFO: Submission timestamp sh:property [ sh:path checkup:submittedAt ; sh:datatype xsd:dateTime ; sh:severity sh:Info ; sh:message "Submission timestamp helps track check-in timing patterns" ] . # ============================================================================ # IMMUNIZATION SUMMARY SHAPE (v2.0) # ============================================================================ checkup:ImmunizationSummaryShape a sh:NodeShape ; sh:targetClass checkup:ImmunizationSummary ; rdfs:label "Immunization Summary Validation" ; rdfs:comment "Validates immunization records for intake forms" ; # VIOLATION: Vaccine name required sh:property [ sh:path checkup:vaccineName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ; sh:severity sh:Violation ; sh:message "Vaccine name is required" ] ; # VIOLATION: Administration date required sh:property [ sh:path checkup:administrationDate ; sh:minCount 1 ; sh:datatype xsd:dateTime ; sh:severity sh:Violation ; sh:message "Vaccine administration date is required" ] ; # WARNING: Immunization status sh:property [ sh:path checkup:immunizationStatus ; sh:in ( "completed" "entered-in-error" "not-done" ) ; sh:severity sh:Warning ; sh:message "Immunization status should be completed, entered-in-error, or not-done" ] ; # WARNING: Vaccine code (CVX) sh:property [ sh:path checkup:vaccineCode ; sh:datatype xsd:string ; sh:severity sh:Warning ; sh:message "CVX vaccine code helps standardize immunization records" ] ; # WARNING: Vaccine category sh:property [ sh:path checkup:vaccineCategory ; sh:in ( "routine" "travel" "occupational" "other" ) ; sh:severity sh:Warning ; sh:message "Vaccine category should be routine, travel, occupational, or other" ] ; # INFO: Manufacturer sh:property [ sh:path checkup:manufacturer ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Vaccine manufacturer helps identify specific products" ] ; # INFO: Lot number sh:property [ sh:path checkup:lotNumber ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Lot number supports vaccine recall tracking" ] ; # INFO: Dose number in series sh:property [ sh:path checkup:doseNumber ; sh:datatype xsd:integer ; sh:minInclusive 1 ; sh:severity sh:Info ; sh:message "Dose number tracks progress in multi-dose vaccine series" ] ; # INFO: Notes sh:property [ sh:path checkup:immunizationNotes ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Additional notes about the immunization" ] . # ============================================================================ # LAB RESULT SUMMARY SHAPE (v2.0 - mixed namespace: clinical:/checkup:) # ============================================================================ checkup:LabResultSummaryShape a sh:NodeShape ; sh:targetClass checkup:LabResultSummary ; rdfs:label "Lab Result Summary Validation" ; rdfs:comment "Validates lab result records using mixed namespace pattern: clinical: for standardized data, checkup: for app-layer categorization" ; # VIOLATION: Test name required (clinical: namespace) sh:property [ sh:path clinical:testName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ; sh:severity sh:Violation ; sh:message "Lab test name is required" ] ; # WARNING: Result value sh:property [ sh:path clinical:resultValue ; sh:datatype xsd:string ; sh:severity sh:Warning ; sh:message "Lab result value helps providers interpret results" ] ; # WARNING: Lab category for intake display sh:property [ sh:path checkup:labCategory ; sh:in ( "metabolic" "hematology" "lipid" "thyroid" "vitamin" "other" ) ; sh:severity sh:Warning ; sh:message "Lab category should be metabolic, hematology, lipid, thyroid, vitamin, or other" ] ; # INFO: Result unit sh:property [ sh:path clinical:resultUnit ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Result unit helps interpret the lab value" ] ; # INFO: Reference range sh:property [ sh:path clinical:referenceRange ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Reference range provides context for the result" ] ; # INFO: Result date sh:property [ sh:path clinical:resultDate ; sh:datatype xsd:dateTime ; sh:severity sh:Info ; sh:message "Result date helps establish the timeline of lab work" ] ; # INFO: Interpretation sh:property [ sh:path clinical:interpretation ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Interpretation (normal, abnormal, critical) helps providers triage results" ] ; # INFO: Notes sh:property [ sh:path clinical:notes ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Additional clinical notes about the lab result" ] . # ============================================================================ # PROCEDURE SUMMARY SHAPE (v2.0 - mixed namespace: clinical:/checkup:) # ============================================================================ checkup:ProcedureSummaryShape a sh:NodeShape ; sh:targetClass checkup:ProcedureSummary ; rdfs:label "Procedure Summary Validation" ; rdfs:comment "Validates procedure records using mixed namespace pattern: checkup: for app-layer fields, clinical: for standardized clinical data" ; # VIOLATION: Procedure name required sh:property [ sh:path checkup:procedureName ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ; sh:severity sh:Violation ; sh:message "Procedure name is required" ] ; # WARNING: Procedure status (clinical: namespace) sh:property [ sh:path clinical:procedureStatus ; sh:datatype xsd:string ; sh:severity sh:Warning ; sh:message "Procedure status helps providers know if the procedure was completed" ] ; # WARNING: Performed date (clinical: namespace) sh:property [ sh:path clinical:performedDate ; sh:datatype xsd:dateTime ; sh:severity sh:Warning ; sh:message "Performed date helps establish the timeline of procedures" ] ; # INFO: Procedure category sh:property [ sh:path checkup:procedureCategory ; sh:in ( "surgical" "diagnostic" "therapeutic" "other" ) ; sh:severity sh:Info ; sh:message "Procedure category should be surgical, diagnostic, therapeutic, or other" ] ; # INFO: Procedure location sh:property [ sh:path checkup:procedureLocation ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Facility or location where the procedure was performed" ] ; # INFO: Body site sh:property [ sh:path clinical:bodySite ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Body site provides specificity about where the procedure was performed" ] ; # INFO: Performer sh:property [ sh:path clinical:performer ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Performer name supports care coordination" ] ; # INFO: Outcome sh:property [ sh:path clinical:outcome ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Outcome description provides context about the procedure result" ] . # ============================================================================ # DISCUSSION TOPIC SHAPE (v2.0) # ============================================================================ checkup:DiscussionTopicShape a sh:NodeShape ; sh:targetClass checkup:DiscussionTopic ; rdfs:label "Discussion Topic Validation" ; rdfs:comment "Validates AI-generated or user-created visit discussion topics" ; # VIOLATION: Topic text required sh:property [ sh:path checkup:topicText ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ; sh:severity sh:Violation ; sh:message "Discussion topic text is required" ] ; # WARNING: Topic category sh:property [ sh:path checkup:topicCategory ; sh:in ( "durationBased" "recentChange" "adherenceConcern" "sideEffectReport" "multipleForSame" "supplementDoctor" "interactionRisk" "refillDue" "effectivenessConcern" "costConcern" "custom" ) ; sh:severity sh:Warning ; sh:message "Topic category must be a recognized value" ] ; # INFO: Topic source sh:property [ sh:path checkup:topicSource ; sh:datatype xsd:string ; sh:severity sh:Info ; sh:message "Topic source indicates how the topic was generated (medication, supplement, adherencePattern, custom)" ] ; # INFO: Relevance score sh:property [ sh:path checkup:relevanceScore ; sh:datatype xsd:double ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ; sh:severity sh:Info ; sh:message "Relevance score should be between 0.0 and 1.0" ] ; # INFO: Is selected for next visit sh:property [ sh:path checkup:isSelected ; sh:datatype xsd:boolean ; sh:severity sh:Info ; sh:message "Selection status indicates if topic is active for the next visit" ] . # ============================================================================ # CHANGELOG # ============================================================================ # # v2.1 (2026-08-08) - checkup v3.3 # - checkup:icd10Code pattern corrected. It required digits in the second and # third characters; the ICD-10-CM Official Guidelines for Coding and # Reporting, Section I.A.2, state that "Characters for categories, # subcategories and codes may be either a letter or a number" and that codes # may be 3 to 7 characters. Categories such as C4A, D3A and M1A are ordinary # ICD-10-CM, and every code under them failed the old pattern. Info severity # throughout, so this changed an advisory that fired wrongly, not a rejection. # No other checkup shape changed. # # v2.0 (2026-02-18): WS8 - Shapes for v1.5-v2.0 ontology classes # - Added health: and clinical: prefix declarations for cross-namespace shapes # - Added 5 new NodeShapes: # - DailyCheckInShape: SelfReport subclass with sh:node health:SelfReportShape # inheritance, checkInDate/checkInResponse required (Violation), missedMedicationIds/ # checkInNotes/submittedAt optional (Info) # - ImmunizationSummaryShape: vaccineName/administrationDate required (Violation), # immunizationStatus/vaccineCode/vaccineCategory warnings, manufacturer/lotNumber/ # doseNumber/immunizationNotes info # - LabResultSummaryShape: mixed namespace — clinical:testName required (Violation), # clinical:resultValue warning, checkup:labCategory warning, clinical info properties # - ProcedureSummaryShape: mixed namespace — checkup:procedureName required (Violation), # clinical:procedureStatus/performedDate warnings, checkup:procedureCategory/ # procedureLocation and clinical:bodySite/performer/outcome info # - DiscussionTopicShape: topicText required (Violation), topicCategory warning, # topicSource/relevanceScore/isSelected info # - Added 5 new aggregate link properties to IntakeFormDataShape: # hasDailyCheckIn, hasDiscussionTopic, hasImmunization, hasLabResult, hasProcedure # - Total shapes: 20 (was 15) # # v1.6 (2026-02-09): Intake Questionnaire SHACL Shapes # - Added IntakeQuestionnaireResponseShape with LOINC panel code validation # - Added ResponseItemShape with question text validation and nested child items # - Added hasQuestionnaireResponse validation to IntakeFormDataShape # - Total shapes: 15 (was 13) # # v1.3 (2026-01-17): Phase 7 Medication Ontology Extensions # - Extended MedicationSummaryShape with episode linkage (episodeId, startDate, # indication) and patient meaning fields (patientCost, costFrequency, # functionalImpact, concernsForDoctor) # - Added SupplementSummaryShape with regulatoryStatus requirement # - Added hasSupplement validation to IntakeFormDataShape # # v1.0 (2026-01-03): Initial release # - 10 core shapes: IntakeFormData, PatientProfile, InsuranceInfo, # MedicationSummary, AllergySummary, ConditionSummary, FamilyHistoryEntry, # VitalSignsTrend, ScreeningStatus, VisitIssue, DiagnosticTestResult, # SuggestedQuestion # # ============================================================================ # END OF ADULT SHAPES # ============================================================================ ## SHACL Shapes: pots ## File: pots.shapes.ttl @prefix sh: . @prefix pots: . @prefix cascade: . @prefix xsd: . @prefix rdfs: . @prefix fhir: . # ============================================================================ # Cascade Protocol -- POTS Screening Vocabulary SHACL Validation Shapes # ============================================================================ # # Version: 1.0 (Phase 4, 2026-02-18) # Validates: pots:POTSCheckResult, pots:HeartRateMeasurement, # pots:BloodPressureMeasurement, pots:SymptomEvent, pots:PostureStability # # Layer 2 domain-specific vocabulary for POTS orthostatic testing protocol. # # Severity levels: # sh:Violation - Required fields; data is invalid without them # sh:Warning - Important optional fields; data is usable but diminished # sh:Info - Suggested fields; improve data quality when present # # ============================================================================ # ============================================================================ # Shape: POTS Check Result (Top-Level) # ============================================================================ pots:POTSCheckResultShape a sh:NodeShape ; sh:targetClass pots:POTSCheckResult ; rdfs:label "POTS Check Result Shape"@en ; rdfs:comment "Validation constraints for a complete POTS orthostatic screening check result bundle"@en ; # ------------------------------------------------------------------ # REQUIRED (sh:Violation) -- data is invalid without these # ------------------------------------------------------------------ # Required: date (when the check was performed) sh:property [ sh:path pots:date ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Check Date"@en ; sh:message "POTSCheckResult MUST have exactly one date (xsd:dateTime)"@en ] ; # Required: protocol (currently only "nasaLean") sh:property [ sh:path pots:protocol ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ("nasaLean") ; sh:severity sh:Violation ; sh:name "Test Protocol"@en ; sh:message "POTSCheckResult MUST specify protocol as 'nasaLean'"@en ] ; # Required: supineHeartRate (baseline HR observation) sh:property [ sh:path pots:supineHeartRate ; sh:class pots:HeartRateMeasurement ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Supine Heart Rate"@en ; sh:message "POTSCheckResult MUST have a supine heart rate measurement"@en ] ; # Required: potsThresholdMet (primary screening outcome) sh:property [ sh:path pots:potsThresholdMet ; sh:datatype xsd:boolean ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "POTS Threshold Met"@en ; sh:message "POTSCheckResult MUST declare whether POTS threshold was met"@en ] ; # ------------------------------------------------------------------ # WARNING (sh:Warning) -- important optional, data is diminished # ------------------------------------------------------------------ # Warning: standingHeartRates (needed for meaningful POTS assessment) sh:property [ sh:path pots:standingHeartRates ; sh:minCount 1 ; sh:severity sh:Warning ; sh:name "Standing Heart Rates"@en ; sh:message "Standing heart rate measurements needed for POTS assessment"@en ] ; # Warning: supineBloodPressure (important for orthostatic evaluation) sh:property [ sh:path pots:supineBloodPressure ; sh:class pots:BloodPressureMeasurement ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Supine Blood Pressure"@en ; sh:message "Supine blood pressure recommended for orthostatic evaluation"@en ] ; # Warning: dataProvenance (important for data trustworthiness) sh:property [ sh:path cascade:dataProvenance ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Data Provenance"@en ; sh:message "POTSCheckResult should have data provenance for traceability"@en ] ; # ------------------------------------------------------------------ # INFO (sh:Info) -- suggested fields for improved data quality # ------------------------------------------------------------------ # Info: maxHeartRateDelta (pre-computed delta, v1.2+) sh:property [ sh:path pots:maxHeartRateDelta ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Maximum Heart Rate Delta"@en ; sh:message "maxHeartRateDelta should be a non-negative number (bpm increase from baseline)"@en ] ; # Info: orthostaticHypotensionSuspected sh:property [ sh:path pots:orthostaticHypotensionSuspected ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Orthostatic Hypotension Suspected"@en ; sh:message "orthostaticHypotensionSuspected improves clinical context"@en ] ; # Info: orthostaticHypertensionSuspected (v1.1+) sh:property [ sh:path pots:orthostaticHypertensionSuspected ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Orthostatic Hypertension Suspected"@en ; sh:message "orthostaticHypertensionSuspected improves clinical context"@en ] ; # Info: isIncomplete (early termination flag) sh:property [ sh:path pots:isIncomplete ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Is Incomplete"@en ; sh:message "isIncomplete flag helps identify partial test results"@en ] ; # Info: earlyTerminationReason (v1.1+) sh:property [ sh:path pots:earlyTerminationReason ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Early Termination Reason"@en ; sh:message "earlyTerminationReason explains why a check ended early"@en ] ; # Info: userAgeAtTest (v1.2+, used for age-adjusted thresholds) sh:property [ sh:path pots:userAgeAtTest ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 150 ; sh:severity sh:Info ; sh:name "User Age at Test"@en ; sh:message "userAgeAtTest should be 0-150 years"@en ] ; # Info: baselineDurationSeconds sh:property [ sh:path pots:baselineDurationSeconds ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Baseline Duration (seconds)"@en ; sh:message "baselineDurationSeconds should be a non-negative integer"@en ] ; # Info: uprightDurationSeconds sh:property [ sh:path pots:uprightDurationSeconds ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Upright Duration (seconds)"@en ; sh:message "uprightDurationSeconds should be a non-negative integer"@en ] ; # Info: postureQualityScore (v1.1+, letter grade) sh:property [ sh:path pots:postureQualityScore ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("A" "B" "C" "D") ; sh:severity sh:Info ; sh:name "Posture Quality Score"@en ; sh:message "postureQualityScore must be A, B, C, or D"@en ] ; # Info: movementArtifactPercentage (v1.1+) sh:property [ sh:path pots:movementArtifactPercentage ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 100 ; sh:severity sh:Info ; sh:name "Movement Artifact Percentage"@en ; sh:message "movementArtifactPercentage should be 0-100"@en ] ; # Info: recoveryHeartRate (v1.1+) sh:property [ sh:path pots:recoveryHeartRate ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Recovery Heart Rate"@en ; sh:message "recoveryHeartRate should be a non-negative number (bpm)"@en ] ; # ------------------------------------------------------------------ # Schema version (required for compatibility tracking) # ------------------------------------------------------------------ sh:property [ sh:path cascade:schemaVersion ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9]+\\.[0-9]+$" ; sh:severity sh:Violation ; sh:name "Schema Version"@en ; sh:message "Schema version must be in format major.minor (e.g., '1.3')"@en ] . # ============================================================================ # Shape: Heart Rate Measurement # ============================================================================ pots:HeartRateMeasurementShape a sh:NodeShape ; sh:targetClass pots:HeartRateMeasurement ; rdfs:label "Heart Rate Measurement Shape"@en ; rdfs:comment "Validation constraints for individual heart rate observations captured during a POTS check. Uses FHIR Observation pattern with fhir:valueQuantity."@en ; # Required: heart rate value via fhir:valueQuantity sh:property [ sh:path fhir:valueQuantity ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Value Quantity"@en ; sh:message "HeartRateMeasurement MUST have a fhir:valueQuantity"@en ] ; # Required: the numeric heart rate value (fhir:value within valueQuantity) sh:property [ sh:path ( fhir:valueQuantity fhir:value ) ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 20 ; sh:maxInclusive 300 ; sh:severity sh:Violation ; sh:name "Heart Rate Value"@en ; sh:message "Heart rate value must be 20-300 bpm"@en ] ; # Info: secondsSinceStand (temporal position in standing phase) sh:property [ sh:path pots:secondsSinceStand ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Seconds Since Stand"@en ; sh:message "secondsSinceStand provides temporal context for the measurement"@en ] ; # Info: atSeconds (legacy timing property) sh:property [ sh:path pots:atSeconds ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "At Seconds"@en ; sh:message "atSeconds provides temporal context for the measurement"@en ] . # ============================================================================ # Shape: Blood Pressure Measurement # ============================================================================ pots:BloodPressureMeasurementShape a sh:NodeShape ; sh:targetClass pots:BloodPressureMeasurement ; rdfs:label "Blood Pressure Measurement Shape"@en ; rdfs:comment "Validation constraints for blood pressure observations. Uses FHIR Observation pattern with fhir:component for systolic and diastolic values."@en ; # Required: at least one component (systolic/diastolic pair) sh:property [ sh:path fhir:component ; sh:minCount 1 ; sh:severity sh:Violation ; sh:name "BP Components"@en ; sh:message "BloodPressureMeasurement MUST have fhir:component with systolic and diastolic values"@en ] ; # Info: secondsSinceStand (temporal position) sh:property [ sh:path pots:secondsSinceStand ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Seconds Since Stand"@en ; sh:message "secondsSinceStand provides temporal context for the measurement"@en ] . # ============================================================================ # Shape: Blood Pressure Component (Systolic) # ============================================================================ # # Validates the systolic component within a blood pressure fhir:component. # Targets blank nodes within fhir:component that carry sct:271649006 code. # Note: SHACL property paths on blank nodes are validated structurally; # this shape serves as reference documentation and for explicit validation. pots:SystolicComponentShape a sh:NodeShape ; rdfs:label "Systolic BP Component Shape"@en ; rdfs:comment "Validates systolic blood pressure component value within a FHIR Observation"@en ; sh:property [ sh:path ( fhir:valueQuantity fhir:value ) ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 40 ; sh:maxInclusive 300 ; sh:severity sh:Violation ; sh:name "Systolic Value"@en ; sh:message "Systolic blood pressure must be 40-300 mmHg"@en ] . # ============================================================================ # Shape: Blood Pressure Component (Diastolic) # ============================================================================ pots:DiastolicComponentShape a sh:NodeShape ; rdfs:label "Diastolic BP Component Shape"@en ; rdfs:comment "Validates diastolic blood pressure component value within a FHIR Observation"@en ; sh:property [ sh:path ( fhir:valueQuantity fhir:value ) ; sh:datatype xsd:double ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 20 ; sh:maxInclusive 200 ; sh:severity sh:Violation ; sh:name "Diastolic Value"@en ; sh:message "Diastolic blood pressure must be 20-200 mmHg"@en ] . # ============================================================================ # Shape: Symptom Event # ============================================================================ pots:SymptomEventShape a sh:NodeShape ; sh:targetClass pots:SymptomEvent ; rdfs:label "Symptom Event Shape"@en ; rdfs:comment "Validation constraints for user-reported symptom events during the standing phase of a POTS check"@en ; # Required: symptom type sh:property [ sh:path pots:symptom ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Symptom Type"@en ; sh:message "SymptomEvent MUST have a non-empty symptom type"@en ] ; # Info: notes (optional description, 100 char limit per ontology) sh:property [ sh:path pots:notes ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:maxLength 100 ; sh:severity sh:Info ; sh:name "Symptom Notes"@en ; sh:message "Symptom notes should be 100 characters or fewer"@en ] ; # Info: timestampSeconds (precise timing) sh:property [ sh:path pots:timestampSeconds ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "Timestamp Seconds"@en ; sh:message "timestampSeconds should be a non-negative number"@en ] . # ============================================================================ # Shape: Posture Stability # ============================================================================ pots:PostureStabilityShape a sh:NodeShape ; sh:targetClass pots:PostureStability ; rdfs:label "Posture Stability Shape"@en ; rdfs:comment "Validation constraints for posture stability readings from Core Motion sensors during the standing phase"@en ; # Warning: isStable (primary stability indicator) sh:property [ sh:path pots:isStable ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Is Stable"@en ; sh:message "PostureStability should indicate whether posture was stable"@en ] ; # Info: stabilityScore (numerical precision) sh:property [ sh:path pots:stabilityScore ; sh:datatype xsd:double ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 1 ; sh:severity sh:Info ; sh:name "Stability Score"@en ; sh:message "stabilityScore should be 0.0 (unstable) to 1.0 (perfectly stable)"@en ] ; # Info: atSeconds (temporal position) sh:property [ sh:path pots:atSeconds ; sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Info ; sh:name "At Seconds"@en ; sh:message "atSeconds provides temporal context for the stability reading"@en ] . # ============================================================================ # Changelog # ============================================================================ # # Version 1.0 (2026-02-18) # - Initial release of POTS SHACL shapes (Phase 4) # - POTSCheckResultShape: 4 required (Violation), 3 warning, 12 info properties # - HeartRateMeasurementShape: FHIR valueQuantity path, 20-300 bpm range # - BloodPressureMeasurementShape: FHIR component pattern validation # - SystolicComponentShape: 40-300 mmHg range # - DiastolicComponentShape: 20-200 mmHg range # - SymptomEventShape: required symptom string, optional notes (100 char) # - PostureStabilityShape: isStable (Warning), stabilityScore 0-1 (Info) # - Aligned with pots.ttl v1.3 ontology (2026-02-10) # ## SHACL Shapes: advisory (DRAFT — v1-draft) ## File: advisory.shapes.ttl @prefix sh: . @prefix advisory: . @prefix cascade: . @prefix prov: . @prefix xsd: . @prefix rdfs: . # ============================================================================ # Cascade Protocol — Advisory Vocabulary SHACL Validation Shapes # ============================================================================ # Version: 1.0-draft (TASK-0.4, 2026-05-05) # Validates against: advisory.ttl v1.0-draft (advisory@v1-draft.0.1) # # Shapes authored: # 1. advisory:CascadeAdvisoryPatchShape — envelope of a CAP file # 2. advisory:TrustedIssuerShape — per-pod trusted-issuer record # 3. advisory:AutoApplyPolicyShape — per-pod auto-apply policy # 4. advisory:AutoApplyScopeShape — (issuer x advisoryClass) tuple # # SEVERITY LEVELS # sh:Violation — Must fix (blocks application; the CAP applier MUST reject) # sh:Warning — Should address (recommended for completeness/UX) # sh:Info — Suggestion (nice to have) # # SCOPE OF THESE SHAPES # These shapes validate the RDF *envelope metadata* of a CAP file — i.e. the # triples describing the advisory itself. They do NOT validate: # # - LDPatch syntax (Add-only, single-Bind, ≤64 triples, etc.). PROFILE.md # constraints C1–C5 are pre-graph parser concerns enforced by the CAP # applier in cascade-cli (TASK-4.2). SHACL operates on the parsed RDF # graph after the patch body has been applied; the .ldpatch file syntax # is not visible to SHACL. # - Signature cryptographic verification. The detached-JWS signature # (D-Q4, RFC 7515 Ed25519) is verified at runtime by the CAP applier # (TASK-4.3) against the public key on the matching TrustedIssuer. # SHACL constrains the *presence and shape* of advisory:signature and # its claim properties but cannot verify the cryptographic signature. # - Trusted-issuer membership. Per Decision Tracker row D-Q3, the # trusted-issuer allowlist lives PER-POD (typically at # /trust/issuers.ttl), not in the spec. The protocol is positioned # as an extensible framework rather than a gatekeeper; community-driven # issuer evaluation/certification is a separate parallel workstream. # Therefore these shapes ONLY require that advisory:issuer is present # and is an IRI — issuer-trust enforcement is a runtime concern handled # by the CAP applier (cascade-cli) reading the per-pod trust graph, # NOT a spec-time SHACL check. # ============================================================================ # ============================================================================ # Shape 1: Cascade Advisory Patch (envelope) # ============================================================================ advisory:CascadeAdvisoryPatchShape a sh:NodeShape ; sh:targetClass advisory:CascadeAdvisoryPatch ; rdfs:label "Cascade Advisory Patch Shape"@en ; rdfs:comment "Validation constraints for the envelope of a CAP file (advisory:CascadeAdvisoryPatch). Implements the required-envelope-metadata clause of PROFILE.md C6 and the closed-enumeration constraints on advisory:advisoryClass and advisory:cadence. Trusted-issuer membership is NOT validated here — see the file-level comment about D-Q3."@en ; # ──────────────────────────────────────────────────────────────────── # Required envelope properties (PROFILE.md C6) # ──────────────────────────────────────────────────────────────────── # VIOLATION: humanSummary required — the user-facing string the queue UI # displays when prompting for approval. The applier MUST reject patches # without it (PROFILE.md §C6, advisory.ttl). sh:property [ sh:path advisory:humanSummary ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Human Summary"@en ; sh:message "CascadeAdvisoryPatch must carry exactly one non-empty advisory:humanSummary (PROFILE.md C6)"@en ] ; # VIOLATION: advisoryClass required and must be one of the six published # named individuals. Closed enumeration — adding a new class requires a # vocabulary version bump. sh:property [ sh:path advisory:advisoryClass ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( advisory:SafetyCritical advisory:VariantReclassification advisory:DrugInteraction advisory:LabReferenceRangeUpdate advisory:SurveillanceGuidelineUpdate advisory:CarrierFrequencyUpdate ) ; sh:severity sh:Violation ; sh:name "Advisory Class"@en ; sh:message "CascadeAdvisoryPatch must declare advisory:advisoryClass as one of the six published advisory:AdvisoryClass named individuals (SafetyCritical, VariantReclassification, DrugInteraction, LabReferenceRangeUpdate, SurveillanceGuidelineUpdate, CarrierFrequencyUpdate)"@en ] ; # VIOLATION: issuer required. Per D-Q3 the trusted-issuer allowlist is # per-pod, so this shape does NOT enforce membership — only that the # property is present and is an IRI. Runtime trust check happens in the # CAP applier against /trust/issuers.ttl. sh:property [ sh:path advisory:issuer ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; sh:severity sh:Violation ; sh:name "Issuer"@en ; sh:message "CascadeAdvisoryPatch must declare advisory:issuer as a single IRI. Per-pod issuer-trust enforcement (D-Q3) is a runtime concern handled by the CAP applier reading /trust/issuers.ttl, not this shape."@en ] ; # VIOLATION: issuedAt required. sh:property [ sh:path advisory:issuedAt ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Issued At"@en ; sh:message "CascadeAdvisoryPatch must declare exactly one advisory:issuedAt xsd:dateTime"@en ] ; # ──────────────────────────────────────────────────────────────────── # Optional envelope properties # ──────────────────────────────────────────────────────────────────── # advisoryId — optional, IRI when present. sh:property [ sh:path advisory:advisoryId ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; sh:severity sh:Warning ; sh:name "Advisory ID"@en ; sh:message "advisory:advisoryId is recommended so later advisories can target this one via advisory:supersedes"@en ] ; # supersedes — optional; if present, MUST point to a CascadeAdvisoryPatch # IRI (per advisory.ttl rdfs:range). sh:property [ sh:path advisory:supersedes ; sh:nodeKind sh:IRI ; sh:class advisory:CascadeAdvisoryPatch ; sh:severity sh:Warning ; sh:name "Supersedes"@en ; sh:message "advisory:supersedes must reference an advisory:CascadeAdvisoryPatch IRI"@en ] ; # applicableUntil — optional dateTime upper bound for clinical applicability. sh:property [ sh:path advisory:applicableUntil ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Applicable Until"@en ; sh:message "advisory:applicableUntil should be an xsd:dateTime; absence means open-ended applicability"@en ] ; # profileVersion — optional but recommended; lets the applier reject # patches written against an unsupported profile version. sh:property [ sh:path advisory:profileVersion ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Profile Version"@en ; sh:message "advisory:profileVersion is recommended (e.g. \"0.1\") so the applier can reject unsupported profile versions"@en ] ; # issuerName — optional human-readable convenience name. sh:property [ sh:path advisory:issuerName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Issuer Display Name"@en ] ; # evidenceUrl — optional IRI of supporting public evidence (ClinVar, CPIC, etc.). sh:property [ sh:path advisory:evidenceUrl ; sh:nodeKind sh:IRI ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Evidence URL"@en ; sh:message "advisory:evidenceUrl should be an IRI pointing at publicly-citable supporting evidence"@en ] ; # appliesToActiveOnly — optional boolean (PGx-style gating). sh:property [ sh:path advisory:appliesToActiveOnly ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Applies To Active Only"@en ] ; # cadence — optional override; closed enumeration over the six Cadence # named individuals. sh:property [ sh:path advisory:cadence ; sh:maxCount 1 ; sh:in ( advisory:EveryAppOpen advisory:Daily advisory:Weekly advisory:Monthly advisory:Quarterly advisory:Annually ) ; sh:severity sh:Violation ; sh:name "Cadence"@en ; sh:message "advisory:cadence, if present, must be one of the six published advisory:Cadence named individuals (EveryAppOpen, Daily, Weekly, Monthly, Quarterly, Annually)"@en ] ; # ──────────────────────────────────────────────────────────────────── # Detached-JWS signature properties (D-Q4) # ──────────────────────────────────────────────────────────────────── # Cryptographic verification is a runtime concern (TASK-4.3); these # constraints only validate presence/shape of the signature claims. sh:property [ sh:path advisory:signature ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Signature"@en ; sh:message "advisory:signature (detached JWS, RFC 7515) is strongly recommended; absence forces the applier to queue the patch for explicit user trust review (PROFILE.md §Profile validation step 4)"@en ] ; sh:property [ sh:path advisory:signatureIssuer ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Signature Issuer (iss)"@en ] ; sh:property [ sh:path advisory:signatureIssuedAt ; sh:datatype xsd:long ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Signature Issued At (iat)"@en ] ; sh:property [ sh:path advisory:signatureExpiresAt ; sh:datatype xsd:long ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Signature Expires At (exp)"@en ] ; sh:property [ sh:path advisory:signatureContentType ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Signature Content Type (cty)"@en ; sh:message "advisory:signatureContentType, if present, should be \"application/x-cascade-advisory-patch\" for v0.1"@en ] . # ============================================================================ # Shape 2: Trusted Issuer (per-pod, per D-Q3) # ============================================================================ advisory:TrustedIssuerShape a sh:NodeShape ; sh:targetClass advisory:TrustedIssuer ; rdfs:label "Trusted Issuer Shape"@en ; rdfs:comment "Validation constraints for advisory:TrustedIssuer records. These records live per-pod (typically at /trust/issuers.ttl) — the protocol does NOT operate a centralized registry. How the record entered the pod's trust graph is captured by advisory:trustSource."@en ; # VIOLATION: trustedIssuerId required — the IRI/DID that MUST equal # advisory:issuer on patches signed by this issuer. sh:property [ sh:path advisory:trustedIssuerId ; sh:datatype xsd:anyURI ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Trusted Issuer ID"@en ; sh:message "TrustedIssuer must carry exactly one advisory:trustedIssuerId (xsd:anyURI). It MUST equal advisory:issuer on any patch this issuer signs."@en ] ; # VIOLATION: trustedIssuerPublicKey required — used to verify the # detached-JWS Ed25519 signature. sh:property [ sh:path advisory:trustedIssuerPublicKey ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Trusted Issuer Public Key"@en ; sh:message "TrustedIssuer must carry exactly one advisory:trustedIssuerPublicKey (Ed25519, JWK string or multibase-encoded raw bytes) for signature verification"@en ] ; # VIOLATION: trustSource required — provenance of how the issuer # entered the pod's trust graph. Closed enumeration over the four # TrustSourceEnum named individuals. sh:property [ sh:path advisory:trustSource ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( advisory:RecommendedStarterList advisory:UserAdded advisory:ImportedFromRegistry advisory:VerifiedViaDID ) ; sh:severity sh:Violation ; sh:name "Trust Source"@en ; sh:message "TrustedIssuer must declare advisory:trustSource as one of the four published advisory:TrustSourceEnum named individuals (RecommendedStarterList, UserAdded, ImportedFromRegistry, VerifiedViaDID)"@en ] ; # WARNING: trustedIssuerName recommended for UI display. sh:property [ sh:path advisory:trustedIssuerName ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Trusted Issuer Display Name"@en ; sh:message "advisory:trustedIssuerName is recommended for human-readable UI display"@en ] ; # INFO: trustAddedAt — provenance timestamp. sh:property [ sh:path advisory:trustAddedAt ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Trust Added At"@en ] . # ============================================================================ # Shape 3: Auto-Apply Policy (per-pod) # ============================================================================ advisory:AutoApplyPolicyShape a sh:NodeShape ; sh:targetClass advisory:AutoApplyPolicy ; rdfs:label "Auto-Apply Policy Shape"@en ; rdfs:comment "Validation constraints for advisory:AutoApplyPolicy records. Auto-apply is opt-in by default; the user adds policy records explicitly. Each policy MUST link to at least one advisory:AutoApplyScope tuple."@en ; # VIOLATION: appliesTo required — at least one AutoApplyScope tuple. sh:property [ sh:path advisory:appliesTo ; sh:minCount 1 ; sh:class advisory:AutoApplyScope ; sh:severity sh:Violation ; sh:name "Applies To (Auto-Apply Scope)"@en ; sh:message "AutoApplyPolicy must link to at least one advisory:AutoApplyScope via advisory:appliesTo"@en ] ; # INFO: effectiveFrom recommended. sh:property [ sh:path advisory:effectiveFrom ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Effective From"@en ] ; # INFO: effectiveUntil optional. sh:property [ sh:path advisory:effectiveUntil ; sh:datatype xsd:dateTime ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Effective Until"@en ] ; # INFO: requiresQuorum forward-compat flag (v0.1 applier ignores). sh:property [ sh:path advisory:requiresQuorum ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Requires Quorum"@en ; sh:message "advisory:requiresQuorum is a v1.0 forward-compat hook; v0.1 appliers MUST treat it as if absent"@en ] . # ============================================================================ # Shape 4: Auto-Apply Scope ((issuer x advisoryClass) tuple) # ============================================================================ advisory:AutoApplyScopeShape a sh:NodeShape ; sh:targetClass advisory:AutoApplyScope ; rdfs:label "Auto-Apply Scope Shape"@en ; rdfs:comment "Validation constraints for advisory:AutoApplyScope tuples. Each scope MUST pair exactly one trusted issuer with exactly one advisory class — the auto-apply matcher fires only when a patch's (advisory:issuer, advisory:advisoryClass) equals at least one scope."@en ; # VIOLATION: scopeIssuer required and must reference a TrustedIssuer. sh:property [ sh:path advisory:scopeIssuer ; sh:minCount 1 ; sh:maxCount 1 ; sh:class advisory:TrustedIssuer ; sh:severity sh:Violation ; sh:name "Scope Issuer"@en ; sh:message "AutoApplyScope must declare exactly one advisory:scopeIssuer referencing an advisory:TrustedIssuer record"@en ] ; # VIOLATION: scopeAdvisoryClass required and must be one of the six # published AdvisoryClass named individuals. sh:property [ sh:path advisory:scopeAdvisoryClass ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( advisory:SafetyCritical advisory:VariantReclassification advisory:DrugInteraction advisory:LabReferenceRangeUpdate advisory:SurveillanceGuidelineUpdate advisory:CarrierFrequencyUpdate ) ; sh:severity sh:Violation ; sh:name "Scope Advisory Class"@en ; sh:message "AutoApplyScope must declare exactly one advisory:scopeAdvisoryClass drawn from the six published advisory:AdvisoryClass named individuals"@en ] . # ============================================================================ # Cross-vocabulary note # ============================================================================ # cascade:AdvisoryApplicationActivity is declared in core/v1 (v3.1) and its # SHACL shape (cascade:AdvisoryApplicationActivityShape, requiring prov:used # minCount 2) lives in core.shapes.ttl. This file does NOT redeclare or # augment that shape — its constraints are domain-general and core-shaped. # If a future advisory-domain-specific constraint is identified (e.g., the # matched-record reference must be an IRI not a literal), it will be added # here as an additional NodeShape targeting cascade:AdvisoryApplicationActivity. # ============================================================================ # Changelog # ============================================================================ # # Version 1.0-draft (2026-05-05) — TASK-0.4 # - Initial release of advisory vocabulary SHACL shapes. # - CascadeAdvisoryPatchShape: VIOLATION for humanSummary, advisoryClass # (sh:in over six AdvisoryClass individuals), issuer (IRI, presence-only; # per-pod trust enforcement is runtime per D-Q3), issuedAt; sh:in for # cadence over six Cadence individuals; sh:class advisory:CascadeAdvisoryPatch # on supersedes; WARNING for advisoryId, profileVersion, signature; INFO # for issuerName, evidenceUrl, appliesToActiveOnly, applicableUntil, and # the JWS signature claim properties. # - TrustedIssuerShape: VIOLATION for trustedIssuerId, trustedIssuerPublicKey, # trustSource (sh:in over four TrustSourceEnum individuals); WARNING for # trustedIssuerName; INFO for trustAddedAt. # - AutoApplyPolicyShape: VIOLATION for appliesTo (minCount 1, sh:class # advisory:AutoApplyScope); INFO for effectiveFrom, effectiveUntil, # requiresQuorum. # - AutoApplyScopeShape: VIOLATION for scopeIssuer (sh:class # advisory:TrustedIssuer) and scopeAdvisoryClass (sh:in over six # AdvisoryClass individuals). # - Out-of-scope (documented at file head): LDPatch syntactic constraints # C1–C5 (parser concern, TASK-4.2); cryptographic signature verification # (runtime concern, TASK-4.3); trusted-issuer membership enforcement # (per-pod runtime concern, D-Q3). ## SHACL Shapes: diabetes (DRAFT — v1-draft): none published yet ## SHACL Shapes: evidence (DRAFT — v1-draft) ## File: evidence.shapes.ttl # Cascade Evidence Vocabulary — SHACL shapes (v1-draft) # # The grounding invariant is expressed in SHACL CORE (sh:or / sh:not / sh:in), # NOT SHACL-SPARQL. This is deliberate: `cascade validate` uses # rdf-validate-shacl, which does not support SHACL-SPARQL constraints. A # sh:sparql formulation would parse but silently never fire. Keep this Core. # # CHANGELOG # v1-draft.0.2 (2026-07-01) — Verdict taxonomy v2: AssertionShape moves to the # facet model. The generalized grounding invariant: a grounded result # (settled Settled, direction Supports/Contradicts/Mixed, basis not None) of # EITHER basis requires >= 1 evidence link. Facet-consistency constraints: # NeedsEvidence must not carry a grounded direction; a grounded direction # must not carry basis None. The legacy evidence:verdict branch is retained # (deprecated) so draft-period data still validates; every assertion must # carry settled OR the legacy verdict. Removal of the legacy branch at v1.0. # # SHACL Core subtlety this file leans on: a property shape with sh:in # constrains only EXISTING values, so it is vacuously satisfied when the # property is absent. Each sh:or branch below reads as "the property is absent # or has only the listed values"; absence therefore never triggers the # grounded-direction constraints, which keeps legacy (facet-less) data valid. @prefix evidence: . @prefix sh: . @prefix owl: . @prefix rdfs: . @prefix xsd: . a owl:Ontology ; owl:imports ; rdfs:comment "SHACL shapes for the Cascade Evidence vocabulary (v1-draft). SHACL Core only, so rdf-validate-shacl enforces every constraint including the grounding invariant."@en . evidence:AssertionShape a sh:NodeShape ; sh:targetClass evidence:Assertion ; sh:property [ sh:path evidence:assertionText ; sh:minCount 1 ; sh:datatype xsd:string ; sh:message "Every Assertion must carry assertionText." ] ; # ── Facet enumerations (canonical form, v1-draft.0.2) ──────────────────── sh:property [ sh:path evidence:direction ; sh:maxCount 1 ; sh:in ( evidence:Supports evidence:Contradicts evidence:Mixed evidence:None ) ; sh:message "direction must be one of the DirectionValue individuals (Supports | Contradicts | Mixed | None)." ] ; sh:property [ sh:path evidence:basis ; sh:maxCount 1 ; sh:in ( evidence:Record evidence:Literature evidence:RecordAndLiterature evidence:None ) ; sh:message "basis must be one of the BasisValue individuals (Record | Literature | RecordAndLiterature | None)." ] ; sh:property [ sh:path evidence:strength ; sh:maxCount 1 ; sh:in ( evidence:Strong evidence:Moderate evidence:Weak ) ; sh:message "strength must be one of the StrengthValue individuals (Strong | Moderate | Weak)." ] ; sh:property [ sh:path evidence:settled ; sh:maxCount 1 ; sh:in ( evidence:Settled evidence:NeedsEvidence ) ; sh:message "settled must be one of the SettledValue individuals (Settled | NeedsEvidence)." ] ; sh:property [ sh:path evidence:reason ; sh:maxCount 1 ; sh:in ( evidence:NoRecord evidence:NeedsLiterature evidence:NotCheckableByNature ) ; sh:message "reason must be one of the NeedsEvidenceReasonValue individuals (NoRecord | NeedsLiterature | NotCheckableByNature)." ] ; sh:property [ sh:path evidence:confidence ; sh:maxCount 1 ; sh:datatype xsd:decimal ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ; sh:message "confidence must be an xsd:decimal in [0.0, 1.0]." ] ; # ── Legacy verdict (deprecated; one-release transition) ────────────────── sh:property [ sh:path evidence:verdict ; sh:maxCount 1 ; sh:in ( evidence:Supported evidence:Contradicted evidence:Unverifiable evidence:NeedsLiterature ) ; sh:message "verdict (deprecated) must be one of the four VerdictValue individuals when present." ] ; # Every assertion is graded: it carries the facet form (settled) or, for # draft-period data, the deprecated flat verdict. sh:or ( [ sh:property [ sh:path evidence:settled ; sh:minCount 1 ] ] [ sh:property [ sh:path evidence:verdict ; sh:minCount 1 ] ] ) ; # ── Grounding invariant, generalized (SHACL Core) ──────────────────────── # A grounded result — settled Settled AND direction Supports/Contradicts/ # Mixed AND basis not None — of EITHER basis requires >= 1 evidence link. # Reads: not settled, or direction None, or basis None, or >= 1 link. sh:or ( [ sh:property [ sh:path evidence:settled ; sh:in ( evidence:NeedsEvidence ) ] ] [ sh:property [ sh:path evidence:direction ; sh:in ( evidence:None ) ] ] [ sh:property [ sh:path evidence:basis ; sh:in ( evidence:None ) ] ] [ sh:property [ sh:path evidence:hasEvidenceLink ; sh:minCount 1 ] ] ) ; # ── Facet consistency: NeedsEvidence must not carry a grounded direction. # Reads: settled is Settled (or absent), or direction is None (or absent). sh:or ( [ sh:property [ sh:path evidence:settled ; sh:in ( evidence:Settled ) ] ] [ sh:property [ sh:path evidence:direction ; sh:in ( evidence:None ) ] ] ) ; # ── Facet consistency: a grounded direction requires a real basis. # Reads: direction is None (or absent), or basis is not None. sh:or ( [ sh:property [ sh:path evidence:direction ; sh:in ( evidence:None ) ] ] [ sh:property [ sh:path evidence:basis ; sh:not [ sh:in ( evidence:None ) ] ] ] ) ; # ── Legacy grounding invariant (deprecated branch; removal at v1.0): # Supported/Contradicted => >= 1 evidence link. sh:or ( [ sh:property [ sh:path evidence:verdict ; sh:not [ sh:in ( evidence:Supported evidence:Contradicted ) ] ] ] [ sh:property [ sh:path evidence:hasEvidenceLink ; sh:minCount 1 ] ] ) ; sh:message "Grounding invariant: a grounded Assertion (settled Settled with direction Supports/Contradicts/Mixed and basis not None, or legacy verdict Supported/Contradicted) must carry at least one evidence link (evidence:hasEvidenceLink), and its facets must be mutually consistent." . evidence:EvidenceLinkShape a sh:NodeShape ; sh:targetClass evidence:EvidenceLink ; sh:property [ sh:path evidence:evidenceStance ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( evidence:Supports evidence:Contradicts evidence:Contextual ) ] ; sh:or ( [ sh:property [ sh:path evidence:citesRecord ; sh:minCount 1 ] ] [ sh:property [ sh:path evidence:citesSource ; sh:minCount 1 ] ] ) . evidence:CitationShape a sh:NodeShape ; sh:targetClass evidence:Citation ; sh:property [ sh:path evidence:citationTitle ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:or ( [ sh:property [ sh:path evidence:doi ; sh:minCount 1 ] ] [ sh:property [ sh:path evidence:pmid ; sh:minCount 1 ] ] ) . ## SHACL Shapes: genomics (DRAFT — v1-draft) ## File: genomics.shapes.ttl @prefix sh: . @prefix genomics: . @prefix cascade: . @prefix prov: . @prefix xsd: . @prefix rdfs: . # ============================================================================ # Cascade Protocol — Genomics Vocabulary SHACL Validation Shapes # ============================================================================ # Version: 1.0-draft (TASK-0.2, 2026-05-05; +v1-draft.0.2 additions, 2026-05-05) # Validates against: genomics.ttl v1.0-draft (genomics@v1-draft.0.2) # # Changelog: # v1-draft.0.2 (2026-05-05): added VariantShape property shapes for the # four new Variant-domain properties — genomics:refAllele, altAllele, # genomicStartEnd (Info-severity datatype/maxCount checks), # genomics:variantAlleleFrequency (Violation-severity 0.0–1.0 range), # genomics:somaticStatus (Violation-severity sh:in over Germline / # Somatic / UnknownSomaticStatus). All optional (no minCount). No # NodeShape added for the new generic genomics:reportedRecord predicate # — it is intentionally domain-broad on GeneticTest with no rdfs:range, # so a SHACL constraint would fight the design intent. Existing fixtures # never reference any of these new properties, so existing data passes # unchanged. # v1-draft.0.1 (2026-05-05): initial 12 NodeShapes (TASK-0.2). # # Shapes authored (12 sh:NodeShape): # 1. genomics:VariantShape — required identity, stable IDs, # quality tier # 2. genomics:VariantInterpretationShape — D-Q5 cardinality, ACMG enum, # D-QUALITY-TIER safety constraint # 3. genomics:HaplotypeShape — required star-allele identity # 4. genomics:DiplotypeShape — exactly two haplotypes (hapA+hapB) # 5. genomics:CopyNumberVariantShape — interval + copy number, VAF range # 6. genomics:SubmitterAssertionShape — submitter + asserted class # 7. genomics:PedigreeShape — proband + at least one member # 8. genomics:PedigreeMemberShape — required relationship role # 9. genomics:GeneticTestShape — required test type # 10. genomics:GeneticTestOrderShape — required order status + date # 11. genomics:SequencingRunShape — coverage, technology, reference # 12. genomics:RawFileShape — format, hash, location, reference # # SEVERITY LEVELS (matching advisory.shapes.ttl / health.shapes.ttl) # sh:Violation — Must fix; data is invalid or unsafe without it. # (Required-field absences and the D-QUALITY-TIER safety # constraint use this severity.) # sh:Warning — Should address; data is technically usable but missing # recommended completeness or audit fields. # sh:Info — Suggested enrichment; nice-to-have. # # ---------------------------------------------------------------------------- # D-Q5 — Multi-condition VariantInterpretation cardinality # ---------------------------------------------------------------------------- # Each VariantInterpretation is per-(variant, condition) pair: SHACL enforces # genomics:variantInterpreted minCount/maxCount = 1 and genomics:condition # minCount/maxCount = 1 on VariantInterpretationShape. Multi-condition cases # produce multiple Interpretation instances (one per condition); same-pair # reclassifications produce new Interpretation instances chained via # prov:wasRevisionOf. # # The reclassification-chain integrity rule (a revision MUST share both # variantInterpreted and condition with its predecessor) is NOT expressible # in pure SHACL Core without sh:sparql, because the constraint is across two # focus nodes (the revising and revised interpretations). This is documented # here as a cross-class invariant; the supplementing code-level check lives # in cascade-cli (TASK-4.x — applier validation step on incoming # VariantReclassification advisories). Adding a sh:sparql block here was # considered and rejected as making the shape harder to read for the # narrow benefit; the cascade-cli check is the authoritative enforcer. # # ---------------------------------------------------------------------------- # D-QUALITY-TIER — Safety constraint on Pathogenic / LikelyPathogenic # ---------------------------------------------------------------------------- # This is THE critical constraint of this shape file. For every # VariantInterpretation whose acmgClassification is Pathogenic or # LikelyPathogenic, EITHER: # (a) the referenced genomics:Variant has # genomics:dataQualityTier genomics:ClinicalGrade, OR # (b) the Interpretation itself has genomics:requiresConfirmation true. # # Rationale: prevents consumer-grade pathogenic calls from masquerading as # clinical findings. Phase 2C (consumer-array imports — 23andMe, AncestryDNA, # MyHeritage, FTDNA) auto-tags every variant ConsumerGrade and would, without # this constraint, be able to push Pathogenic interpretations into the # advisory engine. The advisory engine MUST NOT auto-apply care # recommendations to confirmation-required interpretations regardless of # issuer trust. # # Expression: VariantInterpretationShape uses sh:xone (exactly-one of two # disjoint conditions): the qualifying-class predicate sh:not [sh:in # (Pathogenic LikelyPathogenic)] (i.e., classification is benign-leaning or # VUS — constraint trivially satisfied), OR a require-mitigation predicate # combining (variant.dataQualityTier = ClinicalGrade) with # (requiresConfirmation = true) using sh:or. Using sh:or at the inner level # is correct because either mitigation is sufficient. Using sh:xone at the # outer level is the readable way to encode "if-then" in pure SHACL Core # (one of: classification is non-pathogenic, or one of the mitigations # applies). The violation message names the affected interpretation IRI # and lists both remediation paths so callers know what to change. # # ---------------------------------------------------------------------------- # Vocabulary gaps acknowledged in shape comments (not enforced) # ---------------------------------------------------------------------------- # - genomics:GeneticTest has no testDate property in v1-draft.0.1; only # genomics:GeneticTestOrder has genomics:orderedAt. Test-result date is # captured indirectly via prov:generatedAtTime on the GeneticTest # instance. GeneticTestShape requires testType but not a test date; # date enforcement deferred to v1-draft.0.2 if a property is added. # - genomics:SubmitterAssertion has no assertionDate property and no # forward reference to its parent VariantInterpretation; the inverse # link is genomics:aggregatedFrom (from Interpretation to Assertion). # SubmitterAssertionShape therefore requires submitter and asserted # classification but cannot directly enforce a parent-interpretation # reference. This is acceptable because Interpretation→Assertion is the # authoritative direction in the model. # ============================================================================ # ============================================================================ # Shape 1: Variant # ============================================================================ genomics:VariantShape a sh:NodeShape ; sh:targetClass genomics:Variant ; rdfs:label "Variant Shape"@en ; rdfs:comment "Validation constraints for genomics:Variant. Enforces required gene-symbol identity, presence of at least one stable identifier across the four supported registries (CAid, VRS, ClinVar, dbSNP), and presence of a data-quality tier (used by the D-QUALITY-TIER safety constraint on downstream VariantInterpretations)."@en ; # WARNING: geneSymbol recommended (HGNC approved). Most clinical-grade # imports carry it, but a Variant remains valid via other identifiers # (CAid, VRS, ClinVar VCV, dbSNP rs, or the refAllele+altAllele+ # genomicStartEnd coordinate triple). VRS preserve-only imports # (D-Q6) and gene-less VCF records legitimately lack gene context; # downgraded from Violation to Warning in v1-draft.0.3 per the # 2026-05-06 VRS test-fixture tie-break (cascade-coordination/ # tie-breaks/2026-05-06-vrs-geneSymbol-shape.md). When present, the # symbol must be exactly one non-empty string. sh:property [ sh:path genomics:geneSymbol ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Warning ; sh:name "Gene Symbol"@en ; sh:message "Recommended: genomics:geneSymbol (HGNC approved symbol, e.g., 'BRCA2'). Variant remains valid via stable identifiers (CAid/VRS/ClinVar/dbSNP) or VCF coordinates (refAllele+altAllele+genomicStartEnd) when source data lacks gene context."@en ] ; # VIOLATION: dataQualityTier required and must be one of the four # published DataQualityTier named individuals. Required so the # D-QUALITY-TIER safety constraint on VariantInterpretation can fire # against a non-empty tier value. sh:property [ sh:path genomics:dataQualityTier ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:ClinicalGrade genomics:ResearchGrade genomics:ConsumerGrade genomics:UnknownQuality ) ; sh:severity sh:Violation ; sh:name "Data Quality Tier"@en ; sh:message "Variant must declare genomics:dataQualityTier as one of the four published genomics:DataQualityTier named individuals (ClinicalGrade, ResearchGrade, ConsumerGrade, UnknownQuality). Required so the D-QUALITY-TIER safety constraint on downstream VariantInterpretations can be evaluated."@en ] ; # Stable-identifier datatype constraints (per-property). The cross-class # 'at least one stable ID' constraint is enforced by sh:or at the # NodeShape level below. sh:property [ sh:path genomics:caId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "ClinGen Allele Registry CAid"@en ] ; sh:property [ sh:path genomics:vrsId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "GA4GH VRS Identifier"@en ] ; sh:property [ sh:path genomics:clinvarVariationId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "ClinVar Variation ID"@en ] ; sh:property [ sh:path genomics:dbsnpRsId ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "dbSNP rsID"@en ] ; # VIOLATION: at least one stable identifier among caId / vrsId / # clinvarVariationId / dbsnpRsId. HGVS strings are descriptive but # normalization-fragile — a stable ID is what joins reliably across # databases. sh:or evaluates each branch; at least one must report # zero violations. sh:or ( [ sh:path genomics:caId ; sh:minCount 1 ] [ sh:path genomics:vrsId ; sh:minCount 1 ] [ sh:path genomics:clinvarVariationId ; sh:minCount 1 ] [ sh:path genomics:dbsnpRsId ; sh:minCount 1 ] ) ; # WARNING: zygosity recommended for variant-level reasoning. sh:property [ sh:path genomics:zygosity ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Zygosity"@en ; sh:message "genomics:zygosity is recommended; absence forces downstream tools to assume Heterozygous"@en ] ; # WARNING: mosaicismFraction range check (0.0..1.0 inclusive) when present. sh:property [ sh:path genomics:mosaicismFraction ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ; sh:severity sh:Violation ; sh:name "Mosaicism Fraction (VAF)"@en ; sh:message "genomics:mosaicismFraction, if present, must be an xsd:decimal between 0.0 and 1.0 inclusive (variant allele fraction)"@en ] ; # Added in v1-draft.0.2: VAF as a distinct property from mosaicismFraction. # Range 0.0–1.0 inclusive. Optional — no minCount. sh:property [ sh:path genomics:variantAlleleFrequency ; sh:datatype xsd:decimal ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:maxInclusive 1.0 ; sh:severity sh:Violation ; sh:name "Variant Allele Frequency"@en ; sh:message "genomics:variantAlleleFrequency, if present, must be an xsd:decimal between 0.0 and 1.0 inclusive (fraction of reads supporting the alternate allele)"@en ] ; # Added in v1-draft.0.2: VCF-style coordinate properties. All optional. sh:property [ sh:path genomics:refAllele ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Reference Allele"@en ] ; sh:property [ sh:path genomics:altAllele ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Alternate Allele"@en ] ; sh:property [ sh:path genomics:genomicStartEnd ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Genomic Start-End Coordinates"@en ] ; # Added in v1-draft.0.2: somatic status (germline / somatic / unknown). # Optional but value must be one of three published named individuals. sh:property [ sh:path genomics:somaticStatus ; sh:maxCount 1 ; sh:in ( genomics:Germline genomics:Somatic genomics:UnknownSomaticStatus ) ; sh:severity sh:Violation ; sh:name "Somatic Status"@en ; sh:message "genomics:somaticStatus, if present, must be one of the three published genomics:SomaticStatus named individuals (Germline, Somatic, UnknownSomaticStatus)"@en ] . # Cross-class node-level violation message for the 'at least one stable ID' # constraint. SHACL Core sh:or does not directly produce a top-level message # tied to the parent shape, so we attach a message via sh:message on the # parent NodeShape — most engines surface it when the sh:or branch fails. genomics:VariantShape sh:message "Variant must carry at least one stable identifier among genomics:caId, genomics:vrsId, genomics:clinvarVariationId, or genomics:dbsnpRsId. HGVS strings alone are insufficient for reliable cross-database joins."@en . # ============================================================================ # Shape 2: Variant Interpretation (D-Q5 + D-QUALITY-TIER) # ============================================================================ genomics:VariantInterpretationShape a sh:NodeShape ; sh:targetClass genomics:VariantInterpretation ; rdfs:label "Variant Interpretation Shape"@en ; rdfs:comment "Validation constraints for genomics:VariantInterpretation. Implements D-Q5 (each Interpretation is per-(variant, condition); cardinality 1..1 on both genomics:variantInterpreted and genomics:condition) and D-QUALITY-TIER (the safety constraint on Pathogenic / LikelyPathogenic classifications)."@en ; # ──────────────────────────────────────────────────────────────────── # D-Q5: variantInterpreted cardinality 1..1. # Range widened in v1-draft.0.3 from genomics:Variant alone to # {Variant, CopyNumberVariant, Haplotype} via sh:or — clinical # interpretations attach to all three molecular-record types # (e.g., the retinoblastoma phenopacket interprets a chr13 CNV). # ──────────────────────────────────────────────────────────────────── sh:property [ sh:path genomics:variantInterpreted ; sh:or ( [ sh:class genomics:Variant ] [ sh:class genomics:CopyNumberVariant ] [ sh:class genomics:Haplotype ] ) ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Variant Interpreted"@en ; sh:message "VariantInterpretation must reference exactly one Variant / CopyNumberVariant / Haplotype via genomics:variantInterpreted (D-Q5 — cardinality 1..1; multi-variant cases produce multiple Interpretation instances)"@en ] ; # ──────────────────────────────────────────────────────────────────── # D-Q5: condition cardinality 1..1 # ──────────────────────────────────────────────────────────────────── sh:property [ sh:path genomics:condition ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Associated Condition"@en ; sh:message "VariantInterpretation must reference exactly one condition via genomics:condition (D-Q5 — cardinality 1..1; multi-condition cases produce multiple Interpretation instances, one per condition)"@en ] ; # ──────────────────────────────────────────────────────────────────── # ACMG classification — required, closed enumeration over the 5 # published AcmgClass named individuals. # ──────────────────────────────────────────────────────────────────── sh:property [ sh:path genomics:acmgClassification ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:Pathogenic genomics:LikelyPathogenic genomics:VUS genomics:LikelyBenign genomics:Benign ) ; sh:severity sh:Violation ; sh:name "ACMG Classification"@en ; sh:message "VariantInterpretation must declare genomics:acmgClassification as one of the five published genomics:AcmgClass named individuals (Pathogenic, LikelyPathogenic, VUS, LikelyBenign, Benign)"@en ] ; # ──────────────────────────────────────────────────────────────────── # Review status — optional but, when present, must be one of the seven # published ReviewStatus named individuals. # ──────────────────────────────────────────────────────────────────── sh:property [ sh:path genomics:reviewStatus ; sh:maxCount 1 ; sh:in ( genomics:NoAssertionProvided genomics:CriteriaNotProvided genomics:SingleSubmitter genomics:ConflictingSubmissions genomics:MultipleSubmittersNoConflict genomics:ExpertPanelReviewed genomics:PracticeGuideline ) ; sh:severity sh:Warning ; sh:name "Review Status"@en ; sh:message "genomics:reviewStatus, if present, must be one of the seven published genomics:ReviewStatus named individuals (NoAssertionProvided, CriteriaNotProvided, SingleSubmitter, ConflictingSubmissions, MultipleSubmittersNoConflict, ExpertPanelReviewed, PracticeGuideline). Recommended for advisory-engine trust weighting."@en ] ; # WARNING: requiresConfirmation must be xsd:boolean if present. sh:property [ sh:path genomics:requiresConfirmation ; sh:datatype xsd:boolean ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Requires Confirmation"@en ; sh:message "genomics:requiresConfirmation, if present, must be a single xsd:boolean"@en ] ; # WARNING: interpretedDate recommended for chronology and reclassification chains. sh:property [ sh:path genomics:interpretedDate ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Interpretation Date"@en ; sh:message "genomics:interpretedDate (xsd:date) is recommended; needed for reclassification chronology"@en ] ; # ──────────────────────────────────────────────────────────────────── # D-QUALITY-TIER safety constraint # ──────────────────────────────────────────────────────────────────── # If acmgClassification is Pathogenic or LikelyPathogenic, then EITHER # (a) the referenced Variant has dataQualityTier ClinicalGrade, OR # (b) requiresConfirmation is true on the Interpretation itself. # # Encoded as sh:xone (exactly one) of two disjoint conditions: # Branch X: classification is NOT in {Pathogenic, LikelyPathogenic} # (constraint trivially satisfied, no mitigation needed) # Branch Y: classification IS in {Pathogenic, LikelyPathogenic} AND # at least one mitigation applies (ClinicalGrade variant OR # requiresConfirmation true). # # sh:xone is used so that a malformed instance with an out-of-range # acmgClassification is also caught — the acmgClassification property # constraint above (sh:in) handles the enum check; the sh:xone here # handles the conditional requirement. sh:xone ( # Branch X: classification is non-pathogenic (or absent — trapped # by the acmgClassification property constraint above). [ sh:path genomics:acmgClassification ; sh:not [ sh:in ( genomics:Pathogenic genomics:LikelyPathogenic ) ] ] # Branch Y: classification is pathogenic-leaning AND mitigation present. [ sh:and ( # Y.1: classification IS in {Pathogenic, LikelyPathogenic} [ sh:path genomics:acmgClassification ; sh:in ( genomics:Pathogenic genomics:LikelyPathogenic ) ] # Y.2: at least one mitigation applies — either the linked # variant is ClinicalGrade tier, OR the interpretation has # requiresConfirmation true. [ sh:or ( [ sh:path ( genomics:variantInterpreted genomics:dataQualityTier ) ; sh:hasValue genomics:ClinicalGrade ] [ sh:path genomics:requiresConfirmation ; sh:hasValue true ] ) ] ) ] ) ; sh:severity sh:Violation ; sh:message "D-QUALITY-TIER safety constraint violated: this VariantInterpretation declares acmgClassification Pathogenic or LikelyPathogenic, but the referenced genomics:Variant is NOT genomics:ClinicalGrade tier AND genomics:requiresConfirmation is not true on this Interpretation. To resolve: EITHER promote the variant's genomics:dataQualityTier to genomics:ClinicalGrade (only valid if the underlying sequencing is CLIA/CAP/ISO15189-certified, >=30x coverage or validated panel-equivalent, with raw files retained), OR set genomics:requiresConfirmation true on this Interpretation. Consumer-grade pathogenic calls MUST NOT auto-apply care recommendations regardless of issuer trust."@en . # ============================================================================ # Shape 3: Haplotype # ============================================================================ genomics:HaplotypeShape a sh:NodeShape ; sh:targetClass genomics:Haplotype ; rdfs:label "Haplotype Shape"@en ; rdfs:comment "Validation constraints for genomics:Haplotype. The clinically actionable identifier in pharmacogenomics (PharmVar star alleles) and HLA typing is the star-allele symbol; this is required. Component variants are recommended for full provenance but optional in v1-draft.0.1."@en ; # VIOLATION: starAlleleSymbol required — the clinically actionable PGx/HLA # identifier (e.g., 'CYP2C19*2', 'HLA-DQB1*02:01'). sh:property [ sh:path genomics:starAlleleSymbol ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Star Allele Symbol"@en ; sh:message "Haplotype must carry exactly one non-empty genomics:starAlleleSymbol (PharmVar/HLA nomenclature, e.g. 'CYP2C19*2')"@en ] ; # WARNING: hasComponent recommended for full provenance. # # The acceptable component classes are ENUMERATED rather than left to # subclass inference. sh:class is resolved over SHACL-instances in the DATA # graph, so `sh:class genomics:Variant` alone accepts only nodes explicitly # typed genomics:Variant. A component typed genomics:CopyNumberVariant # would be rejected by a validator that performs no entailment and accepted # by one that does. See validation/index.md, rule S3. sh:property [ sh:path genomics:hasComponent ; sh:or ( [ sh:class genomics:Variant ] [ sh:class genomics:CopyNumberVariant ] ) ; sh:severity sh:Warning ; sh:name "Component Variants"@en ; sh:message "genomics:hasComponent is recommended; absence prevents downstream tools from auditing which constituent variants define this star allele. When present, each component must be a genomics:Variant or genomics:CopyNumberVariant."@en ] . # ============================================================================ # Shape 4: Diplotype # ============================================================================ genomics:DiplotypeShape a sh:NodeShape ; sh:targetClass genomics:Diplotype ; rdfs:label "Diplotype Shape"@en ; rdfs:comment "Validation constraints for genomics:Diplotype. A diplotype is, by definition, a pair of haplotypes — the ontology models this with two distinct properties (genomics:hapA, genomics:hapB) rather than a single 2..2 cardinality property. Each must be present exactly once."@en ; # VIOLATION: hapA required, exactly one Haplotype. sh:property [ sh:path genomics:hapA ; sh:class genomics:Haplotype ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Haplotype A"@en ; sh:message "Diplotype must carry exactly one genomics:hapA referencing a genomics:Haplotype"@en ] ; # VIOLATION: hapB required, exactly one Haplotype. sh:property [ sh:path genomics:hapB ; sh:class genomics:Haplotype ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Haplotype B"@en ; sh:message "Diplotype must carry exactly one genomics:hapB referencing a genomics:Haplotype (a diplotype is by definition a pair of haplotypes)"@en ] ; # WARNING: diplotypeNotation recommended for direct match against PGx # guideline tables (CPIC, DPWG). sh:property [ sh:path genomics:diplotypeNotation ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Diplotype Notation"@en ; sh:message "genomics:diplotypeNotation (e.g., '*1/*2') is recommended for direct matching against CPIC/DPWG guideline tables"@en ] . # ============================================================================ # Shape 5: Copy Number Variant # ============================================================================ # CopyNumberVariant is rdfs:subClassOf genomics:Variant in the ontology, but a # subclass axiom does NOT carry VariantShape's constraints across. SHACL # resolves sh:targetClass over SHACL-instances in the DATA graph, and pod # records carry rdf:type triples, not schema axioms, so an axiom that lives # only in genomics.ttl reaches nothing at validation time. Until v1-draft.0.5 # this shape asserted the inheritance in its comment and did not implement it, # with the result that CNVs were never checked for genomics:dataQualityTier # (a Violation-severity constraint the D-QUALITY-TIER safety rule on # VariantInterpretation depends on) or for carrying a stable identifier. # The inheritance is now stated explicitly with sh:node, which every conforming # validator applies whether or not it entails. See validation/index.md, rule S2. genomics:CopyNumberVariantShape a sh:NodeShape ; sh:targetClass genomics:CopyNumberVariant ; rdfs:label "Copy Number Variant Shape"@en ; rdfs:comment "Validation constraints specific to genomics:CopyNumberVariant. Applies VariantShape's constraints (geneSymbol, dataQualityTier, at least one stable ID) explicitly via sh:node, and adds the CNV-specific requirements: integer copy number, interval coordinates with reference."@en ; # Applies every VariantShape constraint to this focus node. Explicit, so the # constraint set does not depend on the validator performing entailment. sh:node genomics:VariantShape ; # VIOLATION: copyNumber required, integer. sh:property [ sh:path genomics:copyNumber ; sh:datatype xsd:integer ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Violation ; sh:name "Copy Number"@en ; sh:message "CopyNumberVariant must carry exactly one genomics:copyNumber as a non-negative xsd:integer (0 = homozygous deletion, 1 = heterozygous deletion, 3+ = duplication/amplification)"@en ] ; # VIOLATION: cnvIntervalRef required — interval coordinates are # uninterpretable without their reference contig. sh:property [ sh:path genomics:cnvIntervalRef ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "CNV Interval Reference"@en ; sh:message "CopyNumberVariant must carry exactly one genomics:cnvIntervalRef (reference contig name or accession, e.g., 'NC_000013.11')"@en ] ; # VIOLATION: cnvIntervalStart required, integer ≥ 1 (1-based inclusive). sh:property [ sh:path genomics:cnvIntervalStart ; sh:datatype xsd:integer ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 1 ; sh:severity sh:Violation ; sh:name "CNV Interval Start"@en ; sh:message "CopyNumberVariant must carry exactly one genomics:cnvIntervalStart as a positive xsd:integer (1-based, inclusive)"@en ] ; # VIOLATION: cnvIntervalEnd required, integer ≥ 1. sh:property [ sh:path genomics:cnvIntervalEnd ; sh:datatype xsd:integer ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 1 ; sh:severity sh:Violation ; sh:name "CNV Interval End"@en ; sh:message "CopyNumberVariant must carry exactly one genomics:cnvIntervalEnd as a positive xsd:integer (1-based, inclusive)"@en ] . # ============================================================================ # Shape 6: Submitter Assertion # ============================================================================ genomics:SubmitterAssertionShape a sh:NodeShape ; sh:targetClass genomics:SubmitterAssertion ; rdfs:label "Submitter Assertion Shape"@en ; rdfs:comment "Validation constraints for genomics:SubmitterAssertion (e.g., one ClinVar SCV record). Requires submitter identity and the asserted ACMG classification — these are what makes the assertion auditable. Note: the v1-draft.0.1 vocabulary does NOT define an assertionDate property nor a forward reference to the parent VariantInterpretation; the inverse link is genomics:aggregatedFrom (Interpretation→Assertion), which is the authoritative direction."@en ; # VIOLATION: submitter required. sh:property [ sh:path genomics:submitter ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Submitter"@en ; sh:message "SubmitterAssertion must carry exactly one non-empty genomics:submitter (lab/consortium/expert-panel name, e.g., 'ENIGMA', 'Ambry Genetics')"@en ] ; # VIOLATION: assertedClassification required and must be one of the five # published AcmgClass named individuals. sh:property [ sh:path genomics:assertedClassification ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:Pathogenic genomics:LikelyPathogenic genomics:VUS genomics:LikelyBenign genomics:Benign ) ; sh:severity sh:Violation ; sh:name "Asserted ACMG Classification"@en ; sh:message "SubmitterAssertion must declare genomics:assertedClassification as one of the five published genomics:AcmgClass named individuals"@en ] ; # WARNING: scvAccession recommended for ClinVar-sourced records. sh:property [ sh:path genomics:scvAccession ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "SCV Accession"@en ; sh:message "genomics:scvAccession is recommended for ClinVar-sourced submissions (audit trail to the original SCV record)"@en ] ; # INFO: submitterCategory recommended for review-status weighting. sh:property [ sh:path genomics:submitterCategory ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Submitter Category"@en ; sh:message "genomics:submitterCategory is suggested; drives review-status weighting in the aggregate"@en ] . # ============================================================================ # Shape 7: Pedigree # ============================================================================ genomics:PedigreeShape a sh:NodeShape ; sh:targetClass genomics:Pedigree ; rdfs:label "Pedigree Shape"@en ; rdfs:comment "Validation constraints for genomics:Pedigree. A pedigree must have exactly one proband (the index patient who brought the family to attention) and at least one member (the proband counts)."@en ; # VIOLATION: proband required, exactly one PedigreeMember. sh:property [ sh:path genomics:proband ; sh:class genomics:PedigreeMember ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Proband"@en ; sh:message "Pedigree must reference exactly one genomics:proband (the index patient who brought the family to clinical attention)"@en ] ; # VIOLATION: hasMember required, at least one PedigreeMember (the proband # counts as a member). sh:property [ sh:path genomics:hasMember ; sh:class genomics:PedigreeMember ; sh:minCount 1 ; sh:severity sh:Violation ; sh:name "Pedigree Members"@en ; sh:message "Pedigree must reference at least one genomics:hasMember (the proband should be included)"@en ] . # ============================================================================ # Shape 8: Pedigree Member # ============================================================================ genomics:PedigreeMemberShape a sh:NodeShape ; sh:targetClass genomics:PedigreeMember ; rdfs:label "Pedigree Member Shape"@en ; rdfs:comment "Validation constraints for genomics:PedigreeMember. The relationship-to-proband role is required and drawn from the closed HL7 v3 RoleCode-aligned named individuals declared in genomics.ttl."@en ; # VIOLATION: relativeRole required and must be one of the published # RelationshipRole named individuals (HL7 v3 RoleCode-aligned). sh:property [ sh:path genomics:relativeRole ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:Proband genomics:MTH genomics:FTH genomics:SIS genomics:BRO genomics:DAU genomics:SON genomics:MAUNT genomics:MUNCLE genomics:PAUNT genomics:PUNCLE genomics:MGRMTH genomics:MGRFTH genomics:PGRMTH genomics:PGRFTH genomics:MCOUSN genomics:PCOUSN genomics:NIECE genomics:NEPHEW ) ; sh:severity sh:Violation ; sh:name "Relationship Role"@en ; sh:message "PedigreeMember must declare genomics:relativeRole as one of the published HL7 v3 RoleCode-aligned RelationshipRole named individuals (Proband, MTH, FTH, SIS, BRO, DAU, SON, MAUNT, MUNCLE, PAUNT, PUNCLE, MGRMTH, MGRFTH, PGRMTH, PGRFTH, MCOUSN, PCOUSN, NIECE, NEPHEW)"@en ] ; # WARNING: relativeSex recommended for inheritance reasoning. sh:property [ sh:path genomics:relativeSex ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:in ("male" "female" "unknown") ; sh:severity sh:Warning ; sh:name "Biological Sex"@en ; sh:message "genomics:relativeSex is recommended (one of 'male', 'female', 'unknown'); needed for X-linked / Y-linked inheritance reasoning"@en ] ; # WARNING: carrierStatus, when present, must be one of the published values. sh:property [ sh:path genomics:carrierStatus ; sh:maxCount 1 ; sh:in ( genomics:PositiveAffected genomics:PositiveUnaffected genomics:Negative genomics:NotTested genomics:Obligate ) ; sh:severity sh:Violation ; sh:name "Carrier Status"@en ; sh:message "genomics:carrierStatus, if present, must be one of the five published genomics:CarrierStatus named individuals (PositiveAffected, PositiveUnaffected, Negative, NotTested, Obligate)"@en ] . # ============================================================================ # Shape 9: Genetic Test # ============================================================================ # Note: v1-draft.0.1 does NOT declare a testDate property on GeneticTest. # Test-result date is captured indirectly via prov:generatedAtTime on the # instance. Date enforcement deferred to v1-draft.0.2 if/when a property is # added to the ontology. genomics:GeneticTestShape a sh:NodeShape ; sh:targetClass genomics:GeneticTest ; rdfs:label "Genetic Test Shape"@en ; rdfs:comment "Validation constraints for genomics:GeneticTest (FHIR DiagnosticReport-aligned). Requires test type. Note: testDate is not yet a declared property in v1-draft.0.1; chronology comes from prov:generatedAtTime."@en ; # VIOLATION: testType required and must be one of the published TestType # named individuals. sh:property [ sh:path genomics:testType ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:SingleGeneTest genomics:GenePanelTest genomics:ExomeSequencing genomics:GenomeSequencing genomics:ChromosomalMicroarray genomics:KaryotypeAnalysis genomics:MLPA genomics:RepeatExpansionTest genomics:MethylationStudy genomics:RNASequencing ) ; sh:severity sh:Violation ; sh:name "Genetic Test Type"@en ; sh:message "GeneticTest must declare genomics:testType as one of the ten published genomics:TestType named individuals (SingleGeneTest, GenePanelTest, ExomeSequencing, GenomeSequencing, ChromosomalMicroarray, KaryotypeAnalysis, MLPA, RepeatExpansionTest, MethylationStudy, RNASequencing)"@en ] ; # WARNING: genePanel recommended — distinguishes 'no variant in BRCA1/2' # from 'no variant across a 47-gene panel'. sh:property [ sh:path genomics:genePanel ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Gene Panel"@en ; sh:message "genomics:genePanel is recommended; the analytical scope distinguishes a clinically meaningful negative result from absence-of-evidence"@en ] ; # WARNING: performingLab recommended for provenance. sh:property [ sh:path genomics:performingLab ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Performing Laboratory"@en ; sh:message "genomics:performingLab is recommended for provenance audit"@en ] . # ============================================================================ # Shape 10: Genetic Test Order # ============================================================================ # Note on naming: the task spec referenced 'orderDate', but v1-draft.0.1 names # the property genomics:orderedAt (xsd:dateTime). This shape uses orderedAt. genomics:GeneticTestOrderShape a sh:NodeShape ; sh:targetClass genomics:GeneticTestOrder ; rdfs:label "Genetic Test Order Shape"@en ; rdfs:comment "Validation constraints for genomics:GeneticTestOrder (FHIR ServiceRequest-aligned). Requires order status (closed enumeration over the four OrderStatus named individuals) and order timestamp (genomics:orderedAt)."@en ; # VIOLATION: orderStatus required and must be one of the four published # OrderStatus named individuals. sh:property [ sh:path genomics:orderStatus ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:OrderPending genomics:OrderInProgress genomics:OrderResulted genomics:OrderCancelled ) ; sh:severity sh:Violation ; sh:name "Order Status"@en ; sh:message "GeneticTestOrder must declare genomics:orderStatus as one of the four published genomics:OrderStatus named individuals (OrderPending, OrderInProgress, OrderResulted, OrderCancelled)"@en ] ; # VIOLATION: orderedAt required. sh:property [ sh:path genomics:orderedAt ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "Ordered At"@en ; sh:message "GeneticTestOrder must carry exactly one genomics:orderedAt xsd:dateTime"@en ] ; # WARNING: resultedIn — when status is OrderResulted, a fulfilled order # should link to its result. Pure SHACL cannot express the conditional # 'IF status=Resulted THEN resultedIn minCount 1' cleanly without sh:xone # patterns; we only constrain the range here. Conditional check lives in # the cascade-cli importer (TASK-1.x). sh:property [ sh:path genomics:resultedIn ; sh:class genomics:GeneticTest ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "Resulted In"@en ; sh:message "genomics:resultedIn, if present, must reference exactly one genomics:GeneticTest. When orderStatus is OrderResulted, this link should be populated (conditional check lives in cascade-cli importer)."@en ] . # ============================================================================ # Shape 11: Sequencing Run # ============================================================================ genomics:SequencingRunShape a sh:NodeShape ; sh:targetClass genomics:SequencingRun ; rdfs:label "Sequencing Run Shape"@en ; rdfs:comment "Validation constraints for genomics:SequencingRun. Coverage depth, sequencing technology, and reference genome are required — these three drive the data-quality tier inference upstream of the D-QUALITY-TIER safety constraint. Laboratory certification is optional but, when present, must be one of the four published values."@en ; # VIOLATION: coverageDepth required, decimal ≥ 0. sh:property [ sh:path genomics:coverageDepth ; sh:datatype xsd:decimal ; sh:minCount 1 ; sh:maxCount 1 ; sh:minInclusive 0.0 ; sh:severity sh:Violation ; sh:name "Coverage Depth"@en ; sh:message "SequencingRun must carry exactly one genomics:coverageDepth as a non-negative xsd:decimal (e.g., 30.0 for typical clinical WGS). Required for ClinicalGrade tier inference (>=30x WGS or validated panel-equivalent)."@en ] ; # VIOLATION: sequencingTechnology required and must be one of the five # published SequencingTechnology named individuals. sh:property [ sh:path genomics:sequencingTechnology ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:ShortReadIllumina genomics:LongReadONT genomics:LongReadPacBio genomics:Mixed genomics:GenotypingArray ) ; sh:severity sh:Violation ; sh:name "Sequencing Technology"@en ; sh:message "SequencingRun must declare genomics:sequencingTechnology as one of the five published genomics:SequencingTechnology named individuals (ShortReadIllumina, LongReadONT, LongReadPacBio, Mixed, GenotypingArray). GenotypingArray auto-tags ConsumerGrade in the importer."@en ] ; # VIOLATION: laboratoryCertification, when present, must be one of the # four published LaboratoryCertification named individuals. Optional # because some research/consumer runs legitimately have none. sh:property [ sh:path genomics:laboratoryCertification ; sh:maxCount 1 ; sh:in ( genomics:CLIA genomics:CAP genomics:ISO15189 genomics:Uncertified ) ; sh:severity sh:Violation ; sh:name "Laboratory Certification"@en ; sh:message "genomics:laboratoryCertification, if present, must be one of the four published genomics:LaboratoryCertification named individuals (CLIA, CAP, ISO15189, Uncertified)"@en ] ; # WARNING: variantCallerVersion recommended for reproducibility. sh:property [ sh:path genomics:variantCallerVersion ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Variant Caller Version"@en ; sh:message "genomics:variantCallerVersion is recommended (e.g., 'GATK 4.5.0', 'DeepVariant 1.6'); needed to distinguish caller-driven differences from biological ones"@en ] ; # WARNING: sequencingDate recommended. sh:property [ sh:path genomics:sequencingDate ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:severity sh:Warning ; sh:name "Sequencing Date"@en ] . # ============================================================================ # Shape 12: Raw File # ============================================================================ # Note: Cascade pods do NOT ingest raw genomic file bytes. RawFile carries # pointer-and-hash metadata only (see Critical conventions #9 in the # Genomics & Advisory implementation plan). genomics:RawFileShape a sh:NodeShape ; sh:targetClass genomics:RawFile ; rdfs:label "Raw File Shape"@en ; rdfs:comment "Validation constraints for genomics:RawFile pointer-and-hash records. Cascade does NOT ingest the bytes — only the manifest. Required: format (closed enum over seven FileFormat values), SHA-256 hash for integrity, opaque file location URI, and the reference genome the file is aligned against."@en ; # VIOLATION: fileFormat required and must be one of the seven published # FileFormat named individuals. sh:property [ sh:path genomics:fileFormat ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( genomics:BAM genomics:CRAM genomics:FASTQ genomics:gVCF genomics:VCF genomics:BCF genomics:OtherFileFormat ) ; sh:severity sh:Violation ; sh:name "File Format"@en ; sh:message "RawFile must declare genomics:fileFormat as one of the seven published genomics:FileFormat named individuals (BAM, CRAM, FASTQ, gVCF, VCF, BCF, OtherFileFormat). Hot tier (in-pod, encrypted): VCF/gVCF/BCF. Cold tier (out-of-pod, referenced): BAM/CRAM/FASTQ."@en ] ; # VIOLATION: fileHashSHA256 required — integrity check. sh:property [ sh:path genomics:fileHashSHA256 ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 64 ; sh:maxLength 64 ; sh:pattern "^[0-9a-f]{64}$" ; sh:severity sh:Violation ; sh:name "File SHA-256 Hash"@en ; sh:message "RawFile must carry exactly one genomics:fileHashSHA256 as a 64-character lowercase hex SHA-256 digest. Required for byte-on-disk integrity verification."@en ] ; # VIOLATION: fileLocation required (opaque URI; Cascade does NOT validate # or fetch this URI — only its presence and shape). sh:property [ sh:path genomics:fileLocation ; sh:datatype xsd:anyURI ; sh:minCount 1 ; sh:maxCount 1 ; sh:severity sh:Violation ; sh:name "File Location URI"@en ; sh:message "RawFile must carry exactly one genomics:fileLocation xsd:anyURI (opaque pointer to where the bytes live: s3://, https://, file://, vendor-specific). Cascade does NOT validate or fetch this URI."@en ] ; # VIOLATION: referenceGenome required. sh:property [ sh:path genomics:referenceGenome ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 1 ; sh:severity sh:Violation ; sh:name "Reference Genome"@en ; sh:message "RawFile must carry exactly one non-empty genomics:referenceGenome (e.g., 'GRCh38', 'GRCh37', 'T2T-CHM13'). Required for any downstream coordinate-based analysis."@en ] ; # VIOLATION: fileSizeBytes, when present, must be xsd:long ≥ 0. # xsd:long because BAM/CRAM/FASTQ files routinely exceed 2^31 bytes. sh:property [ sh:path genomics:fileSizeBytes ; sh:datatype xsd:long ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:severity sh:Violation ; sh:name "File Size (Bytes)"@en ; sh:message "genomics:fileSizeBytes, if present, must be a non-negative xsd:long (BAM/CRAM/FASTQ files routinely exceed 2^31 bytes)"@en ] ; # INFO: htsgetEndpoint optional convenience. sh:property [ sh:path genomics:htsgetEndpoint ; sh:datatype xsd:anyURI ; sh:maxCount 1 ; sh:severity sh:Info ; sh:name "htsget Endpoint"@en ; sh:message "genomics:htsgetEndpoint is optional; when present, enables GA4GH htsget streaming of BAM/CRAM regions on demand"@en ] . # ============================================================================ # Cross-vocabulary note # ============================================================================ # - cascade:dataProvenance is declared in core/v1 and is not redeclared here. # genomics:dataProvenance is the genomics-specific origin lane (consumer- # array vs clinical-sequencing vs research-sequencing vs imported vs AI- # extracted) used by the importer to drive quality-tier auto-tagging. The # two are distinct and live in separate namespaces. # - checkup: terms (GeneticCounselingSummary, VariantNarrative) are out of # scope for this file and authored separately in the checkup vocabulary. # - prov:wasRevisionOf chain integrity for VariantInterpretation # reclassifications (the within-(variant, condition)-pair invariant) is a # cross-class invariant not expressible cleanly in pure SHACL Core; the # supplementing code-level check lives in cascade-cli (TASK-4.x advisory # applier validation). # ============================================================================ # Changelog # ============================================================================ # # Version 1.0-draft (2026-05-05) — TASK-0.2 # - Initial release of genomics vocabulary SHACL shapes. # - 12 sh:NodeShape covering Variant, VariantInterpretation, Haplotype, # Diplotype, CopyNumberVariant, SubmitterAssertion, Pedigree, # PedigreeMember, GeneticTest, GeneticTestOrder, SequencingRun, RawFile. # - D-Q5 enforced on VariantInterpretationShape: variantInterpreted and # condition each minCount/maxCount=1 (per-(variant, condition) modeling). # - D-QUALITY-TIER safety constraint enforced via sh:xone on # VariantInterpretationShape: classification non-pathogenic OR # (classification pathogenic-leaning AND mitigation present), where # mitigation = (variant.dataQualityTier=ClinicalGrade) OR # (requiresConfirmation=true). Violation message names both remediation # paths. # - Cross-class 'at least one stable identifier' on VariantShape via sh:or # over (caId, vrsId, clinvarVariationId, dbsnpRsId). # - Closed enumerations via sh:in for AcmgClass, OrderStatus, FileFormat, # SequencingTechnology, LaboratoryCertification, RelationshipRole, # CarrierStatus, ReviewStatus, TestType, DataQualityTier. # - Range / pattern checks for fileHashSHA256 (lowercase hex, 64 chars), # mosaicismFraction (decimal in [0.0, 1.0]), fileSizeBytes (xsd:long ≥ 0), # coverageDepth (decimal ≥ 0), CNV interval coordinates (positive int). # - Acknowledged vocabulary gaps (not enforced as SHACL violations): # * GeneticTest has no testDate property; chronology via # prov:generatedAtTime instead. # * SubmitterAssertion has no assertionDate property and no forward # reference to its parent Interpretation; the inverse link # (Interpretation→Assertion via genomics:aggregatedFrom) is # authoritative. # * Reclassification chain integrity (revising Interpretation MUST # share both variantInterpreted and condition with the revised one) # is a cross-focus-node invariant requiring sh:sparql; supplementing # code-level check lives in cascade-cli TASK-4.x. # * GeneticTestOrder.resultedIn conditional on orderStatus=Resulted is # a conditional cardinality not cleanly expressible in pure SHACL; # enforced by the importer. # - Out-of-scope (per TASK-0.2 scope): checkup: terms, advisory: cross- # vocab joins, LDPatch syntax, cryptographic verification. # PhenotypicObservationShape (v1-draft.0.4) genomics:PhenotypicObservationShape a sh:NodeShape ; sh:targetClass genomics:PhenotypicObservation ; sh:property [ sh:path genomics:hpoTerm ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path genomics:phenotypePresence ; sh:minCount 1 ; sh:maxCount 1 ; sh:class genomics:PhenotypePresenceValue ] . ## SHACL Shapes: workbench (DRAFT — v1-draft) ## File: workbench.shapes.ttl # Cascade Workbench Vocabulary — SHACL shapes (v1-draft) @prefix workbench: . @prefix sh: . @prefix owl: . @prefix rdfs: . @prefix xsd: . @prefix oa: . @prefix prov: . @prefix ical: . a owl:Ontology ; owl:imports ; rdfs:comment "SHACL shapes for the Cascade Workbench vocabulary (v1-draft)."@en . workbench:InvestigationShape a sh:NodeShape ; sh:targetClass workbench:Investigation ; sh:property [ sh:path workbench:investigationTitle ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:aboutPatient ; sh:minCount 1 ] ; sh:property [ sh:path workbench:investigationStatus ; sh:minCount 1 ; sh:maxCount 1 ; sh:class workbench:InvestigationStatusValue ] . workbench:ImportedConversationShape a sh:NodeShape ; sh:targetClass workbench:ImportedConversation ; sh:property [ sh:path workbench:conversationSource ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:rawContentLocation ; sh:minCount 1 ; sh:datatype xsd:string ] . # ── Append-only record amendment overlays (v1-draft.0.2) ──────────────────── workbench:AmendmentShape a sh:NodeShape ; sh:targetClass workbench:Amendment ; sh:property [ sh:path workbench:amendsRecord ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ; sh:property [ sh:path workbench:amendsProperty ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:amendedValue ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:amendmentReason ; sh:datatype xsd:string ] . workbench:AnnotationShape a sh:NodeShape ; sh:targetClass workbench:Annotation ; sh:property [ sh:path workbench:annotatesRecord ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ; sh:property [ sh:path workbench:annotationText ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:annotationProperty ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:annotationValue ; sh:datatype xsd:string ] ; # Require at least one of annotationText / annotationValue. sh:or ( [ sh:path workbench:annotationText ; sh:minCount 1 ] [ sh:path workbench:annotationValue ; sh:minCount 1 ] ) . workbench:RetractionShape a sh:NodeShape ; sh:targetClass workbench:Retraction ; sh:property [ sh:path workbench:retractsRecord ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ; sh:property [ sh:path workbench:retractionReason ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:supersededBy ; sh:nodeKind sh:IRI ] . # Content-free audit event (v1-draft.0.3): records the erasure EVENT only — # erased record id, action, optional category/reason — never a content hash. workbench:TombstoneShape a sh:NodeShape ; sh:targetClass workbench:Tombstone ; sh:property [ sh:path workbench:erasedRecord ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ; sh:property [ sh:path workbench:erasureAction ; sh:minCount 1 ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:erasedType ; sh:datatype xsd:string ] ; sh:property [ sh:path workbench:erasureReason ; sh:datatype xsd:string ] . # Verification axis (v1-draft.0.3): orthogonal to cascade:dataProvenance. # Uses sh:in (not sh:class) so the constraint is self-contained in the shapes # graph — validating a bucket record needs no ontology individual-typing. workbench:VerificationStatusShape a sh:NodeShape ; sh:targetObjectsOf workbench:verificationStatus ; sh:in ( workbench:Unverified workbench:Confirmed workbench:Refuted ) . # ── Notes / flags / follow-ups — the Web Annotation substrate (v1-draft.0.5) ─ # Targets every oa:Annotation in the Pod (they live under notes/). SHACL Core # only, like the evidence grounding invariant, so `cascade validate` actually # enforces it. The conditional constraints use the sh:or( sh:not(...) ... ) # pattern: "NOT motivated-by-X, OR carries the fields X requires". workbench:WebAnnotationShape a sh:NodeShape ; sh:targetClass oa:Annotation ; # At least one target: a note floats on nothing. sh:property [ sh:path oa:hasTarget ; sh:minCount 1 ; sh:nodeKind sh:BlankNodeOrIRI ] ; # Exactly which kind of note this is. sh:property [ sh:path oa:motivatedBy ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ; # Attribution is load-bearing (caregiver vs patient vs agent): required. sh:property [ sh:path prov:wasAttributedTo ; sh:minCount 1 ] ; sh:property [ sh:path prov:generatedAtTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:dateTime ] . # A commenting note must actually say something: motivatedBy oa:commenting # requires a body. workbench:CommentingBodyShape a sh:NodeShape ; sh:targetClass oa:Annotation ; sh:or ( [ sh:not [ sh:property [ sh:path oa:motivatedBy ; sh:hasValue oa:commenting ] ] ] [ sh:property [ sh:path oa:hasBody ; sh:minCount 1 ] ] ) . # A follow-up is a to-do: motivatedBy workbench:followUp requires the RFC 5545 # VTODO status (reused ical:status, closed enum); ical:due stays optional. workbench:FollowUpShape a sh:NodeShape ; sh:targetClass oa:Annotation ; sh:or ( [ sh:not [ sh:property [ sh:path oa:motivatedBy ; sh:hasValue workbench:followUp ] ] ] [ sh:property [ sh:path ical:status ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( "NEEDS-ACTION" "IN-PROCESS" "COMPLETED" "CANCELLED" ) ] ] ) . # ============================================================================ # SECTION 4: CASCADE PROTOCOL SCHEMA REFERENCE # ============================================================================ # Cascade Protocol Schema Reference A comprehensive reference for all Cascade Protocol RDF/OWL vocabularies and SHACL validation shapes. **Version:** August 2026 **Website:** https://cascadeprotocol.org **Organization:** Cascade Agentic Labs LLC --- ## Table of Contents 1. [Overview](#overview) 2. [Three-Layer Architecture](#three-layer-architecture) 3. [Core Vocabulary](#core-vocabulary) 4. [Clinical Vocabulary](#clinical-vocabulary) 5. [Clinical SHACL Shapes](#clinical-shacl-shapes) 6. [Health Vocabulary](#health-vocabulary) 7. [POTS Vocabulary](#pots-vocabulary) 8. [Coverage Vocabulary](#coverage-vocabulary) 9. [Checkup Vocabulary](#checkup-vocabulary) 10. [Checkup SHACL Shapes](#checkup-shacl-shapes-adult) 11. [SHACL Validation](#shacl-validation) 12. [Diabetes Vocabulary](#diabetes-vocabulary) 13. [Genomics Vocabulary (Draft)](#genomics-vocabulary) 14. [Advisory Vocabulary (Draft)](#advisory-vocabulary) 15. [Evidence Vocabulary (Draft)](#evidence-vocabulary) 16. [Workbench Vocabulary (Draft)](#workbench-vocabulary) 17. [Settings Storage Convention](#settings-storage-convention-phase-b-target) 18. [Pod Storage Conventions](#pod-storage-conventions) 19. [Namespace Reference](#namespace-reference) --- ## Overview The Cascade Protocol provides a modular ontology framework for representing personal health and wellness data using Linked Data standards (RDF, OWL, SHACL). The protocol is designed for: - **Privacy-first architecture**: Local-first storage with encryption - **Interoperability**: Alignment with FHIR, SNOMED CT, LOINC, and W3C PROV-O - **Consumer wellness focus**: Data classified as consumer-generated (non-diagnostic) - **Solid Pod compatibility**: WebID-based access control ### Design Principles 1. **Modularity**: Core concepts are stable; domain vocabularies evolve independently 2. **Standards-based**: Built on W3C RDF, OWL, and PROV-O recommendations 3. **Future-proof**: New health apps can extend without breaking existing data 4. **Provenance tracking**: All data includes comprehensive lineage metadata ### Guiding Principles 1. **Established vocabularies first.** Reference FHIR, LOINC, SNOMED CT, PROV-O, and other established standards before creating Cascade-specific terms. We only invent vocabulary when no standard exists. 2. **Three-layer ontology architecture.** Every data type follows the Layer 1 (standards) → Layer 2 (domain) → Layer 3 (patient) pattern described above. 3. **SNOMED CT and LOINC mappings required.** All clinical and wellness metrics must map to standard codes unless no code exists — in which case the gap is documented explicitly. 4. **Clear vocabulary separation.** `clinical:` for EHR-derived facts, `health:` for consumer device data, `pots:` for POTS screening protocol data (Layer 2), `coverage:` for insurance/benefits data (Layer 2), `checkup:` for patient-facing summaries, `core:` for cross-cutting primitives, and domain prefixes for specialized protocols. 5. **Privacy-first, local-first.** Data stored in Solid Pods with AES-256-GCM encryption at rest. No external API calls for core functionality. 6. **Provenance tracking.** W3C PROV-O metadata on all data — distinguishing consumer-generated (device/user) from clinical-generated (EHR/provider) sources. ### Ontology Objectives 1. **Interoperability:** Any domain property should be traceable to established standard codes (SNOMED CT, LOINC, FHIR) so that Cascade data can round-trip with clinical systems. 2. **Completeness:** Every data type serialized to Turtle has a corresponding vocabulary definition. No undefined terms. 3. **Consistency:** All vocabularies follow the same structural patterns — OWL ontology headers, version info, changelogs, and SHACL shapes where appropriate. 4. **Discoverability:** Every vocabulary has both a machine-readable `.ttl` file and a human-readable documentation page, linked from this index. 5. **Accuracy:** Version numbers in documentation match the actual `.ttl` file versions. Changelogs reflect all changes. --- ## Three-Layer Architecture The Cascade Protocol uses a three-layer ontology architecture to balance interoperability with established healthcare standards and the need for patient-friendly data presentation. | Layer | Namespaces | Role | Example | | ---------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | **Layer 1: Established Standards** | `fhir:`, `sct:` (SNOMED CT), `loinc:` | Canonical clinical and observation codes from healthcare standards bodies | `sct:364075005` (Heart rate), `loinc:40443-4` (Heart rate resting) | | **Layer 2: Domain Vocabulary** | `health:` (wellness/device), `clinical:` (EHR), `pots:` (POTS screening protocol), `coverage:` (insurance/benefits), `cascade:` (cross-cutting) | Domain-specific properties linking to Layer 1 codes. Adds longitudinal tracking, trend inference, and device-specific semantics. | `health:restingHeartRate` annotated with `health:snomedCode sct:364075005` | | **Layer 3: Patient-Facing** | `checkup:` (summaries, intake forms, visit preparation) | Aggregated summaries, intake forms, insights, and visit preparation | `checkup:WellnessSummary` referencing Layer 2 observations | ### When is Layer 2 Needed? Not all data domains require Layer 2 classes. Layer 2 is needed when the domain requires: - **Longitudinal tracking** — medications use `clinical:MedicationUseEpisode` to track use over time - **Computed aggregation** — lab results use `clinical:LabTestSeries` for trend inference - **Device-specific data modeling** — wellness metrics use `health:` properties with SNOMED/LOINC annotations - **Normalization from FHIR's multiple expression paths.** FHIR allows the same clinical fact to be expressed several ways — a procedure date as `performedDateTime` or `performedPeriod.start`, a medication as `MedicationStatement` or `MedicationRequest`. FHIR RDF preserves these variants faithfully, but that makes deduplication impossible without embedding FHIR-specific logic. Layer 2 normalizes to one canonical representation per clinical concept, which is what enables cross-system reconciliation. Conditions, allergies, and family history are simple categorical data that map directly from Layer 1 standard codes (SNOMED, ICD-10) to Layer 3 patient summaries (`checkup:ConditionSummary`, `checkup:AllergySummary`). No Layer 2 intermediary is needed. **1. Layer 1 — tighter definition** Code systems and terminologies maintained by recognized healthcare standards bodies that provide canonical identifiers for clinical concepts, observations, and measurements._ That would include things like CVX (vaccine codes), RxNorm (drug codes), or CPT (procedure codes) if you needed them, but would exclude general-purpose web vocabularies. **2. Layer 2 — additional examples** - **Semantic bridging.** `health:restingHeartRate` is a human-readable property that carries SNOMED and LOINC annotations. Without it, your serialized data would just say `sct:364075005` with `loinc:40443-4`, which is machine-interoperable but unreadable. Layer 2 gives you a named property that's meaningful to developers while preserving the standard code linkage. - **Adding context that standards don't capture.** SNOMED gives you "heart rate" as a concept. Layer 2 lets you distinguish resting vs. walking heart rate, attach device provenance, define measurement periods, and carry trend metadata — none of which exist in the code system itself. - **Unifying multiple standard codes under one concept.** A `health:BPStatistics` class pulls together systolic (LOINC 8480-6) and diastolic (LOINC 8462-4) and adds AHA categorization — something neither LOINC code captures alone. - **Defining the shape of serialized data.** Layer 1 codes are just identifiers. Layer 2 defines the actual RDF structure — what properties exist, their types, their ranges — that the SDK serializes and deserializes. simple categorical data like conditions, allergies, and family history that map directly from Layer 1 codes to Layer 3 summaries. That's a useful negative test. **3. Layer 3 — scope, restrictions, and graduation** Layer 3 is where all the _patient-facing, app-specific_ classes live. But it's broader than just "downstream of Layer 2." Several patterns emerge: - **Aggregation across Layer 2 sources** — `WellnessSummary` pulls from multiple `health:` properties into a single patient-readable snapshot. - **App-layer enrichment via the mixed namespace pattern** — `LabResultSummary` and `ImmunizationSummary` add a single `checkup:` categorization property while referencing `clinical:` properties directly. They don't duplicate data, just add a presentation lens. - **Purely app-specific concepts with no Layer 1/2 equivalent** — `DiscussionTopic`, `CheckInSettings`, `MedicationChangeRecord`. These are Cascade-proprietary and explicitly document the gap. - **Direct Layer 1 → Layer 3 (skipping Layer 2)** — `DailyCheckIn`, questionnaire responses. The doc explicitly notes "Layer 2: Skipped" with rationale for each. Potential guidelines for Layer 3 classes: They should be tied to a specific user-facing feature or view, and be the _only_ layer that app UI code touches directly. **Graduation from Layer 3 to Layer 2** - when a Layer 3 concept starts being needed across multiple apps. For example, if a Layer 3 summary class turned out to be useful not just in Cascade Checkup but also in POTS Check or a future diabetes app, that's a signal it should move to a Layer 2 domain vocabulary. ### Why Not Use FHIR RDF Directly? FHIR has an official RDF serialization ([FHIR RDF](http://hl7.org/fhir/rdf.html)) that maps every FHIR resource to the `fhir:` namespace. Cascade uses `fhir:` as a Layer 1 vocabulary and aligns all Layer 2 classes to it via `rdfs:subClassOf fhir:Procedure`, `rdfs:subClassOf fhir:Encounter`, etc. But FHIR RDF is not used as the primary serialization format for three reasons: 1. **Ergonomics.** FHIR RDF requires heavy blank-node nesting and `fhir:index` ordering predicates to faithfully represent JSON arrays and complex types. A SPARQL query for a lab panel's LOINC code requires a five-step path (`fhir:DiagnosticReport.code/fhir:CodeableConcept.coding/fhir:Coding.code/fhir:value`). The Layer 2 equivalent is `clinical:loincCode`. 2. **Normalization.** FHIR's data model evolved over many years with multiple ways to express the same clinical fact. FHIR RDF preserves all these variants faithfully. Layer 2 picks one canonical form per concept — a prerequisite for cross-system deduplication and reconciliation. 3. **Perspective.** FHIR is designed for clinical system interoperability. Its resources contain metadata meaningful to EHR systems (security labels, version IDs, transaction references) but irrelevant to a patient's health profile. Layer 2 extracts what matters to the patient and discards the rest, which is why Cascade is privacy-first. FHIR RDF is Layer 1 infrastructure — a reference anchor for semantic alignment, not a storage format. --- ## Core Vocabulary v3.6 **Namespace:** `https://ns.cascadeprotocol.org/core/v1#` **Prefix:** `cascade:` **Status:** Stable The Core Vocabulary defines cross-cutting concepts used by all Cascade Protocol health data applications. > **v3.5 (the ORIGIN axis):** A record carried two source-shaped properties and needed three. `clinical:sourceEHR` is a display LABEL; `cascade:sourceSystem` is the INGESTION batch. Neither is an origin, and using either as one fails in a different direction. New `cascade:sourceIdentity` is the ORIGIN axis: a canonical, transport-independent identity for the organization a record came from, scheme-prefixed `org:` / `ns:` / `transport:`. See [The three source axes](#the-three-source-axes-v35). > **v3.4 (pod export manifest):** 32 `cascade:` terms that a conforming pod export emits had no definition in `core.ttl` at all, so a manifest was parseable but not describable. All 32 are now defined, and the three manifest classes are shaped. `cascade:ExportManifest` is a `dcat:Dataset` ([DCAT 3](https://www.w3.org/TR/vocab-dcat-3/)) and `cascade:RecordSummary` is a `void:Dataset` ([VoID](https://www.w3.org/TR/void/)), so a consumer that already reads those standards can read a Cascade manifest without Cascade-specific code. See [Pod Export Manifest](#pod-export-manifest-v34). ### Ontology Metadata ```turtle @prefix cascade: . @prefix owl: . @prefix rdfs: . @prefix xsd: . @prefix dct: . @prefix prov: . a owl:Ontology ; dct:title "Cascade Protocol Core Vocabulary"@en ; dct:description "Core vocabulary for schema versioning, data provenance, identity management, and patient demographics across all Cascade Protocol applications"@en ; dct:creator "Cascade Agentic Labs" ; owl:versionInfo "3.6" . ``` ### Data Provenance Classes The DataProvenance taxonomy uses a class hierarchy with two broad parent categories and six leaf subclasses used by the SDK: | Class | Parent | Description | |-------|--------|-------------| | `cascade:DataProvenance` | `prov:Entity` | Root classification of how health data was generated or collected. | | `cascade:ConsumerGenerated` | `cascade:DataProvenance` | Data generated directly by the user through personal devices (non-clinical setting). | | `cascade:ClinicalGenerated` | `cascade:DataProvenance` | Data generated in a clinical setting under healthcare provider supervision. | #### Consumer-Generated Subclasses (v1.2) | Class | Parent | Description | |-------|--------|-------------| | `cascade:DeviceGenerated` | `cascade:ConsumerGenerated` | Data generated by a consumer health device (Apple Watch, blood pressure cuff, etc.) without clinical supervision. | | `cascade:SelfReported` | `cascade:ConsumerGenerated` | Data manually entered by the patient (symptom logs, medication adherence, intake form fields). | | `cascade:ConsumerWellness` | `cascade:ConsumerGenerated` | Aggregated wellness data from consumer platforms (Apple Health, Google Fit). | | `cascade:AIAsserted` | `cascade:ConsumerGenerated` | Content surfaced by a general-purpose AI assistant in a patient-directed conversation (e.g. ChatGPT, Claude), **not** grounded in clinical sources. MUST be applied to assertions imported from AI conversations so they can never be mistaken for clinically-extracted data (`cascade:AIExtracted`) or verified records (`cascade:EHRVerified`). Marks content that must be evidence-checked before any reliance. (v3.3) | #### Clinical-Generated Subclasses (v1.2) | Class | Parent | Description | |-------|--------|-------------| | `cascade:EHRVerified` | `cascade:ClinicalGenerated` | Data imported from a verified Electronic Health Record system (Epic MyChart, Cerner, etc.). | | `cascade:ScannedDocument` | `cascade:ClinicalGenerated` | Data extracted from a scanned or photographed clinical document. | | `cascade:AIExtracted` | `cascade:ClinicalGenerated` | Data extracted from clinical documents using AI/NLP processing. | > **SDK Alignment:** The SDK `DataProvenance.swift` enum maps to the six leaf subclasses (`EHRVerified`, `DeviceGenerated`, `ScannedDocument`, `SelfReported`, `AIExtracted`, `ConsumerWellness`). The parent categories (`ConsumerGenerated`, `ClinicalGenerated`) serve as taxonomy anchors — they are not referenced by serializer code but enable SPARQL queries like `?x rdfs:subClassOf+ cascade:ClinicalGenerated`. > **AIExtracted vs AIAsserted (v3.3):** Keep these two AI provenance leaves distinct. `cascade:AIExtracted` (a `ClinicalGenerated` subclass) marks structured records pulled out of clinical documents by an extraction pipeline — clinically grounded source. `cascade:AIAsserted` (a `ConsumerGenerated` subclass, new in core v3.3) marks ungrounded content surfaced by a general-purpose AI assistant in a patient-directed conversation. AIAsserted content must be evidence-checked before reliance and must never be promoted to a clinical provenance class. ### Data Properties | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:schemaVersion` | owl:DatatypeProperty | xsd:string | Semantic version of the ontology schema (format: major.minor.patch). | | `cascade:dataProvenance` | owl:ObjectProperty | cascade:DataProvenance | Classification of how data was generated (consumer vs clinical). | | `cascade:creatorWebID` | owl:ObjectProperty | rdfs:Resource | Solid WebID URI of the user who created this data. Subproperty of `prov:wasAttributedTo`. | | `cascade:sourceOrganization` | owl:DatatypeProperty | xsd:string | Organization that provided this record (e.g., "Kaiser Permanente", "Mayo Clinic"). Populated from FHIR `meta.source` or contained Organization reference during clinical record import. (v2.3) | | `cascade:sourceDeviceName` | owl:DatatypeProperty | xsd:string | Device or app that recorded this measurement (e.g., "Apple Watch Series 9", "Omron BP Monitor"). Populated from HealthKit `HKSource` contributing app/device name. (v2.3) | ### The three source axes (v3.5) A record carries three different answers to three different questions, and collapsing any two of them is a defect that has been measured on real pods. | Axis | Property | Question it answers | |------|----------|---------------------| | ORIGIN | `cascade:sourceIdentity` | Which ORGANIZATION the record came from, as a canonical token that is the same whatever transport carried it. The only one of the three that may be used as a reconciliation key. | | LABEL | `clinical:sourceEHR` | What to CALL that organization on screen. Human-readable and source-worded, so two spellings of one organization are two labels. | | INGESTION | `cascade:sourceSystem` | How and when the data entered the Pod: the import batch. Never an origin. | The failure this distinction exists to end: a reconciler that keys "are these two records from the same source?" on the INGESTION axis answers yes for every pair on a single-batch Pod, so no duplicate is ever compared, and answers no for two exports of one organization, so one organization occupies two rows of any source view. **Value form.** `cascade:sourceIdentity` is a scheme-prefixed token, so a consumer can always tell how much the producer actually knew: | Scheme | Meaning | |--------|---------| | `org:{slug}` | An organization was derivable. `{slug}` is the normalized form below. | | `ns:{namespace}` | No organization was derivable, but the record's identifiers have an assigning authority: the FHIR server base URL, or the C-CDA `` root OID. | | `transport:{label}` | LAST RESORT. Nothing named or located an organization, so the value restates `cascade:sourceSystem`, honestly prefixed. It is not an origin claim; two `transport:` values mean "origin unknown", not "shared source". | **The slug normalization**, which every transport must implement identically: 1. The input is either an organization NAME (a C-CDA custodian or author organization name, a FHIR `Organization.name`, or an institution-looking performer display) or a HOST (the registrable domain of the source FHIR endpoint). 2. HOST: lowercase; drop a leading `www.`; drop the public-suffix label(s), keeping the registrable name label (`fhir.meridianhealth.example` becomes `meridianhealth`). Then repeatedly strip any generic organization word that forms a whole prefix or suffix of that label (`meridianhealth` becomes `meridian`), never stripping to fewer than three characters. 3. NAME: lowercase; fold diacritics to ASCII; replace every run of non-alphanumeric characters with a single space; split on spaces. 4. Drop generic organization words, legal-form words and pure-number tokens. 5. If no token survives, use the whole normalized string with separators removed. 6. The slug is the FIRST surviving token. `Meridian Health System` and `meridianhealth.example` both give `org:meridian`. `Providence Health and Services Washington and Montana` and `providence.org` both give `org:providence`. **Why the leading token and not all of them.** The two failure directions are not symmetric. Collapsing two different organizations onto one identity suppresses comparisons between their records, so duplicates simply remain in the Pod: visible, and recoverable later. Splitting one organization across two identities lets records the organization deliberately kept apart be compared and merged, which destroys content. The normalization is therefore deliberately biased toward collapsing, and regional and specialty qualifiers are dropped rather than being allowed to split a system. **Validation.** `cascade:SourceIdentityShape` uses `sh:targetSubjectsOf`, so it checks the value wherever the property appears and never requires its presence. Absence is not a finding at any severity in core v3.5, and every pod written before v3.5 validates unchanged. ### Data Absence (v3.6) `cascade:dataAbsentReason` states **why a record's primary VALUE is absent**, with exactly the semantics of FHIR R4 [`Observation.dataAbsentReason`](https://hl7.org/fhir/R4/observation-definitions.html#Observation.dataAbsentReason). A record that carries a value MUST NOT also carry this property. A C-CDA document draws distinctions that were previously all discarded on import. `nullFlavor="UNK"` ("a value applies, nobody knows it"), `NAV` ("we will know later"), `NASK` ("nobody asked") and `ASKU` ("we asked, the patient did not know") are four different clinical facts, and an importer that drops all four writes one indistinguishable blank. A reader then cannot tell a test that was never ordered from one whose result is still pending, and re-asking is the only recovery. **Value set:** the 15 codes of [`data-absent-reason`](http://terminology.hl7.org/CodeSystem/data-absent-reason), gathered by [`http://hl7.org/fhir/ValueSet/data-absent-reason`](http://hl7.org/fhir/ValueSet/data-absent-reason) — `unknown`, `asked-unknown`, `temp-unknown`, `not-asked`, `asked-declined`, `masked`, `not-applicable`, `unsupported`, `as-text`, `error`, `not-a-number`, `negative-infinity`, `positive-infinity`, `not-performed`, `not-permitted`. **Why data-absent-reason and not [HL7 v3 NullFlavor](http://terminology.hl7.org/CodeSystem/v3-NullFlavor) directly**, stated so it is not re-litigated: this vocabulary already cites data-absent-reason (`health:interpretation` and `clinical:interpretation` have accepted its `unknown` code since health v2.6 / clinical v1.14, so binding elsewhere would put two absence vocabularies in one record); its 15 codes are all selectable, whereas v3-NullFlavor marks `NI`, `UNK`, `OTH` and `INV` abstract, and the Validation Profile treats abstract concepts as hierarchy nodes rather than values; and FHIR is the transport both converter paths read. **An importer maps `nullFlavor` on the way in** and MUST NOT write the raw code through: | nullFlavor | data-absent-reason | | nullFlavor | data-absent-reason | |---|---|---|---|---| | `UNK` | `unknown` | | `MSK` | `masked` | | `ASKU` | `asked-unknown` | | `NA` | `not-applicable` | | `NASK` | `not-asked` | | `OTH` | `unsupported` | | `NAV` | `temp-unknown` | | `NI` | `unknown` | | `NAVU` | `unknown` | | `NINF` / `PINF` | `negative-infinity` / `positive-infinity` | Any other nullFlavor maps to `unknown`, which asserts only that a value was expected and is missing. `DER`, `UNC`, `QS` and `TRC` are **not** absences — they are claims about a value that exists — and map to nothing. **Validation.** `cascade:DataAbsentReasonShape` uses `sh:targetSubjectsOf`, so it checks the value wherever the property appears and never requires its presence; every pod written before v3.6 validates unchanged. A raw NullFlavor code is rejected, deliberately: an absence with two encodings turns "is this the same absence?" into a string-comparison question. ### Record Identity: Canonical Form of a Multi-Valued Input (v3.6, NORMATIVE) An implementation that derives a record's identifier from its own content hashes a set of named content fields. Since health v2.6 and clinical v1.14 made the code fields (`icd10Code`, `snomedCode`, `testCode`, `labCategory`) 0..* to match [`CodeableConcept.coding`](https://hl7.org/fhir/R4/datatypes.html#CodeableConcept), one of those fields can hold several values — and a hash taken over an unordered field is only stable once the field has a canonical form. Without one, two exports of the SAME record that list the same codings in a different order mint two different identifiers and the record silently splits in two. The canonical form, stated normatively on `cascade:cascadeUri`: 1. Discard members that are null or empty after trimming; trim each survivor. 2. Deduplicate. 3. Sort ascending by **Unicode code point**. Code point, not locale collation: a locale-dependent order would make identity depend on the machine. 4. Join with a single separator character the implementation fixes and documents. **U+002C COMMA is recommended and required of new implementations.** 5. A one-element sequence MUST canonicalize to exactly the bare scalar form. 6. A sequence with no surviving member is treated as an absent field, exactly as a null scalar is. **Three invariants are normative independently of the separator**, and are what the conformance vectors check: | Invariant | Statement | |---|---| | Order independence | Shuffling the members MUST NOT change the identifier. | | Scalar agreement | A field holding one value MUST mint the same identifier whether spelled as a scalar or a one-element sequence. | | Duplicate independence | Repeating a member MUST NOT change the identifier. | Scalar agreement is what keeps every identifier written before those fields became repeatable exactly where it is. **Scope, and the one place this rule must NOT be applied.** It governs identity inputs whose source element is a SET. It must never be applied to an input whose source order carries meaning — FHIR `name[0]` is the primary name, and a component or note list is a sequence — because sorting there merges records the source deliberately distinguished. **Migration.** An implementation already shipping a fixed separator other than U+002C at an existing identity site KEEPS it. Changing a separator re-mints every identifier that site has ever produced, which is precisely the harm the rule exists to prevent. ### Reconciliation Properties (v2.7) > **Implementation note:** Phase 1 of the vocabulary refactor (2026-03-11) changed status and resolution values from string literals to named individuals, updated `cascade:conflictField` to store full predicate URIs, dropped the redundant `cascade:mergedSources` property, and renamed `cascade:conflictValues` to `cascade:conflictDescription`. v2.7 (2026-03-12) removed `cascade:conflictDescription` — `cascade:conflictDetail` blank nodes now carry an `rdfs:comment` for human-readable summaries. See [cascade-reconcile-experiment/docs/2026-03-11-PLAN-vocabulary-refactor.md] for the full phased plan. Added to support multi-system data normalization via `cascade reconcile`. When a record has been processed by the reconciliation pipeline, these properties describe its origin and any conflicts that were detected and resolved. | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:sourceIdentity` | owl:DatatypeProperty | xsd:string | **ORIGIN axis.** The canonical, transport-independent identity of the organization a record came from, as a scheme-prefixed token: `org:{slug}`, `ns:{namespace}`, or `transport:{label}`. The only source property that may be used as a reconciliation key. (v3.5) | | `cascade:sourceSystem` | owl:DatatypeProperty | xsd:string | **INGESTION axis.** The import batch a record entered the Pod through (e.g. "Apple Health export", "primary-care"), set by `cascade convert --source-system`. Records how and when data arrived, NOT where it came from; must not be used as a source identity, since one batch commonly carries several organizations and one organization commonly arrives in many batches. | | `cascade:reconciliationStatus` | owl:ObjectProperty | Named individual | Status of this record after multi-system reconciliation. Values are named individuals: `cascade:Canonical` (`https://ns.cascadeprotocol.org/core/v1#Canonical`) — unique record, no match across systems; `cascade:Merged` (`https://ns.cascadeprotocol.org/core/v1#Merged`) — deduplicated from multiple sources; `cascade:ConflictResolved` (`https://ns.cascadeprotocol.org/core/v1#ConflictResolved`) — conflicting values resolved via trust priority; `cascade:UnresolvedConflict` (`https://ns.cascadeprotocol.org/core/v1#UnresolvedConflict`) — flagged for manual clinical review; `cascade:WithinSourceDuplicate` (`https://ns.cascadeprotocol.org/core/v1#WithinSourceDuplicate`) — encounter-instance duplicate within same source system, suppressed during Pass 1 within-source deduplication. (v2.6) | | `cascade:mergedFrom` | owl:ObjectProperty | rdfs:Resource | Links the canonical record to the original source record URIs that were merged into it. Multiple values permitted. Subproperty of `prov:wasDerivedFrom`. | | `cascade:conflictResolution` | owl:ObjectProperty | Named individual | Strategy used to resolve a conflict between records from different systems. Values are named individuals: `cascade:TrustPriority` (`https://ns.cascadeprotocol.org/core/v1#TrustPriority`) — higher-trust source wins; `cascade:MergeValues` (`https://ns.cascadeprotocol.org/core/v1#MergeValues`) — fill missing fields from all sources; `cascade:FlagUnresolved` (`https://ns.cascadeprotocol.org/core/v1#FlagUnresolved`) — cannot auto-resolve, requires clinical review. | | `cascade:conflictField` | owl:ObjectProperty | rdfs:Resource | The full URI of the RDF predicate where conflicting values were found across systems (e.g., ``, ``). Stored as a URI reference, not a prefixed name string. | | ~~`cascade:conflictDescription`~~ | owl:DatatypeProperty | xsd:string | **Removed v2.7.** Was informational prose describing conflicting values across systems. Replaced by `cascade:conflictDetail` structured blank node (v2.5); blank nodes carry an `rdfs:comment` for human-readable summaries. | | `cascade:conflictDetail` | owl:ObjectProperty | cascade:ConflictDetail | Links a reconciled record to a structured `cascade:ConflictDetail` blank node describing per-system conflict values. Blank node includes `rdfs:comment` with human-readable conflict summary. (v2.5) | | `cascade:ConflictDetail` | owl:Class | — | Structured representation of conflicting values across source systems for a single property. Used as a blank node linked from `cascade:conflictDetail`. (v2.5) | | `cascade:conflictFor` | owl:ObjectProperty | rdfs:Resource | The predicate URI on which the conflict was detected. Links a `cascade:ConflictDetail` to the conflicting property. (v2.5) | | `cascade:systemValue` | owl:ObjectProperty | — | Links a `cascade:ConflictDetail` to a blank node representing one source system's value for the conflicting property. (v2.5) | | `cascade:forSystem` | owl:DatatypeProperty | xsd:string | The name of the source EHR system (e.g., `"primary-care"`, `"specialist"`) within a `cascade:systemValue` blank node. (v2.5) | #### Example: Reconciled Condition Record ```turtle a health:ConditionRecord ; cascade:dataProvenance cascade:EHRVerified ; cascade:schemaVersion "1.3" ; cascade:sourceSystem "primary-care" ; health:conditionName "Ischemic heart disease (disorder)" ; health:status "active" ; health:snomedCode sct:414545008 ; # Reconciliation provenance cascade:reconciliationStatus cascade:ConflictResolved ; cascade:mergedFrom , ; cascade:conflictResolution cascade:TrustPriority ; cascade:conflictField ; cascade:conflictDetail [ a cascade:ConflictDetail ; cascade:conflictFor ; cascade:systemValue [ cascade:forSystem "primary-care" ; rdf:value "active" ] ; cascade:systemValue [ cascade:forSystem "specialist" ; rdf:value "resolved" ] ] ; prov:wasDerivedFrom , . ``` ### Vocabulary Organization (v1.3) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:subConcern` | owl:AnnotationProperty | xsd:string | Classifies a vocabulary class by its functional role within a vocabulary. Values are vocabulary-specific documented conventions (e.g., 'summary', 'intake', 'analytics', 'identity', 'app-config', 'structural', 'deprecated'). | ### Shared Preference Properties (v1.3) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:notificationsEnabled` | owl:DatatypeProperty | xsd:boolean | Whether the user has enabled push notifications across Cascade apps. | | `cascade:preferredUnits` | owl:DatatypeProperty | xsd:string | User's preferred unit system: 'metric' or 'imperial'. | ### Usage Example ```turtle @prefix cascade: . @prefix pots: . @prefix prov: . @prefix xsd: . <#pots-check-123> a pots:POTSCheckResult ; cascade:schemaVersion "1.2" ; cascade:dataProvenance cascade:ConsumerGenerated ; prov:wasAttributedTo ; prov:wasGeneratedBy [ a prov:Activity ; prov:wasAssociatedWith ; prov:startedAtTime "2025-10-31T10:00:00Z"^^xsd:dateTime ] . ``` ### Vocabulary Organization (v1.3) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:subConcern` | owl:AnnotationProperty | xsd:string | Classifies a vocabulary class by its functional role (e.g., 'summary', 'intake', 'analytics', 'identity', 'app-config', 'structural', 'deprecated'). | ### Shared Preference Properties (v1.3) Cross-app user preferences for the `/settings/shared/preferences.ttl` path. | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:notificationsEnabled` | owl:DatatypeProperty | xsd:boolean | Whether push notifications are enabled across Cascade apps. | | `cascade:preferredUnits` | owl:DatatypeProperty | xsd:string | Preferred unit system: 'metric' or 'imperial'. | ### Patient Profile Classes (v2.0) Health-specific patient demographics promoted from `checkup:` namespace as part of the Schema Refactoring Plan (WS6). Designed as a Solid WebID Profile extension for health applications. | Class | Parent | Description | |-------|--------|-------------| | `cascade:PatientProfile` | `rdfs:Resource` | Health-specific patient demographics (Solid WebID Profile extension). Consolidates identity properties previously in `checkup:PatientProfile`. | | `cascade:Address` | `rdfs:Resource` | Structured postal address with FHIR Address semantics. | | `cascade:PharmacyInfo` | `rdfs:Resource` | Preferred pharmacy for prescriptions. Replaces `checkup:preferredPharmacy` (string) with structured data. | | `cascade:AdvanceDirectives` | `rdfs:Resource` | Patient advance directive declarations (living will, power of attorney, DNR). | | `cascade:EmergencyContact` | `rdfs:Resource` | A patient's emergency contact person. Used as a blank node within PatientProfile. (v2.2) | ### Patient Profile Properties (v2.0) #### PatientProfile Properties | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:dateOfBirth` | owl:DatatypeProperty | xsd:date | Patient date of birth. | | `cascade:genderIdentity` | owl:DatatypeProperty | xsd:string | Gender identity: woman, man, non_binary, other, prefer_not_to_say. | | `cascade:biologicalSex` | owl:DatatypeProperty | xsd:string | Biological sex: male, female, intersex. | | `cascade:ageGroup` | owl:DatatypeProperty | xsd:string | Computed age group: pediatric, young_adult, adult, senior. | | `cascade:emergencyContactName` | owl:DatatypeProperty | xsd:string | Full name of emergency contact. | | `cascade:emergencyContactRelationship` | owl:DatatypeProperty | xsd:string | Relationship of emergency contact to patient. | | `cascade:emergencyContactPhone` | owl:DatatypeProperty | xsd:string | Phone number of emergency contact. | | `cascade:hasAddress` | owl:ObjectProperty | cascade:Address | **Deprecated v2.2 on PatientProfile** — use `cascade:address` (blank-node linking). Still used on PharmacyInfo. | | `cascade:maritalStatus` | owl:DatatypeProperty | xsd:string | Marital status. Migrated from `checkup:maritalStatus`. | | `cascade:raceEthnicity` | owl:DatatypeProperty | xsd:string | Race/ethnicity (multi-value). Migrated from `checkup:raceEthnicity`. | | `cascade:hasPreferredPharmacy` | owl:ObjectProperty | cascade:PharmacyInfo | **Deprecated v2.2** — use `cascade:preferredPharmacy` (blank-node linking). | | `cascade:hasAdvanceDirectives` | owl:ObjectProperty | cascade:AdvanceDirectives | **Deprecated v2.2** — use `cascade:advanceDirectives` (blank-node linking). | | `cascade:computedAge` | owl:DatatypeProperty | xsd:integer | Patient age in years, computed from dateOfBirth at serialization time. (v2.2) | | `cascade:profileId` | owl:DatatypeProperty | xsd:string | Unique identifier (UUID) for the patient profile instance. (v2.2) | | `cascade:preferredLanguage` | owl:DatatypeProperty | xsd:string | Patient's preferred language. Backward compatibility; should use `dct:language` in identity profile. (v2.2) | | `cascade:address` | owl:ObjectProperty | cascade:Address | Links to an Address blank node. Used by PatientProfileSerializer. (v2.2) | | `cascade:emergencyContact` | owl:ObjectProperty | cascade:EmergencyContact | Links to an EmergencyContact blank node. (v2.2) | | `cascade:advanceDirectives` | owl:ObjectProperty | cascade:AdvanceDirectives | Links to an AdvanceDirectives blank node. (v2.2) | | `cascade:preferredPharmacy` | owl:ObjectProperty | cascade:PharmacyInfo | Links to a PharmacyInfo blank node. Replaces `cascade:hasPreferredPharmacy`. (v2.2) | #### Address Properties (Simplified Aliases) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:streetAddress` | owl:DatatypeProperty | xsd:string | Street address line. Simplified alias of `cascade:addressLine`. | | `cascade:city` | owl:DatatypeProperty | xsd:string | City name. Simplified alias of `cascade:addressCity`. | | `cascade:state` | owl:DatatypeProperty | xsd:string | State or province. Simplified alias of `cascade:addressState`. | | `cascade:postalCode` | owl:DatatypeProperty | xsd:string | Postal/ZIP code. Simplified alias of `cascade:addressPostalCode`. | | `cascade:country` | owl:DatatypeProperty | xsd:string | Country name or ISO code. Simplified alias of `cascade:addressCountry`. | #### FHIR-Aligned Address Properties (v2.2) These mirror the FHIR Address datatype fields used by the SDK Address struct. | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:addressUse` | owl:DatatypeProperty | xsd:string | Purpose of the address: home, work, temp, old. Aligns with FHIR Address.use. | | `cascade:addressType` | owl:DatatypeProperty | xsd:string | Type of address: postal, physical, both. Aligns with FHIR Address.type. | | `cascade:addressText` | owl:DatatypeProperty | xsd:string | Full address as a single text string. Aligns with FHIR Address.text. | | `cascade:addressLine` | owl:DatatypeProperty | xsd:string | Street address line. Repeatable for multi-line addresses. Aligns with FHIR Address.line. | | `cascade:addressCity` | owl:DatatypeProperty | xsd:string | City name. Aligns with FHIR Address.city. | | `cascade:addressDistrict` | owl:DatatypeProperty | xsd:string | District or county. Aligns with FHIR Address.district. | | `cascade:addressState` | owl:DatatypeProperty | xsd:string | State or province. Aligns with FHIR Address.state. | | `cascade:addressPostalCode` | owl:DatatypeProperty | xsd:string | Postal or ZIP code. Aligns with FHIR Address.postalCode. | | `cascade:addressCountry` | owl:DatatypeProperty | xsd:string | Country name or ISO code. Aligns with FHIR Address.country. | #### PharmacyInfo Properties | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:pharmacyName` | owl:DatatypeProperty | xsd:string | Name of the pharmacy. | | `cascade:pharmacyPhone` | owl:DatatypeProperty | xsd:string | Phone number of the pharmacy. | | `cascade:pharmacyAddress` | owl:DatatypeProperty | xsd:string | Pharmacy address as a single text string. Simplified alternative to nested Address blank node. (v2.2) | | `cascade:hasAddress` | owl:ObjectProperty | cascade:Address | Pharmacy postal address (reuses `cascade:hasAddress`). | #### EmergencyContact Properties (v2.2) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:contactName` | owl:DatatypeProperty | xsd:string | Full name of a contact person. | | `cascade:contactRelationship` | owl:DatatypeProperty | xsd:string | Relationship of the contact to the patient: spouse, parent, child, sibling, partner, friend, caregiver, other. | | `cascade:contactPhone` | owl:DatatypeProperty | xsd:string | Phone number for a contact person. Also used for general contact use on PatientProfile. | #### AdvanceDirectives Properties | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:hasLivingWill` | owl:DatatypeProperty | xsd:boolean | Whether the patient has a living will. Migrated from `checkup:hasLivingWill`. | | `cascade:hasPowerOfAttorney` | owl:DatatypeProperty | xsd:boolean | Whether the patient has a healthcare power of attorney. Migrated from `checkup:hasPowerOfAttorney`. | | `cascade:hasDNR` | owl:DatatypeProperty | xsd:boolean | Whether the patient has a do-not-resuscitate order. Migrated from `checkup:hasDNR`. | | `cascade:advanceDirectiveNotes` | owl:DatatypeProperty | xsd:string | Free-text notes about advance directives. Migrated from `checkup:advanceDirectiveNotes`. | ### FHIR Conversion Support (v2.8) Added to support lossless FHIR ↔ Cascade conversion. These properties annotate converted records with FHIR provenance and layer promotion status. #### LayerPromotionStatus Class and Individuals | Class/Individual | Type | Description | |-----------------|------|-------------| | `cascade:LayerPromotionStatusValue` | owl:Class | Enumeration class for FHIR-to-Cascade layer promotion status values. | | `cascade:FullyMapped` | Named Individual | Record has been fully mapped to a Layer 2 Cascade class. All domain properties are populated from the FHIR source. | | `cascade:PendingLayerTwoPromotion` | Named Individual | Record is stored as a Layer 1 passthrough (FHIR JSON preserved in `cascade:fhirJson`) because no Layer 2 class exists yet for this FHIR resource type. | #### FHIR Conversion Properties | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:layerPromotionStatus` | owl:ObjectProperty | cascade:LayerPromotionStatusValue | Indicates whether a converted FHIR record has been fully promoted to a Layer 2 Cascade class (`cascade:FullyMapped`) or is stored as a Layer 1 passthrough (`cascade:PendingLayerTwoPromotion`). (v2.8) | | `cascade:fhirJson` | owl:DatatypeProperty | xsd:string | Inline FHIR JSON for passthrough records where no Layer 2 class exists. Always plain xsd:string (no base64 encoding). Enables lossless round-trip for obscure resource types. (v2.8) | | `cascade:fhirResourceType` | owl:DatatypeProperty | xsd:string | The originating FHIR resource type (e.g., "Claim", "Appointment", "CarePlan"). Used for passthrough records and as a disambiguation hint on promoted records. (v2.8) | | `cascade:sourceRecordDate` | owl:DatatypeProperty | xsd:dateTime | The primary date extracted from the source FHIR resource (resource-type-specific: Encounter.period.start, Claim.billablePeriod.start, etc.). Enables chronological sorting across mixed record types. (v2.8) | ### Conflict Resolution Classes (v2.9) | Class | Description | |-------|-------------| | `cascade:UserResolution` | A patient's recorded decision for resolving a data conflict between two source records. Persisted so that the same resolution can be automatically applied on re-import. (v2.9) | | `cascade:PendingConflict` | An unresolved conflict between two or more records from different sources, awaiting user resolution. Stored until the user reviews and resolves. (v2.9) | ### Conflict Resolution Properties (v2.9) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:conflictId` | DatatypeProperty | xsd:string | A deterministic identifier for a conflict, derived from record type and identity fields. Stable across re-imports. Domain: `cascade:UserResolution` or `cascade:PendingConflict`. (v2.9) | | `cascade:resolution` | DatatypeProperty | xsd:string | The user's resolution decision. Values: `kept-source-a`, `kept-source-b`, `kept-both`, `manual-edit`. Domain: `cascade:UserResolution`. (v2.9) | | `cascade:keptRecord` | ObjectProperty | — | The record kept as the canonical version after conflict resolution. Domain: `cascade:UserResolution`. (v2.9) | | `cascade:discardedRecords` | ObjectProperty | — | Records discarded during conflict resolution. Domain: `cascade:UserResolution`. (v2.9) | | `cascade:userNote` | DatatypeProperty | xsd:string | Optional free-text note from the patient about their resolution decision. Domain: `cascade:UserResolution`. (v2.9) | | `cascade:candidateRecords` | ObjectProperty | — | The conflicting records in a `PendingConflict`, awaiting user resolution. Domain: `cascade:PendingConflict`. (v2.9) | | `cascade:detectedAt` | DatatypeProperty | xsd:dateTime | When the conflict was first detected during import. Domain: `cascade:PendingConflict`. (v2.9) | | `cascade:autoResolved` | DatatypeProperty | xsd:boolean | True when a conflict was automatically resolved by applying a stored user resolution preference. (v2.9) | ### Document Provenance Properties (v2.9) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:documentType` | DatatypeProperty | xsd:string | The type of clinical document. Values: `summarization`, `progress-note`, `discharge-summary`, `consultation-note`, `operative-note`, `history-physical`. (v2.9) | | `cascade:sectionCode` | DatatypeProperty | xsd:string | LOINC code of the originating C-CDA section for this record (e.g., `11450-4` for Problem List). (v2.9) | | `cascade:sourceRecordId` | DatatypeProperty | xsd:string | The original source system record identifier (e.g., C-CDA `` element value) preserved for provenance. (v2.9) | ### AI Extraction Classes (v3.0) | Class | Description | |-------|-------------| | `cascade:HealthRecord` | Abstract superclass for all Cascade health data records. Provides a common type for cross-record queries and provenance linkage. (v3.0) | | `cascade:AIExtractionActivity` | A PROV-O Activity representing an AI/NLP extraction pass over a clinical document. Linked to extracted records via `prov:wasGeneratedBy`. (v3.0) | | `cascade:AIDiscardedExtraction` | An extraction candidate that the AI model identified but discarded (low confidence, duplicate, or out-of-scope). Stored for audit and re-review purposes. (v3.0) | | `cascade:SocialHistoryConsent` | Consent record governing the storage and processing of sensitive social history data under 42 CFR Part 2 and equivalent regulations. (v3.0) | ### AI Extraction Properties (v3.0) | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:extractionConfidence` | DatatypeProperty | xsd:decimal | Model confidence score for an extracted value, in the range [0.0, 1.0]. Domain: `cascade:AIExtractionActivity`. (v3.0) | | `cascade:extractionModel` | DatatypeProperty | xsd:string | Identifier of the AI/NLP model used for extraction (e.g., `"qwen3.5-4b-q4_k_m"`). Domain: `cascade:AIExtractionActivity`. (v3.0) | | `cascade:sourceNarrativeSection` | DatatypeProperty | xsd:string | The C-CDA or document section the AI extracted data from (e.g., `"medications"`, `"social-history"`). Domain: `cascade:AIExtractionActivity`. (v3.0) | | `cascade:requiresUserReview` | DatatypeProperty | xsd:boolean | True when the extraction confidence is below the auto-accept threshold and the record must be reviewed by the patient before persisting. Domain: `cascade:AIExtractionActivity`. (v3.0) | | `cascade:discardReason` | DatatypeProperty | xsd:string | Human-readable reason the extraction candidate was discarded. Domain: `cascade:AIDiscardedExtraction`. (v3.0) | | `cascade:consentScope` | DatatypeProperty | xsd:string | Scope of the consent grant. Values: `social-history`, `substance-use`, `mental-health`. Domain: `cascade:SocialHistoryConsent`. (v3.0) | | `cascade:consentGrantedAt` | DatatypeProperty | xsd:dateTime | Timestamp when the patient granted consent. Domain: `cascade:SocialHistoryConsent`. (v3.0) | | `cascade:consentRevokedAt` | DatatypeProperty | xsd:dateTime | Timestamp when the patient revoked consent, if applicable. Domain: `cascade:SocialHistoryConsent`. (v3.0) | ### Advisory & AI Generation Provenance (v3.1–v3.2) PROV-O activities recording how advisory patches are applied and how LLM-generated narrative content is produced. | Class/Individual | Type | Description | |------------------|------|-------------| | `cascade:AdvisoryApplicationActivity` | owl:Class (`prov:Activity`) | An activity that applied a Cascade Advisory Patch (CAP) to a Pod. Joins to advisory provenance via `prov:used ` and `prov:used `, recording both the patch source and the record it was matched against. Use `prov:wasGeneratedBy` on records produced or modified by the patch. (v3.1) | | `cascade:AIGenerationActivity` | owl:Class (`prov:Activity`) | An LLM-powered generation activity that produced narrative or summary content (e.g., `checkup:VariantNarrative` chunks). Sibling of `cascade:AIExtractionActivity`: extraction pulls structured records out of unstructured text, generation produces new prose. Records the model identifier, prompt version, temperature, and a `cascade:trigger` value indicating why the generation ran. Link generated content via `prov:wasGeneratedBy`. (v3.1) | | `cascade:GenerationTrigger` | owl:Class | Closed enumeration of reasons an `AIGenerationActivity` may run. (v3.1) | | `cascade:InitialGeneration` | Named Individual (`cascade:GenerationTrigger`) | First-time generation of a narrative or summary chunk; no prior version existed. (v3.1) | | `cascade:RegenerationAfterReclassification` | Named Individual (`cascade:GenerationTrigger`) | Regeneration triggered because an upstream classification (e.g., variant pathogenicity, condition status) changed and the prior narrative is now stale. (v3.1) | | `cascade:AudienceRetargeting` | Named Individual (`cascade:GenerationTrigger`) | Regeneration triggered to retarget existing content for a different audience (e.g., patient-facing vs. clinician-facing) without any underlying classification change. (v3.1) | `AIGenerationActivity` reuses `cascade:extractionModel`, `cascade:extractionConfidence`, `cascade:sourceNarrativeSection`, and `cascade:requiresUserReview` (declared on `AIExtractionActivity`) and adds the properties below. | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:promptVersion` | DatatypeProperty | xsd:string | Identifier of the prompt template version used for this generation, e.g. `"variant-narrative-v2.1"`. Pair with `cascade:extractionModel` for full reproducibility. Domain: `cascade:AIGenerationActivity`. (v3.1) | | `cascade:generationTemperature` | DatatypeProperty | xsd:decimal | Sampling temperature used during generation (typically 0.0–1.0). Recorded for reproducibility and to distinguish deterministic from creative outputs. Domain: `cascade:AIGenerationActivity`. (v3.1) | | `cascade:trigger` | ObjectProperty | cascade:GenerationTrigger | The reason this generation activity ran. One of the `cascade:GenerationTrigger` named individuals. Domain: `cascade:AIGenerationActivity`. (v3.1) | | `cascade:appliedTriplesCount` | DatatypeProperty | xsd:nonNegativeInteger | Total number of triples the advisory's CAP body inserted into the Pod for a single match. Lets an auditor verify CAP profile constraint C5 (≤ 64 inserted triples per match) at activity-record-read time without re-parsing the patch body. Domain: `cascade:AdvisoryApplicationActivity`. (v3.2) | ### Workbench Support: Ungrounded-AI Provenance & Caregiver Proxy (v3.3) Added for the Cascade Workbench. `cascade:AIAsserted` (a `ConsumerGenerated` provenance leaf — see [Data Provenance Classes](#data-provenance-classes)) marks ungrounded general-AI content. `cascade:ProxyAgent` and its properties close the caregiver-proxy gap, recording a person operating a patient's Pod on the patient's behalf. | Class | Type | Description | |-------|------|-------------| | `cascade:ProxyAgent` | owl:Class (`prov:Agent`) | A person operating a patient's Pod on the patient's behalf (e.g. a parent for a minor child, a caregiver for a dependent adult). Distinct from the patient (`cascade:PatientProfile`). Records who is acting and under what authority and scope. (v3.3) | | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:actsForPatient` | ObjectProperty (`prov:actedOnBehalfOf`) | rdfs:Resource | The patient (WebID) on whose behalf the proxy acts. Domain: `cascade:ProxyAgent`. (v3.3) | | `cascade:proxyWebID` | ObjectProperty | rdfs:Resource | WebID of the proxy agent. Domain: `cascade:ProxyAgent`. (v3.3) | | `cascade:proxyRelationship` | DatatypeProperty | xsd:string | Relationship of the proxy to the patient. Reuses the `cascade:contactRelationship` value space: `parent`, `guardian`, `caregiver`, `spouse`, `child`, `other`. Domain: `cascade:ProxyAgent`. (v3.3) | | `cascade:proxyScope` | DatatypeProperty | xsd:string | Authority scope, e.g. `full`, `read-only`, `investigation-only`. Composable with `cascade:consentScope` (data-sensitivity), which it does not replace. Domain: `cascade:ProxyAgent`. (v3.3) | | `cascade:proxyGrantedAt` | DatatypeProperty | xsd:dateTime | When proxy authority was granted. Domain: `cascade:ProxyAgent`. (v3.3) | | `cascade:proxyRevokedAt` | DatatypeProperty | xsd:dateTime | When proxy authority was revoked, if applicable. Domain: `cascade:ProxyAgent`. (v3.3) | ### Pod Export Manifest (v3.4) A pod export carries a `manifest.ttl` describing what the export contains. Before v3.4 every term in that file was undefined vocabulary: a consumer could parse a manifest but nothing said what its terms meant, and no shape checked it. v3.4 defines all 32 terms and shapes the three classes. The change is additive — no serializer emits anything different, and no existing manifest is rewritten. **Modelling.** Two of the three classes are expressed in ratified standards rather than novel Cascade terms: | Class | Modelled on | Why | |-------|-------------|-----| | `cascade:ExportManifest` | `dcat:Dataset` ([DCAT 3](https://www.w3.org/TR/vocab-dcat-3/)) | A pod export is a published dataset with a title, description, creation date and publisher. DCAT is a W3C Recommendation that already standardises exactly that. | | `cascade:RecordSummary` | `void:Dataset` ([VoID](https://www.w3.org/TR/void/)) | A record summary is a statistical description of a dataset subset: how many instances of each class it holds. Each count property is `rdfs:subPropertyOf void:entities` and is paired with the `void:class` it counts, so a VoID-aware consumer reads Cascade counts without Cascade-specific code. | | `cascade:InteractionScenario` | *(novel, deliberately)* | A clinically significant interaction detectable only by correlating resources of differing provenance — an EHR-prescribed drug against a self-reported supplement against a lab value. No ratified vocabulary models cross-provenance correlation as a first-class thing. | **Manifest structure properties** (domain `cascade:ExportManifest`): | Property | Type | Range | Description | |----------|------|-------|-------------| | `cascade:patientProfileVersion` | DatatypeProperty | xsd:string | Version of the patient profile structure, independent of `cascade:schemaVersion` | | `cascade:provenanceLayers` | ObjectProperty | rdf:List | The `cascade:DataProvenance` values represented anywhere in the export | | `cascade:clinicalSummary` | ObjectProperty | `cascade:RecordSummary` | Record counts for the clinical partition | | `cascade:wellnessSummary` | ObjectProperty | `cascade:RecordSummary` | Record counts for the wellness partition | | `cascade:deviceSources` | ObjectProperty | rdf:List | Devices that contributed data, each a `prov:Agent` | | `cascade:interactionScenarios` | ObjectProperty | rdf:List | `cascade:InteractionScenario` entries flagged in the export | **Record count properties** (domain `cascade:RecordSummary`, all `xsd:integer`). All seven are `rdfs:subPropertyOf void:entities`; six additionally name the `void:class` they count: | Property | `void:class` | |----------|--------------| | `cascade:conditionCount` | `health:ConditionRecord` | | `cascade:medicationCount` | `clinical:Medication` | | `cascade:allergyCount` | `health:AllergyRecord` | | `cascade:labResultCount` | `health:LabResultRecord` | | `cascade:immunizationCount` | `health:ImmunizationRecord` | | `cascade:supplementCount` | `clinical:Supplement` | | `cascade:coverageCount` | *(none stated)* — counts insurance coverage records | Day-count properties describe wellness coverage rather than record counts and are not `void:entities` subproperties: `cascade:vitalSignDays`, `cascade:heartRateDays`, `cascade:bloodPressureDays`, `cascade:activityDays`, `cascade:sleepDays`. `cascade:domain` names which partition a summary describes. **Interaction scenario properties** (domain `cascade:InteractionScenario`): `cascade:involvedResources` (rdf:List), `cascade:severity` (xsd:string), `cascade:requiresCrossProvenance` (xsd:boolean). **Reading-level terms** also defined in v3.4 because they are emitted on every history-container entry and are constrained by the health v2.5 daily-snapshot shapes: `cascade:date` (xsd:dateTime), `cascade:sampleCount` (xsd:integer), `cascade:loincCode` (ObjectProperty), plus `cascade:sourceType`, `cascade:dataTypes`, `cascade:version` and `cascade:notes`. ### SHACL Shapes **File:** `core.shapes.ttl` v1.1 Validation shapes for `cascade:PatientProfile` (required demographics, address structure), `cascade:DataProvenance` taxonomy, `cascade:UserResolution` (required conflictId + resolution), `cascade:PendingConflict` (required conflictId), and — new in v1.1 — `cascade:ExportManifest`, `cascade:RecordSummary` and `cascade:InteractionScenario`. See [SHACL Validation](#shacl-validation) for cross-vocabulary overview. --- ## Clinical Vocabulary v1.15 **Namespace:** `https://ns.cascadeprotocol.org/clinical/v1#` **Prefix:** `clinical:` **Status:** Stable **Imports:** Core Vocabulary v1 Vocabulary for clinical documents and structured health records imported from EHR systems via Apple HealthKit. > **v1.13 (deprecations):** `clinical:LabResult`, `clinical:Condition`, `clinical:Allergy` and `clinical:Immunization` are marked `owl:deprecated true` with `rdfs:seeAlso` pointing at their `health:` equivalents, which are the types every import path already emits. They are **retained, not removed** — the pod export path is still their sole emitter and existing pods contain them. Readers must accept both spellings; writers should prefer the `health:` classes. See [Health Vocabulary](#health-vocabulary). > **v1.9 (provenance):** `cascade:AIExtracted` is now a permitted `cascade:dataProvenance` value on every clinical record shape. Records produced by an AI document-extraction pipeline (e.g. the Workbench) carry `cascade:dataProvenance cascade:AIExtracted` and validate without error. See [Clinical SHACL Shapes](#clinical-shacl-shapes). ### Core Classes | Class | Description | FHIR Alignment | |-------|-------------|----------------| | `clinical:ClinicalDocument` | Clinical documentation imported from EHR systems | fhir:DocumentReference | | `clinical:ClinicalSection` | Section within a clinical document | - | | `clinical:ClinicalNarrative` | Free-text clinical narrative | - | ### Document Type Classes (LOINC-aligned) | Class | Description | |-------|-------------| | `clinical:ProgressNote` | Provider progress note documenting a patient encounter | | `clinical:DischargeSummary` | Hospital discharge summary document | | `clinical:ConsultationNote` | Specialist consultation note | | `clinical:LaboratoryReport` | Laboratory test results with narrative interpretation | | `clinical:ImagingReport` | Radiology/imaging report with findings | | `clinical:VisitSummary` | After-visit summary provided to patient | ### Structured Clinical Record Classes | Class | Description | FHIR Alignment | |-------|-------------|----------------| | `clinical:Medication` | Medication record from EHR | fhir:MedicationStatement | | `clinical:Allergy` | **Deprecated (v1.13)** — allergy or intolerance record. Use [`health:AllergyRecord`](#clinical-record-classes-v25). Retained: the pod export path is still its sole emitter and existing pods contain it. | fhir:AllergyIntolerance | | `clinical:LabResult` | **Deprecated (v1.13)** — laboratory test result. Use [`health:LabResultRecord`](#clinical-record-classes-v25). Retained for the same reason. | fhir:Observation | | `clinical:Condition` | **Deprecated (v1.13)** — medical condition or diagnosis. Use [`health:ConditionRecord`](#clinical-record-classes-v25). Retained for the same reason. | fhir:Condition | | `clinical:Immunization` | **Deprecated (v1.13)** — vaccination record. Use [`health:ImmunizationRecord`](#clinical-record-classes-v25). Retained for the same reason. | fhir:Immunization | | `clinical:Procedure` | Medical procedure record | fhir:Procedure | | `clinical:CoverageRecord` | **Deprecated (v1.5)** — Insurance coverage/plan record. Use `coverage:InsurancePlan` for new data. Retained for EHR import pipeline backward compatibility. | fhir:Coverage | | `clinical:VitalSign` | Vital sign observation | fhir:Observation | > **Deprecated is not removed, and it is not a migration.** The four v1.13 deprecations record which spelling is preferred going forward. No pod is rewritten, no `rdf:type` changes anywhere, and the deprecated classes keep their SHACL shapes. A conforming reader must accept both the `clinical:` and the `health:` spelling of these four record types. ### Longitudinal Record Classes (v1.2+) | Class | Version | Description | |-------|---------|-------------| | `clinical:MedicationUseEpisode` | v1.2 | Canonical longitudinal record of patient's relationship with a medication over time. Derived from one or more source records via reconciliation. Episodes represent continuous periods of use - a gap beyond threshold (90 days) creates a new episode. | | `clinical:Supplement` | v1.2 | Dietary supplement, OTC product, or herbal remedy with explicit regulatory status. Separate from Medication class due to different regulatory status and evidence requirements. | | `clinical:LabTestSeries` | v1.4 | Longitudinal record of repeated measurements of the same laboratory test (identified by LOINC code) over time. One series per LOINC code per patient. Includes trend inference with confidence and explainable reason. | ### FHIR Converter Classes (v1.7) Classes added to support lossless FHIR → Cascade conversion for resource types previously dropped by the converter. | Class | Description | FHIR Alignment | |-------|-------------|----------------| | `clinical:Encounter` | Healthcare encounter (office visit, ER visit, inpatient stay). Normalizes FHIR Encounter's multiple period expression paths to canonical start/end. | fhir:Encounter | | `clinical:MedicationAdministration` | Single medication administration event (administered by provider, not patient-reported). Distinct from `clinical:Medication` (patient-reported) and `clinical:MedicationUseEpisode` (longitudinal). | fhir:MedicationAdministration | | `clinical:ImplantedDevice` | Medical device implanted in the patient (pacemaker, joint replacement, insulin pump). Carries UDI carrier for regulatory traceability. | fhir:Device | | `clinical:ImagingStudy` | Diagnostic imaging study (X-ray, MRI, CT, ultrasound). Carries DICOM study UID and modality for linkage to external imaging systems. | fhir:ImagingStudy | ### Social History Classes (v1.8) `clinical:SocialHistoryRecord` captures EHR-extracted social history observations subject to 42 CFR Part 2 sensitivity handling. It is **distinct** from `health:SocialHistoryRecord` (consumer-reported lifestyle data imported from Apple Health / C-CDA Social History sections). The two-class design preserves regulatory provenance: | Class | Namespace | Source | Regulatory | |-------|-----------|--------|------------| | `health:SocialHistoryRecord` | `health:` | Consumer-reported (device, user entry, Apple Health import) | No special sensitivity | | `clinical:SocialHistoryRecord` | `clinical:` | EHR-extracted (C-CDA Social History section, clinical provider) | 42 CFR Part 2 — requires `cascade:SocialHistoryConsent` | | Class | Description | FHIR Alignment | |-------|-------------|----------------| | `clinical:SocialHistoryRecord` | EHR-extracted social history observation from a C-CDA Social History section (LOINC 29762-2). Carries provenance linking to source clinical document and consent record. Distinct from `health:SocialHistoryRecord` (consumer-reported). (v1.8) | fhir:Observation (social history category) | ### Key Properties - SocialHistoryRecord (v1.8) | Property | Type | Range | Description | |----------|------|-------|-------------| | `clinical:socialHistoryCategory` | owl:DatatypeProperty | xsd:string | Category of the social history observation. Values: `smokingStatus`, `alcoholUse`, `substanceUse`, `occupation`, `educationLevel`, `sexualOrientation`, `genderIdentity`, `householdIncome`, `housingStatus`, `socialIsolation`. Maps to FHIR Observation.category `social-history`. (v1.8) | | `clinical:packsPerYear` | owl:DatatypeProperty | xsd:decimal | Cumulative smoking exposure in pack-years (packs/day × years smoked). Applicable when `socialHistoryCategory` is `smokingStatus`. (v1.8) | | `clinical:substanceType` | owl:DatatypeProperty | xsd:string | Type of substance for substance-use records (e.g., `"alcohol"`, `"cannabis"`, `"opioid"`). Applicable when `socialHistoryCategory` is `substanceUse`. (v1.8) | | `clinical:frequencyDescription` | owl:DatatypeProperty | xsd:string | Free-text or coded frequency description (e.g., `"2-3 drinks/week"`, `"daily"`). (v1.8) | | `clinical:socialHistoryConsent` | owl:ObjectProperty | cascade:SocialHistoryConsent | Links this record to the patient's 42 CFR Part 2 consent record governing storage and processing. Required for `substanceUse` category records. (v1.8) | ### Key Properties - Encounter (v1.7) | Property | Type | Range | Description | |----------|------|-------|-------------| | `clinical:encounterClass` | owl:DatatypeProperty | xsd:string | Encounter classification: AMB (ambulatory), EMER (emergency), IMP (inpatient), SS (short stay), VR (virtual). Maps to FHIR Encounter.class. | | `clinical:encounterStatus` | owl:DatatypeProperty | xsd:string | Encounter workflow status: planned, arrived, triaged, in-progress, onleave, finished, cancelled. Maps to FHIR Encounter.status. | | `clinical:encounterType` | owl:DatatypeProperty | xsd:string | Clinical type of encounter (e.g., "Annual physical", "Follow-up visit"). Maps to FHIR Encounter.type display text. | | `clinical:encounterStart` | owl:DatatypeProperty | xsd:dateTime | Start of the encounter period. Maps to FHIR Encounter.period.start. | | `clinical:encounterEnd` | owl:DatatypeProperty | xsd:dateTime | End of the encounter period. Maps to FHIR Encounter.period.end. | | `clinical:facilityName` | owl:DatatypeProperty | xsd:string | Name of the healthcare facility where the encounter occurred. Extracted from FHIR Encounter.serviceProvider or contained Organization. | | `clinical:sourceRecordId` | owl:DatatypeProperty | xsd:string | Original FHIR resource ID from the source EHR system. Enables deduplication and cross-reference resolution. (No domain restriction — reusable across all clinical record classes.) | ### Key Properties - MedicationAdministration (v1.7) | Property | Type | Range | Description | |----------|------|-------|-------------| | `clinical:administeredDate` | owl:DatatypeProperty | xsd:dateTime | Date and time of medication administration. Maps to FHIR MedicationAdministration.effectiveDateTime. | | `clinical:administeredDose` | owl:DatatypeProperty | xsd:string | Dose administered as a human-readable string (value + unit, e.g., "500 mg"). Maps to FHIR MedicationAdministration.dosage.dose. | | `clinical:administeredRoute` | owl:DatatypeProperty | xsd:string | Route of administration (e.g., "oral", "IV", "subcutaneous"). Maps to FHIR MedicationAdministration.dosage.route.text. | | `clinical:administrationStatus` | owl:DatatypeProperty | xsd:string | Administration status: in-progress, not-done, on-hold, completed, entered-in-error, stopped, unknown. Maps to FHIR MedicationAdministration.status. | ### Key Properties - ImplantedDevice (v1.7) | Property | Type | Range | Description | |----------|------|-------|-------------| | `clinical:deviceType` | owl:DatatypeProperty | xsd:string | Type of implanted device (e.g., "Pacemaker", "Hip prosthesis"). Maps to FHIR Device.type display text. | | `clinical:implantDate` | owl:DatatypeProperty | xsd:dateTime | Date the device was implanted. Maps to FHIR Device.manufactureDate or associated Procedure.performedDateTime. | | `clinical:deviceManufacturer` | owl:DatatypeProperty | xsd:string | Manufacturer of the device. Maps to FHIR Device.manufacturer. | | `clinical:udiCarrier` | owl:DatatypeProperty | xsd:string | FDA Unique Device Identifier (UDI) carrier string. Maps to FHIR Device.udiCarrier.carrierHRF. Enables regulatory traceability. | | `clinical:deviceStatus` | owl:DatatypeProperty | xsd:string | Device status: active, inactive, entered-in-error, unknown. Maps to FHIR Device.status. | ### Key Properties - ImagingStudy (v1.7) | Property | Type | Range | Description | |----------|------|-------|-------------| | `clinical:imagingModality` | owl:DatatypeProperty | xsd:string | Imaging modality code (e.g., "MR", "CT", "US", "DX"). Maps to FHIR ImagingStudy.modality.code. | | `clinical:studyDescription` | owl:DatatypeProperty | xsd:string | Human-readable description of the imaging study. Maps to FHIR ImagingStudy.description. | | `clinical:numberOfSeries` | owl:DatatypeProperty | xsd:integer | Number of series in the study. Maps to FHIR ImagingStudy.numberOfSeries. | | `clinical:studyDate` | owl:DatatypeProperty | xsd:dateTime | Date and time the study was started. Maps to FHIR ImagingStudy.started. | | `clinical:dicomStudyUid` | owl:DatatypeProperty | xsd:string | DICOM Study Instance UID. Maps to FHIR ImagingStudy.identifier (DICOM system). Enables linkage to PACS/external imaging systems. | ### Key Properties - LaboratoryReport Additions (v1.7) | Property | Type | Range | Description | |----------|------|-------|-------------| | `clinical:hasLabResult` | owl:ObjectProperty | clinical:LabResult | Links a LaboratoryReport to one or more LabResult instances within the panel. Enables DiagnosticReport → LaboratoryReport grouping of contained Observation resources. (v1.7) | | `clinical:panelName` | owl:DatatypeProperty | xsd:string | Human-readable name of the laboratory panel (e.g., "Comprehensive Metabolic Panel", "CBC with Differential"). Maps to FHIR DiagnosticReport.code.text. (v1.7) | | `clinical:reportCategory` | owl:DatatypeProperty | xsd:string | Category of the laboratory report (e.g., "LAB", "CH" for chemistry, "HM" for hematology). Maps to FHIR DiagnosticReport.category.coding.code. (v1.7) | ### Key Properties - MedicationUseEpisode | Property | Range | Description | |----------|-------|-------------| | `clinical:canonicalName` | xsd:string | Normalized drug name for the medication episode | | `clinical:episodeFirstSeenDate` | xsd:dateTime | Earliest CLINICAL date from source records | | `clinical:episodeLastSeenDate` | xsd:dateTime | Most recent CLINICAL date from source records | | `clinical:inferredStatus` | xsd:string | Inferred active/inactive status | | `clinical:statusConfidence` | xsd:string | Confidence level: high, medium, low | | `clinical:statusReason` | xsd:string | Reason for status inference | | `clinical:usePattern` | xsd:string | Pattern: chronic, shortCourse, oneTime, asNeeded, unknown | | `clinical:derivedFromRecord` | clinical:Medication | Links to source Medication record(s) | ### Key Properties - Supplement | Property | Range | Description | |----------|-------|-------------| | `clinical:supplementName` | xsd:string | Name of the supplement or OTC product | | `clinical:regulatoryStatus` | xsd:string | dietarySupplement, otcDrug, homeopathic, herbalRemedy, unknown | | `clinical:evidenceStrength` | xsd:string | strongEvidence, moderateEvidence, limitedEvidence, traditionalUse, noEvidence, unknown | | `clinical:form` | xsd:string | capsule, tablet, softgel, liquid, powder, gummy, spray, patch, tea, tincture, other | | `clinical:dsldId` | xsd:string | NIH Dietary Supplement Label Database identifier | ### Key Properties - CoverageRecord (Deprecated) > **Deprecation Notice:** `clinical:CoverageRecord` is deprecated. Use `coverage:InsurancePlan` from `coverage.ttl` for new insurance data. This class and its properties are retained for backward compatibility with existing EHR import pipeline data. See [Coverage Vocabulary](#coverage-vocabulary) for the replacement. | Property | Range | Description | |----------|-------|-------------| | `clinical:providerName` | xsd:string | Insurance provider/company name | | `clinical:memberId` | xsd:string | Member ID on insurance card | | `clinical:groupNumber` | xsd:string | Group number for employer-sponsored plans | | `clinical:planName` | xsd:string | Name of the insurance plan | | `clinical:planType` | xsd:string | Plan type: hmo, ppo, epo, pos, hdhp, medicare, medicaid, tricare, other | | `clinical:coverageType` | xsd:string | Coverage classification (e.g., medical, dental, vision) | | `clinical:relationship` | xsd:string | Relationship to subscriber: self, spouse, child, parent, other | | `clinical:effectivePeriodStart` | xsd:dateTime | Coverage start date | | `clinical:effectivePeriodEnd` | xsd:dateTime | Coverage end date | | `clinical:payorName` | xsd:string | Name of the paying organization | | `clinical:subscriberId` | xsd:string | Subscriber ID (may differ from member ID) | ### Key Properties - Procedure | Property | Range | Description | |----------|-------|-------------| | `clinical:procedureName` | xsd:string | Name of the medical procedure | | `clinical:procedureStatus` | xsd:string | Procedure status: completed, in_progress, not_done, entered_in_error, unknown | | `clinical:procedureCategory` | xsd:string | Procedure category (e.g., Surgical, Diagnostic) | | `clinical:performedDate` | xsd:dateTime | Date the procedure was performed | | `clinical:bodySite` | xsd:string | Body site where the procedure was performed | | `clinical:performer` | xsd:string | Name of the performing provider | | `clinical:location` | xsd:string | Facility or location where the procedure was performed | | `clinical:outcome` | xsd:string | Outcome of the procedure | | `clinical:procedureSnomedCode` | xsd:string | SNOMED CT procedure code | | `clinical:cptCode` | xsd:string | CPT procedure code | ### Healthcare Standards Alignment **LOINC Document Type Mappings:** - Progress note: loinc:11506-3 - Discharge summary: loinc:18842-5 - Consultation note: loinc:11488-4 **LOINC Section Codes:** - Chief complaint: loinc:10154-3 - History of present illness: loinc:10164-2 - Medications: loinc:10160-0 - Allergies: loinc:48765-2 - Assessment: loinc:51848-0 --- ## Clinical SHACL Shapes SHACL validation shapes for clinical documents and structured records. ### Document Validation ```turtle clinical:ClinicalDocumentShape a sh:NodeShape ; sh:targetClass clinical:ClinicalDocument ; # Required: importedAt timestamp sh:property [ sh:path clinical:importedAt ; sh:datatype xsd:dateTime ; sh:minCount 1 ; sh:maxCount 1 ; sh:message "Clinical document must have exactly one importedAt timestamp" ] ; # Required: sourceEHR name sh:property [ sh:path clinical:sourceEHR ; sh:datatype xsd:string ; sh:minCount 1 ; sh:message "Clinical document must specify the source EHR system" ] ; # Required: provenance sh:property [ sh:path cascade:dataProvenance ; sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:ScannedDocument cascade:AIExtracted cascade:ConsumerGenerated cascade:DeviceGenerated cascade:SelfReported cascade:ConsumerWellness) ; sh:minCount 1 ; sh:message "Clinical documents must have valid provenance" ] . ``` ### MedicationUseEpisode Validation Required fields per Cascade Protocol: - `canonicalName` - Normalized drug name - `episodeFirstSeenDate` - Earliest clinical date - `episodeLastSeenDate` - Most recent clinical date - `derivedFromRecord` - Must link to at least one source record - `inferredStatus` - active or inactive - `statusReason` - Explains WHY system thinks this status - `statusConfidence` - high, medium, or low ### Supplement Validation Required fields: - `supplementName` - Name of the supplement - `regulatoryStatus` - REQUIRED to distinguish from FDA-approved medications --- ## Health Vocabulary v2.7 **Namespace:** `https://ns.cascadeprotocol.org/health/v1#` **Prefix:** `health:` **Status:** Stable **Imports:** Core Vocabulary v1 Vocabulary for consumer-generated wellness observations from wearable devices and Apple HealthKit, and for the clinical record types that Cascade serializers emit. Maps Cascade properties to established SNOMED CT and LOINC codes following the three-layer ontology architecture. This is a **Layer 2** vocabulary. > **v2.5 (clinical record classes and wellness containers):** Five classes that Cascade serializers have emitted since schema version 1.3 but that this ontology never defined are now defined and shaped — `health:LabResultRecord`, `health:ConditionRecord`, `health:AllergyRecord`, `health:ImmunizationRecord`, `health:FamilyHistoryRecord` — together with the 40 properties they use. Six wellness container classes are declared `rdfs:subClassOf health:HealthProfile`. Nothing is emitted differently; the release makes existing data describable and checkable rather than changing it. See [Clinical Record Classes](#clinical-record-classes-v25) and [Wellness Container Classes](#wellness-container-classes-v25). ### The `health:` / `clinical:` boundary is historical, not a provenance signal `health:` was introduced for consumer- and device-generated data and `clinical:` for EHR-sourced records, but no serializer has ever honoured that division. Records typed `health:LabResultRecord`, `health:ConditionRecord`, `health:AllergyRecord` and `health:ImmunizationRecord` routinely carry EHR-sourced data, and `clinical:VitalSign` records routinely carry consumer-device data. **Provenance is carried by `cascade:dataProvenance`, and only by it.** A consumer deciding whether a record came from an EHR, a device, or the patient must read `cascade:dataProvenance` (`ClinicalGenerated`, `EHRVerified`, `DeviceGenerated`, `PatientReported`, `SelfReported`, `AIExtracted`). Keying on the namespace of the `rdf:type` will give the wrong answer on real pods. Both namespaces are ratified and neither is being migrated. The four `clinical:` classes that duplicate the record classes below are deprecated in clinical v1.13 but are not removed. Readers must accept both spellings; writers should prefer the `health:` spelling. `health:SocialHistoryRecord` and `clinical:SocialHistoryRecord` are a separate case and both are deliberately retained. Neither is a reliable source indicator: the `clinical:` one is written only by the free-text extraction path, and the `health:` one is currently written by nothing. Accept both types and read `cascade:dataProvenance`. ### Clinical Record Classes (v2.5) Each class states the FHIR R4 resource it corresponds to. All five are targeted by SHACL shapes in `health.shapes.ttl` v1.2. | Class | Description | FHIR R4 alignment | |-------|-------------|-------------------| | `health:LabResultRecord` | A laboratory test result: the test performed, its value and unit, the reference range it is read against, and the specimen it came from. Subclass of `fhir:Observation`. | [Observation](https://hl7.org/fhir/R4/observation.html) | | `health:ConditionRecord` | A diagnosed condition or problem-list entry, with clinical status, onset and coding. | [Condition](https://hl7.org/fhir/R4/condition.html) | | `health:AllergyRecord` | An allergy or intolerance: the allergen, the reaction it provokes, and the severity of that reaction. | [AllergyIntolerance](https://hl7.org/fhir/R4/allergyintolerance.html) | | `health:ImmunizationRecord` | An administered vaccine dose, with product identification, lot, route, site and administering party. | [Immunization](https://hl7.org/fhir/R4/immunization.html) | | `health:FamilyHistoryRecord` | A condition recorded for a relative, with the relationship and the relative's age at onset. | [FamilyMemberHistory](https://hl7.org/fhir/R4/familymemberhistory.html) | Properties, by the class or classes that use them. Domains are unions rather than a single class, because a single-class `rdfs:domain` on a property that several classes use is an assertion the data falsifies: | Property | Range | Used by | |----------|-------|---------| | `health:testName`, `health:resultValue`, `health:resultUnit`, `health:referenceRange`, `health:interpretation`, `health:performedDate`, `health:reportedDate`, `health:testCode`, `health:labCategory`, `health:specimenType`, `health:orderingProvider`, `health:performingLab` | xsd:string / xsd:dateTime / IRI | `LabResultRecord` | | `health:conditionName`, `health:onsetDate`, `health:icd10Code`, `health:snomedCode`, `health:conditionClass`, `health:monitoredVitalSigns` | xsd:string / xsd:dateTime / IRI | `ConditionRecord` (`onsetDate` shared with `AllergyRecord`; `conditionName` shared with `FamilyHistoryRecord`) | | `health:allergen`, `health:reaction`, `health:allergySeverity`, `health:allergyCategory` | xsd:string | `AllergyRecord` | | `health:vaccineName`, `health:administrationDate`, `health:vaccineCode`, `health:manufacturer`, `health:lotNumber`, `health:doseQuantity`, `health:route`, `health:site`, `health:administeringProvider`, `health:administeringLocation` | xsd:string / xsd:dateTime | `ImmunizationRecord` | | `health:onsetAge` | xsd:integer | `FamilyHistoryRecord` (relationship is expressed with `clinical:relationship`) | | `health:status` | xsd:string | `ConditionRecord` and `ImmunizationRecord`, with different value sets — see the shapes below | | `health:sourceRecordId` | xsd:string | all five; opaque to Cascade, used for reconciliation against the source system, never as a Cascade identity | ### Wellness Container Classes (v2.5) The wellness partition of a pod stores each metric family in its own container resource. Those six container types are now declared `rdfs:subClassOf health:HealthProfile`: | Class | Holds | |-------|-------| | `health:ActivityData` | Daily activity history: steps, active energy, exercise minutes, stand hours | | `health:SleepData` | Nightly sleep history: duration and quality | | `health:HeartRateData` | Resting and walking heart rate history | | `health:BloodPressureData` | Blood pressure history from home monitors | | `health:HRVData` | Heart rate variability (SDNN) history | | `health:BodyMeasurements` | Body mass and related anthropometric history | This has two effects and requires no code change. The eight history properties (`health:restingHeartRateHistory`, `health:dailyActivityHistory` and siblings) already declare `rdfs:domain health:HealthProfile`; the subclass declarations make that domain true rather than contradicted. And because `sh:targetClass` is subclass-aware, the containers come under `health:HealthProfileShape`. The shapes file also states each container class as an explicit `sh:targetClass` so that coverage does not depend on a validator performing subclass inference. ### Composite Classes | Class | Description | |-------|-------------| | `health:ActivitySnapshot` | Aggregated activity metrics over a measurement period (typically 7 days). Subclass of `prov:Entity`. | | `health:SleepSnapshot` | Aggregated sleep metrics over a measurement period (typically 7 days). Subclass of `prov:Entity`. | | `health:MetricTrend` | Time-bounded trend for a single wellness metric. Captures direction, magnitude, confidence, and comparison period. Subclass of `prov:Entity`. (v1.5) | | `health:VO2MaxStatistics` | Statistical summary of VO2 Max readings over a measurement period (typically 180 days). Provides trend analysis, fitness classification, and min/max/mean calculations. (v1.7) | | `health:HRVStatistics` | Statistical summary of Heart Rate Variability (SDNN) readings over a measurement period. (v1.7) | | `health:BPStatistics` | Statistical summary of blood pressure readings over a measurement period with AHA category classification. Subclass of `prov:Entity`. (v1.8) | | `health:HealthProfile` | Top-level wellness profile aggregating vital signs, body measurements, activity, and sleep data from consumer devices. Stored at `/wellness/` in the Cascade Pod. Subclass of `prov:Entity`. (v2.0) | | `health:DailyVitalReading` | A single day's aggregated vital sign reading with value, unit, date, and sample count. Used in 90-day and 30-day history containers. Subclass of `fhir:Observation`. (v2.0) | | `health:HRVReading` | A single heart rate variability (SDNN) observation with timestamp and optional source. Subclass of `fhir:Observation`. (v2.0) | | `health:BloodPressureReading` | A single blood pressure observation with systolic and diastolic components, timestamp, and optional source. Subclass of `fhir:Observation`. (v2.0) | | `health:VitalSignReading` | A single vital sign observation with value, unit, measurement period, timestamp, and source. Used for baseline readings and VO2 Max history entries. Subclass of `fhir:Observation`. (v2.0) | | `health:DailyActivitySnapshot` | A single day's activity data: steps, active energy, exercise minutes, and stand hours. Subclass of `prov:Entity`. (v2.0) | | `health:DailySleepSnapshot` | A single night's sleep data: duration in hours and quality classification. Subclass of `prov:Entity`. (v2.0) | ### MetricTrend Properties (v1.5) | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:trendMetric` | ObjectProperty | — | The wellness property this trend describes | | `health:trendDirection` | DatatypeProperty | xsd:string | `increasing`, `decreasing`, `stable`, `insufficient_data` | | `health:trendMagnitude` | DatatypeProperty | xsd:decimal | Percentage change between comparison periods | | `health:trendPeriodStart` | DatatypeProperty | xsd:dateTime | Start of trend period | | `health:trendPeriodEnd` | DatatypeProperty | xsd:dateTime | End of trend period | | `health:trendBaselineValue` | DatatypeProperty | xsd:decimal | Reference value (e.g., 90-day average) | | `health:trendCurrentValue` | DatatypeProperty | xsd:decimal | Current period average (e.g., 7-day) | | `health:trendConfidence` | DatatypeProperty | xsd:string | `high` (>30 days), `medium` (14-30 days), `low` (<14 days) | | `health:trendSource` | ObjectProperty | cascade:DataSource | Data source that produced the observations | ### VO2MaxStatistics Properties (v1.7) | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:vo2Mean` | DatatypeProperty | xsd:double | Average VO2 Max over measurement period (mL/kg/min) | | `health:vo2Min` | DatatypeProperty | xsd:double | Lowest VO2 Max reading in period | | `health:vo2MaxValue` | DatatypeProperty | xsd:double | Highest VO2 Max reading in period | | `health:vo2SampleCount` | DatatypeProperty | xsd:integer | Number of measurements | | `health:vo2DaysCovered` | DatatypeProperty | xsd:integer | Unique days with measurements | | `health:vo2TrendDirection` | DatatypeProperty | xsd:string | `improving`, `declining`, `stable`, `unknown` | | `health:vo2PercentageChange` | DatatypeProperty | xsd:double | Percentage change from oldest to newest | | `health:fitnessClassification` | DatatypeProperty | xsd:string | Fitness level (see table below) | | `health:isSparseData` | DatatypeProperty | xsd:boolean | True if < 3 samples or < 30 days | ### Fitness Classification Values | Classification | VO2 Max Range (mL/kg/min) | Description | |---------------|---------------------------|-------------| | `veryPoor` | < 20 | Very poor cardiorespiratory fitness | | `poor` | 20-29 | Poor cardiorespiratory fitness | | `fair` | 30-34 | Fair cardiorespiratory fitness | | `good` | 35-44 | Good cardiorespiratory fitness | | `excellent` | 45-54 | Excellent cardiorespiratory fitness | | `superior` | ≥ 55 | Superior cardiorespiratory fitness | **Note:** Current classification is age/sex-agnostic. Future versions may incorporate user demographics (biological sex, date of birth) from Apple Health for personalized classification using standard VO2 Max percentile tables. ### VO2MaxStatistics TTL Example ```turtle @prefix health: . @prefix cascade: . @prefix xsd: . @prefix prov: . <#vo2-stats-001> a health:VO2MaxStatistics ; health:vo2Mean "42.5"^^xsd:double ; health:vo2Min "40.1"^^xsd:double ; health:vo2MaxValue "44.8"^^xsd:double ; health:vo2SampleCount "8"^^xsd:integer ; health:vo2DaysCovered "45"^^xsd:integer ; health:vo2TrendDirection "improving" ; health:vo2PercentageChange "5.2"^^xsd:double ; health:fitnessClassification "good" ; health:isSparseData "false"^^xsd:boolean ; cascade:dataProvenance cascade:ConsumerGenerated ; prov:generatedAtTime "2026-02-03T10:30:00Z"^^xsd:dateTime . ``` ### Shared Statistical Properties (v1.8) These temporal properties are shared across all statistics classes (VO2MaxStatistics, HRVStatistics, BPStatistics): | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:periodStart` | DatatypeProperty | xsd:dateTime | Start date of the statistical measurement period | | `health:periodEnd` | DatatypeProperty | xsd:dateTime | End date of the statistical measurement period | ### HRVStatistics Properties (v1.7, properties v1.8) | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:hrvSampleCount` | DatatypeProperty | xsd:integer | Number of HRV readings in the period | | `health:hrvDaysCovered` | DatatypeProperty | xsd:integer | Number of unique days with HRV measurements | | `health:hrvMean` | DatatypeProperty | xsd:double | Average HRV (SDNN) over the measurement period (ms) | | `health:hrvMedian` | DatatypeProperty | xsd:double | Median HRV (SDNN) over the measurement period (ms) | | `health:hrvStdDev` | DatatypeProperty | xsd:double | Standard deviation of HRV readings (ms) | | `health:hrvMin` | DatatypeProperty | xsd:double | Lowest HRV reading in the period (ms) | | `health:hrvMax` | DatatypeProperty | xsd:double | Highest HRV reading in the period (ms) | | `health:hrvPercentile25` | DatatypeProperty | xsd:double | 25th percentile of HRV readings (ms) | | `health:hrvPercentile75` | DatatypeProperty | xsd:double | 75th percentile of HRV readings (ms) | | `health:hrvTrendDirection` | DatatypeProperty | xsd:string | Trend direction: `improving`, `declining`, `stable`, `unknown` | ### BPStatistics Properties (v1.8) | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:bpSampleCount` | DatatypeProperty | xsd:integer | Number of blood pressure readings in the period | | `health:bpMeanSystolic` | DatatypeProperty | xsd:double | Average systolic blood pressure (mmHg) | | `health:bpMeanDiastolic` | DatatypeProperty | xsd:double | Average diastolic blood pressure (mmHg) | | `health:bpMinSystolic` | DatatypeProperty | xsd:double | Lowest systolic reading in the period (mmHg) | | `health:bpMaxSystolic` | DatatypeProperty | xsd:double | Highest systolic reading in the period (mmHg) | | `health:bpMinDiastolic` | DatatypeProperty | xsd:double | Lowest diastolic reading in the period (mmHg) | | `health:bpMaxDiastolic` | DatatypeProperty | xsd:double | Highest diastolic reading in the period (mmHg) | | `health:bpCategory` | DatatypeProperty | xsd:string | AHA category: `normal`, `elevated`, `hypertension_stage1`, `hypertension_stage2`, `hypertensive_crisis` | ### History Container Properties (v2.0) Time-series containers emitted by HealthProfileSerializer for trend analysis. Each property links a `health:HealthProfile` to an ordered list of readings. | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:restingHeartRateHistory` | ObjectProperty | health:DailyVitalReading | Time series of daily resting heart rate readings (90-day lookback) | | `health:walkingHeartRateHistory` | ObjectProperty | health:DailyVitalReading | Time series of daily walking heart rate readings (90-day lookback) | | `health:hrvHistory` | ObjectProperty | health:HRVReading | Time series of HRV (SDNN) readings (7-day lookback) | | `health:bloodPressureHistory` | ObjectProperty | health:BloodPressureReading | Time series of blood pressure readings (30-day lookback) with systolic and diastolic values | | `health:vo2MaxHistory` | ObjectProperty | health:VitalSignReading | Time series of VO2 Max readings (180-day lookback) | | `health:bodyMassHistory` | ObjectProperty | health:DailyVitalReading | Time series of daily body mass readings (30-day lookback) | | `health:dailyActivityHistory` | ObjectProperty | health:DailyActivitySnapshot | Time series of daily activity snapshots (90-day lookback): steps, exercise, calories, stand hours per day | | `health:dailySleepHistory` | ObjectProperty | health:DailySleepSnapshot | Time series of daily sleep snapshots (90-day lookback): duration and quality per night | ### SelfReport Base Class (v2.1) Base class for patient-reported daily check-ins and self-assessments. Apps extend this with domain-specific response data via the SelfReportable protocol pattern. | Class | Description | |-------|-------------| | `health:SelfReport` | Base class for patient-reported daily check-ins and self-assessments. Provides common structure for timestamped self-reports across health domains. Examples: medication adherence, symptom tracking, mood logging, sleep records. Subclass of `prov:Entity`. | | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:reportDate` | DatatypeProperty | xsd:dateTime | When the self-report was recorded | | `health:reportType` | DatatypeProperty | xsd:string | Category of self-report (e.g., medication_adherence, symptom_tracking, mood, sleep) | | `health:completionStatus` | DatatypeProperty | xsd:string | Whether the report is complete, partial, or skipped | | `health:reportContext` | DatatypeProperty | xsd:string | What tracking plan or list this report relates to (free-form reference) | | `health:reportNotes` | DatatypeProperty | xsd:string | Optional free-text notes from the patient | ### Statistics Container Linking Properties (v2.2) ObjectProperties used by CheckupSerializer to link an intake form or wellness profile to its statistical summary nodes. | Property | Type | Range | Description | |----------|------|-------|-------------| | `health:hasHRVStatistics` | ObjectProperty | health:HRVStatistics | Links a wellness profile to its HRV statistical summary | | `health:hasBPStatistics` | ObjectProperty | health:BPStatistics | Links a wellness profile to its blood pressure statistical summary | ### Wellness Properties with Standard Code Mappings | Metric | Property | SNOMED CT | LOINC | Unit | |--------|----------|-----------|-------|------| | Resting Heart Rate | `health:restingHeartRate` | 364075005 | 40443-4 | beats/min | | Walking Heart Rate | `health:walkingHeartRate` | 364075005 | 89270-3 | beats/min | | HRV (SDNN) | `health:heartRateVariability` | 80404004 | 80404-7 | ms | | Blood Pressure | `health:bloodPressure` | 75367002 | 85354-9 | — | | Systolic BP | `health:systolicBP` | 271649006 | 8480-6 | mmHg | | Diastolic BP | `health:diastolicBP` | 271650006 | 8462-4 | mmHg | | Respiratory Rate | `health:respiratoryRate` | 86290005 | 9279-1 | breaths/min | | VO2 Max | `health:vo2Max` | 251880009 | 60842-2 | mL/kg/min | | Walking Steadiness | `health:walkingSteadiness` | 364832000 | — | string | | Average Daily Steps | `health:averageDailySteps` | 68130003 | 41950-7 | steps | | Active Energy Burned | `health:activeEnergyBurnedKcal` | 251833007 | 41981-2 | kcal | | Exercise Minutes | `health:exerciseMinutesWeekly` | 68130003 | 73985-4 | min | | Stand Hours | `health:standHoursDaily` | — | — | hours | | Sleep Duration | `health:averageDurationHours` | 248263006 | 93832-4 | hours | | Body Weight | `health:bodyMass` | 27113001 | 29463-7 | kg | | Body Height | `health:bodyHeight` | 50373000 | 8302-2 | cm | | BMI | `health:bodyMassIndex` | 60621009 | 39156-5 | kg/m2 | | Body Temperature | `health:bodyTemperature` | 386725007 | 8310-5 | °C | | Oxygen Saturation | `health:oxygenSaturation` | 431314004 | 2708-6 | % | | Blood Glucose | `health:bloodGlucose` | 33747003 | 2339-0 | mg/dL | | Blood Type | `health:bloodType` | 365637002 | 882-1 | string | ### Annotation Properties | Property | Description | |----------|-------------| | `health:snomedCode` | Links a wellness property to its SNOMED CT concept | | `health:loincCode` | Links a wellness property to its LOINC observation code | | `health:unit` | Human-readable unit of measurement | | `health:ucumCode` | Unified Code for Units of Measure code | ### Social History (v2.4) `health:SocialHistoryRecord` represents social history observations about a patient. Social history records are imported from C-CDA Social History sections (LOINC 29762-2). | Term | Type | Range | Description | |------|------|-------|-------------| | `health:SocialHistoryRecord` | owl:Class | — | A social history observation about a patient, including smoking status, alcohol use, exercise, occupation, and other lifestyle factors. (v2.4) | | `health:smokingStatus` | DatatypeProperty | xsd:string | Patient's smoking status. Common values: `current-smoker`, `former-smoker`, `never-smoker`. (v2.4) | | `health:alcoholUse` | DatatypeProperty | xsd:string | Patient's alcohol consumption description. (v2.4) | | `health:exerciseFrequency` | DatatypeProperty | xsd:string | Patient's reported exercise frequency. (v2.4) | | `health:occupationalExposure` | DatatypeProperty | xsd:string | Occupational exposures relevant to patient health. (v2.4) | ### Health SHACL Shapes (v1.2) **File:** `health.shapes.ttl` v1.2. Existing shapes cover `health:HealthProfile` (required vital signs, measurement periods), `health:MetricTrend` (trendDirection/trendConfidence enum validation), `health:SocialHistoryRecord`, and the statistical summary classes. v1.2 adds eight. Five record shapes. All required-property constraints are `sh:Violation`, matching the `clinical:` shapes they were lifted from: | Shape | Required (`sh:minCount 1`) | Constrained value sets | |-------|----------------------------|------------------------| | `health:LabResultRecordShape` | `health:testName`, `cascade:dataProvenance`, `cascade:schemaVersion` | `health:interpretation` ∈ the 49 selectable codes of [HL7 v3 ObservationInterpretation](http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation), plus **all 15** [`data-absent-reason`](http://terminology.hl7.org/CodeSystem/data-absent-reason) codes (health v2.7; v2.6 admitted only `unknown`, which made every reason for an absence identical) and the ten retained pre-v2.6 words: 74 values. `health:interpretationSourceCode` (v2.7, `sh:maxCount 1`, otherwise unconstrained) carries a source code that is in neither value set, verbatim, rather than dropping it. `health:resultValue`, `health:resultUnit` and `health:referenceRange` are `sh:maxCount 1`; `health:testCode` and `health:labCategory` are repeatable (health v2.6). | | `health:ConditionRecordShape` | `health:conditionName`, `cascade:dataProvenance`, `cascade:schemaVersion` | `health:status` ∈ active, recurrence, relapse, inactive, remission, resolved | | `health:AllergyRecordShape` | `health:allergen`, `cascade:dataProvenance`, `cascade:schemaVersion` | `health:allergySeverity` ∈ mild, moderate, severe; `health:allergyCategory` ∈ food, medication, environment, biologic. `health:reaction` is repeatable. | | `health:ImmunizationRecordShape` | `health:vaccineName`, `cascade:dataProvenance`, `cascade:schemaVersion` | `health:status` ∈ completed, entered-in-error, not-done | | `health:FamilyHistoryRecordShape` | `health:conditionName`, `clinical:relationship`, `cascade:dataProvenance`, `cascade:schemaVersion` | `health:onsetAge` ∈ 0–130 | > **The `sh:maxCount 1` on `health:resultValue` is the constraint worth knowing about.** A FHIR bundle carrying two same-day glucose readings — a fasting 95 and a post-prandial 310, an entirely routine pairing — could previously merge into one subject asserting both values, and validation passed because nothing constrained the class. A query then returned `"95, 310"`, a single string no consumer can interpret. This shape rejects that at write time. Three daily-snapshot shapes for the wellness history entries: | Shape | Constraints | |-------|-------------| | `health:DailyVitalReadingShape` | `cascade:date` required (xsd:dateTime); `cascade:sampleCount` ≥ 0; `health:value`, `health:unit`, `health:date`, `cascade:loincCode` each `sh:maxCount 1` | | `health:DailyActivitySnapshotShape` | `cascade:date` required; `health:steps` ≥ 0; `health:activeEnergyKcal` ≥ 0; `health:exerciseMinutes` 0–1440; `health:standHours` 0–24 | | `health:DailySleepSnapshotShape` | `cascade:date` required; `health:durationHours` 0–24; `health:sleepQuality` ∈ `health:Excellent`, `health:Good`, `health:Fair`, `health:Poor` | See [SHACL Validation](#shacl-validation) for the cross-vocabulary overview. ### Notes - **Walking Steadiness** maps to `sct:364832000` (Balance finding) — the closest available SNOMED concept. Apple's OK/Low/Very Low classification is proprietary. - **Stand Hours** is Cascade-proprietary — no SNOMED CT or LOINC equivalent exists. - Vital sign properties use `fhir:Observation` as their domain. Activity and sleep properties use their respective snapshot classes. --- ## POTS Vocabulary v1.4 **Namespace:** `https://ns.cascadeprotocol.org/pots/v1#` **Prefix:** `pots:` **Status:** Stable **Imports:** Core Vocabulary v1 Domain-specific vocabulary for POTS (Postural Orthostatic Tachycardia Syndrome) home screening checks using the NASA Lean Test protocol. ### Core Classes | Class | Description | |-------|-------------| | `pots:POTSCheckResult` | Complete result bundle from a guided home orthostatic screening check | | `pots:HeartRateMeasurement` | Timed heart rate observation during standing phase | | `pots:BloodPressureMeasurement` | Timed blood pressure observation during check | | `pots:SymptomEvent` | User-reported symptom during standing phase | | `pots:PostureStability` | Posture quality measurement from Core Motion sensors | ### Test Protocol | Class | Description | |-------|-------------| | `pots:NASALean` | 10 minutes supine followed by 10 minutes standing against wall | ### Key Properties | Property | Range | Description | |----------|-------|-------------| | `pots:date` | xsd:dateTime | ISO 8601 timestamp when check was performed | | `pots:protocol` | xsd:string | Protocol used (currently only 'nasaLean') | | `pots:supineHeartRate` | pots:HeartRateMeasurement | Baseline heart rate from supine phase | | `pots:standingHeartRates` | rdf:List | Ordered list of standing phase measurements | | `pots:maxHeartRateDelta` | xsd:double | Maximum HR increase using 30-second rolling window average | | `pots:potsThresholdMet` | xsd:boolean | True if HR increase meets criteria (>=30 bpm adults, >=40 bpm pediatric) | | `pots:orthostaticHypotensionSuspected` | xsd:boolean | True if BP drop suggests OH | | `pots:orthostaticHypertensionSuspected` | xsd:boolean | True if BP increase suggests OH | | `pots:userAgeAtTest` | xsd:integer | User's age for threshold calculation | | `pots:postureQualityScore` | xsd:string | Letter grade (A-D) for posture stability | | `pots:postureStability` | rdf:List | List of posture stability measurements from Core Motion during standing phase | ### Timing Properties (v1.3) | Property | Range | Description | |----------|-------|-------------| | `pots:secondsSinceStand` | xsd:double | Time offset in seconds from the moment the user stood up. Used in standingHeartRates and standingBloodPressures items. | | `pots:timestampSeconds` | xsd:double | Absolute timestamp in seconds. Used in SymptomEvent items for precise event timing. | ### Apple Health Import Properties (v1.3) | Property | Type | Range | Description | |----------|------|-------|-------------| | `pots:importedBloodPressureHistory` | ObjectProperty | rdf:List | Collection of blood pressure readings imported from Apple Health for longitudinal context | | `pots:importedHRVHistory` | ObjectProperty | rdf:List | Collection of HRV readings imported from Apple Health for longitudinal context | | `pots:averageImportedBloodPressure` | DatatypeProperty | xsd:string | Computed average of imported BP readings, formatted as 'systolic/diastolic' | | `pots:averageImportedHRV` | DatatypeProperty | xsd:double | Computed average of imported HRV readings from Apple Health (ms) | ### V2 Test Configuration & Patient Context (v1.4+) | Property | Type | Range | Description | |----------|------|-------|-------------| | `pots:baselineDurationMinutes` | DatatypeProperty | xsd:integer | User-selected baseline duration in whole minutes (5, 10, or 15) (v1.4+) | | `pots:testNotes` | DatatypeProperty | xsd:string | Optional free-text patient notes for the session (max 500 chars) (v1.4+) | | `pots:bpProvided` | DatatypeProperty | xsd:boolean | Whether a blood pressure cuff was used during this check (v1.4+) | | `pots:pppMinute1` | DatatypeProperty | xsd:decimal | PPP ratio (SBP-DBP)/SBP at 1 minute of standing (0.0-1.0) (v1.4+) | | `pots:pppMinute3` | DatatypeProperty | xsd:decimal | PPP ratio at 3 minutes of standing (0.0-1.0) (v1.4+) | | `pots:pppMinute5` | DatatypeProperty | xsd:decimal | PPP ratio at 5 minutes of standing (0.0-1.0) (v1.4+) | | `pots:pppMinute7` | DatatypeProperty | xsd:decimal | PPP ratio at 7 minutes of standing (0.0-1.0) (v1.4+) | | `pots:pppMinute10` | DatatypeProperty | xsd:decimal | PPP ratio at 10 minutes of standing (0.0-1.0) (v1.4+) | ### SNOMED CT Mappings - Heart rate: sct:364075005 - Supine position: sct:40199007 - Standing position: sct:10904000 - POTS: sct:703273007 - Orthostatic hypotension: sct:28651003 ### Data Classification - **Type:** Consumer wellness data (non-diagnostic) - **Target Age Range:** 13+ (COPPA compliant) - **Devices:** Apple Watch (heart rate), iPhone (posture tracking) - **Protocol:** NASA Lean Test (10 min supine + 10 min standing) ### SHACL Shapes **File:** `pots.shapes.ttl` v1.0 Validation shapes for `pots:POTSCheckResult` (required protocol fields, heart rate measurements, threshold calculations) and `pots:HeartRateMeasurement` timing constraints. See [SHACL Validation](#shacl-validation) for cross-vocabulary overview. --- ## Coverage Vocabulary v1.4 **Namespace:** `https://ns.cascadeprotocol.org/coverage/v1#` **Prefix:** `coverage:` **Status:** Stable **Imports:** Core Vocabulary v1 **Layer:** 2 (Domain Vocabulary) **FHIR Alignment:** `fhir:Coverage`, `fhir:Claim`, `fhir:ExplanationOfBenefit` Domain vocabulary for patient-owned insurance, benefits, and financial health data. Resolves the overlap between `checkup:InsuranceInfo` (Layer 3, patient-reported) and `clinical:CoverageRecord` (Layer 2, EHR-imported) with a unified, standardized representation. v1.3 adds clinical evidence linkage, appeal outcome detail, and standard-mapped denial reason codes (HL7/CARIN IG) for the Cascade Appeal system. ### Classes (8) | Class | Description | FHIR Mapping | Status | |-------|-------------|-------------|--------| | `coverage:InsurancePlan` | Insurance plan enrollment | `fhir:Coverage` | Active (14 properties) | | `coverage:Deductible` | Annual deductible tracking | `fhir:ExplanationOfBenefit` (partial) | Active (3 properties, v1.1) | | `coverage:CopaySchedule` | Copay amounts by service | -- | Active (3 properties, v1.1) | | `coverage:ClaimRecord` | Single insurance claim submitted to payer | `fhir:Claim` | Active (6 properties, v1.1) | | `coverage:BenefitStatement` | Payer's adjudication decision on a claim (EOB) | `fhir:ExplanationOfBenefit` | Active (9 properties, v1.1) | | `coverage:DenialNotice` | Formal denial of coverage initiating an appeal process. Recommended Pod storage: `/coverage/denials/{id}.ttl` | `fhir:ExplanationOfBenefit` | Active (7 properties, v1.2-1.3) | | `coverage:AppealRecord` | Formal appeal filing contesting a DenialNotice. One record per appeal level. Recommended Pod storage: `/coverage/appeals/{id}.ttl` | `fhir:Task` | Active (7 properties, v1.2-1.3) | | `coverage:DenialReasonCode` | Enumeration class for structured denial reason codes. Named individuals align with HL7 ClaimResponse adjudication and CARIN IG Blue Button STU2. | `http://terminology.hl7.org/CodeSystem/adjudication` | Active (v1.3) | ### InsurancePlan Properties (14) **Core Insurance Properties (9):** | Property | Type | Description | From | |----------|------|-------------|------| | `coverage:providerName` | xsd:string | Insurance company name | checkup + clinical | | `coverage:memberId` | xsd:string | Member/subscriber ID number | both | | `coverage:groupNumber` | xsd:string | Employer group number | both | | `coverage:planName` | xsd:string | Insurance plan name | clinical | | `coverage:planType` | xsd:string | HMO, PPO, EPO, POS, HDHP, etc. | clinical | | `coverage:coverageType` | xsd:string | Maps to FHIR `Coverage.type`, an **extensible** binding, so a payer-local or `v3-ActCode` code (e.g. `EHCPOL`) is conformant. `primary`, `secondary`, `dental`, `vision` are retained. Since coverage v1.4 the value check is `sh:Warning`; presence is still required. | both | | `coverage:effectiveStart` | xsd:date | Coverage period start date | both | | `coverage:effectiveEnd` | xsd:date | Coverage period end date | both | | `coverage:subscriberId` | xsd:string | Policy holder ID from insurer | clinical | **Subscriber Properties (2):** | Property | Type | Description | From | |----------|------|-------------|------| | `coverage:subscriberName` | xsd:string | Name of insurance subscriber | checkup (Intake Plan) | | `coverage:subscriberRelationship` | xsd:string | The [SubscriberPolicyholder](http://terminology.hl7.org/CodeSystem/subscriber-relationship) code system in full, which FHIR `Coverage.relationship` binds to: child, parent, spouse, common, other, self, injured | checkup + clinical | **Pharmacy Benefit Properties (3):** | Property | Type | Description | From | |----------|------|-------------|------| | `coverage:rxBin` | xsd:string | Pharmacy benefit BIN | checkup | | `coverage:rxPcn` | xsd:string | Pharmacy benefit PCN | checkup | | `coverage:rxGroup` | xsd:string | Pharmacy benefit group number | checkup | ### Mixed Namespace Usage `checkup:InsuranceInfo` (Layer 3) uses `coverage:` properties for standardized insurance data. Properties that remain in `checkup:`: | Property | Reason | |----------|--------| | `checkup:primaryCareProvider` | App-layer enrichment (PCP is not insurance data) | | `checkup:pcpPhone` | App-layer enrichment | ### Recommended Pod Storage ``` /coverage/ plans/ {plan-id}.ttl # InsurancePlan instances deductibles/ {year}.ttl # Annual deductible tracking (future) ``` ### Deductible Properties (v1.1) | Property | Type | Description | |----------|------|-------------| | `coverage:deductibleAmount` | xsd:decimal | Annual deductible amount in USD | | `coverage:deductibleMet` | xsd:decimal | Amount of deductible applied to date | | `coverage:outOfPocketMax` | xsd:decimal | Annual out-of-pocket maximum in USD | ### CopaySchedule Properties (v1.1) | Property | Type | Description | |----------|------|-------------| | `coverage:copayPrimary` | xsd:decimal | Primary care visit copay in USD | | `coverage:copaySpecialist` | xsd:decimal | Specialist visit copay in USD | | `coverage:copayEmergency` | xsd:decimal | Emergency room visit copay in USD | ### ClaimRecord Properties (v1.1) Maps to FHIR `Claim` resource. Represents a single insurance claim submitted by a provider to the payer. | Property | Type | Range | Description | |----------|------|-------|-------------| | `coverage:claimDate` | owl:DatatypeProperty | xsd:dateTime | Date the claim was submitted. Maps to FHIR Claim.billablePeriod.start. | | `coverage:claimTotal` | owl:DatatypeProperty | xsd:decimal | Total billed amount for the claim in USD. Maps to FHIR Claim.total.value. | | `coverage:claimStatus` | owl:DatatypeProperty | xsd:string | Claim lifecycle status: active, cancelled, draft, entered-in-error. Maps to FHIR Claim.status. | | `coverage:claimType` | owl:DatatypeProperty | xsd:string | Category of claim: institutional, oral, pharmacy, professional, vision. Maps to FHIR Claim.type.coding.code. | | `coverage:billingProvider` | owl:DatatypeProperty | xsd:string | Name of the billing provider or organization. Maps to FHIR Claim.provider display text. | | `coverage:sourceRecordId` | owl:DatatypeProperty | xsd:string | Original FHIR resource ID from the source EHR system. Enables deduplication and cross-reference from BenefitStatement. (No domain restriction — shared with clinical: classes.) | ### BenefitStatement Properties (v1.1) Maps to FHIR `ExplanationOfBenefit` resource. Represents the payer's adjudication decision on a claim. | Property | Type | Range | Description | |----------|------|-------|-------------| | `coverage:adjudicationDate` | owl:DatatypeProperty | xsd:dateTime | Date the claim was adjudicated by the payer. Maps to FHIR ExplanationOfBenefit.created. | | `coverage:adjudicationStatus` | owl:DatatypeProperty | xsd:string | EOB processing status: active, cancelled, draft, entered-in-error. Maps to FHIR ExplanationOfBenefit.status. | | `coverage:outcomeCode` | owl:DatatypeProperty | xsd:string | Adjudication outcome: queued, complete, error, partial. Maps to FHIR ExplanationOfBenefit.outcome. | | `coverage:denialReason` | owl:DatatypeProperty | xsd:string | Human-readable reason for claim denial or partial payment (if applicable). | | `coverage:totalBilled` | owl:DatatypeProperty | xsd:decimal | Total amount billed by the provider. Maps to FHIR ExplanationOfBenefit.total (category=submitted). | | `coverage:totalPaid` | owl:DatatypeProperty | xsd:decimal | Total amount paid by the payer. Maps to FHIR ExplanationOfBenefit.total (category=benefit). | | `coverage:patientResponsibility` | owl:DatatypeProperty | xsd:decimal | Amount the patient is responsible for (deductible + copay + coinsurance). Maps to FHIR ExplanationOfBenefit.total (category=patientpay). | | `coverage:relatedClaim` | owl:ObjectProperty | coverage:ClaimRecord | Links this BenefitStatement to the originating ClaimRecord. Maps to FHIR ExplanationOfBenefit.claim. Resolved via deterministic subject URI minting (OQ-2). | | `coverage:sourceRecordId` | owl:DatatypeProperty | xsd:string | Original FHIR resource ID from the source EHR system. | ### FHIR Coverage Alignment | coverage: property | FHIR Coverage field | |--------------------|---------------------| | `coverage:providerName` | `Coverage.payor.display` | | `coverage:memberId` | `Coverage.identifier` | | `coverage:groupNumber` | `Coverage.class` (group) | | `coverage:planName` | `Coverage.class` (plan) | | `coverage:planType` | `Coverage.type` | | `coverage:coverageType` | `Coverage.type` | | `coverage:effectiveStart` | `Coverage.period.start` | | `coverage:effectiveEnd` | `Coverage.period.end` | | `coverage:subscriberId` | `Coverage.subscriberId` | | `coverage:subscriberName` | `Coverage.subscriber` | | `coverage:subscriberRelationship` | `Coverage.relationship` | ### DenialNotice Properties (v1.2-1.3) Represents a formal denial of coverage issued by an insurer. Distinct from `BenefitStatement` — a `DenialNotice` initiates an appeal process, not a payment record. | Property | Type | Description | Version | |----------|------|-------------|---------| | `coverage:deniedProcedureCode` | xsd:string | CPT or HCPCS code of the denied service (e.g., E0787 for CGM) | v1.2 | | `coverage:denialReasonCode` | xsd:string | Structured coded denial reason. Use `coverage:DenialReasonCode` named individuals (see below). Aligns with HL7 ClaimResponse adjudication and CARIN IG Blue Button STU2. Distinct from free-text `coverage:denialReason` | v1.2 | | `coverage:denialLetterDate` | xsd:date | Date the denial notice was issued. The appeal deadline clock starts from this date | v1.2 | | `coverage:appealDeadline` | xsd:date | Computed deadline for filing an appeal, based on denial date, plan type, and jurisdiction rules | v1.2 | | `coverage:coveragePolicyReference` | xsd:string | The LCD, NCD, or coverage policy cited as the basis for denial (e.g., LCD L33822) | v1.2 | | `coverage:sourceRecordId` | xsd:string | Insurer-assigned claim or denial number (shared property, no domain restriction) | v1.2 | | `coverage:deniedClinicalContext` | owl:ObjectProperty (rdfs:Resource) | References clinical/health/pots records in the Pod relevant to this denial. Enables appeals to cite specific clinical evidence against policy criteria. Range is open to allow cross-namespace linking. | v1.3 | Note: `coverage:providerName`, `coverage:memberId`, `coverage:planType`, and `coverage:denialReason` (free-text) are also used by `DenialNotice` via shared or existing properties. ### DenialReasonCode Named Individuals (v1.3) Standard-mapped enumeration values for `coverage:denialReasonCode`. Aligned with HL7 ClaimResponse adjudication codes and [CARIN IG for Blue Button STU2](http://hl7.org/fhir/us/carin-bb/STU2/). | Named Individual | Label | Standard Alignment | |------------------|-------|--------------------| | `coverage:NotMedicallyNecessary` | Not Medically Necessary | HL7 adjudication `noauth`; CMS denial code 50 | | `coverage:LCDCriteriaNotMet` | LCD Criteria Not Met | CMS Local Coverage Determination criteria | | `coverage:NCDCriteriaNotMet` | NCD Criteria Not Met | CMS National Coverage Determination criteria | | `coverage:ServiceNotCovered` | Service Not Covered | HL7 adjudication `benefit` | | `coverage:PriorAuthorizationRequired` | Prior Authorization Required | HL7 adjudication `noauth`; CARIN IG X12 835 reason code 4 | | `coverage:DuplicateClaim` | Duplicate Claim | X12 835 reason code 18 | | `coverage:InformationRequested` | Additional Information Requested | X12 835 reason code 16 | ### AppealRecord Properties (v1.2-1.3) Represents a formal appeal filing contesting a `DenialNotice`. One `AppealRecord` per appeal level. | Property | Type | Description | Version | |----------|------|-------------|---------| | `coverage:appealLevel` | xsd:string | Administrative level: `redetermination`, `qio_reconsideration`, `alj_hearing`, `mac_review`, `federal_court`, `internal_level1`, `internal_level2`, `external_review`, `state_insurance_dept`, `litigation` | v1.2 | | `coverage:relatedDenialNotice` | coverage:DenialNotice | Links this AppealRecord to the DenialNotice it contests | v1.2 | | `coverage:appealFiledDate` | xsd:date | Date the appeal was formally filed | v1.2 | | `coverage:appealOutcome` | xsd:string | Outcome: `approved`, `denied`, `partial`, `withdrawn`, `pending` | v1.2 | | `coverage:appealOutcomeDate` | xsd:date | Date the appeal outcome was received | v1.2 | | `coverage:outcomeReason` | xsd:string | Free-text explanation of why the appeal was approved, denied, or partially approved. Captures the insurer's stated rationale. | v1.3 | | `coverage:reversalAmount` | xsd:decimal | Reimbursement amount granted if the appeal was approved or partially approved. Expressed as a decimal in the currency of the related InsurancePlan. | v1.3 | ### Recommended Pod Storage ``` /coverage/ plans/ {plan-id}.ttl # InsurancePlan instances deductibles/ {year}.ttl # Annual deductible tracking (future) denials/ {denial-id}.ttl # DenialNotice instances (v1.2) appeals/ {appeal-id}.ttl # AppealRecord instances (v1.2) ``` ### Future Companion Vocabulary: billing: A `billing:` vocabulary (Layer 2) will complement `coverage:` for patient-collected claims and bills: `billing:Claim`, `billing:Bill`, `billing:Payment`, `billing:ExplanationOfBenefits`. ### SHACL Shapes **File:** `coverage.shapes.ttl` v1.3 Validation shapes for `coverage:InsurancePlan`, `coverage:ClaimRecord`, `coverage:BenefitStatement`, `coverage:DenialNotice`, and `coverage:AppealRecord`. See [SHACL Validation](#shacl-validation) for cross-vocabulary overview. ### Version History | Version | Date | Changes | |---------|------|---------| | v1.0 | 2026-02-18 | Initial release: InsurancePlan (14 properties), Deductible, CopaySchedule | | v1.1 | 2026-03-12 | Added ClaimRecord (6 properties) and BenefitStatement (9 properties) with FHIR Claim/EOB alignment | | v1.2 | 2026-03-16 | Added DenialNotice class (6 properties) and AppealRecord class (5 properties) for denial/appeal workflow | | v1.3 | 2026-03-17 | Added `coverage:deniedClinicalContext` (clinical evidence linkage), `coverage:outcomeReason`, `coverage:reversalAmount`, `coverage:DenialReasonCode` enumeration class with 7 named individuals mapped to HL7/CARIN IG adjudication codes | --- ## Checkup Vocabulary v3.3 **Namespace:** `https://ns.cascadeprotocol.org/checkup/v1#` **Prefix:** `checkup:` **Status:** Stable **Imports:** Core Vocabulary v1, Clinical Vocabulary v1, Health Vocabulary v1 Vocabulary for patient intake forms and pre-visit health data aggregation. ### Core Classes (28 active) | Class | Description | |-------|-------------| | `checkup:IntakeFormData` | Aggregated patient data for healthcare intake forms | | `checkup:InsuranceInfo` | Insurance coverage details | | `checkup:MedicationSummary` | Current medication with adherence data | | `checkup:SupplementSummary` | Dietary supplement with regulatory classification | | `checkup:AllergySummary` | Known allergen with reaction type and severity | | `checkup:ConditionSummary` | Active or resolved health condition | | `checkup:FamilyHistoryEntry` | Family member health condition for risk assessment | | `checkup:ScreeningStatus` | Preventive screening status (USPSTF/ACS guidelines) | | `checkup:VisitIssue` | Chief complaint for visit planning | | `checkup:SuggestedQuestion` | AI-generated question for provider | | `checkup:DiagnosticTestResult` | Reference to external diagnostic test (e.g., POTS Check) | | `checkup:WellnessSummary` | Layer 3 patient-facing wellness summary aggregating `health:MetricTrend` data (v1.4) | | `checkup:TrendInsight` | Patient-readable observation derived from trend data (v1.4) | | `checkup:CorrelationInsight` | Temporal correlation between data domains — never implies causation (v1.4) | | `checkup:KeyIndicator` | Distilled health metric for dashboard display with traffic-light status (v1.4) | | `checkup:AttentionItem` | Flags baseline deviations, stale data, or threshold crossings (v1.4) | | `checkup:VisitPrepSummary` | Curated one-page clinical summary for appointments (v1.4) | | `checkup:DailyCheckIn` | Patient-reported daily medication adherence check-in with optional notes (v1.5) | | `checkup:DiscussionTopic` | AI-generated or user-created topic for discussion during a healthcare visit (v1.5) | | `checkup:CheckInSettings` | Application configuration for daily check-in notifications and scheduling (v1.5) | | `checkup:IntakeQuestionnaireResponse` | FHIR-inspired intake questionnaire response using LOINC panel codes and SNOMED CT answers (v1.6) | | `checkup:ResponseItem` | Question-answer pair within a questionnaire response with nested sub-item support (v1.6) | | `checkup:ImmunizationSummary` | Patient-facing immunization record for intake forms using mixed namespace pattern (v1.7) | | `checkup:LabResultSummary` | Patient-facing lab result summary using mixed namespace pattern with `clinical:` properties (v1.7) | | `checkup:WellnessProfileReference` | Lightweight pointer to the SDK HealthProfile stored in a separate Pod location (v1.7) | | `checkup:ProcedureSummary` | Patient-facing procedure record using mixed namespace pattern: `checkup:` for app-layer fields, `clinical:` for standardized data (v1.8) | | `checkup:MedicationChangeRecord` | Recorded change to a medication or supplement since the last visit snapshot. Tracks change deltas for the "Changes Since Last Visit" report section (v1.9) | | `checkup:VisitPrepTray` | Organized per-appointment visit preparation container grouping issues, topics, questions, and notes with intent categories and resolution tracking (v3.1) | ### Sub-Concern Classification (v1.10) Every `checkup:` class is annotated with `cascade:subConcern` to document its functional role within the vocabulary. This taxonomy supports migration planning and vocabulary organization. | Sub-Concern | Description | Classes | |-------------|-------------|---------| | **summary** | Genuine L3 — aggregates L2 data into patient-facing records | MedicationSummary, SupplementSummary, AllergySummary, ConditionSummary, FamilyHistoryEntry, ImmunizationSummary, LabResultSummary, ProcedureSummary | | **analytics** | L3 insights — AI-generated or computed patient-facing analysis | WellnessSummary, TrendInsight, CorrelationInsight, KeyIndicator, AttentionItem, VisitPrepSummary, SuggestedQuestion, DiscussionTopic, MedicationChangeRecord | | **intake** | Data collection — captures new patient-reported data | IntakeFormData, VisitIssue, IntakeQuestionnaireResponse, ResponseItem, DiagnosticTestResult, VisitPrepTray | | **self-report** | Patient-reported outcomes — subclass of `health:SelfReport` (WS7 complete) | DailyCheckIn | | **financial** | Insurance/billing — migrating to coverage: domain (WS4) | InsuranceInfo | | **app-config** | Application settings — target: /settings/ hierarchy (WS5) | CheckInSettings | | **structural** | Technical references, not domain concepts | WellnessProfileReference, ScreeningStatus | ### Dark Vocabulary Remediation (v1.10) The following properties are emitted by CheckupSerializer but were not previously defined in the ontology. They are now formally defined in `checkup.ttl` with migration annotations indicating their future namespace. #### Tier 3 Patient Profile Properties (**Removed v3.0** — migrated to `cascade:` namespace in core.ttl v2.0) > **Removed in v3.0:** `checkup:PatientProfile` and these associated properties have been removed from the active vocabulary. Use the `cascade:` equivalents defined in Core Vocabulary v2.0 (`cascade:PatientProfile`, `cascade:Address`, `cascade:PharmacyInfo`, `cascade:AdvanceDirectives`). Existing serialized data using `checkup:` properties remains parseable but new serialization must use the `cascade:` namespace. See [Patient Profile Classes (v2.0)](#patient-profile-classes-v20) in Core Vocabulary. | Property | Range | Migration Target | Status | |----------|-------|-----------------|--------| | `checkup:preferredLanguage` | xsd:string | `dct:language` in `/profile/card.ttl` | **Deprecated v2.0** | | `checkup:maritalStatus` | xsd:string | `cascade:maritalStatus` | **Deprecated v2.0** | | `checkup:raceEthnicity` | xsd:string | `cascade:raceEthnicity` | **Deprecated v2.0** | | `checkup:preferredPharmacy` | xsd:string | `cascade:preferredPharmacy` (blank-node structured) | **Deprecated v2.0** | | `checkup:hasLivingWill` | xsd:boolean | `cascade:hasLivingWill` on `cascade:AdvanceDirectives` | **Deprecated v2.0** | | `checkup:hasPowerOfAttorney` | xsd:boolean | `cascade:hasPowerOfAttorney` on `cascade:AdvanceDirectives` | **Deprecated v2.0** | | `checkup:hasDNR` | xsd:boolean | `cascade:hasDNR` on `cascade:AdvanceDirectives` | **Deprecated v2.0** | | `checkup:advanceDirectiveNotes` | xsd:string | `cascade:advanceDirectiveNotes` on `cascade:AdvanceDirectives` | **Deprecated v2.0** | #### Insurance Properties (migrating to `coverage:` in WS4) | Property | Range | Migration Target | |----------|-------|-----------------| | `checkup:subscriberName` | xsd:string | `coverage:subscriberName` | | `checkup:subscriberRelationship` | xsd:string | `coverage:subscriberRelationship` | | `checkup:effectiveDate` | xsd:date | `coverage:effectiveStart` | | `checkup:expirationDate` | xsd:date | `coverage:effectiveEnd` | | `checkup:rxBin` | xsd:string | `coverage:rxBin` | | `checkup:rxPcn` | xsd:string | `coverage:rxPcn` | | `checkup:rxGroup` | xsd:string | `coverage:rxGroup` | ### Patient Profile Properties > **Removed in v3.0:** `checkup:PatientProfile` class has been removed. Use `cascade:PatientProfile` from Core Vocabulary v2.0. The properties below are retained in the ontology for backward compatibility with existing serialized data but must not be used in new serialization. | Property | Range | Description | |----------|-------|-------------| | `checkup:patientName` | xsd:string | Full legal name | | `checkup:dateOfBirth` | xsd:date | Date of birth — use `cascade:dateOfBirth` for new data | | `checkup:computedAge` | xsd:integer | Age in years at form generation | | `checkup:ageGroup` | xsd:string | pediatric, young_adult, adult, senior — use `cascade:ageGroup` for new data | | `checkup:biologicalSex` | xsd:string | male, female, intersex — use `cascade:biologicalSex` for new data | | `checkup:genderIdentity` | xsd:string | woman, man, non_binary, other, prefer_not_to_say — use `cascade:genderIdentity` for new data | ### Medication Summary Properties (Phase 3) | Property | Range | Description | |----------|-------|-------------| | `checkup:medicationName` | xsd:string | Name of medication | | `checkup:dose` | xsd:string | Dose with units | | `checkup:frequency` | xsd:string | once_daily, twice_daily, as_needed, etc. | | `checkup:adherenceStatus` | xsd:string | taking_as_prescribed, usually_take, sometimes_miss, etc. | | `checkup:adherenceBarrier` | xsd:string | cost, side_effects, forgetfulness, etc. | | `checkup:episodeId` | xsd:string | Link to MedicationUseEpisode | | `checkup:indication` | xsd:string | Why patient takes medication | | `checkup:patientCost` | xsd:decimal | Out-of-pocket cost | | `checkup:functionalImpact` | xsd:string | How medication helps functionally | | `checkup:needsVerification` | xsd:boolean | Whether medication needs patient verification (v1.7) | | `checkup:verificationReason` | xsd:string | Why verification is needed: `historical`, `duplicate`, `conflicting_sources` (v1.7) | | `checkup:lastVerifiedDate` | xsd:dateTime | When patient last verified this medication (v1.7) | | `checkup:importSessionId` | xsd:anyURI | Link to the import session that created this record (v1.7) | ### Supplement Summary Properties | Property | Range | Description | |----------|-------|-------------| | `checkup:supplementName` | xsd:string | Name of supplement | | `checkup:regulatoryStatus` | xsd:string | dietarySupplement, otcDrug, homeopathic, herbalRemedy, unknown | | `checkup:evidenceStrength` | xsd:string | strongEvidence, moderateEvidence, limitedEvidence, etc. | | `checkup:doctorAware` | xsd:boolean | Whether provider knows about supplement | ### Screening Status Properties | Property | Range | Description | |----------|-------|-------------| | `checkup:screeningType` | xsd:string | mammogram, colonoscopy, pap_smear, etc. | | `checkup:screeningStatus` | xsd:string | not_due, due, overdue, completed, declined | | `checkup:lastCompletedDate` | xsd:date | When screening was last completed | | `checkup:nextDueDate` | xsd:date | When screening is next due | | `checkup:guidelineSource` | xsd:string | USPSTF, ACS, AAP, ACOG, ADA | | `checkup:gradeLevel` | xsd:string | USPSTF grade: A, B, C, D, I | ### Allergy Summary Properties (v1.1+, typed codes v1.6) | Property | Namespace | Range | Description | |----------|-----------|-------|-------------| | `checkup:allergen` | checkup | xsd:string | Name of the allergenic substance (drug, food, environmental) | | `checkup:reaction` | checkup | xsd:string | Type of allergic reaction: hives, rash, swelling, difficulty_breathing, anaphylaxis, nausea, other | | `checkup:allergySeverity` | checkup | xsd:string | Severity: mild, moderate, severe, life_threatening | | `checkup:allergyCategory` | checkup | xsd:string | Category: medication, food, environmental, latex, contrast, other | | `clinical:snomedCode` | clinical | xsd:string | SNOMED CT allergen concept code (v1.6 — typed code alignment) | | `clinical:rxNormCode` | clinical | xsd:string | RxNorm code for medication allergens (v1.6 — typed code alignment) | ### Condition Summary Properties (v1.1+, typed codes v1.6) | Property | Namespace | Range | Description | |----------|-----------|-------|-------------| | `checkup:conditionName` | checkup | xsd:string | Name of the health condition or diagnosis | | `checkup:onsetDate` | checkup | xsd:date | Date when condition was first diagnosed or symptoms began | | `checkup:onsetYear` | checkup | xsd:gYear | Year of condition onset if exact date unknown | | `checkup:conditionStatus` | checkup | xsd:string | Current status: active, resolved, remission, recurrence, inactive | | `clinical:icd10Code` | clinical | xsd:string | ICD-10-CM diagnosis code (v1.6 — typed code alignment, replaces checkup:icd10Code) | | `clinical:snomedCode` | clinical | xsd:string | SNOMED CT concept ID (v1.6 — typed code alignment, replaces checkup:snomedCode) | | `clinical:linkedConditionIds` | clinical | xsd:string | Space-separated UUIDs of related ConditionSummary records (v1.6) | ### Family History Entry Properties (v1.1+, typed codes v1.6) | Property | Namespace | Range | Description | |----------|-----------|-------|-------------| | `checkup:relativeType` | checkup | xsd:string | Relationship to patient: mother, father, sibling, maternal_grandmother, etc. | | `checkup:relativeSex` | checkup | xsd:string | Biological sex of relative: male, female | | `checkup:familyCondition` | checkup | xsd:string | Name of health condition the relative has/had | | `checkup:ageAtDiagnosis` | checkup | xsd:integer | Relative's age when condition was diagnosed | | `checkup:ageAtDeath` | checkup | xsd:integer | Relative's age at death if deceased | | `checkup:isDeceased` | checkup | xsd:boolean | Whether the relative is deceased | | `clinical:icd10Code` | clinical | xsd:string | ICD-10-CM code for the family condition (v1.6 — typed code alignment, replaces checkup:familyConditionCode) | | `clinical:snomedCode` | clinical | xsd:string | SNOMED CT concept code for the family condition (v1.6 — typed code alignment) | > **Typed Code Alignment (v1.6 / Task A2):** These classes previously used `checkup:`-namespaced code properties (`checkup:icd10Code`, `checkup:snomedCode`, `checkup:familyConditionCode`). These are now serialized using `clinical:` namespace predicates (`clinical:icd10Code`, `clinical:snomedCode`, `clinical:rxNormCode`, `clinical:cvxCode`, `clinical:loincCode`) consistent with the mixed namespace pattern. The `checkup:` variants are retained in the TTL for backward compatibility but deprecated for new serialization. ### Daily Check-in Class (v1.5) ```turtle checkup:DailyCheckIn a owl:Class ; rdfs:label "Daily Check-in" ; rdfs:comment "Patient-reported daily medication adherence check-in capturing response status, missed medications, and optional notes." ; cascade:subConcern "self-report" ; rdfs:subClassOf health:SelfReport . ``` **Three-Layer Analysis:** - **Layer 1:** LOINC `79474-4` (Health status questionnaire), SNOMED `225390008` (Self-assessment). Primary purpose is medication adherence tracking — SNOMED `418633004` (Medication compliance) is also relevant. - **Layer 2:** `health:SelfReport` base class provides common self-report structure (reportDate, reportType, completionStatus, reportContext, reportNotes). DailyCheckIn extends this with medication-specific properties. Apps implement the SelfReportable protocol pattern to bridge Swift models to the ontology. - **Layer 3:** `checkup:DailyCheckIn` — patient-facing adherence tracking record. > **Gap documented:** No LOINC code exists for structured medication adherence check-in with per-medication tracking. `79474-4` (Health status questionnaire) is the closest standard mapping for self-reported daily check-ins. | Property | Range | Description | Standard Mapping | |----------|-------|-------------|-----------------| | `checkup:checkInDate` | xsd:dateTime | Date of the check-in (normalized to midnight) | — | | `checkup:checkInResponse` | xsd:string | Adherence response: `allTaken`, `missedSome`, `skippedToday` | SNOMED `418633004` | | `checkup:missedMedicationIds` | xsd:string | Comma-separated UUIDs of missed medications (when response is `missedSome`) | — | | `checkup:checkInNotes` | xsd:string | Optional patient notes about the day | — | | `checkup:submittedAt` | xsd:dateTime | Timestamp when the check-in was submitted | — | ### Discussion Topic Class (v1.5) ```turtle checkup:DiscussionTopic a owl:Class ; rdfs:label "Discussion Topic" ; rdfs:comment "AI-generated or user-created topic for discussion during a healthcare visit. Topics are derived from medication data, adherence patterns, supplement interactions, or user input." ; rdfs:subClassOf prov:Entity . ``` **Three-Layer Analysis:** - **Layer 1:** No standard equivalent — Cascade-proprietary concept for visit preparation. - **Layer 2:** Skipped — patient-curated list, not device or EHR data. - **Layer 3:** `checkup:DiscussionTopic` — patient-facing visit discussion item. > **Gap documented:** No SNOMED CT or LOINC code exists for "patient-curated discussion topic." This is a Cascade-proprietary concept combining AI-generated medication-related questions with user-created discussion items for visit preparation. | Property | Range | Description | |----------|-------|-------------| | `checkup:topicText` | xsd:string | The discussion topic question or statement | | `checkup:topicCategory` | xsd:string | Category: `durationBased`, `recentChange`, `adherenceConcern`, `sideEffectReport`, `multipleForSame`, `supplementDoctor`, `interactionRisk`, `refillDue`, `effectivenessConcern`, `costConcern`, `custom` | | `checkup:topicSource` | xsd:string | Source type: `medication`, `supplement`, `medicationPair`, `supplementMedicationPair`, `adherencePattern`, `custom` | | `checkup:relevanceScore` | xsd:double | AI-computed relevance score (0.0-1.0) | | `checkup:isSelected` | xsd:boolean | Whether the topic is active for the next visit | | `checkup:relatedMedicationName` | xsd:string | Optional medication name providing context | | `checkup:detailText` | xsd:string | Optional supplementary detail text | | `checkup:generatedAt` | xsd:dateTime | When the topic was generated or created | ### Check-in Settings Class (v1.5) ```turtle checkup:CheckInSettings a owl:Class ; rdfs:label "Check-in Settings" ; rdfs:comment "Application configuration for daily check-in notification scheduling and day-of-week preferences." . ``` | Property | Range | Description | |----------|-------|-------------| | `checkup:checkInEnabled` | xsd:boolean | Whether daily check-ins are active | | `checkup:notificationHour` | xsd:integer | Notification hour in 24-hour format (0-23) | | `checkup:notificationMinute` | xsd:integer | Notification minute (0-59) | | `checkup:enabledDays` | xsd:string | Comma-separated enabled days: `sunday`, `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, `saturday` | > **Note:** This is application configuration, not health data. No SNOMED/LOINC mapping needed. Included in export for complete backup/restore capability. ### Intake Questionnaire Response Class (v1.6) ```turtle checkup:IntakeQuestionnaireResponse a owl:Class ; rdfs:label "Intake Questionnaire Response" ; rdfs:comment "Response to a standardized intake questionnaire section. Structure inspired by FHIR QuestionnaireResponse but uses native Cascade Protocol serialization. Uses LOINC panel codes as questionnaire identifiers and SNOMED CT codes for standardized answers." ; rdfs:subClassOf prov:Entity ; rdfs:seeAlso fhir:QuestionnaireResponse . ``` **Three-Layer Analysis:** - **Layer 1:** FHIR QuestionnaireResponse (structural alignment, not full adoption). Uses LOINC panel codes as questionnaire identifiers (e.g., `71421-2` Social History, `10187-3` Review of Systems). Uses SNOMED CT for coded answers (e.g., `8517006` Former smoker). - **Layer 2:** Skipped — patient-reported data, not device-generated or EHR-derived. - **Layer 3:** `checkup:IntakeQuestionnaireResponse` — patient-facing intake form data container. > **Design rationale:** Full FHIR QuestionnaireResponse was evaluated and rejected: ~5-6x more verbose in RDF/Turtle due to `fhir:v` blank-node wrapping for every primitive value, creates two incompatible RDF patterns in the same Pod, and no standard FHIR Questionnaires combine surgical + social + safety screening into a single intake form. The hybrid approach uses LOINC/SNOMED as the actual vocabulary with `checkup:` providing only the minimal container. A mechanical transformation to FHIR QuestionnaireResponse JSON is preserved for clinical system export. | Property | Range | Description | Standard Mapping | |----------|-------|-------------|-----------------| | `checkup:questionnaireId` | xsd:string | LOINC panel code identifying the questionnaire section | LOINC panel codes (e.g., `71421-2`) | | `checkup:questionnaireTitle` | xsd:string | Human-readable title of the questionnaire section | — | | `checkup:responseItem` | checkup:ResponseItem | Link to a question-answer pair | FHIR QuestionnaireResponse.item | | `checkup:completedAt` | xsd:dateTime | Timestamp when the questionnaire was completed | — | | `cascade:dataProvenance` | cascade:DataProvenance | Source of the questionnaire data | W3C PROV-O | ### Response Item Class (v1.6) ```turtle checkup:ResponseItem a owl:Class ; rdfs:label "Response Item" ; rdfs:comment "A question-answer pair within an intake questionnaire response. Uses LOINC item codes as link identifiers and SNOMED CT codes for coded answers where available. Supports nested sub-items via childItem for hierarchical questionnaire structures." ; rdfs:subClassOf prov:Entity . ``` **Three-Layer Analysis:** - **Layer 1:** Maps to FHIR QuestionnaireResponse.item structure. Individual items use LOINC codes as linkId values (e.g., `72166-2` Tobacco smoking status, `63512-8` Alcohol use). - **Layer 2:** Skipped — question-answer pairs are patient-facing data. - **Layer 3:** `checkup:ResponseItem` — individual question-answer entry. | Property | Range | Description | Standard Mapping | |----------|-------|-------------|-----------------| | `checkup:itemLinkId` | xsd:string | LOINC item code identifying the question | LOINC item codes | | `checkup:itemText` | xsd:string | Human-readable question display text | — | | `checkup:itemAnswer` | xsd:string | Free-text answer value | — | | `checkup:itemAnswerCode` | xsd:string | Coded answer value (SNOMED CT or LOINC answer code) | SNOMED CT / LOINC | | `checkup:itemAnswerCodeSystem` | xsd:anyURI | Code system URI for the coded answer | — | | `checkup:childItem` | checkup:ResponseItem | Nested sub-item for hierarchical questions | FHIR QuestionnaireResponse.item.item | ### Intake Questionnaire RDF Example (v1.6) ```turtle @prefix checkup: . @prefix cascade: . @prefix xsd: . # Social History questionnaire response _:social-hx a checkup:IntakeQuestionnaireResponse ; checkup:questionnaireId "71421-2" ; # LOINC social history panel checkup:questionnaireTitle "Social History" ; checkup:completedAt "2026-02-09T14:30:00Z"^^xsd:dateTime ; cascade:dataProvenance cascade:SelfReported ; checkup:responseItem _:sh1, _:sh2, _:sh3 . _:sh1 a checkup:ResponseItem ; checkup:itemLinkId "72166-2" ; # LOINC: Tobacco smoking status checkup:itemText "Smoking Status" ; checkup:itemAnswer "Former smoker" ; checkup:itemAnswerCode "8517006" ; # SNOMED: Former smoker checkup:itemAnswerCodeSystem . _:sh2 a checkup:ResponseItem ; checkup:itemLinkId "63512-8" ; # LOINC: Alcohol use checkup:itemText "Alcohol Use" ; checkup:itemAnswer "Social drinker, 2-3 drinks per week" . _:sh3 a checkup:ResponseItem ; checkup:itemLinkId "74009-2" ; # LOINC: Occupation checkup:itemText "Occupation" ; checkup:itemAnswer "Software Engineer" . # Surgical History questionnaire response _:surgical-hx a checkup:IntakeQuestionnaireResponse ; checkup:questionnaireId "10167-5" ; # LOINC: Surgical history checkup:questionnaireTitle "Surgical History" ; checkup:completedAt "2026-02-09T14:30:00Z"^^xsd:dateTime ; cascade:dataProvenance cascade:SelfReported ; checkup:responseItem _:surg1 . _:surg1 a checkup:ResponseItem ; checkup:itemLinkId "80944-3" ; # LOINC: Surgical procedure checkup:itemText "Previous Surgery" ; checkup:itemAnswer "Appendectomy, 2018" ; checkup:itemAnswerCode "80146-5" ; # LOINC: Appendectomy checkup:itemAnswerCodeSystem . ``` > **Template-driven scalability:** Questionnaire templates are defined as JSON data files, not Swift code. A generic questionnaire renderer takes a template and produces a fill-in UI. New intake sections are added by adding template files with zero code changes. Priority order: (P1) surgical history, social history basics; (P2) advanced care planning, pharmacy preference; (P3) safety screening, sexual health. ### Immunization Summary Class (v1.7) ```turtle checkup:ImmunizationSummary a owl:Class ; rdfs:label "Immunization Summary" ; rdfs:comment "Patient-facing immunization record for intake forms. Uses mixed namespace pattern: checkup: properties for app-layer enrichment (vaccineCategory) alongside clinical data fields." ; rdfs:subClassOf prov:Entity . ``` **Three-Layer Analysis:** - **Layer 1:** Vaccine codes from CVX standard; administration details align with FHIR Immunization resource structure. - **Layer 2:** Source data uses `clinical:Immunization` for EHR-imported records. - **Layer 3:** `checkup:ImmunizationSummary` — patient-facing summary using the **mixed namespace pattern**. Most properties use `clinical:` namespace directly; `checkup:vaccineCategory` adds app-layer categorization for intake form display. > **Mixed Namespace Pattern:** This class is a canonical example of the mixed namespace pattern used by Layer 3 summary classes. The `checkup:` namespace defines the container class and adds only app-layer enrichment properties (e.g., `checkup:vaccineCategory`). All standardized clinical data properties are referenced directly from `clinical:` namespace, avoiding data replication across layers. | Property | Namespace | Range | Description | |----------|-----------|-------|-------------| | `checkup:vaccineName` | checkup | xsd:string | Name of vaccine administered | | `checkup:administrationDate` | checkup | xsd:dateTime | Date vaccine was administered | | `checkup:immunizationStatus` | checkup | xsd:string | Status: `completed`, `entered-in-error`, `not-done` | | `checkup:vaccineCode` | checkup | xsd:string | CVX vaccine code for standardized identification | | `checkup:vaccineCategory` | checkup | xsd:string | Categorization for intake: `routine`, `travel`, `occupational`, `other` | | `checkup:manufacturer` | checkup | xsd:string | Vaccine manufacturer name | | `checkup:lotNumber` | checkup | xsd:string | Vaccine lot number | | `checkup:expirationDate` | checkup | xsd:date | Vaccine expiration date | | `checkup:doseNumber` | checkup | xsd:integer | Dose sequence number in multi-dose series | | `checkup:doseQuantity` | checkup | xsd:string | Dose quantity administered | | `checkup:administrationRoute` | checkup | xsd:string | Route: `intramuscular`, `subcutaneous`, `oral`, etc. | | `checkup:administrationSite` | checkup | xsd:string | Body site: `left arm`, `right arm`, etc. | | `checkup:administeringProvider` | checkup | xsd:string | Name of provider who administered vaccine | | `checkup:administeringLocation` | checkup | xsd:string | Location where vaccine was administered | | `checkup:immunizationNotes` | checkup | xsd:string | Additional notes about immunization | | `checkup:immunizationSourceRecord` | checkup | xsd:anyURI | Link to source `clinical:Immunization` record | ### Lab Result Summary Class (v1.7) ```turtle checkup:LabResultSummary a owl:Class ; rdfs:label "Lab Result Summary" ; rdfs:comment "Patient-facing lab result summary for intake forms. Uses mixed namespace pattern: checkup:labCategory for app-layer categorization, clinical: properties for standardized lab data." ; rdfs:subClassOf prov:Entity . ``` **Three-Layer Analysis:** - **Layer 1:** Lab result data uses LOINC codes for test identification and SNOMED CT for specimen types. - **Layer 2:** Source data uses `clinical:LabResult` for EHR-imported records. - **Layer 3:** `checkup:LabResultSummary` — patient-facing summary. Adds only `checkup:labCategory` for intake form display grouping; all clinical properties reference `clinical:` namespace directly via the mixed namespace pattern. | Property | Namespace | Range | Description | |----------|-----------|-------|-------------| | `checkup:labCategory` | checkup | xsd:string | Intake form display category: `metabolic`, `hematology`, `lipid`, `thyroid`, `vitamin`, `other` | | `clinical:testName` | clinical | xsd:string | Name of the lab test | | `clinical:value` | clinical | xsd:string | Test result value | | `clinical:interpretation` | clinical | xsd:string | Clinical interpretation | | `clinical:effectiveDate` | clinical | xsd:dateTime | Date of test | | `clinical:loincCode` | clinical | xsd:string | LOINC code identifying the test | | `clinical:unit` | clinical | xsd:string | Unit of measurement | | `clinical:referenceRange` | clinical | xsd:string | Reference range text | | `clinical:referenceRangeLow` | clinical | xsd:double | Low bound of reference range | | `clinical:referenceRangeHigh` | clinical | xsd:double | High bound of reference range | | `clinical:specimenType` | clinical | xsd:string | Specimen type | | `clinical:orderingProvider` | clinical | xsd:string | Ordering provider | | `clinical:performingLab` | clinical | xsd:string | Performing laboratory | ### Wellness Profile Reference Class (v1.7) ```turtle checkup:WellnessProfileReference a owl:Class ; rdfs:label "Wellness Profile Reference" ; rdfs:comment "Lightweight pointer to the patient's full wellness profile stored in the SDK HealthProfile. Uses rdfs:seeAlso to link to the wellness data file." ; rdfs:subClassOf prov:Entity . ``` **Three-Layer Analysis:** - **Layer 1:** No standard equivalent — Cascade-specific container reference. - **Layer 2:** Points to the SDK `HealthProfile` data via `rdfs:seeAlso`. - **Layer 3:** `checkup:WellnessProfileReference` — structural link, no checkup-specific properties. Uses only `rdfs:seeAlso` (URI) and `rdfs:comment` (string). ### Suggested Question Additional Properties (v1.7) | Property | Range | Description | |----------|-------|-------------| | `checkup:basedOnTemplate` | xsd:string | Template name the question was generated from | | `checkup:basedOnScreening` | xsd:anyURI | Link to the screening record the question relates to | ### Visit Prep Summary — noteContent Property (v1.7) Added to the existing `checkup:VisitPrepSummary` class (v1.4). Merges the previously planned separate `VisitPrepNotes` class into a single property: | Property | Range | Description | |----------|-------|-------------| | `checkup:noteContent` | xsd:string | Free-text notes for visit preparation (user-authored discussion points) | ### Intake Form Data Aggregate Links (v1.7) New aggregate link properties on `checkup:IntakeFormData`: | Property | Range | Description | |----------|-------|-------------| | `checkup:hasImmunization` | checkup:ImmunizationSummary | Link to an immunization summary entry | | `checkup:hasLabResult` | checkup:LabResultSummary | Link to a lab result summary entry | ### MedicationChangeRecord Properties (v1.9) Tracks changes to medications and supplements between visit snapshots. Consumed by report generators (`PlainTextReportGenerator`, `PatientBriefSections`, `ProviderViews`) for the "Changes Since Last Visit" section. **Three-Layer Analysis:** - **Layer 1:** No standard equivalent. FHIR tracks medication versions via `Provenance` resources but has no discrete "change record" resource. - **Layer 2:** Not applicable — change tracking is app-layer behavior, not clinical vocabulary. - **Layer 3:** `checkup:MedicationChangeRecord` — Cascade-proprietary. Captures the delta between visit snapshots for patient-facing reports. | Property | Type | Range | Description | |----------|------|-------|-------------| | `checkup:changeType` | DatatypeProperty | xsd:string | Type of change: `added`, `stopped`, `doseChanged`, `frequencyChanged`, `adherenceChanged`, `statusChanged` | | `checkup:changedField` | DatatypeProperty | xsd:string | Name of the field that was changed (e.g., `dose`, `frequency`) | | `checkup:changeRecordPreviousValue` | DatatypeProperty | xsd:string | Value of the field before the change | | `checkup:changeRecordNewValue` | DatatypeProperty | xsd:string | Value of the field after the change | | `checkup:changeDate` | DatatypeProperty | xsd:dateTime | Date when the change occurred | **Aggregate Link:** | Property | Type | Range | Description | |----------|------|-------|-------------| | `checkup:hasChange` | ObjectProperty | `checkup:MedicationChangeRecord` | Link from `MedicationSummary` or `SupplementSummary` to a change record | **TTL Example:** ```turtle <#med-change-abc123-0> a checkup:MedicationChangeRecord ; checkup:changeType "doseChanged" ; checkup:changedField "dose" ; checkup:changeRecordPreviousValue "10mg" ; checkup:changeRecordNewValue "20mg" ; checkup:changeDate "2026-02-01T00:00:00Z"^^xsd:dateTime . <#medication-abc123> a checkup:MedicationSummary ; checkup:medicationName "Lisinopril" ; checkup:hasChange <#med-change-abc123-0> . ``` ### VisitPrepTray Class (v3.1) ```turtle checkup:VisitPrepTray a owl:Class ; rdfs:label "Visit Prep Tray" ; rdfs:comment "Per-appointment visit preparation container grouping issues, discussion topics, questions, and notes. Uses a typed payload wrapper (VisitItemPayload) to preserve existing type richness from VisitIssue, DiscussionTopic, and SuggestedQuestion." ; cascade:subConcern "intake" ; rdfs:subClassOf prov:Entity . ``` **Three-Layer Analysis:** - **Layer 1:** No standard equivalent. FHIR has no "visit preparation tray" concept. Closest is `Encounter.reasonReference` which links conditions/observations to appointments. - **Layer 2:** Not applicable — visit preparation is patient-curated, not clinical or device data. - **Layer 3:** `checkup:VisitPrepTray` — Cascade-proprietary. Organizes patient visit preparation items by appointment with intent categories (discuss, schedule, inform, ask). > **Distinction from `checkup:VisitPrepSummary`:** `VisitPrepSummary` (v1.4) is a rendered one-page clinical summary for appointments. `VisitPrepTray` (v3.1) is an interactive item container that the patient builds up before a visit — items can be added, resolved, and reorganized. The summary is output; the tray is input. | Property | Type | Range | Description | |----------|------|-------|-------------| | `checkup:prepTitle` | DatatypeProperty | xsd:string | Display title for the visit prep (e.g., "Rheumatologist — Feb 28") | | `checkup:appointmentDate` | DatatypeProperty | xsd:dateTime | Scheduled appointment date | | `checkup:providerName` | DatatypeProperty | xsd:string | Name of the healthcare provider | | `checkup:specialty` | DatatypeProperty | xsd:string | Provider specialty (e.g., "Rheumatology") | | `checkup:prepStatus` | DatatypeProperty | xsd:string | Tray status: `preparing`, `ready`, `completed` | | `checkup:completedDate` | DatatypeProperty | xsd:dateTime | When the visit was marked completed | | `checkup:hasTrayItem` | ObjectProperty | checkup:VisitTrayItem | Link to a tray item | **Visit Tray Item Properties:** | Property | Type | Range | Description | |----------|------|-------|-------------| | `checkup:payloadType` | DatatypeProperty | xsd:string | Payload discriminator: `issue`, `topic`, `question`, `note`, `custom` | | `checkup:intentCategory` | DatatypeProperty | xsd:string | Intent: `discuss`, `schedule`, `inform`, `ask` | | `checkup:isResolved` | DatatypeProperty | xsd:boolean | Whether the item has been addressed | | `checkup:sourceSection` | DatatypeProperty | xsd:string | Health Map section this item originated from (for `custom` payload type) | | `checkup:sourceRecordId` | DatatypeProperty | xsd:anyURI | Link to the source record that prompted this item | **Aggregate Links on IntakeFormData:** | Property | Type | Range | Description | |----------|------|-------|-------------| | `checkup:hasVisitPrep` | ObjectProperty | checkup:VisitPrepTray | Link to a visit preparation tray | | `checkup:activeVisitPrepId` | DatatypeProperty | xsd:string | UUID of the currently active visit prep tray | --- ## Checkup SHACL Shapes (Adult) Validation shapes for adult patients (ages 18-64). ### Severity Levels | Level | Meaning | |-------|---------| | `sh:Violation` | Must fix (blocks export) | | `sh:Warning` | Should address (shown prominently) | | `sh:Info` | Nice to have (suggestions) | ### IntakeFormData Validation ```turtle checkup:IntakeFormDataShape a sh:NodeShape ; sh:targetClass checkup:IntakeFormData ; # VIOLATION: Patient Profile required (now cascade:PatientProfile) sh:property [ sh:path checkup:hasPatientProfile ; sh:minCount 1 ; sh:node cascade:PatientProfileShape ; sh:severity sh:Violation ; sh:message "Patient profile is required for intake forms" ] ; # WARNING: Medications sh:property [ sh:path checkup:hasMedication ; sh:node checkup:MedicationSummaryShape ; sh:severity sh:Warning ; sh:message "Medication list helps providers avoid drug interactions." ] ; # WARNING: Allergies sh:property [ sh:path checkup:hasAllergy ; sh:node checkup:AllergySummaryShape ; sh:severity sh:Warning ; sh:message "Allergy information helps prevent adverse reactions." ] . ``` ### Patient Profile Validation > **Note:** `checkup:PatientProfile` was removed in v3.0. Patient profile validation is now defined in `core.shapes.ttl` as `cascade:PatientProfileShape`. Required fields (VIOLATION if missing): - `patientName` - minimum 2 characters - `dateOfBirth` - xsd:date - `biologicalSex` - male, female, or intersex ### Supplement Summary Validation Required fields: - `supplementName` - minimum 2 characters - `regulatoryStatus` - REQUIRED to distinguish from medications Valid regulatory statuses: dietarySupplement, otcDrug, homeopathic, herbalRemedy, unknown --- ## SHACL Validation The Cascade Protocol uses [SHACL (Shapes Constraint Language)](https://www.w3.org/TR/shacl/) to validate RDF data against vocabulary constraints. Each vocabulary has a companion `.shapes.ttl` file that defines required properties, value ranges, and data quality expectations. ### Shapes Files | File | Version | Vocabulary | Description | |------|---------|------------|-------------| | `core.shapes.ttl` | v1.1 | Core (`cascade:`) | PatientProfile demographics, Address structure, DataProvenance taxonomy, ExportManifest / RecordSummary / InteractionScenario | | `clinical.shapes.ttl` | v1.13 | Clinical (`clinical:`) | ClinicalDocument and its six document subtypes, MedicationUseEpisode, Supplement required fields | | `health.shapes.ttl` | v1.2 | Health (`health:`) | HealthProfile vital signs, MetricTrend enums, statistical summaries, the five clinical record classes, the three daily-snapshot classes | | `pots.shapes.ttl` | v1.0 | POTS (`pots:`) | POTSCheckResult protocol fields, HeartRateMeasurement timing | | `coverage.shapes.ttl` | v1.0 | Coverage (`coverage:`) | InsurancePlan required fields, date ranges, plan type enums | | `checkup.shapes.ttl` | v2.0 | Checkup (`checkup:`) | IntakeFormData completeness, adult patient validation (ages 18-64) | ### A passing validation now means more than it did Before health v2.5 and core v3.4, a validator could report `PASS` on a record it had no constraints for: SHACL only enforces a shape when a node's `rdf:type` is a declared `sh:targetClass`, so an undefined class silently validated against nothing. Records of the five `health:` record types, the six wellness containers and the three manifest classes were all in that state. Two consequences follow, and the second is the one to plan for: - **A `PASS` now carries information it did not carry before.** `cascade validate` reports how many subjects it actually checked alongside the result, so vacuous coverage is visible rather than indistinguishable from conformance. - **Existing data can report violations it did not report before.** That is the validator improving, not the data degrading. Records written before these shapes existed were never checked against them; the first validation after upgrading is the first real check they have had. ### Severity Levels All shapes files use three severity levels consistently: | Level | Meaning | Example | |-------|---------|---------| | `sh:Violation` | Must fix — blocks export or indicates invalid data | Missing `patientName` on PatientProfile | | `sh:Warning` | Should address — shown prominently in validation UI | No medications listed on intake form | | `sh:Info` | Nice to have — suggestions for data completeness | Missing optional screening status | ### Usage Shapes files are designed for: - **Build-time validation** in Swift serialization tests - **Export-time validation** before generating patient reports - **Import-time validation** when processing external data For detailed shape definitions, see the SHACL subsection in each vocabulary's documentation above, or the individual `.shapes.ttl` files in the ontology repository. --- ## Diabetes Vocabulary v1.0-draft **Namespace:** `https://ns.cascadeprotocol.org/diabetes/v1#` **Prefix:** `diabetes:` **Status:** Draft (subject to change) **Imports:** Core Vocabulary v1 Unified vocabulary for diabetes and pre-diabetes management, aggregating data from CGMs, glucose meters, insulin delivery systems, and lifestyle tracking devices. ### Diabetes Classification | Class | Description | SNOMED CT | |-------|-------------|-----------| | `diabetes:Type1` | Autoimmune destruction of beta cells | sct:46635009 | | `diabetes:Type2` | Insulin resistance with relative deficiency | sct:44054006 | | `diabetes:Prediabetes` | Impaired glucose tolerance | sct:714628002 | | `diabetes:GestationalDiabetes` | Diabetes diagnosed during pregnancy | sct:11687002 | | `diabetes:LADA` | Latent autoimmune diabetes in adults | - | ### Glucose Measurement Classes | Class | Description | |-------|-------------| | `diabetes:GlucoseReading` | Single blood glucose measurement from any source | | `diabetes:CGMReading` | Interstitial glucose from CGM sensor | | `diabetes:FingerStick` | Capillary blood from test strip meter | | `diabetes:VenousLab` | Laboratory plasma glucose | | `diabetes:CGMSession` | CGM monitoring session (10-14 days per sensor) | ### Insulin Delivery Classes | Class | Description | |-------|-------------| | `diabetes:InsulinDose` | Record of insulin delivery | | `diabetes:RapidActing` | Onset 15 min, peak 1-2 hr (Humalog, NovoLog) | | `diabetes:ShortActing` | Onset 30 min, peak 2-4 hr (Regular) | | `diabetes:IntermediateActing` | Onset 1-2 hr, peak 4-12 hr (NPH) | | `diabetes:LongActing` | Onset 1-2 hr, no peak, 20-24+ hr (Lantus, Tresiba) | | `diabetes:Bolus` | Meal bolus for carbohydrate coverage | | `diabetes:CorrectionBolus` | Bolus to correct elevated glucose | | `diabetes:Basal` | Background insulin for fasting control | ### Time in Range Metrics (ADA/EASD Consensus) | Property | Target | Description | |----------|--------|-------------| | `diabetes:timeInRange` | >70% | % time 70-180 mg/dL | | `diabetes:timeBelowRange` | <4% | % time <70 mg/dL | | `diabetes:timeAboveRange` | <25% | % time >180 mg/dL | | `diabetes:timeSeverelyLow` | <1% | % time <54 mg/dL | | `diabetes:timeSeverelyHigh` | <5% | % time >250 mg/dL | | `diabetes:coefficientOfVariation` | <36% | CV = SD/Mean * 100 | | `diabetes:gmi` | - | Glucose Management Indicator (estimated HbA1c) | ### Pattern Detection | Pattern Type | Description | |--------------|-------------| | `diabetes:DawnPhenomenon` | Early morning glucose rise (4-7 AM) | | `diabetes:PostprandialSpike` | Glucose rise 1-3 hours after meals | | `diabetes:ExerciseResponse` | Glucose changes during/after activity | | `diabetes:NocturnalHypoglycemia` | Low glucose during sleep (12-6 AM) | | `diabetes:InsulinSensitivityPattern` | Time-of-day variations in insulin effectiveness | ### Diabetes Profile Properties | Property | Range | Description | |----------|-------|-------------| | `diabetes:diabetesType` | diabetes:DiabetesType | Type 1, Type 2, etc. | | `diabetes:diagnosisDate` | xsd:date | When diagnosed | | `diabetes:targetRangeLow` | xsd:double | Lower bound (default 70 mg/dL) | | `diabetes:targetRangeHigh` | xsd:double | Upper bound (default 180 mg/dL) | | `diabetes:carbRatio` | xsd:double | Grams of carbs per unit insulin | | `diabetes:correctionFactor` | xsd:double | Expected glucose drop per unit | | `diabetes:activeCGM` | xsd:string | Current CGM system | | `diabetes:activeInsulinPump` | xsd:string | Current insulin pump | ### Pod Storage Paths (Recommended) ``` /wellness/diabetes/ profile.ttl - DiabetesProfile daily-summaries/YYYY-MM-DD.ttl - DailyGlucoseSummary glucose-readings/YYYY-MM-DD.ttl - GlucoseReading batch insulin-doses/YYYY-MM-DD.ttl - InsulinDose records meals/YYYY-MM-DD.ttl - MealEvent/CarbEntry patterns/detected.ttl - GlucosePattern analysis events/hypo.ttl - HypoglycemicEvent log events/hyper.ttl - HyperglycemicEvent log labs/results.ttl - HbA1c and other labs ``` --- ## Genomics Vocabulary v1.0-draft **Namespace:** `https://ns.cascadeprotocol.org/genomics/v1#` **Prefix:** `genomics:` **Status:** Draft (v1-draft.0.2, 2026-05-05) **Imports:** Core Vocabulary v1, Clinical Vocabulary v1 **Schema files:** [genomics.ttl](/docs/genomics/v1-draft/genomics.ttl) · [genomics.shapes.ttl](/docs/genomics/v1-draft/genomics.shapes.ttl) > **Draft notice (per D-PATH).** The namespace URI is the stable major (`genomics/v1#`); the version-info string is `1.0-draft`. Pre-stable drafts are NOT registered in `spec/VOCAB_VERSIONS` — they land there only at v1.0 graduation. Layer 2 vocabulary that bridges Layer 1 genomics standards (HGVS, ClinVar, VRS, HGNC, Sequence Ontology, HPO, MONDO/OMIM/ORDO, ACMG/AMP via LOINC answer codes, ClinGen review-status taxonomy) to Layer 3 patient-facing genetic counseling summaries (`checkup:GeneticCounselingSummary`, `checkup:VariantNarrative`). ### Variant + Interpretation | Class | Anchor | Purpose | |-------|--------|---------| | `genomics:Variant` | `fhir:Observation`, `prov:Entity` | Single sequence variant. HGVS c./p./g., ClinVar VCV/RCV, VRS/CAid, HGNC gene, SO consequence term, zygosity, phase. | | `genomics:CopyNumberVariant` | subClassOf `Variant` | CNV with `cnvIntervalStart`, `cnvIntervalEnd`, `cnvIntervalRef`, `copyNumber`. | | `genomics:VariantInterpretation` | `prov:Entity` | ACMG/AMP classification at a point in time. Reclassification creates a new instance linked to the prior via `prov:wasRevisionOf`; never edit in place. Cardinality 1..1 on `condition` and `variantInterpreted` (D-Q5). | | `genomics:Haplotype` | `fhir:Observation` | Multi-variant unit on one chromosome (PharmVar star alleles, HLA typing). | | `genomics:Diplotype` | `fhir:Observation` | Pair of haplotypes (e.g., CYP2D6 *1/*4). | ### Pedigree | Class | Purpose | |-------|---------| | `genomics:Pedigree` | Family-structure graph rooted at a `proband`. | | `genomics:PedigreeMember` | One member with `relativeRole` (HL7 v3 RoleCode), `relativeSex`, `isDeceased`, `ageAtDeath`, `carrierStatus`, `testedForVariant`, `hpoTerm`, `phenotypeOnsetAge`. | The Layer 3 `FamilyHistoryEntry` (ICD-10 + SNOMED, patient-reported) is unchanged — apps that don't do genetics keep using it. ### Tests, orders, and submitter assertions | Class | Anchor | Purpose | |-------|--------|---------| | `genomics:GeneticTest` | `prov:Entity` | Test report. `testType`, `genePanel`, `variantsObserved`, `orderingProvider`, `performingLab`. | | `genomics:GeneticTestOrder` | `fhir:ServiceRequest` | Pre-result test order. `orderedAt`, `orderStatus`, `resultedIn`. | | `genomics:SubmitterAssertion` | `prov:Entity` | Per-submitter classification feeding aggregated `VariantInterpretation`. ClinVar VCV/SCV pattern via `aggregatedFrom`. | ### Sequencing-run provenance (per D-DIRECTORY) | Class | Anchor | Purpose | |-------|--------|---------| | `genomics:SequencingRun` | `prov:Activity` | Sequencing-event metadata: `coverageDepth`, `sequencingTechnology`, `variantCallerVersion`, `laboratoryCertification`, sample/sequencing/file-generation dates. | | `genomics:RawFile` | `prov:Entity` | Pointer-and-hash for BAM/CRAM/FASTQ/VCF. **Cascade does NOT ingest the bytes.** Carries `fileFormat`, `fileSizeBytes` (xsd:long for 50–100 GB BAM/CRAM cases), `fileHashSHA256`, `fileLocation`, `referenceGenome`, `htsgetEndpoint`. | ### Data-quality tier model (per D-QUALITY-TIER) | Individual | Criterion | |------------|-----------| | `genomics:ClinicalGrade` | CLIA / CAP / ISO15189 lab + clinical-grade method (≥30x WGS or validated panel) + raw files retained. | | `genomics:ResearchGrade` | Research-quality sequencing without full clinical certification. | | `genomics:ConsumerGrade` | DTC genotyping arrays (23andMe, AncestryDNA, etc.) — explicitly lands here. | | `genomics:UnknownQuality` | Quality tier cannot be determined. | `genomics:dataQualityTier` annotates each `Variant`. The SHACL safety constraint requires that any `Pathogenic` / `LikelyPathogenic` `VariantInterpretation` either reference a `ClinicalGrade` Variant OR carry `requiresConfirmation true`. ### Closed enumerations - **ACMG class:** `Pathogenic`, `LikelyPathogenic`, `VUS`, `LikelyBenign`, `Benign` (LOINC-coded). - **Inheritance:** `AutosomalDominant`, `AutosomalRecessive`, `XLinkedDominant`, `XLinkedRecessive`, `Mitochondrial`, `YLinked`, `MultifactorialPolygenic`, `UnknownInheritance`. - **Allelic requirement:** `Monoallelic`, `Biallelic`. - **Zygosity:** `Heterozygous`, `Homozygous`, `Hemizygous`, `CompoundHeterozygous`, `MosaicLow`, `MosaicHigh`. - **Phase:** `Cis`, `Trans`, `PhaseUnknown`. - **Carrier status:** `PositiveAffected`, `PositiveUnaffected`, `Negative`, `NotTested`, `Obligate`. - **Test type:** `SingleGeneTest`, `GenePanelTest`, `ExomeSequencing`, `GenomeSequencing`, `ChromosomalMicroarray`, `KaryotypeAnalysis`, `MLPA`, `RepeatExpansionTest`, `MethylationStudy`, `RNASequencing`. - **Sequencing technology:** `ShortReadIllumina`, `LongReadONT`, `LongReadPacBio`, `Mixed`, `GenotypingArray`. - **Lab certification:** `CLIA`, `CAP`, `ISO15189`, `Uncertified`. - **File format:** `BAM`, `CRAM`, `FASTQ`, `gVCF`, `VCF`, `BCF`, `OtherFileFormat`. - **Submitter category:** `SubmitterLaboratory`, `SubmitterConsortium`, `SubmitterExpertPanel`, `SubmitterResearch`, `SubmitterClinician`. - **Order status:** `OrderPending`, `OrderInProgress`, `OrderResulted`, `OrderCancelled`. - **Causality status:** `Causative`, `Contributory`, `UncertainCausality`, `Rejected` — Phenopacket-aligned variant-explains-phenotype semantics, distinct from ACMG classification. - **Review status (ClinVar):** `NoAssertionProvided`, `CriteriaNotProvided`, `SingleSubmitter`, `ConflictingSubmissions`, `MultipleSubmittersNoConflict`, `ExpertPanelReviewed`, `PracticeGuideline` with `starRating` annotation. - **Data provenance (genomics-specific):** `ConsumerArray`, `ClinicalSequencing`, `ResearchSequencing`, `Imported`, `AIExtractedGenomics`. Distinct from `cascade:dataProvenance`; the consumer-array lane drives Phase 2C importer routing. - **Data quality tier:** `ClinicalGrade`, `ResearchGrade`, `ConsumerGrade`, `UnknownQuality`. - **Somatic status (v0.2):** `Germline`, `Somatic`, `UnknownSomaticStatus`. ### v1-draft.0.2 additions (2026-05-05) Phase 1 importer gaps surfaced four high-confidence additions. All additive; existing data validates unchanged. | Term | Type | Maps | Purpose | |------|------|------|---------| | `genomics:reportedRecord` | ObjectProperty (no `rdfs:range`) | — | Generic GeneticTest → record predicate covering Diplotype / Haplotype / PGx implication / future genomics records. Resolves the HLA tie-break: `variantsObserved` has `rdfs:range genomics:Variant` and cannot represent non-Variant report links. | | `genomics:refAllele` | DatatypeProperty (`xsd:string`) | LOINC 69547-8 | VCF-style reference allele string. | | `genomics:altAllele` | DatatypeProperty (`xsd:string`) | LOINC 69551-0 | VCF-style alternate allele string. | | `genomics:genomicStartEnd` | DatatypeProperty (`xsd:string`) | LOINC 81254-5 | 1-based inclusive `chrN:start-end` coordinates. | | `genomics:somaticStatus` | ObjectProperty → `genomics:SomaticStatus` | LOINC 48002-0 | Germline vs somatic. Three named individuals: `Germline`, `Somatic`, `UnknownSomaticStatus`. | | `genomics:variantAlleleFrequency` | DatatypeProperty (`xsd:decimal`, SHACL 0.0–1.0) | LOINC 81258-6 | VAF as a sequencing-evidence fraction. Distinct from `mosaicismFraction` (clinical conclusion). | **Deferred** (need Ellen review or more importer experience): `genomics:CompositeVariant`, LOINC 48013-7 / 48019-4 / 48001-2, multi-gene Diplotype, SNOMED reaction-coding system. See [genomics/v1-draft/](/docs/genomics/v1-draft/) for the full vocabulary documentation and the spec changelog at `spec/ontologies/genomics/CHANGELOG.md`. --- ## Advisory Vocabulary v1.0-draft **Namespace:** `https://ns.cascadeprotocol.org/advisory/v1#` **Prefix:** `advisory:` **Status:** Draft (v1.0-draft.0.1, 2026-05-05) **Underlying spec:** [W3C Linked Data Patch Format](https://www.w3.org/TR/ldpatch/) (Recommendation, 2015) **Imports:** Core Vocabulary v1 **Schema files:** [advisory.ttl](/docs/advisory/v1-draft/advisory.ttl) · [advisory.shapes.ttl](/docs/advisory/v1-draft/advisory.shapes.ttl) > **Draft notice (per D-PATH).** Pre-stable drafts are NOT registered in `spec/VOCAB_VERSIONS`. The Advisory Vocabulary defines the **Cascade Advisory Patch (CAP) profile** — a strict subset of W3C LDPatch designed for clinical knowledge updates that are author-readable, statically auditable, and bounded in execution. Forward-compatible with full LDPatch — every CAP file is a valid LDPatch file. ### CAP profile constraints | Constraint | Rule | |------------|------| | **C1: Operations** | Only `Add` and `Bind`. No `Delete`, `Cut`, or `UpdateList`. Supersession via `prov:wasRevisionOf` (additive, never destructive). | | **C2: Single-identifier match** | At most one `Bind` per advisory. Path expression must be `?var ` where the predicate is whitelisted (`genomics:caId`, `genomics:vrsId`, `genomics:clinvarVariationId`, `clinical:loincCode`, `clinical:rxNormCode`, `clinical:icd10Code`, `clinical:snomedCode`, `genomics:hgncId`). Applier cost is O(index lookup). | | **C3: No prefix manipulation** | Whitelisted prefixes only. | | **C4: No IRI computation** | Inserted IRIs are literal patch text or the bound `?var`. No CONCAT / template substitution / string-to-IRI. | | **C5: Bounded insert size** | At most 64 triples per match. | | **C6: Required envelope metadata** | `humanSummary`, `advisoryClass`, `issuer`, `issuedAt` declared before any operation. | ### Classes | Class | Anchor | Purpose | |-------|--------|---------| | `advisory:CascadeAdvisoryPatch` | subClassOf `prov:Entity` | The constrained-LDPatch envelope itself. | | `advisory:AdvisoryClass` | OWL Class | Closed taxonomy (six individuals). | | `advisory:Cadence` | OWL Class | Six-tier check cadence enum. | | `advisory:TrustedIssuer` | OWL Class | Per-pod issuer trust record (D-Q3). | | `advisory:TrustSourceEnum` | OWL Class | How an issuer was added to pod trust. | | `advisory:AutoApplyPolicy` | OWL Class | Per-pod opt-in auto-apply policy. | | `advisory:AutoApplyScope` | OWL Class | `(issuer, advisoryClass)` tuple for auto-apply. | The `cascade:AdvisoryApplicationActivity` class — which records advisory-application events as a `prov:Activity` — is declared in core v3.1 and is referenced here, not redeclared. ### AdvisoryClass taxonomy (six closed-list individuals) | Class | Default cadence | Use | |-------|------------------|-----| | `advisory:SafetyCritical` | `EveryAppOpen` | Drug recall, allergy alert, urgent variant reclassification. | | `advisory:VariantReclassification` | `Monthly` | ACMG/AMP class change for a known variant. | | `advisory:DrugInteraction` | `Monthly` | Newly identified drug-drug or drug-gene interaction. | | `advisory:LabReferenceRangeUpdate` | `Quarterly` | Lab reference-range revision (e.g., new sex- or age-stratified ranges). | | `advisory:SurveillanceGuidelineUpdate` | `Quarterly` to `Annually` | NCCN / USPSTF / specialty-society guideline change. | | `advisory:CarrierFrequencyUpdate` | `Annually` | Updated population carrier frequency for a pathogenic variant. | ### Cadence enum `advisory:EveryAppOpen`, `advisory:Daily`, `advisory:Weekly`, `advisory:Monthly`, `advisory:Quarterly`, `advisory:Annually`. ### Signing envelope (per D-Q4) Detached JWS Ed25519 (RFC 7515 compact serialization). Properties: `signature`, `signatureIssuer` (iss), `signatureIssuedAt` (iat), `signatureExpiresAt` (exp), `signatureContentType` (cty, fixed to `application/x-cascade-advisory-patch`). W3C VC 2.0 wrapping is deferred to a later profile version. ### Trust model (per D-Q3) The trust model is **per-pod**. The vocabulary declares `advisory:TrustedIssuer` and `advisory:TrustSourceEnum` (`RecommendedStarterList`, `UserAdded`, `ImportedFromRegistry`, `VerifiedViaDID`) so each pod can carry its own trust graph at `/trust/issuers.ttl`. The protocol does NOT define a centralized issuer registry. Issuer-trust allowlist enforcement is OUT of SHACL scope — that's a runtime concern. ### Application pipeline (informative) 1. **Parse as LDPatch.** Any LDPatch syntax error → reject. 2. **Profile envelope check.** Required `advisory:` metadata present and well-formed → continue, else reject. 3. **Constraint check.** C1–C5 enforced statically → continue, else reject. 4. **Signature verification.** Detached-JWS signature valid against trusted-issuer keys → continue, else queue for explicit user trust review. 5. **Selector evaluation.** `Bind` query against pod — zero matches: log inapplicable; one match: proceed; N matches: each treated as separate application. 6. **Application.** Inserts performed; `cascade:AdvisoryApplicationActivity` recorded with `prov:used` linking to the patch + matched node. ### Worked examples - `example-brca2-reclassification.ldpatch` — VUS → Likely Pathogenic. - `example-cpic-cyp2c19-warfarin.ldpatch` — CPIC PGx dosing guidance update tied to a CYP2C19 star allele. Both files live at `cascadeprotocol.org/drafts/advisory-v1/`. See [advisory/v1-draft/](/docs/advisory/v1-draft/) for the full vocabulary documentation and the spec changelog at `spec/ontologies/advisory/CHANGELOG.md`. --- ## Evidence Vocabulary v1.0-draft **Namespace:** `https://ns.cascadeprotocol.org/evidence/v1#` **Prefix:** `evidence:` **Status:** Draft (v1-draft.0.2, 2026-07-01) **Layer:** Layer 2 (cross-cutting) **Imports / reuse:** Core Vocabulary v1 provenance primitives, W3C PROV-O **Schema files:** [evidence.ttl](/docs/evidence/v1-draft/evidence.ttl) · [evidence.shapes.ttl](/docs/evidence/v1-draft/evidence.shapes.ttl) > **Draft notice (per D-PATH).** The namespace URI is the stable major (`evidence/v1#`); the version-info string is `1.0-draft`. Pre-stable drafts are NOT registered in `spec/VOCAB_VERSIONS`; they land there only at v1.0 graduation. Layer 2 cross-cutting vocabulary for grounding assertions in cited evidence. Generalizes the assertion, evidence, verdict pattern needed by Cascade Workbench (checking claims pulled from AI conversations against a patient's records and literature), the Cascade Appeal system, and PGx/genomics decision support. Reuses core provenance (`cascade:extractionModel`, `cascade:extractionConfidence`, `cascade:requiresUserReview`, `prov:wasDerivedFrom`) rather than redefining it. Uses `Assertion` (not "Claim") to avoid collision with `coverage:ClaimRecord`. ### Classes | Class | Anchor | Purpose | |-------|--------|---------| | `evidence:Assertion` | subClassOf `prov:Entity` | A discrete, checkable statement evaluated against evidence. Carries the grading facets and zero-or-more evidence links. | | `evidence:EvidenceLink` | OWL Class | Reified link from an Assertion to one piece of evidence (Pod record or Citation) with a stance. Modeled as a node so one Assertion can carry multiple, differently-directed links. | | `evidence:Citation` | subClassOf `prov:Entity` | External bibliographic source (DOI/PMID), a first-class Pod resource once retrieved (recommended path `literature/`). | | `evidence:GroundingActivity` | subClassOf `prov:Activity` | The model pass that graded an Assertion. Reuses `cascade:extractionModel` to record the model. | ### The facet model (verdict taxonomy v2, v1-draft.0.2) The grounding outcome moved from a single flat `evidence:verdict` to orthogonal facets on the Assertion, each an object property over a closed enumeration, plus a numeric confidence. The facets are the canonical serialized form. | Facet property | Range (closed enum) | Individuals | |----------------|---------------------|-------------| | `evidence:direction` | `DirectionValue` | `Supports`, `Contradicts`, `Mixed`, `None` (what the evidence in aggregate says). | | `evidence:basis` | `BasisValue` | `Record`, `Literature`, `RecordAndLiterature`, `None` (where the evidence is). | | `evidence:strength` | `StrengthValue` | `Strong`, `Moderate`, `Weak`. On basis `Record`, a deterministic function of the match (G-2), never model-emitted. | | `evidence:settled` | `SettledValue` | `Settled`, `NeedsEvidence`. | | `evidence:reason` | `NeedsEvidenceReasonValue` | `NoRecord`, `NeedsLiterature`, `NotCheckableByNature`. | | `evidence:confidence` | `xsd:decimal` [0.0, 1.0] | Confidence in the grounding result, distinct from `evidence:groundingConfidence` (the activity's raw model confidence). | Per-link stance stays on `evidence:StanceValue` (`Supports`, `Contradicts`, `Contextual`). Shared individuals: `Supports` / `Contradicts` are both `StanceValue` and `DirectionValue`; `None` is both `DirectionValue` and `BasisValue`; `NeedsLiterature` is both a deprecated `VerdictValue` and a `NeedsEvidenceReasonValue`. The property an individual appears on disambiguates its role. ### Deprecated: the flat verdict (removal at v1.0) `evidence:verdict` and the `VerdictValue` individuals (`Supported`, `Contradicted`, `Unverifiable`, `NeedsLiterature`) are DEPRECATED in v1-draft.0.2, not removed. Draft-period data and fixtures already carry `evidence:verdict`, and the SHACL shapes keep a legacy branch so that data still validates during the one-release transition. Clients derive the flat value from the facets in code and MUST NOT serialize it going forward. Both are scheduled for removal at v1.0 graduation. ### Validation (SHACL) `evidence.shapes.ttl` is SHACL Core only (`sh:or` / `sh:not` / `sh:in`), never SHACL-SPARQL, so `rdf-validate-shacl` and `cascade validate` enforce every constraint. Three node shapes: `AssertionShape`, `EvidenceLinkShape`, `CitationShape`. - **Generalized grounding invariant.** A grounded result (`settled` `Settled`, `direction` one of `Supports`/`Contradicts`/`Mixed`, `basis` not `None`) of EITHER basis requires at least one `evidence:hasEvidenceLink`. Generalizes the v0.1 invariant, which only fired for the flat `Supported`/`Contradicted` verdicts. - **Facet consistency.** A `NeedsEvidence` assertion must not carry a grounded direction; a grounded direction must carry a real basis (not `None`). - **Graded-ness.** Every assertion carries the facet form (`settled`) or, for draft-period data, the deprecated flat `verdict`. Legacy branch removed at v1.0. See [evidence/v1-draft/](/docs/evidence/v1-draft/) for the full vocabulary documentation and the spec changelog at `spec/ontologies/evidence/CHANGELOG.md`. --- ## Workbench Vocabulary v1.0-draft **Namespace:** `https://ns.cascadeprotocol.org/workbench/v1#` **Prefix:** `workbench:` **Status:** Draft (v1-draft.0.5, 2026-07-15) **Layer:** Layer 3 (application-specific) **Imports / reuse:** Core Vocabulary v1, Evidence Vocabulary v1-draft, W3C Web Annotation (`oa:`), PROV-O, W3C RDF Calendar (`ical:`), SKOS **Schema files:** [workbench.ttl](/docs/workbench/v1-draft/workbench.ttl) · [workbench.shapes.ttl](/docs/workbench/v1-draft/workbench.shapes.ttl) > **Draft notice (per D-PATH).** The namespace URI is the stable major (`workbench/v1#`); the version-info string is `1.0-draft`. Pre-stable drafts are NOT registered in `spec/VOCAB_VERSIONS`; they land there only at v1.0 graduation. Layer 3 vocabulary for the Cascade Workbench desktop investigation app. Holds the app-specific workspace objects, the append-only record-amendment overlays, the verification and filing axes, and the notes / research-flags / follow-ups substrate. The grounding model is deliberately NOT here (it is cross-cutting Layer 2 `evidence:`); phenotype capture extends `genomics:`. Per the Layer 3 graduation rule, any class here that proves useful to other apps should graduate to Layer 2. ### Workspace objects | Class | Anchor | Purpose | |-------|--------|---------| | `workbench:Investigation` | OWL Class | A named, longitudinal inquiry over one patient's Pod: the top-level workspace object. | | `workbench:ImportedConversation` | subClassOf `prov:Entity` | An AI-assistant conversation imported as a source. Extracted assertions are `evidence:Assertion` instances carrying `cascade:AIAsserted`. | | `workbench:Hypothesis` | OWL Class | A candidate explanation with a lifecycle status (supports widening the differential and explicitly retiring/excluding). | | `workbench:Pin` | OWL Class | A curated bookmark pointing at any resource with an optional note. | Closed enumerations: Investigation status (`Active`, `Paused`, `Resolved`, `Archived`), Hypothesis status (`Proposed`, `Supported`, `Retired`, `Excluded`). ### Notes, research flags, and follow-ups: W3C Web Annotations (v1-draft.0.5) Caregiver notes, "needs research" flags, and follow-ups are ONE artifact: an `oa:Annotation` over one or more graph nodes, distinguished by `oa:motivatedBy`. Nothing from `oa:` / `ical:` / `prov:` is redeclared; the vocabulary documents the usage profile and mints exactly one term. | Artifact | `oa:motivatedBy` | Notes | |----------|------------------|-------| | Caregiver note | `oa:commenting` | Body required (`CommentingBodyShape`). | | Research flag | `oa:questioning` | Target is typically an assertion / claim node. | | Follow-up / open loop | `workbench:followUp` | Dual-typed `cal:Vtodo`; `ical:status` required, `ical:due` optional. | - **Layer-1 reuse.** `oa:` carries the body (`oa:TextualBody`), multi-target (`oa:hasTarget`), motivation, and span selectors (`oa:TextQuoteSelector` / `oa:TextPositionSelector` via `oa:SpecificResource`, the same byte-verified-span idea the Claims Ledger uses). PROV-O carries required attribution (`prov:wasAttributedTo`, `prov:generatedAtTime`). - **Follow-ups reuse W3C RDF Calendar.** `ical:due` (optional) and `ical:status` (required, the RFC 5545 VTODO enum `NEEDS-ACTION` / `IN-PROCESS` / `COMPLETED` / `CANCELLED`). `schema:dueDate` was considered and rejected: it does not exist (schema.org defines only `paymentDueDate`). - **Exactly one minted term:** `workbench:followUp`, an `oa:Motivation` with `skos:broader oa:questioning`, per the Web Annotation custom-motivation extension rule. - **Removed in v1-draft.0.5:** `workbench:InvestigationNote`, `workbench:hasNote`, `workbench:noteText` (draft removal; never emitted by shipping code). An investigation-scoped note is now an `oa:Annotation` targeting the `workbench:Investigation`. - **Pod placement:** a top-level `notes/` container (`pod-structure.md` §5.2), separate from `annotations/` (record-amendment overlays are edit machinery, not user content). ### Append-only record-amendment overlays Original records are immutable; every edit or delete is a new overlay resource written to `annotations/` and discovered at query time. Overlays carry `cascade:SelfReported` provenance, an optional `prov:wasAttributedTo`, and a `dct:created` timestamp. | Class | Purpose | |-------|---------| | `workbench:Amendment` | Overrides one property value on an existing record without mutating the original. | | `workbench:Annotation` | Augments a record with a note or extra attribute without overriding any value. | | `workbench:Retraction` | Soft-delete / supersede (entered-in-error); the record's bytes are retained. | | `workbench:Tombstone` | Content-free hard-erase audit marker (records the erasure event only: id, actor, timestamp, action), never a content hash. | ### Verification and filing axes - **Verification axis (v1-draft.0.3).** `workbench:verificationStatus` + `VerificationStatusValue` (`Unverified`, `Confirmed`, `Refuted`), orthogonal to the SOURCE axis (`cascade:dataProvenance`). Mirrors FHIR AllergyIntolerance/Condition.verificationStatus. Open domain; patient self-entered data defaults to `Unverified`. Candidate to graduate to Layer 2 `clinical:`. - **Filing / organization axis (v1-draft.0.4).** `workbench:userSourceLabel` (`xsd:string`), the user-chosen label for the SOURCE a record is filed under in the Workbench "filing cabinet". A filing preference attributed to the user, carried on a `workbench:Annotation` overlay (`annotationProperty` = `"workbench:userSourceLabel"`, `annotationValue` = the label) bearing `cascade:SelfReported` provenance. It MUST NOT overwrite the objective imported origin `clinical:sourceEHR`, which is preserved and shown alongside; the effective grouping source prefers this label, else `clinical:sourceEHR`, else the import-batch tag. Open domain, so it can file any record. Reuses already-shaped predicates, so no new SHACL shape. ### Validation (SHACL) `workbench.shapes.ttl` is SHACL Core only. Alongside `InvestigationShape`, `ImportedConversationShape`, the four overlay shapes, and `VerificationStatusShape`, the notes substrate adds three shapes targeting every `oa:Annotation` in the Pod: `WebAnnotationShape` (at least one target, at least one motivation, required attribution plus an `xsd:dateTime` timestamp), `CommentingBodyShape` (commenting requires a body), `FollowUpShape` (followUp requires `ical:status` in the VTODO enum). All three violation classes were verified against `cascade validate` with positive and negative fixtures. See [workbench/v1-draft/](/docs/workbench/v1-draft/) for the full vocabulary documentation and the spec changelog at `spec/ontologies/workbench/CHANGELOG.md`. --- ## Settings Storage Convention (Phase B Target) Application configuration is distinct from health data and should be stored separately. This section documents the target storage architecture for Solid integration. ### Current State CheckInSettings and other app configuration data are serialized inline within the main checkup TTL file. This pattern works correctly and is preserved for now. The `cascade:subConcern "app-config"` annotation identifies these classes as configuration, not health data. ### Target Storage Hierarchy When Solid integration is implemented, settings will be stored at `/settings/`: ``` /settings/ shared/ preferences.ttl # Cross-app: language, timezone, notification prefs checkup/ config.ttl # CheckInSettings, app-specific prefs pots/ config.ttl # POTS Check app-specific prefs ``` ### Namespace Pattern No new `settings:` vocabulary is needed: - **Shared preferences** use `cascade:` properties (e.g., `cascade:notificationsEnabled`, `cascade:preferredUnits`) - **App-specific settings** use their existing vocabulary (`checkup:CheckInSettings`, `pots:` config) - Separation is by **file path**, not by **namespace** **Shared preferences example** (target: `/settings/shared/preferences.ttl`): ```turtle @prefix cascade: . @prefix dct: . <#preferences> dct:language "en-US" ; cascade:notificationsEnabled "true"^^xsd:boolean ; cascade:preferredUnits "imperial" . ``` **App-specific config example** (target: `/settings/checkup/config.ttl`): ```turtle @prefix checkup: . <#checkInSettings> a checkup:CheckInSettings ; checkup:checkInEnabled "true"^^xsd:boolean ; checkup:notificationHour "9"^^xsd:integer ; checkup:notificationMinute "0"^^xsd:integer ; checkup:enabledDays "monday,tuesday,wednesday,thursday,friday" . ``` --- ## Pod Storage Conventions Recommended storage paths for all Cascade data, aligned with Solid container conventions. These are default paths — Solid Type Indexes can override them for discovery. ### Cascade Pod Layout ``` / (root storage) |-- profile/ | |-- card.ttl # General identity (foaf:Agent, vcard:) | |-- health.ttl # cascade:PatientProfile (health extensions) |-- settings/ | |-- shared/ | | |-- preferences.ttl # Cross-app settings (cascade: properties) | |-- checkup/ | | |-- config.ttl # Checkup app settings | |-- pots/ | |-- config.ttl # POTS Check app settings |-- wellness/ # health: vocabulary data | |-- vitals/ | | |-- hrv/ # HRV statistics | | |-- bp/ # Blood pressure statistics | | |-- vo2max/ # VO2 Max statistics | |-- activity/ # Activity snapshots | |-- sleep/ # Sleep snapshots | |-- self-reports/ # health:SelfReport instances |-- clinical/ # clinical: vocabulary data | |-- medications/ | |-- conditions/ | |-- lab-results/ | |-- immunizations/ | |-- procedures/ |-- coverage/ # coverage: vocabulary data | |-- plans/ | |-- deductibles/ |-- checkup/ # checkup: vocabulary data (L3 only) | |-- intake-forms/ | |-- visit-prep/ | |-- daily-checkins/ | |-- summaries/ |-- pots/ # pots: vocabulary data | |-- results/ | |-- measurements/ ``` ### Per-Vocabulary Storage Paths | Vocabulary | Container | Key Classes | |-----------|-----------|-------------| | `cascade:` (core) | `/profile/`, `/settings/shared/` | PatientProfile, preferences | | `health:` | `/wellness/` | HRVStatistics, BPStatistics, VO2MaxStatistics, SelfReport | | `clinical:` | `/clinical/` | Medication, Condition, LabResult, Immunization, Procedure | | `coverage:` | `/coverage/` | InsurancePlan | | `checkup:` | `/checkup/` | IntakeFormData, DailyCheckIn, VisitPrepSummary | | `pots:` | `/pots/` | POTSCheckResult | ### Type Index Registrations (Documentation Only) Solid uses Type Indexes for data discovery. The following registrations are recommended for Cascade data (implementation deferred to Phase B): ```turtle # Private Type Index (documentation only — not implemented in SDK yet) @prefix solid: . @prefix cascade: . @prefix health: . @prefix clinical: . @prefix checkup: . @prefix pots: . @prefix coverage: . <> a solid:TypeIndex ; a solid:UnlistedDocument . <#patient-profile> a solid:TypeRegistration ; solid:forClass cascade:PatientProfile ; solid:instance . <#self-reports> a solid:TypeRegistration ; solid:forClass health:SelfReport ; solid:instanceContainer . <#insurance> a solid:TypeRegistration ; solid:forClass coverage:InsurancePlan ; solid:instanceContainer . <#pots-results> a solid:TypeRegistration ; solid:forClass pots:POTSCheckResult ; solid:instanceContainer . <#daily-checkins> a solid:TypeRegistration ; solid:forClass checkup:DailyCheckIn ; solid:instanceContainer . ``` --- ## Namespace Reference ### Cascade Protocol Namespaces | Prefix | Namespace URI | Description | |--------|---------------|-------------| | `cascade:` | `https://ns.cascadeprotocol.org/core/v1#` | Core vocabulary | | `clinical:` | `https://ns.cascadeprotocol.org/clinical/v1#` | Clinical documents | | `health:` | `https://ns.cascadeprotocol.org/health/v1#` | Wellness & device data | | `pots:` | `https://ns.cascadeprotocol.org/pots/v1#` | POTS screening | | `checkup:` | `https://ns.cascadeprotocol.org/checkup/v1#` | Patient intake | | `coverage:` | `https://ns.cascadeprotocol.org/coverage/v1#` | Insurance/benefits | | `diabetes:` | `https://ns.cascadeprotocol.org/diabetes/v1#` | Diabetes management (draft) | | `genomics:` | `https://ns.cascadeprotocol.org/genomics/v1#` | Variants, interpretations, pedigrees, sequencing-run provenance (draft) | | `advisory:` | `https://ns.cascadeprotocol.org/advisory/v1#` | Cascade Advisory Patch (CAP) profile (draft) | ### External Standard Namespaces | Prefix | Namespace URI | Description | |--------|---------------|-------------| | `owl:` | `http://www.w3.org/2002/07/owl#` | OWL Web Ontology Language | | `rdf:` | `http://www.w3.org/1999/02/22-rdf-syntax-ns#` | RDF syntax | | `rdfs:` | `http://www.w3.org/2000/01/rdf-schema#` | RDF Schema | | `xsd:` | `http://www.w3.org/2001/XMLSchema#` | XML Schema datatypes | | `sh:` | `http://www.w3.org/ns/shacl#` | SHACL shapes | | `prov:` | `http://www.w3.org/ns/prov#` | W3C Provenance | | `dct:` | `http://purl.org/dc/terms/` | Dublin Core terms | | `fhir:` | `http://hl7.org/fhir/` | HL7 FHIR | | `sct:` | `http://snomed.info/sct/` | SNOMED CT | | `loinc:` | `http://loinc.org/rdf#` | LOINC codes | | `ucum:` | `http://unitsofmeasure.org/` | Units of measure | --- ## Design Patterns This section documents recurring patterns used when modeling data with the Cascade Protocol vocabularies. Following these patterns ensures cross-app interoperability and consistent RDF output. ### 1. SelfReport Extension Pattern `health:SelfReport` is a base class for all patient-reported daily check-ins and self-assessments. Apps extend it with domain-specific subclasses. **Base class (in `health:` vocabulary):** | Property | Type | Description | |----------|------|-------------| | `health:reportDate` | xsd:dateTime | When the self-report was recorded | | `health:reportType` | xsd:string | Category: `medication_adherence`, `symptom_tracking`, `mood`, `sleep`, etc. | | `health:completionStatus` | xsd:string | `complete`, `partial`, or `skipped` | | `health:reportContext` | xsd:string | What tracking plan this report relates to | | `health:reportNotes` | xsd:string | Optional free-text notes from the patient | **Extending for a domain (in app vocabulary):** ```turtle # Define a domain-specific subclass checkup:DailyCheckIn a owl:Class ; rdfs:subClassOf health:SelfReport ; rdfs:label "Daily Check-In" ; rdfs:comment "Medication adherence check-in. Extends health:SelfReport." . # Domain-specific properties (checkup: namespace) checkup:checkInResponse a owl:DatatypeProperty ; rdfs:domain checkup:DailyCheckIn ; rdfs:range xsd:string . ``` **Serialized output (mixed namespace pattern):** ```turtle <#checkin-2026-02-18> a checkup:DailyCheckIn ; # Base properties (health: namespace -- any app understands these) health:reportDate "2026-02-18T09:00:00Z"^^xsd:dateTime ; health:reportType "medication_adherence" ; health:completionStatus "complete" ; health:reportNotes "Felt good today" ; # Domain-specific properties (checkup: namespace) checkup:checkInResponse "took_all" . ``` **Cross-app interoperability:** Any app understanding `health:SelfReport` can read the base fields (date, type, status) without understanding the domain-specific data. A medication management app can query all self-reports of type `medication_adherence` across multiple data sources. ### 2. Serializer Delegation Pattern Complex domain serializers delegate to shared SDK serializers for cross-cutting data types, ensuring consistent RDF output regardless of which app writes the data. **Problem:** The same data type (e.g., `cascade:PatientProfile`) might be serialized by both the SDK's `PatientProfileSerializer` and the app's `CheckupSerializer`. Without delegation, the two may emit different property names or structures. **Solution:** Extract shared serialization helpers in the SDK and call them from domain serializers. **Example: PatientProfile delegation** ```swift // SDK: PatientProfileSerializer.swift public static func serializeProfileProperties( _ profile: PatientProfile, includeSchemaVersion: Bool = false, includeProfileId: Bool = false ) -> String { // Canonical patient profile serialization var ttl = "" if let dob = profile.dateOfBirth { ttl += " cascade:dateOfBirth \"\(dob)\"^^xsd:date ;\n" } // ... all other profile properties return ttl } ``` ```swift // Checkup: CheckupSerializer.swift -- delegates to SDK private func patientProfile(_ profile: PatientProfile) -> String { var ttl = "<#patient-profile> a cascade:PatientProfile ;\n" ttl += PatientProfileSerializer.serializeProfileProperties(profile) ttl += " .\n" return ttl } ``` **Benefits:** - Single canonical representation for shared data types - Bug fixes to shared data types automatically apply everywhere - Easier testing (test the helper once, trust call sites) ### 3. Mixed Namespace Pattern A single class or resource can use properties from multiple vocabularies. This is standard RDF practice and is how Cascade enables cross-vocabulary reuse. **When to use:** - An app-level (L3) class naturally uses standardized properties from L2 vocabularies - A summary class aggregates data from multiple domain vocabularies - Established standard vocabularies (FHIR, SNOMED, foaf, vcard) have the right term **Example: checkup:InsuranceInfo using coverage: properties** ```turtle # checkup:InsuranceInfo (L3 -- app-specific container) # Uses coverage: for standardized insurance fields, checkup: for app-layer enrichment <#insurance-primary> a checkup:InsuranceInfo ; # Standardized insurance properties (coverage: namespace) coverage:providerName "BlueCross BlueShield" ; coverage:memberId "XYZ123" ; coverage:coverageType "primary" ; coverage:effectiveStart "2026-01-01"^^xsd:date ; # App-specific enrichment (checkup: namespace -- PCP is not insurance data) checkup:primaryCareProvider "Dr. Smith" ; checkup:pcpPhone "555-1234" . ``` **Rules:** 1. Use the most specific applicable vocabulary (`coverage:` over `checkup:` for insurance properties) 2. App vocabulary (`checkup:`, `pots:`) is for app-layer enrichment only, not re-definitions 3. Never redefine a standard property in an app namespace (use the standard one directly) 4. Document which properties come from which namespace in the class definition comment ### 4. Per-Type Export Pattern (Pod Storage) For Solid Pod compatibility, each data type should be serializable independently as a standalone TTL document that can be stored at its recommended Pod path. **Recommended Pod layout:** ``` / (root) |-- profile/ | |-- card.ttl # foaf:Agent identity (IdentityProfileSerializer) | |-- health.ttl # cascade:PatientProfile (PatientProfileSerializer) |-- wellness/ | |-- vitals/... # health: HRV, BP, VO2Max statistics | |-- activity/... # health: ActivitySnapshot instances | |-- sleep/... # health: SleepSnapshot instances | |-- self-reports/... # health:SelfReport instances |-- clinical/ | |-- medications/... # clinical:Medication instances | |-- conditions/... # health:ConditionRecord instances |-- coverage/ | |-- plans/... # coverage: InsurancePlan instances |-- checkup/ | |-- intake-forms/... # checkup: visit preparation summaries | |-- daily-checkins/... # checkup: DailyCheckIn instances ``` **Design principle:** Each resource should be self-contained -- it should include all the prefixes it needs and not depend on being embedded in a larger document. > **Note:** The SDK currently writes a single combined TTL file per storage namespace. Splitting into per-type files is a Phase B feature requiring Solid Pod infrastructure. This pattern documents the target architecture. --- ## Version History ### Genomics Vocabulary (Draft) - **v1.0-draft.0.1 (2026-05-05)**: Initial production draft authored in `spec/` (TASK-0.1 / TASK-0.2 of the Genomics & Advisory v0.1 workstream). 220 declared `genomics:` terms across 14 classes and ~30 net-new properties. Variant identity in four parallel languages (HGVS / ClinVar / VRS-CAid / HGNC + SO). Variant ≠ Interpretation pattern with `prov:wasRevisionOf` reclassification chain. `Pedigree` + `PedigreeMember` with HPO + MONDO + per-variant carrier status and HL7 v3 RoleCode relationship roles. Multi-variant units: `Haplotype`, `Diplotype`, `CopyNumberVariant`. ClinVar-aligned `SubmitterAssertion` + 7-value `ReviewStatus` taxonomy. `GeneticTest` + `GeneticTestOrder`. Sequencing-run provenance: `SequencingRun` (PROV-O Activity), `RawFile` pointer-and-hash entity (Cascade does NOT ingest BAM/CRAM bytes), htsget endpoint, lab-cert enum. Data-quality tier model: `ClinicalGrade` / `ResearchGrade` / `ConsumerGrade` / `UnknownQuality`. Causality enum (`Causative` / `Contributory` / `UncertainCausality` / `Rejected`) distinct from ACMG classification. SHACL: 12 `sh:NodeShape` declarations; D-Q5 multi-condition cardinality (`condition` 1..1, `variantInterpreted` 1..1); D-QUALITY-TIER `sh:xone` safety constraint requiring Pathogenic/LikelyPathogenic interpretations to either reference a `ClinicalGrade` Variant OR carry `requiresConfirmation true`. Per D-PATH, NOT registered in `spec/VOCAB_VERSIONS` until v1.0 graduation. ### Advisory Vocabulary (Draft) - **v1.0-draft.0.1 (2026-05-05)**: Initial draft authored in `spec/` (TASK-0.3 / TASK-0.4 of the Genomics & Advisory v0.1 workstream). 55 declared `advisory:` terms (8 classes, 16 named individuals across 3 enums, 31 properties). Defines the Cascade Advisory Patch (CAP) envelope as a strict subset of W3C LDPatch (Recommendation, 2015) — author-readable, statically auditable, bounded-execution. Six AdvisoryClass categories (`SafetyCritical`, `VariantReclassification`, `DrugInteraction`, `LabReferenceRangeUpdate`, `SurveillanceGuidelineUpdate`, `CarrierFrequencyUpdate`). Six-tier Cadence enum. Per-pod `TrustedIssuer` + `TrustSourceEnum` (per D-Q3 — no centralized registry). Detached JWS Ed25519 signing envelope per D-Q4 (RFC 7515 compact serialization, content-type fixed to `application/x-cascade-advisory-patch`). `AutoApplyPolicy` with per-(`issuer`, `advisoryClass`) `AutoApplyScope` tuples. SHACL: 4 `sh:NodeShape` declarations enforcing required envelope fields, closed enumerations, and `AutoApplyPolicy` structure. `cascade:AdvisoryApplicationActivity` (declared in core v3.1) is referenced, not redeclared. Per D-PATH, NOT registered in `spec/VOCAB_VERSIONS` until v1.0 graduation. ### Clinical Vocabulary - **v1.15 (2026-08-14)**: Check the unchecked, keep what was being dropped, and stop failing records that carry the thing they are failing for missing. Additive and strictly widening; the only new finding anywhere is one Warning. `clinical:VitalSignShape`'s `clinical:interpretation` is bound to the same value set the lab shapes use, at **Warning** severity — v1.14 put the `sh:in` on the LAB shapes only, so the identical predicate was checked on a lab result and unchecked on a vital sign, and the vital path was the one an importer could write anything to. Warning first and Violation in a later version, once the warning is observably absent from conforming output, is the ratchet core v3.5 wrote down for a value existing data carries. Both interpretation value sets gain the fourteen `data-absent-reason` codes they lacked, so a C-CDA `nullFlavor` of NASK, ASKU and NAV stays three different facts instead of being flattened to `unknown`. Added `clinical:interpretationSourceCode`, semantically identical to `health:interpretationSourceCode`. `clinical:ProcedureShape`'s name requirement becomes an `sh:or` over `clinical:procedureName` and the undefined `health:procedureName` a C-CDA import path emits, plus the new warning-severity `clinical:ProcedureNameSpellingShape`: every converted procedure previously failed the name requirement *while carrying a name*, on a predicate no shape targeted, so the name itself was validated by nothing (measured 2/2, 7/7 and 1/1 across three public samples). That is a MIGRATION WINDOW, not a ratified dual spelling — `clinical:procedureName` is the only spelling any Cascade vocabulary defines, the record is typed `clinical:Procedure`, and the health namespace has no procedure class at all; both halves are removed together in a later version. **A consumer querying `health:procedureName` must add `clinical:procedureName` to its query now.** - **v1.14 (2026-08-08)**: Align to ratified standards; shape the Encounter class. SHACL only — no class or property added, removed, renamed or deprecated, and apart from the new shape the change is strictly widening. `clinical:interpretation` is bound to the [HL7 v3 ObservationInterpretation](http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation) code system (49 selectable codes, version 3.0.0), which is what FHIR R4 binds `Observation.interpretation` to, plus the `data-absent-reason` code `unknown` and the ten retained legacy words; the previous five-member enum was invented here and rejected susceptibility, detection, reactivity and change results that are ordinary laboratory output. `clinical:snomedCode` (4 shapes) and `clinical:icd10Code` are multi-valued, matching `CodeableConcept.coding` 0..*. `clinical:cptCode` accepted five digits, which is CPT Category I only; widened to the full code set structure (Category II ends in `F`, Category III in `T`, Proprietary Laboratory Analyses in `U`). Source-carried date properties (`encounterDate`, `documentDate`, `onsetDate`, `procedureDate`) accept `xsd:date` alongside `xsd:dateTime`, because FHIR's `dateTime` primitive is explicitly partial-precision and C-CDA `effectiveTime` commonly states a calendar day; Cascade-generated timestamps are unchanged. Added `clinical:EncounterShape` and `clinical:EncounterTemporalShape` — `clinical:Encounter` had been defined since v1.7 and targeted by no shape, so encounters validated to PASS with zero constraints evaluated. The new shape is deliberately minimal: `encounterClass` carries no enum (FHIR binds `Encounter.class` extensibly), `encounterStatus` carries the FHIR R4 value set verbatim at Warning severity, and having a position in time is a Warning rather than a Violation because `Encounter.period` is 0..1. - **v1.13 (2026-08-03)**: Deprecated four classes — `clinical:LabResult`, `clinical:Condition`, `clinical:Allergy`, `clinical:Immunization` — each with `owl:deprecated true` and `rdfs:seeAlso` pointing at its `health:` equivalent, which is the type every import path already emits and which health v2.5 now defines and shapes. Not removed: the pod export path remains their sole emitter and existing pods contain them. Documented the intended FHIR R4 value sets on `clinical:status` and `clinical:interpretation` and recorded why two of them are deliberately left unenforced (emitted data uses `discontinued` for medication status and `elevated` for vital-sign interpretation, so constraining them would be breaking rather than clarifying — the ordered fix is to move emitters onto the FHIR values first, then tighten in a release flagged as tightening). Added `clinical:ConsultationNoteShape`: consultation notes had no explicit `sh:targetClass` while their five sibling document types did, and a subclass axiom does not inherit a shape, so they had been validating vacuously. A consultation note missing `importedAt`, `sourceEHR` or `fhirResourceId` passed before and fails now. No emitter changed. - **v1.12 (2026-07-20)**: Added `clinical:parsedIndicationReference`, an `rdfs:subPropertyOf clinical:indicationReference` recording an indication edge the importer *derived* by parsing a coded or free-text reason and matching it to a condition in the same pod, as distinct from an explicit `reasonReference` the source carried. Modelled as a subproperty so traversal over the superproperty returns both families. Matching is code-first with a normalised-name fallback and unambiguous-only; ambiguous and unmatched are counted, never guessed. - **v1.11 (2026-07-16)**: Widened `clinical:indicationReference` by dropping its restrictive `rdfs:domain clinical:Medication` in favour of the broad-domain comment plus SHACL pattern the other cross-class edges use. FHIR carries `reasonReference` on Procedure, MedicationRequest, MedicationAdministration and Encounter, not only medications, and the old domain would have made a reasoner infer those Procedures are Medications. No SHACL change; existing pods unaffected. - **v1.10 (2026-07-16)**: Added three record-to-record edges — `clinical:hasEncounter` (range `clinical:Encounter`), `clinical:indicationReference` (open range: FHIR permits Condition or Observation), and `clinical:linkedCondition` — and deprecated `clinical:linkedConditionIds`, which packed related-condition UUIDs into one space-separated literal no graph query can traverse. Corrected `clinical:hasLabResult`'s `rdfs:range` from `clinical:LabResult` to `health:LabResultRecord`, because both importer paths type panel members `health:LabResultRecord` and the declared range described a class the data never uses. - **v1.9 (2026-06-17)**: Added `cascade:AIExtracted` to every `cascade:dataProvenance` `sh:in` enumeration in `clinical.shapes.ttl`. `cascade:AIExtracted` (a `ClinicalGenerated` subclass, declared in core v3.3) is now a permitted provenance value on all clinical record shapes, so records produced by an AI document-extraction pipeline (e.g. the Workbench) validate without error. Shapes-only change; no new clinical classes or properties. - **v1.8 (2026-03-28)**: Added `clinical:SocialHistoryRecord` class for EHR-extracted social history observations (C-CDA Social History section, LOINC 29762-2). Added 5 properties: `socialHistoryCategory` (10 value set), `packsPerYear`, `substanceType`, `frequencyDescription`, `socialHistoryConsent` (links to `cascade:SocialHistoryConsent`). Two-class social history design: `health:SocialHistoryRecord` (consumer-reported) vs `clinical:SocialHistoryRecord` (EHR-extracted, 42 CFR Part 2). Part of Phase P5.2. - **v1.7 (2026-03-12)**: Added FHIR converter classes: `clinical:Encounter` (6 properties: encounterClass, encounterStatus, encounterType, encounterStart, encounterEnd, facilityName), `clinical:MedicationAdministration` (4 properties: administeredDate, administeredDose, administeredRoute, administrationStatus), `clinical:ImplantedDevice` (5 properties: deviceType, implantDate, deviceManufacturer, udiCarrier, deviceStatus), `clinical:ImagingStudy` (5 properties: imagingModality, studyDescription, numberOfSeries, studyDate, dicomStudyUid). Added 3 `clinical:LaboratoryReport` properties: `hasLabResult` (ObjectProperty → clinical:LabResult), `panelName`, `reportCategory`. Added permissive SHACL shapes for all new classes in `clinical.shapes.ttl` (OQ-4 decision). Part of Lossless FHIR ↔ Cascade Conversion Plan Phase A. - **v1.6 (2026-02-22)**: Added `clinical:linkedConditionIds` DatatypeProperty on `clinical:Condition` for linking related conditions via space-separated UUIDs. Aligned typed code properties on Checkup summary classes to use `clinical:` namespace predicates: `clinical:loincCode` on `LabResult` (replaces legacy `checkup:loincCode`), `clinical:cvxCode` on `ImmunizationRecord` (preferred over `clinical:vaccineCode`), `clinical:icd10Code` on `FamilyHistoryEntry` and `ConditionSummary` (replaces `checkup:familyConditionCode` and `checkup:icd10Code`), `clinical:snomedCode` on `AllergySummary`/`ScreeningStatus`/`FamilyHistoryEntry`/`ConditionSummary`, `clinical:rxNormCode` on `AllergySummary`. Annotated `clinical:vaccineCode` as legacy. Part of Health Map v3 Pre-Phase Track A (Task A2). - **v1.5 (2026-02-10)**: Added `CoverageRecord` class (11 properties) for insurance coverage/plan records imported from EHR via Apple HealthKit (FHIR Coverage resource). Added 3 new properties to existing `Procedure` class (procedureStatus, procedureCategory, procedureSnomedCode — prefixed to avoid domain collisions). Added `clinical:administrationRoute` property (previously missing from v1.3 medication enrichment set). Existing Procedure properties preserved (procedureName, performedDate, bodySite, performer, location, outcome, cptCode). - **v1.4 (2026-01-28)**: Added `LabTestSeries` class for longitudinal lab tracking with trend inference. Enhanced `LabResult` with metadata properties (`orderingProvider`, `performingLab`, `observationStatus`, `referenceRangeLow/High`). - **v1.3 (2026-01-26)**: Added 14 medication enrichment properties from FHIR extraction pipeline (`prescriber`, `indication`, `courseOfTherapyType`, `medicationForm`, `activeIngredient`, `provenanceClass`, `clinicalIntent`, dispensing properties). - **v1.2 (2026-01-15)**: Added MedicationUseEpisode and Supplement classes - **v1.1 (2026-01-01)**: Added 7 structured clinical record classes - **v1.0 (2025-12-26)**: Initial release with document-centric vocabulary ### Health Vocabulary - **v2.7 (2026-08-14)**: Let a source's absence and a source's unrecognised code both survive import. `health:interpretation`'s value set widens from the single `data-absent-reason` code `unknown` to all 15 codes of that system (60 values to 74). v2.6 accepted one code from it, which made every reason for an absent interpretation identical; putting a `data-absent-reason` coding INTO the CodeableConcept that is missing is FHIR's own idiom for an element absent for a stated reason, so this is deference rather than invention. Added `health:interpretationSourceCode`: the source's verbatim interpretation code, written only when that code is in neither bound value set — a laboratory's local abnormality flag is the ordinary case. The alternative considered and rejected was accepting the loss with a warning; a closed value set plus a lossy importer means such a flag is either dropped on the floor or written into a field that then fails validation, and either way the Pod can no longer answer what the source said. The property is deliberately unconstrained (no value set, no pattern, no case folding), because constraining a verbatim carbon copy recreates the loss it prevents. **Not** changed, stated so its absence is not read as an oversight: no interpretation constraint was added to `health:DailyVitalReadingShape`, because `health:interpretation`'s domain is `health:LabResultRecord` and `health:DailyVitalReading` carries no interpretation predicate at all, so the constraint would be vacuous. The vital-sign binding of the same release is `clinical:VitalSignShape`. - **v2.6 (2026-08-08)**: Align the lab-result constraints to the standards they describe. SHACL only; no class or property changed, and nothing that validated under v2.5 stops validating. Real-world Epic FHIR exports were failing validation on records that are correct at source. `health:interpretation` is now the 49 selectable codes of [HL7 v3 ObservationInterpretation](http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation) version 3.0.0 (abstract concepts excluded because they are hierarchy nodes, deprecated codes included because historical results carry them), plus the `data-absent-reason` code `unknown` for a source Observation that carried no interpretation, plus the ten retained v2.5 words. `health:labCategory` is multi-valued, matching `Observation.category` 0..*. `health:testCode`, `health:icd10Code` and `health:snomedCode` are multi-valued, matching `CodeableConcept.coding` 0..*; dual-coded problem lists and multi-coding lab observations are ordinary EHR output. Source-carried date properties (`performedDate`, `reportedDate`, `onsetDate`, `administrationDate`) accept `xsd:date` alongside `xsd:dateTime`, and their JSON-LD terms no longer coerce to `xsd:dateTime`, so a producer states the precision it has instead of inventing a midnight. No `sh:pattern` was added to any code property: ICD-10-CM permits a letter in any character position and SNOMED CT identifiers are 6-18 digit integers, so a pattern would reject valid codes. - **v2.5 (2026-08-03)**: Clinical record classes and wellness containers. Defined five classes that serializers have emitted since schema version 1.3 but that this ontology never declared — `health:LabResultRecord`, `health:ConditionRecord`, `health:AllergyRecord`, `health:ImmunizationRecord`, `health:FamilyHistoryRecord` — plus the 40 properties they use and 4 sleep-quality named individuals. Defined six wellness container classes (`health:ActivityData`, `SleepData`, `HeartRateData`, `BloodPressureData`, `HRVData`, `BodyMeasurements`) as `rdfs:subClassOf health:HealthProfile`, which makes the `rdfs:domain health:HealthProfile` already asserted on the eight history properties true rather than contradicted and brings those containers under `health:HealthProfileShape`. Added the namespace-boundary note recording that `health:` versus `clinical:` is historical and that provenance is carried by `cascade:dataProvenance` alone. `health.shapes.ttl` v1.2 adds eight shapes: the five record classes and the three daily-snapshot classes. No serializer changes; every term added was already being emitted. - **v2.4 (2026-03-27)**: EHR import reconciliation. Added `health:SocialHistoryRecord` for consumer-reported social history observations, plus `health:smokingStatus`, `health:alcoholUse`, `health:exerciseFrequency` and `health:occupationalExposure`. - **v2.3 (2026-02-28)**: BUG-006 Pattern A standardization. Added 6 flat `health:` DatatypeProperties for `VitalSignReading` blank nodes emitted by `HealthProfileSerializer`: `health:value`, `health:date`, `health:sdnn`, `health:systolic`, `health:diastolic`, `health:walkingSteadinessLevel` (ObjectProperty). Added named individuals `health:OK`, `health:Low`, `health:VeryLow`, `health:Unknown` for walking steadiness values. Added `health:WalkingSteadinessUnknown` to legacy individual set. These replace the `fhir:Observation` nesting pattern (Pattern B) for wellness vital sign serialization. - **v2.2 (2026-02-17)**: Added 2 statistics container linking ObjectProperties: `health:hasHRVStatistics` (links wellness profile to HRV statistical summary), `health:hasBPStatistics` (links wellness profile to BP statistical summary). Previously dark vocabulary emitted by CheckupSerializer. Part of Schema Refactoring Plan Phase 3 (PF1-R). - **v2.1 (2026-02-17)**: Added `health:SelfReport` base class and 5 base properties (`reportDate`, `reportType`, `completionStatus`, `reportContext`, `reportNotes`) for patient-reported daily check-ins. Supports the SelfReportable protocol pattern where apps extend this class with domain-specific response data. Part of Schema Refactoring Plan Phase 3 (WS7). - **v2.0 (2026-02-18)**: Schema Refactoring WS3b — Migrated HealthProfileSerializer from `cascade:` to `health:` namespace. Added 8 history container ObjectProperties (`restingHeartRateHistory`, `walkingHeartRateHistory`, `hrvHistory`, `bloodPressureHistory`, `vo2MaxHistory`, `bodyMassHistory`, `dailyActivityHistory`, `dailySleepHistory`) and 7 supporting classes (`HealthProfile`, `DailyVitalReading`, `HRVReading`, `BloodPressureReading`, `VitalSignReading`, `DailyActivitySnapshot`, `DailySleepSnapshot`). These capture time-series data for 90-day, 30-day, and 7-day lookback windows. - **v1.9 (2026-02-17)**: Added `health:bloodType` property for ABO blood group and Rh factor (sct:365637002, loinc:882-1). Values: aPositive, aNegative, bPositive, bNegative, abPositive, abNegative, oPositive, oNegative. Sourced from HealthKit or manual entry. Previously dark vocabulary emitted in QR payload. Part of Schema Refactoring Plan Phase 2.5 (PF4). - **v1.8 (2026-02-10)**: Added 6 body measurement and metabolic properties: `health:bodyMass` (sct:27113001, loinc:29463-7), `health:bodyHeight` (sct:50373000, loinc:8302-2), `health:bodyMassIndex` (sct:60621009, loinc:39156-5), `health:bodyTemperature` (sct:386725007, loinc:8310-5), `health:oxygenSaturation` (sct:431314004, loinc:2708-6), `health:bloodGlucose` (sct:33747003, loinc:2339-0). Added `health:BPStatistics` class with 8 properties for blood pressure statistical summaries (bpSampleCount, bpMean/Min/Max Systolic/Diastolic, bpCategory). Added shared temporal properties (periodStart, periodEnd). Completed `health:HRVStatistics` property definitions (10 properties: hrvSampleCount, hrvDaysCovered, hrvMean, hrvMedian, hrvStdDev, hrvMin, hrvMax, hrvPercentile25, hrvPercentile75, hrvTrendDirection). - **v1.7 (2026-02-03)**: Added `health:VO2MaxStatistics` and `health:HRVStatistics` classes for statistical trend analysis. Added 9 properties for VO2 Max statistics (vo2Mean, vo2Min, vo2MaxValue, vo2SampleCount, vo2DaysCovered, vo2TrendDirection, vo2PercentageChange, fitnessClassification, isSparseData). Supports dual-path UI pattern with rich statistics preferred over single values. - **v1.6 (2026-02-02)**: Added `health:trendPolarity` annotation property to all measurable wellness metrics. Defines whether an increase is favorable/unfavorable/neutral. - **v1.5 (2026-02-01)**: Added `health:MetricTrend` class for time-bounded wellness trends with direction, magnitude, confidence, and baseline/current values. Follows pattern from `clinical:LabTestSeries` but for continuous device observations. - **v1.4 (2026-01-29)**: Initial release with SNOMED CT and LOINC mappings for all wellness metrics. 2 composite classes, 14+ properties, 4 annotation properties. ### Core Vocabulary - **v3.6 (2026-08-14)**: Added `cascade:dataAbsentReason` — why a record's primary VALUE is absent, with exactly FHIR R4 `Observation.dataAbsentReason` semantics, bound to the 15 [`data-absent-reason`](http://terminology.hl7.org/CodeSystem/data-absent-reason) codes, with the full [HL7 v3 NullFlavor](http://terminology.hl7.org/CodeSystem/v3-NullFlavor) mapping stated on the property so UNK / NAV / NASK / ASKU stop collapsing into one indistinguishable blank on import. `core.shapes.ttl` v1.6 adds `cascade:DataAbsentReasonShape`, open-world (`sh:targetSubjectsOf`), so every pod written before v3.6 validates unchanged; raw NullFlavor codes are deliberately rejected because an absence must not have two encodings. Also stated the NORMATIVE canonical form of a multi-valued identity input on `cascade:cascadeUri`: dedupe, sort by Unicode code point, join with a fixed separator (U+002C recommended and required of new implementations), and a one-element sequence canonicalizes to the bare scalar so no identifier already written moves. Three invariants (order independence, scalar agreement, duplicate independence) are normative independently of the separator, which lets an implementation already shipping a different separator comply without re-minting. The scope limit is as load-bearing as the rule: SETS only, never an input whose source order carries meaning. No class or property removed, renamed or deprecated. - **v3.5 (2026-08-09)**: The ORIGIN axis. Added `cascade:sourceIdentity`, a canonical, transport-independent identity for the organization a record came from, scheme-prefixed `org:{slug}` / `ns:{namespace}` / `transport:{label}` so a consumer can tell how much the producer actually knew. Stated the three source axes explicitly: ORIGIN (`cascade:sourceIdentity`), LABEL (`clinical:sourceEHR`, unchanged), INGESTION (`cascade:sourceSystem`, whose comment now says it records how and when data arrived, never where it came from, and must not be used as a reconciliation key). The slug normalization every transport must implement identically is stated on the property, and is deliberately biased toward collapsing two organizations rather than splitting one, because a collapse leaves recoverable duplicates while a split lets records an organization deliberately kept apart be merged. No class or property removed, renamed or deprecated. - **v3.4 (2026-08-03)**: Pod export manifest. Defined all 32 `cascade:` terms that a conforming pod export emits and that this file had never declared, so a manifest is describable rather than only parseable. `cascade:ExportManifest` is `rdfs:subClassOf dcat:Dataset` ([DCAT 3](https://www.w3.org/TR/vocab-dcat-3/)); `cascade:RecordSummary` is `rdfs:subClassOf void:Dataset` ([VoID](https://www.w3.org/TR/void/)) with its per-domain count properties declared `rdfs:subPropertyOf void:entities` and paired with the `void:class` each counts; `cascade:InteractionScenario` is kept as genuinely novel, since no ratified vocabulary models cross-provenance correlation as a first-class thing. Also defined the reading-level terms `cascade:date`, `cascade:sampleCount` and `cascade:loincCode`, emitted on every history-container entry and constrained by the health v2.5 daily-snapshot shapes. `core.shapes.ttl` v1.1 shapes all three manifest classes. - **v3.3 (2026-06-16)**: Cascade Workbench support. Added `cascade:AIAsserted` provenance leaf (subclass of `cascade:ConsumerGenerated`) for ungrounded content surfaced by a general-purpose AI assistant — distinct from `cascade:AIExtracted` (clinically grounded) and `cascade:EHRVerified`; must be evidence-checked before reliance. Added `cascade:ProxyAgent` (subclass of `prov:Agent`) for a person operating a patient's Pod on the patient's behalf (parent/guardian/caregiver), closing the caregiver-proxy gap, with 6 properties: `actsForPatient` (subproperty of `prov:actedOnBehalfOf`), `proxyWebID`, `proxyRelationship`, `proxyScope`, `proxyGrantedAt`, `proxyRevokedAt`. - **v3.2 (2026-05-06)**: Forward-reference closure for the advisory applier. Added `cascade:appliedTriplesCount` (DatatypeProperty, range `xsd:nonNegativeInteger`, domain `cascade:AdvisoryApplicationActivity`) recording the number of triples inserted into the Pod by a single advisory application — auditable post-hoc verification of CAP profile constraint C5 (≤ 64 inserted triples per match). - **v3.1 (2026-05-05)**: Genomics & Advisory provenance. Added `cascade:AdvisoryApplicationActivity` (`prov:Activity` subclass) for recording application of a Cascade Advisory Patch to a Pod (advisory and matched record linked via `prov:used`). Added `cascade:AIGenerationActivity` (`prov:Activity` subclass), sibling of `cascade:AIExtractionActivity`, for LLM-generated narrative chunks (e.g. `checkup:VariantNarrative`); reuses `extractionModel`/`extractionConfidence`/`sourceNarrativeSection`/`requiresUserReview` and adds `cascade:promptVersion` and `cascade:generationTemperature`. Added `cascade:trigger` ObjectProperty (domain `AIGenerationActivity`, range `cascade:GenerationTrigger`) with three named individuals: `cascade:InitialGeneration`, `cascade:RegenerationAfterReclassification`, `cascade:AudienceRetargeting`. - **v3.0 (2026-03-28)**: Added `cascade:HealthRecord` abstract superclass for all health data records. Added `cascade:AIExtractionActivity` (PROV-O Activity for AI/NLP extraction passes, 4 properties: `extractionConfidence`, `extractionModel`, `sourceNarrativeSection`, `requiresUserReview`). Added `cascade:AIDiscardedExtraction` (audit record for discarded extraction candidates, 1 property: `discardReason`). Added `cascade:SocialHistoryConsent` (consent record for 42 CFR Part 2 sensitive data, 3 properties: `consentScope`, `consentGrantedAt`, `consentRevokedAt`). Part of Phase P5.2. - **v2.8 (2026-03-12)**: Added FHIR conversion support: `cascade:LayerPromotionStatusValue` class, `cascade:FullyMapped` and `cascade:PendingLayerTwoPromotion` named individuals, `cascade:layerPromotionStatus` ObjectProperty, `cascade:fhirJson` DatatypeProperty (inline FHIR JSON for passthrough records), `cascade:fhirResourceType` DatatypeProperty, `cascade:sourceRecordDate` DatatypeProperty. Part of Lossless FHIR ↔ Cascade Conversion Plan Phase A (OQ-8 rename from conversionStatus to layerPromotionStatus). - **v2.7 (2026-03-12)**: Removed `cascade:conflictDescription` (deprecated since v2.5). `cascade:conflictDetail` blank nodes now carry an `rdfs:comment` with a human-readable conflict summary, replacing the separate prose property. - **v2.6 (2026-03-11)**: Within-source deduplication vocabulary (Phase 6 of reconciliation pipeline). Added `cascade:WithinSourceDuplicate` named individual for `cascade:ReconciliationStatus`. Represents encounter-instance duplicate records suppressed during Pass 1 within-source deduplication before cross-system matching. - **v2.5 (2026-03-11)**: Structured conflict representation (Phase 5 of reconciliation pipeline). Added `cascade:ConflictDetail` class and 4 supporting properties: `cascade:conflictDetail` (ObjectProperty, links reconciled record to ConflictDetail blank node), `cascade:conflictFor` (ObjectProperty, the conflicting predicate URI), `cascade:systemValue` (ObjectProperty, per-system value blank node), `cascade:forSystem` (DatatypeProperty, system name string). Deprecated `cascade:conflictDescription` (informational prose, replaced by structured blank node). - **v2.4 (2026-03-11)**: Added 7 reconciliation properties for multi-system data normalization: `cascade:sourceSystem` (originating EHR/system name, added by `cascade convert --source-system`), `cascade:reconciliationStatus` (canonical | merged | conflict-resolved | unresolved-conflict), `cascade:mergedFrom` (ObjectProperty, links to source record URIs, subproperty of `prov:wasDerivedFrom`), `cascade:mergedSources` (string list of contributing systems), `cascade:conflictResolution` (trust_priority | merge_values | flag_unresolved), `cascade:conflictField` (predicate where conflict was detected), `cascade:conflictValues` (human-readable conflict description). These properties are emitted by `cascade reconcile` and support the `cascade-reconcile-experiment` pipeline. - **v2.3 (2026-02-20)**: Added 2 provenance detail properties: `cascade:sourceOrganization` (organization that provided a record, populated from FHIR `meta.source`) and `cascade:sourceDeviceName` (device/app that recorded a measurement, populated from HealthKit `HKSource`). Used across all 11 CheckupShared record models for Health Map provenance display. Part of Health Map v3 Pre-Phase Track A (Task A4). - **v2.2 (2026-02-18)**: FHIR-aligned Address expansion and PatientProfile completeness (Phase 3.5). ONT1: Added 9 FHIR Address-aligned properties (`addressUse`, `addressType`, `addressText`, `addressLine`, `addressCity`, `addressDistrict`, `addressState`, `addressPostalCode`, `addressCountry`). Existing simple properties retained as aliases. ONT2: Added `EmergencyContact` class with 3 properties (`contactName`, `contactRelationship`, `contactPhone`). Added PatientProfile properties: `computedAge`, `profileId`, `pharmacyAddress`, `preferredLanguage`. Added blank-node linking object properties: `emergencyContact`, `advanceDirectives`, `preferredPharmacy`, `address`. - **v2.1 (2026-02-17)**: Added `cascade:addedInSchemaVersion` annotation property and `cascade:cascadeUri` URI property (range: `xsd:anyURI`). Previously dark vocabulary emitted by CheckupSerializer. Part of Schema Refactoring Plan Phase 3 (PF1-R). - **v2.0 (2026-02-17)**: Added `cascade:PatientProfile` class with 12 properties for health-specific patient demographics (Solid WebID Profile extension). Added `cascade:Address` class (5 properties) for structured postal addresses with FHIR Address semantics. Added `cascade:PharmacyInfo` class (2 properties + reused `hasAddress`) for preferred pharmacy. Added `cascade:AdvanceDirectives` class (4 properties) for advance directive declarations. Promotes patient identity from `checkup:` (Layer 3) to `cascade:` (cross-cutting) as part of Schema Refactoring Plan WS6. Emergency contact properties (`emergencyContactName`, `emergencyContactRelationship`, `emergencyContactPhone`) are new additions not previously in `checkup:`. - **v1.2 (2026-02-10)**: Promoted `ConsumerGenerated` and `ClinicalGenerated` from OWL individuals to subclasses of `DataProvenance`. Added 6 leaf subclasses matching SDK `DataProvenance.swift` enum: `DeviceGenerated`, `SelfReported`, `ConsumerWellness` (under ConsumerGenerated); `EHRVerified`, `ScannedDocument`, `AIExtracted` (under ClinicalGenerated). Backward-compatible with existing data. - **v1.1 (2026-02-01)**: Added `cascade:DataSource` class for device/app/system identity with reliability tiers. Subclass of `prov:Agent`. - **v1.0 (2025-12-26)**: Initial release with provenance classes. ### Checkup Vocabulary - **v3.3 (2026-08-08)**: SHACL only. The `checkup:icd10Code` pattern required a digit in the second and third characters. The ICD-10-CM Official Guidelines for Coding and Reporting, Section I.A.2, state that "Characters for categories, subcategories and codes may be either a letter or a number" and that codes may be 3, 4, 5, 6 or 7 characters, so live categories such as `C4A`, `D3A` and `M1A` and every code beneath them failed. Corrected to `^[A-Z][0-9A-Z]{2}(\.[0-9A-Z]{1,4})?$`. `sh:Info` severity, so this fixed an advisory that fired on valid codes rather than a rejection. - **v3.2 (2026-02-26)**: Added `checkup:supplyEndDate` DatatypeProperty on `MedicationSummary`. Computed as `startDate + supplyDurationDays`, this field allows consumers to distinguish administrative EHR order expiry (`checkup:medicationEndDate`) from actual supply exhaustion. Fixes BUG-002 where all medications appeared inactive because the signal extraction script used `medicationEndDate` as the sole activity signal. 28 active classes, 1 property added. - **v3.1 (2026-02-20)**: Added `checkup:VisitPrepTray` class (12 properties) for per-appointment visit preparation containers with typed payload wrapper (`VisitItemPayload` enum preserving `VisitIssue`, `DiscussionTopic`, `SuggestedQuestion` type richness). Added `checkup:hasTrayItem` ObjectProperty. Added `checkup:hasVisitPrep` and `checkup:activeVisitPrepId` aggregate links on `IntakeFormData`. 28 active classes. Part of Health Map v3 Pre-Phase Track A (Task A7). - **v3.0 (2026-02-18)**: Removed 8 deprecated classes (`PatientProfile`, `VitalSignsTrend`, `WellnessBaseline`, `ActivitySummary`, `ClinicalSummary`, `HealthRecord`, `ReviewOfSystems`, `DataProvenance`) and associated properties. Clean break per guiding constraint #1. 27 active classes remain. CLN1 version bump. - **v2.3 (2026-02-18)**: Deprecated `checkup:cascadeUri` in favor of `cascade:cascadeUri` from core.ttl (Phase 3.5 ONT3). - **v2.2 (2026-02-17)**: `checkup:InsuranceInfo` class comment updated to reference `coverage:` namespace properties. Version bump to match `checkup.ttl` `owl:versionInfo`. Part of Schema Refactoring Plan Phase 3 (WS4 integration). - **v2.1 (2026-02-17)**: `DailyCheckIn` now `rdfs:subClassOf health:SelfReport` (was `prov:Entity`). Base self-report properties (`reportDate`, `reportType`, `completionStatus`, `reportContext`, `reportNotes`) promoted to `health:` namespace. Part of Schema Refactoring Plan Phase 3 (WS7). - **v2.0 (2026-02-17)**: Deprecated `checkup:` patient profile properties (`maritalStatus`, `raceEthnicity`, `preferredPharmacy`, `hasLivingWill`, `hasPowerOfAttorney`, `hasDNR`, `advanceDirectiveNotes`, `preferredLanguage`) in favor of `cascade:` equivalents in Core Vocabulary v2.0. Core demographics (`dateOfBirth`, `biologicalSex`, `genderIdentity`, `ageGroup`) migrated to `cascade:PatientProfile`. Part of Schema Refactoring Plan Phase 3 (WS6). - **v1.11 (2026-02-18)**: Updated `InsuranceInfo` class comment to reference `coverage:` namespace for mixed namespace pattern. Part of Schema Refactoring Plan Phase 2 (WS4). - **v1.10 (2026-02-17)**: Added `cascade:subConcern` annotations to all 35 checkup classes (9 categories). Deprecated `WellnessBaseline`, `ActivitySummary`, `ReviewOfSystems`. Defined 14 dark vocabulary properties with migration annotations. Part of Schema Refactoring Plan Phase 1 (WS2). - **v1.9 (2026-02-10)**: Added `MedicationChangeRecord` class for tracking medication/supplement changes between visit snapshots (5 properties + `hasChange` link). Enables persistence of "Changes Since Last Visit" report data. Property names prefixed (`changeRecordPreviousValue`, `changeRecordNewValue`) to avoid collision with `VitalSignsTrend` properties. - **v1.8 (2026-02-10)**: Added `ProcedureSummary` class with mixed namespace pattern (4 `checkup:` properties + 7 `clinical:` cross-references). Added `hasProcedure` aggregate link on `IntakeFormData`. Procedure serialization and deserialization implemented in `CheckupSerializer`/`CheckupDeserializer`. - **v1.7 (2026-02-10)**: Added 5 new classes: `WellnessBaseline` (8 properties — aggregated wellness metrics snapshot), `ActivitySummary` (8 properties — activity and sleep metrics), `ImmunizationSummary` (16 properties — mixed namespace pattern with clinical:), `LabResultSummary` (1 checkup: property + 12 clinical: properties via mixed namespace), `WellnessProfileReference` (pointer to SDK HealthProfile). Added 4 MedicationSummary verification properties (needsVerification, verificationReason, lastVerifiedDate, importSessionId). Added 2 SuggestedQuestion properties (basedOnTemplate, basedOnScreening). Added `noteContent` property to VisitPrepSummary (merges planned VisitPrepNotes class). Added 4 IntakeFormData aggregate links (hasImmunization, hasLabResult, hasWellnessBaseline, hasActivitySummary). Mixed namespace pattern documented as standard for Layer 3 summary classes. - **v1.6 (2026-02-09)**: Added 2 new classes for intake questionnaire data: `IntakeQuestionnaireResponse` (FHIR-inspired questionnaire container using LOINC panel codes as identifiers and SNOMED CT for coded answers, with `rdfs:seeAlso fhir:QuestionnaireResponse`), `ResponseItem` (question-answer pair with LOINC item codes, SNOMED CT coded answers, and nested sub-items via `childItem`). Added 10 new properties and `hasQuestionnaireResponse` link from `IntakeFormData`. Design follows "established vocabularies first" — LOINC/SNOMED as actual vocabulary, `checkup:` as minimal container. Mechanical transformation path to FHIR QuestionnaireResponse JSON preserved. Supports template-driven intake form expansion (Kaiser eCheck-In gap analysis). Also backfilled v1.5 classes into `checkup.ttl` ontology file (previously documented but not in TTL). - **v1.5 (2026-02-07)**: Added 3 new classes for export completeness: `DailyCheckIn` (patient-reported medication adherence with per-medication tracking, mapped to LOINC `79474-4` and SNOMED `225390008`/`418633004`), `DiscussionTopic` (AI-generated or user-created visit discussion items with 11 categories, relevance scoring, and medication context — Cascade-proprietary, no standard equivalent), `CheckInSettings` (application configuration for notification scheduling, not health data). All classes follow three-layer ontology: L1 mappings documented or gaps noted, L2 skipped with rationale, L3 classes defined. Supports Bug 6/11 export overhaul Phase 4B serialization. - **v1.4 (2026-02-01)**: Added 6 new classes: `WellnessSummary`, `TrendInsight`, `CorrelationInsight`, `KeyIndicator`, `AttentionItem`, `VisitPrepSummary`. Deprecated `VitalSignsTrend`. Added `health:` import. - **v1.3 (2026-01-17)**: Added EHR provenance properties (`ehrSystemName`, `ehrOrganization`, `fhirResourceType`, `lastSyncedFromSource`) and medication classification (`relevanceTier`, `therapeuticClass`, `tierClassificationReason`). - **v1.2 (2026-01-15)**: Added Phase 3 medication properties, doctorAware - **v1.1 (2026-01-15)**: Added SupplementSummary class - **v1.0 (2026-01-03)**: Initial release with 12 core classes ### Coverage Vocabulary - **v1.4 (2026-08-08)**: Align to the FHIR bindings. SHACL only; no class or property changed, and both changes are strictly widening. `coverage:subscriberRelationship` held five of the seven codes in the [SubscriberPolicyholder](http://terminology.hl7.org/CodeSystem/subscriber-relationship) code system that FHIR R4 `Coverage.relationship` binds to — `common` (Common Law Spouse) and `injured` (Injured Party) were missing, so a conformant export warned. `coverage:coverageType` enforced a closed four-member enum at `sh:Violation` severity on an element FHIR binds [extensibly](https://hl7.org/fhir/R4/terminologies.html#extensible), where alternate codes MAY be used when the value set does not cover the concept; a payer sending `EHCPOL` or a local code had the record rejected outright. Presence remains a Violation; the value is now checked at `sh:Warning` by the new `coverage:CoverageTypeVocabularyShape`, against the FHIR value set plus the four retained Cascade values. Worth recording: those four conflated two FHIR elements, since `primary`/`secondary` describe `Coverage.order` rather than `Coverage.type`. - **v1.1 (2026-03-12)**: Added `coverage:ClaimRecord` class (6 properties: claimDate, claimTotal, claimStatus, claimType, billingProvider, sourceRecordId; fhir:Claim alignment). Added `coverage:BenefitStatement` class (9 properties: adjudicationDate, adjudicationStatus, outcomeCode, denialReason, totalBilled, totalPaid, patientResponsibility, relatedClaim, sourceRecordId; fhir:ExplanationOfBenefit alignment). Promoted previously-deferred Deductible fill properties (deductibleAmount, deductibleMet, outOfPocketMax) and CopaySchedule fill properties (copayPrimary, copaySpecialist, copayEmergency). `coverage:sourceRecordId` defined as domain-unrestricted property shared across ClaimRecord and BenefitStatement (OQ pre-flight correction). Added permissive SHACL shapes for new classes. Part of Lossless FHIR ↔ Cascade Conversion Plan Phase A. - **v1.0 (2026-02-18)**: Initial release. `InsurancePlan` class with 14 properties (8 core, 3 subscriber, 3 pharmacy benefit). `Deductible` and `CopaySchedule` classes defined (properties deferred). Unifies `checkup:InsuranceInfo` (L3) and `clinical:CoverageRecord` (L2). FHIR Coverage alignment. Part of Schema Refactoring Plan Phase 2 (WS4). ### POTS Vocabulary - **v1.4 (2026-02-26)**: Added 8 new optional predicates for POTS Check V2. Test configuration: `baselineDurationMinutes` (user-selected baseline in whole minutes), `testNotes` (free-text patient notes, max 500 chars), `bpProvided` (boolean BP cuff flag). Proportional Pulse Pressure per-minute ratios: `pppMinute1`, `pppMinute3`, `pppMinute5`, `pppMinute7`, `pppMinute10` (decimal 0.0-1.0, PPP = (SBP-DBP)/SBP). All predicates are optional; v1.3 files remain fully parseable. - **v1.3 (2026-02-10)**: Added 2 timing properties (`secondsSinceStand`, `timestampSeconds`) for fine-grained temporal resolution in standing/symptom data. Added 4 Apple Health import properties (`importedBloodPressureHistory`, `importedHRVHistory`, `averageImportedBloodPressure`, `averageImportedHRV`) for longitudinal context. Renamed `postureStabilitySamples` to `postureStability` to match shipped app serializer. - **v1.2**: Added maxHeartRateDelta, userAgeAtTest, posture quality - **v1.1**: Added orthostaticHypertensionSuspected, postureQualityScore - **v1.0 (2025-10-31)**: Initial release ### SHACL Shapes Files - **clinical.shapes.ttl v1.14 (2026-08-08)**: Added `clinical:EncounterShape` and `clinical:EncounterTemporalShape`; bound `clinical:interpretation` to the HL7 v3 ObservationInterpretation code system; made `clinical:snomedCode` and `clinical:icd10Code` multi-valued; widened the `clinical:cptCode` pattern to the full CPT structure; and widened the source-carried date properties to accept `xsd:date` alongside `xsd:dateTime`. - **health.shapes.ttl v1.3 (2026-08-08)**: Bound `health:interpretation` to the HL7 v3 ObservationInterpretation code system plus the `data-absent-reason` code `unknown`; removed `sh:maxCount 1` from `health:labCategory`, `health:testCode`, `health:icd10Code` and `health:snomedCode`; and widened the source-carried date properties to accept `xsd:date`. Strictly widening: every graph valid under v1.2 is valid under v1.3. - **coverage.shapes.ttl v1.1 (2026-08-08)**: Completed the `coverage:subscriberRelationship` enum to the full SubscriberPolicyholder code system, and split the `coverage:coverageType` value check into a new `coverage:CoverageTypeVocabularyShape` at `sh:Warning`, leaving presence as a Violation. - **checkup.shapes.ttl v2.1 (2026-08-08)**: Corrected the `checkup:icd10Code` pattern, which required digits where ICD-10-CM permits letters and therefore rejected every code under `C4A`, `D3A` and `M1A`. - **health.shapes.ttl v1.2 (2026-08-03)**: Added eight shapes. Five for the record classes health v2.5 defines (`LabResultRecord`, `ConditionRecord`, `AllergyRecord`, `ImmunizationRecord`, `FamilyHistoryRecord`), with constraint sets lifted from the corresponding `clinical:` shapes and checked against the FHIR R4 resource definitions; required properties are `sh:Violation`. Three for the daily wellness entries (`DailyVitalReading`, `DailyActivitySnapshot`, `DailySleepSnapshot`). The six wellness container classes are additionally stated as explicit `sh:targetClass` on `health:HealthProfileShape` so their coverage does not depend on a validator performing subclass inference. - **core.shapes.ttl v1.5 (2026-08-09)**: Added `cascade:SourceIdentityShape` for core v3.5's `cascade:sourceIdentity`. Open-world (`sh:targetSubjectsOf`), so it constrains the value wherever the property appears and never requires its presence; every pod written before v3.5 validates unchanged. VIOLATION for a non-string, a repeated value, or a value carrying none of the three declared schemes. The ratchet to a Warning and then a Violation on absence is written into the `core.ttl` changelog rather than left to judgement. - **core.shapes.ttl v1.1 (2026-08-03)**: Added shapes for `cascade:ExportManifest`, `cascade:RecordSummary` and `cascade:InteractionScenario`, the three manifest classes core v3.4 defines. - **clinical.shapes.ttl v1.13 (2026-08-03)**: Added `clinical:ConsultationNoteShape`. Five of the six `clinical:ClinicalDocument` subtypes carried an explicit `sh:targetClass` and this one did not; since a subclass axiom in the ontology does not expand a shape's target set, consultation notes were validating vacuously. - **clinical.shapes.ttl v1.9 (2026-06-17)**: Added `cascade:AIExtracted` to every `cascade:dataProvenance` `sh:in` enumeration so AI-extracted clinical records (e.g. from the Workbench's document-extraction pipeline) validate. Shapes-only change. - **core.shapes.ttl v1.0 (2026-02-18)**: Initial release. Validation shapes for `cascade:PatientProfile` (required demographics, address structure) and `cascade:DataProvenance` taxonomy. - **clinical.shapes.ttl v1.7 additions (2026-03-12)**: Added permissive shapes for `clinical:MedicationAdministration`, `clinical:ImplantedDevice`, `clinical:ImagingStudy`, and `clinical:LaboratoryReport` (panel additions). All new shapes use `sh:minCount 0` on every property (OQ-4 decision). *This entry previously also listed `clinical:Encounter`; that shape was never written. It was authored in clinical v1.14, and until then validating an encounter evaluated zero constraints and returned PASS.* - **clinical.shapes.ttl v1.0 (2026-02-10)**: Initial release. Validation shapes for `clinical:ClinicalDocument`, `clinical:MedicationUseEpisode`, and `clinical:Supplement` required fields. - **coverage.shapes.ttl v1.1 additions (2026-03-12)**: Added permissive shapes for `coverage:ClaimRecord` and `coverage:BenefitStatement`. All new shapes use `sh:minCount 0` on every property (OQ-4 decision). - **coverage.shapes.ttl v1.0 (2026-02-18)**: Initial release. Validation shapes for `coverage:InsurancePlan`. - **health.shapes.ttl v1.0 (2026-02-18)**: Initial release. Validation shapes for `health:HealthProfile`, `health:MetricTrend` enum validation, and statistical summary classes. - **pots.shapes.ttl v1.0 (2026-02-18)**: Initial release. Validation shapes for `pots:POTSCheckResult` protocol fields and `pots:HeartRateMeasurement` timing constraints. - **coverage.shapes.ttl v1.0 (2026-02-18)**: Initial release. Validation shapes for `coverage:InsurancePlan` required fields, date ranges, and plan type enums. - **checkup.shapes.ttl v2.0 (2026-02-18)**: Updated for v3.0 vocabulary changes. Removed shapes for deleted classes (`PatientProfile`, `WellnessBaseline`, `ActivitySummary`). IntakeFormData shape now references `cascade:PatientProfileShape` from core shapes. Updated from v1.0. --- *This document is generated from the official Cascade Protocol TTL schema files.* *For the latest version, visit: https://cascadeprotocol.org/docs/* # ============================================================================ # SECTION 5: SERIALIZATION SPECIFICATION # ============================================================================ # Cascade Protocol Serialization Specification **Version:** 2.1 **Date:** 2026-08-03 **Status:** Descriptive with normative examples **Organization:** Cascade Agentic Labs LLC **Vocabulary versions:** core v3.4, health v2.5, clinical v1.13, coverage v1.3 --- ## 1. Introduction ### 1.1 Purpose and Scope This document specifies how Cascade Protocol health data types are serialized to RDF/Turtle and JSON-LD formats. It serves as the authoritative guide for SDK implementers who need to produce or consume Cascade Protocol data. **This specification covers all Cascade Protocol data types:** **Core Clinical Records (Phase 1):** 1. Medications (`clinical:Medication` and wellness supplements) 2. Conditions (`health:ConditionRecord`) 3. Allergies (`health:AllergyRecord`) 4. Lab Results (`health:LabResultRecord`) 5. Vital Signs (`clinical:VitalSign`) 6. Patient Profile / Demographics (`cascade:PatientProfile`) **Extended Records (Phase 2):** 7. Immunizations (`health:ImmunizationRecord`) 8. Procedures (`clinical:Procedure`) 9. Family History (`health:FamilyHistoryRecord`) 10. Coverage / Insurance (`coverage:InsurancePlan`; legacy `clinical:CoverageRecord`) 11. Wellness Observations (Heart Rate, Blood Pressure, Activity, Sleep, HRV, VO2 Max, Body Measurements) 12. Comprehensive Provenance Model (W3C PROV-O integration) 13. Pod Structure Conventions (cross-reference) Each data type section includes a properties table, annotated Turtle examples, JSON-LD equivalents, provenance patterns, SHACL validation constraints, and multi-system coding guidance where applicable. ### 1.2 Conformance Levels This specification uses a **descriptive-with-normative-examples** approach: - **MUST** (Normative): SDKs MUST produce SHACL-valid output when validated against the published SHACL shapes files (`clinical.shapes.ttl`, `health.shapes.ttl`, `core.shapes.ttl`). The SHACL shapes are the normative validation layer. - **SHOULD** (Recommended): SDKs SHOULD produce output matching the reference Turtle patterns shown in this specification. These examples represent the canonical serialization produced by the reference Swift SDK. - **MAY** (Optional): SDKs MAY include additional properties beyond those listed, provided they do not conflict with the defined vocabulary. The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). ### 1.3 How to Read This Specification Each data type section follows this structure: 1. **Description** -- What the data type represents and its FHIR alignment 2. **Properties Table** -- All predicates with URI, data type, cardinality, and description 3. **Turtle Example** -- Annotated serialization showing every property in context 4. **JSON-LD Equivalent** -- The same record expressed as JSON-LD 5. **Provenance** -- How `cascade:dataProvenance` applies to this type 6. **SHACL Constraints** -- Key validation rules from the shapes files 7. **Multi-System Coding** -- How standard terminology codes (RxNorm, SNOMED, LOINC, ICD-10) are used ### 1.4 Turtle Notation Primer If you are new to RDF, here is a minimal Turtle primer to help you read the examples in this specification. **Triples.** RDF data consists of subject-predicate-object triples. In Turtle syntax: ```turtle . ``` The period (`.`) ends a statement. A semicolon (`;`) continues with the same subject but a new predicate-object pair: ```turtle a clinical:Medication ; # "a" is shorthand for rdf:type clinical:drugName "Metformin" ; # string literal clinical:status "active" ; # string literal health:startDate "2024-01-15T00:00:00Z"^^xsd:dateTime . # typed literal ``` **Prefixes.** Compact URIs use declared prefixes: ```turtle @prefix health: . # Now clinical:drugName expands to https://ns.cascadeprotocol.org/clinical/v1#drugName ``` **Blank nodes.** Anonymous inline resources use square brackets: ```turtle cascade:emergencyContact [ a cascade:EmergencyContact ; cascade:contactName "Jane Doe" ] . ``` **Lists.** Ordered collections use parentheses: ```turtle health:affectsVitalSigns ("heartRate" "bloodPressure") . ``` **Typed literals.** Explicit data types use the `^^` notation: ```turtle cascade:dateOfBirth "1985-03-15"^^xsd:date . cascade:computedAge "40"^^xsd:integer . ``` ### 1.5 Namespace Prefixes All examples in this specification use the following namespace prefix declarations. SDK implementations MUST use these exact namespace URIs. ```turtle @prefix cascade: . @prefix health: . @prefix clinical: . @prefix coverage: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix rxnorm: . @prefix icd10: . @prefix ucum: . @prefix xsd: . @prefix prov: . ``` **IMPORTANT -- LOINC namespace:** The canonical LOINC namespace for this specification is `http://loinc.org/rdf#`. The ontology files and SDK implementations contain historical inconsistency (`https://loinc.org/rdf/` in `health.ttl`, `http://loinc.org/` in `ClinicalRDFSerializer.swift`). This specification establishes `http://loinc.org/rdf#` as the canonical form. Future SDK releases will converge on this namespace. ### 1.6 Provenance Model Overview Every Cascade Protocol resource MUST include a `cascade:dataProvenance` triple that classifies how the data was generated. The provenance taxonomy is defined in `core.ttl` and follows a two-branch hierarchy: ``` cascade:DataProvenance | +-- cascade:ConsumerGenerated # Non-clinical setting | +-- cascade:DeviceGenerated # Wearable/medical device | +-- cascade:SelfReported # Patient-entered data | +-- cascade:ConsumerWellness # Aggregated wellness platform | +-- cascade:ClinicalGenerated # Clinical setting +-- cascade:EHRVerified # Imported from EHR system +-- cascade:ScannedDocument # From scanned/photographed document +-- cascade:AIExtracted # Extracted by AI from unstructured text ``` In Turtle, provenance is expressed as an object property linking to a provenance class: ```turtle cascade:dataProvenance cascade:EHRVerified . ``` Typical mappings by data source: | Data Source | Provenance Class | |---|---| | Epic MyChart / Cerner (HealthKit FHIR) | `cascade:EHRVerified` | | Apple Watch / Fitbit / consumer device | `cascade:DeviceGenerated` | | Patient manual entry in app | `cascade:SelfReported` | | Apple Health aggregated data | `cascade:ConsumerWellness` | | AI extraction from scanned lab report | `cascade:AIExtracted` | ### 1.7 Relationship to SHACL Shapes and Conformance Suite Each data type in this specification has a corresponding SHACL shape that serves as the machine-readable validation contract. The SHACL shapes files are: - `core.shapes.ttl` -- Patient Profile, Address, EmergencyContact, PharmacyInfo, AdvanceDirectives - `clinical.shapes.ttl` -- Medication, Allergy, LabResult, Condition, VitalSign, Immunization, Procedure, MedicationUseEpisode, Supplement - `health.shapes.ttl` -- HealthProfile, VO2MaxStatistics, HRVStatistics, BPStatistics, MetricTrend, ActivitySnapshot, SleepSnapshot, SelfReport - `coverage.shapes.ttl` -- InsurancePlan The shapes use three severity levels: | SHACL Severity | RFC 2119 Mapping | Meaning | |---|---|---| | `sh:Violation` | MUST | Data is invalid without this. Blocks operations. | | `sh:Warning` | SHOULD | Important for completeness. Shown prominently to users. | | `sh:Info` | MAY | Suggested enrichment. Nice to have. | **Validation workflow:** To validate serialized data, load the appropriate shapes file and the data file into a SHACL validator (e.g., Apache Jena, pySHACL, or TopBraid). The validator will report violations, warnings, and informational messages according to the severity levels above. --- ## 2. Medications ### 2.1 Description Medications represent prescription drugs, over-the-counter medications, and other pharmaceutical products. The Cascade Protocol distinguishes between two medication-related types: - **`clinical:Medication`** -- A source record representing a single medication entry from one data source (EHR import, patient entry, pharmacy claim). This is the primary medication serialization type produced by the CLI and SDK converters. Multiple source records may be reconciled into a `MedicationUseEpisode` for longitudinal tracking. - **`clinical:Supplement`** -- A dietary supplement, OTC product, or herbal remedy with explicit regulatory status. Supplements are intentionally separate from medications due to different regulatory status and evidence requirements. **FHIR alignment:** `clinical:Medication` aligns with `fhir:MedicationStatement`. The clinical ontology defines it as a subclass of both `fhir:MedicationStatement` and `prov:Entity`. ### 2.2 Properties Table -- Medication | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Drug Name | `clinical:drugName` | `xsd:string` | MUST (1) | Name of the medication (e.g., "Metformin") | | Status | `clinical:status` | `xsd:string` | MAY (0..1) | FHIR status: `active`, `stopped`, `on-hold`, etc. | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated (see Section 1.6) | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version (format: "major.minor", e.g., "1.3") | | Dosage | `clinical:dosage` | `xsd:string` | MAY (0..1) | Dosage information (e.g., "500mg twice daily") | | Frequency | `clinical:frequency` | `xsd:string` | MAY (0..1) | How often taken (e.g., "twice daily") | | Route | `clinical:route` | `xsd:string` | MAY (0..1) | Route of administration (e.g., "oral") | | Prescriber | `clinical:prescriber` | `xsd:string` | MAY (0..1) | Name of prescribing physician | | Start Date | `health:startDate` | `xsd:dateTime` | MAY (0..1) | When medication was started | | End Date | `health:endDate` | `xsd:dateTime` | MAY (0..1) | When medication was stopped | | RxNorm Code | `clinical:rxNormCode` | (URI) | MAY (0..1) | RxNorm concept URI (e.g., `rxnorm:860975`) | | Notes | `health:notes` | `xsd:string` | MAY (0..1) | Free-text notes | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | | Drug Code | `clinical:drugCode` | (URI) | MAY (0..*) | Drug code URI from any terminology system | | Provenance Class | `clinical:provenanceClass` | `xsd:string` | MUST (1) | Editability class: `healthKitFHIR`, `userTracked`, `pharmacyClaim`, `imported` | | Source FHIR Type | `clinical:sourceFhirResourceType` | `xsd:string` | MAY (0..1) | FHIR resource type: `MedicationRequest`, `MedicationStatement`, `MedicationDispense`, `MedicationAdministration` | | Clinical Intent | `clinical:clinicalIntent` | `xsd:string` | MAY (0..1) | Intent: `reportedUse`, `prescribed`, `dispensed`, `administered` | | Indication | `clinical:indication` | `xsd:string` | MAY (0..1) | Clinical reason for the medication | | Course of Therapy | `clinical:courseOfTherapyType` | `xsd:string` | MAY (0..1) | `acute`, `continuous`, or `unknown` | | As Needed (PRN) | `clinical:asNeeded` | `xsd:boolean` | MAY (0..1) | Whether taken as needed | | Medication Form | `clinical:medicationForm` | `xsd:string` | MAY (0..1) | Physical form: tablet, capsule, liquid, etc. | | Active Ingredient | `clinical:activeIngredient` | `xsd:string` | MAY (0..1) | Primary active ingredient name | | Ingredient Strength | `clinical:ingredientStrength` | `xsd:string` | MAY (0..1) | Strength (e.g., "5 mg per tablet") | | Refills Allowed | `clinical:refillsAllowed` | `xsd:integer` | MAY (0..1) | Number of authorized refills | | Supply Duration | `clinical:supplyDurationDays` | `xsd:integer` | MAY (0..1) | Days each fill is intended to supply | | Dispensed Quantity | `clinical:dispensedQuantity` | `xsd:string` | MAY (0..1) | Quantity per fill (e.g., "90 tablets") | | Prescription Category | `clinical:prescriptionCategory` | `xsd:string` | MAY (0..1) | Context: `community`, `inpatient`, `discharge` | | Medication Class | `health:medicationClass` | `xsd:string` | MAY (0..1) | Therapeutic classification (computed by ClinicalClassifier) | | Affects Vital Signs | `health:affectsVitalSigns` | (List) | MAY (0..1) | Vital signs affected by this medication | ### 2.3 Turtle Example -- Medication (Annotated) ```turtle @prefix health: . @prefix clinical: . @prefix cascade: . @prefix rxnorm: . @prefix xsd: . # A medication record for Metformin imported from Epic MyChart a clinical:Medication ; # --- Required fields --- clinical:drugName "Metformin HCl" ; # Drug name as recorded in EHR cascade:dataProvenance cascade:EHRVerified ; # Imported from verified EHR system cascade:schemaVersion "1.3" ; # Schema version for compatibility # --- Core medication details --- clinical:status "active" ; # FHIR status (active, stopped, etc.) clinical:dosage "1000mg twice daily" ; # Prescribed dosage clinical:frequency "twice daily" ; # How often taken clinical:route "oral" ; # Route of administration clinical:prescriber "Dr. Sarah Chen" ; # Prescribing physician health:startDate "2024-01-15T00:00:00Z"^^xsd:dateTime ; # When started health:notes "Take with meals" ; # Patient instructions # --- Standard coding (RxNorm URI) --- clinical:rxNormCode ; # RxNorm concept for Metformin 1000mg # --- Multi-system drug codes --- clinical:drugCode ; # RxNorm clinical:drugCode ; # SNOMED CT # --- Provenance and source tracking --- clinical:provenanceClass "healthKitFHIR" ; # Read-only EHR import (not editable) clinical:sourceFhirResourceType "MedicationRequest" ; # Originated from a prescription clinical:clinicalIntent "prescribed" ; # Provider ordered this medication health:sourceRecordId "epic-med-12345" ; # FHIR resource ID for traceability # --- FHIR-enriched medication details --- clinical:indication "Type 2 Diabetes Mellitus" ; # Clinical reason for prescribing clinical:courseOfTherapyType "continuous" ; # Ongoing therapy (not short-course) clinical:asNeeded false ; # Fixed schedule, not PRN clinical:medicationForm "tablet" ; # Physical form clinical:activeIngredient "metformin hydrochloride" ; # Active ingredient clinical:ingredientStrength "1000 mg per tablet" ; # Strength per unit # --- Dispensing details --- clinical:refillsAllowed 5 ; # 5 refills authorized clinical:supplyDurationDays 90 ; # 90-day supply per fill clinical:dispensedQuantity "180 tablets" ; # Quantity per fill clinical:prescriptionCategory "community" ; # Community/outpatient prescription # --- Classification metadata (computed) --- health:medicationClass "antidiabetic" ; # Therapeutic class health:affectsVitalSigns ("bloodGlucose") . # Affects blood glucose ``` ### 2.4 JSON-LD Equivalent -- Medication ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890", "@type": "clinical:Medication", "clinical:drugName": "Metformin HCl", "clinical:status": "active", "cascade:dataProvenance": { "@id": "cascade:EHRVerified" }, "cascade:schemaVersion": "1.3", "clinical:dosage": "1000mg twice daily", "clinical:frequency": "twice daily", "clinical:route": "oral", "clinical:prescriber": "Dr. Sarah Chen", "health:startDate": { "@value": "2024-01-15T00:00:00Z", "@type": "xsd:dateTime" }, "clinical:rxNormCode": { "@id": "rxnorm:860975" }, "clinical:provenanceClass": "healthKitFHIR", "clinical:sourceFhirResourceType": "MedicationRequest", "clinical:clinicalIntent": "prescribed", "clinical:indication": "Type 2 Diabetes Mellitus", "clinical:courseOfTherapyType": "continuous", "clinical:asNeeded": false, "clinical:medicationForm": "tablet", "clinical:activeIngredient": "metformin hydrochloride", "clinical:ingredientStrength": "1000 mg per tablet", "clinical:refillsAllowed": 5, "clinical:supplyDurationDays": 90, "clinical:dispensedQuantity": "180 tablets", "clinical:prescriptionCategory": "community" } ``` > **Note on `fhir:status` in JSON-LD:** The SDK's `JSONLDCodable.swift` module auto-injects `"fhir:status": "final"` when a resource has FHIR-aligned additional types (e.g., `fhir:MedicationStatement`). This injection affects JSON-LD output only, not Turtle. Consumers of JSON-LD output SHOULD be aware that `fhir:status` may appear even when not explicitly serialized. ### 2.5 Provenance -- Medications Medication records can originate from multiple sources. The `cascade:dataProvenance` value indicates the source, while `clinical:provenanceClass` controls editability: | Source | `cascade:dataProvenance` | `clinical:provenanceClass` | Editable? | |---|---|---|---| | EHR import (HealthKit FHIR) | `cascade:EHRVerified` | `healthKitFHIR` | No | | Patient manual entry | `cascade:SelfReported` | `userTracked` | Yes | | Pharmacy/PBM claim | `cascade:ClinicalGenerated` | `pharmacyClaim` | No | | Pod sync / file import | `cascade:ClinicalGenerated` | `imported` | No | ### 2.6 SHACL Constraints -- Medications The `clinical:MedicationShape` in `clinical.shapes.ttl` enforces these constraints: | Property | Constraint | Severity | |---|---|---| | `clinical:drugName` | `sh:minCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, `sh:in (cascade:ClinicalGenerated, cascade:EHRVerified, cascade:DeviceGenerated, cascade:PatientReported)` | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `clinical:courseOfTherapyType` | `sh:in ("acute", "continuous", "unknown")` | Warning | | `clinical:provenanceClass` | `sh:in ("healthKitFHIR", "userTracked", "pharmacyClaim", "imported")` | (no severity) | | `clinical:sourceFhirResourceType` | `sh:in ("MedicationRequest", "MedicationStatement", "MedicationDispense", "MedicationAdministration")` | (no severity) | | `clinical:clinicalIntent` | `sh:in ("reportedUse", "prescribed", "dispensed", "administered")` | (no severity) | | `clinical:prescriptionCategory` | `sh:in ("community", "inpatient", "discharge")` | (no severity) | | `clinical:refillsAllowed` | `sh:minInclusive 0`, `xsd:integer` | (no severity) | > **Migration complete (v0.5.6+):** As of cascade-cli v0.5.6 and cascade-agent v1.1.3, the medication type and all its core properties use the `clinical:` namespace: `clinical:Medication`, `clinical:drugName`, `clinical:dosage`, `clinical:route`, `clinical:frequency`, `clinical:status`, `clinical:rxNormCode`. The deprecated `health:MedicationRecord` / `health:medicationName` forms are no longer emitted by any Cascade tool. ### 2.7 Multi-System Coding -- Medications Medications support multiple coding systems for interoperability. The `clinical:drugCode` property accepts URIs from any of these systems: | System | URI Pattern | Example | |---|---|---| | **RxNorm** | `http://www.nlm.nih.gov/research/umls/rxnorm/{code}` | `rxnorm:860975` (Metformin 1000mg) | | **SNOMED CT** | `http://snomed.info/sct/{code}` | `sct:109081006` (Metformin) | | **NDC** | `http://hl7.org/fhir/sid/ndc/{code}` | NDC package code | | **ATC** | `http://www.whocc.no/atc/{code}` | ATC classification code | The `health:rxNormCode` property provides a shorthand for the most common case (RxNorm), but `clinical:drugCode` is the general-purpose multi-system coding property. A medication record MAY include multiple `clinical:drugCode` triples from different systems. ### 2.8 Supplements Supplements use the `clinical:Supplement` type with a distinct set of required fields emphasizing regulatory status: ```turtle a clinical:Supplement ; # --- Required fields --- clinical:supplementName "Vitamin D3" ; # Supplement name clinical:regulatoryStatus "dietarySupplement" ; # REQUIRED: explicit regulatory class clinical:isActive true ; # Currently taking cascade:dataProvenance cascade:SelfReported ; # Patient-entered cascade:schemaVersion "1.3" ; # Schema version # --- Optional details --- clinical:dose "5000 IU" ; # Dosage clinical:frequency "once daily" ; # Frequency clinical:brand "Nature Made" ; # Brand name clinical:form "softgel" ; # Physical form clinical:evidenceStrength "strongEvidence" ; # Evidence level clinical:startDate "2024-06-01T00:00:00Z"^^xsd:dateTime ; # Start date clinical:reasonForUse "Vitamin D deficiency" ; # Why taking it clinical:cascadeUri . # Stable ID ``` #### 2.8.1 Properties Table -- Supplements | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Supplement Name | `clinical:supplementName` | `xsd:string` | MUST (1) | Name of the supplement | | Regulatory Status | `clinical:regulatoryStatus` | `xsd:string` | MUST (1) | Regulatory classification (see values below) | | Is Active | `clinical:isActive` | `xsd:boolean` | MUST (1) | Currently taking | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Dose | `clinical:dose` | `xsd:string` | MAY (0..1) | Dosage amount and unit | | Frequency | `clinical:frequency` | `xsd:string` | MAY (0..1) | How often taken | | Brand | `clinical:brand` | `xsd:string` | MAY (0..1) | Brand name | | Form | `clinical:form` | `xsd:string` | MAY (0..1) | Physical form: `capsule`, `tablet`, `softgel`, `liquid`, `powder`, `gummy`, `spray`, `patch`, `tea`, `tincture`, `other` | | Evidence Strength | `clinical:evidenceStrength` | `xsd:string` | MAY (0..1) | Level of clinical evidence (see values below) | | Start Date | `clinical:startDate` | `xsd:dateTime` | MAY (0..1) | When the patient started taking | | Reason for Use | `clinical:reasonForUse` | `xsd:string` | MAY (0..1) | Why the patient is taking it | | Cascade URI | `clinical:cascadeUri` | (URI) | MAY (0..1) | Stable Cascade identifier URI | **`regulatoryStatus` valid values:** `dietarySupplement`, `otcDrug`, `homeopathic`, `herbalRemedy`, `unknown`. This property is REQUIRED (`sh:Violation`) to prevent implying clinical equivalence with prescription medications. **`evidenceStrength` valid values:** `strongEvidence`, `moderateEvidence`, `limitedEvidence`, `traditionalUse`, `noEvidence`, `unknown`. This classifies the level of clinical evidence supporting the supplement's use for the stated reason. #### 2.8.2 JSON-LD Equivalent -- Supplements ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:b2c3d4e5-f6a7-8901-bcde-f12345678901", "@type": "clinical:Supplement", "clinical:supplementName": "Vitamin D3", "clinical:regulatoryStatus": "dietarySupplement", "clinical:isActive": true, "cascade:dataProvenance": { "@id": "cascade:SelfReported" }, "cascade:schemaVersion": "1.3", "clinical:dose": "5000 IU", "clinical:frequency": "once daily", "clinical:brand": "Nature Made", "clinical:form": "softgel", "clinical:evidenceStrength": "strongEvidence", "clinical:startDate": { "@value": "2024-06-01T00:00:00Z", "@type": "xsd:dateTime" }, "clinical:reasonForUse": "Vitamin D deficiency" } ``` --- ## 3. Conditions ### 3.1 Description Conditions represent medical conditions, diagnoses, and health problems. They are imported from EHR systems (via FHIR `Condition` resources) or manually entered by patients. Conditions are relatively simple categorical data -- they do not require longitudinal episode tracking like medications. **FHIR alignment:** `clinical:Condition` is defined as a subclass of both `fhir:Condition` and `prov:Entity` in the clinical ontology. ### 3.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Condition Name | `health:conditionName` | `xsd:string` | MUST (1) | Name of the condition (e.g., "Type 2 Diabetes Mellitus") | | Status | `health:status` | `xsd:string` | MUST (1) | Condition status: `active`, `resolved`, `remission`, `recurrence`, `inactive` | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Onset Date | `health:onsetDate` | `xsd:dateTime` | MAY (0..1) | When the condition began | | Abatement Date | `health:abatementDate` | `xsd:dateTime` | MAY (0..1) | When the condition resolved | | ICD-10 Code | `health:icd10Code` | (URI) | MAY (0..1) | ICD-10-CM diagnosis code URI | | SNOMED Code | `health:snomedCode` | (URI) | MAY (0..1) | SNOMED CT concept URI | | Notes | `health:notes` | `xsd:string` | MAY (0..1) | Free-text notes | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | | Condition Class | `health:conditionClass` | `xsd:string` | MAY (0..1) | Classification (computed by ClinicalClassifier) | | Monitored Vital Signs | `health:monitoredVitalSigns` | (List) | MAY (0..1) | Vital signs to monitor for this condition | ### 3.3 Turtle Example (Annotated) ```turtle @prefix health: . @prefix cascade: . @prefix sct: . @prefix icd10: . @prefix xsd: . # A condition record for essential hypertension from EHR a health:ConditionRecord ; # --- Required fields --- health:conditionName "Essential Hypertension" ; # Condition name from EHR health:status "active" ; # Currently active condition cascade:dataProvenance cascade:EHRVerified ; # Imported from EHR cascade:schemaVersion "1.3" ; # Schema version # --- Clinical dates --- health:onsetDate "2022-06-15T00:00:00Z"^^xsd:dateTime ; # When diagnosed # --- Standard coding --- health:icd10Code ; # ICD-10 code for essential HTN health:snomedCode ; # SNOMED CT: Essential hypertension # --- Source tracking --- health:sourceRecordId "epic-cond-67890" ; # FHIR Condition resource ID # --- Classification metadata (computed) --- health:conditionClass "cardiovascular" ; # Therapeutic class health:monitoredVitalSigns ("bloodPressure" "heartRate") . # Vitals to monitor ``` ### 3.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:c3d4e5f6-a7b8-9012-cdef-123456789012", "@type": "health:ConditionRecord", "health:conditionName": "Essential Hypertension", "health:status": "active", "cascade:dataProvenance": { "@id": "cascade:EHRVerified" }, "cascade:schemaVersion": "1.3", "health:onsetDate": { "@value": "2022-06-15T00:00:00Z", "@type": "xsd:dateTime" }, "health:icd10Code": { "@id": "icd10:I10" }, "health:snomedCode": { "@id": "sct:59621000" }, "health:sourceRecordId": "epic-cond-67890" } ``` ### 3.5 Provenance -- Conditions | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:EHRVerified` | Conditions from Epic MyChart, Cerner | | Patient entry | `cascade:SelfReported` | Self-reported conditions | | AI extraction | `cascade:AIExtracted` | Conditions extracted from clinical notes | ### 3.6 SHACL Constraints -- Conditions Condition records are typed `health:ConditionRecord`, which health v2.5 defines and `health.shapes.ttl` v1.2 targets with `health:ConditionRecordShape`. That is the shape that applies to the serialization in Section 3.3: | Property | Constraint | Severity | |---|---|---| | `health:conditionName` | `sh:minCount 1`, `sh:maxCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, `sh:maxCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:maxCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `health:status` | `sh:in ("active", "recurrence", "relapse", "inactive", "remission", "resolved")`, `sh:maxCount 1` | Violation | | `health:onsetDate` | `xsd:dateTime`, `sh:maxCount 1` | Violation | | `health:icd10Code`, `health:snomedCode` | `sh:maxCount 1` each | Violation | | `health:conditionClass`, `health:monitoredVitalSigns`, `health:sourceRecordId` | `sh:maxCount 1` each | Violation | > **What changed, and what it means for existing data.** Until health v2.5, `health:ConditionRecord` was emitted but undefined, and no shape targeted it. A condition record therefore validated against nothing and reported `PASS` because zero constraints applied, not because it conformed. It is now actually checked. A condition record that reports a violation on first validation after upgrading has not degraded; it is being checked for the first time. > **On the two class names.** `clinical:Condition` also exists and has its own `clinical:ConditionShape` in `clinical.shapes.ttl`. It is deprecated as of clinical v1.13 in favour of `health:ConditionRecord`, and retained rather than removed because the pod export path still emits it and existing pods contain it. A conforming reader accepts both spellings; a writer should emit `health:ConditionRecord`, which is what every import path already produces. ### 3.7 Multi-System Coding -- Conditions Conditions support two primary coding systems: | System | URI Pattern | Example | |---|---|---| | **ICD-10-CM** | `http://hl7.org/fhir/sid/icd-10-cm/{code}` | `icd10:I10` (Essential hypertension) | | **SNOMED CT** | `http://snomed.info/sct/{code}` | `sct:59621000` (Essential hypertension) | Both codes are emitted as URI references (not string literals) in Turtle to enable linked-data traversal. A condition record MAY include codes from both systems. --- ## 4. Allergies ### 4.1 Description Allergies represent allergic reactions and intolerances to substances including medications, foods, environmental factors, and biologics. They are imported from EHR systems (via FHIR `AllergyIntolerance` resources) or manually entered by patients. **FHIR alignment:** `clinical:Allergy` is defined as a subclass of both `fhir:AllergyIntolerance` and `prov:Entity`. ### 4.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Allergen | `health:allergen` | `xsd:string` | MUST (1) | Name of the allergen or substance | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Category | `health:allergyCategory` | `xsd:string` | MAY (0..1) | Category: `medication`, `food`, `environmental`, `latex`, `contrast`, `insect`, `other` | | Reaction | `health:reaction` | `xsd:string` | MAY (0..1) | Description of allergic reaction | | Severity | `health:allergySeverity` | `xsd:string` | MAY (0..1) | `mild`, `moderate`, `severe`, `life_threatening` | | Onset Date | `health:onsetDate` | `xsd:dateTime` | MAY (0..1) | When the allergy was first identified | | Notes | `health:notes` | `xsd:string` | MAY (0..1) | Free-text notes | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | ### 4.3 Turtle Example (Annotated) ```turtle @prefix health: . @prefix cascade: . @prefix xsd: . # An allergy record for penicillin from EHR a health:AllergyRecord ; # --- Required fields --- health:allergen "Penicillin" ; # Substance causing allergy cascade:dataProvenance cascade:EHRVerified ; # Imported from EHR cascade:schemaVersion "1.3" ; # Schema version # --- Clinical details --- health:allergyCategory "medication" ; # Category of allergen health:reaction "Hives, difficulty breathing" ; # Observed reaction health:allergySeverity "severe" ; # Severity classification health:onsetDate "2015-03-20T00:00:00Z"^^xsd:dateTime ; # When first identified # --- Source tracking --- health:sourceRecordId "epic-allergy-54321" . # FHIR AllergyIntolerance ID ``` ### 4.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:d4e5f6a7-b8c9-0123-defa-234567890123", "@type": "health:AllergyRecord", "health:allergen": "Penicillin", "cascade:dataProvenance": { "@id": "cascade:EHRVerified" }, "cascade:schemaVersion": "1.3", "health:allergyCategory": "medication", "health:reaction": "Hives, difficulty breathing", "health:allergySeverity": "severe", "health:onsetDate": { "@value": "2015-03-20T00:00:00Z", "@type": "xsd:dateTime" }, "health:sourceRecordId": "epic-allergy-54321" } ``` ### 4.5 Provenance -- Allergies | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:EHRVerified` | Allergies documented in medical record | | Patient entry | `cascade:SelfReported` | Self-reported allergies | | Provider report | `cascade:ClinicalGenerated` | Documented during clinical encounter | ### 4.6 SHACL Constraints -- Allergies Allergy records are typed `health:AllergyRecord`, which health v2.5 defines and `health.shapes.ttl` v1.2 targets with `health:AllergyRecordShape`. That is the shape that applies to the serialization in Section 4.3: | Property | Constraint | Severity | |---|---|---| | `health:allergen` | `sh:minCount 1`, `sh:maxCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, `sh:maxCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:maxCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `health:reaction` | `xsd:string`, repeatable (no `sh:maxCount`) | Violation | | `health:allergySeverity` | `sh:in ("mild", "moderate", "severe")`, `sh:maxCount 1` | Violation | | `health:allergyCategory` | `sh:in ("food", "medication", "environment", "biologic")`, `sh:maxCount 1` | Violation | | `health:onsetDate`, `health:sourceRecordId` | `sh:maxCount 1` each | Violation | > **Note on category values:** `health:allergyCategory` is constrained to the FHIR `AllergyIntolerance.category` value set (`food`, `medication`, `environment`, `biologic`), while some SDK enums carry additional values (`latex`, `contrast`, `insect`, `other`). Those extended values are outside the constrained set and now report a violation. Map them onto a FHIR category before writing. > **What changed, and what it means for existing data.** Until health v2.5, `health:AllergyRecord` was emitted but undefined, and no shape targeted it. An allergy record therefore validated against nothing and reported `PASS` because zero constraints applied, not because it conformed. It is now actually checked. An allergy record that reports a violation on first validation after upgrading has not degraded; it is being checked for the first time. > **On the two class names.** `clinical:Allergy` also exists and has its own `clinical:AllergyShape` in `clinical.shapes.ttl`. It is deprecated as of clinical v1.13 in favour of `health:AllergyRecord`, and retained rather than removed because the pod export path still emits it and existing pods contain it. A conforming reader accepts both spellings; a writer should emit `health:AllergyRecord`, which is what every import path already produces. ### 4.7 Multi-System Coding -- Allergies Allergy records MAY include a SNOMED CT code for the allergen substance: | System | URI Pattern | Example | |---|---|---| | **SNOMED CT** | `http://snomed.info/sct/{code}` | `sct:91936005` (Allergy to penicillin) | The SHACL shape allows `clinical:snomedCode` as either a string literal or a URI reference. --- ## 5. Lab Results ### 5.1 Description Lab results represent individual laboratory test observations with a result value, unit, reference range, and interpretation. They are typically imported from EHR systems via FHIR `Observation` resources. Multiple lab results for the same test over time can be aggregated into a `clinical:LabTestSeries` for longitudinal tracking (Phase 2 scope). **FHIR alignment:** `clinical:LabResult` is defined as a subclass of both `fhir:Observation` and `prov:Entity`. ### 5.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Test Name | `health:testName` | `xsd:string` | MUST (1) | Name of the laboratory test | | Result Value | `health:resultValue` | `xsd:string` | MUST (1) | Numeric or string result | | Interpretation | `health:interpretation` | `xsd:string` | MUST (1) | `normal`, `abnormal`, `critical`, `unknown` | | Performed Date | `health:performedDate` | `xsd:dateTime` | MUST (1) | When the test was performed | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Test Code | `health:testCode` | (URI) | MAY (0..1) | LOINC code URI for the test | | Category | `health:labCategory` | `xsd:string` | MAY (0..1) | Lab category (hematology, metabolic, etc.) | | Result Unit | `health:resultUnit` | `xsd:string` | MAY (0..1) | Unit of measurement (UCUM preferred) | | Reference Range | `health:referenceRange` | `xsd:string` | MAY (0..1) | Normal reference range text | | Specimen Type | `health:specimenType` | `xsd:string` | MAY (0..1) | Specimen type (blood, urine, etc.) | | Reported Date | `health:reportedDate` | `xsd:dateTime` | MAY (0..1) | When result was reported/finalized | | Ordering Provider | `health:orderingProvider` | `xsd:string` | MAY (0..1) | Clinician who ordered the test | | Performing Lab | `health:performingLab` | `xsd:string` | MAY (0..1) | Laboratory that ran the test | | Notes | `health:notes` | `xsd:string` | MAY (0..1) | Free-text notes | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | ### 5.3 Turtle Example (Annotated) ```turtle @prefix health: . @prefix cascade: . @prefix loinc: . @prefix xsd: . # A lab result for Hemoglobin A1C from clinical laboratory a health:LabResultRecord ; # --- Required fields --- health:testName "Hemoglobin A1C" ; # Test name health:resultValue "6.8" ; # Result as string health:interpretation "abnormal" ; # Outside normal range health:performedDate "2025-11-15T08:30:00Z"^^xsd:dateTime ; # When specimen was drawn cascade:dataProvenance cascade:EHRVerified ; # From verified EHR cascade:schemaVersion "1.3" ; # Schema version # --- Standard coding --- health:testCode ; # LOINC code for HbA1c # --- Result details --- health:labCategory "metabolic" ; # Lab category health:resultUnit "%" ; # Percentage unit health:referenceRange "4.0-5.6 %" ; # Normal range text health:specimenType "blood" ; # Blood specimen # --- Reporting details --- health:reportedDate "2025-11-16T14:00:00Z"^^xsd:dateTime ; # When result was finalized health:orderingProvider "Dr. Michael Torres" ; # Ordering physician health:performingLab "Quest Diagnostics" ; # Lab that performed test # --- Source tracking --- health:sourceRecordId "epic-lab-98765" . # FHIR Observation ID ``` ### 5.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:e5f6a7b8-c9d0-1234-efab-345678901234", "@type": "health:LabResultRecord", "health:testName": "Hemoglobin A1C", "health:resultValue": "6.8", "health:interpretation": "abnormal", "health:performedDate": { "@value": "2025-11-15T08:30:00Z", "@type": "xsd:dateTime" }, "cascade:dataProvenance": { "@id": "cascade:EHRVerified" }, "cascade:schemaVersion": "1.3", "health:testCode": { "@id": "http://loinc.org/rdf#4548-4" }, "health:labCategory": "metabolic", "health:resultUnit": "%", "health:referenceRange": "4.0-5.6 %", "health:specimenType": "blood", "health:orderingProvider": "Dr. Michael Torres", "health:performingLab": "Quest Diagnostics" } ``` ### 5.5 Provenance -- Lab Results | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:EHRVerified` | Lab results from hospital/clinic EHR | | Patient entry | `cascade:SelfReported` | Self-reported lab values (e.g., home A1C test) | | Device | `cascade:DeviceGenerated` | Point-of-care device readings | ### 5.6 SHACL Constraints -- Lab Results Lab results are typed `health:LabResultRecord`, which health v2.5 defines and `health.shapes.ttl` v1.2 targets with `health:LabResultRecordShape`. That is the shape that applies to the serialization in Section 5.3: | Property | Constraint | Severity | |---|---|---| | `health:testName` | `sh:minCount 1`, `sh:maxCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, `sh:maxCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:maxCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `health:resultValue` | `sh:maxCount 1` | Violation | | `health:resultUnit`, `health:referenceRange` | `xsd:string`, `sh:maxCount 1` each | Violation | | `health:interpretation` | `sh:in ("normal", "high", "low", "abnormal", "critical", "Normal", "High", "Low", "Abnormal", "Critical")`, `sh:maxCount 1` | Violation | | `health:performedDate`, `health:reportedDate` | `xsd:dateTime`, `sh:maxCount 1` each | Violation | | `health:testCode`, `health:labCategory`, `health:specimenType`, `health:orderingProvider`, `health:performingLab`, `health:sourceRecordId` | `sh:maxCount 1` each | Violation | > **`sh:maxCount 1` on `health:resultValue` is the constraint to know about.** A source bundle carrying two same-day results for one test — a fasting glucose of 95 at 07:30 and a post-prandial 310 at 13:00, an entirely routine pairing — can merge into a single subject asserting both values. A query over that subject then returns `"95, 310"`, one string no consumer can interpret. Before this shape existed the file validated clean, because the class was unconstrained. Writers MUST emit one subject per result. > **What changed, and what it means for existing data.** Until health v2.5, `health:LabResultRecord` was emitted but undefined, and no shape targeted it. A lab result therefore validated against nothing and reported `PASS` because zero constraints applied, not because it conformed. It is now actually checked. A record that reports a violation on first validation after upgrading has not degraded; it is being checked for the first time. > **On the two class names.** `clinical:LabResult` also exists and has its own `clinical:LabResultShape` in `clinical.shapes.ttl`. It is deprecated as of clinical v1.13 in favour of `health:LabResultRecord`, and retained rather than removed because the pod export path still emits it and existing pods contain it. A conforming reader accepts both spellings; a writer should emit `health:LabResultRecord`, which is what every import path already produces — and which clinical v1.10 already committed to by correcting `clinical:hasLabResult`'s range to it. ### 5.7 Multi-System Coding -- Lab Results Lab results use LOINC as the primary coding system: | System | URI Pattern | Example | |---|---|---| | **LOINC** | `http://loinc.org/rdf#{code}` | `loinc:4548-4` (Hemoglobin A1C) | The `health:testCode` predicate links to the LOINC URI for the test. See **Appendix B** for a reference table of common LOINC codes used in Cascade Protocol. > **URI construction note:** The canonical LOINC namespace is `http://loinc.org/rdf#`, so `loinc:4548-4` expands to `http://loinc.org/rdf#4548-4`. Historical SDK versions may emit `http://loinc.org/{code}` (without `rdf#`). Consumers SHOULD accept both forms for backward compatibility, but new implementations MUST use the canonical `http://loinc.org/rdf#` form. --- ## 6. Vital Signs (Clinical) ### 6.1 Description Clinical vital signs represent observations taken in a clinical setting (e.g., at a doctor's office, hospital, or clinic). These are distinct from device-measured wellness vitals in `HealthProfile` (see Phase 2) -- clinical vitals carry `cascade:ClinicalGenerated` provenance and include LOINC and SNOMED CT codes for each observation type. **FHIR alignment:** `clinical:VitalSign` is defined as a subclass of both `fhir:Observation` and `prov:Entity`. ### 6.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Vital Type | `clinical:vitalType` | `xsd:string` | MUST (1) | Type code (e.g., `bloodPressureSystolic`, `heartRate`) | | Vital Type Name | `clinical:vitalTypeName` | `xsd:string` | SHOULD (1) | Human-readable name (e.g., "Systolic Blood Pressure") | | Value | `clinical:value` | `xsd:double` | MUST (1) | Numeric observation value | | Unit | `clinical:unit` | `xsd:string` | MUST (1) | Unit of measurement (e.g., "mmHg", "bpm") | | Effective Date | `clinical:effectiveDate` | `xsd:dateTime` | MUST (1) | When measurement was taken | | LOINC Code | `clinical:loincCode` | (URI) | MUST (1) | LOINC observation code URI | | SNOMED Code | `clinical:snomedCode` | (URI) | MUST (1) | SNOMED CT concept URI | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Reference Range Low | `clinical:referenceRangeLow` | `xsd:decimal` | MAY (0..1) | Lower bound of normal range | | Reference Range High | `clinical:referenceRangeHigh` | `xsd:decimal` | MAY (0..1) | Upper bound of normal range | | Interpretation | `clinical:interpretation` | `xsd:string` | MAY (0..1) | Interpretation (normal, high, low, etc.) | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source | | Component Vital Type | `clinical:componentVitalType` | `xsd:string` | MAY (0..1) | For multi-component vitals (e.g., BP systolic/diastolic) | ### 6.3 Turtle Example (Annotated) ```turtle @prefix clinical: . @prefix health: . @prefix cascade: . @prefix sct: . @prefix loinc: . @prefix xsd: . # A systolic blood pressure reading from a clinical encounter a clinical:VitalSign ; # --- Required fields --- clinical:vitalType "bloodPressureSystolic" ; # Vital type code clinical:vitalTypeName "Systolic Blood Pressure" ; # Human-readable name clinical:value 138.0 ; # Numeric value clinical:unit "mmHg" ; # Unit of measurement clinical:effectiveDate "2025-12-01T10:30:00Z"^^xsd:dateTime ; # When measured clinical:loincCode ; # LOINC: Systolic BP clinical:snomedCode ; # SNOMED: Systolic BP cascade:dataProvenance cascade:EHRVerified ; # Clinical measurement cascade:schemaVersion "1.3" ; # Schema version # --- Reference range --- clinical:referenceRangeLow 90.0 ; # Normal lower bound clinical:referenceRangeHigh 120.0 ; # Normal upper bound clinical:interpretation "high" ; # Above normal range # --- Source tracking --- health:sourceRecordId "epic-vital-11111" . # FHIR Observation ID ``` ### 6.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:f6a7b8c9-d0e1-2345-fabc-456789012345", "@type": "clinical:VitalSign", "clinical:vitalType": "bloodPressureSystolic", "clinical:vitalTypeName": "Systolic Blood Pressure", "clinical:value": 138.0, "clinical:unit": "mmHg", "clinical:effectiveDate": { "@value": "2025-12-01T10:30:00Z", "@type": "xsd:dateTime" }, "clinical:loincCode": { "@id": "http://loinc.org/rdf#8480-6" }, "clinical:snomedCode": { "@id": "sct:271649006" }, "cascade:dataProvenance": { "@id": "cascade:EHRVerified" }, "cascade:schemaVersion": "1.3", "clinical:referenceRangeLow": 90.0, "clinical:referenceRangeHigh": 120.0, "clinical:interpretation": "high" } ``` ### 6.5 Provenance -- Vital Signs Clinical vital signs are primarily EHR-sourced: | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:EHRVerified` | Vitals recorded during clinical encounters | | Device in clinical setting | `cascade:ClinicalGenerated` | Medical-grade device in clinic | | Patient-reported | `cascade:SelfReported` | Home BP readings entered manually | ### 6.6 SHACL Constraints -- Vital Signs The `clinical:VitalSignShape` in `clinical.shapes.ttl` enforces: | Property | Constraint | Severity | |---|---|---| | `clinical:vitalType` | `sh:minCount 1`, `sh:in` (see list below) | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `clinical:value` | `sh:maxCount 1` | (no severity) | | `clinical:unit` | `xsd:string`, `sh:maxCount 1` | (no severity) | | `clinical:interpretation` | `xsd:string`, `sh:maxCount 1` | (no severity) | **Valid `vitalType` values:** The SHACL shape accepts both code-style and display-style values: - Code style: `heart_rate`, `blood_pressure`, `respiratory_rate`, `body_temperature`, `oxygen_saturation`, `body_weight`, `body_height`, `bmi` - Display style: `Heart Rate`, `Blood Pressure`, `Respiratory Rate`, `Body Temperature`, `Oxygen Saturation`, `Body Weight`, `Body Height`, `BMI` ### 6.7 Multi-System Coding -- Vital Signs Each vital sign type has both a LOINC observation code and a SNOMED CT concept code. The SDK's `ClinicalVitalType` enum provides these mappings: | Vital Type | LOINC Code | SNOMED CT Code | Unit | |---|---|---|---| | Systolic Blood Pressure | `8480-6` | `271649006` | mmHg | | Diastolic Blood Pressure | `8462-4` | `271650006` | mmHg | | Heart Rate | `8867-4` | `364075005` | bpm | | Respiratory Rate | `9279-1` | `86290005` | breaths/min | | Body Temperature | `8310-5` | `386725007` | degC | | Oxygen Saturation (SpO2) | `2708-6` | `431314004` | % | | Body Weight | `29463-7` | `27113001` | kg | | Body Height | `8302-2` | `50373000` | cm | | BMI | `39156-5` | `60621009` | kg/m2 | Both codes are emitted as URI references in the serialized output. For LOINC: ``. For SNOMED CT: ``. --- ## 7. Patient Profile ### 7.1 Description The Patient Profile represents health-specific patient demographics and identity extensions. It is designed to complement a Solid-compatible `foaf:Agent` identity profile using a two-document model: - **`/profile/card.ttl`** -- General identity: `foaf:givenName`, `foaf:familyName`, `vcard:hasEmail`, `vcard:hasTelephone`, `dct:language`. May be shared with others. - **`/profile/health.ttl`** -- Health demographics: `cascade:PatientProfile` with `cascade:dateOfBirth`, `cascade:biologicalSex`, etc. Always private. The Patient Profile contains health-relevant demographic data that informs clinical calculations, screening eligibility, and care planning. It includes structured sub-resources for emergency contacts, addresses, pharmacy information, and advance directives. **Storage path:** `/profile/health.ttl` ### 7.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Date of Birth | `cascade:dateOfBirth` | `xsd:date` | MUST (1) | Patient date of birth (format: YYYY-MM-DD) | | Biological Sex | `cascade:biologicalSex` | `xsd:string` | MUST (1) | `male`, `female`, `intersex` | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Computed Age | `cascade:computedAge` | `xsd:integer` | SHOULD (1) | Age in years, computed at serialization time | | Age Group | `cascade:ageGroup` | `xsd:string` | SHOULD (1) | `pediatric` (0-17), `young_adult` (18-39), `adult` (40-64), `senior` (65+) | | Gender Identity | `cascade:genderIdentity` | `xsd:string` | SHOULD (0..1) | `woman`, `man`, `non_binary`, `other`, `prefer_not_to_say` | | Emergency Contact | `cascade:emergencyContact` | (Blank node) | SHOULD (0..1) | Link to `cascade:EmergencyContact` | | Address | `cascade:address` | (Blank node) | MAY (0..1) | Link to `cascade:Address` | | Preferred Pharmacy | `cascade:preferredPharmacy` | (Blank node) | MAY (0..1) | Link to `cascade:PharmacyInfo` | | Marital Status | `cascade:maritalStatus` | `xsd:string` | MAY (0..1) | `single`, `married`, `domestic_partnership`, `divorced`, `separated`, `widowed`, `prefer_not_to_say` | | Race/Ethnicity | `cascade:raceEthnicity` | `xsd:string` | MAY (0..*) | Multi-value. See enumerated values below. | | Advance Directives | `cascade:advanceDirectives` | (Blank node) | MAY (0..1) | Link to `cascade:AdvanceDirectives` | | Profile ID | `cascade:profileId` | `xsd:string` | MAY (0..1) | UUID for traceability and deduplication | **Race/Ethnicity values (OMB standard):** `american_indian_alaska_native`, `asian`, `black_african_american`, `hispanic_latino`, `native_hawaiian_pacific_islander`, `white`, `other`, `prefer_not_to_say`. Multiple values MAY be provided as separate triples with the same predicate. ### 7.3 Turtle Example (Annotated) ```turtle @prefix cascade: . @prefix xsd: . @prefix prov: . # Patient Profile stored at /profile/health.ttl <#healthProfile> a cascade:PatientProfile ; # --- Required fields --- cascade:schemaVersion "2.0" ; # Schema version cascade:dataProvenance cascade:SelfReported ; # Patient-entered data # --- Core demographics --- cascade:dateOfBirth "1985-03-15"^^xsd:date ; # Date of birth cascade:biologicalSex "female" ; # For clinical calculations # --- Computed convenience fields --- cascade:computedAge "40"^^xsd:integer ; # Computed at serialization cascade:ageGroup "adult" ; # Derived from DOB # --- Optional demographics --- cascade:genderIdentity "woman" ; # Self-reported gender cascade:maritalStatus "married" ; # Marital status cascade:raceEthnicity "asian" ; # Multi-value (OMB) cascade:raceEthnicity "white" ; # Second race/ethnicity # --- Emergency contact (blank node) --- cascade:emergencyContact [ a cascade:EmergencyContact ; # Typed blank node cascade:contactName "John Doe" ; # Contact name (REQUIRED) cascade:contactRelationship "spouse" ; # Relationship to patient cascade:contactPhone "555-0123" # Phone number (REQUIRED) ] ; # --- Address (blank node with FHIR-aligned properties) --- cascade:address [ a cascade:Address ; # Typed blank node cascade:addressUse "home" ; # Purpose: home, work, temp cascade:addressLine "123 Main Street" ; # Street address line 1 cascade:addressLine "Apt 4B" ; # Street address line 2 cascade:addressCity "Portland" ; # City cascade:addressState "OR" ; # State cascade:addressPostalCode "97201" ; # ZIP code cascade:addressCountry "US" # Country ] ; # --- Preferred pharmacy (blank node) --- cascade:preferredPharmacy [ a cascade:PharmacyInfo ; # Typed blank node cascade:pharmacyName "CVS Pharmacy #1234" ; # Name (REQUIRED) cascade:pharmacyAddress "456 Oak Ave, Portland, OR 97201" ; # Address as string cascade:pharmacyPhone "555-0456" # Phone number ] ; # --- Advance directives (blank node, elevated PHI) --- cascade:advanceDirectives [ a cascade:AdvanceDirectives ; # Typed blank node cascade:hasLivingWill "true"^^xsd:boolean ; # Living will on file cascade:hasPowerOfAttorney "true"^^xsd:boolean ; # Healthcare POA on file cascade:hasDNR "false"^^xsd:boolean ; # No DNR order cascade:advanceDirectiveNotes "Living will updated 2024-01-15" # Notes ] ; # --- Profile identity --- cascade:profileId "A1B2C3D4-E5F6-7890-ABCD-EF1234567890" . # UUID for traceability ``` ### 7.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "#healthProfile", "@type": "cascade:PatientProfile", "cascade:schemaVersion": "2.0", "cascade:dataProvenance": { "@id": "cascade:SelfReported" }, "cascade:dateOfBirth": { "@value": "1985-03-15", "@type": "xsd:date" }, "cascade:biologicalSex": "female", "cascade:computedAge": { "@value": "40", "@type": "xsd:integer" }, "cascade:ageGroup": "adult", "cascade:genderIdentity": "woman", "cascade:maritalStatus": "married", "cascade:raceEthnicity": ["asian", "white"], "cascade:emergencyContact": { "@type": "cascade:EmergencyContact", "cascade:contactName": "John Doe", "cascade:contactRelationship": "spouse", "cascade:contactPhone": "555-0123" }, "cascade:address": { "@type": "cascade:Address", "cascade:addressUse": "home", "cascade:addressLine": ["123 Main Street", "Apt 4B"], "cascade:addressCity": "Portland", "cascade:addressState": "OR", "cascade:addressPostalCode": "97201", "cascade:addressCountry": "US" }, "cascade:preferredPharmacy": { "@type": "cascade:PharmacyInfo", "cascade:pharmacyName": "CVS Pharmacy #1234", "cascade:pharmacyAddress": "456 Oak Ave, Portland, OR 97201", "cascade:pharmacyPhone": "555-0456" }, "cascade:advanceDirectives": { "@type": "cascade:AdvanceDirectives", "cascade:hasLivingWill": true, "cascade:hasPowerOfAttorney": true, "cascade:hasDNR": false, "cascade:advanceDirectiveNotes": "Living will updated 2024-01-15" }, "cascade:profileId": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890" } ``` ### 7.5 Provenance -- Patient Profile Patient profiles are always patient-entered data: | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | Patient entry | `cascade:SelfReported` | Intake forms, manual demographic entry | | EHR import | `cascade:EHRVerified` | Demographics pulled from EHR record | ### 7.6 SHACL Constraints -- Patient Profile The `cascade:PatientProfileShape` in `core.shapes.ttl` enforces: | Property | Constraint | Severity | |---|---|---| | `cascade:dateOfBirth` | `sh:minCount 1`, `xsd:date` | Violation | | `cascade:biologicalSex` | `sh:minCount 1`, `sh:in ("male", "female", "intersex")` | Violation | | `cascade:dataProvenance` | `sh:minCount 1` | Violation | | `cascade:genderIdentity` | `sh:in ("woman", "man", "non_binary", "other", "prefer_not_to_say")` | Warning | | `cascade:emergencyContact` | `sh:class cascade:EmergencyContact` | Warning | | `cascade:ageGroup` | `sh:in ("pediatric", "young_adult", "adult", "senior")` | Info | | `cascade:computedAge` | `xsd:integer`, `sh:minInclusive 0`, `sh:maxInclusive 150` | Info | | `cascade:maritalStatus` | `sh:in ("single", "married", "domestic_partnership", ...)` | Info | | `cascade:raceEthnicity` | `sh:in` (OMB values) | Info | **Sub-resource shapes:** **EmergencyContact** (`cascade:EmergencyContactShape`): | Property | Constraint | Severity | |---|---|---| | `cascade:contactName` | `sh:minCount 1`, `sh:minLength 1` | Violation | | `cascade:contactPhone` | `sh:minCount 1`, `sh:minLength 1` | Violation | | `cascade:contactRelationship` | `sh:in ("spouse", "parent", "child", "sibling", "partner", "friend", "caregiver", "other")` | Warning | **PharmacyInfo** (`cascade:PharmacyInfoShape`): | Property | Constraint | Severity | |---|---|---| | `cascade:pharmacyName` | `sh:minCount 1`, `sh:minLength 1` | Violation | | `cascade:pharmacyAddress` | `xsd:string` | Info | | `cascade:pharmacyPhone` | `xsd:string` | Info | **AdvanceDirectives** (`cascade:AdvanceDirectivesShape`): | Property | Constraint | Severity | |---|---|---| | `cascade:hasLivingWill` | `sh:minCount 1`, `xsd:boolean` | Violation | | `cascade:hasPowerOfAttorney` | `sh:minCount 1`, `xsd:boolean` | Violation | | `cascade:hasDNR` | `sh:minCount 1`, `xsd:boolean` | Violation | | `cascade:advanceDirectiveNotes` | `xsd:string` | Info | ### 7.7 Two-Document Profile Model The Patient Profile follows the Solid WebID Profile pattern with two documents: ``` /profile/ card.ttl <-- General identity (foaf:Agent) health.ttl <-- Health demographics (cascade:PatientProfile) ``` The documents are linked via `rdfs:seeAlso`: ```turtle # In card.ttl: <#me> a foaf:Agent ; foaf:givenName "Jane" ; foaf:familyName "Doe" ; rdfs:seeAlso . # In health.ttl: <#healthProfile> a cascade:PatientProfile ; cascade:dateOfBirth "1985-03-15"^^xsd:date ; cascade:biologicalSex "female" . ``` This separation enables different access control policies: the general profile may be shared with care team members, while the health profile is always private. --- ## 8. Immunizations ### 8.1 Description Immunizations represent vaccination events including the vaccine administered, dose details, manufacturer, lot number, and administration context. They are primarily imported from EHR systems via FHIR `Immunization` resources. **FHIR alignment:** `clinical:Immunization` is defined as a subclass of both `fhir:Immunization` and `prov:Entity` in the clinical ontology. The SDK serializer types the resource as `health:ImmunizationRecord`. ### 8.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Vaccine Name | `health:vaccineName` | `xsd:string` | MUST (1) | Name of the vaccine (e.g., "COVID-19 mRNA Vaccine") | | Administration Date | `health:administrationDate` | `xsd:dateTime` | MUST (1) | When the vaccine was administered | | Status | `health:status` | `xsd:string` | MUST (1) | `completed`, `entered-in-error`, `not-done` | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated (see Section 1.6) | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Vaccine Code | `health:vaccineCode` | `xsd:string` | MAY (0..1) | CVX vaccine code (e.g., "CVX-308") | | Manufacturer | `health:manufacturer` | `xsd:string` | MAY (0..1) | Vaccine manufacturer name | | Lot Number | `health:lotNumber` | `xsd:string` | MAY (0..1) | Vaccine lot number for traceability | | Dose Quantity | `health:doseQuantity` | `xsd:string` | MAY (0..1) | Dose volume (e.g., "0.5 mL") | | Dose Number | `health:doseNumber` | `xsd:integer` | MAY (0..1) | Dose number in multi-dose series (e.g., 1, 2, 3) | | Route | `health:route` | `xsd:string` | MAY (0..1) | Route of administration (e.g., "intramuscular") | | Site | `health:site` | `xsd:string` | MAY (0..1) | Body site (e.g., "Left deltoid") | | Expiration Date | `health:expirationDate` | `xsd:dateTime` | MAY (0..1) | Vaccine expiration date | | Administering Provider | `health:administeringProvider` | `xsd:string` | MAY (0..1) | Name of provider who administered vaccine | | Administering Location | `health:administeringLocation` | `xsd:string` | MAY (0..1) | Facility where vaccine was given | | Notes | `health:notes` | `xsd:string` | MAY (0..1) | Free-text notes | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | ### 8.3 Turtle Example (Annotated) ```turtle @prefix health: . @prefix cascade: . @prefix xsd: . # A COVID-19 vaccination record from EHR a health:ImmunizationRecord ; # --- Required fields --- health:vaccineName "COVID-19 mRNA Vaccine (Pfizer-BioNTech, 2024-2025 Formula)" ; health:administrationDate "2024-10-15T10:00:00Z"^^xsd:dateTime ; health:status "completed" ; # Vaccine was administered cascade:dataProvenance cascade:ClinicalGenerated ; # From EHR import cascade:schemaVersion "1.3" ; # Schema version # --- Vaccine identification --- health:vaccineCode "CVX-308" ; # CVX code for this formulation health:manufacturer "Pfizer-BioNTech" ; # Manufacturer health:lotNumber "FN2487" ; # Lot number for recall tracing # --- Dose details --- health:doseQuantity "0.3 mL" ; # Volume administered health:route "intramuscular" ; # Route of administration health:site "Left deltoid" ; # Body site # --- Administration context --- health:administeringProvider "RN Maria Thompson" ; # Administering nurse/provider health:administeringLocation "Cascade Primary Care" ; # Facility name # --- Notes and source tracking --- health:notes "2024-2025 updated formulation. No adverse reactions observed during 15-min monitoring period." ; health:sourceRecordId "ehr-imm-2024-1015-001" . # FHIR Immunization ID ``` ### 8.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:1mmz-0001-aaaa-bbbb-ccccddddeeee", "@type": "health:ImmunizationRecord", "health:vaccineName": "COVID-19 mRNA Vaccine (Pfizer-BioNTech, 2024-2025 Formula)", "health:administrationDate": { "@value": "2024-10-15T10:00:00Z", "@type": "xsd:dateTime" }, "health:status": "completed", "cascade:dataProvenance": { "@id": "cascade:ClinicalGenerated" }, "cascade:schemaVersion": "1.3", "health:vaccineCode": "CVX-308", "health:manufacturer": "Pfizer-BioNTech", "health:lotNumber": "FN2487", "health:doseQuantity": "0.3 mL", "health:route": "intramuscular", "health:site": "Left deltoid", "health:administeringProvider": "RN Maria Thompson", "health:administeringLocation": "Cascade Primary Care", "health:sourceRecordId": "ehr-imm-2024-1015-001" } ``` ### 8.5 Provenance -- Immunizations Immunization records are primarily clinical: | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:ClinicalGenerated` | Vaccinations from hospital/clinic EHR | | EHR verified | `cascade:EHRVerified` | Verified immunization history from patient portal | | Patient entry | `cascade:SelfReported` | Self-reported vaccinations (e.g., pharmacy walk-in) | ### 8.6 SHACL Constraints -- Immunizations Immunization records are typed `health:ImmunizationRecord`, which health v2.5 defines and `health.shapes.ttl` v1.2 targets with `health:ImmunizationRecordShape`. That is the shape that applies to the serialization in Section 8.3: | Property | Constraint | Severity | |---|---|---| | `health:vaccineName` | `sh:minCount 1`, `sh:maxCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, `sh:maxCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:maxCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `health:status` | `sh:in ("completed", "entered-in-error", "not-done")`, `sh:maxCount 1` | Violation | | `health:administrationDate` | `xsd:dateTime`, `sh:maxCount 1` | Violation | | `health:vaccineCode`, `health:manufacturer`, `health:lotNumber`, `health:doseQuantity`, `health:route`, `health:site`, `health:administeringProvider`, `health:administeringLocation`, `health:sourceRecordId` | `xsd:string`, `sh:maxCount 1` each | Violation | > **What changed, and what it means for existing data.** Until health v2.5, `health:ImmunizationRecord` was emitted but undefined, and no shape targeted it. An immunization record therefore validated against nothing and reported `PASS` because zero constraints applied, not because it conformed. It is now actually checked. A record that reports a violation on first validation after upgrading has not degraded; it is being checked for the first time. > **On the two class names.** `clinical:Immunization` also exists and has its own `clinical:ImmunizationShape` in `clinical.shapes.ttl`. It is deprecated as of clinical v1.13 in favour of `health:ImmunizationRecord`, and retained rather than removed because the pod export path still emits it and existing pods contain it. A conforming reader accepts both spellings; a writer should emit `health:ImmunizationRecord`, which is what every import path already produces. ### 8.7 Multi-System Coding -- Immunizations Immunizations use CVX (CDC Vaccine Codes) as the primary coding system: | System | Format | Example | |---|---|---| | **CVX** | String code (e.g., "CVX-308") | CVX-308 (COVID-19 mRNA, bivalent) | | **CVX** | String code (e.g., "CVX-197") | CVX-197 (Influenza, quadrivalent) | | **CVX** | String code (e.g., "CVX-115") | CVX-115 (Tdap) | The `health:vaccineCode` property stores the CVX code as a string literal. Unlike medications and conditions, immunization codes are not emitted as URI references because no canonical RDF namespace exists for CVX codes. Implementers MAY construct URIs using `http://hl7.org/fhir/sid/cvx/{code}` for linked-data interoperability. --- ## 9. Procedures ### 9.1 Description Procedures represent clinical or surgical procedures performed on the patient. They are imported from EHR systems via FHIR `Procedure` resources and include information about the procedure type, date, performer, body site, and outcome. **FHIR alignment:** `clinical:Procedure` is defined as a subclass of both `fhir:Procedure` and `prov:Entity`. The SDK serializer types the resource as `clinical:Procedure`. ### 9.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Procedure Name | `clinical:procedureName` | `xsd:string` | MUST (1) | Name of the procedure (e.g., "Colonoscopy") | | Status | `clinical:status` | `xsd:string` | MUST (1) | Procedure status (see values below) | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Category | `clinical:category` | `xsd:string` | MAY (0..1) | Procedure category (e.g., "diagnostic", "surgical") | | Performed Date | `clinical:performedDate` | `xsd:dateTime` | MAY (0..1) | When the procedure was performed | | Body Site | `clinical:bodySite` | `xsd:string` | MAY (0..1) | Anatomical site (e.g., "Right knee") | | Performer | `clinical:performer` | `xsd:string` | MAY (0..1) | Name of the performing clinician | | Location | `clinical:location` | `xsd:string` | MAY (0..1) | Facility where procedure was performed | | Outcome | `clinical:outcome` | `xsd:string` | MAY (0..1) | Procedure outcome description | | CPT Code | `clinical:cptCode` | `xsd:string` | MAY (0..1) | CPT procedure code (5 digits) | | SNOMED Code | `sct:code` | `xsd:string` | MAY (0..1) | SNOMED CT concept code | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | **Valid `status` values:** `preparation`, `in-progress`, `not-done`, `on-hold`, `stopped`, `completed`, `entered-in-error`, `unknown`. ### 9.3 Turtle Example (Annotated) ```turtle @prefix clinical: . @prefix health: . @prefix cascade: . @prefix sct: . @prefix xsd: . # A colonoscopy procedure from EHR a clinical:Procedure ; # --- Required fields --- clinical:procedureName "Colonoscopy" ; # Procedure name clinical:status "completed" ; # Successfully completed cascade:dataProvenance cascade:EHRVerified ; # Imported from EHR cascade:schemaVersion "1.3" ; # Schema version # --- Procedure details --- clinical:category "diagnostic" ; # Diagnostic procedure clinical:performedDate "2025-06-15T08:00:00Z"^^xsd:dateTime ; # When performed clinical:bodySite "Colon" ; # Anatomical site clinical:performer "Dr. James Wilson" ; # Performing physician clinical:location "Cascade Surgical Center" ; # Facility # --- Outcome --- clinical:outcome "Normal findings. Two small polyps removed and sent to pathology." ; # --- Standard coding --- sct:code "73761001" ; # SNOMED CT: Colonoscopy clinical:cptCode "45378" ; # CPT: Diagnostic colonoscopy # --- Source tracking --- health:sourceRecordId "ehr-proc-20250615-001" . # FHIR Procedure ID ``` ### 9.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:a1b2c3d4-proc-5678-abcd-ef1234567890", "@type": "clinical:Procedure", "clinical:procedureName": "Colonoscopy", "clinical:status": "completed", "cascade:dataProvenance": { "@id": "cascade:EHRVerified" }, "cascade:schemaVersion": "1.3", "clinical:category": "diagnostic", "clinical:performedDate": { "@value": "2025-06-15T08:00:00Z", "@type": "xsd:dateTime" }, "clinical:bodySite": "Colon", "clinical:performer": "Dr. James Wilson", "clinical:location": "Cascade Surgical Center", "clinical:outcome": "Normal findings. Two small polyps removed and sent to pathology.", "sct:code": "73761001", "clinical:cptCode": "45378", "health:sourceRecordId": "ehr-proc-20250615-001" } ``` ### 9.5 Provenance -- Procedures | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:EHRVerified` | Procedures documented in medical record | | Clinical setting | `cascade:ClinicalGenerated` | Procedure records from clinical encounter | | Patient entry | `cascade:SelfReported` | Self-reported procedures (e.g., outpatient visits) | ### 9.6 SHACL Constraints -- Procedures The `clinical:ProcedureShape` in `clinical.shapes.ttl` enforces: | Property | Constraint | Severity | |---|---|---| | `clinical:procedureName` | `sh:minCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `clinical:status` | `sh:in ("preparation", "in-progress", "not-done", "on-hold", "stopped", "completed", "entered-in-error", "unknown")` | (no severity) | | `clinical:cptCode` | `sh:pattern "^[0-9]{5}$"`, `sh:maxCount 1` | (no severity) | | `clinical:snomedCode` | `sh:maxCount 1` | (no severity) | | `clinical:bodySite` | `xsd:string`, `sh:maxCount 1` | (no severity) | | `clinical:performer` | `xsd:string`, `sh:maxCount 1` | (no severity) | | `clinical:performedDate` | `xsd:dateTime`, `sh:maxCount 1` | (no severity) | ### 9.7 Multi-System Coding -- Procedures Procedures support two primary coding systems: | System | URI Pattern / Format | Example | |---|---|---| | **CPT** | 5-digit string code | `45378` (Diagnostic colonoscopy) | | **SNOMED CT** | String code via `sct:code` | `73761001` (Colonoscopy) | The `clinical:cptCode` property stores CPT codes as 5-digit string literals (validated by SHACL pattern `^[0-9]{5}$`). SNOMED CT codes are emitted via `sct:code` as string literals in the current SDK serializer. Future versions MAY emit SNOMED codes as URI references. --- ## 10. Family History ### 10.1 Description Family history records represent medical conditions in blood relatives that may have implications for the patient's health risk assessment. Each record captures a single condition-relationship pair (e.g., "Father had Type 2 Diabetes"). Family history is typically patient-reported or imported from structured EHR data. **FHIR alignment:** Aligns with `fhir:FamilyMemberHistory`. The SDK serializer types the resource as `health:FamilyHistoryRecord`. ### 10.2 Properties Table | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Relationship | `health:relationship` | `xsd:string` | MUST (1) | Family relationship (see values below) | | Condition | `health:condition` | `xsd:string` | MUST (1) | Name of the condition in the relative | | Is Deceased | `health:isDeceased` | `xsd:boolean` | MUST (1) | Whether the relative is deceased | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Condition Code | `health:conditionCode` | `xsd:string` | MAY (0..1) | ICD-10 or SNOMED code for the condition | | Age at Diagnosis | `health:ageAtDiagnosis` | `xsd:integer` | MAY (0..1) | Age of relative when diagnosed | | Age at Death | `health:ageAtDeath` | `xsd:integer` | MAY (0..1) | Age of relative at death (if deceased) | | Notes | `health:notes` | `xsd:string` | MAY (0..1) | Free-text notes | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source system | | Is First Degree | `health:isFirstDegree` | `xsd:boolean` | SHOULD (1) | Computed: whether relative is first-degree (parent, sibling, child) | | Is Early Onset | `health:isEarlyOnset` | `xsd:boolean` | SHOULD (1) | Computed: whether condition was diagnosed before typical age of onset | **Valid `relationship` values:** `father`, `mother`, `brother`, `sister`, `son`, `daughter`, `maternalGrandmother`, `maternalGrandfather`, `paternalGrandmother`, `paternalGrandfather`, `maternalAunt`, `maternalUncle`, `paternalAunt`, `paternalUncle`, `other`. ### 10.3 Turtle Example (Annotated) ```turtle @prefix health: . @prefix cascade: . @prefix xsd: . # Father with early-onset Type 2 Diabetes a health:FamilyHistoryRecord ; # --- Required fields --- health:relationship "father" ; # Family relationship health:condition "Type 2 Diabetes Mellitus" ; # Condition in relative health:isDeceased false ; # Relative is living cascade:dataProvenance cascade:SelfReported ; # Patient-entered cascade:schemaVersion "1.3" ; # Schema version # --- Clinical detail --- health:conditionCode "E11" ; # ICD-10 code health:ageAtDiagnosis 45 ; # Diagnosed at age 45 # --- Notes --- health:notes "Father manages with metformin and diet. Well controlled." ; # --- Computed classification flags --- health:isFirstDegree true ; # Father is first-degree relative health:isEarlyOnset true . # Onset before age 50 for diabetes ``` ### 10.4 JSON-LD Equivalent ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:f4m1-h1st-aaaa-bbbb-ccccddddeeee", "@type": "health:FamilyHistoryRecord", "health:relationship": "father", "health:condition": "Type 2 Diabetes Mellitus", "health:isDeceased": false, "cascade:dataProvenance": { "@id": "cascade:SelfReported" }, "cascade:schemaVersion": "1.3", "health:conditionCode": "E11", "health:ageAtDiagnosis": 45, "health:notes": "Father manages with metformin and diet. Well controlled.", "health:isFirstDegree": true, "health:isEarlyOnset": true } ``` ### 10.5 Provenance -- Family History | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | Patient entry | `cascade:SelfReported` | Patient-entered family medical history | | EHR import | `cascade:EHRVerified` | Family history from structured EHR data | | AI extraction | `cascade:AIExtracted` | Extracted from clinical notes | ### 10.6 SHACL Constraints -- Family History `health:FamilyHistoryRecord` is defined in health v2.5 and targeted by `health:FamilyHistoryRecordShape` in `health.shapes.ttl` v1.2. It enforces: | Property | Constraint | Severity | |---|---|---| | `health:conditionName` | `sh:minCount 1`, `sh:maxCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `clinical:relationship` | `sh:minCount 1`, `sh:maxCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, `sh:maxCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:maxCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `health:onsetAge` | `xsd:integer`, `sh:minInclusive 0`, `sh:maxInclusive 130`, `sh:maxCount 1` | Violation | | `health:sourceRecordId` | `xsd:string`, `sh:maxCount 1` | Violation | > **UNRESOLVED: this section and the ratified shape disagree, and the disagreement is stated here rather than hidden.** > > The shape names the condition with `health:conditionName`, the relationship with `clinical:relationship`, and the relative's age at onset with `health:onsetAge`. Sections 10.2 and 10.3 above use `health:condition`, `health:relationship` and `health:ageAtDiagnosis`, which is what the SDK serializer emits. Of the twelve properties in the 10.2 table, four are defined vocabulary (`cascade:dataProvenance`, `cascade:schemaVersion`, `health:notes`, `health:sourceRecordId`). The other eight — `health:relationship`, `health:condition`, `health:isDeceased`, `health:conditionCode`, `health:ageAtDiagnosis`, `health:ageAtDeath`, `health:isFirstDegree`, `health:isEarlyOnset` — are defined in no Cascade ontology. > > The example in Section 10.3, validated verbatim against `health.shapes.ttl` v1.2, reports two violations: no `health:conditionName` and no `clinical:relationship`. > > **Sections 10.2 and 10.3 have deliberately not been rewritten to match the shape.** They describe what implementations currently emit, and changing the documented serialization would assert a format no implementation produces. Reconciling the two spellings — settling on one and moving the emitters onto it — is a vocabulary decision that has not been taken. Until it is: the shape is authoritative for validation, and a family history record written exactly as Section 10.3 shows will report violations. Treat this section as the one place in this specification where the documented serialization and the ratified shape are known to be out of step. ### 10.7 Clinical Significance -- Early Onset and First-Degree Flags The `health:isFirstDegree` and `health:isEarlyOnset` flags are computed by the SDK at serialization time: - **`isFirstDegree`** is `true` when the relationship is `father`, `mother`, `brother`, `sister`, `son`, or `daughter`. First-degree relatives share approximately 50% of genetic material and have the strongest predictive value for hereditary risk. - **`isEarlyOnset`** is `true` when the age at diagnosis falls below the condition-specific threshold for early onset. For example, colorectal cancer diagnosed before age 50, or coronary artery disease before age 55 in males / 65 in females. These flags enable AI agents and clinical decision support tools to quickly identify high-risk family history patterns without requiring condition-specific medical knowledge in the consumer application. --- ## 11. Coverage / Insurance ### 11.1 Description Coverage records represent a patient's health insurance plans. The Cascade Protocol supports two types for insurance data, reflecting an ongoing migration: - **`clinical:CoverageRecord`** (deprecated) -- The original coverage type used by the SDK's `ClinicalRDFSerializer`. Records imported from EHR systems via FHIR `Coverage` resources use this type. - **`coverage:InsurancePlan`** (preferred) -- The standardized type from the dedicated coverage vocabulary (`coverage.ttl`). This is the target type for new implementations and provides a unified model for both patient-reported and EHR-imported insurance data. **Migration guidance:** Existing data using `clinical:CoverageRecord` remains valid. New implementations SHOULD use `coverage:InsurancePlan`. The coverage vocabulary unifies `checkup:InsuranceInfo` (patient-reported, Layer 3) and `clinical:CoverageRecord` (EHR-imported, Layer 2) into a single Layer 2 domain model. **FHIR alignment:** Both types align with `fhir:Coverage`. The `coverage:InsurancePlan` class is defined as a subclass of `prov:Entity` with `rdfs:seeAlso fhir:Coverage`. **Storage path:** `/coverage/plans/{plan-id}.ttl` (recommended for `coverage:InsurancePlan`) ### 11.2 Properties Table -- CoverageRecord (Legacy) | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Provider Name | `clinical:providerName` | `xsd:string` | MUST (1) | Insurance company name | | Member ID | `clinical:memberId` | `xsd:string` | MUST (1) | Policy member/subscriber ID | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Group Number | `clinical:groupNumber` | `xsd:string` | MAY (0..1) | Employer group number | | Plan Name | `clinical:planName` | `xsd:string` | MAY (0..1) | Insurance plan name | | Plan Type | `clinical:planType` | `xsd:string` | MAY (0..1) | `ppo`, `hmo`, `epo`, `pos`, `hdhp`, etc. | | Coverage Type | `clinical:coverageType` | `xsd:string` | MAY (0..1) | `primary`, `secondary`, `dental`, `vision` | | Relationship | `clinical:relationship` | `xsd:string` | MAY (0..1) | Subscriber relationship: `self`, `spouse`, `child`, `other` | | Effective Period Start | `clinical:effectivePeriodStart` | `xsd:dateTime` | MAY (0..1) | Coverage start date | | Payor Name | `clinical:payorName` | `xsd:string` | MAY (0..1) | Payor organization name | | Subscriber ID | `clinical:subscriberId` | `xsd:string` | MAY (0..1) | Subscriber identifier | | Source Record ID | `health:sourceRecordId` | `xsd:string` | MAY (0..1) | FHIR resource ID from source | ### 11.3 Properties Table -- InsurancePlan (Preferred) | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Provider Name | `coverage:providerName` | `xsd:string` | MUST (1) | Insurance company name | | Member ID | `coverage:memberId` | `xsd:string` | MUST (1) | Policy member ID | | Coverage Type | `coverage:coverageType` | `xsd:string` | MUST (1) | `primary`, `secondary`, `dental`, `vision` | | Data Provenance | `cascade:dataProvenance` | (Object) | MUST (1) | How data was generated | | Schema Version | `cascade:schemaVersion` | `xsd:string` | MUST (1) | Schema version | | Group Number | `coverage:groupNumber` | `xsd:string` | SHOULD (0..1) | Employer group number | | Plan Type | `coverage:planType` | `xsd:string` | SHOULD (0..1) | `hmo`, `ppo`, `epo`, `pos`, `hdhp`, `medicare`, `medicaid`, `tricare`, `other` | | Effective Start | `coverage:effectiveStart` | `xsd:date` | SHOULD (0..1) | Coverage start date | | Subscriber Relationship | `coverage:subscriberRelationship` | `xsd:string` | SHOULD (0..1) | `self`, `spouse`, `child`, `parent`, `other` | | Plan Name | `coverage:planName` | `xsd:string` | MAY (0..1) | Insurance plan name | | Effective End | `coverage:effectiveEnd` | `xsd:date` | MAY (0..1) | Coverage end date | | Subscriber ID | `coverage:subscriberId` | `xsd:string` | MAY (0..1) | Subscriber identifier | | Subscriber Name | `coverage:subscriberName` | `xsd:string` | MAY (0..1) | Subscriber full name | | Rx BIN | `coverage:rxBin` | `xsd:string` | MAY (0..1) | Pharmacy benefit BIN | | Rx PCN | `coverage:rxPcn` | `xsd:string` | MAY (0..1) | Pharmacy benefit PCN | | Rx Group | `coverage:rxGroup` | `xsd:string` | MAY (0..1) | Pharmacy benefit group | ### 11.4 Turtle Example -- CoverageRecord (Legacy) ```turtle @prefix clinical: . @prefix health: . @prefix cascade: . @prefix xsd: . # Insurance coverage from EHR import (legacy CoverageRecord type) a clinical:CoverageRecord ; # --- Required fields --- clinical:providerName "Blue Cross Blue Shield" ; # Insurance company clinical:memberId "BCBS-AR-2020-78452" ; # Member ID cascade:dataProvenance cascade:ClinicalGenerated ; # From EHR import cascade:schemaVersion "1.3" ; # Schema version # --- Plan details --- clinical:groupNumber "GRP-98765" ; # Employer group clinical:planName "Blue PPO Select" ; # Plan name clinical:planType "ppo" ; # Plan type clinical:coverageType "primary" ; # Primary coverage clinical:relationship "self" ; # Patient is subscriber # --- Coverage period --- clinical:effectivePeriodStart "2020-01-01T00:00:00Z"^^xsd:dateTime ; # --- Payor and subscriber --- clinical:payorName "Blue Cross Blue Shield of Oregon" ; clinical:subscriberId "BCBS-AR-2020-78452" ; # --- Source tracking --- health:sourceRecordId "ehr-coverage-2020-0101-001" . # FHIR Coverage ID ``` ### 11.5 Turtle Example -- InsurancePlan (Preferred) ```turtle @prefix coverage: . @prefix cascade: . @prefix xsd: . # Insurance plan using the preferred coverage vocabulary a coverage:InsurancePlan ; # --- Required fields --- coverage:providerName "Blue Cross Blue Shield" ; # Insurance company coverage:memberId "BCBS-AR-2020-78452" ; # Member ID coverage:coverageType "primary" ; # Primary coverage cascade:dataProvenance cascade:SelfReported ; # Patient-entered cascade:schemaVersion "1.3" ; # Schema version # --- Plan identification --- coverage:groupNumber "GRP-98765" ; # Employer group coverage:planName "Blue PPO Select" ; # Plan name coverage:planType "ppo" ; # Plan type # --- Coverage period --- coverage:effectiveStart "2020-01-01"^^xsd:date ; # Start date (xsd:date) coverage:subscriberRelationship "self" ; # Patient is subscriber # --- Subscriber details --- coverage:subscriberId "BCBS-AR-2020-78452" ; # Subscriber ID coverage:subscriberName "Alex Rivera" ; # Subscriber name # --- Pharmacy benefits --- coverage:rxBin "004336" ; # Rx BIN coverage:rxPcn "ADV" ; # Rx PCN coverage:rxGroup "RX9876" . # Rx Group ``` ### 11.6 JSON-LD Equivalent -- InsurancePlan ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "urn:uuid:d1e2f3a4-b5c6-7890-plan-123456789abc", "@type": "coverage:InsurancePlan", "coverage:providerName": "Blue Cross Blue Shield", "coverage:memberId": "BCBS-AR-2020-78452", "coverage:coverageType": "primary", "cascade:dataProvenance": { "@id": "cascade:SelfReported" }, "cascade:schemaVersion": "1.3", "coverage:groupNumber": "GRP-98765", "coverage:planName": "Blue PPO Select", "coverage:planType": "ppo", "coverage:effectiveStart": { "@value": "2020-01-01", "@type": "xsd:date" }, "coverage:subscriberRelationship": "self", "coverage:subscriberId": "BCBS-AR-2020-78452", "coverage:subscriberName": "Alex Rivera", "coverage:rxBin": "004336", "coverage:rxPcn": "ADV", "coverage:rxGroup": "RX9876" } ``` ### 11.7 Provenance -- Coverage | Source | `cascade:dataProvenance` | Typical Use | |---|---|---| | EHR import | `cascade:ClinicalGenerated` | Coverage from hospital/clinic EHR | | Patient entry | `cascade:SelfReported` | Patient-entered insurance info (intake forms) | | EHR verified | `cascade:EHRVerified` | Verified coverage from patient portal | ### 11.8 SHACL Constraints -- Coverage The `coverage:InsurancePlanShape` in `coverage.shapes.ttl` enforces: | Property | Constraint | Severity | |---|---|---| | `coverage:providerName` | `sh:minCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `coverage:memberId` | `sh:minCount 1`, `sh:minLength 1`, `xsd:string` | Violation | | `coverage:coverageType` | `sh:minCount 1`, `sh:in ("primary", "secondary", "dental", "vision")` | Violation | | `cascade:dataProvenance` | `sh:minCount 1`, valid provenance class | Violation | | `cascade:schemaVersion` | `sh:minCount 1`, `sh:pattern "^[0-9]+\\.[0-9]+$"` | Violation | | `coverage:groupNumber` | `xsd:string`, `sh:maxCount 1` | Warning | | `coverage:planType` | `sh:in ("hmo", "ppo", "epo", "pos", "hdhp", "medicare", "medicaid", "tricare", "other")` | Warning | | `coverage:effectiveStart` | `xsd:date`, `sh:maxCount 1` | Warning | | `coverage:subscriberRelationship` | `sh:in ("self", "spouse", "child", "parent", "other")` | Warning | | `coverage:planName` | `xsd:string`, `sh:maxCount 1` | Info | | `coverage:effectiveEnd` | `xsd:date`, `sh:maxCount 1` | Info | | `coverage:subscriberId` | `xsd:string`, `sh:maxCount 1` | Info | | `coverage:subscriberName` | `xsd:string`, `sh:maxCount 1` | Info | | `coverage:rxBin` | `xsd:string`, `sh:maxCount 1` | Info | | `coverage:rxPcn` | `xsd:string`, `sh:maxCount 1` | Info | | `coverage:rxGroup` | `xsd:string`, `sh:maxCount 1` | Info | > **Note on deprecation, stated concretely.** The legacy `clinical:CoverageRecord` type is targeted by no SHACL shape at all, so a validator reports `PASS` on a legacy coverage record because zero constraints apply, not because it conforms — a record missing `providerName`, `memberId` and `coverageType` entirely passes. `coverage:InsurancePlan` is checked by `coverage:InsurancePlanShape`. That difference, not stylistic preference, is why new implementations SHOULD use the preferred type. The reference patient pod's insurance record uses `coverage:InsurancePlan`; note that `coverage:effectiveStart` is `xsd:date` where the legacy `clinical:effectivePeriodStart` was `xsd:dateTime`, so migrating a record means narrowing that value, not just renaming the predicate. > > `coverage:` v1.3 has no property corresponding to the legacy `clinical:payorName`: it maps `coverage:providerName` to `fhir:Coverage.payor`, and that property is `sh:maxCount 1`. A record carrying both a provider name and a distinct payor organisation name has no lossless single-predicate target in `coverage:`. --- ## 12. Wellness Observations ### 12.1 Description Wellness observations represent consumer-device-generated health data from wearables and personal health devices. Unlike clinical vital signs (Section 6), which are point-in-time observations from clinical encounters, wellness observations use a **time-series container pattern** where a top-level data type (e.g., `health:HeartRateData`) contains a history list of daily snapshots. The wellness data model is designed for high-frequency, device-generated data that accumulates over days, weeks, and months. Each data domain has its own file within the `/wellness/` directory of the Cascade Pod. **Provenance:** Wellness observations typically carry `cascade:DeviceGenerated` (direct from wearable) or `cascade:ConsumerWellness` (aggregated from a platform like Apple Health). ### 12.2 Time-Series Container Pattern All wellness observations follow this structural pattern: ```turtle <#container-id> a health:DataType ; health:historyProperty ( [ a health:SnapshotType ; cascade:date "2026-01-20T00:00:00Z"^^xsd:dateTime ; ... ] [ a health:SnapshotType ; cascade:date "2026-01-21T00:00:00Z"^^xsd:dateTime ; ... ] [ a health:SnapshotType ; cascade:date "2026-01-22T00:00:00Z"^^xsd:dateTime ; ... ] ) . ``` The container resource uses a **fragment identifier** (e.g., `<#heart-rate>`) rather than a UUID, because it represents an aggregate rather than a discrete clinical event. Daily snapshots are anonymous **blank nodes** within an RDF list. Each daily snapshot includes: - `cascade:date` -- The date of the observation - Metric-specific properties -- Values, counts, and quality indicators - `prov:wasGeneratedBy` -- Inline provenance activity identifying the source device ### 12.3 Heart Rate Heart rate data is stored as `health:HeartRateData` with separate history lists for resting and walking heart rates. #### 12.3.1 Properties Table -- HeartRateData Container | Property | Predicate URI | Type | Description | |---|---|---|---| | Resting HR (latest) | `health:restingHeartRate` | (Blank node) | Most recent resting HR reading | | Walking HR (latest) | `health:walkingHeartRate` | (Blank node) | Most recent walking HR reading | | Resting HR History | `health:restingHeartRateHistory` | (RDF List) | Daily resting HR readings | | Walking HR History | `health:walkingHeartRateHistory` | (RDF List) | Daily walking HR readings | #### 12.3.2 Properties Table -- DailyVitalReading (Heart Rate) | Property | Predicate URI | XSD Type | Description | |---|---|---|---| | SNOMED Code | `fhir:code` | (URI) | `sct:364075005` (Heart rate) | | LOINC Code | `cascade:loincCode` | (URI) | `loinc:40443-4` (resting) or `loinc:89270-3` (walking) | | Value | `fhir:valueQuantity / fhir:value` | `xsd:double` | Heart rate in bpm | | Unit | `fhir:valueQuantity / fhir:unit` | `xsd:string` | "bpm" | | Unit System | `fhir:valueQuantity / fhir:system` | (URI) | `ucum:` | | Date | `cascade:date` | `xsd:dateTime` | Date of reading | | Sample Count | `cascade:sampleCount` | `xsd:integer` | Number of samples aggregated | | Provenance | `prov:wasGeneratedBy` | (Blank node) | Source device activity | #### 12.3.3 Turtle Example -- Heart Rate ```turtle @prefix cascade: . @prefix health: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix ucum: . @prefix xsd: . @prefix prov: . # Heart rate data container with daily resting heart rate history <#heart-rate> a health:HeartRateData ; health:restingHeartRateHistory ( [ a health:DailyVitalReading ; fhir:code sct:364075005 ; cascade:loincCode loinc:40443-4 ; fhir:valueQuantity [ fhir:value "68"^^xsd:double ; fhir:unit "bpm" ; fhir:system ucum: ] ; cascade:date "2026-01-20T07:00:00Z"^^xsd:dateTime ; cascade:sampleCount "142"^^xsd:integer ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] [ a health:DailyVitalReading ; fhir:code sct:364075005 ; cascade:loincCode loinc:40443-4 ; fhir:valueQuantity [ fhir:value "65"^^xsd:double ; fhir:unit "bpm" ; fhir:system ucum: ] ; cascade:date "2026-01-21T07:00:00Z"^^xsd:dateTime ; cascade:sampleCount "156"^^xsd:integer ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] ) . ``` ### 12.4 Blood Pressure (Device) Device blood pressure data uses `health:BloodPressureData` with the `health:bloodPressureHistory` list. Each reading is a `fhir:Observation` with two `fhir:component` entries for systolic and diastolic values. > **Open question: blood-pressure readings are the one wellness history entry not typed with a `health:Daily*` class.** Heart rate readings are `health:DailyVitalReading`, activity snapshots are `health:DailyActivitySnapshot` and sleep snapshots are `health:DailySleepSnapshot`, each with a shape as of health v2.5. Blood-pressure readings are `fhir:Observation`, which no Cascade shape targets, so they remain unchecked while their three siblings are checked — a difference that only became visible once the sibling shapes existed. Whether these readings should be retyped `health:DailyVitalReading` (which the two-component structure would have to accommodate) or whether blood pressure warrants its own class is an open vocabulary question. This specification documents the current serialization; it is not a recommendation that the asymmetry is correct. #### 12.4.1 Turtle Example -- Blood Pressure ```turtle @prefix cascade: . @prefix health: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix ucum: . @prefix xsd: . @prefix prov: . # Home blood pressure data from Omron BP monitor <#blood-pressure> a health:BloodPressureData ; health:bloodPressureHistory ( [ a fhir:Observation ; fhir:code sct:75367002 ; # Blood pressure (observable) cascade:loincCode loinc:85354-9 ; # Blood pressure panel fhir:component ( [ fhir:code sct:271649006 ; # Systolic BP fhir:valueQuantity [ fhir:value "132"^^xsd:double ; fhir:unit "mmHg" ; fhir:system ucum: ; fhir:code "mm[Hg]" ] ] [ fhir:code sct:271650006 ; # Diastolic BP fhir:valueQuantity [ fhir:value "82"^^xsd:double ; fhir:unit "mmHg" ; fhir:system ucum: ; fhir:code "mm[Hg]" ] ] ) ; fhir:effectiveDateTime "2026-01-20T07:30:00Z"^^xsd:dateTime ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "Omron Evolv" ] ] ) . ``` ### 12.5 Activity Activity data uses `health:ActivityData` with the `health:dailyActivityHistory` list. Each snapshot captures steps, active energy, exercise minutes, and stand hours. #### 12.5.1 Properties Table -- DailyActivitySnapshot | Property | Predicate URI | XSD Type | Description | |---|---|---|---| | Date | `cascade:date` | `xsd:dateTime` | Date of activity | | Steps | `health:steps` | `xsd:integer` | Total steps for the day | | Active Energy | `health:activeEnergyKcal` | `xsd:decimal` | Active calories burned (kcal) | | Exercise Minutes | `health:exerciseMinutes` | `xsd:integer` | Minutes of exercise | | Stand Hours | `health:standHours` | `xsd:integer` | Hours with standing activity | | Provenance | `prov:wasGeneratedBy` | (Blank node) | Source device activity | #### 12.5.2 Turtle Example -- Activity ```turtle @prefix cascade: . @prefix health: . @prefix xsd: . @prefix prov: . # Activity data from Apple Watch <#activity> a health:ActivityData ; health:dailyActivityHistory ( [ a health:DailyActivitySnapshot ; cascade:date "2026-01-20T00:00:00Z"^^xsd:dateTime ; health:steps "7842"^^xsd:integer ; health:activeEnergyKcal "312"^^xsd:decimal ; health:exerciseMinutes "22"^^xsd:integer ; health:standHours "10"^^xsd:integer ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] [ a health:DailyActivitySnapshot ; cascade:date "2026-01-21T00:00:00Z"^^xsd:dateTime ; health:steps "9234"^^xsd:integer ; health:activeEnergyKcal "385"^^xsd:decimal ; health:exerciseMinutes "35"^^xsd:integer ; health:standHours "11"^^xsd:integer ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] ) . ``` ### 12.6 Sleep Sleep data uses `health:SleepData` with the `health:dailySleepHistory` list. Each snapshot captures duration and quality. #### 12.6.1 Properties Table -- DailySleepSnapshot | Property | Predicate URI | XSD Type | Description | |---|---|---|---| | Date | `cascade:date` | `xsd:dateTime` | Date of sleep period | | Duration | `health:durationHours` | `xsd:decimal` | Total sleep duration in hours | | Sleep Quality | `health:sleepQuality` | (URI) | Quality: `health:Excellent`, `health:Good`, `health:Fair`, `health:Poor` | | Provenance | `prov:wasGeneratedBy` | (Blank node) | Source device activity | #### 12.6.2 Turtle Example -- Sleep ```turtle @prefix cascade: . @prefix health: . @prefix xsd: . @prefix prov: . # Sleep data from Apple Watch <#sleep> a health:SleepData ; health:dailySleepHistory ( [ a health:DailySleepSnapshot ; cascade:date "2026-01-20T00:00:00Z"^^xsd:dateTime ; health:durationHours "7.2"^^xsd:decimal ; health:sleepQuality health:Good ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] [ a health:DailySleepSnapshot ; cascade:date "2026-01-21T00:00:00Z"^^xsd:dateTime ; health:durationHours "6.8"^^xsd:decimal ; health:sleepQuality health:Fair ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] ) . ``` ### 12.7 Heart Rate Variability (HRV) HRV data uses `health:HRVData` with both a latest reading (`health:heartRateVariability`) and a history list (`health:hrvHistory`). The SDK also computes `health:HRVStatistics` for period-based summaries. #### 12.7.1 Properties Table -- HRVStatistics | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Mean HRV | `health:hrvMean` | `xsd:double` | MUST (1) | Average SDNN in ms | | Sample Count | `health:hrvSampleCount` | `xsd:integer` | MUST (1) | Number of readings | | Period Start | `health:periodStart` | `xsd:dateTime` | MUST (1) | Start of measurement window | | Period End | `health:periodEnd` | `xsd:dateTime` | MUST (1) | End of measurement window | | Median | `health:hrvMedian` | `xsd:double` | MAY (0..1) | Median SDNN | | Std Dev | `health:hrvStdDev` | `xsd:double` | MAY (0..1) | Standard deviation | | Minimum | `health:hrvMin` | `xsd:double` | MAY (0..1) | Lowest reading | | Maximum | `health:hrvMax` | `xsd:double` | MAY (0..1) | Highest reading | | Trend Direction | `health:hrvTrendDirection` | `xsd:string` | MAY (0..1) | `improving`, `declining`, `stable`, `unknown` | #### 12.7.2 Turtle Example -- HRV ```turtle @prefix cascade: . @prefix health: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix ucum: . @prefix xsd: . @prefix prov: . # HRV data with statistics and daily history <#hrv> a health:HRVData ; health:heartRateVariability [ a health:VitalSignReading ; fhir:code sct:80404004 ; cascade:loincCode loinc:80404-7 ; fhir:valueQuantity [ fhir:value "42.5"^^xsd:double ; fhir:unit "ms" ; fhir:system ucum: ] ; cascade:date "2026-02-18T07:00:00Z"^^xsd:dateTime ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] ; health:hasHRVStatistics [ a health:HRVStatistics ; health:hrvMean "38.4"^^xsd:double ; health:hrvSampleCount "28"^^xsd:integer ; health:periodStart "2026-01-20T00:00:00Z"^^xsd:dateTime ; health:periodEnd "2026-02-18T00:00:00Z"^^xsd:dateTime ; health:hrvMedian "37.0"^^xsd:double ; health:hrvMin "25.0"^^xsd:double ; health:hrvMax "52.0"^^xsd:double ; health:hrvTrendDirection "stable" ] . ``` ### 12.8 VO2 Max VO2 Max data uses a `health:VO2MaxStatistics` container within the body measurements section of the health profile. It includes a fitness classification based on age and sex. #### 12.8.1 Properties Table -- VO2MaxStatistics | Property | Predicate URI | XSD Type | Cardinality | Description | |---|---|---|---|---| | Mean VO2 Max | `health:vo2Mean` | `xsd:double` | MUST (1) | Average VO2 Max (mL/kg/min) | | Sample Count | `health:vo2SampleCount` | `xsd:integer` | MUST (1) | Number of measurements | | Fitness Classification | `health:fitnessClassification` | `xsd:string` | MUST (1) | `veryPoor`, `poor`, `fair`, `good`, `excellent`, `superior` | | Period Start | `health:periodStart` | `xsd:dateTime` | SHOULD (0..1) | Start of measurement window | | Period End | `health:periodEnd` | `xsd:dateTime` | SHOULD (0..1) | End of measurement window | | Minimum | `health:vo2Min` | `xsd:double` | MAY (0..1) | Lowest reading | | Maximum | `health:vo2MaxValue` | `xsd:double` | MAY (0..1) | Highest reading | | Trend Direction | `health:vo2TrendDirection` | `xsd:string` | MAY (0..1) | `improving`, `declining`, `stable`, `unknown` | | Sparse Data | `health:isSparseData` | `xsd:boolean` | MAY (0..1) | Whether data is sparse | #### 12.8.2 Turtle Example -- VO2 Max ```turtle @prefix health: . @prefix xsd: . # VO2 Max statistics within body measurements <#body-measurements> a health:BodyMeasurements ; health:VO2MaxStatistics [ a health:VO2MaxStatistics ; health:vo2Mean "34.2"^^xsd:double ; health:vo2SampleCount "12"^^xsd:integer ; health:fitnessClassification "fair" ; health:periodStart "2025-08-20T00:00:00Z"^^xsd:dateTime ; health:periodEnd "2026-02-18T00:00:00Z"^^xsd:dateTime ; health:vo2Min "32.1"^^xsd:double ; health:vo2MaxValue "36.8"^^xsd:double ; health:vo2TrendDirection "stable" ] . ``` ### 12.9 Body Measurements Body measurements are stored within a `health:BodyMeasurements` container and include body mass, height, BMI, body temperature, SpO2, and blood glucose. Each measurement uses the `health:VitalSignReading` type with FHIR-aligned coding. #### 12.9.1 Metrics Table | Metric | Property | SNOMED Code | LOINC Code | Unit | |---|---|---|---|---| | Body Mass | `health:bodyMass` | `27113001` | `29463-7` | kg | | Body Height | `health:height` | `50373000` | `8302-2` | cm | | BMI | `health:computedBMI` | `60621009` | `39156-5` | kg/m2 | | Body Temperature | `health:bodyTemperature` | `386725007` | `8310-5` | degC | | Oxygen Saturation | `health:oxygenSaturation` | `431314004` | `2708-6` | % | | Blood Glucose | `health:bloodGlucose` | `33747003` | `2345-7` | mg/dL | #### 12.9.2 Turtle Example -- Body Measurements ```turtle @prefix cascade: . @prefix health: . @prefix fhir: . @prefix sct: . @prefix loinc: . @prefix ucum: . @prefix xsd: . @prefix prov: . # Body measurements container <#body-measurements> a health:BodyMeasurements ; # --- Body mass (latest reading) --- health:bodyMass [ a health:VitalSignReading ; fhir:code sct:27113001 ; cascade:loincCode loinc:29463-7 ; fhir:valueQuantity [ fhir:value "91.2"^^xsd:double ; fhir:unit "kg" ; fhir:system ucum: ] ; cascade:date "2026-02-15T07:30:00Z"^^xsd:dateTime ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Withings Body+" ] ] ; # --- Height --- health:height [ a health:VitalSignReading ; fhir:code sct:50373000 ; cascade:loincCode loinc:8302-2 ; fhir:valueQuantity [ fhir:value "178"^^xsd:double ; fhir:unit "cm" ; fhir:system ucum: ] ; cascade:date "2025-12-01T10:00:00Z"^^xsd:dateTime ] ; # --- Computed BMI --- health:computedBMI [ a health:VitalSignReading ; fhir:code sct:60621009 ; cascade:loincCode loinc:39156-5 ; fhir:valueQuantity [ fhir:value "28.8"^^xsd:double ; fhir:unit "kg/m2" ; fhir:system ucum: ] ; cascade:date "2026-02-15T07:30:00Z"^^xsd:dateTime ] ; # --- Body temperature --- health:bodyTemperature [ a health:VitalSignReading ; fhir:code sct:386725007 ; cascade:loincCode loinc:8310-5 ; fhir:valueQuantity [ fhir:value "36.6"^^xsd:double ; fhir:unit "degC" ; fhir:system ucum: ] ; cascade:date "2026-02-18T08:00:00Z"^^xsd:dateTime ] ; # --- Oxygen saturation (SpO2) --- health:oxygenSaturation [ a health:VitalSignReading ; fhir:code sct:431314004 ; cascade:loincCode loinc:2708-6 ; fhir:valueQuantity [ fhir:value "97"^^xsd:double ; fhir:unit "%" ; fhir:system ucum: ] ; cascade:date "2026-02-18T08:00:00Z"^^xsd:dateTime ; prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; prov:label "Apple Watch Series 9" ] ] . ``` ### 12.10 SHACL Constraints -- Wellness Observations **Container classes (v2.5).** The six container types in this section — `health:ActivityData`, `health:SleepData`, `health:HeartRateData`, `health:BloodPressureData`, `health:HRVData` and `health:BodyMeasurements` — are declared `rdfs:subClassOf health:HealthProfile` in health v2.5, and `health:HealthProfileShape` names each of them as an explicit `sh:targetClass`. Two consequences: the `rdfs:domain health:HealthProfile` that the eight history properties already carried is now true rather than contradicted by every pod ever written, and a container resource is checked by `HealthProfileShape` instead of matching no shape at all. The `sh:targetClass` declarations are explicit rather than left to subclass inference, so coverage does not depend on the validator implementing it. Note that `HealthProfileShape` carries `sh:Info` advisories on the history properties (for example, "consider populating daily activity history for activity trend analysis"). Those now fire on container resources that did not previously match any shape. They are advisories, not violations, and do not affect conformance. **Daily snapshot classes (v2.5).** `health.shapes.ttl` v1.2 adds three shapes for the entries inside the history lists: | Shape | Constraints | |-------|-------------| | `health:DailyVitalReadingShape` | `cascade:date` `sh:minCount 1` `sh:maxCount 1` `xsd:dateTime` (Violation); `cascade:sampleCount` `xsd:integer` `sh:minInclusive 0` `sh:maxCount 1`; `health:value`, `health:unit`, `health:date`, `cascade:loincCode` each `sh:maxCount 1` | | `health:DailyActivitySnapshotShape` | `cascade:date` required; `health:steps` `xsd:integer` ≥ 0; `health:activeEnergyKcal` `xsd:decimal` ≥ 0; `health:exerciseMinutes` `xsd:integer` 0–1440; `health:standHours` `xsd:integer` 0–24 | | `health:DailySleepSnapshotShape` | `cascade:date` required; `health:durationHours` `xsd:decimal` 0–24; `health:sleepQuality` `sh:in (health:Excellent health:Good health:Fair health:Poor)` | > **`Daily*` and the 7-day aggregates are different classes, deliberately.** `health:ActivitySnapshot` and `health:SleepSnapshot` describe an aggregate over a measurement period (typically 7 days); `health:DailyActivitySnapshot` and `health:DailySleepSnapshot` describe a single day. Both are emitted, both are shaped, and the near-identical names are not a divergence to be reconciled. **Statistical summary shapes.** `health.shapes.ttl` also provides: **`health:VO2MaxStatisticsShape`:** | Property | Constraint | Severity | |---|---|---| | `health:vo2Mean` | `sh:minCount 1`, `xsd:double`, `sh:minInclusive 0` | Violation | | `health:vo2SampleCount` | `sh:minCount 1`, `xsd:integer`, `sh:minInclusive 1` | Violation | | `health:fitnessClassification` | `sh:minCount 1`, `sh:in ("veryPoor", "poor", "fair", "good", "excellent", "superior")` | Violation | | `health:periodStart` | `xsd:dateTime`, `sh:maxCount 1` | Warning | | `health:periodEnd` | `xsd:dateTime`, `sh:maxCount 1` | Warning | **`health:HRVStatisticsShape`:** | Property | Constraint | Severity | |---|---|---| | `health:hrvMean` | `sh:minCount 1`, `xsd:double`, `sh:minInclusive 0` | Violation | | `health:hrvSampleCount` | `sh:minCount 1`, `xsd:integer`, `sh:minInclusive 1` | Violation | | `health:periodStart` | `sh:minCount 1`, `xsd:dateTime` | Violation | | `health:periodEnd` | `sh:minCount 1`, `xsd:dateTime` | Violation | | `health:hrvTrendDirection` | `sh:in ("improving", "declining", "stable", "unknown")` | Info | **`health:BPStatisticsShape`:** | Property | Constraint | Severity | |---|---|---| | `health:bpMeanSystolic` | `sh:minCount 1`, `xsd:double`, range 40-300 | Violation | | `health:bpMeanDiastolic` | `sh:minCount 1`, `xsd:double`, range 20-200 | Violation | | `health:bpCategory` | `sh:minCount 1`, `sh:in ("normal", "elevated", "hypertension_stage1", "hypertension_stage2", "hypertensive_crisis")` | Violation | | `health:bpSampleCount` | `xsd:integer`, `sh:minInclusive 1` | Warning | **`health:ActivitySnapshotShape`:** | Property | Constraint | Severity | |---|---|---| | `health:averageDailySteps` | `xsd:integer`, `sh:minInclusive 0` | Warning | | `health:exerciseMinutesWeekly` | `xsd:integer`, `sh:minInclusive 0` | Info | | `health:standHoursDaily` | `xsd:integer`, range 0-24 | Info | **`health:SleepSnapshotShape`:** | Property | Constraint | Severity | |---|---|---| | `health:averageDurationHours` | `xsd:decimal`, range 0-24 | Warning | | `health:sleepQuality` | `xsd:string` | Info | ### 12.11 JSON-LD Equivalent -- Wellness Observations Wellness observation containers can be expressed in JSON-LD. Due to the nested blank node structure, the JSON-LD representation is more verbose: ```json { "@context": "https://ns.cascadeprotocol.org/context/v1/cascade.jsonld", "@id": "#activity", "@type": "health:ActivityData", "health:dailyActivityHistory": { "@list": [ { "@type": "health:DailyActivitySnapshot", "cascade:date": { "@value": "2026-01-20T00:00:00Z", "@type": "xsd:dateTime" }, "health:steps": 7842, "health:activeEnergyKcal": 312, "health:exerciseMinutes": 22, "health:standHours": 10, "prov:wasGeneratedBy": { "@type": "prov:Activity", "cascade:sourceType": "healthKit", "prov:label": "Apple Watch Series 9" } } ] } } ``` --- ## 13. Comprehensive Provenance Model ### 13.1 Description The Cascade Protocol integrates the W3C PROV-O ontology for expressing data lineage. This section documents the full provenance model, including how provenance attaches to records, activity chains, agent attribution, and guidelines for AI agents interpreting provenance. The provenance model operates at two levels: 1. **Record-level provenance** -- The `cascade:dataProvenance` triple on every resource (covered in Section 1.6 and each data type section). 2. **Activity-level provenance** -- W3C PROV-O `prov:Activity`, `prov:wasGeneratedBy`, and `prov:wasAttributedTo` triples that describe the detailed lineage of how data was generated. ### 13.2 Complete Provenance Taxonomy The full provenance class hierarchy defined in `core.ttl`: ``` cascade:DataProvenance (base class, subclass of prov:Entity) | +-- cascade:ConsumerGenerated | | Data from personal devices in non-clinical settings | | | +-- cascade:DeviceGenerated | | Consumer health device (Apple Watch, BP cuff, glucose meter) | | | +-- cascade:SelfReported | | Patient manual entry (symptom logs, intake forms) | | | +-- cascade:ConsumerWellness | Aggregated wellness platform (Apple Health, Google Fit) | +-- cascade:ClinicalGenerated | Data from clinical settings under provider supervision | +-- cascade:EHRVerified | Imported from verified EHR (Epic MyChart, Cerner) | +-- cascade:ScannedDocument | Extracted from scanned/photographed clinical documents | +-- cascade:AIExtracted Extracted from clinical documents using AI/NLP ``` **Disjointness:** `cascade:ConsumerGenerated` and `cascade:ClinicalGenerated` are declared `owl:disjointWith` each other. A record cannot be both consumer-generated and clinical-generated. ### 13.3 How Provenance Attaches to Records Every Cascade Protocol resource MUST include a `cascade:dataProvenance` triple: ```turtle # Record-level provenance (REQUIRED on all resources) cascade:dataProvenance cascade:EHRVerified . ``` Wellness observations additionally carry **inline activity provenance** on each daily snapshot: ```turtle # Activity-level provenance (inline on daily snapshots) [ a health:DailyVitalReading ; ... prov:wasGeneratedBy [ a prov:Activity ; cascade:sourceType "healthKit" ; # Source platform prov:label "Apple Watch Series 9" # Device name ] ] . ``` ### 13.4 Activity Chains and Generation Events For complex data pipelines (e.g., AI extraction from scanned documents), provenance can be chained: ```turtle # A medication extracted from a scanned lab report a clinical:Medication ; clinical:drugName "Lisinopril" ; cascade:dataProvenance cascade:AIExtracted ; cascade:schemaVersion "1.3" ; prov:wasGeneratedBy . # The AI extraction activity a prov:Activity ; prov:label "AI medication extraction from scanned document" ; prov:wasAssociatedWith ; prov:used ; prov:startedAtTime "2026-02-19T10:00:00Z"^^xsd:dateTime ; prov:endedAtTime "2026-02-19T10:00:05Z"^^xsd:dateTime . # The AI agent a prov:SoftwareAgent ; prov:label "Cascade AI Extractor v2.1" ; cascade:agentType "aiExtractor" . # The source document a prov:Entity ; prov:label "Scanned lab report 2026-01-15" ; cascade:dataProvenance cascade:ScannedDocument . ``` ### 13.5 Agent Attribution Patterns The Cascade Protocol recognizes several agent types: | Agent Type | PROV-O Class | `cascade:agentType` | Description | |---|---|---|---| | Patient | `prov:Person` | `patient` | The data subject entering self-reported data | | Software | `prov:SoftwareAgent` | `sdkSerializer` | SDK serializer producing RDF output | | AI Extractor | `prov:SoftwareAgent` | `aiExtractor` | AI/NLP agent extracting structured data | | Device | `prov:SoftwareAgent` | `device` | Consumer health device | | EHR System | `prov:SoftwareAgent` | `ehrSystem` | Source EHR system | ### 13.6 AIGenerated Provenance When an AI agent creates or synthesizes data (as opposed to extracting it from an existing document), the provenance should be explicit: ```turtle # An AI-generated clinical summary a cascade:ClinicalSummary ; cascade:dataProvenance cascade:AIExtracted ; cascade:schemaVersion "1.3" ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "AI clinical summary generation" ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Claude (Anthropic)" ; cascade:agentType "aiAgent" ; cascade:agentVersion "claude-opus-4-6" ] ] . ``` **Key principle:** AI-generated content MUST always carry `cascade:AIExtracted` provenance (or a future `cascade:AIGenerated` subclass when defined). Consumers MUST be able to distinguish AI-generated content from clinician-verified or device-measured data. ### 13.7 Provenance Trust Levels for AI Agents When an AI agent reads Cascade Protocol data, it SHOULD interpret provenance to determine trust levels: | Provenance Class | Trust Level | Agent Guidance | |---|---|---| | `cascade:EHRVerified` | **High** | Treat as authoritative clinical data. Safe for clinical decision support. | | `cascade:ClinicalGenerated` | **High** | Clinical-setting data. Safe for clinical reasoning. | | `cascade:DeviceGenerated` | **Medium** | Consumer device data. Reliable for trends but may have accuracy limitations. | | `cascade:ConsumerWellness` | **Medium** | Aggregated platform data. Good for trends, not for point-of-care decisions. | | `cascade:SelfReported` | **Low-Medium** | Patient-entered data. Verify with clinical sources when possible. | | `cascade:AIExtracted` | **Low** | AI-extracted data. Should be verified by a human before clinical use. | | `cascade:ScannedDocument` | **Low** | OCR/scan data. May contain errors from image quality issues. | **No Silent Inference policy:** When an AI agent makes inferences from provenance data, it MUST disclose the provenance sources and confidence levels. For example: "Based on your EHR-verified medication list (high confidence) and self-reported supplement intake (medium confidence), there is a potential interaction between..." ### 13.8 Provenance in Export Manifests When a Cascade Pod is exported, the manifest file (`manifest.ttl`) includes W3C PROV-O metadata describing the export event: ```turtle @prefix prov: . @prefix cascade: . @prefix xsd: . <#export> a prov:Activity ; prov:label "Cascade Pod Export" ; prov:startedAtTime "2026-02-19T14:00:00Z"^^xsd:dateTime ; prov:endedAtTime "2026-02-19T14:00:03Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Cascade Checkup v1.0" ; cascade:agentType "sdkSerializer" ; cascade:sdkVersion "1.3" ] ; prov:generated <#pod> . <#pod> a prov:Collection ; prov:label "Alex Rivera Health Pod" ; cascade:schemaVersion "1.3" ; cascade:exportFormat "turtle" ; cascade:fileCount "15"^^xsd:integer . ``` --- ## 14. Pod Structure Conventions ### 14.1 Description This section provides a summary of the Cascade Protocol Pod Structure specification. The full specification is available as a standalone document at `docs/spec/pod-structure.md`. A Cascade Protocol **Pod** is a portable, self-describing directory of personal health data serialized as RDF/Turtle files. The Pod structure supports local on-device storage, directory/ZIP export, and upload to a Solid Pod server. ### 14.2 Directory Layout Overview ``` cascade-pod/ .well-known/ solid # Pod metadata discovery profile/ card.ttl # General identity (foaf:Agent) health.ttl # Health demographics (cascade:PatientProfile) settings/ publicTypeIndex.ttl # Public type registrations privateTypeIndex.ttl # Private type registrations clinical/ medications.ttl # clinical:Medication conditions.ttl # health:ConditionRecord allergies.ttl # health:AllergyRecord lab-results.ttl # health:LabResultRecord vital-signs.ttl # clinical:VitalSign immunizations.ttl # health:ImmunizationRecord procedures.ttl # clinical:Procedure family-history.ttl # health:FamilyHistoryRecord insurance.ttl # coverage:InsurancePlan wellness/ heart-rate.ttl # health:HeartRateData blood-pressure.ttl # health:BloodPressureData activity.ttl # health:ActivityData sleep.ttl # health:SleepData supplements.ttl # clinical:Supplement coverage/ plans/ # coverage:InsurancePlan (preferred) manifest.ttl # W3C PROV-O export metadata ``` ### 14.3 Type Index Discovery A conformant Pod reader MUST discover data locations using type indexes rather than hardcoded paths: 1. Read `.well-known/solid` to find the profile URI 2. Read `profile/card.ttl` to find the type index URIs 3. Read `settings/privateTypeIndex.ttl` to find data file locations 4. For each `solid:TypeRegistration`, resolve `solid:instance` or `solid:instanceContainer` The directory names above are RECOMMENDED defaults, not requirements. A Pod author MAY organize data differently as long as type indexes register the locations. ### 14.4 File Naming Conventions - **Aggregate files** contain all records of a single type (e.g., `medications.ttl` contains all medication records) - File names use **lowercase kebab-case** (e.g., `lab-results.ttl`, `heart-rate.ttl`) - Each aggregate file includes namespace prefix declarations at the top - Each aggregate file includes a header comment block describing its contents ### 14.5 Reference For the complete specification including access control (ACL files), export metadata, interoperability requirements, and a full example directory tree, see the [Pod Structure Specification](../pod-structure.md). --- ## Appendix A: Standard Namespace Prefixes The complete set of namespace prefixes used across Cascade Protocol vocabularies: | Prefix | Namespace URI | Description | |---|---|---| | `cascade:` | `https://ns.cascadeprotocol.org/core/v1#` | Core vocabulary (versioning, provenance, identity, demographics) | | `health:` | `https://ns.cascadeprotocol.org/health/v1#` | Health & wellness vocabulary (consumer device observations) | | `clinical:` | `https://ns.cascadeprotocol.org/clinical/v1#` | Clinical document vocabulary (EHR-imported data) | | `checkup:` | `https://ns.cascadeprotocol.org/checkup/v1#` | Checkup vocabulary (patient-facing summaries, Layer 3) | | `pots:` | `https://ns.cascadeprotocol.org/pots/v1#` | POTS screening protocol vocabulary | | `coverage:` | `https://ns.cascadeprotocol.org/coverage/v1#` | Insurance/benefits vocabulary | | `fhir:` | `http://hl7.org/fhir/` | HL7 FHIR | | `sct:` | `http://snomed.info/sct/` | SNOMED CT | | `loinc:` | `http://loinc.org/rdf#` | LOINC (canonical form for this specification) | | `rxnorm:` | `http://www.nlm.nih.gov/research/umls/rxnorm/` | RxNorm (NLM) | | `icd10:` | `http://hl7.org/fhir/sid/icd-10-cm/` | ICD-10-CM | | `ucum:` | `http://unitsofmeasure.org/` | Unified Code for Units of Measure | | `xsd:` | `http://www.w3.org/2001/XMLSchema#` | XML Schema data types | | `prov:` | `http://www.w3.org/ns/prov#` | W3C PROV-O provenance ontology | | `owl:` | `http://www.w3.org/2002/07/owl#` | OWL Web Ontology Language | | `rdfs:` | `http://www.w3.org/2000/01/rdf-schema#` | RDF Schema | | `rdf:` | `http://www.w3.org/1999/02/22-rdf-syntax-ns#` | RDF | | `dct:` | `http://purl.org/dc/terms/` | Dublin Core terms | | `foaf:` | `http://xmlns.com/foaf/0.1/` | FOAF (Friend of a Friend) | | `vcard:` | `http://www.w3.org/2006/vcard/ns#` | vCard ontology | | `sh:` | `http://www.w3.org/ns/shacl#` | SHACL (Shapes Constraint Language) | --- ## Appendix B: LOINC Code Reference Common LOINC codes used across Cascade Protocol data types. ### Vital Signs | Metric | LOINC Code | LOINC Name | |---|---|---| | Systolic Blood Pressure | `8480-6` | Systolic blood pressure | | Diastolic Blood Pressure | `8462-4` | Diastolic blood pressure | | Blood Pressure Panel | `85354-9` | Blood pressure panel | | Heart Rate | `8867-4` | Heart rate | | Resting Heart Rate | `40443-4` | Heart rate --resting | | Walking Heart Rate | `89270-3` | Heart rate --W exercise | | Respiratory Rate | `9279-1` | Respiratory rate | | Body Temperature | `8310-5` | Body temperature | | Oxygen Saturation (SpO2) | `2708-6` | Oxygen saturation | | HRV (SDNN) | `80404-7` | R-R interval standard deviation (SDNN) | ### Body Measurements | Metric | LOINC Code | LOINC Name | |---|---|---| | Body Weight | `29463-7` | Body weight | | Body Height | `8302-2` | Body height | | BMI | `39156-5` | Body mass index | ### Fitness | Metric | LOINC Code | LOINC Name | |---|---|---| | VO2 Max | `60842-2` | VO2 max | ### Activity | Metric | LOINC Code | LOINC Name | |---|---|---| | Steps in 24 hours | `41950-7` | Number of steps in 24 hour Measured | | Calories Burned | `41981-2` | Calories burned | | Exercise Activity | `73985-4` | Exercise activity | ### Sleep | Metric | LOINC Code | LOINC Name | |---|---|---| | Sleep Duration | `93832-4` | Sleep duration | ### Common Lab Tests | Test | LOINC Code | Category | |---|---|---| | Hemoglobin A1C | `4548-4` | Metabolic | | Glucose (fasting) | `1558-6` | Metabolic | | Glucose (random) | `2345-7` | Metabolic | | Total Cholesterol | `2093-3` | Lipid | | HDL Cholesterol | `2085-9` | Lipid | | LDL Cholesterol | `2089-1` | Lipid | | Triglycerides | `2571-8` | Lipid | | TSH | `3016-3` | Thyroid | | Free T4 | `3024-7` | Thyroid | | Hemoglobin | `718-7` | Hematology | | WBC | `6690-2` | Hematology | | Platelet Count | `777-3` | Hematology | | Creatinine | `2160-0` | Renal | | eGFR | `33914-3` | Renal | | ALT | `1742-6` | Hepatic | | AST | `1920-8` | Hepatic | --- ## Appendix C: Provenance Classes The complete provenance class hierarchy defined in `core.ttl`: ``` cascade:DataProvenance (base class, subclass of prov:Entity) | +-- cascade:ConsumerGenerated | | Label: Consumer-Generated | | Description: Data from personal devices in non-clinical settings | | | +-- cascade:DeviceGenerated | | Label: Device-Generated | | Description: Data from consumer health devices | | (Apple Watch, BP cuff, glucose meter) | | | +-- cascade:SelfReported | | Label: Self-Reported | | Description: Data manually entered by the patient | | (symptom logs, medication adherence, intake forms) | | | +-- cascade:ConsumerWellness | Label: Consumer Wellness | Description: Aggregated wellness data from consumer platforms | (Apple Health, Google Fit) | +-- cascade:ClinicalGenerated | Label: Clinical-Generated | Description: Data from clinical settings under provider supervision | +-- cascade:EHRVerified | Label: EHR-Verified | Description: Imported from verified EHR systems | (Epic MyChart, Cerner, AllScripts) | +-- cascade:ScannedDocument | Label: Scanned Document | Description: Extracted from scanned/photographed clinical documents | +-- cascade:AIExtracted Label: AI-Extracted Description: Extracted from clinical documents using AI/NLP ``` **Disjointness:** `cascade:ConsumerGenerated` and `cascade:ClinicalGenerated` are declared `owl:disjointWith` each other. Data cannot be both consumer-generated and clinical-generated. **Provenance in Turtle:** ```turtle # EHR-imported medication cascade:dataProvenance cascade:EHRVerified . # Patient-entered allergy cascade:dataProvenance cascade:SelfReported . # Apple Watch heart rate cascade:dataProvenance cascade:DeviceGenerated . ``` --- ## Appendix D: Data Types Covered and Remaining Phase 2 of this specification now covers all clinical record types, wellness observations, comprehensive provenance, and pod structure conventions. The following items from the original Phase 2 plan are now documented in this specification: - **Immunizations** -- See Section 8 - **Procedures** -- See Section 9 - **Family History** -- See Section 10 - **Coverage/Insurance** -- See Section 11 - **Wellness Observations** (Heart Rate, Blood Pressure, Activity, Sleep, HRV, VO2 Max, Body Measurements) -- See Section 12 - **Comprehensive Provenance Model** -- See Section 13 - **Pod Structure Conventions** -- See Section 14 ### Data Types Deferred to Phase 3 The following data types are not yet covered and will be addressed in subsequent releases: - **Medication Use Episodes** -- `clinical:MedicationUseEpisode` for longitudinal medication tracking with reconciliation from multiple source records, inferred status, and explainable confidence. The SHACL shape (`clinical:MedicationUseEpisodeShape`) is implemented. - **Lab Test Series** -- `clinical:LabTestSeries` for longitudinal lab result trending with inferred trend direction and reference range tracking. - **JSON-LD Context** -- The published `cascade.jsonld` context file for JSON-LD serialization. - **Metric Trends** -- `health:MetricTrend` for time-bounded wellness trends with direction, magnitude, and confidence. The SHACL shape (`health:MetricTrendShape`) is implemented. --- ## Document History | Version | Date | Changes | |---|---|---| | 1.0 | 2026-02-19 | Initial release. Phase 1 covering 6 data types: Medications, Conditions, Allergies, Lab Results, Vital Signs, Patient Profile. | | 2.0 | 2026-02-19 | Phase 2 completion. Added 7 new sections: Immunizations (Section 8), Procedures (Section 9), Family History (Section 10), Coverage/Insurance (Section 11), Wellness Observations (Section 12), Comprehensive Provenance Model (Section 13), Pod Structure Conventions (Section 14). Updated Appendix D to reflect completed coverage. | # ============================================================================ # SECTION 6: POD STRUCTURE SPECIFICATION # ============================================================================ # Cascade Protocol Pod Structure Specification **Status:** Draft **Version:** 1.1 **Date:** 2026-08-03 **Authors:** Cascade Agentic Labs LLC **Website:** https://cascadeprotocol.org **Vocabulary versions:** core v3.4, health v2.5, clinical v1.13, coverage v1.3 > **v1.1 correction.** Every `solid:forClass` registration and every file/class table in this document has been checked against the published ontologies and against the [reference patient pod](/reference-patient-pod/README.md). Fourteen class names were corrected: they named classes that no Cascade ontology defines and no implementation writes, inside registration examples an implementer would copy. Two remaining names (`clinical:ScreeningResult`, `clinical:DiagnosticResult`) have no ratified equivalent and are marked rather than invented. --- ## Table of Contents 1. [Overview](#1-overview) 2. [Discovery Mechanism](#2-discovery-mechanism) 3. [Required Structure](#3-required-structure) 4. [Standard Data Directories](#4-standard-data-directories) 5. [Domain-Specific Extensions](#5-domain-specific-extensions) 6. [Reserved Namespaces](#6-reserved-namespaces) 7. [File Naming Conventions](#7-file-naming-conventions) 8. [Access Control (.acl files)](#8-access-control-acl-files) 9. [Export Metadata](#9-export-metadata) 10. [Interoperability Requirements](#10-interoperability-requirements) 11. [Appendix A: Complete Example Pod Directory Tree](#appendix-a-complete-example-pod-directory-tree) 12. [Appendix B: Type Index Registration Format](#appendix-b-type-index-registration-format) 13. [Appendix C: Manifest TTL Format](#appendix-c-manifest-ttl-format) 14. [Appendix D: ACL File Templates](#appendix-d-acl-file-templates) --- ## 1. Overview A Cascade Protocol **Pod** is a portable, self-describing directory of personal health data serialized as RDF/Turtle files. The Pod structure is designed so that health data can be: - Stored locally on-device with encryption at rest - Exported as a directory or ZIP archive for sharing - Uploaded to a Solid Pod server for decentralized storage and access control - Read by any compliant tool without prior knowledge of the producing application ### 1.1 Relationship to Solid Cascade Pods follow the [Solid Protocol](https://solidproject.org/TR/protocol) conventions for resource organization, discovery, and access control: - **WebID-TLS** identity via `profile/card.ttl` - **Type Indexes** for data discovery (`settings/publicTypeIndex.ttl`, `settings/privateTypeIndex.ttl`) - **LDP Containers** for resource grouping (`index.ttl` files with `ldp:contains` triples) - **WAC (Web Access Control)** for authorization (`.acl` files) - **`.well-known/solid`** for Pod metadata discovery A Cascade Pod is a valid Solid Pod. Any Solid-compatible tool SHOULD be able to read the structural metadata (profile, type indexes, ACLs) even if it does not understand Cascade Protocol vocabularies. ### 1.2 Design Principles 1. **Discovery over convention.** Tools MUST use type indexes to find data, not hardcoded paths. Directory names are human-readable hints, not a contract. 2. **Graceful ignorance.** A tool encountering an unknown directory (e.g., `/diabetes/`) MUST NOT error. It MUST ignore what it does not understand. 3. **Extension without coordination.** Domain-specific applications can add directories to a Pod following the naming conventions in this specification. No central registry is required. 4. **Minimum viable Pod.** A valid Pod can contain as little as three files. Applications MUST NOT require the full directory tree to be present. 5. **ACLs are optional but recommended.** Local-only Pods MAY omit ACL files. Pods intended for export or upload to a Solid server SHOULD include them. 6. **Provenance is first-class.** Every Pod export SHOULD include a manifest with W3C PROV-O metadata describing when, how, and by what software the Pod was generated. ### 1.3 Terminology The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). | Term | Definition | |------|-----------| | **Pod** | A self-contained directory tree of RDF resources conforming to this specification | | **Container** | A directory within a Pod that groups related resources, represented as an LDP Container | | **Resource** | A single Turtle (.ttl) file containing RDF triples | | **Namespace** | A top-level directory in a Pod corresponding to a data domain (e.g., `clinical/`, `wellness/`) | | **Type Index** | A Solid Type Index document that maps RDF classes to their storage locations within the Pod | | **Aggregate file** | A Turtle file containing all records of a single type (e.g., `medications.ttl` contains all medication records) | --- ## 2. Discovery Mechanism ### 2.1 Discovery Flow A conformant Pod reader MUST discover data locations using the following sequence: ``` 1. Read .well-known/solid | v 2. Resolve profile URI --> profile/card.ttl | v 3. Follow solid:publicTypeIndex --> settings/publicTypeIndex.ttl Follow pim:preferencesFile --> settings/preferences | v 4. From settings/preferences, follow solid:privateTypeIndex --> settings/privateTypeIndex.ttl | v 5. For each solid:TypeRegistration, resolve solid:instance or solid:instanceContainer to locate data files ``` ### 2.2 Why Not Hardcoded Paths? A conformant reader MUST NOT assume that clinical data lives at `/clinical/medications.ttl`. A Pod author MAY organize data in any directory structure and register the locations via type indexes. The directory names in this specification are RECOMMENDED defaults, not requirements. This ensures that: - A Pod can be reorganized without breaking readers - Multiple applications can contribute data to the same Pod without path conflicts - Future versions of this specification can change default paths without breaking existing Pods ### 2.3 Fallback Behavior If a reader cannot locate a type index (e.g., `settings/publicTypeIndex.ttl` is missing), it MAY fall back to scanning the root `index.ttl` for `ldp:contains` triples and inspecting container contents. This is a best-effort fallback and SHOULD NOT be relied upon as the primary discovery mechanism. --- ## 3. Required Structure ### 3.1 .well-known/solid **Path:** `/.well-known/solid` **Format:** JSON-LD **Status:** REQUIRED The `.well-known/solid` file is the entry point for Pod discovery. A Solid client resolving a Pod URL fetches this file first to locate the profile document, storage root, and type indexes. ```json { "@context": "https://www.w3.org/ns/solid/terms", "pod_root": "/", "profile": "/profile/card.ttl#me", "storage": "/", "preferencesFile": "/settings/preferences", "publicTypeIndex": "/settings/publicTypeIndex.ttl", "privateTypeIndex": "/settings/privateTypeIndex.ttl" } ``` **Fields:** | Field | Type | Description | |-------|------|-------------| | `@context` | URI | MUST be `https://www.w3.org/ns/solid/terms` | | `pod_root` | Path | Root path of the Pod. MUST be `"/"` for standalone Pods | | `profile` | URI | Path to the WebID profile document, including fragment identifier | | `storage` | Path | Root storage container. MUST be `"/"` for standalone Pods | | `preferencesFile` | Path | Path to the private preferences file (holds `solid:privateTypeIndex`) | | `publicTypeIndex` | Path | Path to the public type index document | | `privateTypeIndex` | Path | Shortcut path to the private type index (also reachable via `preferencesFile`) | ### 3.2 profile/card.ttl **Path:** `/profile/card.ttl` **Format:** Turtle (RDF) **Status:** REQUIRED The WebID profile card identifies the Pod owner and links to discovery resources. The `<#me>` fragment identifier serves as the WebID. The document itself MUST be declared as a `foaf:PersonalProfileDocument`. ```turtle @prefix foaf: . @prefix solid: . @prefix pim: . @prefix rdfs: . @prefix cascade: . a foaf:PersonalProfileDocument ; foaf:primaryTopic <#me> . <#me> a foaf:Person ; foaf:name "Cascade Protocol User" ; pim:storage ; pim:preferencesFile ; solid:publicTypeIndex ; rdfs:seeAlso ; cascade:schemaVersion "1.3" . ``` **Required triples on the document node (``):** | Predicate | Object | Notes | |-----------|--------|-------| | `a` | `foaf:PersonalProfileDocument` | REQUIRED per Solid WebID spec | | `foaf:primaryTopic` | `<#me>` | REQUIRED per Solid WebID spec | **Required triples for `<#me>`:** | Predicate | Object | Notes | |-----------|--------|-------| | `a` | `foaf:Person` | REQUIRED | | `pim:storage` | URI | REQUIRED. Root storage container | | `pim:preferencesFile` | URI | REQUIRED. Path to private preferences file | | `solid:publicTypeIndex` | URI | REQUIRED. Path to the public type index | **Optional triples:** | Predicate | Object | Notes | |-----------|--------|-------| | `foaf:name` | Literal | RECOMMENDED. Display name for the Pod owner | | `rdfs:seeAlso` | URI | RECOMMENDED. Link to private extended profile (e.g., `profile/extended.ttl`) | | `cascade:schemaVersion` | Literal | RECOMMENDED. Cascade Protocol schema version | | `cascade:potsTestContainer` | URI | Domain-specific pointer (POTS Check app) | **Important:** `solid:privateTypeIndex` MUST NOT appear on `<#me>` in `card.ttl`. It belongs in `settings/preferences` (see Section 3.5), which is owner-private. Placing it in the publicly-readable `card.ttl` exposes the private type index location to unauthenticated readers. A Pod exporter SHOULD use a generic display name (e.g., `"Cascade Protocol User"`) rather than the patient's real name in the profile card, unless the user has explicitly consented to include identifying information. PHI (date of birth, address, phone, email) MUST NOT appear in `card.ttl`. It belongs in `profile/extended.ttl` (see Section 3.6). ### 3.3 settings/publicTypeIndex.ttl **Path:** `/settings/publicTypeIndex.ttl` **Format:** Turtle (RDF) **Status:** REQUIRED The public type index registers data locations that are visible to other authenticated agents. Each registration maps an RDF class to its storage location within the Pod. ```turtle @prefix solid: . @prefix health: . @prefix clinical: . @prefix dct: . <#medications> a solid:TypeRegistration ; solid:forClass clinical:Medication ; solid:instance ; dct:title "Medication Records" . <#conditions> a solid:TypeRegistration ; solid:forClass health:ConditionRecord ; solid:instance ; dct:title "Condition Records" . <#heart-rate> a solid:TypeRegistration ; solid:forClass health:HeartRateData ; solid:instance ; dct:title "Heart Rate Data" . ``` See [Appendix B](#appendix-b-type-index-registration-format) for the complete registration format and all standard registrations. ### 3.4 settings/privateTypeIndex.ttl **Path:** `/settings/privateTypeIndex.ttl` **Format:** Turtle (RDF) **Status:** REQUIRED The private type index registers data locations visible only to the Pod owner. This is appropriate for sensitive data that the user does not wish to share via type index discovery. ```turtle @prefix solid: . @prefix cascade: . @prefix dct: . <#all-health-data> a solid:TypeRegistration ; solid:forClass cascade:HealthRecord ; solid:instance ; dct:title "All Health Data" ; dct:description "Complete health data from this Pod" . ``` **Guidance on public vs. private registrations:** - Data types the user wishes to make discoverable to authorized agents (e.g., medications for a healthcare provider) SHOULD be registered in the public type index. - Broad, catch-all registrations and sensitive data types SHOULD be registered in the private type index. - A data type MAY appear in both indexes with different access scopes. ### 3.5 settings/preferences **Path:** `/settings/preferences` **Format:** Turtle (RDF) **Status:** REQUIRED **Access:** Owner-only (MUST be protected by an ACL; MUST NOT be publicly readable) The preferences file is the root of the Pod owner's private configuration. It holds the pointer to the private type index and links to the extended profile document containing PHI. ```turtle @prefix pim: . @prefix solid: . @prefix rdfs: . <> a pim:ConfigurationFile . <#me> solid:privateTypeIndex ; rdfs:seeAlso . ``` By placing `solid:privateTypeIndex` here rather than in `card.ttl`, the private type index location is hidden from unauthenticated readers. Agents that have been granted owner-level access can follow `pim:preferencesFile` from `card.ttl` to locate this file. ### 3.6 profile/extended.ttl **Path:** `/profile/extended.ttl` **Format:** Turtle (RDF) **Status:** RECOMMENDED **Access:** Owner-only (MUST NOT be publicly readable) The extended profile holds PHI that must not appear in the publicly-readable `card.ttl`. This is the Solid extended profile convention (linked via `rdfs:seeAlso` from `card.ttl`). ```turtle @prefix foaf: . @prefix vcard: . @prefix cascade: . @prefix xsd: . <#me> cascade:dateOfBirth "1990-01-01"^^xsd:date ; cascade:biologicalSex "M" ; vcard:hasTelephone "+1-555-000-0000" ; vcard:hasEmail "user@example.com" ; cascade:address [ cascade:addressLine "123 Main St" ; cascade:addressCity "Seattle" ; cascade:addressState "WA" ; cascade:addressPostalCode "98101" ; ] . ``` Note: the `profile/health.ttl` file (written by the Cascade Swift SDK) serves a similar purpose but contains a richer Cascade-specific health profile (emergency contacts, pharmacy, advance directives, computed demographics). Both files MAY coexist, each linked from `card.ttl` via separate `rdfs:seeAlso` triples. ### 3.7 index.ttl (Root LDP Container) **Path:** `/index.ttl` **Format:** Turtle (RDF) **Status:** RECOMMENDED The root container index declares the Pod as an LDP BasicContainer and lists all top-level containers using `ldp:contains` triples. This enables clients to enumerate the Pod's top-level structure. ```turtle @prefix ldp: . @prefix dct: . <./> a ldp:Container, ldp:BasicContainer ; dct:title "Pod Root Container" ; ldp:contains , , , . ``` The `ldp:contains` list MUST include all top-level directories present in the Pod. When new namespaces are added to a Pod, the root `index.ttl` MUST be updated to include them. --- ## 4. Standard Data Directories The following directories are the RECOMMENDED defaults for organizing health data. Tools MUST use type index discovery rather than relying on these paths, but Pod exporters SHOULD use these paths unless there is a specific reason to deviate. ### 4.1 clinical/ -- Clinical Data **Path:** `/clinical/` **Vocabulary:** `clinical:` (`https://ns.cascadeprotocol.org/clinical/v1#`) **Description:** Provider-generated clinical data imported from Electronic Health Records (EHRs), Apple Health clinical records, or manually entered by users with clinical provenance. **Standard aggregate files:** | File | RDF Class | Description | |------|-----------|-------------| | `medications.ttl` | `clinical:Medication` | Active and historical medications with dosages | | `conditions.ttl` | `health:ConditionRecord` | Diagnosed medical conditions | | `allergies.ttl` | `health:AllergyRecord` | Allergies and adverse reactions | | `lab-results.ttl` | `health:LabResultRecord` | Laboratory test results with reference ranges | | `immunizations.ttl` | `health:ImmunizationRecord` | Vaccination records | | `vital-signs.ttl` | `clinical:VitalSign` | Clinical vital sign measurements and trends | | `patient-profile.ttl` | `cascade:PatientProfile` | Demographics, contact information | | `insurance.ttl` | `coverage:InsurancePlan` | Insurance plan details | | `procedures.ttl` | `clinical:Procedure` | Medical procedures | | `family-history.ttl` | `health:FamilyHistoryRecord` | Family medical history | | `screenings.ttl` | `clinical:ScreeningResult` ⚠ | Health screening results | | `diagnostic-results.ttl` | `clinical:DiagnosticResult` ⚠ | Diagnostic test results | Each file contains all records of its type as an aggregate. For example, `medications.ttl` contains all medication records in a single file, not one file per medication. > ⚠ **`clinical:ScreeningResult` and `clinical:DiagnosticResult` are not ratified vocabulary.** Neither has an `owl:Class` definition in any Cascade ontology, and no implementation writes either file. They are listed here as a planned layout, not as a type an implementer should register today. Register a type only if the class it names exists in a published `.ttl`. > **On the class names in this table.** Conditions, allergies, lab results, immunizations and family history are typed in the `health:` namespace, not `clinical:`, even though they live under `/clinical/`. That is what every import path emits and what health v2.5 defines and shapes. The directory reflects provenance, not the vocabulary a record is typed in — read `cascade:dataProvenance` for provenance. `clinical:Condition`, `clinical:Allergy`, `clinical:LabResult` and `clinical:Immunization` also exist and are deprecated as of clinical v1.13; a reader must accept both spellings. ### 4.2 wellness/ -- Wellness/Device Data **Path:** `/wellness/` **Vocabulary:** `health:` (`https://ns.cascadeprotocol.org/health/v1#`) **Description:** Consumer-generated wellness data from wearable devices (Apple Watch, Fitbit, etc.), self-tracking apps, and manual user entries. This data carries consumer-generated provenance and is classified as non-diagnostic. **Standard aggregate files:** | File | RDF Class | Description | |------|-----------|-------------| | `heart-rate.ttl` | `health:HeartRateData` | Resting and walking heart rate history | | `blood-pressure.ttl` | `health:BloodPressureData` | Blood pressure readings and history | | `activity.ttl` | `health:ActivityData` | Daily activity summaries (steps, calories, exercise) | | `sleep.ttl` | `health:SleepData` | Sleep duration and quality statistics | | `hrv.ttl` | `health:HRVData` | Heart rate variability data | | `body-measurements.ttl` | `health:BodyMeasurements` | VO2 max, body mass, height, temperature, SpO2, glucose | | `supplements.ttl` | `clinical:Supplement` | Supplement and vitamin tracking | > **The six container classes are `rdfs:subClassOf health:HealthProfile`** as of health v2.5, which is what makes the `rdfs:domain health:HealthProfile` on the history properties (`health:restingHeartRateHistory` and siblings) true, and what brings a container resource under `health:HealthProfileShape`. Note that `supplements.ttl` is the one file in this directory typed outside the `health:` namespace: supplements are `clinical:Supplement`, which carries the regulatory-status and evidence-strength vocabulary they need. **Nested container pattern (for per-record storage):** When individual records need to be stored separately (e.g., for Solid server compatibility where each resource has its own URI), the wellness directory MAY use nested containers: ``` wellness/ diagnostics/ pots-checks/ index.ttl test-2026-01-15T10-30-00Z.ttl test-2026-02-01T14-15-00Z.ttl ``` This nested pattern is used by POTS Check for individual test results that need their own URIs and access control. --- ## 5. Domain-Specific Extensions ### 5.1 Extension Pattern The Cascade Protocol is designed to be extended by domain-specific applications. Any application MAY add its own top-level directory to a Pod, provided it follows these rules: 1. The directory name MUST be a lowercase, hyphen-separated identifier (e.g., `visit-prep`, `adherence`). 2. The directory MUST be registered in a type index so that other tools can discover it. 3. The directory MUST be listed in the root `index.ttl` container. 4. Files within the directory MUST use the `.ttl` extension for Turtle resources. 5. The directory SHOULD include an `index.ttl` file listing its contents as an LDP Container. Applications reading a Pod MUST ignore directories they do not recognize. An application that understands only `clinical/` and `wellness/` MUST NOT error or warn when encountering `adherence/`, `visit-prep/`, or any other unknown directory. ### 5.2 Examples **Cascade Checkup** adds these domain-specific directories: ``` adherence/ daily-check-ins.ttl # checkup:DailyCheckIn instances visit-prep/ discussion-topics.ttl # checkup:DiscussionTopic instances visit-prep-notes.ttl # checkup:VisitPrepNotes visit-issues.ttl # checkup:VisitIssue instances suggested-questions.ttl # checkup:SuggestedQuestion instances ``` **POTS Check** uses a nested wellness container: ``` wellness/ diagnostics/ pots-checks/ index.ttl # LDP Container listing test results test-{id}.ttl # Individual pots:POTSCheckResult results ``` **Hypothetical diabetes management app:** ``` diabetes/ glucose-readings.ttl # diabetes:GlucoseReading instances insulin-doses.ttl # diabetes:InsulinDose instances carb-entries.ttl # diabetes:CarbEntry instances pump-settings.ttl # diabetes:PumpSettings ``` A Cascade Checkup reader encountering a Pod with `/diabetes/` would simply ignore that directory. A diabetes app encountering `/visit-prep/` would do the same. --- ## 6. Reserved Namespaces The following namespace directories are reserved for future Cascade Protocol features. Applications MUST NOT use these directory names for other purposes. The directories are defined in the SDK's `PodNamespace` enum but are not yet implemented in any shipping application. | Directory | Purpose | Status | |-----------|---------|--------| | `consents/` | ODRL consent policies governing data sharing | RESERVED | | `acl/` | WAC/ACL authorization rules (centralized ACL store) | RESERVED | | `profiles/` | WebID identity cards (multi-identity support) | RESERVED | | `shares/` | ShareManifest resources for tracking shared data | RESERVED | | `provenance/` | PROV-O audit trail and access receipts | RESERVED | | `context/` | JSON-LD context definitions for vocabulary resolution | RESERVED | | `shapes/` | SHACL validation shapes for data quality enforcement | RESERVED | Applications MAY create these directories for experimental use, but MUST NOT rely on their structure being stable until the corresponding specification is finalized. --- ## 7. File Naming Conventions ### 7.1 Aggregate Files ({data-type}.ttl) The default pattern for data storage is one aggregate file per data type within a namespace directory: ``` {namespace}/{data-type}.ttl ``` **Examples:** - `clinical/medications.ttl` -- all medication records - `wellness/heart-rate.ttl` -- all heart rate observations - `adherence/daily-check-ins.ttl` -- all daily check-in records The data-type portion of the filename MUST be lowercase and hyphen-separated. It SHOULD match the RDF class name in lowercase-hyphenated form (e.g., `Medication` becomes `medications.ttl`, `HeartRateStatistics` becomes `heart-rate.ttl`). ### 7.2 Individual Records ({id}.ttl) When data needs per-record granularity (e.g., for individual access control or Solid server resource creation), records MAY be stored as individual files: ``` {namespace}/{sub-container}/{id}.ttl ``` **Examples:** - `wellness/diagnostics/pots-checks/test-2026-01-15T10-30-00Z.ttl` - `clinical/lab-results/result-abc123.ttl` Individual record filenames SHOULD include a human-readable prefix and a unique identifier (UUID, timestamp, or domain-specific ID). Filenames MUST NOT contain spaces or characters outside the set `[a-zA-Z0-9._-]`. ### 7.3 Dated Resource Paths For time-series data with large volumes, a dated directory structure MAY be used: ``` {namespace}/{sub-container}/{YYYY}/{MM}/{filename}.ttl ``` **Example:** - `wellness/diagnostics/pots-checks/2026/01/test-result-abc123.ttl` This pattern is supported by the SDK's `PodNamespace.datedResourcePath()` method and is RECOMMENDED for containers expected to hold more than 100 individual records. ### 7.4 Container Indexes (index.ttl) Any directory acting as an LDP Container SHOULD include an `index.ttl` file that declares its contents: ```turtle @prefix ldp: . @prefix dct: . @prefix pots: . <./> a ldp:Container ; dct:title "POTS Test Results" ; dct:description "NASA Lean Test results for POTS screening" ; ldp:contains , . ``` Container index files are RECOMMENDED for directories containing individual records. They are REQUIRED for the Pod root (`/index.ttl`). ### 7.5 SHACL Shapes Files SHACL validation shapes files SHOULD follow the naming convention `{vocabulary}.shapes.ttl`: ``` shapes/clinical.shapes.ttl shapes/health.shapes.ttl shapes/checkup.shapes.ttl ``` > **Resolved:** The Checkup SHACL shapes file has been renamed from `checkup-adult.shapes.ttl` to `checkup.shapes.ttl` to follow the `{vocabulary}.shapes.ttl` convention (ISSUE-3, resolved 2026-02-19). --- ## 8. Access Control (.acl files) Cascade Pods use [Web Access Control (WAC)](https://solidproject.org/TR/wac) for authorization. ACL files define who can read, write, and control resources within the Pod. ### 8.1 Root ACL **Path:** `/.acl` **Status:** RECOMMENDED for exported Pods The root ACL grants the Pod owner full control over the entire Pod. It uses `acl:default` to apply to all resources that do not have their own ACL. ```turtle @prefix acl: . @prefix foaf: . # Owner has full control <#owner> a acl:Authorization ; acl:agent ; acl:accessTo <./> ; acl:default <./> ; acl:mode acl:Read, acl:Write, acl:Control . ``` The `acl:default` triple means this ACL applies to all resources within the container (and sub-containers) unless overridden by a more specific ACL. ### 8.2 Per-Container ACLs **Path:** `/{namespace}/.acl` **Status:** RECOMMENDED for exported Pods Each top-level data directory SHOULD have its own ACL file. By default, these grant owner-only access with the same pattern as the root ACL: ```turtle @prefix acl: . @prefix foaf: . <#owner> a acl:Authorization ; acl:agent ; acl:accessTo <./> ; acl:default <./> ; acl:mode acl:Read, acl:Write, acl:Control . ``` Per-container ACLs are generated for each unique top-level directory derived from the Pod's containers. For a Pod with `clinical/` and `wellness/`, this means: - `/clinical/.acl` - `/wellness/.acl` Per-container ACLs enable granular sharing. For example, a user could grant a healthcare provider read access to `/clinical/` while keeping `/wellness/` owner-only. ### 8.3 Profile ACL (Public Read) **Path:** `/profile/.acl` **Status:** RECOMMENDED for exported Pods The profile directory has a special ACL that grants authenticated agents read access to the WebID profile card. This is necessary for Solid server identity verification. ```turtle @prefix acl: . <#owner> a acl:Authorization ; acl:agent ; acl:accessTo <./card.ttl> ; acl:mode acl:Read, acl:Write, acl:Control . <#public> a acl:Authorization ; acl:agentClass acl:AuthenticatedAgent ; acl:accessTo <./card.ttl> ; acl:mode acl:Read . ``` Note that the `<#public>` authorization uses `acl:AuthenticatedAgent` rather than `foaf:Agent`. This means only agents with a valid WebID can read the profile -- unauthenticated (anonymous) access is not granted. ### 8.4 Settings ACL **Path:** `/settings/.acl` **Status:** RECOMMENDED for exported Pods The settings directory uses an owner-only ACL. Type index documents are sensitive because they reveal what data exists in the Pod, even if the data itself has separate access controls. ### 8.5 ACL Generation Rules When an exporter generates ACL files: 1. A root `.acl` MUST be created if any ACL files are generated. 2. A `profile/.acl` MUST use the public-read pattern (Section 8.3). 3. A `settings/.acl` MUST use the owner-only pattern. 4. Each unique top-level data directory SHOULD have an owner-only `.acl`. 5. The `ownerWebID` used in ACL files MUST match the `<#me>` identity in `profile/card.ttl`. ACL files are OPTIONAL for local-only Pods that will never be uploaded to a server. They are RECOMMENDED for any Pod that will be exported or shared. --- ## 9. Export Metadata ### 9.1 manifest.ttl Structure **Path:** `/manifest.ttl` **Format:** Turtle (RDF) **Status:** RECOMMENDED The manifest file provides machine-readable metadata about the Pod export, including provenance, versioning, and content summary. It uses the W3C PROV-O ontology for provenance tracking. ```turtle @prefix prov: . @prefix cascade: . @prefix xsd: . @prefix dct: . @prefix rdfs: . <#manifest> a cascade:ExportManifest ; dct:title "Cascade Checkup Export" ; dct:created "2026-02-19T10:00:00Z"^^xsd:dateTime ; cascade:schemaVersion "1.3" ; cascade:exportFormat "solidPod" ; cascade:containerCount "15"^^xsd:integer ; cascade:totalRecords "142"^^xsd:integer ; cascade:includedCategories "12"^^xsd:integer ; prov:wasGeneratedBy [ a prov:Activity ; prov:startedAtTime "2026-02-19T10:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; rdfs:label "Cascade Checkup" ; cascade:exporterVersion "1.0" ] ] . ``` ### 9.2 Manifest Properties | Property | Type | Description | |----------|------|-------------| | `dct:title` | Literal | Human-readable name of the export | | `dct:created` | `xsd:dateTime` | ISO 8601 timestamp of when the export was generated | | `cascade:schemaVersion` | Literal | Version of the Cascade Protocol schema used | | `cascade:exportFormat` | Literal | Export format identifier. `"solidPod"` for Pod exports, `"directory"` for uncompressed directory exports | | `cascade:containerCount` | `xsd:integer` | Number of non-empty resource files in the export | | `cascade:totalRecords` | `xsd:integer` | Total number of individual data records across all containers | | `cascade:includedCategories` | `xsd:integer` | Number of PHI categories included (consent-gated) | | `prov:wasGeneratedBy` | Blank node | PROV-O Activity describing the export process | ### 9.3 Export Provenance The `prov:wasGeneratedBy` block records: - **When:** `prov:startedAtTime` with an ISO 8601 timestamp - **What:** `prov:wasAssociatedWith` linking to a `prov:SoftwareAgent` that identifies the exporting application and version This provenance chain ensures that any recipient of a Pod export can determine which application produced it and when, without relying on filesystem metadata. --- ## 10. Interoperability Requirements ### 10.1 Unknown Directories MUST Be Ignored A conformant Pod reader that encounters a directory it does not recognize MUST silently ignore it. This is the fundamental extensibility guarantee of the Cascade Pod structure. **Example:** A tool that understands only `clinical/` and `wellness/` receives a Pod containing: ``` clinical/ wellness/ adherence/ visit-prep/ diabetes/ ``` The tool MUST process `clinical/` and `wellness/` normally and MUST NOT error, warn, or log diagnostics for `adherence/`, `visit-prep/`, or `diabetes/`. ### 10.2 Type Index SHOULD Be Used for Discovery A conformant Pod reader SHOULD use type indexes as the primary discovery mechanism. Readers SHOULD NOT assume that a particular RDF class is stored at a specific hardcoded path. **Correct approach:** ``` 1. Read settings/publicTypeIndex.ttl 2. Find: <#medications> solid:forClass clinical:Medication ; solid:instance . 3. Fetch /clinical/medications.ttl ``` **Incorrect approach:** ``` 1. Directly fetch /clinical/medications.ttl (hardcoded path) ``` ### 10.3 Minimum Viable Pod The smallest valid Cascade Pod consists of three files: ``` .well-known/ solid # Pod metadata (JSON-LD) profile/ card.ttl # WebID profile card settings/ publicTypeIndex.ttl # Public type index (may be empty) ``` A `privateTypeIndex.ttl` is REQUIRED by this specification but MAY be an empty Turtle document (containing only prefix declarations or no triples). The root `index.ttl` is RECOMMENDED but not required for a minimum viable Pod. Any Pod reader MUST be able to process a minimum viable Pod without error, even though it contains no health data. ### 10.4 Partial Exports A Pod export MAY include structural containers (empty directories) for namespaces that have no data. This preserves the expected Pod shape for tools that check directory existence. The `PodContainer.isStructural` flag in the SDK supports this pattern. For example, a Pod with only clinical data might still include an empty `wellness/` directory to signal that the namespace is recognized but currently unpopulated. ### 10.5 Pod Versioning The Pod structure itself is versioned implicitly through the `cascade:schemaVersion` property in `manifest.ttl`. Readers SHOULD check this version and MAY refuse to process Pods with an unrecognized schema version. The current schema version is `"1.3"`. Future versions of this specification will maintain backward compatibility: new directories and file types MAY be added, but existing paths and formats MUST NOT change their semantics. --- ## Appendix A: Complete Example Pod Directory Tree The following shows a fully-populated Pod export from Cascade Checkup, including all optional elements: ``` CascadeCheckup-Export-20260219T100000Z/ | |-- .well-known/ | |-- solid # Pod discovery metadata (JSON-LD) | |-- .acl # Root ACL (owner-only) | |-- profile/ | |-- card.ttl # WebID profile card (public — identity + discovery links) | |-- extended.ttl # Extended profile (owner-only — PHI: DOB, address, phone, email) | |-- health.ttl # Health profile (owner-only — full Cascade PatientProfile) | |-- .acl # Profile ACL (public read for card.ttl only) | |-- settings/ | |-- preferences # Private preferences (owner-only — links privateTypeIndex) | |-- publicTypeIndex.ttl # Public type registrations | |-- privateTypeIndex.ttl # Private type registrations | |-- .acl # Settings ACL (owner-only) | |-- clinical/ | |-- medications.ttl # clinical:Medication | |-- conditions.ttl # health:ConditionRecord | |-- allergies.ttl # health:AllergyRecord | |-- lab-results.ttl # health:LabResultRecord | |-- immunizations.ttl # health:ImmunizationRecord | |-- vital-signs.ttl # clinical:VitalSign | |-- patient-profile.ttl # cascade:PatientProfile | |-- insurance.ttl # coverage:InsurancePlan | |-- family-history.ttl # health:FamilyHistoryRecord | |-- screenings.ttl # clinical:ScreeningResult | |-- diagnostic-results.ttl # clinical:DiagnosticResult | |-- .acl # Container ACL (owner-only) | |-- wellness/ | |-- heart-rate.ttl # health:HeartRateData | |-- blood-pressure.ttl # health:BloodPressureData | |-- activity.ttl # health:ActivityData | |-- sleep.ttl # health:SleepData | |-- hrv.ttl # health:HRVData | |-- body-measurements.ttl # health:BodyMeasurements | |-- supplements.ttl # clinical:Supplement | |-- .acl # Container ACL (owner-only) | |-- adherence/ | |-- daily-check-ins.ttl # checkup:DailyCheckIn | |-- .acl # Container ACL (owner-only) | |-- visit-prep/ | |-- discussion-topics.ttl # checkup:DiscussionTopic | |-- visit-prep-notes.ttl # checkup:VisitPrepNotes | |-- visit-issues.ttl # checkup:VisitIssue | |-- suggested-questions.ttl # checkup:SuggestedQuestion | |-- .acl # Container ACL (owner-only) | |-- index.ttl # Root LDP container listing |-- manifest.ttl # Export provenance metadata |-- README.md # Human-readable documentation ``` A POTS Check Pod would look different: ``` Cascade-Pod-Export-20260219T100000Z/ | |-- .well-known/ | |-- solid | |-- .acl | |-- profile/ | |-- card.ttl | |-- extended.ttl | |-- .acl | |-- settings/ | |-- preferences | |-- publicTypeIndex.ttl | |-- privateTypeIndex.ttl | |-- .acl | |-- wellness/ | |-- diagnostics/ | | |-- pots-checks/ | | | |-- index.ttl # LDP Container listing test files | | | |-- test-20260115T103000Z.ttl | | | |-- test-20260201T141500Z.ttl | | | |-- .acl | | |-- .acl | |-- .acl | |-- README.md ``` --- ## Appendix B: Type Index Registration Format ### B.1 Registration Structure Each type registration in a type index follows this pattern: ```turtle <#{fragment-id}> a solid:TypeRegistration ; solid:forClass {vocabulary}:{ClassName} ; solid:instance ; dct:title "{Human-readable title}" ; dct:description "{Optional description}" . ``` **Properties:** | Property | Status | Description | |----------|--------|-------------| | `a solid:TypeRegistration` | REQUIRED | Declares this as a type registration | | `solid:forClass` | REQUIRED | The RDF class being registered | | `solid:instance` | REQUIRED* | Path to a specific resource file | | `solid:instanceContainer` | REQUIRED* | Path to a container holding instances | | `dct:title` | RECOMMENDED | Human-readable title | | `dct:description` | OPTIONAL | Longer description | *One of `solid:instance` or `solid:instanceContainer` MUST be present. Use `solid:instance` for aggregate files (one file containing all records) and `solid:instanceContainer` for directories containing individual record files. ### B.2 Standard Clinical Registrations ```turtle @prefix solid: . @prefix clinical: . @prefix cascade: . @prefix coverage: . @prefix dct: . <#medications> a solid:TypeRegistration ; solid:forClass clinical:Medication ; solid:instance ; dct:title "Medication Records" . <#conditions> a solid:TypeRegistration ; solid:forClass health:ConditionRecord ; solid:instance ; dct:title "Condition Records" . <#allergies> a solid:TypeRegistration ; solid:forClass health:AllergyRecord ; solid:instance ; dct:title "Allergy Records" . <#lab-results> a solid:TypeRegistration ; solid:forClass health:LabResultRecord ; solid:instance ; dct:title "Lab Results" . <#immunizations> a solid:TypeRegistration ; solid:forClass health:ImmunizationRecord ; solid:instance ; dct:title "Immunization Records" . <#vital-signs> a solid:TypeRegistration ; solid:forClass clinical:VitalSign ; solid:instance ; dct:title "Vital Signs" . <#patient-profile> a solid:TypeRegistration ; solid:forClass cascade:PatientProfile ; solid:instance ; dct:title "Patient Profile" . <#insurance> a solid:TypeRegistration ; solid:forClass coverage:InsurancePlan ; solid:instance ; dct:title "Insurance Information" . <#family-history> a solid:TypeRegistration ; solid:forClass health:FamilyHistoryRecord ; solid:instance ; dct:title "Family History" . <#procedures> a solid:TypeRegistration ; solid:forClass clinical:Procedure ; solid:instance ; dct:title "Procedures" . ``` ### B.3 Standard Wellness Registrations ```turtle @prefix solid: . @prefix health: . @prefix dct: . <#heart-rate> a solid:TypeRegistration ; solid:forClass health:HeartRateData ; solid:instance ; dct:title "Heart Rate Data" . <#blood-pressure> a solid:TypeRegistration ; solid:forClass health:BloodPressureData ; solid:instance ; dct:title "Blood Pressure Data" . <#activity> a solid:TypeRegistration ; solid:forClass health:ActivityData ; solid:instance ; dct:title "Activity Data" . <#sleep> a solid:TypeRegistration ; solid:forClass health:SleepData ; solid:instance ; dct:title "Sleep Data" . <#hrv> a solid:TypeRegistration ; solid:forClass health:HRVData ; solid:instance ; dct:title "Heart Rate Variability" . <#body-measurements> a solid:TypeRegistration ; solid:forClass health:BodyMeasurements ; solid:instance ; dct:title "Body Measurements" . <#supplements> a solid:TypeRegistration ; solid:forClass clinical:Supplement ; solid:instance ; dct:title "Supplements" . ``` ### B.4 Domain-Specific Registrations ```turtle @prefix solid: . @prefix checkup: . @prefix pots: . @prefix dct: . # Checkup-specific registrations <#daily-check-ins> a solid:TypeRegistration ; solid:forClass checkup:DailyCheckIn ; solid:instance ; dct:title "Daily Check-ins" . <#discussion-topics> a solid:TypeRegistration ; solid:forClass checkup:DiscussionTopic ; solid:instance ; dct:title "Discussion Topics" . # POTS Check-specific registrations <#pots-tests> a solid:TypeRegistration ; solid:forClass pots:POTSCheckResult ; solid:instanceContainer ; dct:title "POTS Test Results" ; dct:description "NASA Lean Test results for POTS screening" . ``` --- ## Appendix C: Manifest TTL Format ### C.1 Minimal Manifest The smallest useful manifest records the export timestamp and schema version: ```turtle @prefix cascade: . @prefix prov: . @prefix xsd: . @prefix dct: . <#export> a cascade:PodExport ; prov:generatedAtTime "2026-02-19T10:00:00Z"^^xsd:dateTime ; cascade:exportFormat "solidPod" ; cascade:schemaVersion "1.3" ; dct:description "Cascade Protocol Pod Export" ; cascade:resourceCount 15 . ``` ### C.2 Full Manifest with Provenance A complete manifest includes the generating software agent and content summary: ```turtle @prefix prov: . @prefix cascade: . @prefix xsd: . @prefix dct: . @prefix rdfs: . <#manifest> a cascade:ExportManifest ; dct:title "Cascade Checkup Export" ; dct:created "2026-02-19T10:00:00Z"^^xsd:dateTime ; cascade:schemaVersion "1.3" ; cascade:exportFormat "solidPod" ; cascade:containerCount "15"^^xsd:integer ; cascade:totalRecords "142"^^xsd:integer ; cascade:includedCategories "12"^^xsd:integer ; prov:wasGeneratedBy [ a prov:Activity ; prov:startedAtTime "2026-02-19T10:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; rdfs:label "Cascade Checkup" ; cascade:exporterVersion "1.0" ] ] . ``` ### C.3 Manifest Properties Reference | Property | Type | Required | Description | |----------|------|----------|-------------| | `a` | `cascade:PodExport` or `cascade:ExportManifest` | REQUIRED | Resource type | | `dct:created` or `prov:generatedAtTime` | `xsd:dateTime` | REQUIRED | Export timestamp | | `cascade:schemaVersion` | Literal | REQUIRED | Protocol schema version | | `cascade:exportFormat` | Literal | RECOMMENDED | `"solidPod"` or `"directory"` | | `cascade:resourceCount` or `cascade:containerCount` | `xsd:integer` | RECOMMENDED | Number of data files | | `cascade:totalRecords` | `xsd:integer` | OPTIONAL | Total record count | | `cascade:includedCategories` | `xsd:integer` | OPTIONAL | Number of PHI categories | | `dct:title` | Literal | OPTIONAL | Human-readable export name | | `dct:description` | Literal | OPTIONAL | Longer description | | `prov:wasGeneratedBy` | Blank node or URI | RECOMMENDED | Provenance activity chain | --- ## Appendix D: ACL File Templates ### D.1 Owner-Only ACL (Default) Used for root, settings, and data container ACLs: ```turtle @prefix acl: . @prefix foaf: . # Owner has full control <#owner> a acl:Authorization ; acl:agent <{OWNER_WEBID}> ; acl:accessTo <./> ; acl:default <./> ; acl:mode acl:Read, acl:Write, acl:Control . ``` Replace `{OWNER_WEBID}` with the Pod owner's WebID URI (e.g., `https://example.org/profile/card#me`). ### D.2 Profile ACL (Authenticated Read) Used for the `profile/` directory: ```turtle @prefix acl: . <#owner> a acl:Authorization ; acl:agent <{OWNER_WEBID}> ; acl:accessTo <./card.ttl> ; acl:mode acl:Read, acl:Write, acl:Control . <#public> a acl:Authorization ; acl:agentClass acl:AuthenticatedAgent ; acl:accessTo <./card.ttl> ; acl:mode acl:Read . ``` ### D.3 Shared Container ACL (Grant Read to Specific Agent) For sharing data with a specific agent (e.g., healthcare provider): ```turtle @prefix acl: . <#owner> a acl:Authorization ; acl:agent <{OWNER_WEBID}> ; acl:accessTo <./> ; acl:default <./> ; acl:mode acl:Read, acl:Write, acl:Control . <#provider-read> a acl:Authorization ; acl:agent <{PROVIDER_WEBID}> ; acl:accessTo <./> ; acl:default <./> ; acl:mode acl:Read . ``` This pattern is not yet generated by the SDK but illustrates the intended sharing model for future Solid server integration. --- *This specification is maintained as part of the Cascade Protocol documentation at [cascadeprotocol.org](https://cascadeprotocol.org). For implementation details, see the [CascadeSDK source](https://github.com/CascadeAgenticLabs/cascade-sdk-swift) `PodExporter.swift` and `PodNamespace.swift`.* # ============================================================================ # SECTION 7: CONFORMANCE TEST SUITE # ============================================================================ # ============================================================================ # SECTION 8: SDK DOCUMENTATION # ============================================================================ ## TypeScript SDK ## Python SDK ## Getting Started: typescript Getting Started with TypeScript - Cascade Protocol /* ── Guide-specific styles ────────────────────────────────────────── */ .guide-step { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .step-header { display: flex; align-items: center; gap: var(--space-md); margin-bottom: var(--space-lg); } .step-number { width: 40px; height: 40px; background: var(--primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.125rem; font-weight: 700; flex-shrink: 0; margin: 0; /* override global .step-number { margin: 0 auto } */ } .step-header h2 { margin: 0; border: none; padding: 0; font-size: 1.5rem; } /* ── Code blocks with copy button ─────────────────────────────────── */ .code-block { position: relative; margin: var(--space-lg) 0; } .code-block pre { margin: 0; padding: var(--space-lg); padding-right: 3.5rem; } .code-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-xs); } .copy-btn { position: absolute; top: var(--space-sm); right: var(--space-sm); background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: var(--code-text); padding: 0.3rem 0.6rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-family: var(--font-sans); cursor: pointer; transition: var(--transition); display: flex; align-items: center; gap: 0.3rem; } .copy-btn:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.4); } .copy-btn.copied { background: var(--success-border); border-color: var(--success-border); color: white; } .copy-btn svg { width: 14px; height: 14px; } /* ── Expected output block ────────────────────────────────────────── */ .output-block pre { background: #0f172a; border-left: 3px solid var(--accent); } .output-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); margin-bottom: var(--space-xs); } /* ── Privacy sidebar ──────────────────────────────────────────────── */ .privacy-box { background: #e6f7f7; border: 1px solid #b2dfdb; border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-2xl) 0; } .privacy-box-header { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-md); } .privacy-box-header h3 { margin: 0; color: var(--primary); font-size: 1.125rem; } .privacy-box-header svg { width: 24px; height: 24px; color: var(--primary); flex-shrink: 0; } .privacy-box ul { margin: 0; padding-left: var(--space-lg); } .privacy-box li { margin-bottom: var(--space-sm); color: var(--text-secondary); } .privacy-box code { font-size: 0.85em; } /* ── Next steps cards ─────────────────────────────────────────────── */ .next-steps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-md); margin-top: var(--space-lg); } .next-step-card { display: block; background: white; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-lg); transition: var(--transition); text-decoration: none; } .next-step-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-md); } .next-step-card h4 { margin: 0 0 var(--space-xs) 0; font-size: 0.95rem; color: var(--text); } .next-step-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); } /* ── Prerequisites list ───────────────────────────────────────────── */ .prereq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .prereq-item { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); display: flex; align-items: center; gap: var(--space-sm); } .prereq-item code { font-weight: 600; } .prereq-check { color: var(--success-border); font-weight: 700; flex-shrink: 0; } /* ── File tree ────────────────────────────────────────────────────── */ .file-tree { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-lg); font-family: var(--font-mono); font-size: 0.875rem; line-height: 1.8; margin: var(--space-lg) 0; } .file-tree .dir { color: var(--primary); font-weight: 600; } .file-tree .file { color: var(--text-secondary); } .file-tree .highlight { color: var(--accent-dark); font-weight: 600; } /* ── Inline version badge ─────────────────────────────────────────── */ .version-badge { display: inline-block; padding: 0.15rem 0.5rem; font-size: 0.7rem; font-weight: 600; border-radius: var(--radius-full); background: var(--accent); color: white; vertical-align: middle; margin-left: var(--space-xs); } /* ── Responsive ───────────────────────────────────────────────────── */ @media (max-width: 768px) { .prereq-grid { grid-template-columns: 1fr; } .next-steps-grid { grid-template-columns: 1fr; } .step-header h2 { font-size: 1.25rem; } } Cascade Protocol Home Documentation Contact Getting Started with TypeScript Create your first health record, serialize it to RDF, and build a local Pod — in five minutes Documentation / Getting Started / TypeScript Cascade Protocol SDK v1.4.0 — Requires Node.js 18+. All processing runs locally on your machine. Prerequisites Before you begin, make sure you have the following installed: ✓ Node.js 18+Runtime ✓ npm or yarnPackage manager ✓ TypeScript 5.4+Optional, for type checking Verify your Node.js version: node --version # v18.0.0 or higher Copy 1 Install the SDK Create a new project directory and install the Cascade Protocol SDK: Terminal mkdir my-cascade-app && cd my-cascade-app npm init -y npm install @the-cascade-protocol/sdk Copy If you want to type-check your scripts with TypeScript, also install the compiler: Terminal npm install -D typescript tsx npx tsc --init Copy The SDK has zero runtime dependencies. It provides typed interfaces for all Cascade Protocol data types, vocabulary constants, and serialization utilities for RDF/Turtle format. 2 Create your first record Create a file called create-record.ts and add the following. This creates a medication record and serializes it to RDF/Turtle format: create-record.ts import { serialize } from '@the-cascade-protocol/sdk'; import type { Medication } from '@the-cascade-protocol/sdk'; // Create a medication record with required fields const medication: Medication = { id: 'urn:uuid:' + crypto.randomUUID(), type: 'MedicationRecord', medicationName: 'Lisinopril', isActive: true, dose: '10 mg', frequency: 'Once daily', route: 'Oral', startDate: '2025-06-15T00:00:00Z', rxNormCode: 'http://www.nlm.nih.gov/research/umls/rxnorm/314076', dataProvenance: 'ClinicalGenerated', schemaVersion: '1.3', }; // Serialize the record to RDF/Turtle const turtle = serialize(medication); console.log(turtle); Copy Run the script: Terminal npx tsx create-record.ts Copy Expected output @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix clinical: <https://ns.cascadeprotocol.org/clinical/v1#> . @prefix rxnorm: <http://www.nlm.nih.gov/research/umls/rxnorm/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890> a clinical:Medication ; clinical:drugName "Lisinopril" ; clinical:status "active" ; clinical:dosage "10 mg" ; clinical:frequency "Once daily" ; clinical:route "Oral" ; clinical:startDate "2025-06-15T00:00:00Z"^^xsd:dateTime ; clinical:rxNormCode <http://www.nlm.nih.gov/research/umls/rxnorm/314076> ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:schemaVersion "1.3" . The serialize() function converts the typed JavaScript object into a complete Turtle document with the correct namespace prefixes, RDF types, and XSD datatype annotations. Each field maps to a predicate in the Cascade Protocol vocabulary. About the UUID Each record needs a unique id in URN UUID format. The crypto.randomUUID() function is built into Node.js 18+ and generates a cryptographically random identifier. Your actual output will show a different UUID than the example above. 3 Build a Pod A Pod is the Cascade Protocol's local data container. It organizes health records into directories by category (clinical, wellness, profile) and generates an index file that describes the Pod's contents. Create a new file called build-pod.ts: build-pod.ts import { PodBuilder } from '@the-cascade-protocol/sdk/pod'; import type { Medication } from '@the-cascade-protocol/sdk'; import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; // 1. Create a medication record const medication: Medication = { id: 'urn:uuid:' + crypto.randomUUID(), type: 'MedicationRecord', medicationName: 'Lisinopril', isActive: true, dose: '10 mg', frequency: 'Once daily', route: 'Oral', startDate: '2025-06-15T00:00:00Z', rxNormCode: 'http://www.nlm.nih.gov/research/umls/rxnorm/314076', dataProvenance: 'ClinicalGenerated', schemaVersion: '1.3', }; // 2. Build the Pod const pod = new PodBuilder({ title: 'My Health Pod', description: 'Getting started example', }); pod.addMedication(medication); const files = pod.build(); // 3. Write all files to disk for (const file of files) { const fullPath = join('./my-pod', file.path); mkdirSync(dirname(fullPath), { recursive: true }); writeFileSync(fullPath, file.content); } console.log('Pod created with', files.length, 'files:'); for (const file of files) { console.log(' ', file.path); } Copy Run it: Terminal npx tsx build-pod.ts Copy Expected output Pod created with 2 files: clinical/medications.ttl index.ttl The PodBuilder handles: File routing: Medications go to clinical/medications.ttl, vital signs from devices go to wellness/vital-signs.ttl, and so on. Prefix merging: When multiple records of the same type are added, their Turtle output is merged into a single file with shared namespace prefixes. Index generation: An index.ttl file is created that describes the Pod container using the W3C Linked Data Platform vocabulary. Pod structure After running the script, your my-pod directory contains: my-pod/   index.ttl — Pod manifest (LDP container description)   clinical/     medications.ttl — Your medication record as RDF/Turtle You can inspect the generated Turtle file directly: Terminal cat my-pod/clinical/medications.ttl Copy 4 Verify with the CLI The Cascade Protocol CLI includes a SHACL-based validator that checks your Pod against the protocol's constraint shapes. Install it globally and validate your Pod: Terminal # Install the CLI globally npm install -g @the-cascade-protocol/cli Copy Terminal # Validate the Pod structure and data cascade validate ./my-pod Copy Expected output Validating Pod at ./my-pod ... clinical/medications.ttl 1 record PASS index.ttl container PASS Result: 1 file validated, 0 errors, 0 warnings You can also inspect the Pod metadata: Terminal # View Pod metadata cascade pod info ./my-pod Copy The validator uses SHACL (Shapes Constraint Language) to verify that each record conforms to the Cascade Protocol schema. It checks required fields, data types, value constraints, and vocabulary usage. What happened to your data? Everything just happened locally on your machine. Your medication record was written to ./my-pod/clinical/medications.ttl as RDF/Turtle. No data was sent to any server. The SDK and CLI operate entirely offline. No account was created. There is no sign-up, no API key, and no telemetry. You can delete ./my-pod to remove everything: rm -rf ./my-pod Read the Security & Compliance Guide for a full description of the trust model and data flow architecture. Next Steps Now that you have a working Pod, here is where to go next: Interactive Playground Try more data types: conditions, allergies, lab results, vital signs, and more. Security & Compliance Understand the trust model, PHI handling, encryption, and audit logging. Semantic Vocabularies Browse all supported data types: clinical, health, coverage, and core vocabularies. Versioning Policy Learn about schema version compatibility guarantees and migration paths. ← Back to Documentation © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { var buttons = document.querySelectorAll('.copy-btn'); buttons.forEach(function (btn) { btn.addEventListener('click', function () { var codeBlock = btn.closest('.code-block') || btn.closest('.output-block'); if (!codeBlock) return; var code = codeBlock.querySelector('code'); if (!code) return; var text = code.textContent || ''; navigator.clipboard.writeText(text).then(function () { var label = btn.querySelector('span'); var originalText = label.textContent; label.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(function () { label.textContent = originalText; btn.classList.remove('copied'); }, 2000); }); }); }); })(); ## Getting Started: python Getting Started with Python — Cascade Protocol /* ── Guide-specific styles ────────────────────────────────────────── */ .guide-step { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .step-header { display: flex; align-items: center; gap: var(--space-md); margin-bottom: var(--space-lg); } .step-number { width: 40px; height: 40px; background: var(--primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.125rem; font-weight: 700; flex-shrink: 0; margin: 0; /* override global .step-number { margin: 0 auto } */ } .step-header h2 { margin: 0; border: none; padding: 0; font-size: 1.5rem; } /* ── Code blocks with copy button ─────────────────────────────────── */ .code-block { position: relative; margin: var(--space-lg) 0; } .code-block pre { margin: 0; padding: var(--space-lg); padding-right: 3.5rem; } .code-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-xs); } .copy-btn { position: absolute; top: var(--space-sm); right: var(--space-sm); background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: var(--code-text); padding: 0.3rem 0.6rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-family: var(--font-sans); cursor: pointer; transition: var(--transition); display: flex; align-items: center; gap: 0.3rem; } .copy-btn:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.4); } .copy-btn.copied { background: var(--success-border); border-color: var(--success-border); color: white; } .copy-btn svg { width: 14px; height: 14px; } /* ── Expected output block ────────────────────────────────────────── */ .output-block pre { background: #0f172a; border-left: 3px solid var(--accent); } .output-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); margin-bottom: var(--space-xs); } /* ── Privacy box ──────────────────────────────────────────────────── */ .privacy-box { background: #e6f7f7; border: 1px solid #b2dfdb; border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-2xl) 0; } .privacy-box-header { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-md); } .privacy-box-header h3 { margin: 0; color: var(--primary); font-size: 1.125rem; } .privacy-box-header svg { width: 24px; height: 24px; color: var(--primary); flex-shrink: 0; } .privacy-box ul { margin: 0; padding-left: var(--space-lg); } .privacy-box li { margin-bottom: var(--space-sm); color: var(--text-secondary); } .privacy-box code { font-size: 0.85em; } /* ── Next steps cards ─────────────────────────────────────────────── */ .next-steps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-md); margin-top: var(--space-lg); } .next-step-card { display: block; background: white; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-lg); transition: var(--transition); text-decoration: none; } .next-step-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-md); } .next-step-card h4 { margin: 0 0 var(--space-xs) 0; font-size: 0.95rem; color: var(--text); } .next-step-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); } /* ── Prerequisites list ───────────────────────────────────────────── */ .prereq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .prereq-item { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); display: flex; align-items: center; gap: var(--space-sm); } .prereq-item code { font-weight: 600; } .prereq-check { color: var(--success-border); font-weight: 700; flex-shrink: 0; } /* ── Extras table ─────────────────────────────────────────────────── */ .extras-table { width: 100%; border-collapse: collapse; margin: var(--space-lg) 0; font-size: 0.875rem; } .extras-table th { background: var(--bg-secondary); color: var(--text-secondary); font-weight: 600; text-align: left; padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; } .extras-table td { padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); } .extras-table tr:hover td { background: #f8fdfd; } /* ── Supported types table ────────────────────────────────────────── */ .types-table { width: 100%; border-collapse: collapse; margin: var(--space-lg) 0; font-size: 0.875rem; } .types-table th { background: var(--bg-secondary); color: var(--text-secondary); font-weight: 600; text-align: left; padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; } .types-table td { padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); } .types-table tr:hover td { background: #f8fdfd; } /* ── Notebook cards ───────────────────────────────────────────────── */ .notebook-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .notebook-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-lg); } .notebook-card h4 { margin: 0 0 var(--space-xs) 0; font-size: 0.9rem; color: var(--text); } .notebook-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); } .notebook-card code { display: block; font-size: 0.8rem; margin-top: var(--space-xs); color: var(--primary); } /* ── Inline version badge ─────────────────────────────────────────── */ .version-badge { display: inline-block; padding: 0.15rem 0.5rem; font-size: 0.7rem; font-weight: 600; border-radius: var(--radius-full); background: var(--accent); color: white; vertical-align: middle; margin-left: var(--space-xs); } /* ── Responsive ───────────────────────────────────────────────────── */ @media (max-width: 768px) { .prereq-grid { grid-template-columns: 1fr; } .next-steps-grid { grid-template-columns: 1fr; } .notebook-grid { grid-template-columns: 1fr; } .step-header h2 { font-size: 1.25rem; } } Cascade Protocol Home Documentation Contact Getting Started with Python Create your first health record, serialize it to RDF, query a Pod, and explore data with pandas — in six steps. Documentation / Getting Started / Python Cascade Protocol Python SDK v1.3.0 — Requires Python 3.9+. All processing runs locally on your machine. Prerequisites Before you begin, make sure you have the following: ✓ Python 3.9+Runtime ✓ pipPackage manager ✓ Virtual environmentRecommended Verify your Python version: python3 --version # Python 3.9.0 or higher Copy 1 Install the SDK Install the core SDK with pip: Terminal pip install cascade-protocol Copy The SDK supports optional extras for specific use cases: Extra Install command Adds pandas pip install "cascade-protocol[pandas]" DataFrame conversion via to_dataframe() validation pip install "cascade-protocol[validation]" SHACL validation via pyshacl notebooks pip install "cascade-protocol[notebooks]" Jupyter notebook support all pip install "cascade-protocol[all]" All of the above The SDK has zero mandatory runtime dependencies beyond the Python standard library. Optional extras are installed only when you need them. 2 Create your first record Create a file called create_record.py. This creates a medication record and serializes it to RDF/Turtle format: create_record.py from cascade_protocol import Medication, serialize # Create a medication record with required fields med = Medication( id="urn:uuid:med0-0001-aaaa-bbbb-ccccddddeeee", medication_name="Metoprolol Succinate", is_active=True, dose="25mg", data_provenance="ClinicalGenerated", schema_version="1.3", ) # Serialize to Turtle turtle = serialize(med) print(turtle) Copy Run it: Terminal python3 create_record.py Copy Expected output @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix clinical: <https://ns.cascadeprotocol.org/clinical/v1#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <urn:uuid:med0-0001-aaaa-bbbb-ccccddddeeee> a clinical:Medication ; clinical:drugName "Metoprolol Succinate" ; clinical:status "active" ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:schemaVersion "1.3" ; clinical:dosage "25mg" . The serialize() function converts the Python object into a complete Turtle document with correct namespace prefixes, RDF types, and XSD datatype annotations. Python snake_case field names map automatically to the camelCase RDF predicates used in the Cascade Protocol vocabulary. Supported data types The SDK supports twelve data model classes covering medications, conditions, allergies, lab results, vital signs, immunizations, procedures, family history, insurance coverage, patient profile, activity, and sleep. See the schema reference for the full list. 3 Validate your data The SDK includes a validate() function that checks Turtle output against the Cascade Protocol's SHACL constraint shapes. Install the validation extra first, then call validate() on any serialized Turtle string: Terminal pip install "cascade-protocol[validation]" Copy validate_record.py from cascade_protocol import Medication, serialize, validate med = Medication( id="urn:uuid:med0-0001-aaaa-bbbb-ccccddddeeee", medication_name="Metoprolol Succinate", is_active=True, dose="25mg", data_provenance="ClinicalGenerated", schema_version="1.3", ) turtle = serialize(med) # Validate structural integrity against SHACL shapes result = validate(turtle) print("Valid:", result.is_valid) print("Errors:", result.errors) Copy Expected output Valid: True Errors: [] The validator uses SHACL (Shapes Constraint Language) to check required fields, data types, value constraints, and vocabulary usage against the Cascade Protocol schema. 4 Open a Pod A Pod is the Cascade Protocol's local data container — a directory of RDF/Turtle files organized by data category. Use Pod.open() to open an existing Pod and pod.query() to read records by type: open_pod.py from cascade_protocol import Pod # Open a Cascade Pod from a local directory pod = Pod.open("./my-pod") # Query by data type meds = pod.query("medications") vitals = pod.query("vital-signs") profile = pod.query("patient-profile") # Iterate records for med in meds: print(med.medication_name, med.dose) # Query other data categories conditions = pod.query("conditions") labs = pod.query("lab-results") allergies = pod.query("allergies") Copy The pod.query() method reads the appropriate .ttl file from the Pod directory, parses the Turtle, and returns a typed result set. Results are Python objects with the same field names as the model classes. You can also parse Turtle strings directly if you have Turtle data from another source: parse_turtle.py from cascade_protocol.deserializer import parse, parse_one # Parse multiple records from a Turtle string meds = parse(turtle_string, "MedicationRecord") # Parse a single record (raises if zero or more than one) med = parse_one(turtle_string, "MedicationRecord") print(med.medication_name) Copy 5 Pandas integration Install the pandas extra, then call .to_dataframe() on any query result to convert records into a pandas DataFrame. This is useful for data analysis, visualization, and export workflows: Terminal pip install "cascade-protocol[pandas]" Copy dataframe_example.py from cascade_protocol import Pod, Medication pod = Pod.open("./my-pod") meds = pod.query("medications") # Convert to pandas DataFrame df = meds.to_dataframe() print(df.head()) # Standard pandas operations work as expected active_meds = df[df["is_active"] == True] print(f"Active medications: {len(active_meds)}") # Reconstruct typed models from DataFrame rows restored = Medication.from_dataframe(df) for med in restored: print(med.medication_name, med.dose) Copy The from_dataframe() class method reconstructs typed model instances from a DataFrame, preserving all field values and provenance metadata. This enables round-trip workflows: Pod → DataFrame → analysis → back to models → re-serialize. 6 Jupyter notebooks The SDK ships with three Jupyter notebooks that demonstrate common workflows interactively. Install the notebooks extra, then open them from the SDK directory: Terminal pip install "cascade-protocol[notebooks]" Copy Quick Start Covers installation, creating records, serialization, and validation with runnable cells. notebooks/01-quickstart.ipynb Pod Exploration Opening a Pod, querying multiple data types, and building summary visualizations with pandas and matplotlib. notebooks/02-pod-exploration.ipynb Data Analysis Statistical analysis of wellness time-series data — trend detection, provenance filtering, and correlation across data types. notebooks/03-data-analysis.ipynb The notebooks use the reference patient Pod included in the SDK repository, so you can run them immediately without creating your own data. Privacy & Security The Python SDK is designed with the same privacy-first principles as the rest of the Cascade Protocol. Zero network calls during normal operation. All processing is local. No telemetry or analytics. The package does not phone home. Data never leaves your machine. Serialization and validation run entirely in-process. Delete your Pod directory at any time to remove all data: rm -rf ./my-pod Read the Security & Compliance Guide for the full trust model and data flow architecture. Next Steps Now that you have the SDK working, here is where to go next: Schema Reference Browse all twelve supported data types and their RDF predicates, with Python field name mappings. Security & Compliance Understand the trust model, PHI handling, encryption, and audit logging. Conformance Test Suite Canonical fixtures and round-trip tests for all supported data types. Protocol Comparison How Cascade compares to FHIR R4, HealthKit, OpenEHR, and ad-hoc JSON for health data workflows. ← Back to Documentation TypeScript Guide © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { var buttons = document.querySelectorAll('.copy-btn'); buttons.forEach(function (btn) { btn.addEventListener('click', function () { var codeBlock = btn.closest('.code-block') || btn.closest('.output-block'); if (!codeBlock) return; var code = codeBlock.querySelector('code'); if (!code) return; var text = code.textContent || ''; navigator.clipboard.writeText(text).then(function () { var label = btn.querySelector('span'); var originalText = label.textContent; label.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(function () { label.textContent = originalText; btn.classList.remove('copied'); }, 2000); }); }); }); })(); ## Getting Started: cli Getting Started with the CLI - Cascade Protocol /* ── Guide-specific styles ────────────────────────────────────────── */ .guide-step { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .step-header { display: flex; align-items: center; gap: var(--space-md); margin-bottom: var(--space-lg); } .step-number { width: 40px; height: 40px; background: var(--primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.125rem; font-weight: 700; flex-shrink: 0; margin: 0; /* override global .step-number { margin: 0 auto } */ } .step-header h2 { margin: 0; border: none; padding: 0; font-size: 1.5rem; } /* ── Code blocks with copy button ─────────────────────────────────── */ .code-block { position: relative; margin: var(--space-lg) 0; } .code-block pre { margin: 0; padding: var(--space-lg); padding-right: 3.5rem; } .code-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-xs); } .copy-btn { position: absolute; top: var(--space-sm); right: var(--space-sm); background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: var(--code-text); padding: 0.3rem 0.6rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-family: var(--font-sans); cursor: pointer; transition: var(--transition); display: flex; align-items: center; gap: 0.3rem; } .copy-btn:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.4); } .copy-btn.copied { background: var(--success-border); border-color: var(--success-border); color: white; } .copy-btn svg { width: 14px; height: 14px; } /* ── Expected output block ────────────────────────────────────────── */ .output-block pre { background: #0f172a; border-left: 3px solid var(--accent); } .output-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); margin-bottom: var(--space-xs); } /* ── Privacy sidebar ──────────────────────────────────────────────── */ .privacy-box { background: #e6f7f7; border: 1px solid #b2dfdb; border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-2xl) 0; } .privacy-box-header { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-md); } .privacy-box-header h3 { margin: 0; color: var(--primary); font-size: 1.125rem; } .privacy-box-header svg { width: 24px; height: 24px; color: var(--primary); flex-shrink: 0; } .privacy-box ul { margin: 0; padding-left: var(--space-lg); } .privacy-box li { margin-bottom: var(--space-sm); color: var(--text-secondary); } .privacy-box code { font-size: 0.85em; } /* ── Next steps cards ─────────────────────────────────────────────── */ .next-steps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-md); margin-top: var(--space-lg); } .next-step-card { display: block; background: white; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-lg); transition: var(--transition); text-decoration: none; } .next-step-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-md); } .next-step-card h4 { margin: 0 0 var(--space-xs) 0; font-size: 0.95rem; color: var(--text); } .next-step-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); } /* ── Prerequisites list ───────────────────────────────────────────── */ .prereq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .prereq-item { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); display: flex; align-items: center; gap: var(--space-sm); } .prereq-item code { font-weight: 600; } .prereq-check { color: var(--success-border); font-weight: 700; flex-shrink: 0; } /* ── Command reference table ──────────────────────────────────────── */ .cmd-table { width: 100%; border-collapse: collapse; margin: var(--space-lg) 0; font-size: 0.9rem; } .cmd-table th { text-align: left; padding: var(--space-sm) var(--space-md); background: var(--bg-secondary); border: 1px solid var(--border); font-weight: 600; color: var(--text); } .cmd-table td { padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); vertical-align: top; } .cmd-table td:first-child { font-family: var(--font-mono); font-size: 0.85rem; white-space: nowrap; color: var(--primary); } .cmd-table tr:hover td { background: var(--bg-secondary); } /* ── Inline version badge ─────────────────────────────────────────── */ .version-badge { display: inline-block; padding: 0.15rem 0.5rem; font-size: 0.7rem; font-weight: 600; border-radius: var(--radius-full); background: var(--accent); color: white; vertical-align: middle; margin-left: var(--space-xs); } /* ── Responsive ───────────────────────────────────────────────────── */ @media (max-width: 768px) { .prereq-grid { grid-template-columns: 1fr; } .next-steps-grid { grid-template-columns: 1fr; } .step-header h2 { font-size: 1.25rem; } .cmd-table { font-size: 0.8rem; } } Cascade Protocol Home Documentation Contact Getting Started with the CLI Validate health data, convert FHIR records, and manage Pods — from the command line, with zero network calls Documentation / Getting Started / CLI Cascade Protocol CLI — current version on npm. Requires Node.js 18+. All processing runs locally on your machine. Prerequisites Before you begin, make sure you have the following installed: ✓ Node.js 18+Runtime ✓ npmIncluded with Node.js Verify your Node.js version: node --version # v18.0.0 or higher Copy 1 Install the CLI Install the CLI globally so the cascade command is available anywhere: Terminal npm install -g @the-cascade-protocol/cli Copy Verify the installation: Terminal cascade --version cascade --help Copy The CLI bundles the Cascade Protocol SHACL shapes and all conversion logic. It has no external network dependencies — every operation runs entirely on your machine. Available commands Command Description cascade validate Validate Turtle files against SHACL shapes cascade convert Convert between FHIR R4 JSON and Cascade Turtle cascade pod init Create a new Pod directory structure cascade pod info Show Pod metadata and record counts cascade pod query Query records from a Pod by type cascade pod export Export a Pod as a ZIP archive or directory cascade pod import Convert FHIR or C-CDA files and import them directly into a Pod, splitting records by type and updating type indexes cascade pod conflicts List unresolved reconciliation conflicts in a Pod; exits 1 if any (CI-friendly) cascade pod resolve Record a conflict resolution decision to the Pod's settings cascade pod extract Extract structured clinical entities from narrative text sections using AI (auto-starts agent if needed) cascade reconcile Merge Cascade Turtle files from multiple EHR systems, resolving duplicates and conflicts cascade conformance run Run the conformance test suite against fixtures cascade serve --mcp Start a local MCP-compatible agent server cascade agent Natural language interface — describe tasks in plain English; the agent runs the right commands. See Cascade Agent guide. cascade capabilities Print machine-readable tool descriptions as JSON 2 Initialize a Pod A Pod is a local directory that organizes your health data into a standard structure. Create one with pod init: Terminal cascade pod init ./my-health-pod Copy Expected output Cascade Pod initialized at: ./my-health-pod Created: .well-known/solid profile/card.ttl profile/extended.ttl settings/preferences settings/publicTypeIndex.ttl settings/privateTypeIndex.ttl clinical/ wellness/ index.ttl README.md Next steps: 1. Edit profile/card.ttl to set patient name and demographics 2. Add data files to clinical/ and wellness/ directories 3. Run: cascade pod info ./my-health-pod View Pod metadata at any time with pod info: Terminal cascade pod info ./my-health-pod Copy 3 Convert FHIR data Apple Health, Epic MyChart, and most EHR patient portals can export your records as FHIR R4 JSON. Use convert to transform them into Cascade Protocol Turtle: Terminal cascade convert patient-export.json --from fhir --to cascade Copy This outputs Turtle to stdout. Redirect it to a file or pipe it directly into your Pod: Terminal # Save to a file cascade convert patient-export.json --from fhir --to cascade \ > ./my-health-pod/clinical/medications.ttl # Or read from stdin (for piping) cat patient-export.json | cascade convert --from fhir --to cascade Copy Example output (FHIR MedicationStatement → Cascade Turtle) @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix health: <https://ns.cascadeprotocol.org/health/v1#> . @prefix clinical: <https://ns.cascadeprotocol.org/clinical/v1#> . @prefix rxnorm: <http://www.nlm.nih.gov/research/umls/rxnorm/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <urn:uuid:3d441e1f-1578-46f3-b2fb-32f59ae808a5> a clinical:Medication ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:sourceSystem "my-ehr" ; clinical:drugName "Metformin HCl 500mg" ; clinical:rxNormCode rxnorm:860975 ; clinical:dosage "500 mg" ; health:startDate "2024-01-15" . Converted 1 resource (fhir -> turtle) The converter handles FHIR Bundles (multiple resources at once) as well as single resources. It maps FHIR resource types to the appropriate Cascade vocabularies and preserves coded values like RxNorm, SNOMED CT, LOINC, and ICD-10. To get machine-readable JSON output instead of raw Turtle, use --json: Terminal cascade convert patient-export.json --from fhir --to cascade --json Copy Import manifest Add --manifest to write a sidecar JSON file that records exactly what was converted, what types were preserved as Layer 1 passthrough, and what was intentionally excluded and why. Terminal cascade convert patient-export.json --from fhir --to cascade \ --source-system my-ehr --manifest \ > patient.ttl # Also writes: patient-export-manifest.json Copy patient-export-manifest.json { "sourceFile": "patient-export.json", "sourceSystem": "my-ehr", "convertedAt": "2026-03-12T10:00:00.000Z", "summary": { "total": 47, "fullyMapped": 41, "passthrough": 2, "excluded": 4 }, "byType": { "MedicationStatement": { "count": 8, "strategy": "mapped" }, "Condition": { "count": 12, "strategy": "mapped" }, "Encounter": { "count": 6, "strategy": "mapped" }, "Procedure": { "count": 7, "strategy": "mapped" }, "DiagnosticReport": { "count": 4, "strategy": "mapped" }, "Claim": { "count": 4, "strategy": "mapped" }, "QuestionnaireResponse": { "count": 2, "strategy": "passthrough" }, "CareTeam": { "count": 3, "strategy": "excluded", "reason": "Personnel registry. Care team context is captured via encounter provenance." }, "Medication": { "count": 1, "strategy": "excluded", "reason": "Standalone medication definitional resources. Clinical meaning is in MedicationRequest/Statement." } } } You can specify a custom manifest path with --manifest path/to/my-manifest.json. C-CDA documents Hospital and clinic systems often export records in C-CDA (Clinical Document Architecture) format instead of FHIR — look for .xml files or IHE XDM ZIP archives from Epic, Cerner, and similar EHRs. Use --from c-cda to convert them directly: Terminal # Single XML file cascade convert patient-summary.xml --from c-cda --to cascade > patient.ttl # IHE XDM ZIP archive (exported from hospital portal) cascade convert SUBSET01.ZIP --from c-cda --to cascade > patient.ttl # Flag narrative sections for AI extraction (requires cascade agent serve) cascade convert patient-summary.xml --from c-cda --to cascade \ --extract-narratives > patient.ttl Copy The C-CDA converter handles 12 section types (medications, conditions, allergies, lab results, immunizations, vital signs, procedures, encounters, implanted devices, and more). It includes vendor-specific normalization for Epic and Cerner exports, and looks up RxNorm ingredient names for medications whose display name is missing. The --extract-narratives flag marks free-text narrative sections in the output so they can later be processed by cascade pod extract. Exporting Cascade Turtle back to FHIR The same convert command handles the reverse direction. Use --from cascade --to fhir to produce a FHIR R4 Bundle from a Cascade Turtle file: Terminal cascade convert --from cascade --to fhir reconciled-patient.ttl \ > patient-fhir-export.json Copy Records converted with full Layer 2 mapping export with all original fields. Records preserved as Layer 1 passthrough (types without a full Cascade vocabulary mapping) are restored verbatim from the embedded cascade:fhirJson triple — the original FHIR JSON is preserved inline in the Turtle file for exactly this purpose. 4 Import FHIR Data into a Pod cascade pod import is the recommended way to populate a Pod from FHIR data. It combines conversion, type-based splitting, and Pod index registration into a single command — replacing the manual convert-then-copy workflow for most use cases. Recommended workflow: pod init → pod import → pod query. The pod import command handles conversion, splitting records by type, and updating the type index in one step. Single-source import Pass a FHIR JSON file and the Pod directory. Use --source-system to tag all imported records with the originating system name (used later for reconciliation): Terminal cascade pod import ./my-pod patient-records.json \ --source-system "mass-general" Copy Records are split by type into organized files under clinical/: Pod structure after import my-pod/ clinical/ patient-profile.ttl (1 record per patient) medications.ttl conditions.ttl allergies.ttl lab-results.ttl immunizations.ttl vital-signs.ttl procedures.ttl encounters.ttl documents.ttl lab-reports.ttl imaging.ttl claims.ttl benefits.ttl fhir-passthrough.ttl (unmapped FHIR types, preserved verbatim) settings/ publicTypeIndex.ttl (updated with solid:TypeRegistration entries) index.ttl (updated with ldp:contains references) Preview before importing Use --dry-run to see what would be written without making any changes: Terminal cascade pod import ./my-pod patient-records.json --dry-run Copy Multi-source import with automatic reconciliation Pass multiple FHIR files to import from several providers at once. The command automatically reconciles duplicates across sources using the same logic as cascade reconcile. Use --trust to set per-source trust scores, and --report to write a full import report: Terminal cascade pod import ./my-pod \ hospital-records.json \ primary-care.json \ --trust hospital-records=0.95,primary-care=0.85 \ --report import-report.json Copy Use --no-reconcile to skip reconciliation even when multiple input files are provided. Options reference OptionDescription --source-system <name>Tag all imported records with this system name (used for reconciliation) --reconcile-existingDeduplicate the new import against records already in the Pod (cross-batch deduplication) --no-reconcileSkip reconciliation even when multiple input files are provided --trust <scores>Per-source trust scores for multi-source reconciliation, e.g. hospital=0.95,clinic=0.85 --dry-runPreview the import without writing any files --report <file>Write an import report JSON to the specified file --passthrough full|minimalControls whether FHIR passthrough resources include the raw JSON blob (default: full) Reviewing Conflicts When reconciliation cannot automatically resolve a conflict (trust scores too similar), the records are flagged in settings/pending-conflicts.ttl. Use these commands to review and resolve them: Terminal # List all unresolved conflicts (exits 1 if any — useful in CI) cascade pod conflicts ./my-pod # Accept the record from a specific source cascade pod resolve ./my-pod --conflict <conflict-id> --keep hospital # Keep both records (no discard) cascade pod resolve ./my-pod --conflict <conflict-id> --keep both Copy Resolution decisions are persisted in settings/user-resolutions.ttl and are re-applied automatically on subsequent imports with --reconcile-existing, so you never have to resolve the same conflict twice. The Cascade Agent can also walk you through the queue interactively with cascade agent review --pod ./my-pod. 5 Validate your data The validate command checks Turtle files against the bundled SHACL shapes for all Cascade Protocol vocabularies. It catches missing required fields, type mismatches, and vocabulary violations. Validate a single file: Terminal cascade validate ./my-health-pod/clinical/medications.ttl Copy Expected output PASS ./my-health-pod/clinical/medications.ttl (192 triples) Validate an entire directory at once: Terminal cascade validate ./my-health-pod/clinical/ Copy Expected output (all passing) PASS clinical/allergies.ttl (29 triples) PASS clinical/conditions.ttl (64 triples) PASS clinical/immunizations.ttl (64 triples) PASS clinical/lab-results.ttl (187 triples) PASS clinical/medications.ttl (192 triples) Validation Summary Files: 5 total, 5 passed, 0 failed Use --verbose to see which SHACL shapes were applied and which RDF types were detected: Terminal cascade validate medications.ttl --verbose Copy Use --json for machine-readable output suitable for CI pipelines: Terminal cascade validate ./my-health-pod/clinical/ --json Copy Exit codes CodeMeaning 0All files pass validation 1One or more SHACL violations 2Error (file not found, malformed Turtle, etc.) 6 Query a Pod Read records out of a Pod by type using pod query: Terminal # View all medications (human-readable) cascade pod query ./my-health-pod --medications # View all data types as JSON cascade pod query ./my-health-pod --all --json Copy Available query filters: FlagReturns --medicationsPrescription and OTC medications --conditionsDiagnoses and active conditions --allergiesDrug, food, and environmental allergies --lab-resultsLab and diagnostic results --immunizationsVaccine records --vital-signsClinical vital sign readings --supplementsSupplements and OTC vitamins --insuranceInsurance / coverage plans --proceduresSurgical and clinical procedures --encountersClinical encounters and visits --documentsClinical documents (e.g. discharge summaries) --lab-reportsLaboratory reports (DiagnosticReport) --medication-administrationsInpatient medication administrations --devicesImplanted medical devices --imagingImaging studies (CT, MRI, X-ray) --claimsInsurance claims --benefitsExplanation of benefits --fhir-passthroughFHIR resource types without a full Cascade mapping --allAll data types in the Pod The JSON output from --json is designed for consumption by AI agents and is the same format returned by the MCP cascade_pod_query tool. Zero network calls The CLI is designed for complete offline operation. No data leaves your machine. SHACL validation, FHIR conversion, and Pod management all run locally. No telemetry. There is no usage tracking, no error reporting, and no analytics. No account required. Install and use without signing up for anything. The Docker image adds an extra layer: docker run --rm -v $(pwd):/data cascade-protocol/tools cascade validate /data/record.ttl Read the Security & Compliance Guide for a full description of the trust model and data flow. Reconciling Records from Multiple Systems v0.5.8 When the same patient has records in multiple EHR systems, use cascade reconcile to merge them into a single canonical Turtle file. The reconciler removes exact duplicates, merges near-duplicates, and resolves conflicts using configurable trust scores. Simpler alternative: If you are importing directly into a Pod, cascade pod import with multiple FHIR files handles reconciliation automatically as part of the import step — no separate convert-then-reconcile workflow needed. The manual cascade reconcile workflow below is still available for full control over the process or for working with pre-converted Turtle files. Step 1: Tag records with their source system during conversion Use the --source-system flag when converting each system's FHIR export: Terminal cascade convert patient.json --from fhir --to cascade \ --source-system primary-care > primary-care.ttl cascade convert patient.json --from fhir --to cascade \ --source-system hospital > hospital.ttl Copy Step 2: Reconcile Terminal cascade reconcile primary-care.ttl specialist.ttl hospital.ttl \ --output merged.ttl \ --report reconciliation-report.json \ --trust primary-care=0.90,specialist=0.85,hospital=0.95 Copy Console output Reconciliation complete Input records: 1,737 (across 3 systems) Exact duplicates: -452 removed Near-duplicates: ~9 merged Conflicts resolved: 2 (trust_priority) Final record count: 787 Output: merged.ttl Report: reconciliation-report.json How conflicts are resolved Match type Strategy Description exact_duplicate trust_priority All values identical across systems — keep highest-trust source near_duplicate merge_values Same record identity, minor differences — merge fields from all sources status_conflict trust_priority Conflicting status (e.g. active vs resolved) — highest-trust system wins value_conflict trust_priority Conflicting clinical value — highest-trust system wins unresolved flag_unresolved Trust scores too similar (<3% gap) — flagged with cascade:reconciliationStatus "unresolved-conflict" for manual review Records identified as coded using RxNorm, SNOMED CT, LOINC, CVX, or ICD-10 are matched by code first; plain-text records fall back to normalized name matching. Reconciliation provenance is written using Core Vocabulary v2.4 reconciliation properties. Conformance Testing The CLI ships with a conformance test runner for verifying that your data producer or consumer correctly implements the Cascade Protocol. The conformance fixtures are published separately and cover all data types with both positive and negative test cases. Terminal # Clone the fixtures git clone https://github.com/the-cascade-protocol/cascadeprotocol.org.git cd cascadeprotocol.org # Run the full suite cascade conformance run --suite ./conformance/fixtures --self Copy Expected output (abridged) Cascade Protocol Conformance Test Suite ======================================== Suite: ./conformance/fixtures Mode: self-conformance Fixtures: 59 Medication (10 fixtures) ✓ med-001: Happy path: Active prescription medication (Lisinopril) ✓ med-002: Happy path: Self-reported OTC medication (Cetirizine) ✓ med-008: [negative] Negative: Medication missing required drugName field ... Results: 59 passed, 0 failed, 0 errors Exit code: 0 Add --json to get a structured report you can parse in CI. Agent Server (MCP) The CLI includes a local Model Context Protocol server that exposes your Pod to AI assistants like Claude. It provides six typed tools: cascade_pod_read, cascade_pod_query, cascade_validate, cascade_convert, cascade_write, and cascade_capabilities. Start the server in stdio mode (for Claude Desktop or other MCP clients): Terminal cascade serve --mcp --pod ./my-health-pod Copy To add it to your Claude Desktop configuration, edit ~/Library/Application Support/Claude/claude_desktop_config.json: claude_desktop_config.json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp", "--pod", "/path/to/my-health-pod"] } } } Copy All data read and written by the agent stays in your local Pod. Agent-written observations are tagged with cascade:AIGenerated provenance, keeping them distinct from clinical and patient-entered data. Every operation is logged to provenance/audit-log.ttl. See the full list of available tools: Terminal cascade capabilities --json Copy Next Steps TypeScript SDK Build applications that create, read, and serialize Cascade Protocol records programmatically. Python SDK Use the Python bindings for data science, research, and scripting workflows. Security & Compliance Understand the trust model, PHI handling, encryption, and audit logging. Semantic Vocabularies Browse all supported data types: clinical, health, coverage, and core. CLI Reference Complete reference for every CLI command, flag, and subcommand. ← Back to Documentation © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { var buttons = document.querySelectorAll('.copy-btn'); buttons.forEach(function (btn) { btn.addEventListener('click', function () { var codeBlock = btn.closest('.code-block') || btn.closest('.output-block'); if (!codeBlock) return; var code = codeBlock.querySelector('code'); if (!code) return; var text = code.textContent || ''; navigator.clipboard.writeText(text).then(function () { var label = btn.querySelector('span'); var originalText = label.textContent; label.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(function () { label.textContent = originalText; btn.classList.remove('copied'); }, 2000); }); }); }); })(); ## Getting Started: agent Getting Started with Cascade Agent - Cascade Protocol /* ── Guide-specific styles ────────────────────────────────────────── */ .guide-step { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .step-header { display: flex; align-items: center; gap: var(--space-md); margin-bottom: var(--space-lg); } .step-number { width: 40px; height: 40px; background: var(--primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.125rem; font-weight: 700; flex-shrink: 0; margin: 0; /* override global .step-number { margin: 0 auto } */ } .step-header h2 { margin: 0; border: none; padding: 0; font-size: 1.5rem; } /* ── Code blocks with copy button ─────────────────────────────────── */ .code-block { position: relative; margin: var(--space-lg) 0; } .code-block pre { margin: 0; padding: var(--space-lg); padding-right: 3.5rem; } .code-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-xs); } .copy-btn { position: absolute; top: var(--space-sm); right: var(--space-sm); background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: var(--code-text); padding: 0.3rem 0.6rem; border-radius: var(--radius-sm); font-size: 0.75rem; font-family: var(--font-sans); cursor: pointer; transition: var(--transition); display: flex; align-items: center; gap: 0.3rem; } .copy-btn:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.4); } .copy-btn.copied { background: var(--success-border); border-color: var(--success-border); color: white; } /* ── Output blocks ────────────────────────────────────────────────── */ .output-block pre { background: #0f172a; border-left: 3px solid var(--accent); } .output-label { display: inline-block; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); margin-bottom: var(--space-xs); } /* ── Privacy / info box ───────────────────────────────────────────── */ .privacy-box { background: #e6f7f7; border: 1px solid #b2dfdb; border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-2xl) 0; } .privacy-box-header { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-md); } .privacy-box-header h3 { margin: 0; color: var(--primary); font-size: 1.125rem; } .privacy-box ul { margin: 0; padding-left: var(--space-lg); } .privacy-box li { margin-bottom: var(--space-sm); color: var(--text-secondary); } /* ── Deprecation / warning box ────────────────────────────────────── */ .warning-box { background: #fffbeb; border: 1px solid #fbbf24; border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-2xl) 0; } .warning-box-header { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-md); } .warning-box-header h3 { margin: 0; color: #92400e; font-size: 1rem; } .warning-box p { margin: 0; color: #78350f; font-size: 0.9rem; } .warning-box code { background: rgba(0,0,0,0.07); } /* ── Prerequisites grid ───────────────────────────────────────────── */ .prereq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .prereq-item { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); display: flex; align-items: center; gap: var(--space-sm); } .prereq-item code { font-weight: 600; } .prereq-check { color: var(--success-border); font-weight: 700; flex-shrink: 0; } /* ── Provider table ───────────────────────────────────────────────── */ .cmd-table { width: 100%; border-collapse: collapse; margin: var(--space-lg) 0; font-size: 0.9rem; } .cmd-table th { text-align: left; padding: var(--space-sm) var(--space-md); background: var(--bg-secondary); border: 1px solid var(--border); font-weight: 600; color: var(--text); } .cmd-table td { padding: var(--space-sm) var(--space-md); border: 1px solid var(--border); vertical-align: top; } .cmd-table td:first-child { font-family: var(--font-mono); font-size: 0.85rem; white-space: nowrap; color: var(--primary); } .cmd-table tr:hover td { background: var(--bg-secondary); } .cmd-table tr.highlight-row td { background: #f0fdf4; } .cmd-table tr.highlight-row:hover td { background: #dcfce7; } /* ── Next steps ───────────────────────────────────────────────────── */ .next-steps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-md); margin-top: var(--space-lg); } .next-step-card { display: block; background: white; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-lg); transition: var(--transition); text-decoration: none; } .next-step-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-md); } .next-step-card h4 { margin: 0 0 var(--space-xs) 0; font-size: 0.95rem; color: var(--text); } .next-step-card p { margin: 0; font-size: 0.85rem; color: var(--text-secondary); } /* ── Version badge ────────────────────────────────────────────────── */ .version-badge { display: inline-block; padding: 0.15rem 0.5rem; font-size: 0.7rem; font-weight: 600; border-radius: var(--radius-full); background: var(--accent); color: white; vertical-align: middle; margin-left: var(--space-xs); } /* ── Demo transcript ──────────────────────────────────────────────── */ .demo-transcript { background: #0f172a; border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-lg) 0; font-family: var(--font-mono); font-size: 0.85rem; line-height: 1.7; color: #e2e8f0; } .demo-prompt { color: #7dd3fc; } .demo-tool { color: #86efac; font-size: 0.8rem; } .demo-result { color: #94a3b8; font-size: 0.8rem; } .demo-response { color: #e2e8f0; margin-top: var(--space-sm); } /* ── Pipeline diagram ─────────────────────────────────────────────── */ .pipeline-steps { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; margin: var(--space-lg) 0; font-size: 0.85rem; } .pipeline-step { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-sm) var(--space-md); text-align: center; white-space: nowrap; } .pipeline-step strong { display: block; font-size: 0.8rem; color: var(--primary); } .pipeline-arrow { color: var(--text-muted); font-size: 1.2rem; } @media (max-width: 768px) { .prereq-grid { grid-template-columns: 1fr; } .next-steps-grid { grid-template-columns: 1fr; } .step-header h2 { font-size: 1.25rem; } .cmd-table { font-size: 0.8rem; } .pipeline-steps { flex-direction: column; align-items: flex-start; } .pipeline-arrow { transform: rotate(90deg); } } Cascade Protocol Home Documentation Contact Getting Started with Cascade Agent Use plain English to convert FHIR records, extract clinical data from documents, manage Pods, and query health data — all locally Documentation / Getting Started / Cascade Agent Cascade Agent v1.2.0 — Bundled with Cascade CLI v0.5.10+. Requires Node.js 18+. All health data processed locally. What is Cascade Agent? Cascade Agent is a natural language interface and document intelligence engine built into the Cascade Protocol CLI. It does two things: Conversational assistant — Describe what you want in plain English; the agent figures out the right CLI commands to run. Document intelligence — Extracts structured clinical entities (medications, conditions, lab results, etc.) from free-text narrative sections in C-CDA documents using an on-device LLM. ▶ Convert all FHIR files in ~/records to Cascade RDF and save them to ~/output ⚙ shell $ mkdir -p ~/output && for f in ~/records/*.json; do cascade convert ... ↳ (command succeeded — 1,201 files converted) Done. 1,201 patient records converted and saved to ~/output as .ttl files. The agent streams responses in real time, shows every command it runs, and maintains conversation context so you can follow up naturally. 1 Prerequisites ✓ Node.js 18+Runtime ✓ Cascade CLIIncludes Agent ✓ AI providerAPI key or local Cascade Agent is bundled with the CLI since v0.5. Install or update the CLI to get both: Copy npm install -g @the-cascade-protocol/cli Verify both are available: Copy cascade --version cascade agent --version ⚠  Deprecation notice: cascade-agent standalone binary The separate cascade-agent package and its binary are deprecated as of v1.0.0. Use cascade agent (subcommand of the CLI) instead. The standalone binary still works but prints a warning and will be removed in v2.0.0. 2 Configure an AI Provider Cascade Agent supports five AI providers. You only need one. The Local provider runs entirely on-device with no API key or internet connection. Provider Flag Cost / account Default model Anthropic (Claude) -p anthropic Paid — console.anthropic.com claude-opus-4-6 OpenAI (GPT) -p openai Paid — platform.openai.com gpt-4o Google (Gemini) -p google Free tier — aistudio.google.com gemini-2.0-flash Ollama -p ollama Free — runs on your machine llama3.2 Local (Qwen3.5-4B) -p local Free, fully on-device — no account Qwen3.5-4B (~2.5 GB) The Local provider is powered by node-llama-cpp and downloads the Qwen3.5-4B model (~2.5 GB) on first use. It is the recommended provider for document intelligence tasks because no text ever leaves your machine. A smaller Qwen3.5-2B variant (~1.5 GB) is also available for memory-constrained machines. Set up a cloud provider Run the interactive setup to save your API key: Copy cascade agent login Or use environment variables (always take precedence over saved keys): Copy export ANTHROPIC_API_KEY=sk-ant-... export OPENAI_API_KEY=sk-... export GOOGLE_AI_API_KEY=AI... Note: AI provider subscriptions (Claude.ai, ChatGPT Plus, Gemini Advanced) are separate from API access. You need an API key from each provider's developer console. Google AI Studio offers a free tier with generous rate limits. 3 Use the Agent Interactive REPL Start a conversation where the agent remembers context across requests: Copy cascade agent # uses your configured provider cascade agent -p google # override provider for this session cascade agent -p local # use fully on-device Qwen3.5-2B Inside the REPL: InputAction any textSend a request to the agent /clearReset conversation history /helpShow usage examples /exitQuit One-shot mode Run a single request and exit — useful in scripts: Copy cascade agent "validate ~/health-data/patient.ttl" cascade agent "how many lab results are in this record?" ~/records/patient.json cascade agent -p openai -m gpt-4o "initialize a Pod at ~/my-pod and import patient.json" Pod auto-detection v1.1.0 When you start the REPL from inside a directory that contains index.ttl (a Pod root), the agent automatically detects it and announces it at startup: Output Cascade Agent v1.1.0 (local provider) ✓ Pod detected: ./my-health-pod cascade> _ The detected pod is passed automatically to pod commands, so you can say "show me my medications" without specifying a path. Multi-line input v1.1.0 End a line with \ to continue typing on the next line — useful for longer requests: Copy cascade> Import hospital-records.json and primary-care.json \ ... into my pod, reconcile against existing records, \ ... and show me a summary of any conflicts found 4 Document Intelligence C-CDA clinical documents often contain free-text narrative sections alongside structured data. Cascade Agent can extract structured clinical entities from these narratives using an on-device LLM, then write them to your Pod as validated RDF. How it works cascade pod importParses C-CDA, flags narrative sections → cascade agent serveLocal LLM extraction server → cascade pod extractSends narratives, routes by confidence → cascade agent reviewHuman review of borderline entities Step 1 — Start the extraction server cascade agent serve starts a local HTTP server on port 8765 that accepts narrative text and returns extracted clinical entities. On first run it downloads the Qwen3.5-4B model (~2.5 GB). Copy cascade agent serve Output ⌄ Cascade Agent extraction server Checking model... Qwen3.5-4B not found, downloading (~2.5 GB) … download complete Listening on http://localhost:8765 POST /extract — GET /health Keep the server running in a terminal tab while you run the next steps. Step 2 — Import and extract After importing a C-CDA document (cascade pod import), the import command will tell you if any narrative sections are queued for extraction. Then run: Copy cascade pod extract ~/my-pod Extracted entities are routed by confidence: ConfidenceDestinationAction needed ≥ 0.85clinical/ai-extracted.ttlAuto-accepted, none 0.50 – 0.84analysis/review-queue.jsonHuman review (see below) < 0.50analysis/discarded-extractions.ttlDiscarded, auditable Step 3 — Review borderline entities Entities with confidence between 0.50 and 0.84 are placed in a review queue. Approve or discard them interactively: Copy cascade agent review --pod ~/my-pod The review command walks through each queued entity, showing the source narrative and the extracted data, and asks you to accept or discard. Accepted entities are written to clinical/ai-extracted.ttl; discarded ones are moved to the audit log. All extraction is on-device The extraction server uses Qwen3.5-4B running locally via node-llama-cpp. No text is sent to any external API. Extracted entities are tagged with cascade:dataProvenance cascade:AIExtracted and a PROV-O activity node recording the model ID and confidence score. All three output paths (auto-accepted, review queue, discarded) are preserved for audit purposes. What you can ask The agent understands any task expressible with the Cascade CLI. Some examples: ▸ Convert all the FHIR bundles in ~/Downloads/records to Cascade RDF ▸ Validate these .ttl files and tell me which ones have errors ▸ Initialize a new Pod at ~/my-health-data and import patient.xml into it ▸ How many condition records are in this Pod? ▸ Show me the medications and lab results from both providers, reconcile any duplicates ▸ There are 8 items in the review queue at ~/my-pod — walk me through them For batch jobs the agent writes a shell loop rather than making one call per file, so converting thousands of records is a single tool invocation. Managing Providers & Models Copy # List configured providers cascade agent provider # Switch active provider cascade agent provider google # Show current model cascade agent model # Switch model using a shortcut cascade agent model flash # gemini-2.0-flash cascade agent model opus # claude-opus-4-6 cascade agent model sonnet # claude-sonnet-4-6 # Use any full model ID cascade agent model gemini-1.5-pro Settings are saved to ~/.config/cascade-agent/config.json. Environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY) always take precedence over saved keys. Model shortcuts ShortcutResolves to opusclaude-opus-4-6 sonnetclaude-sonnet-4-6 haikuclaude-haiku-4-5 gpt4ogpt-4o o3o3 flashgemini-2.0-flash progemini-1.5-pro Session Logs Every session is logged automatically. Review past sessions with: Copy cascade agent logs Next Steps CLI Reference Learn the full set of commands the agent can execute on your behalf. TypeScript SDK Build applications that create and process Cascade Protocol records programmatically. Python SDK Use the Python bindings for data science, research, and scripting workflows. Security & Compliance Understand PHI handling, encryption, and what data the agent does and does not transmit. ← Back to Documentation © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { var buttons = document.querySelectorAll('.copy-btn'); buttons.forEach(function (btn) { btn.addEventListener('click', function () { var codeBlock = btn.closest('.code-block') || btn.closest('.output-block'); if (!codeBlock) return; var code = codeBlock.querySelector('code'); if (!code) return; var text = code.textContent || ''; navigator.clipboard.writeText(text).then(function () { var label = btn.querySelector('span'); var originalText = label.textContent; label.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(function () { label.textContent = originalText; btn.classList.remove('copied'); }, 2000); }); }); }); })(); # ============================================================================ # SECTION 9: AGENT EXAMPLES # ============================================================================ ## Example: agent-medication-review # Agent Example: Medication Interaction Review An AI agent reads a Cascade Protocol Pod, identifies a cross-provenance drug interaction between a prescribed ACE inhibitor and a self-reported potassium supplement, correlates it with an elevated lab value, and writes back a flagged observation. ## Why This Matters Traditional drug interaction databases only check prescription-vs-prescription pairs. This scenario requires an agent to correlate data across **three provenance boundaries**: | Data | File | Provenance | |------|------|-----------| | Lisinopril 20 mg (ACE inhibitor) | `clinical/medications.ttl` | `ClinicalGenerated` (EHR) | | Potassium Chloride 20 mEq supplement | `wellness/supplements.ttl` | `SelfReported` (patient-entered) | | Serum K+ 5.1 mEq/L (borderline high) | `clinical/lab-results.ttl` | `ClinicalGenerated` (EHR) | An agent that only examines clinical records would miss the self-reported potassium supplement. Only cross-provenance reasoning detects the complete picture. ## Prerequisites - [Cascade CLI](https://cascadeprotocol.org/docs/cli) installed (`cascade` on PATH) - The reference patient pod at `../reference-patient-pod/` (included in this repository) - One of: - **Claude Desktop** (for MCP approach) - **Claude Code** (for CLI-as-skill approach) ## Quick Start (Under 10 Minutes) ### Option A: MCP via Claude Desktop 1. Add the Cascade MCP server to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp"], "env": { "CASCADE_POD_PATH": "/path/to/cascadeprotocol.org/reference-patient-pod" } } } } ``` 2. Restart Claude Desktop. 3. Open a new conversation and paste the contents of `agent-prompt.md` as your message. 4. The agent will use the `cascade_pod_query` and `cascade_write` MCP tools automatically. ### Option B: CLI-as-Skill via Claude Code 1. Open a terminal in this directory. 2. Run Claude Code with the reference pod path: ```bash export CASCADE_POD_PATH="../reference-patient-pod" claude ``` 3. Paste the contents of `agent-prompt.md` as your prompt. 4. The agent will invoke the Cascade CLI directly: ```bash # Step 1: Read medications cascade pod query ./reference-patient-pod --medications --json # Step 2: Read supplements cascade pod query ./reference-patient-pod --supplements --json # Step 3: Read lab results cascade pod query ./reference-patient-pod --labs --json # Step 4: Write back observation cascade pod write ./reference-patient-pod --type observation --json '{...}' ``` ## Files in This Example | File | Purpose | |------|---------| | `README.md` | This file -- setup and walkthrough | | `agent-prompt.md` | The prompt/instructions to give the agent | | `expected-output.md` | What the agent should produce (reference answer) | | `security-model.md` | Data flow documentation and consent model | ## What the Agent Should Do 1. **Read** the patient's medication list (both prescribed and self-reported) 2. **Identify** the ACE inhibitor (lisinopril) + potassium supplement interaction 3. **Correlate** with the serum potassium lab result (5.1 mEq/L, above normal range of 3.5-5.0) 4. **Write back** a flagged observation to the Pod with `AIGenerated` provenance ## Key Cascade Protocol Concepts Demonstrated - **Cross-provenance correlation**: Combining ClinicalGenerated, SelfReported, and DeviceGenerated data - **Medication-supplement interaction detection**: Going beyond drug-drug to drug-supplement - **Structured write-back**: AI observations are written with `cascade:AIGenerated` provenance, keeping them distinct from clinical and patient data - **Audit trail**: Every read and write is logged in the Pod's audit log ## Reference Patient **Alex Rivera**, 52M, Portland, OR. See `../reference-patient-pod/README.md` for the full patient narrative. # Agent Prompt: Medication Interaction Review Use this prompt with Claude Desktop (MCP) or Claude Code (CLI-as-skill) to run the medication review agent against the reference patient pod. --- ## Prompt You are a medication safety review agent. You have access to a patient's Cascade Protocol Pod containing their complete health record. Your job is to review all medications and supplements -- regardless of provenance -- and identify potential interactions, correlating with available lab data. **Important:** The patient's Pod contains data from multiple provenance sources: - `ClinicalGenerated` -- from the patient's EHR (electronic health record) - `SelfReported` -- entered by the patient themselves - `DeviceGenerated` -- from wearable devices You must examine ALL provenance layers. A prescribed medication can interact with a self-reported supplement just as dangerously as with another prescription. ### Your Task **Step 1: Read all medications and supplements.** Using the Cascade MCP server: ``` cascade_pod_query({ category: "medications", format: "json" }) cascade_pod_query({ category: "supplements", format: "json" }) ``` Or using the CLI: ```bash cascade pod query ./reference-patient-pod --medications --json cascade pod query ./reference-patient-pod --supplements --json ``` Review each medication/supplement, noting its provenance source. Pay special attention to any items with `SelfReported` provenance, as these may not be known to the prescribing physician. **Step 2: Check for drug-drug and drug-supplement interactions.** For each active medication, evaluate potential interactions with every other active medication AND supplement. Consider: - Pharmacological class interactions (e.g., ACE inhibitors affect potassium) - Additive effect risks - Contraindications based on the patient's conditions **Step 3: Correlate with lab results.** ``` cascade_pod_query({ category: "labs", format: "json" }) ``` Or: ```bash cascade pod query ./reference-patient-pod --labs --json ``` If you identify a potential interaction, check whether lab results support or refute the concern. A borderline or abnormal lab value that aligns with a predicted interaction mechanism significantly raises the clinical importance. **Step 4: Write back your findings.** For each significant finding, write an observation back to the Pod: ``` cascade_write({ type: "observation", data: { observationType: "MedicationInteractionFlag", severity: "high", summary: "", evidence: [""], recommendation: "", provenance: "AIGenerated" } }) ``` Or: ```bash cascade pod write ./reference-patient-pod --type observation --json '{ "observationType": "MedicationInteractionFlag", "severity": "high", "summary": "...", "evidence": ["..."], "recommendation": "...", "provenance": "AIGenerated" }' ``` ### Output Format Provide your analysis as a structured report with: 1. **Medication Inventory** -- all active medications and supplements with provenance 2. **Interaction Analysis** -- each identified interaction with mechanism and severity 3. **Lab Correlation** -- supporting lab values with interpretation 4. **Recommendations** -- prioritized list of actions 5. **Write-back Confirmation** -- the observation(s) written to the Pod ### Constraints - You are an advisory agent. Clearly state that findings require physician review. - Always attribute data to its provenance source. - Never modify existing clinical data -- only write new `AIGenerated` observations. - Flag any data gaps (e.g., missing recent labs, discontinued medications without clear reason). # Expected Output: Medication Interaction Review This document shows the expected agent output when running the medication review prompt against the Alex Rivera reference patient pod. Your agent's output should cover the same findings, though wording may vary. --- ## 1. Medication Inventory ### Prescribed Medications (ClinicalGenerated) | Medication | Dose | Frequency | Indication | Prescriber | Status | |-----------|------|-----------|------------|------------|--------| | Lisinopril | 20 mg | Once daily | Essential Hypertension | Dr. Sarah Chen | Active | | Metformin | 1000 mg | Twice daily | Type 2 Diabetes | Dr. Sarah Chen | Active | | Atorvastatin | 40 mg | Once daily | Hyperlipidemia | Dr. Sarah Chen | Active | | Albuterol | 90 mcg/actuation | As needed | Mild Persistent Asthma | Dr. Sarah Chen | Active | | Amlodipine | 5 mg | Once daily | Hypertension | Dr. Sarah Chen | **Discontinued** (2024-06-15) | ### Self-Reported Medications and Supplements (SelfReported) | Item | Dose | Frequency | Reason | Status | |------|------|-----------|--------|--------| | Cetirizine (Zyrtec) | 10 mg | Once daily | Seasonal allergic rhinitis | Active | | Potassium Chloride | 20 mEq | Once daily | Muscle cramps | Active | | Fish Oil (Omega-3) | 1000 mg | Once daily | Cardiovascular health | Active | ## 2. Interaction Analysis ### CRITICAL: Lisinopril + Potassium Chloride Supplement | Field | Value | |-------|-------| | **Severity** | HIGH | | **Type** | Drug-supplement interaction | | **Cross-provenance** | Yes -- ClinicalGenerated (Lisinopril) + SelfReported (KCl) | **Mechanism:** Lisinopril is an ACE inhibitor that reduces aldosterone secretion, thereby decreasing renal potassium excretion. When combined with exogenous potassium supplementation (20 mEq/day), the risk of hyperkalemia (elevated serum potassium) is significantly increased. **Clinical significance:** This interaction is classified as a major drug-supplement interaction. Hyperkalemia (K+ > 5.5 mEq/L) can cause life-threatening cardiac arrhythmias, including ventricular fibrillation and cardiac arrest. **Provenance note:** The potassium supplement is `SelfReported` -- the prescribing physician (Dr. Sarah Chen) may not be aware of this supplement. The lisinopril prescription note explicitly states "Monitor potassium levels due to ACE inhibitor mechanism," indicating awareness of the K+-raising effect but no knowledge of the supplement. ### Low-risk: Fish Oil + Atorvastatin | Field | Value | |-------|-------| | **Severity** | LOW | | **Type** | Drug-supplement overlap | **Mechanism:** Fish oil (omega-3 fatty acids) and atorvastatin both target lipid management. Fish oil primarily lowers triglycerides while atorvastatin targets LDL cholesterol. This combination is generally considered safe and potentially beneficial, particularly given Alex's elevated triglycerides (190 mg/dL). **No action required.** This is a complementary rather than conflicting combination. ## 3. Lab Correlation ### Serum Potassium: 5.1 mEq/L -- CONFIRMS INTERACTION CONCERN | Lab | Value | Reference Range | Interpretation | Date | |-----|-------|----------------|----------------|------| | **Serum Potassium** | **5.1 mEq/L** | 3.5 - 5.0 mEq/L | **Above normal** | 2026-01-15 | | Serum Creatinine | 1.1 mg/dL | 0.7 - 1.3 mg/dL | Normal | 2026-01-15 | | eGFR | 78 mL/min/1.73m2 | > 90 | Mildly decreased (CKD Stage 2) | 2026-01-15 | **Analysis:** The serum potassium of 5.1 mEq/L is above the normal upper limit of 5.0 mEq/L. This borderline hyperkalemia is consistent with the predicted interaction between lisinopril (ACE inhibitor, reduces K+ excretion) and the self-reported potassium chloride supplement (adds exogenous K+). **Compounding factors:** - The mildly decreased eGFR (78 mL/min, CKD Stage 2) further reduces the kidney's capacity to excrete excess potassium - The lab note itself reads: "Patient is on lisinopril (ACE inhibitor) which can elevate potassium. Verify patient is not taking potassium supplements. Recheck in 2 weeks." - This lab note confirms the prescriber already suspected the risk but the supplement was never reported ### Additional Lab Context | Lab | Value | Range | Interpretation | |-----|-------|-------|----------------| | HbA1c | 7.2% | 4.0 - 5.6% | Above target (<7.0%), suggest lifestyle modifications | | Fasting Glucose | 142 mg/dL | 70 - 100 mg/dL | Elevated, consistent with diabetes | | LDL Cholesterol | 112 mg/dL | < 100 mg/dL | Above target for diabetic patient | | Triglycerides | 190 mg/dL | < 150 mg/dL | Borderline high | | HDL Cholesterol | 48 mg/dL | > 40 mg/dL | Low-normal | ## 4. Recommendations ### Priority 1 (URGENT): Potassium Supplement Review - **Action:** Discontinue potassium chloride supplement pending physician consultation - **Reason:** Active hyperkalemia risk confirmed by lab result (K+ 5.1 mEq/L) in the context of ACE inhibitor therapy and mildly reduced renal function - **Required:** Physician review. This agent does not have prescriptive authority. ### Priority 2: Serum Potassium Recheck - **Action:** Repeat serum potassium lab within 1-2 weeks - **Reason:** Lab note from 2026-01-15 recommended recheck in 2 weeks; verify whether levels have changed since last draw ### Priority 3: Communication Gap - **Action:** Inform prescribing physician (Dr. Sarah Chen) about the self-reported potassium supplement - **Reason:** The supplement has `SelfReported` provenance and appears in the wellness section, not the clinical medication list. The prescriber's lab note asks to "verify patient is not taking potassium supplements" -- confirming this information has not been communicated. ### Priority 4: Glycemic Control Review - **Action:** Discuss HbA1c (7.2%, target <7.0%) and fasting glucose (142 mg/dL) at next visit - **Reason:** Slightly above target; lifestyle modifications or medication adjustment may be warranted ## 5. Write-back: AIGenerated Observation The agent writes the following observation to the Pod: ```turtle @prefix cascade: . @prefix health: . @prefix clinical: . @prefix prov: . @prefix xsd: . a cascade:AIObservation ; cascade:observationType "MedicationInteractionFlag" ; cascade:severity "high" ; cascade:dataProvenance cascade:AIGenerated ; cascade:schemaVersion "1.3" ; cascade:summary "Potential hyperkalemia risk: concurrent use of lisinopril (ACE inhibitor, ClinicalGenerated) and self-reported potassium chloride 20 mEq supplement (SelfReported). Serum K+ is 5.1 mEq/L (above normal range 3.5-5.0). Risk compounded by mildly reduced eGFR (78 mL/min, CKD Stage 2)." ; cascade:evidence ( ) ; cascade:recommendation "Discontinue potassium supplement pending physician review. Recheck serum potassium in 1-2 weeks. Inform Dr. Sarah Chen of self-reported supplement." ; cascade:requiresPhysicianReview true ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "AI Medication Interaction Review" ; prov:startedAtTime "2026-02-20T00:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Cascade Medication Review Agent" ] ] . ``` --- **Disclaimer:** This analysis was generated by an AI agent and is not a substitute for professional medical judgment. All findings require review by a licensed healthcare provider before any clinical action is taken. # Security Model: Medication Interaction Review Agent This document describes the data flows, access boundaries, and consent model for the medication interaction review agent example. ## Data Access Summary ### What the Agent Read | Resource | File | Data Accessed | |----------|------|--------------| | Medications | `clinical/medications.ttl` | 8 medication records (5 prescribed, 1 discontinued, 2 self-reported) | | Supplements | `wellness/supplements.ttl` | 3 supplement records (all self-reported) | | Lab Results | `clinical/lab-results.ttl` | 11 lab results from 2026-01-15 panel | | Conditions | `clinical/conditions.ttl` | 5 active conditions (for context on renal function) | ### What the Agent Did NOT Access | Resource | File | Reason | |----------|------|--------| | Patient demographics | `clinical/patient-profile.ttl` | Not needed for interaction review | | Allergies | `clinical/allergies.ttl` | Not relevant to this specific interaction | | Immunizations | `clinical/immunizations.ttl` | Not relevant to medication interactions | | Vital signs | `clinical/vital-signs.ttl` | Not needed for this analysis | | Heart rate | `wellness/heart-rate.ttl` | Not needed for this analysis | | Blood pressure | `wellness/blood-pressure.ttl` | Not needed for this analysis | | Activity data | `wellness/activity.ttl` | Not needed for this analysis | | Sleep data | `wellness/sleep.ttl` | Not needed for this analysis | | Insurance | `clinical/insurance.ttl` | Not needed for this analysis | ### What the Agent Wrote | Resource | Type | Provenance | |----------|------|-----------| | Medication interaction flag | `cascade:AIObservation` | `cascade:AIGenerated` | The written observation is clearly tagged with `cascade:AIGenerated` provenance, distinguishing it from clinical and patient-reported data. It includes a `cascade:requiresPhysicianReview true` flag. ## Data Flow Diagram ``` +-------------------------------------------------------------------+ | LOCAL MACHINE ONLY | | | | +-------------------+ +----------------------------+ | | | Reference Patient | READ | AI Agent | | | | Pod (filesystem) |------->| (Claude Desktop or | | | | | | Claude Code) | | | | clinical/ | | | | | | medications.ttl | | 1. Parse medications | | | | lab-results.ttl | | 2. Parse supplements | | | | conditions.ttl | | 3. Identify interactions | | | | | | 4. Correlate with labs | | | | wellness/ | | 5. Generate observation | | | | supplements.ttl | | | | | | | WRITE | | | | | |<-------| AIGenerated observation | | | +-------------------+ +----------------------------+ | | | | No data leaves this machine. No external API calls are made | | for data retrieval. The LLM processes data in-context only. | +-------------------------------------------------------------------+ ``` ## No Data Left the Machine This is a **local-first** operation: 1. **Pod storage:** The reference patient pod lives on the local filesystem. It is not uploaded to any cloud service. 2. **MCP server:** The `cascade serve --mcp` process runs locally, reading from the local filesystem and communicating with Claude Desktop via stdio (standard input/output). No network requests are made by the MCP server. 3. **CLI invocations:** The `cascade pod query` and `cascade pod write` commands operate entirely on local files. No network traffic is generated. 4. **LLM processing:** When using Claude Desktop or Claude Code, the Pod data is sent to the Anthropic API as part of the conversation context. **This is the only network hop**, and it is governed by Anthropic's data handling policies (data is not used for training, and conversations can be set to zero-retention). - For fully air-gapped deployments, a local LLM can be substituted. ## Consent Model ### Read Consent The agent accesses the Pod because the patient (Pod owner) explicitly invoked the agent. In the Cascade Protocol consent model: - **Pod owner grants access** by configuring the MCP server with `CASCADE_POD_PATH` pointing to their Pod - **Scope is implicit:** The agent can read any file in the Pod that the MCP server exposes - **Granular scoping** (future): The Cascade CLI will support `--scope medications,labs` to restrict agent access to specific categories ### Write Consent The agent writes an observation back to the Pod: - **Write is explicitly requested** in the agent prompt ("write back your findings") - **Provenance is mandatory:** All agent-written data carries `cascade:AIGenerated` provenance - **Non-destructive:** The agent creates a new observation record. It never modifies or deletes existing clinical or patient-reported data. - **Physician gate:** The observation includes `cascade:requiresPhysicianReview true`, marking it as advisory-only ### Audit Records Created Each MCP tool invocation or CLI command generates an audit log entry in the Pod: | Timestamp | Action | Resource | Agent | |-----------|--------|----------|-------| | T+0s | `pod_query` | `clinical/medications.ttl` | Cascade Medication Review Agent | | T+1s | `pod_query` | `wellness/supplements.ttl` | Cascade Medication Review Agent | | T+2s | `pod_query` | `clinical/lab-results.ttl` | Cascade Medication Review Agent | | T+3s | `pod_query` | `clinical/conditions.ttl` | Cascade Medication Review Agent | | T+4s | `pod_write` | `observations/ai-med-review-20260220.ttl` | Cascade Medication Review Agent | These audit entries persist in the Pod and can be reviewed by the patient or their care team at any time. ## Threat Model Considerations | Threat | Mitigation | |--------|-----------| | Agent reads more data than necessary | Current: full Pod access. Future: scoped access via `--scope` flag | | Agent writes incorrect clinical advice | `cascade:AIGenerated` provenance + `requiresPhysicianReview` flag | | Pod data sent to cloud LLM | Anthropic zero-retention policy; local LLM option for air-gapped use | | Agent modifies existing clinical data | MCP server enforces append-only for AI-written data | | Unauthorized agent access | Pod owner must explicitly configure MCP server path | ## Summary - **Data accessed:** 4 files (medications, supplements, labs, conditions) - **Data written:** 1 AI-generated observation with full provenance chain - **Network hops:** Zero for data retrieval; one for LLM inference (Anthropic API) - **No PHI left the local machine** via Cascade Protocol tooling - **Audit trail:** 5 entries logged in the Pod # Agent Example: Medication Interaction Review An AI agent reads a Cascade Protocol Pod, identifies a cross-provenance drug interaction between a prescribed ACE inhibitor and a self-reported potassium supplement, correlates it with an elevated lab value, and writes back a flagged observation. ## Why This Matters Traditional drug interaction databases only check prescription-vs-prescription pairs. This scenario requires an agent to correlate data across **three provenance boundaries**: | Data | File | Provenance | |------|------|-----------| | Lisinopril 20 mg (ACE inhibitor) | `clinical/medications.ttl` | `ClinicalGenerated` (EHR) | | Potassium Chloride 20 mEq supplement | `wellness/supplements.ttl` | `SelfReported` (patient-entered) | | Serum K+ 5.1 mEq/L (borderline high) | `clinical/lab-results.ttl` | `ClinicalGenerated` (EHR) | An agent that only examines clinical records would miss the self-reported potassium supplement. Only cross-provenance reasoning detects the complete picture. ## Prerequisites - [Cascade CLI](https://cascadeprotocol.org/docs/cli) installed (`cascade` on PATH) - The reference patient pod at `../reference-patient-pod/` (included in this repository) - One of: - **Claude Desktop** (for MCP approach) - **Claude Code** (for CLI-as-skill approach) ## Quick Start (Under 10 Minutes) ### Option A: MCP via Claude Desktop 1. Add the Cascade MCP server to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp"], "env": { "CASCADE_POD_PATH": "/path/to/cascadeprotocol.org/reference-patient-pod" } } } } ``` 2. Restart Claude Desktop. 3. Open a new conversation and paste the contents of `agent-prompt.md` as your message. 4. The agent will use the `cascade_pod_query` and `cascade_write` MCP tools automatically. ### Option B: CLI-as-Skill via Claude Code 1. Open a terminal in this directory. 2. Run Claude Code with the reference pod path: ```bash export CASCADE_POD_PATH="../reference-patient-pod" claude ``` 3. Paste the contents of `agent-prompt.md` as your prompt. 4. The agent will invoke the Cascade CLI directly: ```bash # Step 1: Read medications cascade pod query ./reference-patient-pod --medications --json # Step 2: Read supplements cascade pod query ./reference-patient-pod --supplements --json # Step 3: Read lab results cascade pod query ./reference-patient-pod --labs --json # Step 4: Write back observation cascade pod write ./reference-patient-pod --type observation --json '{...}' ``` ## Files in This Example | File | Purpose | |------|---------| | `README.md` | This file -- setup and walkthrough | | `agent-prompt.md` | The prompt/instructions to give the agent | | `expected-output.md` | What the agent should produce (reference answer) | | `security-model.md` | Data flow documentation and consent model | ## What the Agent Should Do 1. **Read** the patient's medication list (both prescribed and self-reported) 2. **Identify** the ACE inhibitor (lisinopril) + potassium supplement interaction 3. **Correlate** with the serum potassium lab result (5.1 mEq/L, above normal range of 3.5-5.0) 4. **Write back** a flagged observation to the Pod with `AIGenerated` provenance ## Key Cascade Protocol Concepts Demonstrated - **Cross-provenance correlation**: Combining ClinicalGenerated, SelfReported, and DeviceGenerated data - **Medication-supplement interaction detection**: Going beyond drug-drug to drug-supplement - **Structured write-back**: AI observations are written with `cascade:AIGenerated` provenance, keeping them distinct from clinical and patient data - **Audit trail**: Every read and write is logged in the Pod's audit log ## Reference Patient **Alex Rivera**, 52M, Portland, OR. See `../reference-patient-pod/README.md` for the full patient narrative. ## Example: agent-trend-analysis # Agent Example: Wellness Trend Analysis An AI agent reads 30 days of wellness time-series data from a Cascade Protocol Pod, identifies clinically relevant trends, ties confidence to provenance and sample density, and writes trend observations back to the Pod with `AIGenerated` provenance. ## Why This Matters Wearable devices generate hundreds of data points per day, but raw numbers are overwhelming and clinically unhelpful. An agent that can: 1. **Detect meaningful trends** in noisy time-series data 2. **Tie confidence to provenance** (device-generated data with 150 samples/day gets higher confidence than sparse self-reported data) 3. **Correlate across data streams** (e.g., poor sleep correlating with elevated heart rate) 4. **Write structured observations** with proper provenance ...transforms raw device data into actionable clinical intelligence. ## Prerequisites - [Cascade CLI](https://cascadeprotocol.org/docs/cli) installed (`cascade` on PATH) - The reference patient pod at `../reference-patient-pod/` - One of: - **Claude Desktop** (for MCP approach) - **Claude Code** (for CLI-as-skill approach) ## Quick Start (Under 10 Minutes) ### Option A: MCP via Claude Desktop 1. Add the Cascade MCP server to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp"], "env": { "CASCADE_POD_PATH": "/path/to/cascadeprotocol.org/reference-patient-pod" } } } } ``` 2. Restart Claude Desktop. 3. Open a new conversation and paste the contents of `agent-prompt.md` as your message. 4. The agent will use `cascade_pod_query` to read wellness data and `cascade_write` to write observations. ### Option B: CLI-as-Skill via Claude Code 1. Open a terminal in this directory. 2. Run Claude Code: ```bash export CASCADE_POD_PATH="../reference-patient-pod" claude ``` 3. Paste the contents of `agent-prompt.md` as your prompt. 4. The agent will invoke the Cascade CLI: ```bash # Read wellness time-series data cascade pod query ./reference-patient-pod --wellness --json # Or read individual categories cascade pod query ./reference-patient-pod --heart-rate --json cascade pod query ./reference-patient-pod --blood-pressure --json cascade pod query ./reference-patient-pod --activity --json cascade pod query ./reference-patient-pod --sleep --json # Read clinical context (conditions, medications) cascade pod query ./reference-patient-pod --conditions --json cascade pod query ./reference-patient-pod --medications --json # Write back observations cascade pod write ./reference-patient-pod --type observation --json '{...}' ``` ## Files in This Example | File | Purpose | |------|---------| | `README.md` | This file -- setup and walkthrough | | `agent-prompt.md` | The prompt/instructions to give the agent | | `expected-output.md` | What the agent should produce (reference answer) | | `security-model.md` | Data flow documentation and consent model | ## What the Agent Should Do 1. **Read** 30 days of wellness time-series data (heart rate, blood pressure, activity, sleep) 2. **Identify** clinically relevant trends (rising BP, declining activity, sleep irregularity) 3. **Tie confidence to provenance**: Device data with many daily samples gets higher confidence than sparse self-reported data 4. **Correlate across streams**: Link poor sleep to elevated HR, low activity to BP changes 5. **Write** trend observations back to the Pod with `AIGenerated` provenance ## Key Cascade Protocol Concepts Demonstrated - **Time-series analysis**: Processing 30 days of daily snapshots to extract trends - **Provenance-weighted confidence**: Device data with high sample counts rated more confidently - **Cross-stream correlation**: Linking patterns across different wellness data types - **Structured write-back**: Trend observations tagged with `cascade:AIGenerated` provenance - **Clinical context integration**: Using conditions and medications to interpret wellness patterns ## Reference Patient **Alex Rivera**, 52M, Portland, OR. See `../reference-patient-pod/README.md` for the full patient narrative. ### Wellness Data Available | Data Stream | Source | Days | Samples/Day | Date Range | |-------------|--------|------|-------------|-----------| | Resting Heart Rate | Apple Watch Series 9 | 30 | 120-168 | 2026-01-20 to 2026-02-18 | | Blood Pressure | Omron Evolv | 30 | 1 | 2026-01-20 to 2026-02-18 | | Activity (steps, energy, exercise) | Apple Watch Series 9 | 30 | Continuous | 2026-01-20 to 2026-02-18 | | Sleep (duration, quality) | Apple Watch Series 9 | 30 | 1 | 2026-01-20 to 2026-02-18 | # Agent Prompt: Wellness Trend Analysis Use this prompt with Claude Desktop (MCP) or Claude Code (CLI-as-skill) to run the trend analysis agent against the reference patient pod. --- ## Prompt You are a wellness trend analysis agent. You have access to a patient's Cascade Protocol Pod containing 30 days of device-generated wellness data alongside their clinical context (conditions, medications). Your job is to analyze time-series trends, assess confidence based on provenance and sample density, and write clinically relevant observations back to the Pod. ### Your Task **Step 1: Read wellness time-series data.** Using the Cascade MCP server: ``` cascade_pod_query({ category: "heart-rate", format: "json" }) cascade_pod_query({ category: "blood-pressure", format: "json" }) cascade_pod_query({ category: "activity", format: "json" }) cascade_pod_query({ category: "sleep", format: "json" }) ``` Or using the CLI: ```bash cascade pod query ./reference-patient-pod --heart-rate --json cascade pod query ./reference-patient-pod --blood-pressure --json cascade pod query ./reference-patient-pod --activity --json cascade pod query ./reference-patient-pod --sleep --json ``` **Step 2: Read clinical context.** To interpret trends correctly, you need to know the patient's conditions and medications: ``` cascade_pod_query({ category: "conditions", format: "json" }) cascade_pod_query({ category: "medications", format: "json" }) ``` Or: ```bash cascade pod query ./reference-patient-pod --conditions --json cascade pod query ./reference-patient-pod --medications --json ``` **Step 3: Analyze each data stream.** For each wellness data stream, compute: 1. **Descriptive statistics**: Mean, standard deviation, min, max, range 2. **Trend direction**: Is the metric rising, falling, or stable over 30 days? Use first-half vs second-half comparison and/or linear regression. 3. **Variability pattern**: Is there a weekly cycle? Are there outlier days? 4. **Confidence rating**: Based on provenance and sample density: - **High confidence**: DeviceGenerated data with >100 samples/day (e.g., heart rate from Apple Watch) - **Medium confidence**: DeviceGenerated data with 1 sample/day (e.g., home BP, sleep duration) - **Low confidence**: SelfReported data with no automated validation **Step 4: Cross-stream correlation.** Look for patterns across data streams: - Does poor sleep correlate with elevated heart rate the next day? - Do low-activity days correlate with higher blood pressure? - Do weekday/weekend patterns differ across streams? - Do any trends align with medication effects or condition progression? **Step 5: Write trend observations back to the Pod.** For each clinically relevant finding, write an observation: ``` cascade_write({ type: "observation", data: { observationType: "WellnessTrendObservation", trendType: "", metric: "", direction: "rising|falling|stable|variable", confidence: "high|medium|low", clinicalRelevance: "", dataPoints: , period: "2026-01-20 to 2026-02-18", provenance: "AIGenerated" } }) ``` Or: ```bash cascade pod write ./reference-patient-pod --type observation --json '{ "observationType": "WellnessTrendObservation", "trendType": "...", "metric": "...", "direction": "...", "confidence": "...", "clinicalRelevance": "...", "dataPoints": 30, "period": "2026-01-20 to 2026-02-18", "provenance": "AIGenerated" }' ``` ### Output Format Provide your analysis as a structured report with: 1. **Data Stream Summaries** -- statistics and trends for each stream 2. **Confidence Assessment** -- provenance-based confidence for each finding 3. **Cross-Stream Correlations** -- patterns linking multiple data streams 4. **Clinical Context Integration** -- how trends relate to conditions and medications 5. **Trend Observations Written** -- the observations written back to the Pod ### Confidence Rating Framework | Confidence | Criteria | Example | |-----------|----------|---------| | **High** | DeviceGenerated, >100 samples/day, consistent device wear | Apple Watch resting HR (avg 144 samples/day) | | **Medium** | DeviceGenerated, 1 sample/day, regular measurement | Omron Evolv daily BP reading | | **Low** | SelfReported, no automated validation, unknown adherence | Supplement intake reporting | ### Constraints - You are an analytical agent, not a diagnostic tool. Frame findings as observations, not diagnoses. - Always state the confidence level and basis for each finding. - Distinguish between statistically observable trends and clinically meaningful ones. - When writing observations, always use `AIGenerated` provenance. - Note limitations: 30 days is a short window for some trend types. # Expected Output: Wellness Trend Analysis This document shows the expected agent output when running the trend analysis prompt against the Alex Rivera reference patient pod. --- # Wellness Trend Analysis Report **Patient:** Alex Rivera, 52M **Analysis Period:** 2026-01-20 to 2026-02-18 (30 days) **Data Sources:** Apple Watch Series 9 (HealthKit), Omron Evolv (Bluetooth BP monitor) **Clinical Context:** Essential Hypertension (on Lisinopril 20 mg), Type 2 Diabetes (on Metformin 1000 mg), Hyperlipidemia (on Atorvastatin 40 mg) --- ## 1. Data Stream Summaries ### Heart Rate (Resting) `[Device: Apple Watch Series 9]` | Statistic | Value | |-----------|-------| | Mean | 68.2 bpm | | Std Dev | 5.8 bpm | | Min | 58 bpm (2026-01-27) | | Max | 82 bpm (2026-02-07) | | Range | 24 bpm | | Samples/day | 120-168 (avg ~144) | **Trend:** Stable. First-half average (68.0 bpm) and second-half average (68.4 bpm) are essentially identical. No upward or downward drift. **Weekly pattern:** Resting HR tends to be lower on weekends/rest days (58-64 bpm) and higher midweek (72-82 bpm), likely reflecting higher physical and psychological stress during the workweek. **Outlier:** 82 bpm on 2026-02-07 (Friday) is the highest reading. This coincides with the lowest sleep quality that week (5.9 hours, Poor quality), suggesting sleep deprivation drove the elevated HR. **Confidence: HIGH** -- Continuous device monitoring with 120-168 samples/day. Consistent device wear indicated by daily data without gaps. ### Blood Pressure `[Device: Omron Evolv]` | Statistic | Systolic | Diastolic | |-----------|----------|-----------| | Mean | 129.3 mmHg | 80.0 mmHg | | Std Dev | 5.3 mmHg | 3.7 mmHg | | Min | 120 mmHg | 74 mmHg | | Max | 138 mmHg | 86 mmHg | | Range | 18 mmHg | 12 mmHg | **Trend:** Stable with oscillating pattern. First-half systolic average (129.5) vs second-half (129.1) shows no meaningful change. BP oscillates between 120-130 on good days and 132-138 on higher days without a clear directional trend. **Pattern:** Higher BP readings (>134 mmHg systolic) appear to cluster on days following poor sleep or low activity. The two highest readings (138 on 2026-01-24, 137 on 2026-02-07) both follow days with below-average activity. **Clinical context:** Alex takes Lisinopril 20 mg for hypertension. His systolic BP ranges consistently above the 120 mmHg optimal threshold but below the 140 mmHg Stage 2 hypertension cutoff. This represents "controlled but not optimized" hypertension. **Confidence: MEDIUM** -- Single daily measurement from a validated home BP device. While the device is medical-grade (Omron Evolv), one reading per day captures only morning BP. Intra-day variability is not measured. ### Activity `[Device: Apple Watch Series 9]` | Metric | Mean | Min | Max | |--------|------|-----|-----| | Steps/day | 7,207 | 4,089 | 11,987 | | Active energy | 306 kcal | 198 kcal | 498 kcal | | Exercise min | 20.1 min | 0 min | 45 min | | Stand hours | 9.4 hrs | 6 hrs | 12 hrs | **Trend:** Stable with strong weekly cyclicality. **Weekly pattern (strongly visible):** - **Gym days** (typically Mon, Wed, or Thu): 8,000-12,000 steps, 30-45 exercise minutes - **Non-gym weekdays**: 5,000-8,000 steps, 10-20 exercise minutes - **Weekends**: 4,000-5,200 steps, 0-5 exercise minutes **Notable observations:** - Zero-exercise days: 6 out of 30 days (20%) had 0 exercise minutes, all on weekends or Fridays - Weekend activity drop is significant: 60% reduction in steps vs peak gym days - The pattern is consistent over the 30-day window with no decline or improvement trend **Confidence: HIGH** -- Continuous accelerometer and heart rate monitoring throughout the day. Steps and active energy are highly accurate from wrist-worn devices during daily wear. ### Sleep `[Device: Apple Watch Series 9]` | Statistic | Value | |-----------|-------| | Mean duration | 7.10 hours | | Std Dev | 0.83 hours | | Min | 5.5 hours (2026-01-24) | | Max | 8.5 hours (2026-01-26) | | Quality: Excellent | 4 nights (13%) | | Quality: Good | 13 nights (43%) | | Quality: Fair | 10 nights (33%) | | Quality: Poor | 3 nights (10%) | **Trend:** Stable average, but with notable periodic dips. **Pattern:** Poor sleep nights (5.5-5.9 hours) occur on a roughly weekly cycle, clustering on Thursdays and Fridays: - 2026-01-24 (Friday): 5.5 hours, Poor - 2026-01-31 (Saturday): 5.8 hours, Poor - 2026-02-07 (Friday): 5.9 hours, Poor These are followed by compensatory long-sleep weekends (8.0-8.5 hours). This suggests end-of-week fatigue accumulation with weekend recovery. **Confidence: MEDIUM** -- Apple Watch sleep tracking is based on accelerometer and heart rate analysis. It captures time in bed and movement-based sleep stages but is less accurate than clinical polysomnography for quality assessment. Duration measurements are generally reliable. --- ## 2. Confidence Assessment Summary | Data Stream | Provenance | Samples/Day | Confidence | Basis | |-------------|-----------|-------------|------------|-------| | Resting Heart Rate | DeviceGenerated | 120-168 | **High** | Continuous passive monitoring, no user intervention needed | | Blood Pressure | DeviceGenerated | 1 | **Medium** | Validated device, but single daily reading; requires user action | | Activity (steps) | DeviceGenerated | Continuous | **High** | Passive accelerometer; no user action needed | | Activity (exercise min) | DeviceGenerated | Continuous | **High** | Auto-detected from HR + motion | | Sleep (duration) | DeviceGenerated | 1 | **Medium** | Wrist-based detection; less accurate than clinical measurement | | Sleep (quality) | DeviceGenerated | 1 | **Medium** | Algorithmically derived; quality labels are device-specific | | Supplements | SelfReported | N/A | **Low** | No adherence tracking; assumes patient takes as reported | --- ## 3. Cross-Stream Correlations ### Correlation 1: Poor Sleep and Elevated Heart Rate **Finding:** On the three worst sleep nights (5.5, 5.8, 5.9 hours), the following day's resting heart rate was elevated: | Poor Sleep Night | Hours | Next-Day Resting HR | 30-Day HR Average | |-----------------|-------|--------------------|--------------------| | 2026-01-24 | 5.5 | 62 bpm (2026-01-25) | 68.2 bpm | | 2026-01-31 | 5.8 | 69 bpm (2026-02-01) | 68.2 bpm | | 2026-02-07 | 5.9 | 68 bpm (2026-02-08) | 68.2 bpm | **Note:** The correlation is inconsistent. The 2026-01-25 reading is actually *lower* than average. The expected pattern (poor sleep leads to elevated HR) is not reliably demonstrated in this 30-day window. This may be due to confounding factors like weekend rest and reduced stress. **Confidence: LOW** -- Only 3 poor sleep events in the 30-day window is insufficient for statistical significance. The expected correlation is not consistently observed in this data. ### Correlation 2: Weekend Activity Drop and Blood Pressure **Finding:** Weekend days consistently show lower activity and slightly variable BP: | Day Type | Avg Steps | Avg Systolic BP | |----------|-----------|----------------| | Gym days (weekday) | ~9,800 | ~130 mmHg | | Non-gym weekdays | ~6,900 | ~130 mmHg | | Weekends | ~4,600 | ~129 mmHg | **Analysis:** Despite a 50% reduction in activity on weekends, blood pressure does not show a corresponding meaningful change. This suggests the BP is primarily pharmacologically controlled (Lisinopril) rather than activity-dependent in the short term. **Confidence: MEDIUM** -- Activity data is high-confidence, BP data is medium-confidence. The comparison is limited by having only 8 weekend days in the 30-day window. ### Correlation 3: Weekly Sleep-Activity Cycle **Finding:** A clear weekly cycle emerges when viewing sleep and activity together: ``` Mon-Wed: High activity (gym) -> Fair/Good sleep -> Moderate HR Thu-Fri: Declining activity -> Poor sleep -> Higher HR Sat-Sun: Low activity -> Long sleep -> Lower HR ``` This cycle is consistent across all 4 weeks in the data, suggesting an established lifestyle pattern rather than a random fluctuation. **Confidence: HIGH** -- Pattern repeats consistently across 4 weekly cycles with data from multiple independent streams. --- ## 4. Clinical Context Integration ### Hypertension Management Alex's home BP (mean 129/80 mmHg) on Lisinopril 20 mg indicates controlled but not optimized hypertension. The variability (systolic range 120-138) suggests room for improvement. No upward trend was detected, which is reassuring. The lack of correlation between activity and BP in this short window suggests the medication is the primary control factor. ### Diabetes and Activity Alex's exercise pattern (gym 3x/week, 20 min/day average) is below the ADA recommendation of 150 min/week of moderate activity. His 30-day total is approximately 603 exercise minutes, averaging ~140 min/week. Increasing weekend activity could help improve glycemic control (HbA1c currently 7.2%, target <7.0%). ### Medication Effects on Vitals - **Lisinopril**: Expected to lower BP and slightly lower HR. The observed resting HR of 68 bpm is consistent with mild bradycardic effect. - **Metformin**: No expected effect on vitals measured here. - **Atorvastatin**: No expected effect on vitals measured here. --- ## 5. Trend Observations Written to Pod ### Observation 1: Blood Pressure Trend ```turtle @prefix cascade: . @prefix health: . @prefix prov: . @prefix xsd: . a cascade:AIObservation ; cascade:observationType "WellnessTrendObservation" ; cascade:trendType "vitalSign" ; cascade:metric "bloodPressure" ; cascade:direction "stable" ; cascade:severity "informational" ; cascade:confidence "medium" ; cascade:dataProvenance cascade:AIGenerated ; cascade:schemaVersion "1.3" ; cascade:summary "30-day home blood pressure is stable (mean 129/80 mmHg, range 120-138/74-86). Controlled but consistently above optimal threshold of 120/80. No upward or downward trend detected. Pattern: higher readings follow low-activity or poor-sleep days." ; cascade:dataPoints 30 ; cascade:period "2026-01-20 to 2026-02-18" ; cascade:clinicalRelevance "Hypertension managed with Lisinopril 20 mg. Consider discussing optimization strategies at next visit." ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "AI Wellness Trend Analysis" ; prov:startedAtTime "2026-02-20T00:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Cascade Trend Analysis Agent" ] ] . ``` ### Observation 2: Activity Pattern ```turtle a cascade:AIObservation ; cascade:observationType "WellnessTrendObservation" ; cascade:trendType "activity" ; cascade:metric "exerciseMinutes" ; cascade:direction "stable" ; cascade:severity "informational" ; cascade:confidence "high" ; cascade:dataProvenance cascade:AIGenerated ; cascade:schemaVersion "1.3" ; cascade:summary "30-day exercise averages ~140 min/week (target 150 min/week per ADA guidelines for diabetes management). Strong weekday/weekend cyclicality: gym sessions 3x/week produce 30-45 min exercise, but 6 of 30 days had zero exercise minutes. Weekend activity drops to 4,000-5,200 steps vs 8,000-12,000 on gym days." ; cascade:dataPoints 30 ; cascade:period "2026-01-20 to 2026-02-18" ; cascade:clinicalRelevance "Patient with Type 2 Diabetes (HbA1c 7.2%, target <7.0%). Increasing weekend activity and adding 1-2 additional exercise sessions per week could help reach the 150 min/week target and potentially improve glycemic control." ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "AI Wellness Trend Analysis" ; prov:startedAtTime "2026-02-20T00:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Cascade Trend Analysis Agent" ] ] . ``` ### Observation 3: Sleep Pattern ```turtle a cascade:AIObservation ; cascade:observationType "WellnessTrendObservation" ; cascade:trendType "sleep" ; cascade:metric "sleepDuration" ; cascade:direction "stable" ; cascade:severity "informational" ; cascade:confidence "medium" ; cascade:dataProvenance cascade:AIGenerated ; cascade:schemaVersion "1.3" ; cascade:summary "30-day sleep averages 7.1 hours/night (adequate for age). However, 10% of nights (3/30) are classified as Poor quality with duration under 6 hours. Poor sleep nights cluster on Thursdays/Fridays, suggesting end-of-week fatigue accumulation. Compensatory long sleep occurs on weekends (8.0-8.5 hours)." ; cascade:dataPoints 30 ; cascade:period "2026-01-20 to 2026-02-18" ; cascade:clinicalRelevance "Sleep consistency may be more important than average duration. The weekly poor-sleep pattern could affect glucose regulation and blood pressure control. Consider sleep hygiene counseling for work-week consistency." ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "AI Wellness Trend Analysis" ; prov:startedAtTime "2026-02-20T00:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Cascade Trend Analysis Agent" ] ] . ``` ### Observation 4: Weekly Lifestyle Cycle ```turtle a cascade:AIObservation ; cascade:observationType "WellnessTrendObservation" ; cascade:trendType "crossStream" ; cascade:metric "weeklyLifestyleCycle" ; cascade:direction "cyclical" ; cascade:severity "informational" ; cascade:confidence "high" ; cascade:dataProvenance cascade:AIGenerated ; cascade:schemaVersion "1.3" ; cascade:summary "A consistent weekly lifestyle cycle was detected across all 4 data streams over 4 weeks: Mon-Wed high activity and moderate sleep, Thu-Fri declining activity and poor sleep, Sat-Sun low activity and long recovery sleep. This cycle correlates with heart rate patterns (lower on weekends, higher midweek). The pattern is lifestyle-driven rather than indicating disease progression." ; cascade:dataPoints 120 ; cascade:period "2026-01-20 to 2026-02-18" ; cascade:clinicalRelevance "Established lifestyle pattern, not a disease indicator. However, the weekly stress-recovery cycle may contribute to suboptimal chronic disease management. More consistent daily routines could benefit both BP and glucose control." ; prov:wasGeneratedBy [ a prov:Activity ; prov:label "AI Wellness Trend Analysis" ; prov:startedAtTime "2026-02-20T00:00:00Z"^^xsd:dateTime ; prov:wasAssociatedWith [ a prov:SoftwareAgent ; prov:label "Cascade Trend Analysis Agent" ] ] . ``` --- ## Limitations - **30-day window**: Insufficient for detecting long-term trends (seasonal patterns, disease progression). Minimum 90 days recommended for reliable trend detection. - **Single BP reading/day**: Morning-only BP may not capture afternoon/evening hypertensive episodes. - **No continuous glucose monitor**: Activity and sleep trends cannot be directly correlated with glucose data. Only periodic HbA1c is available. - **Self-reported supplement adherence**: Confidence in supplement-related correlations is inherently limited. --- **Disclaimer:** This analysis was generated by an AI agent processing device-generated wellness data. Trends and observations are statistical in nature and do not constitute medical diagnoses. All findings should be reviewed with a licensed healthcare provider. # Security Model: Wellness Trend Analysis Agent This document describes the data flows, access boundaries, and consent model for the wellness trend analysis agent example. ## Data Access Summary ### What the Agent Read | Resource | File | Records Read | Purpose | |----------|------|-------------|---------| | Heart rate | `wellness/heart-rate.ttl` | 30 daily readings (120-168 samples/day) | Primary trend analysis | | Blood pressure | `wellness/blood-pressure.ttl` | 30 daily readings | Primary trend analysis | | Activity | `wellness/activity.ttl` | 30 daily snapshots | Primary trend analysis | | Sleep | `wellness/sleep.ttl` | 30 daily snapshots | Primary trend analysis | | Conditions | `clinical/conditions.ttl` | 5 active conditions | Clinical context for interpretation | | Medications | `clinical/medications.ttl` | 8 medication records | Clinical context for interpretation | **Total: 6 files, ~133 records** ### What the Agent Did NOT Access | Resource | File | Reason | |----------|------|--------| | Patient demographics | `clinical/patient-profile.ttl` | Not needed for trend analysis | | Allergies | `clinical/allergies.ttl` | Not relevant to wellness trends | | Lab results | `clinical/lab-results.ttl` | Not needed for device trend analysis | | Immunizations | `clinical/immunizations.ttl` | Not relevant to wellness trends | | Vital signs (clinical) | `clinical/vital-signs.ttl` | Focused on device data; clinical vitals not used | | Insurance | `clinical/insurance.ttl` | Not relevant | | Supplements | `wellness/supplements.ttl` | Not directly analyzed (noted as low-confidence data) | ### What the Agent Wrote | Resource | Type | Provenance | Description | |----------|------|-----------|-------------| | BP trend observation | `cascade:AIObservation` | `cascade:AIGenerated` | Stable BP trend, controlled hypertension | | Activity pattern observation | `cascade:AIObservation` | `cascade:AIGenerated` | Below-target exercise, weekend drop | | Sleep pattern observation | `cascade:AIObservation` | `cascade:AIGenerated` | Weekly poor-sleep cycle | | Weekly cycle observation | `cascade:AIObservation` | `cascade:AIGenerated` | Cross-stream lifestyle pattern | **4 observations written**, all with `cascade:AIGenerated` provenance and `severity: informational`. ## Data Flow Diagram ``` +-------------------------------------------------------------------+ | LOCAL MACHINE ONLY | | | | +-------------------+ +----------------------------+ | | | Reference Patient | READ | AI Agent | | | | Pod (filesystem) |------->| (Claude Desktop or | | | | | | Claude Code) | | | | wellness/ | | | | | | heart-rate.ttl | | 1. Parse time-series | | | | blood-pressure | | 2. Compute statistics | | | | activity.ttl | | 3. Detect trends | | | | sleep.ttl | | 4. Cross-correlate | | | | | | 5. Assess confidence | | | | clinical/ | | 6. Generate observations | | | | conditions.ttl | | | | | | medications.ttl | | | | | | | WRITE | | | | | |<-------| 4 AIGenerated observations | | | +-------------------+ +----------------------------+ | | | | No data leaves this machine via Cascade tooling. | | LLM inference is the only network hop (Anthropic API). | +-------------------------------------------------------------------+ ``` ## No Data Left the Machine This is a **local-first** operation: 1. **Pod storage:** Local filesystem. No cloud uploads. 2. **MCP/CLI:** All reads and writes are local filesystem operations via stdio (MCP) or direct file access (CLI). Zero network traffic from Cascade tooling. 3. **LLM processing:** Pod data (wellness time-series and clinical context) is sent to the Anthropic API for processing. This is the only network hop. Governed by Anthropic's data handling policies. 4. **Write-back:** The 4 trend observations are written to the local Pod filesystem. They do not leave the machine. ## Consent Model ### Read Consent - **Scoped access:** This agent reads only wellness data (4 files) and clinical context (2 files). It does not access demographics, allergies, labs, immunizations, or insurance. - **Patient-initiated:** The Pod owner explicitly invokes the agent by configuring the MCP server or running the CLI. - **Minimum necessary:** The clinical context (conditions, medications) is read only to interpret wellness trends correctly, not to analyze clinical data directly. ### Write Consent The agent writes 4 observations back to the Pod: - **Explicitly requested:** The agent prompt instructs it to write trend observations. - **Clearly provenance-tagged:** All observations carry `cascade:AIGenerated` provenance. - **Non-destructive:** New observation records are appended. No existing data is modified. - **Informational severity:** All observations are marked `severity: informational`, not clinical alerts. ### Audit Records Created | Timestamp | Action | Resource | Agent | |-----------|--------|----------|-------| | T+0s | `pod_query` | `wellness/heart-rate.ttl` | Cascade Trend Analysis Agent | | T+1s | `pod_query` | `wellness/blood-pressure.ttl` | Cascade Trend Analysis Agent | | T+2s | `pod_query` | `wellness/activity.ttl` | Cascade Trend Analysis Agent | | T+3s | `pod_query` | `wellness/sleep.ttl` | Cascade Trend Analysis Agent | | T+4s | `pod_query` | `clinical/conditions.ttl` | Cascade Trend Analysis Agent | | T+5s | `pod_query` | `clinical/medications.ttl` | Cascade Trend Analysis Agent | | T+6s | `pod_write` | `observations/ai-trend-bp-20260220.ttl` | Cascade Trend Analysis Agent | | T+7s | `pod_write` | `observations/ai-trend-activity-20260220.ttl` | Cascade Trend Analysis Agent | | T+8s | `pod_write` | `observations/ai-trend-sleep-20260220.ttl` | Cascade Trend Analysis Agent | | T+9s | `pod_write` | `observations/ai-trend-cycle-20260220.ttl` | Cascade Trend Analysis Agent | All 10 audit entries persist in the Pod for patient review. ## Threat Model Considerations | Threat | Mitigation | |--------|-----------| | False trend detection | Confidence ratings + disclaimer language; findings are informational only | | Overinterpretation of 30-day window | Agent explicitly notes limitations of short observation period | | Device data inaccuracy | Provenance-based confidence rating; medical-grade devices rated higher | | Written observations treated as clinical truth | `cascade:AIGenerated` provenance clearly distinguishes from clinical data | | Agent accesses unneeded clinical data | Access limited to conditions + medications for context only | | Pod data sent to cloud LLM | Anthropic zero-retention policy; local LLM option for air-gapped use | ## Provenance Chain The write-back observations create a clear provenance chain: ``` Device (Apple Watch / Omron Evolv) --> DeviceGenerated data in Pod (raw readings) --> AI Agent reads DeviceGenerated data --> AI Agent computes trends and correlations --> AIGenerated observations written to Pod --> Human reviews AIGenerated observations --> Physician makes clinical decisions ``` Each layer is provenance-tagged. No data is relabeled or overwritten. The chain is fully auditable. ## Summary - **Data accessed:** 6 files (4 wellness, 2 clinical context), ~133 records - **Data written:** 4 AI-generated trend observations - **Network hops:** Zero for data retrieval; one for LLM inference (Anthropic API) - **No PHI left the local machine** via Cascade Protocol tooling - **Audit trail:** 10 entries logged in the Pod (6 reads + 4 writes) # Agent Example: Wellness Trend Analysis An AI agent reads 30 days of wellness time-series data from a Cascade Protocol Pod, identifies clinically relevant trends, ties confidence to provenance and sample density, and writes trend observations back to the Pod with `AIGenerated` provenance. ## Why This Matters Wearable devices generate hundreds of data points per day, but raw numbers are overwhelming and clinically unhelpful. An agent that can: 1. **Detect meaningful trends** in noisy time-series data 2. **Tie confidence to provenance** (device-generated data with 150 samples/day gets higher confidence than sparse self-reported data) 3. **Correlate across data streams** (e.g., poor sleep correlating with elevated heart rate) 4. **Write structured observations** with proper provenance ...transforms raw device data into actionable clinical intelligence. ## Prerequisites - [Cascade CLI](https://cascadeprotocol.org/docs/cli) installed (`cascade` on PATH) - The reference patient pod at `../reference-patient-pod/` - One of: - **Claude Desktop** (for MCP approach) - **Claude Code** (for CLI-as-skill approach) ## Quick Start (Under 10 Minutes) ### Option A: MCP via Claude Desktop 1. Add the Cascade MCP server to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp"], "env": { "CASCADE_POD_PATH": "/path/to/cascadeprotocol.org/reference-patient-pod" } } } } ``` 2. Restart Claude Desktop. 3. Open a new conversation and paste the contents of `agent-prompt.md` as your message. 4. The agent will use `cascade_pod_query` to read wellness data and `cascade_write` to write observations. ### Option B: CLI-as-Skill via Claude Code 1. Open a terminal in this directory. 2. Run Claude Code: ```bash export CASCADE_POD_PATH="../reference-patient-pod" claude ``` 3. Paste the contents of `agent-prompt.md` as your prompt. 4. The agent will invoke the Cascade CLI: ```bash # Read wellness time-series data cascade pod query ./reference-patient-pod --wellness --json # Or read individual categories cascade pod query ./reference-patient-pod --heart-rate --json cascade pod query ./reference-patient-pod --blood-pressure --json cascade pod query ./reference-patient-pod --activity --json cascade pod query ./reference-patient-pod --sleep --json # Read clinical context (conditions, medications) cascade pod query ./reference-patient-pod --conditions --json cascade pod query ./reference-patient-pod --medications --json # Write back observations cascade pod write ./reference-patient-pod --type observation --json '{...}' ``` ## Files in This Example | File | Purpose | |------|---------| | `README.md` | This file -- setup and walkthrough | | `agent-prompt.md` | The prompt/instructions to give the agent | | `expected-output.md` | What the agent should produce (reference answer) | | `security-model.md` | Data flow documentation and consent model | ## What the Agent Should Do 1. **Read** 30 days of wellness time-series data (heart rate, blood pressure, activity, sleep) 2. **Identify** clinically relevant trends (rising BP, declining activity, sleep irregularity) 3. **Tie confidence to provenance**: Device data with many daily samples gets higher confidence than sparse self-reported data 4. **Correlate across streams**: Link poor sleep to elevated HR, low activity to BP changes 5. **Write** trend observations back to the Pod with `AIGenerated` provenance ## Key Cascade Protocol Concepts Demonstrated - **Time-series analysis**: Processing 30 days of daily snapshots to extract trends - **Provenance-weighted confidence**: Device data with high sample counts rated more confidently - **Cross-stream correlation**: Linking patterns across different wellness data types - **Structured write-back**: Trend observations tagged with `cascade:AIGenerated` provenance - **Clinical context integration**: Using conditions and medications to interpret wellness patterns ## Reference Patient **Alex Rivera**, 52M, Portland, OR. See `../reference-patient-pod/README.md` for the full patient narrative. ### Wellness Data Available | Data Stream | Source | Days | Samples/Day | Date Range | |-------------|--------|------|-------------|-----------| | Resting Heart Rate | Apple Watch Series 9 | 30 | 120-168 | 2026-01-20 to 2026-02-18 | | Blood Pressure | Omron Evolv | 30 | 1 | 2026-01-20 to 2026-02-18 | | Activity (steps, energy, exercise) | Apple Watch Series 9 | 30 | Continuous | 2026-01-20 to 2026-02-18 | | Sleep (duration, quality) | Apple Watch Series 9 | 30 | 1 | 2026-01-20 to 2026-02-18 | ## Example: agent-visit-prep # Agent Example: Visit Preparation Summary An AI agent reads a patient's entire Cascade Protocol Pod and generates a structured visit summary for an upcoming doctor appointment. The summary distinguishes clinical-source data from device-source data and identifies open questions based on data gaps. ## Why This Matters Patients accumulate health data across multiple systems: EHR exports, wearable devices, and self-reported supplements. Before a doctor visit, this data is scattered and unstructured. An agent that can read a Cascade Pod and produce a concise, provenance-annotated summary saves time for both patient and physician. Key challenges: - **Provenance differentiation:** Clinical data from an EHR has different reliability characteristics than device-generated or self-reported data - **Data completeness assessment:** Identifying what is missing is as important as summarizing what is present - **Trend extraction:** Transforming 30 days of raw vitals into actionable insights ## Prerequisites - [Cascade CLI](https://cascadeprotocol.org/docs/cli) installed (`cascade` on PATH) - The reference patient pod at `../reference-patient-pod/` - One of: - **Claude Desktop** (for MCP approach) - **Claude Code** (for CLI-as-skill approach) ## Quick Start (Under 10 Minutes) ### Option A: MCP via Claude Desktop 1. Add the Cascade MCP server to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp"], "env": { "CASCADE_POD_PATH": "/path/to/cascadeprotocol.org/reference-patient-pod" } } } } ``` 2. Restart Claude Desktop. 3. Open a new conversation and paste the contents of `agent-prompt.md` as your message. 4. The agent will use `cascade_pod_query` with `--all` to read the full Pod. ### Option B: CLI-as-Skill via Claude Code 1. Open a terminal in this directory. 2. Run Claude Code: ```bash export CASCADE_POD_PATH="../reference-patient-pod" claude ``` 3. Paste the contents of `agent-prompt.md` as your prompt. 4. The agent will invoke the Cascade CLI: ```bash # Read everything cascade pod query ./reference-patient-pod --all --json # Or read category by category cascade pod query ./reference-patient-pod --medications --json cascade pod query ./reference-patient-pod --conditions --json cascade pod query ./reference-patient-pod --allergies --json cascade pod query ./reference-patient-pod --labs --json cascade pod query ./reference-patient-pod --vitals --json cascade pod query ./reference-patient-pod --wellness --json cascade pod query ./reference-patient-pod --immunizations --json cascade pod info ./reference-patient-pod --json ``` ## Files in This Example | File | Purpose | |------|---------| | `README.md` | This file -- setup and walkthrough | | `agent-prompt.md` | The prompt/instructions to give the agent | | `expected-output.md` | What the agent should produce (reference answer) | | `security-model.md` | Data flow documentation and consent model | ## What the Agent Should Do 1. **Read** the complete Pod (all clinical and wellness data) 2. **Generate** a structured visit summary containing: - Medication list with provenance annotations - Recent lab results with interpretation - Vital sign trends from both clinical and device sources - Open questions based on data gaps 3. **Distinguish** clinical-source data from device-source data throughout 4. **Identify** action items for the upcoming visit ## Key Cascade Protocol Concepts Demonstrated - **Full Pod traversal**: Reading across all resource categories - **Provenance annotation**: Every data point attributed to its source - **Clinical vs device data**: Same vital sign type from different sources shown side-by-side - **Data gap identification**: Recognizing missing or outdated information ## Reference Patient **Alex Rivera**, 52M, Portland, OR. See `../reference-patient-pod/README.md` for the full patient narrative. # Agent Prompt: Visit Preparation Summary Use this prompt with Claude Desktop (MCP) or Claude Code (CLI-as-skill) to generate a visit preparation summary from the reference patient pod. --- ## Prompt You are a visit preparation agent. You have access to a patient's Cascade Protocol Pod containing their complete health record. Your job is to generate a comprehensive, structured visit summary that the patient can bring to their next doctor appointment. **Important:** The patient's Pod contains data from three provenance sources. You must clearly label every data point with its provenance: - `[Clinical]` -- from the patient's EHR (ClinicalGenerated) - `[Device]` -- from wearable devices (DeviceGenerated) - `[Self-Reported]` -- entered by the patient (SelfReported) ### Your Task **Step 1: Read the full Pod.** Using the Cascade MCP server: ``` cascade_pod_query({ category: "all", format: "json" }) ``` Or using the CLI: ```bash cascade pod query ./reference-patient-pod --all --json ``` If the `--all` flag is not available, read each category individually: ```bash cascade pod query ./reference-patient-pod --medications --json cascade pod query ./reference-patient-pod --conditions --json cascade pod query ./reference-patient-pod --allergies --json cascade pod query ./reference-patient-pod --labs --json cascade pod query ./reference-patient-pod --vitals --json cascade pod query ./reference-patient-pod --supplements --json cascade pod query ./reference-patient-pod --immunizations --json cascade pod info ./reference-patient-pod --json ``` **Step 2: Generate a structured visit summary.** The summary must include the following sections: #### Section 1: Patient Overview - Name, age, key demographics - Active conditions with onset dates #### Section 2: Current Medications - Complete medication list with dose, frequency, prescriber, and provenance - Separate prescribed medications from self-reported supplements/OTC drugs - Flag any potential concerns (interactions, expired prescriptions, etc.) #### Section 3: Allergies - All known allergies with severity and reaction type - Note provenance (EHR-confirmed vs self-reported) #### Section 4: Recent Lab Results - All lab results from the most recent panel - Flag abnormal values with reference ranges - Note trends if prior results are available #### Section 5: Vital Sign Trends - **Clinical vitals**: From the EHR (clinical/vital-signs.ttl) - **Device vitals**: From wearable devices (wellness/) - Present side-by-side where the same vital is available from both sources - Calculate 30-day averages, ranges, and trends #### Section 6: Wellness Data Summary - Activity trends (steps, exercise minutes, active energy) - Sleep patterns (average duration, quality distribution) - Heart rate trends (resting HR from Apple Watch) #### Section 7: Immunization Status - Current immunizations with dates - Any that may be due for update #### Section 8: Open Questions for the Visit - Data gaps you identified (missing labs, overdue screenings, etc.) - Discrepancies between data sources - Items that need physician attention ### Output Format Format the summary as a clean, printable document. Use tables where appropriate. Every data point must include a provenance tag: `[Clinical]`, `[Device]`, or `[Self-Reported]`. ### Constraints - This is a preparation document, not clinical advice. - Do not diagnose or prescribe. Summarize and highlight. - Always note the date of each data point so the physician can assess currency. - If clinical and device data conflict, present both and note the discrepancy. # Expected Output: Visit Preparation Summary This document shows the expected agent output when running the visit preparation prompt against the Alex Rivera reference patient pod. --- # Visit Preparation Summary **Patient:** Alex Rivera | **Age:** 52 | **Sex:** Male | **DOB:** August 15, 1973 **Location:** Portland, Oregon **Primary Care Provider:** Dr. Sarah Chen **Emergency Contact:** Maria Rivera (spouse) **Insurance:** Blue Cross Blue Shield (PPO), active since 2020-01-01 `[Clinical]` **Prepared:** 2026-02-20 --- ## 1. Active Conditions | Condition | ICD-10 | Onset | Status | Provenance | |-----------|--------|-------|--------|-----------| | Essential Hypertension | I10 | 2018-03-22 | Active | `[Clinical]` | | Type 2 Diabetes Mellitus | E11.9 | 2020-06-10 | Active | `[Clinical]` | | Hyperlipidemia | E78.5 | 2019-11-05 | Active | `[Clinical]` | | Mild Persistent Asthma | J45.30 | 2005-09-01 | Active | `[Clinical]` | | Seasonal Allergic Rhinitis | J30.2 | 2010-04-15 | Active | `[Clinical]` | --- ## 2. Current Medications ### Prescribed Medications `[Clinical]` | Medication | Dose | Frequency | Route | Indication | Prescriber | Start Date | |-----------|------|-----------|-------|------------|------------|-----------| | Lisinopril | 20 mg | Once daily | Oral | Hypertension | Dr. Sarah Chen | 2024-06-15 | | Metformin | 1000 mg | Twice daily | Oral | Type 2 Diabetes | Dr. Sarah Chen | 2020-07-01 | | Atorvastatin | 40 mg | Once daily | Oral | Hyperlipidemia | Dr. Sarah Chen | 2019-12-01 | | Albuterol | 90 mcg/actuation | As needed | Inhalation | Asthma | Dr. Sarah Chen | 2005-09-15 | ### Discontinued `[Clinical]` | Medication | Dose | Reason | Discontinued | |-----------|------|--------|-------------| | Amlodipine | 5 mg | Switched to lisinopril monotherapy for renal protection | 2024-06-15 | ### Self-Reported Supplements and OTC Medications `[Self-Reported]` | Item | Dose | Frequency | Reason | |------|------|-----------|--------| | Cetirizine (Zyrtec) | 10 mg | Once daily | Seasonal allergic rhinitis | | Potassium Chloride | 20 mEq | Once daily | Muscle cramps | | Fish Oil (Omega-3) | 1000 mg | Once daily | Cardiovascular health | **FLAG:** Potassium Chloride supplement (self-reported) may interact with Lisinopril (ACE inhibitor). See Lab Results section for serum potassium level. --- ## 3. Allergies | Allergen | Category | Reaction | Severity | Provenance | |----------|----------|----------|----------|-----------| | Penicillin | Medication | Hives (urticaria) | Moderate | `[Clinical]` | | Sulfonamide antibiotics | Medication | Maculopapular rash | Mild | `[Clinical]` | | Shellfish | Food | Throat swelling (angioedema) | **Severe** -- carries EpiPen | `[Self-Reported]` | --- ## 4. Recent Lab Results (2026-01-15) All labs from a comprehensive metabolic panel drawn on 2026-01-15 at Cascade Regional Medical Lab. Ordered by Dr. Sarah Chen. `[Clinical]` | Test | Result | Unit | Reference Range | Status | |------|--------|------|----------------|--------| | HbA1c | 7.2 | % | 4.0 - 5.6 | **Abnormal** (target <7.0%) | | Fasting Glucose | 142 | mg/dL | 70 - 100 | **Abnormal** | | Total Cholesterol | 198 | mg/dL | < 200 | Normal (borderline) | | LDL Cholesterol | 112 | mg/dL | < 100 | **Abnormal** (target <100 for diabetic) | | HDL Cholesterol | 48 | mg/dL | > 40 | Normal (low-normal) | | Triglycerides | 190 | mg/dL | < 150 | **Abnormal** | | Serum Creatinine | 1.1 | mg/dL | 0.7 - 1.3 | Normal | | eGFR | 78 | mL/min/1.73m2 | > 90 | **Abnormal** (CKD Stage 2) | | **Serum Potassium** | **5.1** | **mEq/L** | **3.5 - 5.0** | **Abnormal -- borderline high** | | TSH | 2.1 | mIU/L | 0.4 - 4.0 | Normal | | WBC | 6.8 | x10^3/uL | 4.5 - 11.0 | Normal | **Key concerns:** - Serum K+ 5.1 mEq/L above normal, relevant given lisinopril + self-reported potassium supplement - HbA1c 7.2% above target of <7.0%, indicating suboptimal glycemic control - LDL 112 mg/dL above target of <100 for a diabetic patient - Triglycerides 190 mg/dL elevated - eGFR 78 mL/min indicates mildly decreased kidney function (CKD Stage 2) --- ## 5. Vital Sign Trends (30 Days: 2026-01-20 to 2026-02-18) ### Blood Pressure -- Clinical vs Device Comparison | Source | Systolic (Avg) | Systolic (Range) | Diastolic (Avg) | Diastolic (Range) | Provenance | |--------|---------------|-----------------|----------------|-------------------|-----------| | Clinical vital signs | ~132 mmHg | 120-140 mmHg | ~82 mmHg | 74-88 mmHg | `[Clinical]` | | Omron Evolv (home) | ~129 mmHg | 120-138 mmHg | ~80 mmHg | 74-86 mmHg | `[Device]` | **Observation:** Home blood pressure readings trend slightly lower than clinical readings, which is a common "white coat" effect. Both sources show systolic values consistently above the 120 mmHg optimal target, consistent with managed but not fully controlled hypertension. ### Heart Rate | Source | Average | Range | Provenance | |--------|---------|-------|-----------| | Clinical setting | ~72 bpm | 64-80 bpm | `[Clinical]` | | Apple Watch (resting HR) | ~68 bpm | 58-82 bpm | `[Device]` | **Observation:** Resting heart rate from continuous wearable monitoring averages slightly lower than clinical spot-checks, which is expected. The device provides 130-168 samples/day giving high confidence in trend data. Normal range for a 52-year-old male. --- ## 6. Wellness Data Summary (30 Days) `[Device]` ### Activity (Apple Watch Series 9) | Metric | 30-Day Average | Range | Pattern | |--------|---------------|-------|---------| | Steps/day | ~7,200 | 4,089 - 11,987 | Higher on weekdays, lower on weekends | | Active energy | ~306 kcal/day | 198 - 498 kcal | Peaks on gym days | | Exercise minutes | ~20 min/day | 0 - 45 min | ~3 sessions/week | | Stand hours | ~9.5 hrs/day | 6 - 12 hrs | Lower on weekends | **Observation:** Activity pattern is consistent with the reported routine (desk job, gym 3x/week). Weekend activity drops significantly (4,000-5,000 steps vs 8,000-12,000 on gym days). Several zero-exercise days noted. ### Sleep (Apple Watch Series 9) | Metric | 30-Day Average | Range | |--------|---------------|-------| | Duration | ~7.1 hours/night | 5.5 - 8.5 hours | | Quality distribution | Good: 13 nights, Fair: 9 nights, Excellent: 4 nights, Poor: 3 nights | **Observation:** Average sleep duration is adequate (7.1 hours) but with notable variability. Three nights below 6 hours (classified "Poor") were noted on 2026-01-24, 2026-01-31, and 2026-02-07 -- all Fridays, suggesting possible end-of-week stress or social activity pattern. Poor sleep nights correlate with higher resting heart rate the following day. --- ## 7. Immunization Status `[Clinical]` | Vaccine | Date | Status | Next Due | |---------|------|--------|----------| | COVID-19 mRNA (Pfizer, 2024-25 formula) | 2024-10-15 | Complete | Per updated guidance | | Influenza (Quadrivalent, 2025-26) | 2025-09-20 | Complete | Fall 2026 | | Tdap | 2022-03-10 | Complete | 2032 | | PCV20 (Pneumococcal) | 2025-01-08 | Complete | One-time dose (done) | **Status:** All immunizations are current. No action required. --- ## 8. Open Questions for the Visit ### Priority Items 1. **Potassium supplement disclosure:** The self-reported potassium chloride 20 mEq supplement does not appear in the clinical medication record. The lab note asks to "verify patient is not taking potassium supplements." This must be discussed with Dr. Chen. 2. **Serum potassium recheck:** The 2026-01-15 lab recommended rechecking potassium in 2 weeks. As of this summary date, it has been more than 4 weeks. A repeat potassium level is overdue. 3. **HbA1c management:** At 7.2%, the HbA1c is above the 7.0% target. Discuss lifestyle modifications or medication adjustment. 4. **LDL cholesterol target:** LDL at 112 mg/dL exceeds the <100 target for a patient with diabetes. Consider atorvastatin dose adjustment or additional therapy. ### Data Gaps Identified | Gap | Detail | |-----|--------| | No prior lab results | Only one lab panel available (2026-01-15). Trending not possible without historical data. | | No ophthalmology records | Diabetic patients should have annual eye exams. No records present. | | No podiatry records | Diabetic foot exams recommended annually. No records present. | | No dental records | Not available in the Pod | | Supplement start context | Potassium supplement started 2025-08-01 but no documentation of leg cramp assessment | ### Discrepancies Between Sources | Observation | Detail | |-------------|--------| | BP: Clinical vs Device | Clinical readings average ~3 mmHg higher systolic than home readings. Not clinically significant but consistent with white-coat effect. | | Medication list completeness | Clinical medication list includes 5 prescribed + 3 self-reported. Supplement list includes 3 items. Cetirizine and Fish Oil appear in both. Potassium appears in both. Cross-referencing is consistent. | --- **This summary was generated from Cascade Protocol Pod data and is intended for visit preparation purposes only. It is not a clinical document and should be reviewed with the patient's healthcare provider.** # Security Model: Visit Preparation Summary Agent This document describes the data flows, access boundaries, and consent model for the visit preparation summary agent example. ## Data Access Summary ### What the Agent Read This agent requires **full Pod access** to generate a comprehensive visit summary. | Resource | File | Records Read | |----------|------|-------------| | Patient profile | `clinical/patient-profile.ttl` | 1 profile (demographics, emergency contact) | | Conditions | `clinical/conditions.ttl` | 5 active conditions | | Medications | `clinical/medications.ttl` | 8 medication records | | Allergies | `clinical/allergies.ttl` | 3 allergy records | | Lab results | `clinical/lab-results.ttl` | 11 lab results | | Immunizations | `clinical/immunizations.ttl` | 4 immunization records | | Vital signs | `clinical/vital-signs.ttl` | 30 days (180 individual readings) | | Insurance | `clinical/insurance.ttl` | 1 coverage record | | Heart rate | `wellness/heart-rate.ttl` | 30 daily readings | | Blood pressure | `wellness/blood-pressure.ttl` | 30 daily readings | | Activity | `wellness/activity.ttl` | 30 daily snapshots | | Sleep | `wellness/sleep.ttl` | 30 daily snapshots | | Supplements | `wellness/supplements.ttl` | 3 supplement records | **Total resources accessed:** 13 files, ~356 individual records ### What the Agent Wrote This agent is **read-only**. It generates a text summary but does not write any data back to the Pod. The visit summary output is displayed to the user and can be printed or saved separately. ## Data Flow Diagram ``` +-------------------------------------------------------------------+ | LOCAL MACHINE ONLY | | | | +-------------------+ +----------------------------+ | | | Reference Patient | READ | AI Agent | | | | Pod (filesystem) |------->| (Claude Desktop or | | | | | | Claude Code) | | | | clinical/ | | | | | | patient-profile | | 1. Parse all categories | | | | conditions | | 2. Cross-reference data | | | | medications | | 3. Compute trends | | | | allergies | | 4. Identify gaps | | | | lab-results | | 5. Generate summary doc | | | | immunizations | | | | | | vital-signs | +-------------+--------------+ | | | insurance | | | | | | v | | | wellness/ | +----------------------------+ | | | heart-rate | | Visit Summary (text) | | | | blood-pressure | | Displayed to user | | | | activity | | Can be printed/saved | | | | sleep | +----------------------------+ | | | supplements | | | +-------------------+ | | | | No data leaves this machine via Cascade tooling. | | No write-back to the Pod occurs. | +-------------------------------------------------------------------+ ``` ## No Data Left the Machine This is a **local-first, read-only** operation: 1. **Pod storage:** Local filesystem only. No cloud uploads. 2. **MCP/CLI:** All data retrieval happens locally via stdio (MCP) or filesystem reads (CLI). Zero network traffic from Cascade tooling. 3. **LLM processing:** Pod data is sent to the Anthropic API as conversation context for processing. This is the only network hop. Governed by Anthropic's data handling policies (not used for training, zero-retention available). 4. **Output:** The visit summary is displayed in the chat interface. It is not automatically sent anywhere. The user decides whether to print, save, or share it. ## Consent Model ### Read Consent - **Full Pod access:** This agent reads every category in the Pod. The patient grants this access by configuring the MCP server or invoking the CLI with the Pod path. - **Justification:** A visit preparation summary requires comprehensive data access. Partial access would produce an incomplete summary. - **Scope control (future):** For agents that need less data, the Cascade CLI will support `--scope` flags to restrict access. ### Write Consent - **No write operations.** This agent is purely analytical. It does not modify the Pod in any way. ### Audit Records Created Each MCP tool invocation or CLI command generates an audit log entry: | Timestamp | Action | Resource | Agent | |-----------|--------|----------|-------| | T+0s | `pod_query` | `clinical/patient-profile.ttl` | Cascade Visit Prep Agent | | T+1s | `pod_query` | `clinical/conditions.ttl` | Cascade Visit Prep Agent | | T+2s | `pod_query` | `clinical/medications.ttl` | Cascade Visit Prep Agent | | T+3s | `pod_query` | `clinical/allergies.ttl` | Cascade Visit Prep Agent | | T+4s | `pod_query` | `clinical/lab-results.ttl` | Cascade Visit Prep Agent | | T+5s | `pod_query` | `clinical/immunizations.ttl` | Cascade Visit Prep Agent | | T+6s | `pod_query` | `clinical/vital-signs.ttl` | Cascade Visit Prep Agent | | T+7s | `pod_query` | `clinical/insurance.ttl` | Cascade Visit Prep Agent | | T+8s | `pod_query` | `wellness/heart-rate.ttl` | Cascade Visit Prep Agent | | T+9s | `pod_query` | `wellness/blood-pressure.ttl` | Cascade Visit Prep Agent | | T+10s | `pod_query` | `wellness/activity.ttl` | Cascade Visit Prep Agent | | T+11s | `pod_query` | `wellness/sleep.ttl` | Cascade Visit Prep Agent | | T+12s | `pod_query` | `wellness/supplements.ttl` | Cascade Visit Prep Agent | All 13 audit entries persist in the Pod for patient review. ## Threat Model Considerations | Threat | Mitigation | |--------|-----------| | Agent reads entire Pod | Intentional for comprehensive summary; future scoped access available | | Summary contains sensitive data | Output is local-only, displayed to the Pod owner. Not persisted to Pod. | | Summary shared inappropriately | Patient responsibility; summary is plaintext, not a controlled resource | | Pod data sent to cloud LLM | Anthropic zero-retention policy; local LLM option for air-gapped use | | Incorrect summary content | Clearly labeled as preparation document, not clinical advice | | Stale data presented as current | All data points include their original dates; agent notes data currency | ## Privacy Considerations for Printing If the patient prints the visit summary to bring to an appointment: - The printed document contains PHI (names, conditions, medications, lab values) - The patient should handle it as they would any medical document - The Cascade Protocol does not control or track printed copies - Consider using the `--redact-identifiers` flag (future) for partially anonymized summaries ## Summary - **Data accessed:** 13 files (full Pod), ~356 individual records - **Data written:** None (read-only agent) - **Network hops:** Zero for data retrieval; one for LLM inference (Anthropic API) - **No PHI left the local machine** via Cascade Protocol tooling - **Audit trail:** 13 entries logged in the Pod # Agent Example: Visit Preparation Summary An AI agent reads a patient's entire Cascade Protocol Pod and generates a structured visit summary for an upcoming doctor appointment. The summary distinguishes clinical-source data from device-source data and identifies open questions based on data gaps. ## Why This Matters Patients accumulate health data across multiple systems: EHR exports, wearable devices, and self-reported supplements. Before a doctor visit, this data is scattered and unstructured. An agent that can read a Cascade Pod and produce a concise, provenance-annotated summary saves time for both patient and physician. Key challenges: - **Provenance differentiation:** Clinical data from an EHR has different reliability characteristics than device-generated or self-reported data - **Data completeness assessment:** Identifying what is missing is as important as summarizing what is present - **Trend extraction:** Transforming 30 days of raw vitals into actionable insights ## Prerequisites - [Cascade CLI](https://cascadeprotocol.org/docs/cli) installed (`cascade` on PATH) - The reference patient pod at `../reference-patient-pod/` - One of: - **Claude Desktop** (for MCP approach) - **Claude Code** (for CLI-as-skill approach) ## Quick Start (Under 10 Minutes) ### Option A: MCP via Claude Desktop 1. Add the Cascade MCP server to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "cascade": { "command": "cascade", "args": ["serve", "--mcp"], "env": { "CASCADE_POD_PATH": "/path/to/cascadeprotocol.org/reference-patient-pod" } } } } ``` 2. Restart Claude Desktop. 3. Open a new conversation and paste the contents of `agent-prompt.md` as your message. 4. The agent will use `cascade_pod_query` with `--all` to read the full Pod. ### Option B: CLI-as-Skill via Claude Code 1. Open a terminal in this directory. 2. Run Claude Code: ```bash export CASCADE_POD_PATH="../reference-patient-pod" claude ``` 3. Paste the contents of `agent-prompt.md` as your prompt. 4. The agent will invoke the Cascade CLI: ```bash # Read everything cascade pod query ./reference-patient-pod --all --json # Or read category by category cascade pod query ./reference-patient-pod --medications --json cascade pod query ./reference-patient-pod --conditions --json cascade pod query ./reference-patient-pod --allergies --json cascade pod query ./reference-patient-pod --labs --json cascade pod query ./reference-patient-pod --vitals --json cascade pod query ./reference-patient-pod --wellness --json cascade pod query ./reference-patient-pod --immunizations --json cascade pod info ./reference-patient-pod --json ``` ## Files in This Example | File | Purpose | |------|---------| | `README.md` | This file -- setup and walkthrough | | `agent-prompt.md` | The prompt/instructions to give the agent | | `expected-output.md` | What the agent should produce (reference answer) | | `security-model.md` | Data flow documentation and consent model | ## What the Agent Should Do 1. **Read** the complete Pod (all clinical and wellness data) 2. **Generate** a structured visit summary containing: - Medication list with provenance annotations - Recent lab results with interpretation - Vital sign trends from both clinical and device sources - Open questions based on data gaps 3. **Distinguish** clinical-source data from device-source data throughout 4. **Identify** action items for the upcoming visit ## Key Cascade Protocol Concepts Demonstrated - **Full Pod traversal**: Reading across all resource categories - **Provenance annotation**: Every data point attributed to its source - **Clinical vs device data**: Same vital sign type from different sources shown side-by-side - **Data gap identification**: Recognizing missing or outdated information ## Reference Patient **Alex Rivera**, 52M, Portland, OR. See `../reference-patient-pod/README.md` for the full patient narrative. # ============================================================================ # SECTION 10: SECURITY AND COMPLIANCE GUIDE # ============================================================================ Security & Compliance Guide - Cascade Protocol /* ── Security Guide Layout ─────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } /* ── Sidebar / Table of Contents ───────────────────────────────────── */ .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } /* ── Main Content Area ─────────────────────────────────────────────── */ .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { display: flex; align-items: center; gap: var(--space-sm); } .doc-content .section h3 { margin-top: var(--space-lg); } /* ── Shield Icon (inline SVG via CSS) ──────────────────────────────── */ .shield-icon { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; } .shield-icon svg { width: 22px; height: 22px; } /* ── Status Indicators ─────────────────────────────────────────────── */ .status-row { display: flex; align-items: flex-start; gap: var(--space-sm); margin-bottom: var(--space-sm); line-height: 1.6; } .status-handled::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--success-border); margin-top: 0.55em; flex-shrink: 0; } .status-infra::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--warning-border); margin-top: 0.55em; flex-shrink: 0; } .status-not::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--danger-border); margin-top: 0.55em; flex-shrink: 0; } /* ── Legend row ────────────────────────────────────────────────────── */ .legend { display: flex; gap: var(--space-lg); flex-wrap: wrap; margin-bottom: var(--space-lg); font-size: 0.875rem; color: var(--text-secondary); } .legend-item { display: flex; align-items: center; gap: var(--space-xs); } .legend-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .legend-dot--green { background: var(--success-border); } .legend-dot--amber { background: var(--warning-border); } .legend-dot--red { background: var(--danger-border); } /* ── Data Flow Diagram ─────────────────────────────────────────────── */ .flow-diagram { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-xl); text-align: center; margin: var(--space-lg) 0; font-family: var(--font-mono); font-size: 0.875rem; line-height: 2; overflow-x: auto; } .flow-diagram .flow-main { color: var(--primary); font-weight: 600; } .flow-diagram .flow-no { color: var(--danger-border); font-weight: 600; } .flow-diagram .flow-arrow { color: var(--text-muted); } /* ── Provenance Table ──────────────────────────────────────────────── */ .prov-type { font-family: var(--font-mono); font-size: 0.85rem; white-space: nowrap; } /* ── Responsive ────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { position: static; max-height: none; border-bottom: 1px solid var(--border); padding-bottom: var(--space-lg); margin-bottom: var(--space-lg); } } Cascade Protocol Home Documentation Contact Security & Compliance Guide Architecture, PHI handling, audit logging, and regulatory considerations Documentation / Security On this page Security Architecture Overview PHI Handling Provenance Model Audit Logging AI Agent Access Encryption Regulatory Considerations Threat Model Open Source Audit Legend Cascade handles this Requires user infrastructure Not in scope Last updated: February 2026 — Applies to Cascade Protocol CLI v0.x, CascadeSDK (Swift) v0.x, and all published vocabularies. Security Architecture Overview The Cascade Protocol follows a local-first, zero-network-call architecture. All data processing occurs on the user's machine. There are no backend services, cloud databases, or remote APIs involved in core protocol operations. Design Principles Local-only processing. The CLI, MCP server, and Swift SDK operate entirely on the local filesystem. No data leaves the device during normal operation. Open source. All repositories are public, enabling full code audit. No proprietary dependencies are required for core functionality. No telemetry. There are no analytics, crash reporting, phone-home behaviors, or tracking of any kind in the protocol tools or SDK. Minimal attack surface. The protocol defines data structure and serialization, not networking. There is no HTTP server, no database daemon, and no listening socket in default operation. Data Flow Diagram [ User's Health Data ]  ——>  [ Cascade CLI / SDK (local process) ]  ——>  [ Local Pod (encrypted at rest) ] NO data sent to external APIs |  NO cloud storage |  NO telemetry |  NO analytics Verifiable claim The absence of network calls can be verified by auditing the source code or running the CLI under a network monitor (e.g., Little Snitch, tcpdump, Wireshark). The CLI and SDK make zero outbound connections. PHI Handling Protected Health Information (PHI) under HIPAA includes any individually identifiable health information. The Cascade Protocol stores PHI categories locally in the user's Pod as RDF/Turtle files, organized by data type. PHI Categories The protocol defines the following PHI-relevant data categories, each stored in its own directory within the Pod: Category Pod Directory Vocabulary Medicationsclinical/clinical: Conditionsclinical/clinical: Allergiesclinical/clinical: Lab Resultsclinical/clinical: Vital Signswellness/ or clinical/health: / clinical: Immunizationsclinical/clinical: Demographicsprofile/cascade: Consent Model The protocol supports category-based consent filtering. When sharing data (e.g., via the MCP server for AI agent access or via export), consumers can specify which PHI categories to include or exclude. Only authorized categories are surfaced. The consent configuration is defined per Pod using a consent.json file in the Pod root. This allows fine-grained control over which data categories are accessible to external consumers. // Example consent.json (planned implementation) { "version": "1.0", "categories": { "medications": { "share": true }, "conditions": { "share": true }, "allergies": { "share": true }, "lab_results": { "share": false }, "vital_signs": { "share": true }, "immunizations": { "share": true }, "demographics": { "share": false } }, "defaultPolicy": "deny" } Implementation status The consent.json configuration file is specified but not yet enforced at the protocol level. Current implementations rely on the consuming application to respect category filtering. Enforcement at the MCP server layer is planned for a future release. Data Minimization The protocol follows a share-only-what-is-authorized principle. Export and query operations accept a list of requested data types, and only matching records are returned. There is no "select all" behavior in the MCP server — the agent must explicitly request each data type. Provenance Model Every record in the Cascade Protocol carries provenance metadata based on the W3C PROV-O ontology. Provenance distinguishes how data was created and by whom, which is critical for clinical decision-making and regulatory compliance. Provenance Types Type URI Description Example Source ClinicalGenerated cascade:ClinicalGenerated Data originating from clinical or EHR systems Apple Health clinical records imported from a hospital DeviceGenerated cascade:DeviceGenerated Data from wearable or medical devices Apple Watch heart rate, blood oxygen readings SelfReported cascade:SelfReported Data entered directly by the patient Symptom diary entry, manually entered medication AIExtracted cascade:AIExtracted AI-extracted from existing clinical documents Structured data parsed from a scanned lab report AIGenerated cascade:AIGenerated AI-generated observations, analyses, or recommendations Agent-written health summary, trend analysis Provenance Attachment Provenance is attached to every record as RDF triples using the cascade: and prov: namespaces. The following Turtle example demonstrates how provenance metadata is serialized: @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix prov: <http://www.w3.org/ns/prov#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890> a clinical:Medication ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:schemaVersion "1.5" ; prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-123> ; prov:generatedAtTime "2026-01-15T10:30:00Z"^^xsd:dateTime ; clinical:drugName "Metoprolol" ; clinical:dosage "25 mg" ; clinical:frequency "twice daily" . Verifying Provenance Provenance can be verified by querying the Pod's Turtle files for cascade:dataProvenance triples. The cascade_pod_query MCP tool and the CLI cascade pod read command both return provenance metadata with every record. To audit provenance across all records: # Query all records and inspect provenance fields cascade pod read --path ./my-pod # Or use the MCP server programmatically via cascade_pod_query # The response includes dataProvenance for each record Audit Logging All operations performed through the MCP server are logged to an append-only audit log stored within the Pod at provenance/audit-log.ttl. This log is written in RDF/Turtle format using Cascade Protocol vocabulary. Log Format Each audit entry records the following fields: Field RDF Property Description Timestamp cascade:timestamp ISO 8601 datetime of the operation Operation cascade:operation Type of operation: read, write, query, validate, convert Data Types cascade:dataTypes RDF list of data types accessed (e.g., "medications", "conditions") Agent ID cascade:agentId Identifier of the AI agent or tool that performed the operation Record Count cascade:recordsAccessed Number of records read or written Example Audit Entry The following is a real example of the Turtle output written by the audit logger (from cli/src/lib/mcp/audit.ts): @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix prov: <http://www.w3.org/ns/prov#> . # Cascade Protocol Agent Audit Log # Generated by cascade serve --mcp <#audit-3f8a91c2-7b4d-4e6f-9a1c-2d3e4f5a6b7c> a cascade:AuditEntry ; cascade:timestamp "2026-02-15T14:30:22.451Z"^^xsd:dateTime ; cascade:operation "read" ; cascade:dataTypes ("medications" "conditions") ; cascade:agentId "claude-desktop-v1" ; cascade:recordsAccessed 12 . <#audit-8c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f> a cascade:AuditEntry ; cascade:timestamp "2026-02-15T14:31:05.789Z"^^xsd:dateTime ; cascade:operation "write" ; cascade:dataTypes ("medications") ; cascade:agentId "claude-desktop-v1" ; cascade:recordsAccessed 1 . Reviewing Audit Logs Audit logs are standard Turtle files and can be read with any RDF tool or text editor. # View the raw audit log cat ./my-pod/provenance/audit-log.ttl # Count total operations grep -c "a cascade:AuditEntry" ./my-pod/provenance/audit-log.ttl # Filter by agent grep -A5 "agentId" ./my-pod/provenance/audit-log.ttl Implementation detail Audit entry IDs use crypto.randomUUID(), ensuring globally unique identifiers that survive server restarts and concurrent sessions without collision. The log is append-only — entries are never modified or deleted during normal operation. AI Agent Access The Cascade Protocol exposes health data to AI agents via the Model Context Protocol (MCP). The MCP server (cascade serve --mcp) enforces several security controls. Path Containment All file operations are validated against a boundary directory to prevent path traversal attacks. The implementation resolves both the target path and the Pod boundary to absolute paths, then verifies containment: // From cli/src/lib/mcp/tools.ts function validatePathBoundary( resolvedPath: string, boundary: string ): boolean { const normalizedPath = path.resolve(resolvedPath); const normalizedBoundary = path.resolve(boundary); return ( normalizedPath.startsWith(normalizedBoundary + path.sep) || normalizedPath === normalizedBoundary ); } This check runs on every write operation. If the resolved target file path falls outside the Pod directory, the operation is rejected with an error. Automatic AIGenerated Provenance All data written through the MCP server is automatically tagged with AIGenerated provenance. This is enforced at the server level — the agent cannot override it. The provenance record includes: Provenance type: Always AIGenerated for MCP-written data Agent ID: Identifier of the agent (e.g., "claude-desktop-v1"), defaults to "unknown-agent" if not provided Timestamp: ISO 8601 datetime of the write operation Reason: Optional free-text explanation for why the record was created Confidence level: A float between 0.0 and 1.0 indicating the agent's confidence in the data Source records: URIs of existing records used to derive the new data // Provenance metadata returned for every MCP write operation { "type": "AIGenerated", "agentId": "claude-desktop-v1", "timestamp": "2026-02-15T14:31:05.789Z", "reason": "Summarized medication list for visit prep", "confidence": 0.92 } Audit Trail Every MCP tool invocation — read, write, query, validate, or convert — is logged to the Pod's audit log. See Audit Logging above for format details. Encryption Encryption responsibilities are deliberately separated across layers. The protocol itself defines data structure, not encryption — this is an intentional design decision to avoid coupling serialization with security infrastructure. Cascade handles Infrastructure handles Protocol Layer (Vocabulary & Serialization) The protocol vocabularies and Turtle serialization format do not include encryption. This is intentional. The protocol defines what data looks like, not how it is secured at rest. Application SDK Layer (cascade-sdk-swift) AES-256-GCM encryption at rest. The Swift SDK encrypts all Pod data on disk using AES-256-GCM authenticated encryption. Encryption keys are stored in the platform keychain (iOS Keychain / macOS Keychain) and are never written to disk or transmitted. Per-Pod encryption keys. Each Pod has its own encryption key, generated on first creation. Deleting a Pod's key makes its data irrecoverable. Infrastructure Layer TLS in transit. If data is transmitted (e.g., future Solid Pod sync), transport encryption is the responsibility of the infrastructure layer. Filesystem encryption. iOS and macOS provide full-disk encryption (Data Protection / FileVault). This provides an additional encryption layer beneath the SDK's application-level encryption. Access control. Operating system permissions, app sandboxing, and keychain access groups are managed by the platform, not by the protocol. Regulatory Considerations Not legal advice This section describes how the Cascade Protocol's technical architecture aligns with regulatory frameworks. It is not legal advice and does not constitute a compliance certification. Organizations building on the protocol should conduct their own regulatory assessment. HIPAA (Health Insurance Portability and Accountability Act) What Cascade helps with Data minimization. Category-based consent filtering ensures only authorized PHI categories are shared. Query operations require explicit data type selection. Audit trails. All operations are logged with timestamps, agent IDs, data types accessed, and record counts. Logs are stored in the Pod as immutable Turtle files. Access control patterns. The protocol defines consent models and provenance tracking that support access control implementation by consuming applications. Encryption at rest. AES-256-GCM encryption in the Swift SDK meets the HIPAA encryption addressable specification. What Cascade does NOT help with Business Associate Agreements (BAAs). The protocol is a technical framework, not a business entity. BAAs must be established between covered entities and their business associates independently. Breach notification. The protocol does not include breach detection or notification mechanisms. Organizations must implement their own incident response procedures. Administrative safeguards. Workforce training, security officers, risk assessments, and contingency planning are organizational responsibilities outside the protocol's scope. GDPR (General Data Protection Regulation) What Cascade helps with Data portability. Pod data is stored in open RDF/Turtle format. Users can export their entire Pod at any time — no proprietary format lock-in. Right to erasure. Deleting a Pod removes all associated data. With per-Pod encryption keys stored in the keychain, deleting the key renders data cryptographically irrecoverable. Data minimization. Category-based consent and explicit data type queries prevent over-collection. Reduced cross-border transfer risk. Local-first architecture means data stays on the user's device by default. No data crosses jurisdictional boundaries unless the user explicitly initiates a transfer. 21st Century Cures Act Patient access to data. The protocol is designed to give patients full ownership and control of their health records, directly supporting the Cures Act's information blocking provisions. Interoperability. FHIR alignment and conversion support (cascade_convert MCP tool) enable data exchange between Cascade Pods and FHIR-compliant systems. Open standards. Built on W3C RDF/OWL, PROV-O, and Solid specifications. No proprietary formats or vendor lock-in. Threat Model Threats Cascade Mitigates Data exfiltration via AI agents. The MCP server operates locally with no network access. An AI agent accessing a Pod through MCP cannot transmit data to external services because the protocol tools make no outbound connections. Unauthorized data access. Category-based consent filtering restricts which PHI categories are visible. Agents must request specific data types; there is no wildcard access. Data tampering without attribution. All writes are tagged with provenance (including agent ID, timestamp, and confidence level) and logged to the audit trail. Modifications are traceable. Path traversal attacks. The validatePathBoundary() function resolves all paths to absolute form and verifies they remain within the Pod directory before any filesystem operation. Provenance forgery for agent-written data. The MCP server automatically sets provenance type to AIGenerated for all write operations. Agents cannot override this to claim data is ClinicalGenerated. Threats Cascade Does NOT Mitigate Limitations The following threats are outside the protocol's scope. They must be addressed by the user's platform, infrastructure, or organizational security measures. Compromised local machine. If the user's device is compromised (malware, rootkit), the attacker has access to everything the user can access, including Pod data and encryption keys in the keychain. The protocol cannot protect against a compromised runtime environment. Social engineering. If a user is tricked into granting an untrusted agent access to their Pod or sharing their data, the protocol cannot prevent the resulting data exposure. Physical access to device. An attacker with physical access to an unlocked device can read Pod files directly. Device-level security (passcode, biometrics, full-disk encryption) is the user's responsibility. Vulnerabilities in the user's infrastructure. The protocol does not audit or secure the user's operating system, network configuration, or third-party software. A vulnerability in any of these could expose Pod data. Supply chain attacks on dependencies. While the protocol minimizes dependencies, the tools and SDK do rely on third-party packages. A compromised dependency could introduce vulnerabilities. See Open Source Audit for dependency review guidance. Open Source Audit All Cascade Protocol repositories are open source and available for independent audit. No proprietary code or closed-source dependencies are required for core protocol functionality. Repositories Repository Description Language cascadeprotocol.org Protocol CLI, MCP server, SHACL validator, FHIR converter, documentation TypeScript cascade-sdk-swift Swift SDK: Pod storage, encryption, HealthKit integration, RDF serialization Swift Verifying No Telemetry The absence of telemetry, analytics, or phone-home behavior can be verified by searching the codebase for network-related code. The following commands should return no results for core protocol functionality: # Search for HTTP/network calls in the CLI grep -r "fetch\|axios\|http\.\|https\.\|XMLHttpRequest\|net\.connect" \ --include="*.ts" --include="*.js" \ cli/src/ # Search for analytics or tracking grep -ri "analytics\|telemetry\|tracking\|phone.home\|beacon" \ --include="*.ts" --include="*.js" \ cli/src/ # Search for network calls in the Swift SDK grep -r "URLSession\|URLRequest\|Alamofire\|Network\.framework" \ --include="*.swift" \ Sources/ Expected results The CLI uses node:fs and node:path for local filesystem operations only. The Swift SDK uses CryptoKit for encryption and HealthKit for Apple Health data access. Neither makes outbound network requests. Dependency Audit The protocol's runtime dependencies and their purposes are documented below. All are open source. CLI (TypeScript / Node.js) Dependency Purpose @modelcontextprotocol/sdkMCP server implementation for AI agent access n3RDF/Turtle parsing and serialization rdf-validate-shaclSHACL constraint validation zodRuntime schema validation for MCP tool inputs commanderCLI argument parsing Swift SDK Dependency Purpose CryptoKit (Apple)AES-256-GCM encryption, key derivation HealthKit (Apple)Apple Health data access (on-device only) No third-party packagesThe SDK has zero external dependencies How to Audit Clone the repositories and review the source code directly. All code paths are auditable. Check dependencies with npm ls --production (CLI) or review Package.swift (SDK) for the full dependency tree. Run under network monitoring to confirm no outbound connections during normal operation. Review the audit log after any agent interaction to verify all operations were properly recorded. ← Back to Documentation © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { const links = document.querySelectorAll('.toc-list a'); const sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); # ============================================================================ # SECTION 11: PROTOCOL COMPARISON (Cascade vs FHIR, HealthKit, OpenEHR, JSON) # ============================================================================ Protocol Comparison - Cascade Protocol /* ── Comparison Page Layout ────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } /* ── Sidebar / Table of Contents ───────────────────────────────────── */ .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } /* ── Main Content Area ─────────────────────────────────────────────── */ .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { display: flex; align-items: center; gap: var(--space-sm); } .doc-content .section h3 { margin-top: var(--space-lg); } /* ── Comparison Table ──────────────────────────────────────────────── */ .comparison-table { width: 100%; border-collapse: collapse; margin: var(--space-lg) 0; font-size: 0.875rem; table-layout: fixed; } .comparison-table th, .comparison-table td { border: 1px solid var(--border); padding: 0.75rem 0.875rem; text-align: left; vertical-align: top; } .comparison-table th { background: var(--primary); color: white; font-weight: 600; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; } .comparison-table th.dim-col { background: var(--primary-dark); width: 160px; } .comparison-table th.cascade-col { background: #007a7a; } .comparison-table td.dim-label { font-weight: 600; color: var(--primary); background: var(--bg-secondary); font-size: 0.85rem; } .comparison-table tr:nth-child(even) td { background: #fafbfc; } .comparison-table tr:nth-child(even) td.dim-label { background: var(--bg-tertiary); } .comparison-table td.cascade-cell { background: #f0fafa; border-left: 2px solid #00b4d8; } .comparison-table tr:nth-child(even) td.cascade-cell { background: #e6f5f5; } /* Rating indicators */ .rating { display: inline-block; font-size: 0.75rem; font-weight: 600; padding: 0.15rem 0.5rem; border-radius: var(--radius-sm); margin-bottom: 0.35rem; } .rating-strong { background: var(--success-bg); color: var(--success-text); border: 1px solid var(--success-border); } .rating-good { background: #e8f5e9; color: #1b5e20; border: 1px solid #81c784; } .rating-moderate { background: var(--warning-bg); color: var(--warning-text); border: 1px solid var(--warning-border); } .rating-limited { background: var(--danger-bg); color: var(--danger-text); border: 1px solid var(--danger-border); } .rating-na { background: var(--bg-tertiary); color: var(--text-muted); border: 1px solid var(--border); } /* ── System Profile Cards ──────────────────────────────────────────── */ .system-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .system-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md); } .system-card h4 { margin-top: 0; margin-bottom: var(--space-xs); color: var(--primary); font-size: 0.95rem; } .system-card .system-tag { font-size: 0.75rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: var(--space-sm); } .system-card p { font-size: 0.875rem; color: var(--text-secondary); margin-bottom: 0; } /* ── Code Tabs ─────────────────────────────────────────────────────── */ .code-example { margin: var(--space-lg) 0; } .code-label { display: inline-block; background: var(--primary); color: white; font-family: var(--font-mono); font-size: 0.75rem; font-weight: 600; padding: 0.25rem 0.75rem; border-radius: var(--radius-sm) var(--radius-sm) 0 0; margin-bottom: 0; } .code-label.cascade { background: #007a7a; } .code-label.fhir { background: #c0392b; } .code-label.healthkit { background: #555; } .code-label.openehr { background: #34495e; } .code-label.json { background: #8e44ad; } .code-example pre { margin-top: 0; border-radius: 0 var(--radius) var(--radius) var(--radius); } /* ── Responsive ────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { position: static; max-height: none; border-bottom: 1px solid var(--border); padding-bottom: var(--space-lg); margin-bottom: var(--space-lg); } .comparison-table { font-size: 0.8rem; table-layout: auto; } .comparison-table th, .comparison-table td { padding: 0.5rem 0.6rem; } } @media (max-width: 700px) { .comparison-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; } } Cascade Protocol Home Documentation Contact Protocol Comparison Cascade Protocol vs FHIR R4, Apple HealthKit, OpenEHR, and Ad-hoc JSON Documentation / Comparison On this page Overview Systems Compared Comparison Table Data Model Expressiveness Provenance Support Agent Readability Local-First Capability Patient Ownership Code Examples Cascade Weaknesses When to Use Each Last updated: February 2026 — Applies to Cascade Protocol vocabularies (core v2.3, clinical v1.6, health v2.2). Comparison is based on publicly available documentation for each system. Overview This document compares Cascade Protocol against four commonly used approaches to health data representation: FHIR R4, Apple HealthKit, OpenEHR, and ad-hoc JSON. The goal is to give implementors and evaluators an honest picture of where each approach fits, where Cascade has genuine advantages, and where it falls short. This is reference material, not a sales pitch. Each system solves real problems. The right choice depends on your use case, infrastructure, and constraints. The comparison focuses on seven dimensions that matter most for modern health data applications, particularly those involving AI agents, patient ownership, and longitudinal data. Scope of this comparison This comparison addresses health data representation and storage, not clinical workflow software or EHR systems broadly. FHIR, for example, covers far more than data modeling—it includes APIs, messaging, terminology, and clinical decision support. This comparison focuses only on its role as a data model. Systems Compared HL7 Standard FHIR R4 The dominant interoperability standard for clinical health data exchange. Mandated by US federal regulation for EHR data access. Resource-based JSON/XML with REST API conventions. Maintained by HL7 International since 2011, R4 released in 2019. Platform API Apple HealthKit Apple's on-device health data store for iOS and watchOS. Provides a walled-garden API for consumer wellness and clinical records imported from participating hospitals. Access is iOS-only and mediated through Apple's permission system. Clinical Modeling OpenEHR Archetype-based clinical data modeling standard developed since 1999. Separates the information model from the knowledge model using ADL archetypes. Strong in complex clinical modeling; widely used in Europe and Australia. Common Practice Ad-hoc JSON Custom JSON schemas designed per application. Most digital health startups begin here. Flexible and fast to ship, but typically lacks provenance, standard terminology mappings, and interoperability outside the originating system. This Protocol Cascade Protocol RDF/Turtle-based health data vocabulary built on W3C Linked Data standards. Three-layer ontology (standards → domain → patient-facing), W3C PROV-O provenance, local-first encrypted storage via Solid Pods, and AI agent access via MCP. First production use: POTS Check, App Store, December 2025. Comparison Table Each dimension is rated qualitatively. Ratings reflect the system's design intent and out-of-the-box capability, not what is theoretically achievable with custom extensions. Dimension FHIR R4 Apple HealthKit OpenEHR Ad-hoc JSON Cascade Protocol Data Model Expressiveness Strong Rich resource types covering most clinical domains. Extensions for edge cases. Standardized terminology bindings. Moderate Good for consumer wellness and common vitals. Limited clinical modeling depth. Opaque type system. Strong Extremely detailed clinical modeling via archetypes. Steeper learning curve. Designed for the most complex clinical data. Variable Completely flexible. Quality depends entirely on the team's design choices and domain expertise. Good Three-layer ontology covers wellness, clinical, and patient-facing views. Newer vocabulary; covers all common EHR export types (procedures, encounters, lab panels, medications, devices, imaging studies, claims, EOBs) plus consumer wellness and patient-facing summaries. Fewer total resource types than FHIR's full specification. Extensible via domain-specific namespaces. Provenance Support Moderate Provenance resource exists but is optional and often omitted in practice. Meta.source and meta.lastUpdated are widely used but coarse. Limited Source bundle tracks originating app. No provenance model for distinguishing clinical vs device vs user-entered data at the record level. Good Audit trails and contribution metadata are part of the reference model. Detailed contributor tracking built in. Limited No standard mechanism. Teams typically add ad-hoc created_by and updated_at fields, if anything. Strong W3C PROV-O on every record. Five typed provenance classes: ClinicalGenerated, DeviceGenerated, SelfReported, AIExtracted, AIGenerated. Non-optional by design. Agent Readability Moderate JSON is processable, but lacks self-describing semantics. FHIR profiles and terminology require external schema documentation for correct agent interpretation. Limited Platform API; not directly accessible to AI agents. Requires iOS app intermediary. No standard query protocol. Moderate Rich semantic model but complex to parse. Agents need archetype definitions to interpret data correctly. Limited Fully opaque to agents without per-application schema documentation. No standard semantics. Strong RDF data is self-describing. Every predicate is a URI with a defined meaning. Agents can reason over data without external schema docs. Native MCP server integration for structured AI access. Local-First Capability Moderate FHIR is server-centric by design (REST API). Local-only FHIR is possible but requires additional infrastructure (e.g., embedded HAPI FHIR). Strong Fully on-device by design. Apple manages local storage; no server required for core functionality. Moderate Reference implementations are typically server-based. Local-only deployments are possible but non-standard. Variable Depends on implementation choices. Can be local or cloud depending on where the database is deployed. Strong Local-first by design. Turtle files on disk. No server required. AES-256-GCM encryption at rest via CryptoKit. Zero network calls in core SDK. Patient Ownership Moderate Patient access via SMART on FHIR APIs. Data still lives on provider or payer servers. Export is possible but not frictionless. Moderate Patient holds data on their device, but it is locked to the Apple ecosystem. No standard export format; data can leave via Health Records XML but loses queryability. Limited OpenEHR is primarily an institutional data store. Patient-controlled deployment is architecturally possible but not the primary use case. Limited No standard ownership or portability model. Typically controlled by whoever operates the server. Strong Patient holds the Pod on their device. Open RDF/Turtle format is readable without proprietary tools. Solid-compatible for future server sync. Designed around individual data sovereignty. Format Conversion Strong Rich converter ecosystem (HAPI FHIR, Azure API for FHIR, Google Cloud Healthcare). FHIR is the de facto exchange format for clinical data. Limited Apple Health Records XML, FHIR import via CDA documents. No standard outbound conversion path. Moderate FHIR ↔ OpenEHR converters exist. Archetype complexity makes lossless conversion difficult. Limited Custom converters required for every integration. No standard tooling. Moderate CLI cascade convert command supports FHIR output. RDF’s graph model maps naturally to FHIR resources. Tooling is early-stage and limited compared to the FHIR ecosystem. Ecosystem Maturity Dominant Mandated by US federal regulations (21st Century Cures Act). Supported by every major EHR vendor. Enormous tooling ecosystem. HL7 International backing. Large Billions of iOS devices. Every iOS app with health integration uses HealthKit. Apple's ecosystem lock-in is both a strength and a limitation. Established 25+ years of development. Significant adoption in Europe (particularly Scandinavia and the UK) and Australia. Smaller ecosystem than FHIR in the US. N/A No ecosystem by definition. Each implementation is bespoke. Early One production application (POTS Check). Open-source Swift SDK and CLI. No enterprise adoption. No third-party tooling yet. This is an honest limitation. Data Model Expressiveness "Expressiveness" here means: how completely can a system represent the structure, relationships, and semantics of health data without lossy simplification? FHIR R4 FHIR has the most comprehensive standard resource library for clinical data. Resources like MedicationRequest, Observation, Condition, AllergyIntolerance, and DiagnosticReport cover the majority of clinical documentation needs with well-defined semantics. FHIR Extensions allow customization, though they reduce interoperability if not profiled properly. OpenEHR OpenEHR is arguably the most expressive system in this comparison for complex clinical data. Its archetype model separates the generic information model from domain knowledge, allowing extremely detailed clinical modeling. The CKM (Clinical Knowledge Manager) contains thousands of community-reviewed archetypes. The tradeoff is significant implementation complexity. Apple HealthKit HealthKit is strong for consumer wellness data: heart rate, step count, sleep analysis, blood oxygen, and similar metrics. It imports clinical records (via CDA/FHIR) from participating institutions. Its type system is opaque and not designed for extensibility or complex clinical relationships between records. Ad-hoc JSON Completely flexible but offers nothing by default. The expressiveness of any given ad-hoc JSON API depends entirely on the design team's domain knowledge. Without standard terminology bindings, relationships between records often cannot be resolved without custom integration code. Cascade Protocol Cascade's three-layer ontology provides good coverage for its target domains: consumer wellness data (health:), clinical records imported from EHR systems (clinical:), patient-facing summaries (checkup:), and specialized screening protocols (pots:). The protocol maps all domain terms to SNOMED CT and LOINC codes where they exist, maintaining FHIR alignment. Cascade currently covers fewer resource types than FHIR R4. It is well-suited for individual patient data management and AI agent access; it is not designed as an enterprise clinical data exchange format. FHIR interoperability: Cascade imports and exports FHIR R4 bundles directly. Internally it uses its own Layer 2 vocabulary (clinical:, health:, coverage:) for storage — flat, queryable properties designed for patient-owned data rather than clinical system exchange. FHIR's official RDF serialization (FHIR RDF) is not used as storage because it requires verbose blank-node nesting and preserves clinical-system metadata irrelevant to patients. Instead, every Cascade Layer 2 class declares rdfs:subClassOf alignment to the corresponding FHIR type, maintaining semantic traceability without inheriting FHIR RDF's ergonomic costs. Provenance Support Provenance answers: Where did this data come from? Who created it? When? Through what process? This matters for clinical decision-making (a self-reported symptom score has different weight than a lab result from a certified instrument) and for AI agent interactions (an AI-generated summary should be distinguishable from a clinician-authored note). Cascade's provenance model is a first-class design concern rather than an optional extension. Every record carries a typed provenance class from the core vocabulary: cascade:ClinicalGenerated — originated from an EHR or clinical system cascade:DeviceGenerated — measured by a wearable or medical device cascade:SelfReported — entered directly by the patient cascade:AIExtracted — structured data parsed from an existing clinical document by AI cascade:AIGenerated — created or synthesized by an AI agent These types are enforced at the serialization layer, not optional metadata. The MCP server automatically applies AIGenerated provenance to any record written through agent access, and this cannot be overridden by the agent. FHIR's Provenance resource provides similar capabilities, but in practice it is rarely populated in EHR exports. HL7's own implementation guides acknowledge that provenance data completeness varies widely across FHIR implementations. OpenEHR has strong audit trail support as part of its reference model, making it a genuine peer to Cascade in this dimension. Agent Readability Agent readability means: can an AI agent correctly interpret health data without requiring hand-crafted system prompts, per-schema documentation, or custom parsing code? This is where RDF-based data formats have a structural advantage over JSON or binary formats. In RDF, every predicate is a globally unique URI that resolves to a defined meaning. A triple like: <urn:uuid:abc123> health:heartRate "72"^^xsd:integer ; health:heartRateSnomedCode "364075005" . …is self-describing. An agent encountering health:heartRate can resolve the URI https://ns.cascadeprotocol.org/health/v1#heartRate to retrieve a definition. The SNOMED code 364075005 maps to the universal clinical concept for heart rate. No system prompt required to tell the agent what this field means. By contrast, a FHIR JSON payload like: { "resourceType": "Observation", "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4" }] }, "valueQuantity": { "value": 72, "unit": "bpm" } } …is machine-readable, but the agent needs to know the FHIR Observation resource structure to correctly extract the heart rate value, and needs LOINC code 8867-4 to be in its training data to understand the concept. FHIR is well-covered by LLM training data, so this is less of a practical concern for established resource types, but the requirement for external schema knowledge remains. The Cascade Protocol also provides a native MCP server (cascade serve --mcp) that exposes structured query and write tools directly to AI agents, eliminating the need to parse raw Turtle files. Agents interact with typed tools that return provenance-annotated records. Local-First Capability A local-first system stores data on the user's device and operates without a network connection. This has significant implications for privacy, reliability, and patient control. FHIR was designed for server-based data exchange. Running FHIR locally requires deploying a FHIR server (HAPI FHIR, IBM FHIR Server, etc.) on the local machine, which is viable for development but adds operational overhead for consumer applications. Apple HealthKit is natively local-first. Data lives on the device. The limitation is ecosystem lock-in: the data is only accessible through Apple's APIs, on Apple hardware. Cascade Protocol's local-first design is intentional and complete. The CascadeSDK (Swift) stores Turtle files directly on the local filesystem, encrypted with AES-256-GCM using keys stored in the platform keychain. There are no server dependencies. The CLI and MCP server run as local processes. This is verifiable by auditing the source code for network calls—there are none in the core SDK. Patient Ownership Patient ownership means the patient holds, controls, and can portably export their health data without depending on a provider, payer, or platform. FHIR improves patient access significantly compared to pre-2016 EHR data silos. SMART on FHIR enables patient-mediated access. However, the data still lives on the provider's or payer's FHIR server. Patients can read their data; they do not hold it. Apple HealthKit gives patients strong local control, but the data is encoded in Apple's proprietary format and accessible only through Apple's APIs. Exporting HealthKit data in a portable, queryable format requires significant custom development. Cascade Protocol stores data as plain Turtle files that any RDF tool can read. The open .ttl format is readable in a text editor. The Pod structure is compatible with the Solid specification, enabling future server sync while maintaining the patient as the primary data holder. The protocol does not require any Cascade-specific server or tooling to access stored data. Code Examples The following examples show how each system represents the same data: a heart rate measurement of 72 bpm, taken by a wearable device on a specific patient at a specific time. This illustrates the structural and semantic differences between formats. FHIR R4 (JSON) FHIR R4 JSON { "resourceType": "Observation", "id": "heart-rate-example-001", "status": "final", "category": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs", "display": "Vital Signs" }] }], "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" }] }, "subject": { "reference": "Patient/patient-123" }, "effectiveDateTime": "2026-01-15T10:30:00Z", "valueQuantity": { "value": 72, "unit": "beats/minute", "system": "http://unitsofmeasure.org", "code": "/min" }, "device": { "display": "Apple Watch Series 9" } } FHIR is explicit and well-specified. The LOINC code 8867-4 is the universal identifier for heart rate. The resource is self-contained and parseable without external documentation if you know the FHIR Observation structure. The verbosity is a deliberate tradeoff for unambiguous interoperability. Apple HealthKit (Swift API) HealthKit Swift API // Writing a heart rate sample let heartRateType = HKQuantityType(.heartRate) let heartRateQuantity = HKQuantity( unit: HKUnit(from: "count/min"), doubleValue: 72.0 ) let sample = HKQuantitySample( type: heartRateType, quantity: heartRateQuantity, start: Date(), end: Date(), metadata: [HKMetadataKeyDeviceName: "Apple Watch Series 9"] ) healthStore.save(sample) { success, error in // handle result } // Reading is via HKSampleQuery -- data is opaque until queried // No direct file access; no standard export format HealthKit is an API, not a file format. Data is stored in Apple's on-device database, accessible only through the HealthKit API. There is no way to inspect or export raw data without going through Apple's query interfaces. The metadata dictionary provides some flexibility, but it is not semantically typed. OpenEHR (ADL Archetype snippet) OpenEHR ADL2 Archetype -- Excerpt from openEHR-EHR-OBSERVATION.pulse.v2 archetype -- Full archetype is ~300 lines; this shows the heart rate data point OBSERVATION[at0000] matches { -- Pulse/Heart beat data matches { HISTORY[at0002] matches { events cardinality matches {1..*; unordered} matches { EVENT[at0003] occurrences matches {0..*} matches { -- Any event data matches { ITEM_TREE[at0001] matches { items cardinality matches {0..*; unordered} matches { ELEMENT[at0004] occurrences matches {0..1} matches { -- Rate value matches { C_DV_QUANTITY < list = < ["1"] = < units = <"1/min"> magnitude = <|>=0.0|> > > > } } } } } } } } } } OpenEHR archetypes are thorough and clinically precise. The pulse archetype covers far more than the rate: rhythm, character, clinical interpretation, and many other attributes. This completeness is valuable for complex clinical scenarios. For a simple wearable heart rate sample, it is substantially more structure than required. Ad-hoc JSON Ad-hoc JSON (typical startup API) { "user_id": "patient-123", "metric": "heart_rate", "value": 72, "unit": "bpm", "timestamp": "2026-01-15T10:30:00Z", "source": "apple_watch", "created_by": "device_sync" } Fast to implement, but the semantics are entirely private to this application. The string "heart_rate" has no universal meaning outside this system. There is no standard code that a clinical system could map to. The "created_by": "device_sync" field is a provenance attempt, but it is neither typed nor standardized. Any integration with a third system requires custom mapping code. Cascade Protocol (Turtle RDF) Cascade Protocol Turtle @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix health: <https://ns.cascadeprotocol.org/health/v1#> . @prefix prov: <http://www.w3.org/ns/prov#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <urn:uuid:f7a3c21b-8e4d-4f09-9a2c-3b4e5f6a7b8c> a health:HeartRateRecord ; # Semantic value with standard code mapping health:heartRate "72"^^xsd:integer ; health:heartRateUnit "beats/min" ; health:heartRateSnomedCode "364075005" ; # SNOMED: Heart rate (observable entity) health:heartRateLoincCode "8867-4" ; # LOINC: Heart rate # Timestamp health:recordedAt "2026-01-15T10:30:00Z"^^xsd:dateTime ; # Provenance (non-optional, typed) cascade:dataProvenance cascade:DeviceGenerated ; cascade:schemaVersion "2.2" ; # PROV-O linkage prov:wasAttributedTo <https://id.cascadeprotocol.org/users/patient-123> ; prov:generatedAtTime "2026-01-15T10:30:00Z"^^xsd:dateTime ; # Device context health:sourceDevice "Apple Watch Series 9" . The Cascade Turtle representation is more verbose than ad-hoc JSON, but each predicate is a globally dereferenceable URI. The SNOMED and LOINC codes provide standard clinical mappings. Provenance is typed (DeviceGenerated) rather than a free-text string. Any RDF-capable agent or tool can process this without Cascade-specific knowledge. Cascade Weaknesses Honest assessment The following are genuine limitations of the Cascade Protocol as of February 2026. They are not engineering oversights—they reflect the protocol's current stage of development and its deliberately narrow initial focus on patient-controlled data and AI agent access. Small ecosystem There is one production application (POTS Check), one organization building with the protocol, and no third-party tooling. Compare this to FHIR, which has thousands of implementations and is mandated by US law for EHR access. If your project requires enterprise EHR integration, broad vendor support, or regulatory credibility, FHIR is the appropriate choice. No enterprise EHR integration Cascade does not have certified integrations with Epic, Cerner, or other major EHR vendors. There is no Cascade FHIR Server that hospitals can connect to. Data can be imported from FHIR sources (via the Swift SDK's ClinicalRecordImporter) and exported to FHIR format, but the protocol has no native position in the institutional health IT stack. Newer and less proven OpenEHR has been in development since 1999. FHIR has been in production since 2011. The Cascade Protocol published its first stable vocabulary in 2025. The provenance model and three-layer architecture are well-reasoned, but they have not been subjected to the scale, adversarial use, and edge-case pressure that decades of production systems accumulate. RDF tooling is less approachable Turtle and RDF are familiar to semantic web practitioners but represent a learning curve for most web and mobile developers. JSON is universally understood; SPARQL and Turtle are not. The protocol mitigates this with high-level SDK abstractions, but implementors who need to work directly with the Turtle layer will encounter a steeper onramp than FHIR JSON. Limited vocabulary coverage Cascade vocabularies cover the data types used by the protocol's existing applications. There are currently no Cascade vocabulary definitions for radiology, genomics, surgical procedures, or many other clinical domains. FHIR covers these with standardized resources; Cascade does not. When to Use Each Use Case Recommended Approach Rationale Enterprise EHR integration FHIR R4 Mandated by regulation, supported by all major EHR vendors, vast tooling ecosystem iOS wellness or consumer health app HealthKit or Cascade HealthKit for Apple-ecosystem-only apps; Cascade if you need provenance, AI agent access, or portability outside Apple Complex clinical data modeling OpenEHR Most expressive for nuanced clinical models; strong institutional adoption in Europe Individual patient data + AI agent access Cascade Protocol Self-describing RDF, typed provenance, native MCP server, local-first, open format Patient-controlled longitudinal records Cascade Protocol Pod-based storage on user device, open format readable without proprietary tools, Solid-compatible for sync Quick prototype or single-app data store Ad-hoc JSON Lowest friction. Acceptable if interoperability and provenance are not requirements Specialized screening protocol (e.g., POTS, diabetes) Cascade Protocol Domain-specific vocabulary layers (pots:, diabetes:) provide specialized modeling while retaining standard code mappings These are not mutually exclusive Many production systems use multiple approaches. A hospital EHR exchanges data via FHIR; the patient imports that data into HealthKit; a Cascade Protocol application imports it from HealthKit via the Swift SDK and stores it in a local Pod for AI agent access. The protocol is designed to be a downstream consumer of FHIR data, not a replacement for it. ← Back to Documentation   POTS Check Case Study → © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { var links = document.querySelectorAll('.toc-list a'); var sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); # ============================================================================ # SECTION 12: TECHNICAL BLOG POSTS # ============================================================================ ## Blog: introducing-cascade-bridge Introducing Cascade Bridge: Patient-Owned Health Data, Connected and AI-Ready - Cascade Protocol .post-header { margin-bottom: var(--space-2xl); } .post-meta { display: flex; align-items: center; gap: var(--space-md); font-size: 0.875rem; color: var(--text-muted); font-family: var(--font-mono); margin-bottom: var(--space-md); flex-wrap: wrap; } .post-title { font-size: 2.25rem; font-weight: 700; color: var(--primary); line-height: 1.2; margin-bottom: var(--space-md); } .post-lead { font-size: 1.125rem; color: var(--text-secondary); line-height: 1.7; border-left: 4px solid var(--accent); padding-left: var(--space-lg); margin-bottom: var(--space-xl); } .post-tags { display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-top: var(--space-md); } .tag { display: inline-block; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); font-size: 0.75rem; font-weight: 500; padding: 0.2rem 0.6rem; border-radius: var(--radius-full); } .post-body h2 { clear: both; font-size: 1.5rem; color: var(--primary); border-bottom: 2px solid var(--border); padding-bottom: var(--space-sm); margin: var(--space-2xl) 0 var(--space-lg); } .post-body p { line-height: 1.8; color: var(--text); margin-bottom: var(--space-lg); } .post-body figure { margin: var(--space-xl) 0; } .post-body figure img { width: 100%; border-radius: var(--radius-lg); border: 1px solid var(--border); box-shadow: var(--shadow-md); } .post-body figcaption { font-size: 0.9rem; color: var(--text-muted); margin-top: var(--space-sm); text-align: center; } .post-body .figure-float { float: right; width: 320px; max-width: 46%; margin: 0.25rem 0 var(--space-md) var(--space-xl); } .post-body .figure-float figcaption { text-align: left; } @media (max-width: 600px) { .post-body .figure-float { float: none; width: 100%; max-width: 100%; margin: var(--space-xl) 0; } } .three-c-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); margin: var(--space-xl) 0; } .three-c-card { background: var(--bg-secondary); border: 1px solid var(--border); border-top: 4px solid var(--accent); border-radius: var(--radius-lg); padding: var(--space-lg); } .three-c-card .c-label { font-size: 1.75rem; font-weight: 700; color: var(--accent); font-family: var(--font-mono); } .three-c-card h3 { font-size: 1.05rem; color: var(--primary); margin: var(--space-xs) 0; } .three-c-card p { font-size: 0.9rem; color: var(--text-secondary); margin: 0; line-height: 1.55; } .cta-block { background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg) 100%); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-2xl) 0; } .cta-block h3 { color: var(--primary); margin: 0 0 var(--space-md); } .cta-actions { display: flex; gap: var(--space-md); flex-wrap: wrap; margin-top: var(--space-lg); } @media (max-width: 768px) { .three-c-grid { grid-template-columns: 1fr; } .post-title { font-size: 1.75rem; } } Cascade Protocol Home Documentation Security Blog Contact Blog / Introducing Cascade Bridge June 15, 2026 · ~1,400 words · Product & vision Introducing Cascade Bridge: Patient-Owned Health Data, Connected and AI-Ready Cascade Bridge is a reference application for the Cascade Protocol. It collects health data from any source, connects it into a single deduplicated knowledge graph with full provenance, and gives patients genuine control: explore, augment, query with on-device AI, and share selectively. We submitted it to the ONC EHIgnite Challenge, and the full narrative is now public. Cascade Bridge EHIgnite FHIR Knowledge Graph Patient-Owned Data AI Agents The 21st Century Cures Act and ONC's December 2023 EHI export mandate gave every patient a legal right to their complete health record. Over 45,000 certified providers now offer single-patient EHI exports, and TEFCA facilitated the exchange of more than 500 million records by early 2026. The data transport problem is largely solved, and FHIR is its centerpiece. But access is not the same as usability. What arrives at the patient is a raw FHIR bundle, a sprawling C-CDA XML archive, or an unstructured PDF. These formats were engineered for institution-to-institution exchange, not for a patient managing a chronic condition across five providers. FHIR is an exceptional shipping container, but it does not deliver usable, readable, or actionable information to patients. Cascade Bridge is the application that does. Collect. Connect. Control. Patients face three distinct barriers, and Cascade Bridge is organized around closing each one. 1 Collect EHI exports from any FHIR or C-CDA system, PDFs and scans, wearable streams, and self-reported data flow into one encrypted, patient-owned pod. No format is rejected. 2 Connect Every record becomes RDF anchored to standard codes. The reconcile engine deduplicates, fuses observations, and preserves conflicts with provenance and trust ranking. 3 Control Explore your record by meaning, correct it without erasing the source, ask on-device AI with citations, and share selectively through a health wallet. How data moves through the three C's: collected into a zero-access pod, connected via an RDF knowledge graph with strict provenance, and controlled by the patient through a secure SDK. An earlier framing of these three C's used "Communicate" as the third pillar. Working through the EHIgnite submission sharpened our thinking: communication is just one thing a patient does with a unified record. The deeper promise is control: correcting records while preserving source authority, reasoning over them with AI, responding to living updates like a genomic reclassification, and sharing selectively on the patient's own terms. Control is the superset, and it is the right third pillar. Why we built it as an EHIgnite submission The interactive 3D health graph in Cascade Bridge. Color encodes provenance; shape encodes type. The ONC EHIgnite Challenge asks a pointed question: now that single-patient EHI export is a legal right, how do we turn those exports into something usable, readable, and actionable? That is exactly the gap the Cascade Protocol was designed to close, so Cascade Bridge became our answer. The submission forced us to be concrete about the whole journey, from a universal-receiver import pipeline, through an RDF reconciliation engine that resolves real-world conflicts without destroying authority, to patient-facing experiences that are impossible with static files: AI Q&A with a live citation for every claim, a BRCA2 variant that reclassifies itself when a consortium publishes an advisory patch, and a wallet-mediated SMART Health Check-in that kills the paper clipboard. The thread that ties it together is the same one that runs through everything we build: provenance as a non-negotiable primitive, validated at write-time, so that an AI agent never has to guess whether a heart-rate reading came from a Fitbit or an ICU monitor. Running today, open by design Cascade Bridge is not a theoretical architecture. The core pipeline already runs in production. POTS Check is live on the App Store, built entirely on the Cascade Protocol Swift SDK. The cascade CLI and agent perform lossless FHIR and C-CDA conversion with multi-source reconciliation and on-device AI, available now on npm. The TypeScript, Python, and Swift SDKs and the full specification are published and documented on this site. The integrated Bridge app and the production SMART Health Check-in wallet are what we are building next. Because the protocol underneath is open, every new application inherits the full Collect-Connect-Control stack and only authors the Layer 3 vocabulary it needs. That is how a focused, single-condition app like POTS Check and a whole-record app like Cascade Bridge live on the same foundation. Read the full story The complete Cascade Bridge narrative, including the patient walkthrough, the standards-alignment detail, the cross-domain reasoning example, and the wireframes, is on the Cascade Bridge page. Our full ONC EHIgnite Phase 1 submission is available as a PDF. Explore Cascade Bridge Read the EHIgnite narrative (PDF) Developer docs Further reading Collect. Connect. Control. Taking Back Ownership of Your Health Records — the CLI walkthrough that started this thread Why RDF/OWL is the Right Foundation for Health AI Agents Provenance as a Compliance Primitive POTS Check case study — the protocol in production ← Back to Blog The Cascade Protocol is developed by Cascade Agentic Labs, a product incubation studio building foundational frameworks for agentic AI in specialized domains. Protocol Cascade Bridge Documentation Core Vocabulary Security & Compliance Developer Getting Started CLI Reference TypeScript SDK Versioning Policy Company Cascade Agentic Labs Contact Privacy Policy Terms of Service © 2026 Cascade Agentic Labs LLC. All rights reserved. ## Blog: local-first-health-ai Local-First is the Only Responsible Architecture for AI + Health Data - Cascade Protocol /* ── Blog Post Layout ───────────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { font-size: 1.5rem; color: var(--primary); border-bottom: 2px solid var(--border); padding-bottom: var(--space-sm); margin-bottom: var(--space-lg); } .doc-content .section h3 { margin-top: var(--space-lg); color: var(--text); } /* ── Post Header ────────────────────────────────────────────────────── */ .post-header { margin-bottom: var(--space-2xl); } .post-meta { display: flex; align-items: center; gap: var(--space-md); font-size: 0.875rem; color: var(--text-muted); font-family: var(--font-mono); margin-bottom: var(--space-md); } .post-tags { display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-top: var(--space-md); } .tag { display: inline-block; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); font-size: 0.75rem; font-weight: 500; padding: 0.2rem 0.6rem; border-radius: var(--radius-full); } .post-title { font-size: 2.25rem; font-weight: 700; color: var(--primary); line-height: 1.2; margin-bottom: var(--space-md); } .post-lead { font-size: 1.125rem; color: var(--text-secondary); line-height: 1.7; border-left: 4px solid var(--accent); padding-left: var(--space-lg); margin-bottom: var(--space-xl); } /* ── Architecture Diagram ───────────────────────────────────────────── */ .arch-diagram { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-xl); text-align: center; margin: var(--space-lg) 0; font-family: var(--font-mono); font-size: 0.875rem; line-height: 2.2; overflow-x: auto; } .arch-local { color: var(--primary); font-weight: 600; } .arch-arrow { color: var(--text-muted); } .arch-no { color: var(--danger-border); font-weight: 600; } .arch-boundary { border: 2px dashed var(--success-border); border-radius: var(--radius); padding: var(--space-md) var(--space-lg); display: inline-block; margin: var(--space-sm) 0; } .arch-boundary-label { font-size: 0.75rem; color: var(--success-border); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; margin-bottom: var(--space-xs); } /* ── Risk List ──────────────────────────────────────────────────────── */ .risk-item { display: flex; align-items: flex-start; gap: var(--space-md); margin-bottom: var(--space-md); padding: var(--space-lg); background: var(--danger-bg); border-left: 4px solid var(--danger-border); border-radius: 0 var(--radius) var(--radius) 0; } .risk-icon { font-size: 1.25rem; flex-shrink: 0; margin-top: 0.1em; } .risk-content h4 { margin-top: 0; margin-bottom: var(--space-xs); font-size: 0.95rem; color: var(--danger-text); } .risk-content p { margin-bottom: 0; font-size: 0.9rem; color: var(--danger-text); } /* ── Benefit List ───────────────────────────────────────────────────── */ .benefit-item { display: flex; align-items: flex-start; gap: var(--space-md); margin-bottom: var(--space-md); padding: var(--space-lg); background: var(--success-bg); border-left: 4px solid var(--success-border); border-radius: 0 var(--radius) var(--radius) 0; } .benefit-icon { font-size: 1.25rem; flex-shrink: 0; margin-top: 0.1em; } .benefit-content h4 { margin-top: 0; margin-bottom: var(--space-xs); font-size: 0.95rem; color: var(--success-text); } .benefit-content p { margin-bottom: 0; font-size: 0.9rem; color: var(--success-text); } /* ── Flow Box ───────────────────────────────────────────────────────── */ .flow-diagram { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-xl); text-align: center; margin: var(--space-lg) 0; font-family: var(--font-mono); font-size: 0.875rem; line-height: 2; overflow-x: auto; } .flow-main { color: var(--primary); font-weight: 600; } .flow-no { color: var(--danger-border); font-weight: 600; } .flow-arrow { color: var(--text-muted); } /* ── Responsive ─────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { position: static; max-height: none; border-bottom: 1px solid var(--border); padding-bottom: var(--space-lg); margin-bottom: var(--space-lg); } .post-title { font-size: 1.75rem; } } Cascade Protocol Home Documentation Security Blog Contact Blog / Local-First AI On this page The Common Pattern Risks of Cloud AI + Health Data The Local-First Alternative How It Works Technically The Compliance Argument Claude Desktop MCP Config The Offline Assurance What Local-First Cannot Do Conclusion February 22, 2026 · ~2,000 words · For health app developers and architects Local-First is the Only Responsible Architecture for AI + Health Data The common pattern: you build a health app, you add AI features, you send user health data to an AI API. You have just created a massive HIPAA compliance risk. This post makes the case that local-first architecture — AI agents that run on-device with zero bytes of health data leaving the machine — is the only genuinely responsible approach. Local-First Privacy HIPAA MCP Architecture The Common Pattern Here is how most health apps add AI features in 2026: User opens the app. App pulls health data from local storage or an EHR API. App assembles a prompt with the health data and sends it to an AI API (OpenAI, Anthropic, Google). AI API returns a response. App displays the AI-generated summary or recommendation to the user. This pattern is fast, easy to implement, and produces impressive demos. It is also, in most configurations, a significant compliance problem that is easy to underestimate until something goes wrong. The question is not whether this pattern produces a bad user experience — it often produces a great one. The question is whether you have thought through the full compliance, liability, and user trust implications of transmitting health data to a third-party AI service. Most teams have not, because the default developer experience of AI APIs makes it trivially easy to skip that thinking. Before you continue building If you are sending health data to any third-party AI API today, ask yourself: Does your company have a signed Business Associate Agreement (BAA) with that AI provider? Do you know where the AI provider stores inference data? Do you know whether the AI provider uses API inputs for model training? If you cannot answer all three with certainty, you have compliance exposure. The Risks of Cloud AI + Health Data ⚠ No BAA with most LLM providers HIPAA requires a signed Business Associate Agreement with any vendor that handles PHI on your behalf. Most major AI providers (OpenAI, Anthropic, Google) do not offer BAAs to individual developers or small companies by default. Enterprise-tier agreements are available, but they require formal negotiation and typically prohibit consumer health data use cases. If you are sending PHI to an AI API without a BAA, you are in violation of HIPAA, regardless of how compelling the product is. ⚠ Unclear data retention policies AI providers retain inference data for varying lengths of time. Even if a provider claims it does not use API inputs for training (OpenAI's API has this policy), data is typically retained for safety monitoring and abuse detection. Your users' health data is sitting in a third-party's infrastructure, subject to that provider's retention schedule, security posture, and potential legal process. ⚠ Vendor risk: terms of service can change You build your product on an AI API's current terms of service. The provider changes those terms, or gets acquired, or changes its data handling policies. Your users' health data is now subject to a different data handling regime — one they never consented to and one you did not design for. GDPR's data minimization and purpose limitation principles are particularly relevant here. ⚠ Users have no control over their own data Once health data is transmitted to a third-party AI provider, the user has lost meaningful control over it. They cannot delete it from the provider's systems. They cannot audit what the provider did with it. They cannot revoke access. This is in direct tension with the 21st Century Cures Act's information blocking provisions and GDPR's right to erasure. ⚠ Cross-contamination risk at scale When multiple users' health data passes through the same AI inference infrastructure, there is a non-zero risk of cross-user contamination in model outputs, caching layers, or logging systems. This risk is small but non-zero for any shared infrastructure. For health data, "small but non-zero" is not an acceptable risk profile. The Local-First Alternative The Cascade Protocol is designed from the ground up around a different architecture: the AI agent runs on the user's device or local server, and health data never leaves the machine. [ Health Data Pod ]  ——>  [ Local MCP Server ]  ——>  [ AI Agent (local) ] ZERO bytes of health data transmitted to external services This is not a limitation. It is the correct design for health AI. The AI agent needs access to the user's health data to do useful work. The health data does not need to leave the device for the agent to access it. The model context protocol (MCP) provides a standardized interface that runs entirely over local socket connections. The key insight is that there are two separable concerns: The AI model: The language model that does the reasoning. This can be a locally-running model (Ollama, LM Studio, etc.) or a cloud model accessed via a network connection. When using a cloud model, you are sending the model's context to the cloud, which includes whatever data you put in the prompt. The health data: The structured health records in the user's Pod. These should never go in the prompt. Instead, the agent uses MCP tools to retrieve specific, relevant data in structured form, do its reasoning, and write results back — all locally. Wait — if the agent uses a cloud model and the MCP tools return health data, doesn't that data still end up in the model's context? Yes. This is the honest complexity of local-first health AI. We address it in the limits section below. How It Works Technically The Cascade local-first architecture has four components: 1. The Local Pod Health data is stored in a local Pod — a directory of RDF/Turtle files on the user's device. When using the CascadeSDK (Swift), this data is encrypted at rest with AES-256-GCM, with encryption keys stored in the device keychain. The Pod directory contains all of the user's health records, organized by data type. 2. The Local MCP Server Running cascade serve --mcp starts a local Model Context Protocol server that listens on a stdio or socket interface. This server exposes a set of tools that allow an AI agent to read from and write to the Pod in a structured, consent-gated way. All file operations are validated against the Pod boundary to prevent path traversal. Critically, the MCP server runs entirely locally. It makes no outbound network connections. Its only communication is with the AI agent framework on the same machine via the MCP protocol. 3. The AI Agent Framework Claude Desktop, and increasingly other AI clients, support MCP natively. You configure the Cascade MCP server in Claude Desktop's MCP configuration file, and Claude can then use the Cascade tools to read and analyze the user's health data in a conversational interface. When Claude uses an MCP tool to read health data, that data flows: Pod files → MCP server (local) → Claude Desktop client (local) → Claude API (cloud, for the model inference). The model receives the data in its context window and generates a response. The response flows back: Claude API → Claude Desktop client → displayed to user. 4. Audit Logging Every MCP tool invocation is logged to the Pod's audit trail at provenance/audit-log.ttl. The user can see exactly what data the agent accessed and when. Every agent write is tagged as AIGenerated provenance, clearly distinguishing AI-generated content from clinical data. Your device (nothing leaves this boundary) Health Pod → cascade serve --mcp → Claude Desktop RDF/Turtle files — AES-256-GCM — stdio/socket — local process ↑ model inference only (no raw PHI if using local model) Cloud: Claude API (for model inference, if not using a local model) The Compliance Argument ✓ No BAA required when data doesn't leave the device If health data never leaves the user's device, there is no business associate relationship to create with the AI provider. The AI provider is processing model inputs and outputs, not receiving or storing health data on behalf of your business. This is a bright-line compliance position, not a grey area. (Note: if you are using a cloud AI API and health data does appear in the model's context, you should still consult legal counsel about BAA requirements.) ✓ The audit log is local and user-controlled The Pod's audit log is stored on the user's device, not in your cloud infrastructure. Users can read it, export it, and delete it. Regulators can inspect it. There is no data silo in your infrastructure that a regulator would need subpoena power to access. Everything the AI agent did is recorded in a standard RDF/Turtle file that any person or machine can read. ✓ Data can be deleted trivially GDPR's right to erasure requires that you be able to delete a user's data on request. With a local-first architecture, deletion is straightforward: remove the Pod directory (or, with CascadeSDK, delete the keychain key, which renders the AES-256-GCM-encrypted data cryptographically irrecoverable). There is no data spread across your servers, your AI provider's servers, and your logging infrastructure that needs to be coordinated and deleted. ✓ Users see exactly what the agent accessed Because the audit log is a user-accessible file in the Pod, users can read it directly. They can see every read and write operation the agent performed, what data categories it touched, and what it wrote back. This is the 21st Century Cures Act's spirit of patient data transparency made concrete. Practical Example: Claude Desktop MCP Configuration Setting up Claude Desktop to access a Cascade Pod locally takes about two minutes. Add the following to your Claude Desktop MCP configuration file (~/.claude/claude_desktop_config.json on macOS): { "mcpServers": { "cascade-health-pod": { "command": "cascade", "args": [ "serve", "--mcp", "--pod-path", "/Users/yourname/health-pod" ] } } } With this configuration in place, Claude Desktop will start the Cascade MCP server as a subprocess when it launches. The MCP server provides Claude with the following tools: cascade_pod_read — Read structured health data from the Pod by data type cascade_pod_write — Write AI-generated observations and summaries back to the Pod with automatic AIGenerated provenance tagging cascade_pod_query — Query the Pod for specific records or data ranges cascade_validate — Validate Pod data against SHACL shapes cascade_convert — Convert Pod data to FHIR R4 JSON for clinical system interoperability Claude can now ask questions like "Review my medication list and check for any potential interactions" and access the relevant records directly through the MCP interface — without any health data being sent to the AI provider outside of the model inference context. No code required The above configuration is the entire integration. There is no application layer to build, no data pipeline to maintain, no API keys to manage for the health data access layer. The Cascade CLI handles all the structured data access, and the MCP protocol handles the agent-tool interface. The Offline Assurance There is a simple test you can perform to verify the local-first architecture is working as expected: disconnect from the internet, then use Claude Desktop to query your health Pod. If you are using a local AI model (via Ollama or LM Studio), the entire workflow continues to work offline. The model runs locally, the MCP server runs locally, and the Pod is local. Zero network dependencies. If you are using Claude Desktop with Anthropic's cloud API, the model inference will fail offline (Claude needs to reach Anthropic's servers for inference). But the MCP server and Pod access remain local — you can confirm this by watching network traffic: the Cascade MCP server makes no outbound connections whatsoever. This offline test is a useful sanity check during development. If your AI + health data workflow does not degrade gracefully offline, it means health data is flowing out of your local environment during normal operation — which may not be what you intended. For extra assurance You can verify the absence of outbound connections from the Cascade MCP server at any time using a network monitor (Little Snitch on macOS, or lsof -i / Wireshark). The server process should show zero network connections during normal operation. What Local-First Cannot Do Honesty matters here. Local-first architecture has real limitations that are worth acknowledging explicitly. Using a cloud AI model means health data enters the model's context When Claude Desktop uses a Cascade MCP tool to read health data and includes it in a message to Claude (Anthropic's cloud API), that health data is being sent to Anthropic's servers for model inference. The MCP server itself is local and makes no outbound connections, but the data Claude retrieves via MCP tools does appear in the model context that goes to the API. This is a fundamental architectural tension: you cannot use a cloud-hosted AI model and simultaneously guarantee that health data never leaves the device. The honest position is that local-first architecture with a cloud model significantly reduces the scope of data transmission (you only send what the agent explicitly requests, not a bulk dump), but does not eliminate it for the model inference step. If this is unacceptable for your use case, use a locally-running model (Ollama, LM Studio) instead of a cloud API. The Cascade MCP server works identically with either. The tradeoff is model quality: current local models are capable but generally less capable than frontier cloud models for complex clinical reasoning tasks. Local-first does not guarantee security of the device Local-first means data does not leave the device through the Cascade stack. It does not protect against a compromised operating system, malware with filesystem access, or physical access to an unlocked device. These are device-level security concerns outside the protocol's scope. Platform-level protections (iOS Secure Enclave, macOS FileVault, keychain access control) are what address these threats. Collaborative health data sharing requires a different architecture If your use case requires sharing health data between multiple parties — a patient and their cardiologist, a research participant and a study coordinator — the local-first Pod model needs to be extended with a sync and access control layer. The Cascade Protocol's Solid-compatible data format supports this via WebID-based access control, but the sync infrastructure is beyond the scope of the local CLI tools. Conclusion The default path for adding AI to a health app is the easy path: send data to an API, get an answer, display it. It is easy, fast, and creates compliance problems that are invisible until they are not. The local-first alternative requires a small amount of additional thinking: you need to understand MCP, you need to install the Cascade CLI, you need to think about what data the agent should have access to. But the result is an architecture where you can answer the compliance questions that matter: where is the data? who can access it? what did the agent do with it? can the user delete it? For most health AI use cases — medication review, symptom tracking, visit preparation, trend analysis — local-first with a Cascade Pod and Claude Desktop MCP integration is sufficient, simple to set up, and compliant by default. For use cases that genuinely require cloud data sharing, build that deliberately, with appropriate consent, BAAs, and access controls — not because an API call was convenient. Health data is among the most sensitive personal information that exists. It deserves an architecture that treats it that way. Further reading Security & Compliance Guide — full technical specification of the security architecture Agent example: Medication Review — step-by-step walkthrough of a local AI agent workflow Provenance as a Compliance Primitive — how provenance tracking makes the audit trail work Why RDF/OWL is the Right Foundation for Health AI Agents — the semantic foundation that makes structured data access possible Model Context Protocol — the open standard for agent-tool communication ← Back to Blog The Cascade Protocol is developed by Cascade Agentic Labs, a product incubation studio building foundational frameworks for agentic AI in specialized domains. Protocol Documentation Core Vocabulary Clinical Vocabulary Security & Compliance Developer Getting Started CLI Reference TypeScript SDK Versioning Policy Company Cascade Agentic Labs Contact Privacy Policy Terms of Service © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { const links = document.querySelectorAll('.toc-list a'); const sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); ## Blog: personal-health-graph Collect. Connect. Control. Taking Back Ownership of Your Health Records - Cascade Protocol /* ── Blog Post Layout ───────────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } /* ── Sidebar / Table of Contents ────────────────────────────────────── */ .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } /* ── Main Content Area ──────────────────────────────────────────────── */ .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { font-size: 1.5rem; color: var(--primary); border-bottom: 2px solid var(--border); padding-bottom: var(--space-sm); margin-bottom: var(--space-lg); } .doc-content .section h3 { margin-top: var(--space-lg); color: var(--text); } /* ── Post Header ────────────────────────────────────────────────────── */ .post-header { margin-bottom: var(--space-2xl); } .post-meta { display: flex; align-items: center; gap: var(--space-md); font-size: 0.875rem; color: var(--text-muted); font-family: var(--font-mono); margin-bottom: var(--space-md); } .post-tags { display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-top: var(--space-md); } .tag { display: inline-block; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); font-size: 0.75rem; font-weight: 500; padding: 0.2rem 0.6rem; border-radius: var(--radius-full); } .post-title { font-size: 2.25rem; font-weight: 700; color: var(--primary); line-height: 1.2; margin-bottom: var(--space-md); } .post-lead { font-size: 1.125rem; color: var(--text-secondary); line-height: 1.7; border-left: 4px solid var(--accent); padding-left: var(--space-lg); margin-bottom: var(--space-xl); } /* ── Three-C Cards ───────────────────────────────────────────────────── */ .three-c-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); margin: var(--space-xl) 0; } .three-c-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-xl); text-align: center; } .three-c-card .c-label { font-size: 2rem; font-weight: 700; color: var(--accent); font-family: var(--font-mono); line-height: 1; margin-bottom: var(--space-sm); } .three-c-card h3 { font-size: 1rem; font-weight: 600; color: var(--primary); margin: 0 0 var(--space-sm) 0; } .three-c-card p { font-size: 0.875rem; color: var(--text-secondary); margin: 0; line-height: 1.5; } /* ── Demo Step ───────────────────────────────────────────────────────── */ .demo-step { margin: var(--space-xl) 0; } .demo-step-header { display: flex; align-items: center; gap: var(--space-md); margin-bottom: var(--space-md); } .step-number { display: flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; background: var(--accent); color: white; border-radius: 50%; font-size: 0.875rem; font-weight: 700; flex-shrink: 0; } .demo-step-header h3 { margin: 0; font-size: 1.1rem; color: var(--primary); } /* ── CTA Block ───────────────────────────────────────────────────────── */ .cta-block { background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg) 100%); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-xl) 0; } .cta-block h3 { color: var(--primary); margin: 0 0 var(--space-md) 0; } .cta-actions { display: flex; gap: var(--space-md); flex-wrap: wrap; margin-top: var(--space-lg); } /* ── Responsive ─────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { display: none; } .three-c-grid { grid-template-columns: 1fr; } .post-title { font-size: 1.75rem; } } Cascade Protocol Home Documentation Security Blog Contact Blog / Personal Health Graph On this page The Problem Collect. Connect. Control. Why a CLI? Walking Through the Demo What You Actually Get Try It Yourself March 19, 2026 · ~2,200 words · For developers and patients Collect. Connect. Control.Taking Back Ownership of Your Health Records Your health records are scattered across a dozen patient portals, each speaking a slightly different dialect of FHIR, each unaware of the others. The Cascade Protocol gives you a three-step path to owning your data: collect raw FHIR exports, connect them into a unified knowledge graph, and take control of them, querying in plain English with a local AI agent and sharing on your own terms — using nothing but open-source tools and a command line. Health Records FHIR AI Agents CLI Knowledge Graph Personal Data The Problem If you see three doctors — a primary care physician, a specialist, and a hospital system for an acute event — you have three separate medical records, in three separate portals, that have never spoken to each other. You can download each one as a FHIR bundle. But a raw FHIR export is a 20MB JSON file with thousands of resources, duplicates across every provider, conflicting values where one system says a medication is active and another says it was stopped at discharge. The patient portal experience is designed for human browsing, not data ownership. There is no way to ask all three records simultaneously: What conditions am I being treated for? What is my HbA1c trend? Are there gaps between my diagnoses and my current medications? This is not a data availability problem. Thanks to the 21st Century Cures Act, covered healthcare providers must make your records available in FHIR format on request. The data exists. The problem is that it is fragmented, dirty, and locked in a format designed for data exchange between institutions — not for personal use or AI-powered reasoning. A concrete example You have Type 2 Diabetes managed by both a primary care doctor and an endocrinologist. Your primary care export has your HbA1c results. Your endocrinology export has the same results, but measured on different equipment, showing values 3% higher due to lab calibration differences. Your hospital discharge summary lists three of your medications as "stopped" — because they were held during your admission — while your other two records still show them as active. Which is right? Who reconciles this? Right now, that reconciliation happens in your head, or it does not happen at all. Collect. Connect. Control. The Cascade Protocol approaches personal health data ownership as three distinct problems that must be solved in sequence. 1 Collect Get your raw FHIR exports from every provider portal. You own this data by law. Download it. 2 Connect Convert, deduplicate, and reconcile across sources into a single linked knowledge graph — your Cascade Pod. 3 Control Take ownership: query your unified record in plain English, correct it, share structured summaries selectively, and build applications on top. Each step has a corresponding tool. Collect is a manual step you do once per provider: download your data export from the patient portal. Connect is handled by the cascade CLI: convert each FHIR bundle to RDF/Turtle, then reconcile across sources to detect and resolve duplicates and conflicts. Control is where the magic happens: a natural language agent queries your pod and synthesizes answers across your entire health history, and from there the data is yours to correct, share, and build on. The end product is what we call a Cascade Pod — a local, encrypted, portable knowledge graph of your health. It lives on your device. No data leaves your machine unless you explicitly export or share it. The AI agent that queries it runs locally too. Why a CLI? A few months ago, I wrote about how AI agents are driving a resurgence of the command line. The thesis: GUIs are designed for humans to click through; CLIs are designed for composition and automation. When you add an AI agent to the picture, the CLI becomes the shared interface between human and agent. The same cascade convert command you run in your terminal is callable by an agent with zero additional integration work. This is not an accident. The Cascade CLI was designed from the start as an agent-callable interface. Every command produces structured output. Every flag has a clear semantic. The reconciler emits a machine-readable JSON report. The pod stores data in a graph format that any agent can traverse. You can wire the entire pipeline into an agentic workflow with a few lines of shell script, and that workflow is exactly as powerful as the one you would run manually. Consider the alternative: a GUI health app that ingests your FHIR data and presents it in a dashboard. You can view it. You can scroll through it. But you cannot programmatically query across it. You cannot feed it to an AI agent. You cannot export it in a format that another tool can understand. The data is useful only within the walls of that application. We are at the beginning of an era where agents will actively manage our digital lives. Scheduling appointments, tracking treatment adherence, flagging when a new medication conflicts with a known allergy, preparing summaries for specialist visits. Every one of these tasks requires structured, queryable, portable health data — not a locked dashboard. The CLI is the interface that makes this possible today, while the broader agent ecosystem catches up. Walking Through the Demo Let's make this concrete. The following is a real walkthrough using the Cascade CLI and a synthetic patient: Rogelio, a 64-year-old with Type 2 Diabetes, Hypertension, Diabetic Neuropathy, and Chronic Kidney Disease. He has FHIR exports from three providers: his primary care doctor, his endocrinologist, and a recent hospital discharge. Each export covers his full history with substantial overlap — and realistic conflicts. 1 Start with three raw FHIR exports $ ls -lh *.json -rw-r--r-- 1 jed 36M primary-care.json -rw-r--r-- 1 jed 36M endocrinology.json -rw-r--r-- 1 jed 36M hospital-discharge.json $ python3 -c "import json; d=json.load(open('primary-care.json')); print(len(d['entry']), 'resources')" 13662 resources Three files, 36MB each, over 13,000 resources per file. They are largely identical — same patient, same history — but with important differences: the endocrinology export has HbA1c values measured on different equipment (3% variance), and the hospital discharge export correctly reflects three medications that were held during the admission. 2 Convert to Cascade Protocol format $ for f in primary-care endocrinology hospital-discharge; do cascade convert $f.json --from fhir --to cascade \ --source-system $f --passthrough minimal > $f.ttl done Converted 12901 resources (fhir -> turtle) [primary-care] Converted 12901 resources (fhir -> turtle) [endocrinology] Converted 12901 resources (fhir -> turtle) [hospital-discharge] $ wc -l *.ttl 85234 primary-care.ttl 85234 endocrinology.ttl 85234 hospital-discharge.ttl Each FHIR bundle is converted to RDF/Turtle: a semantically rich, self-describing knowledge graph. Every resource gets typed against the Cascade ontology. Every clinical concept gets a SNOMED CT or LOINC URI. Every record gets provenance metadata tracking which system it came from. The result is structured data that an AI agent can reason about without requiring any additional schema documentation. 3 Reconcile across sources $ cascade reconcile primary-care.ttl endocrinology.ttl hospital-discharge.ttl \ --output merged.ttl \ --report report.json \ --trust hospital-discharge=0.95,endocrinology=0.90,primary-care=0.85 $ jq '{input, exact_dups, near_merged, conflicts, final}' report.json { "input": 38703, "exact_dups": 25789, "near_merged": 412, "conflicts": 13, "final": 12490 } The reconciler does three things in sequence. First, it strips exact duplicates — records with identical content that appear across all three exports. That removes 25,789 redundant triples. Second, it detects near-duplicates: same entity identifier, different values. The 412 near-merged cases are mostly the lab measurement variance between primary care and endocrinology. Third, it resolves 13 genuine conflicts using the trust scores you supply — in this case, deferring to the hospital discharge record on medication status, since discharge reconciliation is more authoritative. The output is a single merged.ttl file: 12,490 de-duplicated, conflict-resolved records representing Rogelio's complete health history across all three providers. 4 Build a Cascade Pod $ cascade pod init my-health-pod Initialized Cascade Pod at ./my-health-pod $ cascade pod import my-health-pod merged.ttl Imported 12490 records into my-health-pod $ cascade pod info my-health-pod Pod: my-health-pod Records: 12,490 Data types: conditions (18), medications (24), lab-results (847), procedures (312), encounters (156), observations (9,133) Encrypted: yes (AES-256-GCM) Size: 4.2 MB The pod is a local, encrypted directory. It stores the knowledge graph in a format optimized for agent queries. Every record is indexed by type, date, and clinical code. The whole thing is 4.2MB — down from 108MB of raw FHIR JSON. It lives on your device and travels with you as a portable ZIP. 5 Ask questions in plain English $ cascade-agent --script questions.txt ▶ What conditions am I being treated for? Rogelio is being actively treated for four primary conditions: Type 2 Diabetes Mellitus (diagnosed 2009), Essential Hypertension (2011), Diabetic Peripheral Neuropathy (2016), and Stage 3 Chronic Kidney Disease (2019). All four are currently active across all three provider records. ▶ What's my most recent HbA1c result and how has it trended? Most recent HbA1c: 8.4% (endocrinology, March 2026). There is a small but consistent divergence between primary care and endocrinology measurements — approximately 3% — likely reflecting different laboratory equipment. Adjusting for this, the underlying trend shows improvement from a peak of 9.1% in mid-2024 to the current 8.1–8.4% range. ▶ Are there any gaps between my diagnoses and current medications? One potential gap: Diabetic Peripheral Neuropathy is an active diagnosis, but there is no corresponding neuropathic pain medication (gabapentin, pregabalin, or duloxetine) in the active medication list. This may be intentional — not all neuropathy cases require pharmacotherapy — but it is worth raising at the next visit. The agent has access to the full knowledge graph. It can cross-reference conditions, medications, and lab results in a single query. It understands the provenance of each record — which system it came from, how it was reconciled, whether it was AI-generated or clinically sourced. The third answer, in particular, is the kind of synthesis that is essentially impossible to generate manually from three separate portal views. What You Actually Get At the end of this workflow, you have something that did not exist before: a single, authoritative, queryable representation of your health history. Not a portal view. Not a PDF summary. A structured knowledge graph that: Preserves provenance. Every record knows where it came from. If two sources disagree, the reconciler records both values, tracks the conflict, and documents how it was resolved and why. Is portable. Export as a ZIP and take it anywhere. Import into another tool, share with a specialist, or use as the input for a specialized application. Is agent-native. The RDF graph format is self-describing. Any agent that loads your pod can reason about it without requiring a separate schema file or system prompt explaining what each field means. Stays on your device. The cascade CLI runs locally. The agent runs locally. Nothing leaves your machine unless you choose to export. There is no account to create, no cloud service to trust. This is not a demo of a future capability. The CLI and agent are available today. The SDKs for TypeScript and Swift let you build applications that read and write Cascade Pods. A patient-facing iOS app built on the Swift SDK can import the same pod you built from the command line and display it with a proper UI, share summaries via QR code to a physician's intake system, and sync with HealthKit. Try It Yourself Everything shown here is open source and available on npm. Getting started takes about five minutes if you have Node.js installed. # Install the CLI and agent npm install -g @the-cascade-protocol/cli @the-cascade-protocol/agent # Download a FHIR export from any patient portal, then: cascade convert my-records.json --from fhir --to cascade > my-records.ttl cascade pod init my-pod cascade pod import my-pod my-records.ttl cascade-agent Get started with the Cascade Protocol The CLI, agent, and SDKs are all open source. The protocol specification, vocabulary documentation, and implementation guides are available on this site. If you are building a health application and want to give users genuine ownership of their data — portable, queryable, agent-ready — the Cascade Protocol is the foundation. Read the Docs cascade CLI on npm cascade-agent on npm SDKs for application developers If you are building a production health application rather than running CLI scripts, the Cascade SDKs handle the storage, encryption, and serialization layer so you can focus on your application logic. Swift SDK — for iOS, macOS, and watchOS applications. Handles local AES-256-GCM encrypted storage, HealthKit integration, and FHIR import. Used in production by POTS Check. TypeScript SDK — for Node.js and server-side applications. Full RDF graph I/O, pod management, and the same reconciliation engine used by the CLI. MCP Server — exposes a Cascade Pod as a Model Context Protocol resource, making it directly accessible to Claude, Cursor, and any MCP-compatible agent host. Further reading Why RDF/OWL is the Right Foundation for Health AI Agents — the semantic grounding problem Provenance as a Compliance Primitive — how the W3C PROV-O model makes data lineage auditable Local-First is the Only Responsible Architecture for AI + Health Data — why zero-egress matters Cascade Protocol Documentation — vocabulary reference, getting started, and SDK guides ← Back to Blog The Cascade Protocol is developed by Cascade Agentic Labs, a product incubation studio building foundational frameworks for agentic AI in specialized domains. Protocol Documentation Core Vocabulary Clinical Vocabulary Security & Compliance Developer Getting Started CLI Reference TypeScript SDK Versioning Policy Company Cascade Agentic Labs Contact Privacy Policy Terms of Service © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { const links = document.querySelectorAll('.toc-list a'); const sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); ## Blog: provenance-compliance-primitive Provenance as a Compliance Primitive - Cascade Protocol /* ── Blog Post Layout ───────────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { font-size: 1.5rem; color: var(--primary); border-bottom: 2px solid var(--border); padding-bottom: var(--space-sm); margin-bottom: var(--space-lg); } .doc-content .section h3 { margin-top: var(--space-lg); color: var(--text); } /* ── Post Header ────────────────────────────────────────────────────── */ .post-header { margin-bottom: var(--space-2xl); } .post-meta { display: flex; align-items: center; gap: var(--space-md); font-size: 0.875rem; color: var(--text-muted); font-family: var(--font-mono); margin-bottom: var(--space-md); } .post-tags { display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-top: var(--space-md); } .tag { display: inline-block; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); font-size: 0.75rem; font-weight: 500; padding: 0.2rem 0.6rem; border-radius: var(--radius-full); } .post-title { font-size: 2.25rem; font-weight: 700; color: var(--primary); line-height: 1.2; margin-bottom: var(--space-md); } .post-lead { font-size: 1.125rem; color: var(--text-secondary); line-height: 1.7; border-left: 4px solid var(--accent); padding-left: var(--space-lg); margin-bottom: var(--space-xl); } /* ── Provenance Class Cards ──────────────────────────────────────────── */ .prov-card { background: white; border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-lg); margin-bottom: var(--space-md); border-left: 4px solid; } .prov-card--clinical { border-left-color: var(--success-border); } .prov-card--device { border-left-color: var(--info-border); } .prov-card--self { border-left-color: var(--warning-border); } .prov-card--extracted { border-left-color: #9333ea; } .prov-card--generated { border-left-color: var(--danger-border); } .prov-card h3 { font-family: var(--font-mono); font-size: 1rem; margin-bottom: var(--space-xs); margin-top: 0; } .prov-card .prov-uri { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); margin-bottom: var(--space-sm); } .trust-meter { display: flex; align-items: center; gap: var(--space-sm); font-size: 0.8rem; color: var(--text-muted); margin-top: var(--space-sm); } .trust-bar { height: 6px; border-radius: var(--radius-full); background: var(--border); flex: 1; overflow: hidden; } .trust-fill { height: 100%; border-radius: var(--radius-full); } .trust-fill--5 { width: 100%; background: var(--success-border); } .trust-fill--4 { width: 80%; background: var(--info-border); } .trust-fill--3 { width: 60%; background: var(--warning-border); } .trust-fill--2 { width: 40%; background: #9333ea; } .trust-fill--1 { width: 20%; background: var(--danger-border); } /* ── Scenario Walk-through ───────────────────────────────────────────── */ .scenario-step { display: flex; gap: var(--space-md); margin-bottom: var(--space-lg); padding: var(--space-lg); background: var(--bg-secondary); border-radius: var(--radius); border: 1px solid var(--border); } .scenario-number { width: 36px; height: 36px; background: var(--primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; flex-shrink: 0; } .scenario-content h4 { margin-top: 0; margin-bottom: var(--space-xs); font-size: 1rem; } .scenario-content p { color: var(--text-secondary); font-size: 0.95rem; margin-bottom: 0; } .prov-type { font-family: var(--font-mono); font-size: 0.85rem; } /* ── Responsive ─────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { position: static; max-height: none; border-bottom: 1px solid var(--border); padding-bottom: var(--space-lg); margin-bottom: var(--space-lg); } .post-title { font-size: 1.75rem; } } Cascade Protocol Home Documentation Security Blog Contact Blog / Provenance On this page The Trust Problem The W3C PROV-O Model Five Provenance Classes The K+ / Lisinopril Scenario The Compliance Angle The Audit Log Full TTL Example Conclusion February 22, 2026 · ~2,000 words · For compliance officers and architects Provenance as a Compliance Primitive When an AI agent writes "your blood pressure trend is improving," how do you know which readings it used? Were they from a medical-grade device or a consumer app? Were they from three days ago or three months ago? Without provenance, you cannot answer these questions. This post explains how the Cascade Protocol turns provenance into a first-class compliance primitive. Provenance HIPAA Compliance W3C PROV-O Audit Trail The Trust Problem Health data is not homogeneous. A blood pressure reading from a certified medical-grade device that has been calibrated, validated, and imported from an EHR is fundamentally different from a blood pressure reading entered manually by a patient who may have used a consumer cuff, may have misread the display, or may have estimated from memory. Yet when AI agents process health data, they routinely work with both types of data without any systematic way to distinguish them. The data arrives as fields in a JSON object or rows in a table. There is no attached quality classification, no source attribution, no chain of custody. This creates a category of problems that cannot be solved at the application layer: Clinical decision support: An agent should weight a certified lab value differently from a patient-estimated value. If the data does not carry this distinction, the agent must either treat all data equally (unsafe) or require the operator to maintain external source-quality tables (fragile and unauditable). Regulatory audit: HIPAA requires that organizations maintain audit trails documenting what data was accessed and by whom. But "what data" means nothing without knowing the provenance of that data. A regulator reviewing an AI-generated clinical summary needs to know whether the underlying values came from certified sources. Liability: If an AI-generated recommendation is acted upon clinically and the outcome is negative, the legal question is: what data did the AI use, and was that data trustworthy? Without provenance, this question is unanswerable. These are not theoretical risks. They are the baseline compliance requirements for any application that uses AI to process health data and surfaces the results to patients or clinicians. The W3C PROV-O Model The W3C PROV-O Provenance Ontology is a standard for representing and querying provenance information in RDF. It defines three core concepts: prov:Entity — A thing that exists or has existed. In health data, an entity is a specific lab result, medication record, or device reading. prov:Activity — Something that occurred. In health data, an activity might be a clinical import, a device sync, a manual data entry session, or an AI analysis run. prov:Agent — Something that bears responsibility for an activity. In health data, an agent might be a clinical system, a device manufacturer, the patient themselves, or an AI model. PROV-O connects these concepts with relations: prov:wasGeneratedBy — Links an entity to the activity that produced it prov:wasAttributedTo — Links an entity to the agent responsible for it prov:wasDerivedFrom — Links an entity to the entities it was derived from prov:used — Links an activity to the entities it consumed A simple example: a blood pressure reading (prov:Entity) was generated by a device sync activity (prov:Activity) performed by an Apple Watch (prov:Agent). Those three facts, expressed as RDF triples, give you a machine-readable chain of custody. @prefix prov: <http://www.w3.org/ns/prov#> . @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . # The blood pressure reading (entity) <urn:uuid:bp-reading-001> a prov:Entity ; prov:wasGeneratedBy <urn:uuid:device-sync-activity-001> ; prov:wasAttributedTo <urn:uuid:apple-watch-agent> ; prov:generatedAtTime "2026-02-15T09:15:00Z"^^xsd:dateTime . # The device sync activity <urn:uuid:device-sync-activity-001> a prov:Activity ; cascade:dataProvenance cascade:DeviceGenerated . # The device agent <urn:uuid:apple-watch-agent> a prov:Agent ; cascade:agentDescription "Apple Watch Series 10, FDA 510(k) cleared" . Cascade's Five Provenance Classes The Cascade Protocol defines five provenance classes in the core vocabulary, each representing a distinct source of data with different trust implications: cascade:ClinicalGenerated https://ns.cascadeprotocol.org/core/v1#ClinicalGenerated Data originating from an EHR or clinical system. Imported via Apple Health clinical records, HL7 FHIR export, or direct EHR integration. This represents the highest-trust data source for clinical decision-making: the record was created by a certified clinical system, potentially reviewed by a clinician, and follows formal data quality standards. Examples: Lab results from a hospital lab panel; medication records imported from an EHR; diagnoses from a physician's chart note. Trust Highest cascade:DeviceGenerated https://ns.cascadeprotocol.org/core/v1#DeviceGenerated Data generated by a wearable or medical device. Trust depends heavily on the device: an FDA 510(k)-cleared blood pressure monitor is considerably more trustworthy than a consumer fitness tracker. Consuming applications can attach device metadata to add specificity. Device-generated data is typically objective (not self-reported) but may have measurement error or calibration drift. Examples: Apple Watch heart rate and SpO2 readings; CGM glucose values; validated blood pressure cuff readings synced via Bluetooth. Trust High (device-dependent) cascade:SelfReported https://ns.cascadeprotocol.org/core/v1#SelfReported Data entered directly by the patient or care recipient. This data is genuinely valuable — patients often know things about their health that do not appear in clinical records, such as supplement use, symptom patterns, and lifestyle factors. But it is unverified: the patient may misremember, estimate, or enter data that differs from clinical measurements. Examples: Manually entered medication doses; symptom diary entries; self-reported supplement use; diet and exercise logs. Trust Medium (unverified) cascade:AIExtracted https://ns.cascadeprotocol.org/core/v1#AIExtracted Structured data that an AI model extracted from an existing document. For example, parsing a scanned lab report PDF into structured fields, or extracting medication names from a discharge summary. The source document may be authoritative, but the extraction process introduces AI error. Data tagged as AIExtracted should be treated as requiring human review before clinical use. Examples: Medications parsed from a scanned prescription; lab values extracted from a PDF report; diagnoses structured from a clinical note. Trust Low (needs review) cascade:AIGenerated https://ns.cascadeprotocol.org/core/v1#AIGenerated Data created entirely by AI reasoning. Observations, summaries, risk assessments, recommendations, and trend analyses written by an AI agent. This is not "extracted" from any specific source — it is the AI's synthetic output. It must always be clearly distinguished from clinical data, which is why the Cascade MCP server automatically tags all agent writes with this provenance class. Agents cannot override this. Examples: AI-written health summaries; drug interaction observations; trend analysis; visit preparation notes generated by an AI agent. Trust Lowest (AI output, verify before clinical use) The K+ / Lisinopril Scenario Let's walk through a concrete clinical scenario to see how the five provenance classes enable differential trust reasoning. A patient takes Lisinopril for hypertension and has recently started taking a potassium gluconate supplement they read about online. Their most recent lab panel shows serum potassium at 5.1 mEq/L — slightly above the reference range. 1 Serum K+ = 5.1 mEq/L — cascade:ClinicalGenerated This lab result was imported from the patient's EHR via Apple Health clinical records. It carries the LOINC code for serum potassium (loinc:2951-2) and a reference range of 3.5–5.0 mEq/L. The agent treats this as the highest-trust data point: a certified lab measurement from a clinical system. The fact that it is above the reference range is clinically significant. 2 Potassium supplement 500mg BID — cascade:SelfReported The patient entered this supplement in the app manually. The agent can see it carries cascade:SelfReported provenance. This means the supplement is clinically plausible and worth attending to — but it is unverified. The agent notes that this could explain the elevated K+ value, but flags that the supplement data should be verified with the patient before clinical action. 3 Lisinopril 10mg daily — cascade:ClinicalGenerated The Lisinopril prescription was imported from the patient's pharmacy records via the EHR. It carries a SNOMED CT code for Lisinopril (sct:372756006). This is high-trust data: an active prescription from a clinical system. The agent knows Lisinopril is an ACE inhibitor and can cause potassium retention as a side effect. 4 Interaction observation — cascade:AIGenerated The agent writes a structured observation noting the potential interaction: ACE inhibitor + potassium supplementation + elevated serum K+. The MCP server automatically tags this observation as AIGenerated. The observation explicitly links to the three source records via prov:wasDerivedFrom. Anyone reading the audit trail can see exactly what the agent used, and that the output is AI reasoning, not a clinical measurement. The provenance chain here is not just a nice-to-have. It is what makes this scenario auditable. A clinician reviewing the AI-generated observation can see that it is based on one certified lab value, one certified prescription record, and one self-reported supplement — and can calibrate their response accordingly. The Compliance Angle HIPAA Audit Trails HIPAA's Security Rule (45 CFR § 164.312(b)) requires covered entities to implement hardware, software, and procedural mechanisms that record and examine activity in information systems that contain or use electronic protected health information. This requirement is typically interpreted as a technical audit trail. Cascade's provenance model and audit log satisfy the spirit of this requirement in two ways. First, every record carries data provenance metadata: you can query the Pod for all records created by AI agents, all records derived from specific source data, or all records of a given trust level. Second, the MCP server writes a structured audit entry to provenance/audit-log.ttl for every operation, including the agent ID, timestamp, operation type, and data categories accessed. 21st Century Cures Act The 21st Century Cures Act's information blocking provisions require that health data be accessible to patients in ways they can use. Provenance is a key part of this: when a patient receives a summary of their health data, they have a right to know where that data came from and whether any part of it was AI-generated. The Cascade provenance model makes this possible at the data level. Every record is tagged. Every AI-generated observation carries a clear label and links back to its source records. Patients and their authorized representatives can inspect the Pod directly and see exactly what an AI agent read and wrote. Data Lineage for Regulatory Submissions In research and clinical trial contexts, data lineage — the complete chain from raw measurement to analyzed result — is a regulatory requirement for submissions to the FDA and other agencies. The Cascade provenance model provides this chain in machine-readable form. A SPARQL query over the Pod can reconstruct the complete derivation graph for any AI-generated output. The Audit Log Beyond the provenance metadata on individual records, the Cascade MCP server maintains an append-only audit log at provenance/audit-log.ttl within the Pod. This log records every operation performed by an AI agent through the MCP server. Each cascade:AuditEntry in the log records: Timestamp: ISO 8601 datetime of the operation Operation type: read, write, query, validate, or convert Data types accessed: An RDF list of the categories of data touched (e.g., "medications", "lab_results") Agent ID: The identifier of the AI agent or tool that performed the operation Record count: The number of records read or written Crucially, the audit log is stored in the same RDF/Turtle format as the rest of the Pod data. It is not a separate database or log file in a proprietary format. It is a first-class citizen of the data model, queryable with any RDF tool. The audit log as a compliance artifact Because the audit log is machine-readable RDF, it can be used directly in regulatory submissions, legal proceedings, and compliance audits. It does not require any translation or export step. A regulator can query the log directly using standard SPARQL tools. Full TTL Example: Provenance Chain + Audit Log Here is a complete Turtle example showing the provenance chain for the K+ scenario above, including the AI-generated observation and the corresponding audit log entries: @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix clinical: <https://ns.cascadeprotocol.org/clinical/v1#> . @prefix prov: <http://www.w3.org/ns/prov#> . @prefix loinc: <http://loinc.org/rdf#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . # ── Serum K+ Lab Result ─────────────────────────────────────────────────── <urn:uuid:lab-k-serum-001> a clinical:LabResult ; clinical:loincCode loinc:2951-2 ; clinical:labTestName "Potassium, Serum" ; clinical:value "5.1"^^xsd:decimal ; clinical:unit "mEq/L" ; clinical:referenceRangeLow "3.5"^^xsd:decimal ; clinical:referenceRangeHigh "5.0"^^xsd:decimal ; clinical:resultDate "2026-02-15"^^xsd:date ; cascade:dataProvenance cascade:ClinicalGenerated ; prov:wasGeneratedBy cascade:ClinicalGenerated ; prov:generatedAtTime "2026-02-15T14:00:00Z"^^xsd:dateTime . # ── AI-Generated Interaction Observation ───────────────────────────────── <urn:uuid:obs-k-interaction-001> a cascade:Observation ; cascade:observationText """Potential ACE inhibitor + potassium supplementation interaction. Serum K+ is 5.1 mEq/L (above reference), with concurrent Lisinopril (ClinicalGenerated) and self-reported potassium gluconate supplementation. Recommend clinical review before adjusting supplementation.""" ; cascade:dataProvenance cascade:AIGenerated ; cascade:agentId "claude-desktop-v1" ; cascade:confidence "0.85"^^xsd:decimal ; prov:wasGeneratedBy cascade:AIGenerated ; prov:wasDerivedFrom <urn:uuid:lab-k-serum-001> , <urn:uuid:med-lisinopril-001> , <urn:uuid:med-k-supplement-001> ; prov:generatedAtTime "2026-02-22T10:30:00Z"^^xsd:dateTime . # ── Audit Log Entries ───────────────────────────────────────────────────── # (stored in provenance/audit-log.ttl) <#audit-read-medications> a cascade:AuditEntry ; cascade:timestamp "2026-02-22T10:29:45.102Z"^^xsd:dateTime ; cascade:operation "read" ; cascade:dataTypes ("medications") ; cascade:agentId "claude-desktop-v1" ; cascade:recordsAccessed 3 . <#audit-read-labs> a cascade:AuditEntry ; cascade:timestamp "2026-02-22T10:29:52.781Z"^^xsd:dateTime ; cascade:operation "read" ; cascade:dataTypes ("lab_results") ; cascade:agentId "claude-desktop-v1" ; cascade:recordsAccessed 8 . <#audit-write-observation> a cascade:AuditEntry ; cascade:timestamp "2026-02-22T10:30:01.449Z"^^xsd:dateTime ; cascade:operation "write" ; cascade:dataTypes ("observations") ; cascade:agentId "claude-desktop-v1" ; cascade:recordsAccessed 1 . The complete trace: what the agent read, what it wrote, the exact time of each operation, and the full provenance chain linking the AI-generated output back to its source data. All in standard RDF/Turtle, readable by any person or machine. Conclusion Provenance is not a logging afterthought. It is a first-class semantic property of every health data record. When you build on the Cascade Protocol, every piece of data your AI agent reads carries a machine-readable classification of how trustworthy it is. Every piece of data your AI agent writes carries an explicit label identifying it as AI output. And the audit log keeps a complete record of every operation. This is what it means to treat provenance as a compliance primitive: it is not added on top of the data model, it is woven into it. The cost of getting this right is substantially lower than the cost of retrofitting provenance tracking onto a system that was not designed with it in mind. For organizations building AI-powered health applications, the Cascade Protocol's provenance model provides a structural answer to the compliance questions that regulators, legal teams, and clinical partners will inevitably ask: what data did the AI use, where did that data come from, and how do we know the AI's output is clearly labeled as such? Further reading Security & Compliance Guide — detailed provenance model and audit log specification Agent examples — working code for MCP server integration Why RDF/OWL is the Right Foundation for Health AI Agents — the semantic grounding context for this post W3C PROV-O Specification ← Back to Blog The Cascade Protocol is developed by Cascade Agentic Labs, a product incubation studio building foundational frameworks for agentic AI in specialized domains. Protocol Documentation Core Vocabulary Clinical Vocabulary Security & Compliance Developer Getting Started CLI Reference TypeScript SDK Versioning Policy Company Cascade Agentic Labs Contact Privacy Policy Terms of Service © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { const links = document.querySelectorAll('.toc-list a'); const sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); ## Blog: rdf-foundation-health-ai Why RDF/OWL is the Right Foundation for Health AI Agents - Cascade Protocol /* ── Blog Post Layout ───────────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } /* ── Sidebar / Table of Contents ────────────────────────────────────── */ .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } /* ── Main Content Area ──────────────────────────────────────────────── */ .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { font-size: 1.5rem; color: var(--primary); border-bottom: 2px solid var(--border); padding-bottom: var(--space-sm); margin-bottom: var(--space-lg); } .doc-content .section h3 { margin-top: var(--space-lg); color: var(--text); } /* ── Post Header ────────────────────────────────────────────────────── */ .post-header { margin-bottom: var(--space-2xl); } .post-meta { display: flex; align-items: center; gap: var(--space-md); font-size: 0.875rem; color: var(--text-muted); font-family: var(--font-mono); margin-bottom: var(--space-md); } .post-tags { display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-top: var(--space-md); } .tag { display: inline-block; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary); font-size: 0.75rem; font-weight: 500; padding: 0.2rem 0.6rem; border-radius: var(--radius-full); } .post-title { font-size: 2.25rem; font-weight: 700; color: var(--primary); line-height: 1.2; margin-bottom: var(--space-md); } .post-lead { font-size: 1.125rem; color: var(--text-secondary); line-height: 1.7; border-left: 4px solid var(--accent); padding-left: var(--space-lg); margin-bottom: var(--space-xl); } /* ── Comparison Block ───────────────────────────────────────────────── */ .comparison-block { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-xl); margin: var(--space-lg) 0; } .comparison-block h4 { color: var(--primary); margin-bottom: var(--space-sm); font-family: var(--font-mono); font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.04em; } .comparison-verdict { display: flex; align-items: flex-start; gap: var(--space-sm); font-size: 0.9rem; color: var(--text-secondary); margin-top: var(--space-sm); } .verdict-icon { font-size: 1rem; flex-shrink: 0; margin-top: 0.1em; } /* ── Responsive ─────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { position: static; max-height: none; border-bottom: 1px solid var(--border); padding-bottom: var(--space-lg); margin-bottom: var(--space-lg); } .post-title { font-size: 1.75rem; } } Cascade Protocol Home Documentation Security Blog Contact Blog / RDF & Health AI On this page The Problem The RDF Difference Three-Way Comparison The OWL Ontology Angle Three-Layer Architecture Agent Workflow Example Full Turtle Example Conclusion February 22, 2026 · ~2,000 words · For AI/ML developers Why RDF/OWL is the Right Foundation for Health AI Agents AI agents working with health data typically receive raw JSON with no schema context, proprietary API formats, or flat CSVs. None of these are self-describing. RDF/OWL solves the semantic grounding problem, and here is why the Cascade Protocol is built on it. RDF AI Agents Ontology Health Data OWL The Problem Imagine you are building an AI agent to help patients manage their medications. Your agent needs to understand what medications a patient takes, what the dosages are, when they started, and what clinical codes map to those medications. You have data. You need the agent to reason about it. In practice, you are probably handing that agent one of three things: Ad-hoc JSON: A bespoke schema your team invented. No external documentation. The agent has to guess what "med" means, whether "dose" is a string or a number, and what unit system is in use. Proprietary API responses: A format specific to one EHR vendor or health data platform. Semantics are locked in that vendor's documentation, which the agent cannot access at inference time. Flat CSV exports: Column headers like MedicationName, StartDate, Dose. No type information, no coding system, no provenance. Is "25" in the Dose column milligrams or micrograms? The agent cannot tell. None of these formats are self-describing. The agent must rely on external documentation, system prompts, or hand-crafted schema definitions to understand the data it is working with. This creates fragility: add a new field, rename a column, change a coding system, and the agent's understanding breaks. This is the semantic grounding problem for health AI. It is not a small inconvenience. It is the reason most health AI applications are brittle, difficult to audit, and nearly impossible to reason about for compliance purposes. Why this matters for compliance If your agent cannot reliably identify what data it is working with, you cannot build a reliable audit trail. You cannot demonstrate to a regulator that the agent used a lab value from a certified device rather than a self-reported estimate. Semantic grounding is not just a developer ergonomics problem — it is a compliance foundation. The RDF Difference RDF (Resource Description Framework) takes a fundamentally different approach to data. Instead of key-value pairs in a document, RDF models everything as a graph of triples: subject, predicate, object. Every resource is identified by a URI. Every predicate is a globally defined term with formal semantics. The graph structure enables reasoning. Consider what happens when an agent reads a Cascade Protocol Turtle file: URIs are globally identified. The subject <urn:uuid:a1b2c3d4> is a universally unique identifier. It refers to exactly one thing, anywhere in the world. Predicates are typed. health:rxNormCode is defined in the Cascade health vocabulary. Its domain and range are specified in the OWL ontology. An agent that loads the ontology knows this is an RxNorm drug code, not a free-text field. The graph structure enables traversal. An agent can follow a triple from a medication record to its provenance record, to the originating clinical system, to the patient's WebID, without any additional schema documentation. The data is self-describing. The Turtle prefixes at the top of every file declare the vocabularies in use. An agent reading the file knows exactly what ontology namespace each property comes from. No external documentation required at inference time. When an agent reads a Cascade Turtle file, it knows exactly what every triple means. Not because you told it in a system prompt. Because the data itself carries the semantic context as global URIs that resolve to formal ontology definitions. Three-Way Comparison Let's make this concrete. Here is the same medication record — Metoprolol 25mg, started January 15, 2024 — represented three different ways. Option A: Ad-hoc JSON { "med": "Metoprolol", "dose": "25mg", "start": "2024-01-15" } — No types, no coding system, no unit disambiguation, no provenance. The field name "med" is ambiguous. Is "25mg" a string or two fields? The date format is undeclared. An agent working with this data is making assumptions at every step. Option B: FHIR R4 MedicationStatement JSON { "resourceType": "MedicationStatement", "status": "active", "medicationCodeableConcept": { "coding": [{ "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "866511", "display": "Metoprolol Tartrate 25 MG Oral Tablet" }] }, "effectivePeriod": { "start": "2024-01-15" }, "dosage": [{ "doseAndRate": [{ "doseQuantity": { "value": 25, "unit": "mg", "system": "http://unitsofmeasure.org", "code": "mg" } }] }] } — Typed and coded, which is a significant improvement. The RxNorm code and UCUM unit are present. But the FHIR schema is enormous and requires external documentation to understand. There is no native provenance model — you would need to add a FHIR Provenance resource separately, with no formal link between them. And an agent receiving this JSON still needs to know the FHIR schema to parse it correctly. Option C: Cascade Turtle @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix clinical: <https://ns.cascadeprotocol.org/clinical/v1#> . @prefix health: <https://ns.cascadeprotocol.org/health/v1#> . @prefix prov: <http://www.w3.org/ns/prov#> . @prefix sct: <http://snomed.info/id/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890> a clinical:MedicationRecord ; clinical:medicationName "Metoprolol Tartrate" ; clinical:dose "25"^^xsd:decimal ; clinical:doseUnit "mg" ; clinical:rxNormCode "866511" ; clinical:snomedCode sct:372756006 ; clinical:startDate "2024-01-15"^^xsd:date ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:schemaVersion "1.6" ; prov:wasGeneratedBy cascade:ClinicalGenerated ; prov:generatedAtTime "2024-01-15T10:00:00Z"^^xsd:dateTime . — Typed, coded, provenance-attached, and self-describing. The dose is a decimal, the unit is explicit. The SNOMED CT code and RxNorm code are present as globally resolvable URIs. Provenance is attached directly to the record using W3C PROV-O. An agent reading this file can load the vocabulary ontology at https://ns.cascadeprotocol.org/clinical/v1# and know the formal semantics of every property without any additional documentation. The OWL Ontology Angle RDF gives you a graph model. OWL (Web Ontology Language) gives you formal semantics for that graph. The Cascade Protocol vocabulary files are OWL ontologies, which means they define classes, properties, domain and range constraints, and class hierarchies that an agent or reasoner can use to make inferences. For example, the clinical vocabulary defines: clinical:MedicationRecord is an OWL class. An agent that loads the ontology knows that any resource of this type represents a medication, not a vital sign or a lab result. clinical:snomedCode has a formal domain (clinical:MedicationRecord) and a range (an SNOMED CT URI). The agent knows this property is always a SNOMED CT concept URI, not a free-text description. Class hierarchies let you reason about type subsumption. A clinical:LabResult is a subclass of clinical:ClinicalRecord. A query for all clinical records will return lab results, which is what you want. This matters for AI agents because it means an agent can reason about the data without being told explicitly. If an agent knows that clinical:snomedCode produces SNOMED CT URIs, and it knows that SNOMED CT encodes drug-supplement interaction information, it can traverse those relationships to identify potential interactions — using the ontology as its reasoning backbone, not hard-coded logic in the system prompt. Formal vs. informal semantics JSON schemas can describe structure, but they cannot describe semantics. You can say that a field is a string, but you cannot say that the string is a SNOMED CT code that participates in subsumption relationships with other clinical concepts. OWL ontologies can. That gap is the entire difference between a JSON API and a knowledge graph. The Three-Layer Architecture The Cascade Protocol organizes health semantics into three layers that build on each other: Layer 1: Established Standards These are the universal languages of healthcare: SNOMED CT for clinical concepts, LOINC for lab and observation identifiers, RxNorm for medications, ICD-10 for diagnoses, UCUM for units, FHIR for resource structure. The Cascade Protocol does not replace these — it references them as external URIs in every record. A SNOMED CT code is always a resolvable URI: <http://snomed.info/id/372756006> identifies Lisinopril, globally and unambiguously. A LOINC code like http://loinc.org/rdf#2951-2 identifies serum potassium. These are not strings in a field; they are globally identifiable resources in the linked data graph. Layer 2: Cascade Domain Vocabulary The domain vocabularies — health: for device-generated wellness data, clinical: for EHR-imported records — model health data at the application level. They provide the properties that link to Layer 1 codes and add domain-specific context: provenance type, schema version, data quality annotations. This is where Cascade creates vocabulary that does not yet exist in any standard. Properties like cascade:dataProvenance link to the provenance classification system. Properties like health:rxNormCode make the RxNorm linkage explicit as a typed property, not a generic string. Layer 3: Application Vocabulary The application-specific vocabularies — pots: for POTS screening data, checkup: for patient intake summaries — model the user-facing artifacts that aggregating applications need. These are patient-friendly representations that combine data from multiple Layer 2 sources into actionable summaries. The three layers ensure that Cascade data always traces back to established standards. An AI agent can start with a patient-facing checkup:MedicationSummary, traverse to the underlying clinical:MedicationRecord records, and from there follow the SNOMED CT URIs to the global clinical ontology. Agent Workflow Example Here is a concrete scenario that shows how an AI agent uses the Cascade vocabulary to reason about a potential drug-supplement interaction — without any hard-coded logic. Scenario: A patient takes Lisinopril (prescribed, EHR-imported) and a potassium supplement (self-reported). Their latest serum potassium from a lab panel is 5.1 mEq/L, flagged slightly above the reference range. Step 1: Agent receives the Cascade Pod The agent connects to the local MCP server (cascade serve --mcp) and queries for medications and lab results. The MCP server returns Turtle data for both. The agent does not need to parse a proprietary schema — it reads the vocabulary prefixes and knows it is working with clinical: data. Step 2: Agent identifies the K+ supplement The supplement record carries clinical:snomedCode sct:88526004 (the SNOMED code for dietary potassium supplement). Because this is an RDF URI, the agent can recognize it as a potassium compound without any keyword matching. The provenance is cascade:SelfReported — the agent notes this data came from the patient, not from a prescriber. Step 3: Agent cross-references with Lisinopril The Lisinopril record carries clinical:snomedCode sct:372756006. Both SNOMED codes are in the RDF graph. The agent, using the SNOMED ontology or its training knowledge, recognizes that Lisinopril is an ACE inhibitor that can cause potassium retention — creating a known additive risk when combined with potassium supplementation. Step 4: Agent checks the lab value The lab result record carries health:loincCode "http://loinc.org/rdf#2951-2" (serum potassium) and health:value "5.1"^^xsd:decimal with health:unit "mEq/L". The provenance is cascade:ClinicalGenerated — this is a certified lab value, not a consumer device reading. Step 5: Agent reasons about trust and generates an observation Because all three data points carry explicit provenance, the agent can reason about trust differentially: the Lisinopril prescription is ClinicalGenerated (highest trust), the supplement is SelfReported (valuable but unverified), and the lab value is ClinicalGenerated (certified). The agent writes a structured observation back to the Pod with cascade:AIGenerated provenance, noting the interaction risk and the evidence chain. The MCP server automatically attaches this provenance tag — the agent cannot claim its output is clinical data. No hard-coded drug interaction logic. No keyword matching on medication names. The reasoning is grounded in globally identified clinical concepts, and the provenance chain is preserved end-to-end. Full Turtle Example: Medication Interaction Scenario Here is the complete Turtle representation of the scenario above, showing all three records and the AI-generated interaction observation: @prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> . @prefix clinical: <https://ns.cascadeprotocol.org/clinical/v1#> . @prefix health: <https://ns.cascadeprotocol.org/health/v1#> . @prefix prov: <http://www.w3.org/ns/prov#> . @prefix sct: <http://snomed.info/id/> . @prefix loinc: <http://loinc.org/rdf#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . # ── Lisinopril (EHR-imported, ClinicalGenerated) ───────────────────────── <urn:uuid:med-lisinopril-001> a clinical:MedicationRecord ; clinical:medicationName "Lisinopril" ; clinical:dose "10"^^xsd:decimal ; clinical:doseUnit "mg" ; clinical:frequency "once daily" ; clinical:snomedCode sct:372756006 ; clinical:rxNormCode "29046" ; clinical:startDate "2023-06-01"^^xsd:date ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:schemaVersion "1.6" ; prov:wasGeneratedBy cascade:ClinicalGenerated ; prov:generatedAtTime "2023-06-01T09:00:00Z"^^xsd:dateTime . # ── Potassium Supplement (patient self-reported) ────────────────────────── <urn:uuid:med-k-supplement-001> a clinical:SupplementRecord ; clinical:medicationName "Potassium Gluconate" ; clinical:dose "500"^^xsd:decimal ; clinical:doseUnit "mg" ; clinical:frequency "twice daily" ; clinical:snomedCode sct:88526004 ; cascade:dataProvenance cascade:SelfReported ; cascade:schemaVersion "1.6" ; prov:wasGeneratedBy cascade:SelfReported ; prov:generatedAtTime "2026-01-10T18:00:00Z"^^xsd:dateTime . # ── Serum Potassium Lab Result (certified lab, ClinicalGenerated) ───────── <urn:uuid:lab-k-serum-001> a clinical:LabResult ; clinical:loincCode loinc:2951-2 ; clinical:labTestName "Potassium, Serum" ; health:value "5.1"^^xsd:decimal ; health:unit "mEq/L" ; clinical:referenceRangeLow "3.5"^^xsd:decimal ; clinical:referenceRangeHigh "5.0"^^xsd:decimal ; clinical:resultDate "2026-02-15"^^xsd:date ; cascade:dataProvenance cascade:ClinicalGenerated ; cascade:schemaVersion "1.6" ; prov:wasGeneratedBy cascade:ClinicalGenerated ; prov:generatedAtTime "2026-02-15T14:00:00Z"^^xsd:dateTime . # ── AI-Generated Interaction Observation ───────────────────────────────── <urn:uuid:obs-drug-interaction-001> a cascade:Observation ; cascade:observationText """Potential drug-supplement interaction identified. Lisinopril (ACE inhibitor, SNOMED 372756006) combined with potassium gluconate supplementation (SNOMED 88526004) may cause additive hyperkalemia. Current serum K+ is 5.1 mEq/L (LOINC 2951-2), slightly above reference range. Recommend clinical review.""" ; cascade:dataProvenance cascade:AIGenerated ; cascade:agentId "claude-desktop-v1" ; cascade:confidence "0.87"^^xsd:decimal ; prov:wasGeneratedBy cascade:AIGenerated ; prov:wasDerivedFrom <urn:uuid:med-lisinopril-001> , <urn:uuid:med-k-supplement-001> , <urn:uuid:lab-k-serum-001> ; prov:generatedAtTime "2026-02-22T10:30:00Z"^^xsd:dateTime . Every element in this file is traceable. Every data point has a provenance class. Every clinical concept has a SNOMED CT or LOINC URI. Every agent-generated output is tagged as AIGenerated with a confidence score and explicit links to the source records it derived from. This is what it looks like when AI reasoning is built on a proper semantic foundation. Conclusion The semantic grounding problem in health AI is not going to be solved by better prompt engineering or larger context windows. It is a structural problem: if the data you feed an agent has no self-describing semantics, the agent will always be working from incomplete information and making assumptions that are invisible to auditors. RDF/OWL provides the foundation that health AI needs. Globally identified resources, formally typed predicates, class hierarchies that support reasoning, and native provenance attachment via W3C PROV-O. The Cascade Protocol builds this foundation into every record, at every layer, from device-generated wellness data through EHR-imported clinical records to AI-generated observations. The cost of learning Turtle syntax and the RDF data model is real. But the alternative — building health AI agents on JSON with informal schemas and hope — has a much higher cost: brittle systems, unauditable reasoning, and compliance risk that compounds with every new data source you add. Further reading Core Vocabulary documentation — the cascade: namespace Clinical Vocabulary documentation — the clinical: namespace Security & Compliance Guide — provenance model and audit logging W3C PROV-O Specification W3C OWL Web Ontology Language ← Back to Blog The Cascade Protocol is developed by Cascade Agentic Labs, a product incubation studio building foundational frameworks for agentic AI in specialized domains. Protocol Documentation Core Vocabulary Clinical Vocabulary Security & Compliance Developer Getting Started CLI Reference TypeScript SDK Versioning Policy Company Cascade Agentic Labs Contact Privacy Policy Terms of Service © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { const links = document.querySelectorAll('.toc-list a'); const sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })(); # ============================================================================ # SECTION 13: CASE STUDIES # ============================================================================ POTS Check Case Study - Cascade Protocol in Production /* ── Case Study Layout ─────────────────────────────────────────────── */ .doc-layout { display: grid; grid-template-columns: 260px 1fr; gap: var(--space-2xl); max-width: 1200px; margin: 0 auto; padding: 0 var(--space-xl); } /* ── Sidebar / Table of Contents ───────────────────────────────────── */ .doc-sidebar { position: sticky; top: var(--space-xl); align-self: start; max-height: calc(100vh - 2 * var(--space-xl)); overflow-y: auto; padding-right: var(--space-md); } .toc-title { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: var(--space-md); } .toc-list { list-style: none; margin: 0; padding: 0; } .toc-list li { margin-bottom: 0; line-height: 1.4; } .toc-list a { display: block; padding: 0.3rem 0 0.3rem var(--space-md); font-size: 0.875rem; color: var(--text-secondary); border-left: 2px solid var(--border); transition: var(--transition); } .toc-list a:hover, .toc-list a.active { color: var(--primary); border-left-color: var(--accent); } /* ── Main Content Area ─────────────────────────────────────────────── */ .doc-content { min-width: 0; } .doc-content .section { margin-bottom: var(--space-3xl); scroll-margin-top: var(--space-xl); } .doc-content .section h2 { display: flex; align-items: center; gap: var(--space-sm); } .doc-content .section h3 { margin-top: var(--space-lg); } /* ── Callout / App Info Box ────────────────────────────────────────── */ .app-info-box { background: linear-gradient(135deg, #f0fafa 0%, #e6f5f5 100%); border: 1px solid #b2dfdb; border-left: 4px solid #00b4d8; border-radius: var(--radius); padding: var(--space-lg); margin: var(--space-lg) 0; } .app-info-box h4 { color: var(--primary); margin-top: 0; margin-bottom: var(--space-sm); } .app-info-box dl { margin: 0; display: grid; grid-template-columns: auto 1fr; column-gap: var(--space-lg); row-gap: 0.35rem; font-size: 0.9rem; } .app-info-box dt { font-weight: 600; color: var(--text-secondary); margin-top: 0; } .app-info-box dd { margin-left: 0; margin-top: 0; color: var(--text); } /* ── Data Flow Diagram ─────────────────────────────────────────────── */ .flow-diagram { background: var(--code-bg); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-xl); text-align: left; margin: var(--space-lg) 0; font-family: var(--font-mono); font-size: 0.85rem; line-height: 1.8; overflow-x: auto; color: var(--code-text); } .flow-diagram .flow-node { color: #7dd3fc; font-weight: 600; } .flow-diagram .flow-label { color: #86efac; font-style: italic; } .flow-diagram .flow-arrow { color: #94a3b8; } /* ── Provenance Timeline ───────────────────────────────────────────── */ .prov-timeline { margin: var(--space-lg) 0; } .prov-event { display: flex; gap: var(--space-md); margin-bottom: var(--space-md); align-items: flex-start; } .prov-dot { width: 12px; height: 12px; border-radius: 50%; margin-top: 0.35rem; flex-shrink: 0; } .prov-dot--device { background: var(--accent); } .prov-dot--self { background: var(--warning-border); } .prov-dot--clinical { background: var(--success-border); } .prov-event-content strong { display: block; font-size: 0.9rem; margin-bottom: 0.2rem; } .prov-event-content p { font-size: 0.875rem; color: var(--text-secondary); margin-bottom: 0; } /* ── Results Grid ──────────────────────────────────────────────────── */ .results-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-md); margin: var(--space-lg) 0; } .result-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-md); text-align: center; } .result-card .result-label { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin-bottom: var(--space-xs); } .result-card .result-value { font-size: 1.5rem; font-weight: 700; color: var(--primary); font-family: var(--font-mono); } .result-card .result-detail { font-size: 0.8rem; color: var(--text-secondary); margin-top: var(--space-xs); } /* ── Proof Points ──────────────────────────────────────────────────── */ .proof-item { display: flex; gap: var(--space-md); margin-bottom: var(--space-md); padding: var(--space-md); background: var(--success-bg); border: 1px solid var(--success-border); border-radius: var(--radius); } .proof-check { font-size: 1.1rem; flex-shrink: 0; margin-top: 0.1rem; } .proof-item p { margin-bottom: 0; font-size: 0.9rem; } .proof-item strong { display: block; margin-bottom: 0.2rem; } /* ── Responsive ────────────────────────────────────────────────────── */ @media (max-width: 900px) { .doc-layout { grid-template-columns: 1fr; } .doc-sidebar { position: static; max-height: none; border-bottom: 1px solid var(--border); padding-bottom: var(--space-lg); margin-bottom: var(--space-lg); } } Cascade Protocol Home Documentation Contact POTS Check: Cascade Protocol in Production A case study in real-world protocol use — one app, one condition, end-to-end Documentation / Case Studies / POTS Check On this page App Overview The Clinical Problem Architecture Data Flow Cascade Protocol in Practice Provenance in a Test Session Encryption and Storage What the App Demonstrates What This Proves Scope and Limitations Related POTS Check App Page Protocol Comparison Security Guide POTS Vocabulary Docs Published: February 2026 — Documents POTS Check v1.3a (App Store, December 2025) and CascadeSDK Swift as of the same release. App Overview POTS Check — Application Facts Full namePOTS Check PlatformiOS 18.0+ / watchOS 11.0+ StatusProduction — v1.3a on the App Store since December 2025 DeveloperCascade Agentic Labs LLC ProtocolCascade Protocol (Swift SDK, CascadeSDK) Vocabularypots: (https://ns.cascadeprotocol.org/pots/v1#), v1.3 Stable Data storeLocal Solid Pod (AES-256-GCM encrypted) Network callsZero 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. DeviceGenerated — Heart rate readings 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. SelfReported — Symptom scores 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. DeviceGenerated — Computed result 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. ClinicalGenerated — Practitioner notes (if present) 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 .pod archive. 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 Production Status Live App Store since December 2025 Data Types Stored 5+ Sessions, HR readings, symptom scores, results, provenance Provenance Types Used 3 DeviceGenerated, SelfReported, ClinicalGenerated External Network Calls 0 During core test operation 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. ✓ The pots: vocabulary is sufficient for a complete screening protocol 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. ✓ Mixed-provenance sessions are handled correctly 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 local-first architecture is viable for consumer health apps 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. ✓ Pod export produces files a clinician can inspect 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. ✓ HealthKit → CascadeSDK → Turtle is a working pipeline 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. ✓ The three-layer ontology works in practice 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. ← Back to Documentation   Protocol Comparison   POTS Check App Page → © 2026 Cascade Agentic Labs LLC. All rights reserved. (function () { var links = document.querySelectorAll('.toc-list a'); var sections = []; links.forEach(function (link) { var id = link.getAttribute('href').replace('#', ''); var el = document.getElementById(id); if (el) sections.push({ id: id, el: el, link: link }); }); function onScroll() { var scrollY = window.scrollY + 100; var current = sections[0]; for (var i = 0; i < sections.length; i++) { if (sections[i].el.offsetTop <= scrollY) { current = sections[i]; } } links.forEach(function (l) { l.classList.remove('active'); }); if (current) current.link.classList.add('active'); } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); })();