@prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dct: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix vcard: <http://www.w3.org/2006/vcard/ns#> .
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix void: <http://rdfs.org/ns/void#> .

# ============================================================================
# Ontology Metadata
# ============================================================================

<https://ns.cascadeprotocol.org/core/v1#> 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 <https://cascadeprotocol.org/docs/core/v1.0/> .

# ============================================================================
# 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 <https://semver.org/> .

# 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 <https://www.w3.org/TR/webid/> .

# ============================================================================
# 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 <id> 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. <https://ns.cascadeprotocol.org/health/v1#status>). 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 <value nullFlavor="NAV"/> ("we will know this later"), a
# <value nullFlavor="NASK"/> ("nobody asked") and a <value nullFlavor="ASKU"/>
# ("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 <http://terminology.hl7.org/CodeSystem/data-absent-reason> ;
    rdfs:seeAlso <http://terminology.hl7.org/CodeSystem/v3-NullFlavor> ;
    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 <https://id.cascadeprotocol.org/users/abc123> ;
#     prov:wasGeneratedBy [
#         a prov:Activity ;
#         prov:wasAssociatedWith <https://id.cascadeprotocol.org/users/abc123> ;
#         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 <id> 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 <advisory-iri> and prov:used <matched-record-iri>, 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 <https://www.w3.org/TR/vocab-dcat-3/> ;
    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 <https://www.w3.org/TR/void/> ;
    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 <https://ns.cascadeprotocol.org/health/v1#ConditionRecord> ;
    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 <https://ns.cascadeprotocol.org/clinical/v1#Medication> ;
    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 <https://ns.cascadeprotocol.org/health/v1#AllergyRecord> ;
    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 <https://ns.cascadeprotocol.org/health/v1#LabResultRecord> ;
    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 <https://ns.cascadeprotocol.org/health/v1#ImmunizationRecord> ;
    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 <https://ns.cascadeprotocol.org/clinical/v1#Supplement> ;
    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 <http://loinc.org/> ;
    owl:versionInfo "Added in core v3.4" .
