@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 sh: <http://www.w3.org/ns/shacl#> .
@prefix uco-core: <https://ontology.unifiedcyberontology.org/uco/core/> .
@prefix uco-identity: <https://ontology.unifiedcyberontology.org/uco/identity/> .
@prefix uco-location: <https://ontology.unifiedcyberontology.org/uco/location/> .
@prefix uco-observable: <https://ontology.unifiedcyberontology.org/uco/observable/> .
@prefix uco-action: <https://ontology.unifiedcyberontology.org/uco/action/> .
@prefix cacontology: <https://cacontology.projectvic.org#> .
@prefix cacontology-registry: <https://cacontology.projectvic.org/sex-offender-registry#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dcterms: <http://purl.org/dc/terms/> .

# Ontology Declaration
<https://cacontology.projectvic.org/sex-offender-registry/shapes/3.0.0> rdf:type owl:Ontology ;
    rdfs:label "CAC Sex Offender Registry SHACL Shapes"@en ;
    rdfs:comment "SHACL validation shapes for the CAC Sex Offender Registry Ontology, providing validation for registry data, registration requirements, and registry management systems."@en ;
    owl:versionIRI <https://cacontology.projectvic.org/sex-offender-registry/shapes/3.0.0> ;
    owl:versionInfo "3.0.0" ;
    dcterms:creator "CAC Ontology Team" ;
    dcterms:modified "2025-11-18"^^xsd:date ;
    owl:imports <https://cacontology.projectvic.org/sex-offender-registry/3.0.0> ,
                <http://purl.org/nemo/gufo#> ,
                <https://ontology.unifiedcyberontology.org/uco/core/> .

# =============================================================================
# REGISTRY CORE SHAPES
# =============================================================================

cacontology-registry:SexOffenderRegistryShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:SexOffenderRegistry ;
    rdfs:label "Sex Offender Registry Shape"@en ;
    rdfs:comment "Validation shape for sex offender registry instances."@en ;
    sh:property [
        sh:path uco-core:name ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 5 ;
        sh:maxLength 200 ;
        sh:message "Registry must have a name (5-200 characters)."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:managedBy ;
        sh:class uco-identity:Organization ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Registry must specify managing organization."@en
    ] .

cacontology-registry:RegisteredSexOffenderShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegisteredSexOffender ;
    rdfs:label "Registered Sex Offender Shape"@en ;
    rdfs:comment "Validation shape for registered sex offender instances."@en ;
    sh:property [
        sh:path cacontology-registry:fullName ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 100 ;
        sh:message "Offender must have a full name (2-100 characters)."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:dateOfBirth ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Offender must have a date of birth."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:socialSecurityNumber ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]{3}-[0-9]{2}-[0-9]{4}$" ;
        sh:message "SSN must follow format XXX-XX-XXXX when provided."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:sex ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "Male" "Female" "Unknown" ) ;
        sh:message "Sex must be Male, Female, or Unknown."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:registeredIn ;
        sh:class cacontology-registry:SexOffenderRegistry ;
        sh:minCount 1 ;
        sh:message "Offender must be registered in at least one registry."@en
    ] .

cacontology-registry:OffenderProfileShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:OffenderProfile ;
    rdfs:label "Offender Profile Shape"@en ;
    rdfs:comment "Validation shape for offender profile instances."@en ;
    sh:property [
        sh:path uco-core:createdTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Profile must specify creation time."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:lastUpdate ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:message "Profile may specify last update time."@en
    ] .

cacontology-registry:RegistrationRecordShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegistrationRecord ;
    rdfs:label "Registration Record Shape"@en ;
    rdfs:comment "Validation shape for registration record instances."@en ;
    sh:property [
        sh:path cacontology-registry:registrationNumber ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 5 ;
        sh:maxLength 50 ;
        sh:message "Registration record must have a registration number (5-50 characters)."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:registrationDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Registration record must specify registration date."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:expirationDate ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:message "Registration record may specify expiration date."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:tier ;
        sh:datatype xsd:integer ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minInclusive 1 ;
        sh:maxInclusive 3 ;
        sh:message "Registration tier must be 1, 2, or 3."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:lifetimeRegistration ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Registration record must specify if lifetime registration is required."@en
    ] .

