@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix coverage: <https://ns.cascadeprotocol.org/coverage/v1#> .
@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#> .

# ============================================================================
# Cascade Protocol — Coverage Vocabulary SHACL Validation Shapes
# Version: 1.0 (Phase 4, 2026-02-18)
# Validates: coverage:InsurancePlan
#
# Layer 2 domain-specific vocabulary for patient-owned insurance and benefits data.
# Aligns with FHIR Coverage resource (fhir:Coverage).
# ============================================================================

# ============================================================================
# Shape: Insurance Plan
# ============================================================================

coverage:InsurancePlanShape a sh:NodeShape ;
    sh:targetClass coverage:InsurancePlan ;
    rdfs:label "Insurance Plan Shape"@en ;
    rdfs:comment "Validation constraints for patient insurance plan records. Covers core identification, plan classification, coverage period, subscriber info, and pharmacy benefits."@en ;

    # ------------------------------------------------------------------
    # REQUIRED (sh:Violation) — Must be present for valid plan record
    # ------------------------------------------------------------------

    # Required: providerName
    sh:property [
        sh:path coverage:providerName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:name "Provider Name"@en ;
        sh:message "Insurance provider name is required"@en ;
        sh:severity sh:Violation
    ] ;

    # Required: memberId
    sh:property [
        sh:path coverage:memberId ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:name "Member ID"@en ;
        sh:message "Member ID is required for claims processing"@en ;
        sh:severity sh:Violation
    ] ;

    # Required: coverageType
    #
    # PRESENCE is required. The VALUE is checked separately, at sh:Warning, by
    # coverage:CoverageTypeVocabularyShape below.
    #
    # WHY THE VALUE CHECK IS NOT A VIOLATION
    # --------------------------------------
    # This property maps to FHIR R4 Coverage.type, whose binding to
    # http://hl7.org/fhir/ValueSet/coverage-type is EXTENSIBLE, not required.
    # FHIR defines extensible as: codes SHALL come from the value set if one of
    # them applies, and "if the value set does not cover the concept, alternate
    # codes MAY be used" (https://hl7.org/fhir/R4/terminologies.html#extensible).
    # A closed enum at Violation severity therefore contradicted the binding
    # strength of the element the data is converted from: a payer sending a
    # v3-ActCode coverage-type code such as EHCPOL, or its own local code, had a
    # conformant Coverage resource rejected outright.
    #
    # Note also that the original four values conflated two different FHIR
    # elements: "primary"/"secondary" describe Coverage.order (which plan pays
    # first), while "dental"/"vision" describe Coverage.type (what is covered).
    # Both spellings are retained below for backward compatibility.
    sh:property [
        sh:path coverage:coverageType ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:name "Coverage Type"@en ;
        sh:message "Coverage type is required"@en ;
        sh:severity sh:Violation
    ] ;

    # ------------------------------------------------------------------
    # WARNING (sh:Warning) — Important for coverage verification
    # ------------------------------------------------------------------

    # Warning: groupNumber
    sh:property [
        sh:path coverage:groupNumber ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Group Number"@en ;
        sh:message "Group number helps verify coverage"@en ;
        sh:severity sh:Warning
    ] ;

    # Warning: planType
    sh:property [
        sh:path coverage:planType ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ("hmo" "ppo" "epo" "pos" "hdhp" "medicare" "medicaid" "tricare" "other") ;
        sh:name "Plan Type"@en ;
        sh:message "Plan type should be hmo, ppo, epo, pos, hdhp, medicare, medicaid, tricare, or other"@en ;
        sh:severity sh:Warning
    ] ;

    # Warning: effectiveStart
    sh:property [
        sh:path coverage:effectiveStart ;
        sh:datatype xsd:date ;
        sh:maxCount 1 ;
        sh:name "Effective Start"@en ;
        sh:message "Plan effective date helps verify active coverage"@en ;
        sh:severity sh:Warning
    ] ;

    # Warning: subscriberRelationship
    #
    # The list is the SubscriberPolicyholder code system in full and verbatim,
    # http://terminology.hl7.org/CodeSystem/subscriber-relationship, which is
    # what FHIR R4 Coverage.relationship binds to. The previous list held five
    # of its seven codes; "common" (Common Law Spouse) and "injured" (Injured
    # Party) were simply missing, so an export carrying either produced a
    # warning for being correct.
    sh:property [
        sh:path coverage:subscriberRelationship ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:in ("child" "parent" "spouse" "common" "other" "self" "injured") ;
        sh:name "Subscriber Relationship"@en ;
        sh:message "Subscriber relationship should be a code from the HL7 SubscriberPolicyholder code system (http://terminology.hl7.org/CodeSystem/subscriber-relationship): child, parent, spouse, common, other, self, injured"@en ;
        sh:severity sh:Warning
    ] ;

    # ------------------------------------------------------------------
    # INFO (sh:Info) — Suggested for completeness
    # ------------------------------------------------------------------

    # Info: planName
    sh:property [
        sh:path coverage:planName ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Plan Name"@en ;
        sh:severity sh:Info
    ] ;

    # Info: effectiveEnd
    sh:property [
        sh:path coverage:effectiveEnd ;
        sh:datatype xsd:date ;
        sh:maxCount 1 ;
        sh:name "Effective End"@en ;
        sh:severity sh:Info
    ] ;

    # Info: subscriberId
    sh:property [
        sh:path coverage:subscriberId ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Subscriber ID"@en ;
        sh:severity sh:Info
    ] ;

    # Info: subscriberName
    sh:property [
        sh:path coverage:subscriberName ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Subscriber Name"@en ;
        sh:severity sh:Info
    ] ;

    # Info: rxBin
    sh:property [
        sh:path coverage:rxBin ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Rx BIN"@en ;
        sh:severity sh:Info
    ] ;

    # Info: rxPcn
    sh:property [
        sh:path coverage:rxPcn ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Rx PCN"@en ;
        sh:severity sh:Info
    ] ;

    # Info: rxGroup
    sh:property [
        sh:path coverage:rxGroup ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
        sh:name "Rx Group"@en ;
        sh:severity sh:Info
    ] ;

    # ------------------------------------------------------------------
    # Standard Cascade metadata
    # ------------------------------------------------------------------

    # Required: provenance
    sh:property [
        sh:path cascade:dataProvenance ;
        sh:in (cascade:ClinicalGenerated cascade:EHRVerified cascade:DeviceGenerated cascade:PatientReported cascade:SelfReported) ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:name "Data Provenance"@en ;
        sh:message "Insurance plan must have valid provenance"@en
    ] ;

    # Required: schemaVersion
    sh:property [
        sh:path cascade:schemaVersion ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]+\\.[0-9]+$" ;
        sh:name "Schema Version"@en ;
        sh:message "Schema version must be in format major.minor"@en
    ] .

