@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix cascade: <https://ns.cascadeprotocol.org/core/v1#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dct: <http://purl.org/dc/terms/> .

# ============================================================================
# Cascade Protocol -- Core Vocabulary SHACL Validation Shapes
# ============================================================================
# Version: 1.6 (2026-08-14)
# Validates: cascade:PatientProfile, cascade:Address, cascade:EmergencyContact,
#            cascade:PharmacyInfo, cascade:AdvanceDirectives, cascade:ProxyAgent,
#            cascade:ExportManifest, cascade:RecordSummary,
#            cascade:InteractionScenario, and the values of
#            cascade:sourceIdentity and cascade:dataAbsentReason wherever
#            they appear
#
# Severity levels:
#   sh:Violation -- Must fix (blocks operations)
#   sh:Warning   -- Should address (shown prominently)
#   sh:Info      -- Suggestion (nice to have)
#
# These shapes validate core patient demographics and identity structures
# defined in core.ttl v2.2. They are used by all Cascade Protocol applications
# that serialize PatientProfile data (e.g., Cascade Checkup intake forms,
# POTS Check patient context).
# ============================================================================

# ============================================================================
# Shape: Patient Profile
# ============================================================================

cascade:PatientProfileShape a sh:NodeShape ;
    sh:targetClass cascade:PatientProfile ;
    rdfs:label "Patient Profile Shape"@en ;
    rdfs:comment "Validation constraints for patient demographics and identity extensions"@en ;

    # VIOLATION: dateOfBirth required for age calculation and screening eligibility
    sh:property [
        sh:path cascade:dateOfBirth ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Date of Birth"@en ;
        sh:message "Patient profile must have a date of birth for age calculation and screening eligibility"@en
    ] ;

    # VIOLATION: biologicalSex required for clinical calculations
    sh:property [
        sh:path cascade:biologicalSex ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "male" "female" "intersex" ) ;
        sh:severity sh:Violation ;
        sh:name "Biological Sex"@en ;
        sh:message "Patient profile must specify biological sex (male, female, or intersex) for clinical calculations"@en
    ] ;

    # VIOLATION: dataProvenance required for source tracking
    sh:property [
        sh:path cascade:dataProvenance ;
        sh:minCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Data Provenance"@en ;
        sh:message "Patient profile must have at least one data provenance classification"@en
    ] ;

    # WARNING: genderIdentity recommended for patient-centered care
    sh:property [
        sh:path cascade:genderIdentity ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ( "woman" "man" "non_binary" "other" "prefer_not_to_say" ) ;
        sh:severity sh:Warning ;
        sh:name "Gender Identity"@en ;
        sh:message "Gender identity is recommended for patient-centered care. Values: woman, man, non_binary, other, prefer_not_to_say"@en
    ] ;

    # WARNING: emergencyContact recommended for safety
    sh:property [
        sh:path cascade:emergencyContact ;
        sh:class cascade:EmergencyContact ;
        sh:severity sh:Warning ;
        sh:name "Emergency Contact"@en ;
        sh:message "An emergency contact is recommended for patient safety"@en
    ] ;

    # INFO: ageGroup for screening classification
    sh:property [
        sh:path cascade:ageGroup ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ( "pediatric" "young_adult" "adult" "senior" ) ;
        sh:severity sh:Info ;
        sh:name "Age Group"@en ;
        sh:message "Age group helps classify patients for screening recommendations (pediatric, young_adult, adult, senior)"@en
    ] ;

    # INFO: computedAge for convenience
    sh:property [
        sh:path cascade:computedAge ;
        sh:datatype xsd:integer ;
        sh:maxCount 1 ;
        sh:minInclusive 0 ;
        sh:maxInclusive 150 ;
        sh:severity sh:Info ;
        sh:name "Computed Age"@en ;
        sh:message "Computed age should be between 0 and 150 years"@en
    ] ;

    # INFO: maritalStatus for demographic context
    sh:property [
        sh:path cascade:maritalStatus ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ( "single" "married" "domestic_partnership" "divorced" "separated" "widowed" "prefer_not_to_say" ) ;
        sh:severity sh:Info ;
        sh:name "Marital Status"@en ;
        sh:message "Marital status provides demographic context for care planning"@en
    ] ;

    # INFO: raceEthnicity for health disparity tracking
    sh:property [
        sh:path cascade:raceEthnicity ;
        sh:datatype xsd:string ;
        sh:in ( "american_indian_alaska_native" "asian" "black_african_american" "hispanic_latino" "native_hawaiian_pacific_islander" "white" "other" "prefer_not_to_say" ) ;
        sh:severity sh:Info ;
        sh:name "Race/Ethnicity"@en ;
        sh:message "Race/ethnicity data supports health equity tracking and risk assessment"@en
    ] ;

    # INFO: address for contact information
    sh:property [
        sh:path cascade:address ;
        sh:class cascade:Address ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Address"@en ;
        sh:message "A postal address is helpful for care coordination and correspondence"@en
    ] ;

    # INFO: preferredPharmacy for prescription routing
    sh:property [
        sh:path cascade:preferredPharmacy ;
        sh:class cascade:PharmacyInfo ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Preferred Pharmacy"@en ;
        sh:message "A preferred pharmacy helps streamline prescription fulfillment"@en
    ] ;

    # INFO: advanceDirectives for care planning
    sh:property [
        sh:path cascade:advanceDirectives ;
        sh:class cascade:AdvanceDirectives ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Advance Directives"@en ;
        sh:message "Advance directives document patient wishes for end-of-life care"@en
    ] ;

    # INFO: profileId for traceability
    sh:property [
        sh:path cascade:profileId ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Profile ID"@en ;
        sh:message "A profile ID (UUID) enables traceability and deduplication"@en
    ] .

