@prefix : <https://ontology.inferal.com/modules/competency-question/> .
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix mod: <https://w3id.org/mod#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix vann: <http://purl.org/vocab/vann/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<https://ontology.inferal.com/modules/competency-question/shapes>
    a owl:Ontology ;
    dcterms:title "Competency Question Ontology SHACL shapes" ;
    dcterms:description "Closed-world structural constraints for complete competency-question records, ordered templates, qualified instantiations and bindings, and operational evidence." ;
    dcterms:license <https://creativecommons.org/licenses/by/4.0/> ;
    dcterms:source : ;
    vann:preferredNamespacePrefix "cqo" ;
    vann:preferredNamespaceUri "https://ontology.inferal.com/modules/competency-question/" ;
    rdfs:label "Competency Question Ontology SHACL shapes" ;
    rdfs:comment "These shapes validate graph completeness and cross-resource consistency. They neither execute query text nor assert that an expected answer is semantically true." ;
    owl:imports : ;
    owl:versionInfo "0.1.0" .

:CompetencyQuestionShape
    a sh:NodeShape ;
    rdfs:label "competency question shape" ;
    rdfs:comment "Requires a complete first-class competency-question record while leaving executable formalization optional for questions that cannot be safely queried." ;
    sh:targetClass :CompetencyQuestion ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :hasFormulation ;
        sh:class :QuestionFormulation ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :addressesRequirement ;
        sh:class :OntologyRequirement ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :supportsUseCase ;
        sh:class :UseCase ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :hasStakeholder ;
        sh:class prov:Agent ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :questionKind ;
        sh:class :CompetencyQuestionKind ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :questionPurpose ;
        sh:class :QuestionPurpose ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :targetsArtifact ;
        sh:class mod:SemanticArtefact ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :targetsVersion ;
        sh:class mod:SemanticArtefact ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :hasAnswerExpectation ;
        sh:class :AnswerExpectation ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :hasFormalQuery ;
        sh:class :FormalQuery ;
    ] ;
    sh:property [
        sh:path :hasTemplateInstantiation ;
        sh:class :TemplateInstantiation ;
    ] ;
    sh:property [
        sh:path :lifecycleState ;
        sh:class :LifecycleState ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :reviewState ;
        sh:class :ReviewState ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "A targetsVersion resource must identify the targetsArtifact resource through dcterms:isVersionOf." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            PREFIX dcterms: <http://purl.org/dc/terms/>
            SELECT $this ?version ?artifact WHERE {
                $this :targetsArtifact ?artifact ;
                      :targetsVersion ?version .
                FILTER NOT EXISTS { ?version dcterms:isVersionOf ?artifact . }
            }
        """ ;
    ] .

:QuestionSetShape
    a sh:NodeShape ;
    rdfs:label "question set shape" ;
    rdfs:comment "Requires a named question set to contain questions and declare its lifecycle purpose and stakeholder." ;
    sh:targetClass :QuestionSet ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :containsQuestion ;
        sh:class :CompetencyQuestion ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :questionPurpose ;
        sh:class :QuestionPurpose ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :hasStakeholder ;
        sh:class prov:Agent ;
        sh:minCount 1 ;
    ] .

:QuestionFormulationShape
    a sh:NodeShape ;
    rdfs:label "question formulation shape" ;
    rdfs:comment "Requires exactly one language-tagged natural-language text for each formulation resource." ;
    sh:targetClass :QuestionFormulation ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :formulationText ;
        sh:datatype rdf:langString ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:uniqueLang true ;
    ] .

:OntologyRequirementShape
    a sh:NodeShape ;
    rdfs:label "ontology requirement shape" ;
    rdfs:comment "Requires each ontology requirement to carry a language-tagged requirement statement." ;
    sh:targetClass :OntologyRequirement ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :requirementText ;
        sh:datatype rdf:langString ;
        sh:minCount 1 ;
    ] .

:UseCaseShape
    a sh:NodeShape ;
    rdfs:label "use case shape" ;
    rdfs:comment "Requires each use case to state its stakeholder-relevant situation or intended decision." ;
    sh:targetClass :UseCase ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :useCaseText ;
        sh:datatype rdf:langString ;
        sh:minCount 1 ;
    ] .

:QuestionTemplateShape
    a sh:NodeShape ;
    rdfs:label "question template shape" ;
    rdfs:comment "Requires one human-readable rendering, an extractable ordered element sequence, and resource-identified parameters owned and used by the same template." ;
    sh:targetClass :QuestionTemplate ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :templateText ;
        sh:datatype rdf:langString ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :hasTemplateElement ;
        sh:class :TemplateElement ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :hasTemplateParameter ;
        sh:class :TemplateParameter ;
    ] ;
    sh:sparql [
        sh:message "Template element indices must be unique and contiguous from zero." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this WHERE {
                {
                    $this :hasTemplateElement ?left, ?right .
                    ?left :elementIndex ?index .
                    ?right :elementIndex ?index .
                    FILTER(?left != ?right)
                }
                UNION
                {
                    $this :hasTemplateElement ?element .
                    ?element :elementIndex ?index .
                    FILTER(?index > 0)
                    FILTER NOT EXISTS {
                        $this :hasTemplateElement ?previous .
                        ?previous :elementIndex ?previousIndex .
                        FILTER(?previousIndex = ?index - 1)
                    }
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Template text segments must use the same language tag as the template rendering." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?element ?templateText ?segmentText WHERE {
                $this :templateText ?templateText ;
                      :hasTemplateElement ?element .
                ?element a :TemplateTextSegment ;
                         :segmentText ?segmentText .
                FILTER(LANG(?templateText) != LANG(?segmentText))
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Template parameter names must be unique within one template." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?parameterName ?left ?right WHERE {
                $this :hasTemplateParameter ?left, ?right .
                ?left :parameterName ?parameterName .
                ?right :parameterName ?parameterName .
                FILTER(?left != ?right)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every template parameter must be used by at least one slot occurrence owned by the same template." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?parameter WHERE {
                $this :hasTemplateParameter ?parameter .
                FILTER NOT EXISTS {
                    $this :hasTemplateElement ?slot .
                    ?slot a :TemplateSlot ;
                          :usesParameter ?parameter .
                }
            }
        """ ;
    ] .