# ============================================================================
# Shape: Coverage Type vocabulary (advisory)
# ============================================================================
#
# Split out of InsurancePlanShape so that the REQUIREMENT (a coverage type is
# present) and the RECOMMENDATION (it comes from the value set FHIR names) can
# carry different severities, which a single sh:property cannot do.
#
# The list is the union of:
#   - the v3-ActCode coverage-type codes reachable from
#     http://hl7.org/fhir/ValueSet/coverage-type, the value set FHIR R4 binds
#     Coverage.type to;
#   - the four values this vocabulary has accepted since v1.0, retained so no
#     existing record starts warning.
#
# Severity is sh:Warning because the FHIR binding is EXTENSIBLE: alternate codes
# MAY be used where the value set does not cover the concept
# (https://hl7.org/fhir/R4/terminologies.html#extensible). A payer's local code
# is therefore worth flagging for review and is not grounds for rejecting the
# record.

coverage:CoverageTypeVocabularyShape a sh:NodeShape ;
    sh:targetClass coverage:InsurancePlan ;
    rdfs:label "Coverage Type Vocabulary Shape"@en ;
    rdfs:comment "Advisory check that coverage:coverageType comes from the value set FHIR R4 binds Coverage.type to. Warning severity, because that binding is extensible."@en ;

    sh:property [
        sh:path coverage:coverageType ;
        sh:in (
            # Retained Cascade values (v1.0-v1.3).
            "primary" "secondary" "dental" "vision"
            # v3-ActCode coverage types.
            "EHCPOL" "HSAPOL" "AUTOPOL" "COL" "UNINSMOT" "PUBLICPOL"
            "DENTPRG" "DISEASEPRG" "CANPRG" "ENDRENAL" "HIVAIDS" "MANDPOL"
            "MENTPRG" "SAFNET" "SUBPRG" "SUBSIDIZ" "SUBSIDMC" "SUBSUPP"
            "WCBPOL" "DENTAL" "DISEASE" "DRUGPOL" "HIP" "LTC" "MCPOL"
            "POS" "HMO" "PPO" "MENTPOL" "SUBPOL" "VISPOL"
        ) ;
        sh:name "Coverage Type Vocabulary"@en ;
        sh:message "Coverage type should come from the value set FHIR R4 binds Coverage.type to (http://hl7.org/fhir/ValueSet/coverage-type), or one of the retained Cascade values primary, secondary, dental, vision. That binding is extensible, so a payer-local code is acceptable where no listed code applies."@en ;
        sh:severity sh:Warning
    ] .

# ============================================================================
# Changelog
# ============================================================================
#
# Version 1.1 (2026-08-08) — coverage v1.4
# - coverage:subscriberRelationship: the sh:in list is now the HL7
#   SubscriberPolicyholder code system in full,
#   http://terminology.hl7.org/CodeSystem/subscriber-relationship, which is what
#   FHIR R4 Coverage.relationship binds to. Two of its seven codes were missing:
#   "common" (Common Law Spouse) and "injured" (Injured Party). An export
#   carrying either was warned for being correct.
# - coverage:coverageType: the closed four-member enum was a sh:Violation on an
#   element FHIR R4 binds EXTENSIBLY (Coverage.type), so a payer sending a
#   conformant v3-ActCode code such as EHCPOL had the record rejected. The
#   Violation now checks presence only; the value is checked against the FHIR
#   value set plus the four retained Cascade values by the new
#   coverage:CoverageTypeVocabularyShape, at sh:Warning.
# - Strictly widening: nothing that validated under v1.0 fails under v1.1, and
#   two classes of previously-rejected conformant record now pass.
#
# Version 1.0 (2026-02-18)
# - Initial release with InsurancePlanShape
#   - 3 REQUIRED (sh:Violation): providerName, memberId, coverageType
#   - 4 WARNING (sh:Warning): groupNumber, planType, effectiveStart, subscriberRelationship
#   - 6 INFO (sh:Info): planName, effectiveEnd, subscriberId, subscriberName, rxBin, rxPcn, rxGroup
#   - Standard Cascade metadata: dataProvenance, schemaVersion
# - Aligns with coverage.ttl v1.0 InsurancePlan class (14 properties)
#