# =============================================================================
# OFFENSE AND CONVICTION SHAPES
# =============================================================================

cacontology-registry:ConvictingOffenseShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:ConvictingOffense ;
    rdfs:label "Convicting Offense Shape"@en ;
    rdfs:comment "Validation shape for convicting offense instances."@en ;
    sh:property [
        sh:path cacontology-registry:offenseDescription ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 10 ;
        sh:maxLength 1000 ;
        sh:message "Offense must have a description (10-1000 characters)."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:convictionDate ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Offense must specify conviction date."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:sentenceLength ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:maxLength 100 ;
        sh:message "Sentence length should be 1-100 characters when specified."@en
    ] .

cacontology-registry:VictimInformationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:VictimInformation ;
    rdfs:label "Victim Information Shape"@en ;
    rdfs:comment "Validation shape for victim information instances."@en ;
    sh:property [
        sh:path cacontology-registry:victimAge ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:minInclusive 0 ;
        sh:maxInclusive 25 ;
        sh:message "Victim age must be between 0 and 25 when specified."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:victimCount ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:minInclusive 1 ;
        sh:message "Victim count must be at least 1 when specified."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:victimRelationship ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "stranger" "acquaintance" "family_member" "authority_figure" "romantic_partner" "unknown" ) ;
        sh:message "Victim relationship must be from the allowed list."@en
    ] .

# =============================================================================
# PHYSICAL DESCRIPTION SHAPES
# =============================================================================

cacontology-registry:PhysicalDescriptionShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:PhysicalDescription ;
    rdfs:label "Physical Description Shape"@en ;
    rdfs:comment "Validation shape for physical description instances."@en ;
    sh:property [
        sh:path cacontology-registry:height ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]'[0-9]{1,2}\"$|^[0-9]{2,3} cm$" ;
        sh:message "Height must be in format X'XX\" or XXX cm when specified."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:weight ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]{2,3} lbs$|^[0-9]{2,3} kg$" ;
        sh:message "Weight must be in format XXX lbs or XXX kg when specified."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:eyeColor ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "brown" "blue" "green" "hazel" "gray" "black" "unknown" ) ;
        sh:message "Eye color must be from the allowed list."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:hairColor ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "black" "brown" "blonde" "red" "gray" "white" "bald" "unknown" ) ;
        sh:message "Hair color must be from the allowed list."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:race ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "white" "black" "hispanic" "asian" "native_american" "pacific_islander" "other" "unknown" ) ;
        sh:message "Race must be from the allowed list."@en
    ] .

# =============================================================================
# ADDRESS AND LOCATION SHAPES
# =============================================================================

cacontology-registry:RegisteredAddressShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegisteredAddress ;
    rdfs:label "Registered Address Shape"@en ;
    rdfs:comment "Validation shape for registered address instances."@en ;
    sh:property [
        sh:path uco-location:street ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 5 ;
        sh:maxLength 200 ;
        sh:message "Address must have a street (5-200 characters)."@en
    ] ;
    sh:property [
        sh:path uco-location:city ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 100 ;
        sh:message "Address must have a city (2-100 characters)."@en
    ] ;
    sh:property [
        sh:path uco-location:state ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 50 ;
        sh:message "Address must have a state (2-50 characters)."@en
    ] ;
    sh:property [
        sh:path uco-location:postalCode ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:pattern "^[0-9]{5}(-[0-9]{4})?$" ;
        sh:message "Postal code must be in format XXXXX or XXXXX-XXXX."@en
    ] .

