@prefix re: <https://ontology.inferal.com/modules/commerce-refund-evidence/> .
@prefix ctx: <https://ontology.inferal.com/modules/commerce-transaction/> .
@prefix rng: <https://ontology.inferal.com/modules/range/> .
@prefix time: <http://www.w3.org/2006/time#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix mod: <https://w3id.org/mod#> .
@prefix vann: <http://purl.org/vocab/vann/> .
@prefix pubacc: <https://ontology.inferal.com/modules/publication-access/> .
re:CohortShape a sh:NodeShape ; sh:targetClass re:Cohort ; sh:nodeKind sh:IRI ; sh:property [ sh:path re:merchantScope ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:policy ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; sh:class re:EligibilityPolicy ; sh:node re:EligibilityPolicyShape ] ;
 sh:property [ sh:path re:placementWindow ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:cutoff ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:selectionEvidence ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:orderSelectionCoverageEvidence ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:selectedOrder ; sh:minCount 0 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:cutoffInclusive ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:boolean ] ;
 sh:property [ sh:path re:orderSelectionCompleteness ; sh:minCount 1 ; sh:maxCount 1 ; sh:in (re:DeclaredComplete re:DeclaredPartial re:Unassessed) ] ;
 sh:property [ sh:path re:revisionStanding ; sh:minCount 1 ; sh:maxCount 1 ; sh:hasValue re:SelectedRepresentation ] .
re:CohortShape sh:property [ sh:path rdf:type ; sh:maxCount 1 ; sh:hasValue re:Cohort ] .
re:EligibilityPolicyShape a sh:NodeShape ; sh:targetClass re:EligibilityPolicy ; sh:nodeKind sh:IRI ; sh:property [ sh:path re:policyEvidence ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:currencySupportEvidence ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:signedValueEvidence ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:prePurchaseEvidence ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:orderNamespace ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:refundNamespace ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:policyVersion ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; sh:minLength 1 ] ;
 sh:property [ sh:path re:denominatorBasis ; sh:minCount 1 ; sh:maxCount 1 ; sh:hasValue re:FullBuyerCharge ] ;
 sh:property [ sh:path re:identityStanding ; sh:minCount 1 ; sh:maxCount 1 ; sh:hasValue re:SuppliedDeclaration ] ;
 sh:property [ sh:path re:allowSigned ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:boolean ] ;
 sh:property [ sh:path re:allowPrePurchase ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:boolean ] ;
 sh:property [ sh:path re:recognisedPayment ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:recognisedLifecycle ; sh:minCount 1 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:admittedPayment ; sh:minCount 0 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:admittedLifecycle ; sh:minCount 0 ; sh:nodeKind sh:IRI ] ;
 sh:property [ sh:path re:supportedCurrency ; sh:minCount 1 ; sh:datatype xsd:string ; sh:pattern "^[A-Z]{3}$" ] .
re:EligibilityPolicyShape sh:property [ sh:path rdf:type ; sh:maxCount 1 ; sh:hasValue re:EligibilityPolicy ] .
re:CohortShape sh:property [ sh:path re:placementWindow ; sh:node re:WindowShape ] ; sh:property [ sh:path re:cutoff ; sh:node re:InstantShape ] .
re:EligibilityPolicyShape sh:sparql [ sh:message "Admitted states must be recognised" ; sh:select "PREFIX re: <https://ontology.inferal.com/modules/commerce-refund-evidence/> SELECT $this WHERE { $this re:admittedPayment ?state . FILTER NOT EXISTS { $this re:recognisedPayment ?state } }" ] .
re:EligibilityPolicyShape sh:sparql [ sh:message "Admitted states must be recognised" ; sh:select "PREFIX re: <https://ontology.inferal.com/modules/commerce-refund-evidence/> SELECT $this WHERE { $this re:admittedLifecycle ?state . FILTER NOT EXISTS { $this re:recognisedLifecycle ?state } }" ] .
re:WindowShape a sh:NodeShape ; sh:nodeKind sh:IRI ; sh:property [ sh:path rng:lowerBound ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; sh:node re:InstantShape ] ; sh:property [ sh:path rng:upperBound ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; sh:node re:InstantShape ] ; sh:property [ sh:path rng:lowerInclusive ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:boolean ] ; sh:property [ sh:path rng:upperInclusive ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:boolean ] .
re:InstantShape a sh:NodeShape ; sh:nodeKind sh:IRI ; sh:property [ sh:path time:inXSDDateTimeStamp ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:dateTimeStamp ; sh:pattern "^[0-9]{4}-[0-9]{2}-[0-9]{2}T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,6}0*)?(Z|[+-](0[0-9]|1[0-3]):[0-5][0-9]|[+-]14:00)$" ; sh:not [ sh:pattern "^0000-" ] ] .
re:InstantShape sh:sparql [ sh:message "Invalid Gregorian calendar instant" ; sh:select "PREFIX time: <http://www.w3.org/2006/time#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> SELECT $this WHERE { $this time:inXSDDateTimeStamp ?stamp . BIND(xsd:dateTime(STR(?stamp)) AS ?cast) FILTER(!BOUND(?cast)) }" ] .
re:WindowShape sh:sparql [ sh:message "Placement lower bound exceeds upper bound" ; sh:select "PREFIX rng: <https://ontology.inferal.com/modules/range/> PREFIX time: <http://www.w3.org/2006/time#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> SELECT $this WHERE { $this rng:lowerBound/time:inXSDDateTimeStamp ?lower ; rng:upperBound/time:inXSDDateTimeStamp ?upper . FILTER(xsd:dateTime(STR(?lower)) > xsd:dateTime(STR(?upper))) }" ] .
re:CohortShape sh:sparql [ sh:message "Cutoff precedes placement-window upper bound" ; sh:select "PREFIX re: <https://ontology.inferal.com/modules/commerce-refund-evidence/> PREFIX rng: <https://ontology.inferal.com/modules/range/> PREFIX time: <http://www.w3.org/2006/time#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> SELECT $this WHERE { $this re:placementWindow/rng:upperBound/time:inXSDDateTimeStamp ?upper ; re:cutoff/time:inXSDDateTimeStamp ?cutoff . FILTER(xsd:dateTime(STR(?cutoff)) < xsd:dateTime(STR(?upper))) }" ] .

# Validate Gregorian fields independently of a query engine's datatype-cast treatment.
re:InstantShape sh:sparql [ sh:message "Gregorian day is outside the declared month" ; sh:select """
PREFIX time: <http://www.w3.org/2006/time#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT $this WHERE {
 $this time:inXSDDateTimeStamp ?stamp .
 BIND(xsd:integer(SUBSTR(STR(?stamp), 1, 4)) AS ?year)
 BIND(xsd:integer(SUBSTR(STR(?stamp), 6, 2)) AS ?month)
 BIND(xsd:integer(SUBSTR(STR(?stamp), 9, 2)) AS ?day)
 BIND(?year / 400 = FLOOR(?year / 400) || (?year / 4 = FLOOR(?year / 4) && ?year / 100 != FLOOR(?year / 100)) AS ?leap)
 BIND(IF(?month = 2, IF(?leap, 29, 28), IF(?month IN (4,6,9,11), 30, 31)) AS ?days)
 FILTER(?year < 1 || ?year > 9999 || ?month < 1 || ?month > 12 || ?day < 1 || ?day > ?days)
}
""" ] .