# ============================================================================
# Shape: Address
# ============================================================================

cascade:AddressShape a sh:NodeShape ;
    sh:targetClass cascade:Address ;
    rdfs:label "Address Shape"@en ;
    rdfs:comment "Validation constraints for structured postal addresses. Accepts both simplified aliases (city, state) and FHIR-aligned properties (addressCity, addressState)."@en ;

    # WARNING: city or addressCity recommended for location identification
    sh:property [
        sh:path cascade:city ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "City"@en ;
        sh:message "City is recommended for meaningful address identification"@en
    ] ;

    sh:property [
        sh:path cascade:addressCity ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "Address City (FHIR-aligned)"@en ;
        sh:message "City (FHIR-aligned) is recommended for meaningful address identification"@en
    ] ;

    # WARNING: state or addressState recommended for location identification
    sh:property [
        sh:path cascade:state ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "State"@en ;
        sh:message "State is recommended for meaningful address identification"@en
    ] ;

    sh:property [
        sh:path cascade:addressState ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "Address State (FHIR-aligned)"@en ;
        sh:message "State (FHIR-aligned) is recommended for meaningful address identification"@en
    ] ;

    # INFO: streetAddress or addressLine
    sh:property [
        sh:path cascade:streetAddress ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Street Address"@en ;
        sh:message "Street address provides complete location details"@en
    ] ;

    sh:property [
        sh:path cascade:addressLine ;
        sh:datatype xsd:string ;
        sh:severity sh:Info ;
        sh:name "Address Line (FHIR-aligned)"@en ;
        sh:message "Address line (FHIR-aligned) provides complete location details"@en
    ] ;

    # INFO: postalCode or addressPostalCode
    sh:property [
        sh:path cascade:postalCode ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Postal Code"@en ;
        sh:message "Postal code helps with geographic identification"@en
    ] ;

    sh:property [
        sh:path cascade:addressPostalCode ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Address Postal Code (FHIR-aligned)"@en ;
        sh:message "Postal code (FHIR-aligned) helps with geographic identification"@en
    ] ;

    # INFO: country or addressCountry
    sh:property [
        sh:path cascade:country ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Country"@en ;
        sh:message "Country is helpful for international patients"@en
    ] ;

    sh:property [
        sh:path cascade:addressCountry ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Address Country (FHIR-aligned)"@en ;
        sh:message "Country (FHIR-aligned) is helpful for international patients"@en
    ] ;

    # INFO: addressUse for address purpose
    sh:property [
        sh:path cascade:addressUse ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ( "home" "work" "temp" "old" ) ;
        sh:severity sh:Info ;
        sh:name "Address Use"@en ;
        sh:message "Address use should be home, work, temp, or old"@en
    ] ;

    # INFO: addressType for address classification
    sh:property [
        sh:path cascade:addressType ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ( "postal" "physical" "both" ) ;
        sh:severity sh:Info ;
        sh:name "Address Type"@en ;
        sh:message "Address type should be postal, physical, or both"@en
    ] .