cacontology-registry:PrimaryResidenceShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:PrimaryResidence ;
    rdfs:label "Primary Residence Shape"@en ;
    rdfs:comment "Validation shape for primary residence instances."@en ;
    sh:property [
        sh:path uco-core:startTime ;
        sh:datatype xsd:date ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Primary residence must specify start date."@en
    ] .

cacontology-registry:WorkLocationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:WorkLocation ;
    rdfs:label "Work Location Shape"@en ;
    rdfs:comment "Validation shape for work location instances."@en ;
    sh:property [
        sh:path uco-core:name ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 200 ;
        sh:message "Work location must have a name (2-200 characters)."@en
    ] .

# =============================================================================
# EMPLOYMENT AND EDUCATION SHAPES
# =============================================================================

cacontology-registry:EmploymentInformationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:EmploymentInformation ;
    rdfs:label "Employment Information Shape"@en ;
    rdfs:comment "Validation shape for employment information instances."@en ;
    sh:property [
        sh:path uco-core:name ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 200 ;
        sh:message "Employment must specify employer name (2-200 characters)."@en
    ] ;
    sh:property [
        sh:path uco-core:startTime ;
        sh:datatype xsd:date ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:message "Employment may specify start date."@en
    ] .

cacontology-registry:EducationInformationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:EducationInformation ;
    rdfs:label "Education Information Shape"@en ;
    rdfs:comment "Validation shape for education information instances."@en ;
    sh:property [
        sh:path uco-core:name ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 200 ;
        sh:message "Education must specify institution name (2-200 characters)."@en
    ] .

# =============================================================================
# VEHICLE INFORMATION SHAPES
# =============================================================================

cacontology-registry:VehicleInformationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:VehicleInformation ;
    rdfs:label "Vehicle Information Shape"@en ;
    rdfs:comment "Validation shape for vehicle information instances."@en ;
    sh:property [
        sh:path uco-observable:make ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 50 ;
        sh:message "Vehicle must specify make (2-50 characters)."@en
    ] ;
    sh:property [
        sh:path uco-observable:model ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 1 ;
        sh:maxLength 50 ;
        sh:message "Vehicle must specify model (1-50 characters)."@en
    ] ;
    sh:property [
        sh:path uco-observable:year ;
        sh:datatype xsd:integer ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:minInclusive 1900 ;
        sh:maxInclusive 2030 ;
        sh:message "Vehicle year must be between 1900 and 2030 when specified."@en
    ] ;
    sh:property [
        sh:path uco-observable:licensePlate ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:minLength 2 ;
        sh:maxLength 15 ;
        sh:message "License plate should be 2-15 characters when specified."@en
    ] .

# =============================================================================
# COMPLIANCE AND STATUS SHAPES
# =============================================================================

cacontology-registry:RegistrationStatusShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegistrationStatus ;
    rdfs:label "Registration Status Shape"@en ;
    rdfs:comment "Validation shape for registration status instances."@en ;
    sh:property [
        sh:path uco-core:name ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:in ( "active" "inactive" "non_compliant" "delinquent" "absconded" "deceased" "incarcerated" ) ;
        sh:message "Registration status must be from the allowed list."@en
    ] ;
    sh:property [
        sh:path uco-core:startTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Status must specify start time."@en
    ] .

cacontology-registry:ComplianceViolationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:ComplianceViolation ;
    rdfs:label "Compliance Violation Shape"@en ;
    rdfs:comment "Validation shape for compliance violation instances."@en ;
    sh:property [
        sh:path uco-core:description ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:minLength 10 ;
        sh:maxLength 1000 ;
        sh:message "Violation must have a description (10-1000 characters)."@en
    ] ;
    sh:property [
        sh:path uco-core:startTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Violation must specify occurrence time."@en
    ] ;
    sh:property [
        sh:path cacontology-registry:violationCategory ;
        sh:datatype xsd:string ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:in ( "failure_to_register" "false_information" "late_update" "residency_restriction_violation" ) ;
        sh:message "Violation category, when provided, must be one of: failure_to_register, false_information, late_update, residency_restriction_violation."@en
    ] .