:TemplateElementShape
    a sh:NodeShape ;
    rdfs:label "template element shape" ;
    rdfs:comment "Requires each element occurrence to belong to exactly one template, carry one zero-based index, and be exactly one of a text segment or slot under the validation regime." ;
    sh:targetClass :TemplateElement ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path [ sh:inversePath :hasTemplateElement ] ;
        sh:class :QuestionTemplate ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :elementIndex ;
        sh:datatype xsd:nonNegativeInteger ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:xone (
        [ sh:class :TemplateTextSegment ]
        [ sh:class :TemplateSlot ]
    ) .

:TemplateTextSegmentShape
    a sh:NodeShape ;
    rdfs:label "template text segment shape" ;
    rdfs:comment "Requires one language-tagged fixed text value and prohibits slot-specific content on a template text segment." ;
    sh:targetClass :TemplateTextSegment ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :segmentText ;
        sh:datatype rdf:langString ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [ sh:path :usesParameter ; sh:maxCount 0 ] .

:TemplateSlotShape
    a sh:NodeShape ;
    rdfs:label "template slot shape" ;
    rdfs:comment "Requires a replaceable slot occurrence to use one template parameter owned by the same template and prohibits fixed segment text." ;
    sh:targetClass :TemplateSlot ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :usesParameter ;
        sh:class :TemplateParameter ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [ sh:path :segmentText ; sh:maxCount 0 ] ;
    sh:sparql [
        sh:message "A slot occurrence must use a parameter owned by the same template as the slot." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?template ?parameter WHERE {
                ?template :hasTemplateElement $this .
                $this :usesParameter ?parameter .
                FILTER NOT EXISTS { ?template :hasTemplateParameter ?parameter . }
            }
        """ ;
    ] .

:TemplateParameterShape
    a sh:NodeShape ;
    rdfs:label "template parameter shape" ;
    rdfs:comment "Requires each logical parameter to belong to one template, carry one non-empty local name, and be used by at least one slot occurrence in that template." ;
    sh:targetClass :TemplateParameter ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path [ sh:inversePath :hasTemplateParameter ] ;
        sh:class :QuestionTemplate ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :parameterName ;
        sh:datatype xsd:string ;
        sh:minLength 1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :parameterRole ;
        sh:nodeKind sh:IRI ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path [ sh:inversePath :usesParameter ] ;
        sh:class :TemplateSlot ;
        sh:minCount 1 ;
    ] .

:TemplateInstantiationShape
    a sh:NodeShape ;
    rdfs:label "template instantiation shape" ;
    rdfs:comment "Requires a qualified template realization to belong to one question, identify one template and one of that question's formulations, and bind exactly the template's declared parameters." ;
    sh:targetClass :TemplateInstantiation ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path [ sh:inversePath :hasTemplateInstantiation ] ;
        sh:class :CompetencyQuestion ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :usesTemplate ;
        sh:class :QuestionTemplate ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :realizesFormulation ;
        sh:class :QuestionFormulation ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :hasTemplateBinding ;
        sh:class :TemplateBinding ;
    ] ;
    sh:sparql [
        sh:message "A template instantiation must realize a formulation owned by its competency question." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?question ?formulation WHERE {
                ?question :hasTemplateInstantiation $this .
                $this :realizesFormulation ?formulation .
                FILTER NOT EXISTS { ?question :hasFormulation ?formulation . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A template instantiation must bind every declared template parameter exactly once and no undeclared parameter." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?parameter WHERE {
                $this :usesTemplate ?template .
                {
                    ?template :hasTemplateParameter ?parameter .
                    FILTER NOT EXISTS {
                        $this :hasTemplateBinding ?binding .
                        ?binding :bindsParameter ?parameter .
                    }
                }
                UNION
                {
                    $this :hasTemplateBinding ?binding .
                    ?binding :bindsParameter ?parameter .
                    FILTER NOT EXISTS { ?template :hasTemplateParameter ?parameter . }
                }
                UNION
                {
                    $this :hasTemplateBinding ?left, ?right .
                    ?left :bindsParameter ?parameter .
                    ?right :bindsParameter ?parameter .
                    FILTER(?left != ?right)
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Template rendering, realized formulation, fixed segments, and binding text must use one language tag." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?templateText ?otherText WHERE {
                $this :usesTemplate ?template ;
                      :realizesFormulation ?formulation .
                ?template :templateText ?templateText .
                {
                    ?formulation :formulationText ?otherText .
                }
                UNION
                {
                    ?template :hasTemplateElement ?segment .
                    ?segment a :TemplateTextSegment ;
                             :segmentText ?otherText .
                }
                UNION
                {
                    $this :hasTemplateBinding ?binding .
                    ?binding :bindingText ?otherText .
                }
                FILTER(LANG(?templateText) != LANG(?otherText))
            }
        """ ;
    ] .