# ============================================================================
# Shape: Emergency Contact
# ============================================================================

cascade:EmergencyContactShape a sh:NodeShape ;
    sh:targetClass cascade:EmergencyContact ;
    rdfs:label "Emergency Contact Shape"@en ;
    rdfs:comment "Validation constraints for patient emergency contact persons"@en ;

    # VIOLATION: contactName required
    sh:property [
        sh:path cascade:contactName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:severity sh:Violation ;
        sh:name "Contact Name"@en ;
        sh:message "Emergency contact must have a name"@en
    ] ;

    # VIOLATION: contactPhone required
    sh:property [
        sh:path cascade:contactPhone ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:severity sh:Violation ;
        sh:name "Contact Phone"@en ;
        sh:message "Emergency contact must have a phone number"@en
    ] ;

    # WARNING: contactRelationship recommended
    sh:property [
        sh:path cascade:contactRelationship ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ( "spouse" "parent" "child" "sibling" "partner" "friend" "caregiver" "other" ) ;
        sh:severity sh:Warning ;
        sh:name "Contact Relationship"@en ;
        sh:message "Emergency contact relationship is recommended (spouse, parent, child, sibling, partner, friend, caregiver, other)"@en
    ] .

# ============================================================================
# Shape: Pharmacy Info
# ============================================================================

cascade:PharmacyInfoShape a sh:NodeShape ;
    sh:targetClass cascade:PharmacyInfo ;
    rdfs:label "Pharmacy Info Shape"@en ;
    rdfs:comment "Validation constraints for preferred pharmacy information"@en ;

    # VIOLATION: pharmacyName required
    sh:property [
        sh:path cascade:pharmacyName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:severity sh:Violation ;
        sh:name "Pharmacy Name"@en ;
        sh:message "Pharmacy must have a name"@en
    ] ;

    # INFO: pharmacyAddress for location
    sh:property [
        sh:path cascade:pharmacyAddress ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Pharmacy Address"@en ;
        sh:message "Pharmacy address helps identify the correct location"@en
    ] ;

    # INFO: pharmacyPhone for contact
    sh:property [
        sh:path cascade:pharmacyPhone ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Pharmacy Phone"@en ;
        sh:message "Pharmacy phone number is helpful for prescription coordination"@en
    ] .

# ============================================================================
# Shape: Advance Directives
# ============================================================================

cascade:AdvanceDirectivesShape a sh:NodeShape ;
    sh:targetClass cascade:AdvanceDirectives ;
    rdfs:label "Advance Directives Shape"@en ;
    rdfs:comment "Validation constraints for patient advance directive declarations. Elevated PHI sensitivity."@en ;

    # VIOLATION: hasLivingWill required
    sh:property [
        sh:path cascade:hasLivingWill ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Has Living Will"@en ;
        sh:message "Advance directives must declare whether a living will is on file"@en
    ] ;

    # VIOLATION: hasPowerOfAttorney required
    sh:property [
        sh:path cascade:hasPowerOfAttorney ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Has Power of Attorney"@en ;
        sh:message "Advance directives must declare whether a healthcare power of attorney is on file"@en
    ] ;

    # VIOLATION: hasDNR required
    sh:property [
        sh:path cascade:hasDNR ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Has DNR"@en ;
        sh:message "Advance directives must declare whether a do-not-resuscitate order is on file"@en
    ] ;

    # INFO: advanceDirectiveNotes for additional context
    sh:property [
        sh:path cascade:advanceDirectiveNotes ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Advance Directive Notes"@en ;
        sh:message "Notes can provide additional context about advance directive wishes"@en
    ] .