cacontology-registry:VerificationCheckShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:VerificationCheck ;
    rdfs:label "Verification Check Shape"@en ;
    rdfs:comment "Validation shape for verification check instances."@en ;
    sh:property [
        sh:path uco-core:startTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Verification check must specify start time."@en
    ] ;
    sh:property [
        sh:path uco-action:performer ;
        sh:class uco-identity:Person ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:message "Verification check must specify performing officer."@en
    ] .

# =============================================================================
# CROSS-REFERENCE VALIDATION SHAPES
# =============================================================================

cacontology-registry:OffenderRegistrationCrossReferenceShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegisteredSexOffender ;
    rdfs:label "Offender Registration Cross Reference Shape"@en ;
    rdfs:comment "Validates that registered offenders have required registration records."@en ;
    sh:sparql [
        sh:message "Registered sex offender must have at least one registration record."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this a cacontology-registry:RegisteredSexOffender .
                FILTER NOT EXISTS {
                    $this cacontology-registry:hasRegistrationRecord ?record .
                    ?record a cacontology-registry:RegistrationRecord .
                }
            }
        """
    ] .

cacontology-registry:RegistrationOffenseCrossReferenceShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegistrationRecord ;
    rdfs:label "Registration Offense Cross Reference Shape"@en ;
    rdfs:comment "Validates that registration records are based on convicting offenses."@en ;
    sh:sparql [
        sh:message "Registration record must be based on at least one convicting offense."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this a cacontology-registry:RegistrationRecord .
                FILTER NOT EXISTS {
                    $this cacontology-registry:basedOnOffense ?offense .
                    ?offense a cacontology-registry:ConvictingOffense .
                }
            }
        """
    ] .

cacontology-registry:AddressVerificationCrossReferenceShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegisteredSexOffender ;
    rdfs:label "Address Verification Cross Reference Shape"@en ;
    rdfs:comment "Validates that registered offenders have current address information."@en ;
    sh:sparql [
        sh:message "Registered sex offender must have at least one registered address."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this a cacontology-registry:RegisteredSexOffender .
                FILTER NOT EXISTS {
                    $this cacontology-registry:residesAt ?address .
                    ?address a cacontology-registry:RegisteredAddress .
                }
            }
        """
    ] .

# =============================================================================
# TEMPORAL VALIDATION SHAPES
# =============================================================================

cacontology-registry:TemporalValidationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegistrationRecord ,
                   cacontology-registry:VerificationCheck ,
                   cacontology-registry:RegistrationUpdate ;
    rdfs:label "Temporal Validation Shape"@en ;
    rdfs:comment "Validates temporal consistency for registry actions and records."@en ;
    sh:sparql [
        sh:message "Start time must be before end time when both are specified."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this uco-core:startTime ?start ;
                      uco-core:endTime ?end .
                FILTER (?start >= ?end)
            }
        """
    ] .

cacontology-registry:RegistrationDateValidationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegistrationRecord ;
    rdfs:label "Registration Date Validation Shape"@en ;
    rdfs:comment "Validates that registration dates are logical and consistent."@en ;
    sh:sparql [
        sh:message "Registration date must be after conviction date."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this a cacontology-registry:RegistrationRecord ;
                      cacontology-registry:registrationDate ?regDate ;
                      cacontology-registry:basedOnOffense ?offense .
                ?offense cacontology-registry:convictionDate ?convDate .
                FILTER (?regDate < ?convDate)
            }
        """
    ] ;
    sh:sparql [
        sh:message "Expiration date must be after registration date when specified."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this cacontology-registry:registrationDate ?regDate ;
                      cacontology-registry:expirationDate ?expDate .
                FILTER (?expDate <= ?regDate)
            }
        """
    ] .