:TemplateBindingShape
    a sh:NodeShape ;
    rdfs:label "template binding shape" ;
    rdfs:comment "Requires one owning instantiation, one bound parameter, one language-tagged rendering, and exactly one semantic value represented as either an IRI or a literal." ;
    sh:targetClass :TemplateBinding ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path [ sh:inversePath :hasTemplateBinding ] ;
        sh:class :TemplateInstantiation ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :bindsParameter ;
        sh:class :TemplateParameter ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :bindingText ;
        sh:datatype rdf:langString ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:xone (
        [
            sh:property [
                sh:path :boundResource ;
                sh:nodeKind sh:IRI ;
                sh:minCount 1 ;
                sh:maxCount 1 ;
            ] ;
            sh:property [ sh:path :boundLiteral ; sh:maxCount 0 ] ;
        ]
        [
            sh:property [
                sh:path :boundLiteral ;
                sh:nodeKind sh:Literal ;
                sh:minCount 1 ;
                sh:maxCount 1 ;
            ] ;
            sh:property [ sh:path :boundResource ; sh:maxCount 0 ] ;
        ]
    ) .

:FormalQueryShape
    a sh:NodeShape ;
    rdfs:label "formal query shape" ;
    rdfs:comment "Requires a formal query to declare executable text, language, and at least one fixed fixture." ;
    sh:targetClass :FormalQuery ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :queryText ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :queryLanguage ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :usesFixture ;
        sh:class :TestFixture ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path dcat:downloadURL ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] .

:TestFixtureShape
    a sh:NodeShape ;
    rdfs:label "test fixture shape" ;
    rdfs:comment "Requires a fixture to identify one retrievable representation and media type." ;
    sh:targetClass :TestFixture ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path dcat:downloadURL ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path dcterms:format ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] .