# ============================================================================
# Shapes: UserResolution and PendingConflict (v2.9)
# ============================================================================

cascade:UserResolutionShape a sh:NodeShape ;
    sh:targetClass cascade:UserResolution ;
    rdfs:label "User Resolution Shape"@en ;
    sh:property [
        sh:path cascade:conflictId ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Conflict ID"@en ;
        sh:message "UserResolution requires a conflictId"@en
    ] ;
    sh:property [
        sh:path cascade:resolution ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Resolution"@en ;
        sh:message "UserResolution requires a resolution decision"@en
    ] .

cascade:PendingConflictShape a sh:NodeShape ;
    sh:targetClass cascade:PendingConflict ;
    rdfs:label "Pending Conflict Shape"@en ;
    sh:property [
        sh:path cascade:conflictId ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Conflict ID"@en ;
        sh:message "PendingConflict requires a conflictId"@en
    ] .

# ============================================================================
# Shape: AI Extraction Activity (v3.0)
# ============================================================================

cascade:AIExtractionActivityShape a sh:NodeShape ;
    sh:targetClass cascade:AIExtractionActivity ;
    rdfs:label "AI Extraction Activity Shape"@en ;

    # Required: extractionConfidence (0.0 to 1.0)
    sh:property [
        sh:path cascade:extractionConfidence ;
        sh:datatype xsd:decimal ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minInclusive 0.0 ;
        sh:maxInclusive 1.0 ;
        sh:severity sh:Violation ;
        sh:name "Extraction Confidence"@en ;
        sh:message "AIExtractionActivity requires extractionConfidence between 0.0 and 1.0"@en
    ] ;

    # Required: extractionModel
    sh:property [
        sh:path cascade:extractionModel ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Extraction Model"@en ;
        sh:message "AIExtractionActivity requires an extractionModel identifier"@en
    ] ;

    # Optional: sourceNarrativeSection
    sh:property [
        sh:path cascade:sourceNarrativeSection ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "Source Narrative Section"@en
    ] ;

    # Optional: requiresUserReview
    sh:property [
        sh:path cascade:requiresUserReview ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "Requires User Review"@en
    ] .

# ============================================================================
# Shape: AI Generation Activity (v3.1)
# ============================================================================

cascade:AIGenerationActivityShape a sh:NodeShape ;
    sh:targetClass cascade:AIGenerationActivity ;
    rdfs:label "AI Generation Activity Shape"@en ;

    # Required: extractionModel (reused from AIExtractionActivity)
    sh:property [
        sh:path cascade:extractionModel ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Generation Model"@en ;
        sh:message "AIGenerationActivity requires an extractionModel identifier"@en
    ] ;

    # Required: trigger
    sh:property [
        sh:path cascade:trigger ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:severity sh:Violation ;
        sh:name "Trigger"@en ;
        sh:message "AIGenerationActivity requires a cascade:trigger value (InitialGeneration, RegenerationAfterReclassification, or AudienceRetargeting)"@en
    ] ;

    # Optional: promptVersion
    sh:property [
        sh:path cascade:promptVersion ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:severity sh:Warning ;
        sh:name "Prompt Version"@en ;
        sh:message "Prompt version is recommended for reproducibility"@en
    ] ;

    # Optional: generationTemperature (0.0 - 2.0 covers typical model ranges)
    sh:property [
        sh:path cascade:generationTemperature ;
        sh:datatype xsd:decimal ;
        sh:maxCount 1 ;
        sh:minInclusive 0.0 ;
        sh:maxInclusive 2.0 ;
        sh:severity sh:Warning ;
        sh:name "Generation Temperature"@en ;
        sh:message "Generation temperature should be between 0.0 and 2.0"@en
    ] ;

    # Optional: extractionConfidence (reused; not all generations score themselves)
    sh:property [
        sh:path cascade:extractionConfidence ;
        sh:datatype xsd:decimal ;
        sh:maxCount 1 ;
        sh:minInclusive 0.0 ;
        sh:maxInclusive 1.0 ;
        sh:severity sh:Info ;
        sh:name "Generation Confidence"@en
    ] .