# =============================================================================
# DATA QUALITY SHAPES
# =============================================================================

cacontology-registry:DataQualityShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegisteredSexOffender ,
                   cacontology-registry:RegistrationRecord ,
                   cacontology-registry:ConvictingOffense ;
    rdfs:label "Data Quality Shape"@en ;
    rdfs:comment "Validates data quality for registry entities."@en ;
    sh:property [
        sh:path uco-core:createdTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:message "Creation time should be specified when available."@en
    ] ;
    sh:property [
        sh:path uco-core:modifiedTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 0 ;
        sh:maxCount 1 ;
        sh:message "Modification time should be specified when available."@en
    ] .

cacontology-registry:RegistrationFrequencyValidationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegistrationRecord ;
    rdfs:label "Registration Frequency Validation Shape"@en ;
    rdfs:comment "Validates registration frequency requirements based on tier."@en ;
    sh:sparql [
        sh:message "Tier 1 offenders should register annually."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this cacontology-registry:tier 1 ;
                      cacontology-registry:registrationFrequency ?freq .
                FILTER (?freq != "annual")
            }
        """
    ] ;
    sh:sparql [
        sh:message "Tier 2 offenders should register semi-annually."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this cacontology-registry:tier 2 ;
                      cacontology-registry:registrationFrequency ?freq .
                FILTER (?freq != "semi-annual")
            }
        """
    ] ;
    sh:sparql [
        sh:message "Tier 3 offenders should register quarterly."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this cacontology-registry:tier 3 ;
                      cacontology-registry:registrationFrequency ?freq .
                FILTER (?freq != "quarterly")
            }
        """
    ] .

cacontology-registry:AgeValidationShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RegisteredSexOffender ;
    rdfs:label "Age Validation Shape"@en ;
    rdfs:comment "Validates age-related constraints for registered offenders."@en ;
    sh:sparql [
        sh:message "Offender must be at least 18 years old at time of registration."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this cacontology-registry:dateOfBirth ?dob ;
                      cacontology-registry:hasRegistrationRecord ?record .
                ?record cacontology-registry:registrationDate ?regDate .
                BIND(YEAR(?regDate) - YEAR(?dob) AS ?age)
                FILTER (?age < 18)
            }
        """
    ] .

# =============================================================================
# RECIDIVISM SHAPES
# =============================================================================

cacontology-registry:RecidivistSexOffenderShape rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RecidivistSexOffender ;
    rdfs:label "Recidivist Sex Offender Shape"@en ;
    rdfs:comment "Validation shape for recidivist sex offenders, ensuring post-registration offending events can be linked for analytics (e.g., Utah Christensen Garfield County CSAM case)."@en ;
    sh:property [
        sh:path cacontology-registry:postRegistrationEvent ;
        sh:class cacontology:ChildSexualAbuseEvent ;
        sh:minCount 0 ;
        sh:message "Post-registration events must be modeled as ChildSexualAbuseEvent (or subclasses) when present."@en
    ] .

cacontology-registry:RecidivismPostRegistrationBusinessRule rdf:type sh:NodeShape ;
    sh:targetClass cacontology-registry:RecidivistSexOffender ;
    rdfs:label "Recidivism Post-Registration Business Rule"@en ;
    rdfs:comment "Business rule: a recidivist sex offender SHOULD have at least one postRegistrationEvent linked to post-registration abuse/CSAM incidents."@en ;
    sh:sparql [
        sh:message "Recidivist sex offenders should have at least one postRegistrationEvent representing post-registration abuse or CSAM incidents."@en ;
        sh:select """
            SELECT $this
            WHERE {
                $this a cacontology-registry:RecidivistSexOffender .
                FILTER NOT EXISTS {
                    $this cacontology-registry:postRegistrationEvent ?event .
                }
            }
        """
    ] .