:AnswerExpectationShape
    a sh:NodeShape ;
    rdfs:label "answer expectation shape" ;
    rdfs:comment "Provides shared IRI identity validation for concrete answers and answer constraints." ;
    sh:targetClass :AnswerExpectation ;
    sh:nodeKind sh:IRI .

:ExpectedAnswerShape
    a sh:NodeShape ;
    rdfs:label "expected answer shape" ;
    rdfs:comment "Requires an expected answer to provide natural-language text, an answer resource, or both." ;
    sh:targetClass :ExpectedAnswer ;
    sh:nodeKind sh:IRI ;
    sh:or (
        [
            sh:property [
                sh:path :answerText ;
                sh:datatype rdf:langString ;
                sh:minCount 1 ;
            ] ;
        ]
        [
            sh:property [
                sh:path :answerResource ;
                sh:nodeKind sh:IRI ;
                sh:minCount 1 ;
            ] ;
        ]
    ) .

:AnswerConstraintShape
    a sh:NodeShape ;
    rdfs:label "answer constraint shape" ;
    rdfs:comment "Requires answer constraints to declare one expression and one interpretation language." ;
    sh:targetClass :AnswerConstraint ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :constraintExpression ;
        sh:datatype xsd:string ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :constraintLanguage ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "A result-cardinality constraint expression must be a non-negative integer lexical form." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?expression WHERE {
                $this :constraintLanguage :ResultCardinalityConstraintLanguage ;
                      :constraintExpression ?expression .
                FILTER(!REGEX(STR(?expression), "^[0-9]+$"))
            }
        """ ;
    ] .

:QueryExecutionShape
    a sh:NodeShape ;
    rdfs:label "query execution shape" ;
    rdfs:comment "Requires a concrete execution to identify its query, evaluated questions, fixture, completion time, and generated result." ;
    sh:targetClass :QueryExecution ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :executionOf ;
        sh:class :FormalQuery ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :evaluatesQuestion ;
        sh:class :CompetencyQuestion ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :usesFixture ;
        sh:class :TestFixture ;
        sh:minCount 1 ;
    ] ;
    sh:property [
        sh:path :hasExecutionResult ;
        sh:class :ExecutionResult ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path prov:endedAtTime ;
        sh:datatype xsd:dateTime ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path prov:used ;
        sh:qualifiedValueShape [
            sh:class mod:SemanticArtefact ;
        ] ;
        sh:qualifiedMinCount 1 ;
    ] ;
    sh:sparql [
        sh:message "Every evaluated question must link to the query named by executionOf through hasFormalQuery." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?question ?query WHERE {
                $this :executionOf ?query ;
                      :evaluatesQuestion ?question .
                FILTER NOT EXISTS { ?question :hasFormalQuery ?query . }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "Every hasExecutionResult resource must identify this execution through prov:wasGeneratedBy." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?result WHERE {
                $this :hasExecutionResult ?result .
                FILTER NOT EXISTS { ?result prov:wasGeneratedBy $this . }
            }
        """ ;
    ] .