# ============================================================================
# Shape: Advisory Application Activity (v3.1)
# ============================================================================

cascade:AdvisoryApplicationActivityShape a sh:NodeShape ;
    sh:targetClass cascade:AdvisoryApplicationActivity ;
    rdfs:label "Advisory Application Activity Shape"@en ;

    # Required: prov:used must appear at least twice (advisory IRI + matched record IRI)
    sh:property [
        sh:path <http://www.w3.org/ns/prov#used> ;
        sh:minCount 2 ;
        sh:severity sh:Violation ;
        sh:name "Used (advisory + matched record)"@en ;
        sh:message "AdvisoryApplicationActivity must record both the advisory IRI and the matched record IRI via prov:used"@en
    ] ;

    # Optional but recommended: appliedTriplesCount stamps the application size
    sh:property [
        sh:path cascade:appliedTriplesCount ;
        sh:datatype xsd:nonNegativeInteger ;
        sh:maxCount 1 ;
        sh:severity sh:Info ;
        sh:name "Applied Triples Count"@en ;
        sh:message "Recommended: stamp cascade:appliedTriplesCount with the number of triples inserted by this advisory application (≤64 per CAP C5)"@en
    ] .

# ============================================================================
# Shape: Source Identity (core v3.5)
# ============================================================================
# The ORIGIN axis. See cascade:sourceIdentity in core.ttl for the value form and
# the slug normalization; this shape checks that a producer wrote a well-formed
# one.
#
# WHY sh:targetSubjectsOf AND NOT sh:targetClass, AND WHY ABSENCE IS UNCHECKED
# ---------------------------------------------------------------------------
# This is an open-world property shape: it evaluates a record only if that record
# ALREADY carries cascade:sourceIdentity. A pod written before core v3.5 carries
# it nowhere, so this shape reports nothing on one, and every previously-valid
# pod stays valid.
#
# That is a deliberate choice over the alternative, sh:minCount 1 at sh:Warning
# on each record shape in health: and clinical:. Requiring presence the moment
# the property is defined turns every existing pod amber for a property no
# producer has yet had the opportunity to write, and a warning that fires on
# correct historical data is a warning readers learn to skip. The ratchet is
# stated in core.ttl's v3.5 changelog rather than left to judgement: presence
# becomes a Warning on the record shapes once the reference producers emit it,
# and a Violation only after a release in which that Warning is observably absent
# from conforming output.
#
# What IS checked, whenever the property is present: exactly one value, a string,
# and one of the three declared schemes. An unprefixed value is the specific
# error this catches, because it is what a producer writes when it has stamped a
# display label or an import-batch label into the origin axis, which is the
# confusion the axis exists to end.

cascade:SourceIdentityShape a sh:NodeShape ;
    sh:targetSubjectsOf cascade:sourceIdentity ;
    rdfs:label "Source Identity Shape"@en ;
    rdfs:comment "Constrains the ORIGIN axis on any record that carries it. Open-world: absence is not a finding in core v3.5."@en ;

    sh:property [
        sh:path cascade:sourceIdentity ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:pattern "^(org:[a-z0-9]+|ns:.+|transport:.+)$" ;
        sh:name "Source Identity"@en ;
        sh:message "cascade:sourceIdentity must be a single scheme-prefixed token: 'org:{slug}' for a derivable organization, 'ns:{namespace}' for an identifier assigning authority, or 'transport:{label}' as the honestly-labelled last resort. An unprefixed value is usually a display label or an import-batch label written into the origin axis."@en ;
        sh:severity sh:Violation
    ] .