:ExecutionResultShape
    a sh:NodeShape ;
    rdfs:label "execution result shape" ;
    rdfs:comment "Requires each execution result to identify its generating execution and one controlled status." ;
    sh:targetClass :ExecutionResult ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :executionStatus ;
        sh:class :ExecutionStatus ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path prov:wasGeneratedBy ;
        sh:class :QueryExecution ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :resultMessage ;
        sh:datatype xsd:string ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :resultCardinality ;
        sh:datatype xsd:nonNegativeInteger ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :resultBoolean ;
        sh:datatype xsd:boolean ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "A passed or failed execution result must record a machine-observed cardinality or boolean value." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?status WHERE {
                $this :executionStatus ?status .
                FILTER(?status IN (:PassedStatus, :FailedStatus))
                FILTER NOT EXISTS {
                    $this (:resultCardinality|:resultBoolean) ?observation .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A not-run or errored execution result must not claim a machine-observed cardinality or boolean value." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?status ?observation WHERE {
                $this :executionStatus ?status ;
                      (:resultCardinality|:resultBoolean) ?observation .
                FILTER(?status IN (:NotRunStatus, :ErrorStatus))
            }
        """ ;
    ] .

:ExpectationAssessmentShape
    a sh:NodeShape ;
    rdfs:label "expectation assessment shape" ;
    rdfs:comment "Requires each qualified assessment to compare one execution result with one answer expectation and to record one boolean judgement." ;
    sh:targetClass :ExpectationAssessment ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :assessesResult ;
        sh:class :ExecutionResult ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :assessesExpectation ;
        sh:class :AnswerExpectation ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :expectationSatisfied ;
        sh:datatype xsd:boolean ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:sparql [
        sh:message "The assessed expectation must belong to a question evaluated by the execution that generated the assessed result." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            PREFIX prov: <http://www.w3.org/ns/prov#>
            SELECT $this ?result ?expectation WHERE {
                $this :assessesResult ?result ;
                      :assessesExpectation ?expectation .
                ?result prov:wasGeneratedBy ?execution .
                FILTER NOT EXISTS {
                    ?execution :evaluatesQuestion ?question .
                    ?question :hasAnswerExpectation ?expectation .
                }
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A result-cardinality assessment must agree with the observed result cardinality and the constraint expression." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
            SELECT $this ?observed ?expected ?stated WHERE {
                $this :assessesResult ?result ;
                      :assessesExpectation ?expectation ;
                      :expectationSatisfied ?stated .
                ?expectation :constraintLanguage :ResultCardinalityConstraintLanguage ;
                             :constraintExpression ?expression .
                ?result :resultCardinality ?observed .
                BIND(xsd:integer(?expression) AS ?expected)
                BIND(?observed = ?expected AS ?computed)
                FILTER(?stated != ?computed)
            }
        """ ;
    ] ;
    sh:sparql [
        sh:message "A result-cardinality assessment requires an observed result cardinality; absence is unknown, not zero." ;
        sh:select """
            PREFIX : <https://ontology.inferal.com/modules/competency-question/>
            SELECT $this ?result WHERE {
                $this :assessesResult ?result ;
                      :assessesExpectation ?expectation .
                ?expectation :constraintLanguage :ResultCardinalityConstraintLanguage .
                FILTER NOT EXISTS { ?result :resultCardinality ?observed . }
            }
        """ ;
    ] .

:CoverageAssertionShape
    a sh:NodeShape ;
    rdfs:label "coverage assertion shape" ;
    rdfs:comment "Requires a qualified coverage assertion to name one question, one covered resource, and one coverage status." ;
    sh:targetClass :CoverageAssertion ;
    sh:nodeKind sh:IRI ;
    sh:property [
        sh:path :coverageQuestion ;
        sh:class :CompetencyQuestion ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :coveredResource ;
        sh:nodeKind sh:IRI ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] ;
    sh:property [
        sh:path :coverageStatus ;
        sh:class :CoverageStatus ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
    ] .

:CompetencyQuestionKindShape
    a sh:NodeShape ;
    rdfs:label "competency question kind shape" ;
    rdfs:comment "Requires competency-question kind values to be identified by IRIs." ;
    sh:targetClass :CompetencyQuestionKind ;
    sh:nodeKind sh:IRI .

:QuestionPurposeShape
    a sh:NodeShape ;
    rdfs:label "question purpose shape" ;
    rdfs:comment "Requires question-purpose values to be identified by IRIs." ;
    sh:targetClass :QuestionPurpose ;
    sh:nodeKind sh:IRI .

:LifecycleStateShape
    a sh:NodeShape ;
    rdfs:label "lifecycle state shape" ;
    rdfs:comment "Requires lifecycle-state values to be identified by IRIs." ;
    sh:targetClass :LifecycleState ;
    sh:nodeKind sh:IRI .

:ReviewStateShape
    a sh:NodeShape ;
    rdfs:label "review state shape" ;
    rdfs:comment "Requires review-state values to be identified by IRIs." ;
    sh:targetClass :ReviewState ;
    sh:nodeKind sh:IRI .

:ExecutionStatusShape
    a sh:NodeShape ;
    rdfs:label "execution status shape" ;
    rdfs:comment "Requires execution-status values to be identified by IRIs." ;
    sh:targetClass :ExecutionStatus ;
    sh:nodeKind sh:IRI .

:CoverageStatusShape
    a sh:NodeShape ;
    rdfs:label "coverage status shape" ;
    rdfs:comment "Requires coverage-status values to be identified by IRIs." ;
    sh:targetClass :CoverageStatus ;
    sh:nodeKind sh:IRI .