# ============================================================================
# Shape: Data Absent Reason (core v3.6)
# ============================================================================
# Open-world, exactly like SourceIdentityShape above: sh:targetSubjectsOf, so it
# constrains the VALUE wherever cascade:dataAbsentReason appears and never
# requires the property's presence. Every pod written before core v3.6 validates
# unchanged, because none of them carries the predicate.
#
# The sh:in list is the 15 codes of
# http://terminology.hl7.org/CodeSystem/data-absent-reason, verbatim, as
# gathered by the value set http://hl7.org/fhir/ValueSet/data-absent-reason. No
# code is added and none is dropped. In particular the raw HL7 v3 NullFlavor
# codes (UNK, NAV, NASK, ASKU, ...) are deliberately NOT accepted here: an
# importer maps them on the way in, per the mapping table stated on the property
# in core.ttl, and accepting both spellings would give every absence two
# encodings and make "is this the same absence?" a string-comparison question.
#
# sh:Violation rather than sh:Warning, and that is not a ratchet skipped. The
# ratchet in core v3.5 was about REQUIRING a property no producer had written
# yet. Here nothing is required; the constraint only says that a producer which
# chooses to write this brand-new property must write a code from the value set
# it was defined against. There is no existing data to be lenient toward.

cascade:DataAbsentReasonShape a sh:NodeShape ;
    sh:targetSubjectsOf cascade:dataAbsentReason ;
    rdfs:label "Data Absent Reason Shape"@en ;
    rdfs:comment "Constrains the value of cascade:dataAbsentReason on any record that carries it. Open-world: absence is not a finding."@en ;

    sh:property [
        sh:path cascade:dataAbsentReason ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ("unknown" "asked-unknown" "temp-unknown" "not-asked"
               "asked-declined" "masked" "not-applicable" "unsupported"
               "as-text" "error" "not-a-number" "negative-infinity"
               "positive-infinity" "not-performed" "not-permitted") ;
        sh:name "Data Absent Reason"@en ;
        sh:message "cascade:dataAbsentReason must be a single code from http://terminology.hl7.org/CodeSystem/data-absent-reason. A raw HL7 v3 NullFlavor code (UNK, NAV, NASK, ASKU, ...) is not accepted: map it on import using the table on the property in core.ttl."@en ;
        sh:severity sh:Violation
    ] .

# ============================================================================
# Changelog
# ============================================================================
#
# Version 1.6 (2026-08-14)
# - Added DataAbsentReasonShape for core v3.6's cascade:dataAbsentReason.
#   sh:targetSubjectsOf, so it constrains the value wherever the property
#   appears and never requires its presence; pods written before v3.6 validate
#   unchanged. VIOLATION for a non-string, a repeated value, or a code outside
#   the 15-member data-absent-reason value set, which includes a raw HL7 v3
#   NullFlavor code written through unmapped.
#
# Version 1.5 (2026-08-09)
# - Added SourceIdentityShape for core v3.5's cascade:sourceIdentity.
#   sh:targetSubjectsOf, so it constrains the VALUE wherever the property
#   appears and never requires its presence; pods written before v3.5 validate
#   unchanged. VIOLATION for a non-string, a repeated value, or a value that
#   does not carry one of the org: / ns: / transport: schemes.
#
# Version 1.4 (2026-05-06)
# - Added cascade:appliedTriplesCount Info-severity property shape on
#   AdvisoryApplicationActivityShape (recommended; auditable size of an
#   advisory application — pairs with CAP profile constraint C5).
#
# Version 1.3 (2026-05-05)
# - Added AIGenerationActivityShape for LLM-generated narrative provenance
#   - REQUIRED: extractionModel, trigger
#   - WARNING: promptVersion, generationTemperature
#   - INFO: extractionConfidence
# - Added AdvisoryApplicationActivityShape for advisory patch application
#   - REQUIRED: prov:used (minCount 2 — advisory IRI and matched record IRI)
#
# Version 1.2 (2026-03-28)
# - Added AIExtractionActivityShape for AI extraction provenance tracking
#   - REQUIRED: extractionConfidence (xsd:decimal, 0.0-1.0), extractionModel
#   - OPTIONAL: sourceNarrativeSection, requiresUserReview
#
# Version 1.1 (2026-03-27)
# - Added UserResolutionShape: VIOLATION for conflictId, resolution
# - Added PendingConflictShape: VIOLATION for conflictId
#
# Version 1.0 (2026-02-18)
# - Initial release of core vocabulary SHACL shapes (Phase 4)
# - PatientProfileShape: VIOLATION for dateOfBirth, biologicalSex,
#   dataProvenance; WARNING for genderIdentity, emergencyContact;
#   INFO for ageGroup, computedAge, maritalStatus, raceEthnicity,
#   address, preferredPharmacy, advanceDirectives, profileId
# - AddressShape: WARNING for city/addressCity, state/addressState;
#   INFO for streetAddress/addressLine, postalCode/addressPostalCode,
#   country/addressCountry, addressUse, addressType
# - EmergencyContactShape: VIOLATION for contactName, contactPhone;
#   WARNING for contactRelationship
# - PharmacyInfoShape: VIOLATION for pharmacyName;
#   INFO for pharmacyAddress, pharmacyPhone
# - AdvanceDirectivesShape: VIOLATION for hasLivingWill,
#   hasPowerOfAttorney, hasDNR; INFO for advanceDirectiveNotes
#

# ProxyAgentShape (v3.3) — caregiver-proxy actor
cascade:ProxyAgentShape a sh:NodeShape ;
    sh:targetClass cascade:ProxyAgent ;
    sh:property [ sh:path cascade:actsForPatient ; sh:minCount 1 ;
                  sh:message "A ProxyAgent must declare the patient it acts for (cascade:actsForPatient)." ] ;
    sh:property [ sh:path cascade:proxyRelationship ; sh:minCount 1 ; sh:datatype xsd:string ] ;
    sh:property [ sh:path cascade:proxyGrantedAt ; sh:minCount 1 ; sh:datatype xsd:dateTime ] .

# ============================================================================
# Pod Export Manifest Shapes (v1.1 -- core v3.4)
# ============================================================================
#
# The manifest is the first thing a consuming agent reads and the thing it
# trusts to decide whether an export is complete. Until core v3.4 none of its
# classes was defined, so nothing targeted them and validating a manifest
# checked nothing at all.

cascade:ExportManifestShape a sh:NodeShape ;
    sh:targetClass cascade:ExportManifest ;
    rdfs:label "Export Manifest Shape"@en ;
    rdfs:comment "Validation constraints for a pod export manifest. cascade:ExportManifest is a dcat:Dataset, so the DCAT-standard descriptive terms are the required ones."@en ;

    # REQUIRED: a title. An untitled dataset cannot be presented to a user.
    sh:property [
        sh:path dct:title ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:name "Title"@en ;
        sh:message "Export manifest must carry exactly one non-empty dct:title"@en ;
        sh:severity sh:Violation
    ] ;

    # REQUIRED: creation timestamp. Without it a consumer cannot tell whether
    # the export is current, which is the manifest's main job.
    sh:property [
        sh:path dct:created ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "Created"@en ;
        sh:message "Export manifest must carry exactly one dct:created timestamp"@en ;
        sh:severity sh:Violation
    ] ;

    # REQUIRED: schema version, so a consumer can refuse an export it cannot read
    sh:property [
        sh:path cascade:schemaVersion ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]+\\.[0-9]+$" ;
        sh:name "Schema Version"@en ;
        sh:message "Export manifest must declare exactly one schemaVersion in major.minor form"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [
        sh:path cascade:patientProfileVersion ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]+\\.[0-9]+$" ;
        sh:name "Patient Profile Version"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [
        sh:path cascade:clinicalSummary ;
        sh:maxCount 1 ;
        sh:name "Clinical Summary"@en ;
        sh:message "Export manifest must not carry more than one clinical summary"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [
        sh:path cascade:wellnessSummary ;
        sh:maxCount 1 ;
        sh:name "Wellness Summary"@en ;
        sh:message "Export manifest must not carry more than one wellness summary"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [
        sh:path dct:description ;
        sh:maxCount 1 ;
        sh:name "Description"@en ;
        sh:severity sh:Violation
    ] .

cascade:RecordSummaryShape a sh:NodeShape ;
    sh:targetClass cascade:RecordSummary ;
    rdfs:label "Record Summary Shape"@en ;
    rdfs:comment "Validation constraints for per-domain record counts. Every count must be a single non-negative integer: a negative or repeated count silently corrupts any completeness check built on it."@en ;

    sh:property [
        sh:path cascade:domain ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:name "Domain"@en ;
        sh:message "Record summary must name exactly one domain"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [ sh:path cascade:conditionCount ;     sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Condition Count"@en ;      sh:message "conditionCount must be a single non-negative integer"@en ;      sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:medicationCount ;    sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Medication Count"@en ;     sh:message "medicationCount must be a single non-negative integer"@en ;     sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:allergyCount ;       sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Allergy Count"@en ;        sh:message "allergyCount must be a single non-negative integer"@en ;        sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:labResultCount ;     sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Lab Result Count"@en ;     sh:message "labResultCount must be a single non-negative integer"@en ;      sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:immunizationCount ;  sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Immunization Count"@en ;   sh:message "immunizationCount must be a single non-negative integer"@en ;   sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:coverageCount ;      sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Coverage Count"@en ;       sh:message "coverageCount must be a single non-negative integer"@en ;       sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:supplementCount ;    sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:name "Supplement Count"@en ;     sh:message "supplementCount must be a single non-negative integer"@en ;     sh:severity sh:Violation ] ;

    # Day counts are bounded above as well: a "days covered" figure larger than
    # a decade of daily readings is a unit error, not a long history.
    sh:property [ sh:path cascade:vitalSignDays ;      sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Vital Sign Days"@en ;      sh:message "vitalSignDays must be a single integer between 0 and 36500"@en ;      sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:heartRateDays ;      sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Heart Rate Days"@en ;      sh:message "heartRateDays must be a single integer between 0 and 36500"@en ;      sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:bloodPressureDays ;  sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Blood Pressure Days"@en ;  sh:message "bloodPressureDays must be a single integer between 0 and 36500"@en ;  sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:activityDays ;       sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Activity Days"@en ;       sh:message "activityDays must be a single integer between 0 and 36500"@en ;       sh:severity sh:Violation ] ;
    sh:property [ sh:path cascade:sleepDays ;          sh:datatype xsd:integer ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 36500 ; sh:name "Sleep Days"@en ;          sh:message "sleepDays must be a single integer between 0 and 36500"@en ;          sh:severity sh:Violation ] ;

    sh:property [
        sh:path cascade:dataProvenance ;
        sh:maxCount 1 ;
        sh:name "Data Provenance"@en ;
        sh:severity sh:Violation
    ] .

cascade:InteractionScenarioShape a sh:NodeShape ;
    sh:targetClass cascade:InteractionScenario ;
    rdfs:label "Interaction Scenario Shape"@en ;
    rdfs:comment "Validation constraints for a flagged cross-provenance interaction. A scenario that names no resources is not actionable, so involvedResources is required."@en ;

    sh:property [
        sh:path dct:title ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:name "Title"@en ;
        sh:message "Interaction scenario must carry exactly one non-empty dct:title"@en ;
        sh:severity sh:Violation
    ] ;

    # REQUIRED: the resources to correlate. Without them the scenario states a
    # risk exists but gives a consumer nothing to check it against.
    sh:property [
        sh:path cascade:involvedResources ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "Involved Resources"@en ;
        sh:message "Interaction scenario must name exactly one list of involved resources"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [
        sh:path cascade:severity ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ("low" "moderate" "high" "critical") ;
        sh:name "Severity"@en ;
        sh:message "Interaction scenario severity must be low, moderate, high or critical"@en ;
        sh:severity sh:Violation
    ] ;

    sh:property [
        sh:path cascade:requiresCrossProvenance ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
        sh:name "Requires Cross-Provenance"@en ;
        sh:message "requiresCrossProvenance must be a single boolean"@en ;
        sh:severity sh:Violation
    ] .
